From 0686db3bd36909cc3b322415f8e4373fc4fa3f10 Mon Sep 17 00:00:00 2001 From: justint3ng Date: Sun, 12 Oct 2025 13:28:19 +1000 Subject: [PATCH 01/15] initial checking out the topic-recognition branch within your fork --- .idea/.gitignore | 3 +++ .idea/PatternAnalysis-2025.iml | 8 ++++++++ .idea/inspectionProfiles/Project_Default.xml | 12 ++++++++++++ .idea/inspectionProfiles/profiles_settings.xml | 6 ++++++ .idea/misc.xml | 4 ++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ 7 files changed, 47 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/PatternAnalysis-2025.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/PatternAnalysis-2025.iml b/.idea/PatternAnalysis-2025.iml new file mode 100644 index 000000000..d0876a78d --- /dev/null +++ b/.idea/PatternAnalysis-2025.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..0318588e8 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..105ce2da2 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..dc9ea4906 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..33e468890 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From e20a387c6111600e55041eb5e8a5f970b21984b8 Mon Sep 17 00:00:00 2001 From: justint3ng Date: Sun, 12 Oct 2025 13:38:59 +1000 Subject: [PATCH 02/15] adding files added different .py files according to the task sheet and added a README.md file --- recognition/GAN_model_47508042/README.md | 0 recognition/GAN_model_47508042/dataset.py | 0 recognition/GAN_model_47508042/modules.py | 0 recognition/GAN_model_47508042/predict.py | 0 recognition/GAN_model_47508042/train.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recognition/GAN_model_47508042/README.md create mode 100644 recognition/GAN_model_47508042/dataset.py create mode 100644 recognition/GAN_model_47508042/modules.py create mode 100644 recognition/GAN_model_47508042/predict.py create mode 100644 recognition/GAN_model_47508042/train.py diff --git a/recognition/GAN_model_47508042/README.md b/recognition/GAN_model_47508042/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/recognition/GAN_model_47508042/dataset.py b/recognition/GAN_model_47508042/dataset.py new file mode 100644 index 000000000..e69de29bb diff --git a/recognition/GAN_model_47508042/modules.py b/recognition/GAN_model_47508042/modules.py new file mode 100644 index 000000000..e69de29bb diff --git a/recognition/GAN_model_47508042/predict.py b/recognition/GAN_model_47508042/predict.py new file mode 100644 index 000000000..e69de29bb diff --git a/recognition/GAN_model_47508042/train.py b/recognition/GAN_model_47508042/train.py new file mode 100644 index 000000000..e69de29bb From ed69a15a074da7ba58f7da7cefe080f1b712e76d Mon Sep 17 00:00:00 2001 From: justint3ng Date: Sun, 12 Oct 2025 13:40:00 +1000 Subject: [PATCH 03/15] renaming folder renamed the folder from GAN_model_47508042 to GANmodel_47508042 --- recognition/{GAN_model_47508042 => GANmodel_47508042}/README.md | 0 recognition/{GAN_model_47508042 => GANmodel_47508042}/dataset.py | 0 recognition/{GAN_model_47508042 => GANmodel_47508042}/modules.py | 0 recognition/{GAN_model_47508042 => GANmodel_47508042}/predict.py | 0 recognition/{GAN_model_47508042 => GANmodel_47508042}/train.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename recognition/{GAN_model_47508042 => GANmodel_47508042}/README.md (100%) rename recognition/{GAN_model_47508042 => GANmodel_47508042}/dataset.py (100%) rename recognition/{GAN_model_47508042 => GANmodel_47508042}/modules.py (100%) rename recognition/{GAN_model_47508042 => GANmodel_47508042}/predict.py (100%) rename recognition/{GAN_model_47508042 => GANmodel_47508042}/train.py (100%) diff --git a/recognition/GAN_model_47508042/README.md b/recognition/GANmodel_47508042/README.md similarity index 100% rename from recognition/GAN_model_47508042/README.md rename to recognition/GANmodel_47508042/README.md diff --git a/recognition/GAN_model_47508042/dataset.py b/recognition/GANmodel_47508042/dataset.py similarity index 100% rename from recognition/GAN_model_47508042/dataset.py rename to recognition/GANmodel_47508042/dataset.py diff --git a/recognition/GAN_model_47508042/modules.py b/recognition/GANmodel_47508042/modules.py similarity index 100% rename from recognition/GAN_model_47508042/modules.py rename to recognition/GANmodel_47508042/modules.py diff --git a/recognition/GAN_model_47508042/predict.py b/recognition/GANmodel_47508042/predict.py similarity index 100% rename from recognition/GAN_model_47508042/predict.py rename to recognition/GANmodel_47508042/predict.py diff --git a/recognition/GAN_model_47508042/train.py b/recognition/GANmodel_47508042/train.py similarity index 100% rename from recognition/GAN_model_47508042/train.py rename to recognition/GANmodel_47508042/train.py From 2a47d329f611dbaab3e76a30145c0156d9b55c6d Mon Sep 17 00:00:00 2001 From: justint3ng Date: Sun, 12 Oct 2025 15:54:15 +1000 Subject: [PATCH 04/15] updated modules.py added imports and a rough outline for the encoder and decoder class to modules.py --- recognition/GANmodel_47508042/modules.py | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/recognition/GANmodel_47508042/modules.py b/recognition/GANmodel_47508042/modules.py index e69de29bb..0fa15492b 100644 --- a/recognition/GANmodel_47508042/modules.py +++ b/recognition/GANmodel_47508042/modules.py @@ -0,0 +1,32 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +class Encoder(nn.Module): + def __init__(self, in_ch=1, hidden=128, z_channels=64): + super().__init__() + self.enc = nn.Sequential( + nn.Conv2d(in_ch, hidden//2, 4, 2, 1), + nn.ReLU(True), + nn.Conv2d(hidden//2, hidden, 4, 2, 1), + nn.ReLU(True), + nn.Conv2d(hidden, z_channels, 3, 1, 1), + ) + + def forward(self, x): + return self.enc(x) + +class Decoder(nn.Module): + def __init__(self, out_ch=1, hidden=128, z_channels=64): + super().__init__() + self.dec = nn.Sequential( + nn.Conv2d(z_channels, hidden, 3, 1, 1), + nn.ReLU(True), + nn.ConvTranspose2d(hidden, hidden//2, 4, 2, 1), + nn.ReLU(True), + nn.ConvTranspose2d(hidden//2, out_ch, 4, 2, 1), + nn.Sigmoid() + ) + + def forward(self, z): + return self.dec(z) \ No newline at end of file From dcdca518df38e0b3db1dced0973d39405dfcc18d Mon Sep 17 00:00:00 2001 From: justint3ng Date: Sun, 12 Oct 2025 17:35:51 +1000 Subject: [PATCH 05/15] readme created a rough readme template to be used at a later time --- recognition/GANmodel_47508042/README.md | 15 +++++++++++++++ recognition/GANmodel_47508042/modules.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/recognition/GANmodel_47508042/README.md b/recognition/GANmodel_47508042/README.md index e69de29bb..082a92e42 100644 --- a/recognition/GANmodel_47508042/README.md +++ b/recognition/GANmodel_47508042/README.md @@ -0,0 +1,15 @@ +# Pattern Recognition - Generative Model using VQVAE + +This project uses VQVAE to create a generative model of the HipMRI Study on Prostate Cancer. Accuracy of this model is +measured to be over 0.6. + +# Author +* Justin Teng - 47508042 + +# Table of Contents +1. Project Highlights + + +# Project Highlights + +# Authors and Acknowledgements \ No newline at end of file diff --git a/recognition/GANmodel_47508042/modules.py b/recognition/GANmodel_47508042/modules.py index 0fa15492b..60729f473 100644 --- a/recognition/GANmodel_47508042/modules.py +++ b/recognition/GANmodel_47508042/modules.py @@ -12,7 +12,7 @@ def __init__(self, in_ch=1, hidden=128, z_channels=64): nn.ReLU(True), nn.Conv2d(hidden, z_channels, 3, 1, 1), ) - + def forward(self, x): return self.enc(x) From 9afadb9f165e947337e017656e2f680a8422e2e4 Mon Sep 17 00:00:00 2001 From: justint3ng Date: Tue, 14 Oct 2025 13:05:52 +1000 Subject: [PATCH 06/15] vectorquantizer created a rough template for a vector quantizer. --- recognition/GANmodel_47508042/modules.py | 46 +++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/recognition/GANmodel_47508042/modules.py b/recognition/GANmodel_47508042/modules.py index 60729f473..b812aac03 100644 --- a/recognition/GANmodel_47508042/modules.py +++ b/recognition/GANmodel_47508042/modules.py @@ -29,4 +29,48 @@ def __init__(self, out_ch=1, hidden=128, z_channels=64): ) def forward(self, z): - return self.dec(z) \ No newline at end of file + return self.dec(z) + +class VectorQuantizer(nn.Module): + def __init__(self, num_embeddings=512, embedding_dim=64, beta=0.25): + super().__init__() + self.num_embeddings = num_embeddings + self.embedding_dim = embedding_dim + self.beta = beta + + self.embedding = nn.Embedding(self.num_embeddings, self.embedding_dim) + nn.init.uniform_(self.embedding.weight, -1.0 / self.num_embeddings, 1.0 / self.num_embeddings) + + def forward(self, z): + z_perm = z.permute(0, 2, 3, 1).contagious() + flat_z = z_perm.view(-1, self.embedding_dim) + + # compute distances + distances = ( + torch.sum(flat_z**2, dim=1, keepdim=True) + + torch.sum(self.embedding.weight**2, dim=1) + - 2 * torch.matmul(flat_z, self.embedding.weight.t()) + ) + + encoding_indices = torch.argmin(distances, dim=1).unsqueeze(1) + encodings = torch.zeros(encoding_indices.size(0), self.num_embeddings, device=z.device) + encodings.scatter_(1, encoding_indices, 1) + + # quantized + quantized = torch.matmul(encodings, self.embeddings.weights) + quantized = quantized.view(z_perm.shape) + quantized = quantized.permute(0, 3, 1, 2).contiguous() + + # losses + e_latent_loss = F.mse_loss(quantized.detach(), z) + q_latent_loss = F.mse_loss(quantized, z.detach()) + loss = q_latent_loss + self.beta * e_latent_loss + + # estimator + quantized = z + (quantized - z).detach() + + # visualisation + encoding_indices = encoding_indices.view(z.shape[0], z.shape[2], z.shape[3]) + return quantized, loss, encoding_indices + + From 1aa41ed7033d9001cfdb3e3b25b1a809fa518634 Mon Sep 17 00:00:00 2001 From: justint3ng Date: Tue, 14 Oct 2025 16:22:51 +1000 Subject: [PATCH 07/15] VQVAE created a rough template for the VQVAE class --- recognition/GANmodel_47508042/modules.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/recognition/GANmodel_47508042/modules.py b/recognition/GANmodel_47508042/modules.py index b812aac03..a188f28f2 100644 --- a/recognition/GANmodel_47508042/modules.py +++ b/recognition/GANmodel_47508042/modules.py @@ -73,4 +73,17 @@ def forward(self, z): encoding_indices = encoding_indices.view(z.shape[0], z.shape[2], z.shape[3]) return quantized, loss, encoding_indices +class VQVAE(nn.Module): + def __init__(self, in_ch=1, hidden=128, z_channels=64, num_embeddings=512, embedding_dim=64, beta=0.25): + super().__init__() + assert z_channels == embedding_dim + self.encoder = Encoder(in_ch, hidden, z_channels) + self.vq = VectorQuantizer(num_embeddings=num_embeddings, embedding_dim=embedding_dim, beta=beta) + self.decoder = Decoder(in_ch, hidden, z_channels) + def forward(self, x): + z_e = self.encoder(x) + quantized, vq_loss, indices = self.vq(z_e) + x_recon = self.decoder(quantized) + return x_recon, vq_loss, indices + \ No newline at end of file From a0c4b3efdcacb1c7c47e5e77ffa073a09ff8d177 Mon Sep 17 00:00:00 2001 From: justint3ng Date: Wed, 15 Oct 2025 15:38:26 +1000 Subject: [PATCH 08/15] readme update updated the readme from the previous format as i realised i wont have too much considering i only have a couple files, hence i removed the contents part and author since i am the only one doing this project --- recognition/GANmodel_47508042/README.md | 28 +++++++++++++++--------- recognition/GANmodel_47508042/modules.py | 1 - 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/recognition/GANmodel_47508042/README.md b/recognition/GANmodel_47508042/README.md index 082a92e42..f5d3e43b3 100644 --- a/recognition/GANmodel_47508042/README.md +++ b/recognition/GANmodel_47508042/README.md @@ -1,15 +1,23 @@ # Pattern Recognition - Generative Model using VQVAE -This project uses VQVAE to create a generative model of the HipMRI Study on Prostate Cancer. Accuracy of this model is -measured to be over 0.6. +## Overview +This project uses VQVAE to create a generative model of the HipMRI Study on Prostate Cancer. The goal is to produce +reasonably clear images and reach SSIM > 0.6 on testing. -# Author -* Justin Teng - 47508042 +## Files + - 'dataset.py' - Encoder, Decoder, VectorQuantizer, VQVAE model + - 'modules.py' - 'HipMRIDataset' loader + - 'predict.py' - load best model and produce reconstructions + - 'train.py' - training loop, validation, checkpointing, plots -# Table of Contents -1. Project Highlights +## Requirements +- Python 3.8+ +- PyTorch +- torchvision +- matplotlib +- scikit-image +- numpy - -# Project Highlights - -# Authors and Acknowledgements \ No newline at end of file +Install: +```bash +pip install torch torchvision matplotlib scikit-image numpy \ No newline at end of file diff --git a/recognition/GANmodel_47508042/modules.py b/recognition/GANmodel_47508042/modules.py index a188f28f2..4aea23d59 100644 --- a/recognition/GANmodel_47508042/modules.py +++ b/recognition/GANmodel_47508042/modules.py @@ -86,4 +86,3 @@ def forward(self, x): quantized, vq_loss, indices = self.vq(z_e) x_recon = self.decoder(quantized) return x_recon, vq_loss, indices - \ No newline at end of file From ad7f0797ef1d0e39cbd8b9d486a8b32ebc7a176e Mon Sep 17 00:00:00 2001 From: justint3ng Date: Wed, 15 Oct 2025 18:12:22 +1000 Subject: [PATCH 09/15] dataset.py & train.py added classes to dataset.py and train.py (code unfinished) --- recognition/GANmodel_47508042/dataset.py | 50 ++++++++++++++++++++++++ recognition/GANmodel_47508042/train.py | 12 ++++++ 2 files changed, 62 insertions(+) diff --git a/recognition/GANmodel_47508042/dataset.py b/recognition/GANmodel_47508042/dataset.py index e69de29bb..3968b1312 100644 --- a/recognition/GANmodel_47508042/dataset.py +++ b/recognition/GANmodel_47508042/dataset.py @@ -0,0 +1,50 @@ +import os +import numpy as np +import nibabel as nib +from PIL import Image +from torch.utils.data import Dataset +from torchvision import transforms + +class HipMRIDataset(Dataset): + def __init__(self, root_dir, image_size=256, transform=None, max_slices_per_volume=None, recursive=True): + self.root_dir = root_dir + self.image_size = image_size + self.max_slices = max_slices_per_volume + self.recursive = recursive + + # finding files + self.files = self.collect_files() + if len(self.files) == 0: + raise ValueError(f"no files found in {root_dir}") + + # transform + if transform is None: + self.transform = transforms.Compose([ + transforms.Resize((image_size, image_size)), + transforms.ToTensor(), + ]) + else: + self.transform = transform + + # pre-index slices + self.index_map = [] + for f_idx, path in enumerate(self.files): + vol = nib.load(path).get_fdata() + depth = vol.shape[2] + n_slices = depth if self.max_slices is None else min(depth, self.max_slices) + for si in range(n_slices): + self.index_map.append((f_idx, si)) + + def collect_files(self): + exists = ('.nii', '.nii.gz') + files = [] + if self.recursive: + for root, _, filenames in os.walk(self.root_dir): + for fn in filenames: + if fn.lower().endswith(exists): + files.append(os.path.join(root, fn)) + else: + for fn in os.listdir(self.root_dir): + if fn.lower().endswith(exists): + files.append(os.path.join(self.root_dir, fn)) + return sorted(files) \ No newline at end of file diff --git a/recognition/GANmodel_47508042/train.py b/recognition/GANmodel_47508042/train.py index e69de29bb..8af6c24e1 100644 --- a/recognition/GANmodel_47508042/train.py +++ b/recognition/GANmodel_47508042/train.py @@ -0,0 +1,12 @@ +import os +import argparse +import torch +from torch import optim +from torch.utils.data import DataLoader, random_split +import torch.nn.functional as F +from tqdm import tqdm +from modules import VQVAE + +def train_loop(args): + device = torch.device('cuda' if torch.cude.is_available() else 'cpu') + print("device:", device) From 4093547f528a2fb3a4a96e11df823a1b87adb314 Mon Sep 17 00:00:00 2001 From: justint3ng Date: Fri, 17 Oct 2025 18:19:21 +1000 Subject: [PATCH 10/15] changes made and added a utils.py added on to the dataset and train files. added a utils file to have some helper functions as well --- .../__pycache__/dataset.cpython-310.pyc | Bin 0 -> 2292 bytes .../__pycache__/modules.cpython-310.pyc | Bin 0 -> 3446 bytes recognition/GANmodel_47508042/dataset.py | 23 ++++- recognition/GANmodel_47508042/train.py | 95 ++++++++++++++++++ recognition/GANmodel_47508042/utils.py | 50 +++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 recognition/GANmodel_47508042/__pycache__/dataset.cpython-310.pyc create mode 100644 recognition/GANmodel_47508042/__pycache__/modules.cpython-310.pyc create mode 100644 recognition/GANmodel_47508042/utils.py diff --git a/recognition/GANmodel_47508042/__pycache__/dataset.cpython-310.pyc b/recognition/GANmodel_47508042/__pycache__/dataset.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ffd77ddd11d8477c77a1156e4ff17dd7040ab22 GIT binary patch literal 2292 zcmZ8i-ER~(5V!aH_CA1+M0|*rLe=V2NTL7&RS1CqQKUAa6#B5DvX-;&vX{Nx!}dXP z+LKU2-`YM`sw&;RRI1d!$dBz4kA1CL=!}oh1g~~>Y>&sYp5OdjHk&blcJ{BoHh-@X z@+WroHxD}3;Z^rQaKdR$lKGb=j_tdN2VW=m2SE~0oOAPV5G9e-y}UMvlbDh_g!?>r zL3kjdT{o$7`W0!1FTpF>Xj9|g9i%-0N_Z>PsS+C0Sj)6f-BJ!z2krJZ7dqGBRVyF_ zNhl`}PlJ2he?b$M2RwX1AhL+p;Oq04*Wnv*2X<|m=4aXPi~D!qZ1s{*2;xB}09Sjh z0SfkS!mA93idKY?$^l6$52RE1Al-^gy%_<`*Fi=4{x0Qo%aQNvkUJIqnf&7H(r;5&l ztRs}ENzoZel|2=v*(vi}bTqThj6eUlxhPEhb()XFZ7EAB5n4%jvm6XdB}{N%pwA?F zP(BcaDy6BtO~0vS1sB_FkPb~v6r+KVsTRg9vd6~D%aog1Pr!B@pksWsnGRuckQK%U z0QJCkIvk3Ew;RS&BJauwc9wv4B9DMDOaK1{8w$yumN{-w$N2m%%lNkOhN<2(F3cF8 zi=p03qGIpQ9hLn?VJkb3nj)0^@H{Fgih!g&1>;1TAT(g^AR@TLWChw>!O24Wi~lx$j4gubE7)DXu- zIsmXc-NGh(F3zA^B#e*XRquiDs0Zm-rV*rKkvh+vS5bKQ-xiEG&m%fMc`zXd%H=>- zFhf{@HY-Tf8knI^@f6z>&Ox?dOfG-p4;TsZk9e5+z=i%am=VV^RuxvkF~HIm zk)P>I>@<&nBsLWjRQtOm~1#6uLJ4C@b_8%OZe5iyhxmRT3ju zplX4(lDehHdB?&}B1Y`K@&az|6>!HTp*36Q#?1!3cN1A>3@9$sV!+sCSfem%tn(IB zIac~%XBi&g0D#{3_`4~7ySD8~yw)~09<)Q`6~r@RCT0xiX9OWL(_rk$D9z_H@+??e zaLYB+uA)G$+b7Oo7vX{gq7bGL;4C^7wHmG+Z#U$JFlv0D?%|F^FeV|8cluc5HXFvh zclUF9%I2^xWoL6`q_bSDAO}hpm)7m@(@X(b+MB~V-WP<82_rGl)%mYn!|G93ArXn7 K6dU$gbp8YM;yk+m literal 0 HcmV?d00001 diff --git a/recognition/GANmodel_47508042/__pycache__/modules.cpython-310.pyc b/recognition/GANmodel_47508042/__pycache__/modules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04d604bdf628dc43751046950cd790521b6a56bd GIT binary patch literal 3446 zcma)8TW=f36`tAI8&{&lNQ@;ry&(7#|iq3H^JLnKyE+iL~r*DF!tj#5B zJ2L#19ddhCtu3k(+U~c^Wo-|8)l6_J?+j$B&N)_{|B1+lAf5ScXk#*CbyR_>{|HjB z$85rFL2LtSL~{zrpH zd2f(Nz5jVq6sgK@DlxF`JWVAUyVumI46B@GP3nVU3@CHWdItvu6DdPPg3N|04onoNw z3=_Ef2yu%69u}UJH-SyN;B>{5_(9=y z!$}SOpo{2BW3M5`RsX4=SdVBW4vw%f%D?|MZ+y^NTbSN#~`>KE-; zW_{y+nHGs6e3;s;`7Berm}FdvbtW9<8MNDvjsGakj*g2NKGd(VQoT)NkH`ve{2Y8u z2Sfk_eOt~Oe6;(5E?2{Yz!jk0q&?n8nke}MSq~m05aC_Q17m1$;z-1A(%V=RZr4Lx zceW|ZA*(&9IIQb;gBdq`;_N_^g*ypJ_cSu8!Ka)i+ht=HJ4B_6)tEO~qf9dPz*{f9 zNnIl7VZ}ztyPLB1M2!8?pERUfQpSNz?4D%DSGrq}CMbgmHerYC5?{jOtfiyL32r&%X$T{ZzS zUw>Gn?Wi-zi{vO947G7jv-FX*zVYA~$ZtXq`|2tk^bUye`$^Fs_6#qKpQxjLmYdoz z*GQP@C^a5@XnP8_Ag+lO;F)ZBMToVC#kV=WJg1igMVzozE0!b zKx>k@ND#3IkMIP_N$Qs%ZGRNgCEEUBFDc@YsmV+iN#03yyMa_aApbySm$U)L1G#c38=Nv#E&qttExQKl=x0}FP61kf$Z=BPIHrSg0Nsdx_ zE2tns>1!#=J-_R!xb`U)XiBjWUnj%yEk4??N?9GtNC~6>8_Nis^G6gS7+$ocGv$8} zV=;E&w+w!bw*f^LQUZB!l^BKk3@g>=MDBphrGE|Wwiv~!`UEW#oC2BT48a`F!PhZ8 z74uc_yoV*4-V70L5#bxvt9#5r=dU#L|AD~+Y)S$xFc>J}!7qP