From af30d2308c12c4fdfb3011e9e879ed383591a50a Mon Sep 17 00:00:00 2001 From: Yanchao Zhang <47974713+yanchaoz@users.noreply.github.com> Date: Wed, 16 Sep 2026 21:31:46 +0800 Subject: [PATCH 1/2] Make inference runnable and preserve the original SegNeuron snapshot --- .gitattributes | 2 + .github/workflows/tests.yml | 40 + .gitignore | 7 + Postprocess/FRMC_post.py | 197 ++++- Pretrain/config/SegNeuron.yaml | 3 +- Pretrain/pretrain.py | 9 +- Pretrain/pretrain_provider.py | 2 +- README.md | 119 ++- Train_and_Inference/config/SegNeuron.yaml | 3 +- Train_and_Inference/inference.py | 273 ++++-- Train_and_Inference/supervised_train.py | 12 +- environment-postprocess.yml | 14 + legacy/Figures/example.png | Bin 0 -> 1597961 bytes legacy/Figures/logo.png | Bin 0 -> 21329 bytes legacy/Figures/pipeline.png | Bin 0 -> 451543 bytes legacy/LICENSE | 21 + legacy/Postprocess/FRMC_post.py | 50 ++ legacy/Pretrain/config/SegNeuron.yaml | 48 ++ legacy/Pretrain/loss/loss.py | 190 ++++ legacy/Pretrain/model/Mnet_pretrain.py | 327 +++++++ legacy/Pretrain/pretrain.py | 263 ++++++ legacy/Pretrain/pretrain_provider.py | 159 ++++ .../utils/__pycache__/aff_util.cpython-37.pyc | Bin 0 -> 4493 bytes .../utils/__pycache__/aff_util.cpython-38.pyc | Bin 0 -> 4674 bytes .../utils/__pycache__/aff_util.cpython-39.pyc | Bin 0 -> 4564 bytes .../__pycache__/augmentation.cpython-37.pyc | Bin 0 -> 22769 bytes .../__pycache__/augmentation.cpython-38.pyc | Bin 0 -> 22518 bytes .../__pycache__/augmentation.cpython-39.pyc | Bin 0 -> 22473 bytes .../consistency_aug.cpython-37.pyc | Bin 0 -> 6577 bytes .../consistency_aug.cpython-38.pyc | Bin 0 -> 6560 bytes .../consistency_aug.cpython-39.pyc | Bin 0 -> 6566 bytes ...nsistency_aug_perturbations.cpython-37.pyc | Bin 0 -> 21552 bytes ...nsistency_aug_perturbations.cpython-38.pyc | Bin 0 -> 21316 bytes ...nsistency_aug_perturbations.cpython-39.pyc | Bin 0 -> 21309 bytes ...tency_aug_perturbations_sup.cpython-37.pyc | Bin 0 -> 22519 bytes ...tency_aug_perturbations_sup.cpython-38.pyc | Bin 0 -> 22123 bytes ...tency_aug_perturbations_sup.cpython-39.pyc | Bin 0 -> 22121 bytes .../__pycache__/coordinate.cpython-37.pyc | Bin 0 -> 5543 bytes .../__pycache__/coordinate.cpython-38.pyc | Bin 0 -> 5280 bytes .../__pycache__/coordinate.cpython-39.pyc | Bin 0 -> 5255 bytes .../__pycache__/flow_display.cpython-37.pyc | Bin 0 -> 3931 bytes .../__pycache__/flow_display.cpython-38.pyc | Bin 0 -> 3948 bytes .../__pycache__/flow_display.cpython-39.pyc | Bin 0 -> 3948 bytes .../__pycache__/flow_synthesis.cpython-37.pyc | Bin 0 -> 2991 bytes .../__pycache__/flow_synthesis.cpython-38.pyc | Bin 0 -> 2996 bytes .../__pycache__/flow_synthesis.cpython-39.pyc | Bin 0 -> 2996 bytes .../__pycache__/image_warp.cpython-37.pyc | Bin 0 -> 2334 bytes .../__pycache__/image_warp.cpython-38.pyc | Bin 0 -> 2341 bytes .../__pycache__/image_warp.cpython-39.pyc | Bin 0 -> 2341 bytes .../utils/__pycache__/post_lmc.cpython-37.pyc | Bin 0 -> 2988 bytes .../utils/__pycache__/post_lmc.cpython-38.pyc | Bin 0 -> 2818 bytes .../utils/__pycache__/post_lmc.cpython-39.pyc | Bin 0 -> 2845 bytes .../__pycache__/post_waterz.cpython-37.pyc | Bin 0 -> 2959 bytes .../__pycache__/post_waterz.cpython-38.pyc | Bin 0 -> 2996 bytes .../__pycache__/post_waterz.cpython-39.pyc | Bin 0 -> 2971 bytes .../utils/__pycache__/seg_util.cpython-37.pyc | Bin 0 -> 5686 bytes .../utils/__pycache__/seg_util.cpython-38.pyc | Bin 0 -> 5708 bytes .../utils/__pycache__/seg_util.cpython-39.pyc | Bin 0 -> 5677 bytes .../utils/__pycache__/show.cpython-37.pyc | Bin 0 -> 9123 bytes .../utils/__pycache__/show.cpython-38.pyc | Bin 0 -> 7950 bytes .../utils/__pycache__/show.cpython-39.pyc | Bin 0 -> 8484 bytes legacy/Pretrain/utils/aff_util.py | 139 +++ legacy/Pretrain/utils/affine.py | 288 +++++++ legacy/Pretrain/utils/augmentation.py | 737 ++++++++++++++++ legacy/Pretrain/utils/augmentation_affine.py | 240 ++++++ legacy/Pretrain/utils/compute_sdf.py | 32 + legacy/Pretrain/utils/consistency_aug.py | 235 +++++ .../utils/consistency_aug_perturbations.py | 728 ++++++++++++++++ .../consistency_aug_perturbations_sup.py | 763 +++++++++++++++++ legacy/Pretrain/utils/coordinate.py | 131 +++ legacy/Pretrain/utils/encoder_dict.py | 159 ++++ legacy/Pretrain/utils/flow_display.py | 180 ++++ legacy/Pretrain/utils/flow_synthesis.py | 161 ++++ legacy/Pretrain/utils/gen_pseudo.py | 74 ++ legacy/Pretrain/utils/image_warp.py | 112 +++ legacy/Pretrain/utils/malis_loss.py | 14 + legacy/Pretrain/utils/optim_weight_ema.py | 25 + legacy/Pretrain/utils/post_func.py | 213 +++++ legacy/Pretrain/utils/post_lmc.py | 77 ++ legacy/Pretrain/utils/post_waterz.py | 92 ++ legacy/Pretrain/utils/seeds_func.py | 443 ++++++++++ legacy/Pretrain/utils/seg_util.py | 194 +++++ legacy/Pretrain/utils/show.py | 299 +++++++ legacy/Pretrain/utils/torch_utils.py | 17 + legacy/Pretrain/utils/utils.py | 57 ++ legacy/README.md | 107 +++ legacy/SNAPSHOT.md | 10 + legacy/SegNeuron_Colab_Inference.ipynb | 810 ++++++++++++++++++ .../Train_and_Inference/config/SegNeuron.yaml | 50 ++ legacy/Train_and_Inference/inference.py | 67 ++ .../Train_and_Inference/inference_provider.py | 201 +++++ legacy/Train_and_Inference/loss/loss.py | 190 ++++ legacy/Train_and_Inference/model/Mnet.py | 310 +++++++ .../supervised_provider.py | 380 ++++++++ .../Train_and_Inference/supervised_train.py | 266 ++++++ legacy/Train_and_Inference/utils/aff_util.py | 139 +++ legacy/Train_and_Inference/utils/affine.py | 288 +++++++ .../Train_and_Inference/utils/augmentation.py | 737 ++++++++++++++++ .../utils/augmentation_affine.py | 240 ++++++ .../Train_and_Inference/utils/compute_sdf.py | 32 + .../utils/consistency_aug.py | 235 +++++ .../utils/consistency_aug_perturbations.py | 728 ++++++++++++++++ .../consistency_aug_perturbations_sup.py | 763 +++++++++++++++++ .../Train_and_Inference/utils/coordinate.py | 131 +++ .../Train_and_Inference/utils/encoder_dict.py | 159 ++++ .../Train_and_Inference/utils/flow_display.py | 180 ++++ .../utils/flow_synthesis.py | 161 ++++ .../Train_and_Inference/utils/gen_pseudo.py | 74 ++ .../Train_and_Inference/utils/image_warp.py | 112 +++ .../Train_and_Inference/utils/malis_loss.py | 14 + .../utils/optim_weight_ema.py | 25 + legacy/Train_and_Inference/utils/post_func.py | 213 +++++ legacy/Train_and_Inference/utils/post_lmc.py | 77 ++ .../Train_and_Inference/utils/post_waterz.py | 92 ++ .../Train_and_Inference/utils/seeds_func.py | 443 ++++++++++ legacy/Train_and_Inference/utils/seg_util.py | 194 +++++ legacy/Train_and_Inference/utils/show.py | 273 ++++++ .../Train_and_Inference/utils/torch_utils.py | 17 + legacy/Train_and_Inference/utils/utils.py | 57 ++ legacy/requirements.txt | 248 ++++++ requirements-training.txt | 13 + requirements.txt | 253 +----- tests/test_inference.py | 161 ++++ tests/test_postprocess.py | 198 +++++ tests/test_training.py | 89 ++ 125 files changed, 15495 insertions(+), 391 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 environment-postprocess.yml create mode 100644 legacy/Figures/example.png create mode 100644 legacy/Figures/logo.png create mode 100644 legacy/Figures/pipeline.png create mode 100644 legacy/LICENSE create mode 100644 legacy/Postprocess/FRMC_post.py create mode 100644 legacy/Pretrain/config/SegNeuron.yaml create mode 100644 legacy/Pretrain/loss/loss.py create mode 100644 legacy/Pretrain/model/Mnet_pretrain.py create mode 100644 legacy/Pretrain/pretrain.py create mode 100644 legacy/Pretrain/pretrain_provider.py create mode 100644 legacy/Pretrain/utils/__pycache__/aff_util.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/aff_util.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/aff_util.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/augmentation.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/augmentation.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/augmentation.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug_perturbations.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug_perturbations.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug_perturbations.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug_perturbations_sup.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug_perturbations_sup.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/consistency_aug_perturbations_sup.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/coordinate.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/coordinate.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/coordinate.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/flow_display.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/flow_display.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/flow_display.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/flow_synthesis.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/flow_synthesis.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/flow_synthesis.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/image_warp.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/image_warp.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/image_warp.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/post_lmc.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/post_lmc.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/post_lmc.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/post_waterz.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/post_waterz.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/post_waterz.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/seg_util.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/seg_util.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/seg_util.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/show.cpython-37.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/show.cpython-38.pyc create mode 100644 legacy/Pretrain/utils/__pycache__/show.cpython-39.pyc create mode 100644 legacy/Pretrain/utils/aff_util.py create mode 100644 legacy/Pretrain/utils/affine.py create mode 100644 legacy/Pretrain/utils/augmentation.py create mode 100644 legacy/Pretrain/utils/augmentation_affine.py create mode 100644 legacy/Pretrain/utils/compute_sdf.py create mode 100644 legacy/Pretrain/utils/consistency_aug.py create mode 100644 legacy/Pretrain/utils/consistency_aug_perturbations.py create mode 100644 legacy/Pretrain/utils/consistency_aug_perturbations_sup.py create mode 100644 legacy/Pretrain/utils/coordinate.py create mode 100644 legacy/Pretrain/utils/encoder_dict.py create mode 100644 legacy/Pretrain/utils/flow_display.py create mode 100644 legacy/Pretrain/utils/flow_synthesis.py create mode 100644 legacy/Pretrain/utils/gen_pseudo.py create mode 100644 legacy/Pretrain/utils/image_warp.py create mode 100644 legacy/Pretrain/utils/malis_loss.py create mode 100644 legacy/Pretrain/utils/optim_weight_ema.py create mode 100644 legacy/Pretrain/utils/post_func.py create mode 100644 legacy/Pretrain/utils/post_lmc.py create mode 100644 legacy/Pretrain/utils/post_waterz.py create mode 100644 legacy/Pretrain/utils/seeds_func.py create mode 100644 legacy/Pretrain/utils/seg_util.py create mode 100644 legacy/Pretrain/utils/show.py create mode 100644 legacy/Pretrain/utils/torch_utils.py create mode 100644 legacy/Pretrain/utils/utils.py create mode 100644 legacy/README.md create mode 100644 legacy/SNAPSHOT.md create mode 100644 legacy/SegNeuron_Colab_Inference.ipynb create mode 100644 legacy/Train_and_Inference/config/SegNeuron.yaml create mode 100644 legacy/Train_and_Inference/inference.py create mode 100644 legacy/Train_and_Inference/inference_provider.py create mode 100644 legacy/Train_and_Inference/loss/loss.py create mode 100644 legacy/Train_and_Inference/model/Mnet.py create mode 100644 legacy/Train_and_Inference/supervised_provider.py create mode 100644 legacy/Train_and_Inference/supervised_train.py create mode 100644 legacy/Train_and_Inference/utils/aff_util.py create mode 100644 legacy/Train_and_Inference/utils/affine.py create mode 100644 legacy/Train_and_Inference/utils/augmentation.py create mode 100644 legacy/Train_and_Inference/utils/augmentation_affine.py create mode 100644 legacy/Train_and_Inference/utils/compute_sdf.py create mode 100644 legacy/Train_and_Inference/utils/consistency_aug.py create mode 100644 legacy/Train_and_Inference/utils/consistency_aug_perturbations.py create mode 100644 legacy/Train_and_Inference/utils/consistency_aug_perturbations_sup.py create mode 100644 legacy/Train_and_Inference/utils/coordinate.py create mode 100644 legacy/Train_and_Inference/utils/encoder_dict.py create mode 100644 legacy/Train_and_Inference/utils/flow_display.py create mode 100644 legacy/Train_and_Inference/utils/flow_synthesis.py create mode 100644 legacy/Train_and_Inference/utils/gen_pseudo.py create mode 100644 legacy/Train_and_Inference/utils/image_warp.py create mode 100644 legacy/Train_and_Inference/utils/malis_loss.py create mode 100644 legacy/Train_and_Inference/utils/optim_weight_ema.py create mode 100644 legacy/Train_and_Inference/utils/post_func.py create mode 100644 legacy/Train_and_Inference/utils/post_lmc.py create mode 100644 legacy/Train_and_Inference/utils/post_waterz.py create mode 100644 legacy/Train_and_Inference/utils/seeds_func.py create mode 100644 legacy/Train_and_Inference/utils/seg_util.py create mode 100644 legacy/Train_and_Inference/utils/show.py create mode 100644 legacy/Train_and_Inference/utils/torch_utils.py create mode 100644 legacy/Train_and_Inference/utils/utils.py create mode 100644 legacy/requirements.txt create mode 100644 requirements-training.txt create mode 100644 tests/test_inference.py create mode 100644 tests/test_postprocess.py create mode 100644 tests/test_training.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c5ce765 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Keep the archived bytes (including historical line endings) unchanged. +/legacy/** -text -whitespace linguist-vendored diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f795f0a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +name: SegNeuron tests + +on: + push: + branches: [main, 'codex/**'] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + cpu: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: pip + - run: python -m pip install torch==2.9.1 --index-url https://download.pytorch.org/whl/cpu + - run: python -m pip install -r requirements-training.txt + - run: python -m unittest discover -s tests -v + + frmc: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: mamba-org/setup-micromamba@v2 + with: + environment-file: environment-postprocess.yml + cache-environment: true + init-shell: bash + - name: Test the real ELF multicut backend + shell: bash -el {0} + env: + SEGNEURON_REQUIRE_ELF: '1' + run: python -m unittest discover -s tests -p test_postprocess.py -v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa7d857 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/Train_and_Inference/**/__pycache__/ +/Pretrain/**/__pycache__/ +/Postprocess/__pycache__/ +/tests/__pycache__/ +/runs/ +/weights/ +/.venv/ diff --git a/Postprocess/FRMC_post.py b/Postprocess/FRMC_post.py index caf247a..357d211 100644 --- a/Postprocess/FRMC_post.py +++ b/Postprocess/FRMC_post.py @@ -1,50 +1,177 @@ -from skimage.metrics import adapted_rand_error as adapted_rand_ref -from skimage.metrics import variation_of_information as voi_ref -import elf.segmentation.multicut as mc -import elf.segmentation.features as feats -import elf.segmentation.watershed as ws +"""ELF watershed + multicut postprocessing for SegNeuron probability outputs.""" + +import argparse +import json +from pathlib import Path + import numpy as np -import imageio + + +def _probabilities(values, name, ndim): + values = np.asarray(values) + if values.ndim != ndim or any(size == 0 for size in values.shape): + raise ValueError(f"{name} must be a nonempty {ndim}-D array") + if values.dtype.kind not in "buif": + raise ValueError(f"{name} must contain real probabilities") + if not np.isfinite(values).all() or values.min() < 0 or values.max() > 1: + raise ValueError(f"{name} must contain finite probabilities in [0, 1]") + return np.ascontiguousarray(values, dtype=np.float32) + + +def _load_elf(): + # Keep --help and input validation usable without the compiled ELF backend. + try: + import elf.segmentation.features as feats + import elf.segmentation.multicut as mc + import elf.segmentation.watershed as ws + except (ImportError, OSError) as exc: + raise RuntimeError( + "ELF multicut is unavailable. Install a compatible python-elf, " + "nifty and vigra environment (see README); no fallback is used. " + f"Backend error: {exc}" + ) from exc + return feats, mc, ws def post_mc(affs, beta=0.25): - affs = 1 - affs - boundary_input = np.maximum(affs[1], affs[2]) - watershed = np.zeros_like(boundary_input, dtype='uint64') + """Segment merge affinities of shape (3, z, y, x), returning uint32 IDs. + + Channels correspond to offsets (-1,0,0), (0,-1,0), (0,0,-1). + The original watershed settings and ELF Kernighan-Lin multicut are kept. + Output IDs start at 1; cluster 0 from the solver is a neuron, not background. + """ + affs = _probabilities(affs, "affinities", 4) + if affs.shape[0] != 3: + raise ValueError("affinities must have shape (3, z, y, x)") + if isinstance(beta, (bool, np.bool_)) or not np.isscalar(beta): + raise ValueError("beta must be a finite number strictly between 0 and 1") + try: + beta = float(beta) + except (TypeError, ValueError) as exc: + raise ValueError("beta must be a finite number strictly between 0 and 1") from exc + if not np.isfinite(beta) or not 0 < beta < 1: + raise ValueError("beta must be a finite number strictly between 0 and 1") + + feats, mc, ws = _load_elf() + split_affs = 1.0 - affs + boundary_input = np.maximum(split_affs[1], split_affs[2]) + watershed = np.empty(boundary_input.shape, dtype=np.uint64) offset = 0 for z in range(watershed.shape[0]): - wsz, max_id = ws.distance_transform_watershed(boundary_input[z], threshold=0.25, sigma_seeds=2.0) - wsz += offset - offset += max_id - watershed[z] = wsz + wsz, _ = ws.distance_transform_watershed( + boundary_input[z], threshold=0.25, sigma_seeds=2.0 + ) + wsz = np.asarray(wsz) + if wsz.shape != watershed.shape[1:] or wsz.dtype.kind not in "ui" or np.any(wsz <= 0): + raise RuntimeError(f"ELF watershed returned invalid or unassigned fragments at z={z}") + # Dense zero-based RAG nodes, disjoint across slices even for sparse IDs. + ids, inverse = np.unique(wsz, return_inverse=True) + watershed[z] = inverse.reshape(wsz.shape).astype(np.uint64) + offset + offset += len(ids) + rag = feats.compute_rag(watershed) - offsets = [[-1, 0, 0], [0, -1, 0], [0, 0, -1]] - costs = feats.compute_affinity_features(rag, affs, offsets)[:, 0] - edge_sizes = feats.compute_boundary_mean_and_length(rag, boundary_input)[:, 1] - costs = mc.transform_probabilities_to_costs(costs, edge_sizes=edge_sizes, beta=beta) - node_labels = mc.multicut_kernighan_lin(rag, costs) + if rag.numberOfEdges == 0: + # Exact edgeless-graph solution; ELF's cost scaling needs nonempty edges. + node_labels = np.arange(rag.numberOfNodes, dtype=np.uint64) + else: + offsets = [[-1, 0, 0], [0, -1, 0], [0, 0, -1]] + costs = feats.compute_affinity_features(rag, split_affs, offsets)[:, 0] + edge_sizes = feats.compute_boundary_mean_and_length(rag, boundary_input)[:, 1] + if not np.isfinite(costs).all() or not np.isfinite(edge_sizes).all() or np.any(edge_sizes <= 0): + raise RuntimeError("ELF returned invalid edge probabilities or sizes") + costs = mc.transform_probabilities_to_costs(costs, edge_sizes=edge_sizes, beta=beta) + node_labels = mc.multicut_kernighan_lin(rag, costs) segmentation = feats.project_node_labels_to_pixels(rag, node_labels) - return segmentation + labels, inverse = np.unique(segmentation, return_inverse=True) + if len(labels) > np.iinfo(np.uint32).max: + raise OverflowError("Too many instances for uint32 output") + return (inverse.reshape(watershed.shape) + 1).astype(np.uint32) -if __name__ == "__main__": - aff_root = '/***/***' - bound_root = '/***/***' - gt_root = '/***/***' - beta = 0.25 +def _load_volume(path): + if path.suffix.lower() == ".npy": + return np.load(path, allow_pickle=False) + if path.suffix.lower() in (".tif", ".tiff"): + import tifffile + return tifffile.imread(path) + raise ValueError(f"Expected .npy, .tif or .tiff: {path}") + + +def _validate_ground_truth(gt, shape): + if gt.shape != shape: + raise ValueError(f"ground truth shape {gt.shape} does not match {shape}") + if gt.dtype.kind not in "ui" or np.any(gt < 0): + raise ValueError("ground truth must contain nonnegative integer instance IDs") + if not np.any(gt): + raise ValueError("ground truth contains only ignored label 0") + # Metric implementations index by label: compact sparse IDs without dropping background. + ids, inverse = np.unique(gt, return_inverse=True) + return (inverse.reshape(gt.shape) + int(ids[0] != 0)).astype(np.uint64) - gt_seg = imageio.volread(gt_root) - gt_seg = np.uint32(gt_seg) - boundary_input = imageio.volread(bound_root) - boundary_input = np.array([boundary_input, boundary_input, boundary_input]) - affine = np.load(aff_root) - affine = np.minimum(boundary_input, affine) +def _write_labels(path, segmentation): + # Exclusive creation also protects against a file appearing during computation. + with path.open("xb") as stream: + try: + if path.suffix.lower() == ".npy": + np.save(stream, segmentation, allow_pickle=False) + else: + import tifffile + tifffile.imwrite(stream, segmentation, photometric="minisblack", metadata={"axes": "ZYX"}) + except BaseException: + stream.close() + path.unlink() + raise - pred_seg = post_mc(affine, beta) - arand = adapted_rand_ref(gt_seg, pred_seg, ignore_labels=(0,))[0] - voi_split, voi_merge = voi_ref(gt_seg, pred_seg, ignore_labels=(0,)) +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--affinities", type=Path, required=True, help="NPY merge probabilities, shape (3,z,y,x)") + parser.add_argument("--boundaries", type=Path, required=True, + help="TIFF foreground-head probabilities (original boundary output), shape (z,y,x)") + parser.add_argument("--output", type=Path, required=True, help="New uint32 labels file (.tif, .tiff or .npy)") + parser.add_argument("--beta", type=float, default=0.25, help="Multicut bias strictly between 0 and 1 (default: 0.25)") + parser.add_argument("--ground-truth", type=Path, help="Optional same-grid neuron instance labels; 0 is ignored") + args = parser.parse_args(argv) - voi_sum = voi_split + voi_merge - print('voi_split:', voi_split, 'voi_merge:', voi_merge, 'voi:', voi_sum, 'arand', arand) + try: + if args.output.suffix.lower() not in (".npy", ".tif", ".tiff"): + raise ValueError("output must use .npy, .tif or .tiff") + if args.output.exists(): + raise FileExistsError(f"Refusing to overwrite {args.output}") + if not args.output.parent.is_dir(): + raise ValueError(f"Output directory does not exist: {args.output.parent}") + if args.affinities.suffix.lower() != ".npy": + raise ValueError("affinities must be a .npy file") + if args.boundaries.suffix.lower() not in (".tif", ".tiff"): + raise ValueError("boundaries must be a .tif or .tiff file") + affinities = _probabilities(_load_volume(args.affinities), "affinities", 4) + boundaries = _probabilities(_load_volume(args.boundaries), "boundaries", 3) + if affinities.shape[0] != 3 or affinities.shape[1:] != boundaries.shape: + raise ValueError("affinities must be (3,z,y,x) and boundaries must match (z,y,x)") + gt = None + if args.ground_truth is not None: + gt = _validate_ground_truth(_load_volume(args.ground_truth), boundaries.shape) + from skimage.metrics import adapted_rand_error, variation_of_information + + # Keep the original foreground-head fusion and probability direction. + segmentation = post_mc(np.minimum(affinities, boundaries[None]), args.beta) + result = {"output": str(args.output), "shape": list(segmentation.shape), + "dtype": str(segmentation.dtype), "instances": int(segmentation.max()), "beta": args.beta} + if gt is not None: + arand = adapted_rand_error(gt, segmentation, ignore_labels=(0,))[0] + voi_split, voi_merge = variation_of_information(gt, segmentation, ignore_labels=(0,)) + metrics = {"arand": float(arand), "voi_split": float(voi_split), + "voi_merge": float(voi_merge), "voi": float(voi_split + voi_merge)} + if not all(np.isfinite(value) for value in metrics.values()): + raise ValueError("Ground-truth metrics are undefined for this volume") + result["metrics"] = metrics + _write_labels(args.output, segmentation) + except (ValueError, OSError, RuntimeError, ImportError, OverflowError) as exc: + parser.exit(1, f"error: {exc}\n") + print(json.dumps(result, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Pretrain/config/SegNeuron.yaml b/Pretrain/config/SegNeuron.yaml index 80f2028..0e818ff 100644 --- a/Pretrain/config/SegNeuron.yaml +++ b/Pretrain/config/SegNeuron.yaml @@ -1,6 +1,7 @@ NAME: 'SegNeuron' -MODEL: +MODEL: + model_type: 'superhuman' # Same-size prediction; disable the legacy MALA crop. pre_train: True pretrain_path: '/***/***' continue_train: False diff --git a/Pretrain/pretrain.py b/Pretrain/pretrain.py index 75f9efb..37dac4e 100644 --- a/Pretrain/pretrain.py +++ b/Pretrain/pretrain.py @@ -4,14 +4,13 @@ import os -os.environ['CUDA_VISIBLE_DEVICES'] = "0, 1" import sys import yaml import time import logging import argparse import numpy as np -from attrdict import AttrDict +from addict import Dict as AttrDict from tensorboardX import SummaryWriter from collections import OrderedDict import torch @@ -78,7 +77,7 @@ def load_dataset(cfg): t1 = time.time() train_provider = Provider('train', cfg) print('Done (time: %.2fs)' % (time.time() - t1)) - return train_provider, valid_provider + return train_provider def build_model(cfg, writer): @@ -154,7 +153,7 @@ def loop(cfg, train_provider, model, optimizer, iters, writer): sum_labeled_loss = 0 sum_unlabel_loss = 0 - while iters <= cfg.TRAIN.total_iters: + while iters < cfg.TRAIN.total_iters: # train model.train() iters += 1 @@ -260,4 +259,4 @@ def loop(cfg, train_provider, model, optimizer, iters, writer): writer.close() else: pass - print('***Done***') \ No newline at end of file + print('***Done***') diff --git a/Pretrain/pretrain_provider.py b/Pretrain/pretrain_provider.py index a7c588a..3442c71 100644 --- a/Pretrain/pretrain_provider.py +++ b/Pretrain/pretrain_provider.py @@ -41,7 +41,7 @@ def __init__(self, cfg): def __getitem__(self, index): - k = random.randint(0, len(self.dataset)) + k = random.randrange(len(self.dataset)) used_data = self.dataset[k] raw_data_shape = used_data.shape diff --git a/README.md b/README.md index 6a857b8..d279a04 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Official implementation, datasets and trained models of "SegNeuron: 3D Neuron In [](https://huggingface.co/datasets/yanchaoz/EMNeuron) [](https://colab.research.google.com/github/yanchaoz/SegNeuron/blob/main/SegNeuron_Colab_Inference.ipynb) +The Colab notebook is the original self-contained demonstration with its own environment. For the maintained command-line workflow, use the installation and inference instructions below. + > [!TIP] > **SegNeuron is now available as an Agent Skill in [EM-Skills](https://github.com/yanchaoz/EM-Skills).** @@ -32,8 +34,41 @@ The general-purpose model achieves outstanding reconstruction performance on ent -## Environments -We have packaged all the dependencies into Connect.tar.gz, which can be directly downloaded for easy access [here](https://huggingface.co/yanchaoz/SegNeuron). +## Installation + +Use Python 3.10 or newer for affinity inference. From the repository root: + +```bash +git clone https://github.com/yanchaoz/SegNeuron.git +cd SegNeuron +python -m venv .venv +``` + +Activate the environment with `source .venv/bin/activate` on Linux/macOS or `.venv\Scripts\Activate.ps1` in Windows PowerShell. Install the appropriate PyTorch build using the [official selector](https://pytorch.org/get-started/locally/), then install the remaining dependencies: + +```bash +python -m pip install -r requirements.txt +``` + +For CPU-only inference, an explicit PyTorch installation is: + +```bash +python -m pip install "torch>=2.6,<3" --index-url https://download.pytorch.org/whl/cpu +python -m pip install -r requirements.txt +``` + +FRMC instance segmentation uses the original ELF/nifty/vigra backend in a separate Linux conda environment: + +```bash +conda env create -f environment-postprocess.yml +conda activate segneuron-postprocess +python -c "import elf.segmentation.multicut, elf.segmentation.features, elf.segmentation.watershed" +``` + +The environment pins `python-elf=0.8.1`: [ELF 0.9 changed its C++ backend](https://github.com/constantinpape/elf), so upgrading it is a separate compatibility change. On Windows, run this postprocessing environment in Linux/WSL; a successful `pip install` alone does not establish that its compiled dependencies work. The scripts report missing or broken dependencies rather than substituting a different segmentation algorithm. + +The original source, README, notebook and environment freeze are preserved in [`legacy/`](legacy/) from commit `ccb0ba2c5e28e0d2c454e7320c341e71f4eb148c`. The historical `Connect.tar.gz` environment remains available from the [model repository](https://huggingface.co/yanchaoz/SegNeuron); it is not required for the inference commands below. + ## Datasets and Models The datasets required for model development and validation are available [here](https://huggingface.co/datasets/yanchaoz/EMNeuron). The trained models can be download [here](https://huggingface.co/yanchaoz/SegNeuron). If you use any of the following vEM datasets in your work, please also cite the corresponding original publications: @@ -68,40 +103,66 @@ The datasets required for model development and validation are available [here]( -## Training -### 1. Pretraining -``` -cd Pretrain -``` -``` -python pretrain.py -``` -### 2. Supervised Training -``` -cd Train_and_Inference -``` -``` -python supervised_train.py -``` ## Inference -### 1. Affinity Inference -``` -cd Train_and_Inference -``` -``` -python inference.py -``` -### 2. Instance Segmentation -``` -cd Postprocess -``` + +### 1. Affinity inference + +Download [`SegNeuronModel.ckpt`](https://huggingface.co/yanchaoz/SegNeuron/resolve/main/SegNeuronModel.ckpt) to `weights/SegNeuronModel.ckpt`. Input is a nonempty **3D `uint8` TIFF or NPY volume in `(z, y, x)` order**, without a channel or time axis. Choose the imaging scale before running inference; the script does not resize, resample or reinterpret axes. The paper targets approximately 5–10 nm x/y sampling. + +Run from the repository root in the inference environment: + +```bash +python Train_and_Inference/inference.py --input data/raw.tif --checkpoint weights/SegNeuronModel.ckpt --output-dir runs/example --device cpu ``` -python FRMC_post.py + +For a CUDA-enabled PyTorch installation, use `--device cuda:0`. The output directory must not already exist. + +Inference retains MNet's architecture, divides intensities by 255, and blends overlapping `20 × 128 × 128` tiles with Gaussian weights and stride `10 × 64 × 64`. Small inputs are padded for the model and outputs are cropped back to the original shape. Inputs and output accumulators are held in RAM; begin with a representative crop before processing a large volume. + +| Output | Meaning | +|---|---| +| `affinities.npy` | `float32` probabilities, shape `(3, z, y, x)`; channels connect to offsets `(-1,0,0)`, `(0,-1,0)`, `(0,0,-1)` | +| `boundaries.tif` | `float32` auxiliary-head probabilities, shape `(z, y, x)`; historical filename retained | +| `inference.json` | Run settings and input/checkpoint hashes | + +The auxiliary head is trained against `label != 0`; despite its historical filename, its output is foreground/interior confidence, not a membrane probability to invert. FRMC combines it with each affinity channel using the original elementwise minimum. Keep both files from the same run and grid. + +### 2. Instance segmentation + +Activate `segneuron-postprocess` and run from the repository root: + +```bash +python Postprocess/FRMC_post.py --affinities runs/example/affinities.npy --boundaries runs/example/boundaries.tif --output runs/example/segmentation-beta025.tif --beta 0.25 ``` + +The output is a `uint32` neuron-instance label volume with positive IDs on the same `(z, y, x)` grid. NPY output is also supported by choosing a `.npy` filename. Its parent directory must exist and an existing output file is refused. A JSON summary is printed to stdout. Ground truth is optional; add `--ground-truth data/neuron-labels.tif` only when it contains matching neuron-instance annotations on the same grid. This adds adapted Rand error and split/merge variation of information to the summary. Synapse or mitochondria annotations are not neuron ground truth. + +To compare parameters, repeat postprocessing with a different `--beta` and output filename; reuse the same inference outputs. The default `0.25` reproduces the original parameter, not an accuracy guarantee on a new dataset. Inspect merge/split errors across slices and use neuron ground truth to compare accuracy when available. + ### 3. Zero-shot Segmentation Examples on [MitoEM](https://mitoem.grand-challenge.org/) and [Wildenberg](https://bossdb.org/project/wildenberg2023) (scale bar: 2 um)
iQ(dm$sLKu7aJh#g|<4N49mBEaX{z%NdFN4fQh?wYD<4{%=})*6b1nBu>WrX z_KP2=0RZRUbu}No^0(TWV@|bnOq1A4uMqi5b2I2}HY!p$TTn0YNk`5H*UJ7xdZxdh z!~2DGjnrRvB6u_lQNrQEw{OyPls=)EnHqR%XgK`z=&AG=X+Bm(MQ`}eZU(-z`MnqH z$Ht$;Pel0ef$GtG6&!!kf>Zc+j^2GGB3xVkJ=x5q=wf9RZ+*JRg98VX7u@z+Hc~OY zpy0jGZQht}QcFwAmlLHf&sd43-PRy;5YD~rj6`yFPrp0|kvMAn^}&Io@qe%M1&6bwYW*QN4@7M7C?-G^&twRh^u`Fd^z#M*Zr_-|ppxoOKa z`leU?(pB*N92HTS%41#`2J2n|=YUr??3Gt1U+78IjQ?-t8k=~O{C{@G?>nw6gn?tj z1OnFr0``6~E-B$D2Jqb=e2~>?@ZS)|Me50}M-)DG=nUJ3z#spqn;~v{AWtZ;O2alc ziGUJA-1hm7UmH#pGCb7k`oPNFFT>OAV6NccV3t_Bg?D}un}Q%e&D jWs>I?_HZOWdk@dAOi_oJh175<6PSh;WP5!?B&=+|!p1PBY@{y`q)NVHsQa z*2>ZYe_XhImGwz~k5k*NSl!HRzYP2SB?oi>{7Keo)9~qb72HA~9mk*KJ9GeRG7NNx z9uYb+`Daa%QOPt>mHQ62!X_>6*Ct?t?;CDcsl-XfeX~wn{qC-SJ{67aqlWJ`l-{ZR z(lfB_n0aU}kbBys>>gmv(gK^!h#IcETo;q4kI0YBJS&)2J*boUw-jV*cv3Z7iEUkN zP#v2LaHmWi()MkyoUPa&emT&y{DIZq$9di=dU;$}5=2VqNj^)34C9NE4h}5vV+M2K z_Ak*} Oys zI-algS%Dix{&SmFJdAwrp_f@%Ylq7fYtsqMO1sSuYUcwt$x}V5+!uE^YpV(R<2HSc zq)4BOao>5(HkPj+mD0Q{w*SMVZDn(cwOV!mML-azap(JLb}Ri~_BTx06s?Ybr*sz; zWZf`S-q$=uUp)CA-q78$7SfD7`ETgbOZ4%p(_d<*zh+c4Vs^oZ!%_LQ>`=Rfj!&E0 z=r_FX*p= ~==pCfQ`ygAoRESGlE-5cLdW$>4d9{e%)a9iEv z#gn_qKS9A22CX-GJaW2IhpjI@M7MpM3OD$8By6#r78I?)svw#LC-%u~?irpR8p^;3 zgHvw{GVN?^Y_|XG%;?mD^>_`zoGM|NR0{gM!E*QI)EC6I{pR4@Y33?Oj-;a)Q|)m- z-Sb7iR9)I7PY;NDfDLgu2rsqs92{)pN*oEDAZB{>97S#(|M#B<&ru=%)=q)k1Ims@ z7h?Ta7s5{N#d!a&^5Nbdan9~Mw97NyY}SO^6XJf$ZvW>lH!=wBeAP6_P^m=u*=%}n z@tOxL=_D#>U07V=Hs~r0o?R$mpS;V3WJHw9BZY~7{gy&J)LspEpdQ|c`9Esrk~UM> z#ckdR!MTo6fln3oJ2`PSX?Q3%8r3l%-KMO1@rJr5Osx5 @G`Ab pSh3LQ>9jW=*m5H8npv zYCAu3Q;}|1PORT)+?+u>qY=L5$euJ@&f};Ve24+TEt=CnUMN @#8s1~3RK=SNH(9Ueb_Q7-#KP5Z{ ztFW5kzx~b{$MBDX?qQqC(d>h-Lry_iuDx2TSwZeNajeS0-~;sekgw}t)+!nqY#uG- z>ncKcA}6!P_g5Yq_1u?p7y1;2LmbU5W$wbAPxzKN&Z5|l*HVW5`TW+EsIfOR=mYH+ zfe*NFqyvd|%dGurtkUjm>>~ sXxLVAwI9ErjV}b@^ zzeb^-cWE@uaPZ~qvj3@V?8#1(2Tpr&fN%Lr23?+_f%4wbz(wh+wPc!`9+vMWY*rUw z;KTAZ8V5AKC;H>8_G9$f@5+v4rI$Y|^Ta7wMJ`Qiw64F5jzlO%K8Ic9Y(Vtz`?>aH zFLf1N *izDTM2x_7F}Uef_9;-uI1JN zy%4Y7fMW+|m>sgQkZ~gtvz{O$-8yX`5Xldl!1K=UefD(^XC`@ lJAIW#900-7cnOe_`) w^4Sj>6&u&A_6PgT`Ap8VObjU;&B6?u#Cv6HZk`F5EEgcm z-5VU0eAgh;d4_VO=Dg80XFI!8_mdm#=yP)1dA;xE6wYC7^_1@C`9?YQw(2=$xNb8% zaO*}`IK}XMU9HCc_L8~=TQP>no^a-eJo0nvK%J%|4?Je}OaBBPkD-HA1tfBfhU#GH z_(gIZ@}v$Y+a+sS7CGjg-tb#VS1BMr*b(vS VAAr9 zeaNylTJE85Tqti-jBt5$s9y#* 7+F*)TA~1vnl3IRpe4+Q0 %`{)kL#w{Kb=-L*SYntbc-mr2b3 z)9@!WR~bv#0DI1=2WgOY<~#tAW=lovEDT8w+XqvBA;Z; t{|!qA??>%3 zBKH`#C909 YxqMb{~2(@`ebHurWmfQfQ1lrodv(B{`;AaCU;8A1g{t#)XZfJtvIWlnBrJV zjU|Imm%w#r^m|-UcEio=BbLm>O5evRb`FS^?qtozY|)5qlcmA?9B96-G6Tlx57n z7!P=^igqy#*$hnRGYr23n*i|&i?Y{yUH%zW`2MmWt3sH+tu`qI 8WvG$7_rJ7w7@OpyQ`>K-bPh@YXXlqCT{E;EW&_uCUqEUgh zH}du4NWtgvmRmHM%P>SFxYl+6O+75w=Bu3Z@C58V^h0f>v>tx1EuXSDCrCOcc|U&{ z@9AY8TNSn2d#RHbm6k=&uNw{~Rk@*(EGKSzVkcFB8!1+SZ*j>wV8z+slWjL^!PKc& zQXFdV5@|t2+^FH!*C>CE++*!{K-196l8EaRLGEP^^C%GP;qEYE2eu8PbhvO}dU(7# zeqC`bWLP&xd}(uuw$I1~FPU!({TbA|!cB{Hrd|HxX4j$I`4cv*GHDgl)vEJ^%8(Hy zM|yBKJzYAJ-;hz^?Wbr%AN4JaO6AQ6Rau_2$DJQGdoFzGJoY){VXNwLv}zY9{f0L7 ztPg#v+mt23Le`HJ!3_>8xb6sjQrfA{wd7pyF4(Gc8v5$1wk5Q4>LlVjQP=F3VMuus ztizd?Y<78k9FU$jy9JAW$9jKExhvzf_U(SR{a?e~(3`;ED#Oyt8H!rX6$&pnDH32U z7nlEz!wLz8li*1ZBCYDiFp>$t hPE@;vfvl)?!AWb`?B|fmr5FIq?#`!%L zvNU&!OnQTli(0(ilt^t0Q|GGccLX0C*mA)%|8rKMq_ }ob0V9ysZKT8I{IPQ z6WlW#eHPnPAMf}HZl^<-y>Wur;>B2dTGv>6O5gjEM`>yUhrwZQGb$?Bd!**!4)ZH9 z@|5wE?k?O=?7)qlp2cp@Ujv5?7;;Qg_af&F47r{q&AgXV9@! |rZn5`W6 R|mczBQa5kQa11ZcMNwvS8v<*lC!=Ko|O zro0E`b@01mgY`ew)CAUt;89}jJi;`OXbTybXlFu-plmityUb-A2j6&UK-Vs3(4+!C z02|u_pqM4TC0)N_2wRj61MHr2HiuxO07||Qu)6&~0>ADQBoO+SMu67Jm3Bn%KAWPB zqT!O#u);JLz=Hw883s95*M9l7tISPv+yjw;4oU(eSK=S_1G-BAfYg&KgW81(JWSCK zv%&2nvkBLj=$8Zq5Hu|+{Gg|FiJLtbfS;f&FR(oN(|f3y;Ap&%agShNJWcpDUuTJ) z$jtwZ6}F9}jrZ3$WwcEc1+RBTyS3k@j NrEn1D^sOB2C1aQoE5rluY)Wpk6RV zi^m?L2Cia0n@&<2)W}Ga>tIO>i?(?Ha~3(7rhOK%^{kx9a-3gLk>OT>@1B@-gf`}1 zpJ8yN3i#s}1wq}euc^Pw-MsCUpoKFc-JLXQIBYPn-ES3`- *5n}RCBZS)Yi$4c z9(RY9MV{25GvB_t8l5 Y=|k7lSLRWmiRJ*-;**M_8h)+6=h&g8zs-n6 @86|Rx2@@0|q z3s*c8jL};wak|DCkpOd&Ltu<%x*KBcFCi)vz=Zk@*zsdmVd~((Mm`;+hR&;My39$0 zb!b?*OTUc8sBSse2z@uJENu~d%k|DbWoEig)|}^e8?`E9U@OteTds~36BZ>FJn0+y z4GP^Xyp!TJ0^ee$k|Y-`gGH3RV$DdF^Trd6DrK{L=Cuo&>C~yQ{yR07)>N~!3i?Kh zT|wvu7wzDRx?>R#n*luEy+JLJaU;s8+2uPq0Zq)XUde%TOC*i<2l_&Ab3wgAvL-Fh zc2)ipyE{>Ro90c)&{^nlnLsMKp>g+m*?t^+$>SgTiY3TWqaOlxx5DlYF7&x5 vWCdP7Pp1~9U&^f#}+rr@IMqNv8=gCHv#1dHjv<`Y(auf>KhXg7;gnF)eIX!f~ z8&1%6k0P|&JdApG@+$FqF;ajLAD};|(#EcG3G%>nJe<>DUIvR;`G&EX$#^}L%6aLA z3fBu){E{ptd(Zs);N4}F@+f*aY!p4BYd>8*c_@~?1-7W6J%0lc6QXXmlw#R>9U;-C zbbMD8a?Fj5ZKbGmISZxmc=-tFU0P4CF?Qni@)An9N=#VWwB$x~sd(T$)Ho1GtF`@t z$H?@EL#nBy>WNb6Yl^qzanuxF%2nif*&XT*a!cPMG9)Lzx?9-XoxgOO6#YjPA8ict z5h}$D%y*rgyE$ub&ZmuT>=rpj>aF;c{9lNhp9ru4>b3J4eh|?>(Ps&2a|pgJ*`d)A zP6x!dd)Zw6D`7$qyn$-63Lw0N0ccSGrpxyL9rEC8rYS~WbD_|OfM VK& zhnuc^`pb5%>zfA7L8*0RP!GFIYDL~gM>%|xQ(1GNhRZHj8hzVh$ Q)uZbR?FUK zXY6_qqs;Y=6eW_^F#9Sr-T=GGi+OWdG;eA$jM^D}{JE1eZc+bqgNL%LEUWGs$!u@d z7rAvGY%gLyge#ckkPcTm`@tBlfsr0w$x)~Z@#o;Ni!9sHG?Dq`?#*6)2$5d;&40_` z3$uFN(?68z7&Y?X4;>IwMNDHy?6IG|OxEcO{J2+uy@BhSr;1gXupzV?Uyp!s+NgeJ zO`IWFFXo=KXQ8jW7FDQu{F3L|j0U4Za<&Ui=B=-Wo<-6;TCi^q&_yf8^rnyA(TjqA zbD8$!8y8i5n#D-Lp2Hi<`hzHC0>V$087k!ejnr|?xm<4(VqR}g`6as=$e*36u-8tD zqf0RpG#Ru;GCk=rlEuB`Xea5S7qCSxSUp~Yyzh4fK0E_=@q%OPe3ZiY4yCr!0DaOa)5^fA!xyx_)7L#hqr;Q}OR>H^*(8^})TCH9uv+ zk5WWiPwpP{nLLZF`<}I^o%uj*lEuff-Z(4$%QshDIfwdpA2y|EB;$%JKe^e*OnSkr zo-{b3RV~dYXnfu_4~5c~S-K>th8-v<)yEc2T1Av#>lwX02-H!(qbK3|%a(P;hI4X} z8Z~fbH+e+Q@2M&a>mOUHS;iCEq)dCqf!*3MvG22r!`I7vNSn$p+-v I{uo z^PS26xzbIud~N$;l^jgr^%%ywi0-7Z&%I5~QHWxe>z6`~%*X6-!~EP2YJ-Kp#{6XU zDtj4uHinKS{Or+!Sta^7rU?v9 bxUp8BGcV`TcljGjnBfNe;4#C(M(3>7%`R#xH)9(4;*rj0PF!MRt zvC5trX}^RhK->!~P~+zm-@5TGXID%ux>e&iXLmL!XSby)cXy^nbI!}bvOqMz&(3r3 z`sh)gWbUq^oOETZ;Fdem_rpg^PeX5y{iFS{E`+DpEt6YgIW AVK$4esQx;24Qh-s7!jDPg3cjVsx}xLiuJz1#-;3UK**dt^$o9d!nf z0YF|b_WhOhg}UGXg{X&g06Q9{azj*t0x%y{(M=aEvb+XR28sq5eGA~_WJ{5pMfJtf zivkS*FdYHNUW9`lKp@^#?Bg!rZ9B^xbI#@WE4p4d;0X30@aC#Qm>ZDL{-8(rQ!@*P zjT`mQlkd!np0|DEbvT=iY!01FO8E;1Lr4Y3l?I19fdzByGVot Vwzd zw91PbfctEGraBV7nqGi)fGZRI->Y~LpDBJOiR4b5)($p7M^^8OE7$XoHdSt|RVrwj zJocx62erVdG3^o{WqmN8u)rsH(+=uN-cI80;#8_qIU7ogqX;b6w;pDvbLlV%c)Gh{ zIzV<;DE&NGS!aYWc&fd=-K+CeB9eJeI386^7-HN?U1q_j7o@7+o5sfVGBpalCUb=R z8s7R4ZsXjUlX?$yChR%&Y{$y0Ps~l7@s==T&2Iiwyr9Ge7&~@l 9H9Xx7fh 7~R}a1OqlM{_LL$r 0^ z)*yYVU%GgLsl9BJe}2jov>Wq)x5Akq>oo{Z*ok%Yqlj4z;#7T~ Ir%#bV*b zknmfwKV?}XBv^m {lD`H@dAHuvvL&H4Cvt+_*cx{GaksWd8;JOf9o5 zJ6jx98P+L<3m_wm{wZ6J-3_+1PMS67^OtE8{Ti{oel%)ENyRR~_<|Qi8dE3z)4vYw zxR{eHC&tw#)qV6Yr v zIdQ1NVie7H1{A*FbXLEZQ|eE%NK@B_UT0E!2`7rkWgjsmZm;)3FqSmln9R7??%7GL zCE6e@d&=F>`k<3H${5FoS&tXFdg65E-MDaw
#LX3ukMec=FjUc#BYQo&POidE) zFDbayy$L>$9JP=&(rF4&&x-w}8f-S$Q1SI1j?aPAp?>#mr>C 51-;ttf1+GrK>udbU zN_1<^K|AN}ju>7}@EULiAy_3zJinD592@iTAcligj;VI~VxF}{bhdsy4>(1(cJA)f zwVBUOnsCCn?jtteY@dF8BO8=cvzxJ)G c*BCj?$0-t6~bmmgFi(5 zUDC{6AXJL~Z=m3osMQi3ie`<*gWVVP+VB%C4?9SQw_Tx~<=eu9BOnNR^$`m&Atc$D z$N#qlvKIhIDToT`0qAAtBcKir*P+*L(vHx(Q1n44DK8*fOgznfUPj%JH#C8?{_$cc zRY`T!?QojE_0%;NN7iRoifk=DW|`bG{mJ+WR&oF^hnf{{0Ez(Ce-(eTq_Vy-P#5n5 zOOBxeu$Ua+8<6XtxK{SZR*yfS4*pZp B~O4mNzS(K20szDyXE8_es(J_2+Ou(I}OMi3r*XhVwzN1(6+p&vi5WpQyn=ui0 z?IBVz(nxV_ER~6d^bn$kiLe0Zh(wp%P&W>L>EN08##OL&8pQq!y_h &y^f-AcY5_W89j)T!;Ya5kY8toW+5Szw~B~ z8nH`x{o`xLOG`o@)&{g1Za>c$kfO>8(DbIdFeDmeL;&q0IfG?9=$HhjBIQ+cd0JLJ zP<|N3vg7T(8s0a7(eotmY(aTClNAaXT|ZRkr0=?lw07!qBzncGbkSLF;tI}AV>2E& z7niSfig$ZA+aEG3J4&x&lJ?hht?9nNp$69n_Pr*Z6JIray=Kce#ic>K%Dh`=c8h1i z{okZ#Fl|7~6MdDdk~-QB`3he-0q9sLobqTHWJ#e_SZGA0YAEl0c^xlZi!+R;>0X8% zESN*?B{zS0Kpd;-@ZJ9f@{Tu&Wq&~Ek|sX2hI%MQNm6 vVKuGR zHC)9j(rgMCu9}1nb;-uvZKM7x$DJseU%xt`h w6c?sup`h_J=4q?3Kw zwVv&L*-TfCcdk7sF!C0WJ+8V``CQHQQAx)rqwup#8ze~X^_Q0n$%y2>*h)8u_W&jW z9)IHPBr-{IPY+h~xqFg-iYOThmGxqI)T4bdvUc96yKxH?-%Qj$HJy0v9xP(IT&8Ri zq`Zgfl{IMyea2b5!sR&Qf9T8l;qm1kx*NGN ;tGfy6 xBFAd^f$Fg?(xd=?Z#V-cXgfl-a*u;ZTzMIDv^+Fu>{sOvA z{gOF|G|?PBb*Xes>hIv8dpk*Ot|n)wsl+PTpZND=ST5yFmq#Z#aoykL^6W1%@1#gP zv @v{dShW;+>2Ab3(s|&ayF!|SW&e*gr9IMG(?b24 zWcLs8joS`=n9-x@s+?U=D;R-nRfMEW_$H}OaUs#FFHYzF{M)>j;B$hFg4ou@4w{_= zXbZh#DH2@$kte DnJRMn zQ~}KQ*m^HNW#|=AL{W{m0m^`QD9A?U?*$62uFUDHN3pNIZU9z5^Vh(Ap@r`SPy)To z$_)Pr%>Zmrs>`9OfIO5_HmH5rg}0Nw9e Qqu@MlO;>N2JX_-EmF- z2qb>ypBiHk24pYA08x50z<_Q8!X@&fY?{;QPb%@Eu5rpfe9vY##Gy(f2_7g1S5311 z9Z^%7H*zKi4A0DfPEAXRA2o;pj>0s|*-m9!-38&0Jl(SJ``qeBYq@dmC%liAihKbX z@)~!^SVO4afumpBbToSU4}_{o2RtOlslh8_+?Y5ZOWNh44q#GVm`u!2H^6=WXB^hn zWa+hx?Nf(x4C-?)o#z`hDAr#!B)XSrDN-UhnUU~Ti5C-6zv_W1rFPZ9lO&+F0BLB7 zG#M3ps)KADk>;Sg8Lp!-G9z9-WnkrGP$@xVWBz5(ZnQrH?xj-{LdJ6|h~tHa$=pnO zYOwq!x5bxmjAT=)QaXl~$f8q{FPPUU6K}*9-c#K+Y=Lo{x9fyxDj~yl?IT{oUWG;v zvu@coQKouQ7CG3(NGlODP#zy>b7dUa*4#7dl^v%nYVQ*@UdL5}m5lTFSa>k_#bY`% zbD0xS;{9JJ(P-{oa%1ExTal%HH$|c}c+|S>G(0aMX#-#$X}gM%c~q~+NbYBK0h$Xn z#0;bmJ{Bfy+L 4?2j5gZG$4oGQVDr1LO1MP^0CB%E;hb)BSCT-{`gy1{ip;RkKu^DqOYFLXKbE zQCX`HG9yK0zhW$1yRUuhGuD5QNK1U7|0QLsoXQ4j>Amu=J5+Wfe#%yjRM&r%QYK!X zT%;0UEpBqC@}lGosR7a${QAmz@RSBXr+pN=?HbFavD3gt5Rt`vD{I(ELw?yNPpOog zaGm?rr{`1m!@j0tCiHWa_{quE31=>!ce|ZHxYOiI!8il9D8kb0mP9(<9%Ea(fbF9; zZcoar_ZVE)lSJpXVO490M}02S&H>F{2SFEf^)pv-1O6G4SFEV1i5O{E(!u> ^5g?X@|Ko|ddZhe-!K5cW1>81WT9)2Y zNNz4<@5wakoz}R2)T}kwm^YQxY3X^HSazp&_!c$@*AOGW60OyIVEHLSLFI1!LhtK( zSNqfR#_4S?pA-upm!QXUH4lz+{mdkjWwRn8+8C;y7(d2uw(=qL2r)4gB6`k`@y^4s zt$$2DGgYZcA8!bbKxz-U?_d8oXBgSW+a$bIJ1hR#c>LdGkqdVGR>+Y1{xco=N0YZ? zZI5`xCMIkDUnW6)as7Qe>jw!FBAX5U-qjF7pI^HK+aK^b6N4!GvtJ$nA-ndmRVLcM zZ0|2sfaEL9Xgrh4NmYxq;dH4`CO~TZjo8OG6u94efQ5nbwvRz0v_*h58U|n{^RsLI zmoypI%vu<(9(2l%@Prfi4rqyjswh{Kcf0ss#Xf#i3&ph0RsiL2HuHHz;|8$x&|@AY zSbJgm^4Uylh1LN >-b7_L?54M&>0_sba!VFY(fr+v2oQZl zjk_7R33^m$m<@i>6#(x=x~falg$B^N0T-cCA0v(U+wW#qQYoL1Qxj9h0YYC7i$FHg zmI-uBT&klIl@}C(5YC>*(v)6sFhwVLt86fiwU-Yu5``pxO{HzW?)h y;X^gNRH|-Ss+x19kEFP6U@MGs;y~ zn3&Ld+O0-pNf4Eb{`6)*6 e!;X6~#?;@rrGpZ?6aGUOvK0Q>AuD_Sfmu n1q2iPPCc4b*#m2F<*~ E&8J2rF+rDbhSo8(VPaYj9WimmuO#A6i|4fI;bhab@6*jM>M; zyf`_{Todd>L1TxLOI1+*c++hE!RUoW7jWg^-0DzuX$@ikc^Cd7n#@78Yi^e(xfZIf z6l<4e_{{krU~UJ3A@$Pb5V1u~ pJ6#{k;KLPp7}@*z;A;wR#|`K| z-B;mA++Oxuy=M0ZeyCsucjUCUOQOzY uPoLV%2pPvlJQw#}Odb zk%WQa7=nA4Hf;C;HgrQq;rb$9ALxCYeRID!p0nWbLH7l+O$*JTe9FzC#2h_7NWkSP z)aO@kU^m4j!z+g#t&zt$J$rei 5#N?A*q?N2FQkC3sblT#J zJLvI?_ps+G-gH0CHHIT5I+>e#8pXD1rxeXj_;@EA`1f4~^;iOO;=fiDJ0gl&TmOHI zdw$Ny6jiNP!8LN3j>vqEZVNaCoNU+rTdy`AA^_D!meA6NNQOf`T9i%E3M;6YlmL1G zRnS*-U=EO}JsMWR1b7X|0yaY3PEa !=h$l1ue|7_6CVFLn0 zsX`TcXfezcF;>TnUqyBRO8`cI>KuZ`0;PevZ-_USSuJ{dL34YF>&lD%Bz1zR7{e>| zvUO15!Zie@bo-dLLij!4KGRc_lJKQ=7M@8mD8S(Qr||ncOCwQgU7-rg z=X{zUFbOhjT#JKbCOSG!b(WhwJvt!Ii17s_465Bmq?PdCA~O-rKh0$;-pZDJ`XO3f zRq*|qTNcwzhfr!vC?9|{suJ-MQ6Y(@xLd&Jt_pKbGh`F^0Ai@jG9Au+7prg<0N;r@ z1w}`zTzRQ7Ox3O(bLd<0`f;p$vQ+pkz%)P;nfG C1N`x>gzU!RW)gXlq5`Kd!Gj$2yvvUh2+FC`YQ2w-lBFXR>W?1%5?O<^ZREX ztiw&xd|f1k(%ola`z}dibVYrf_g8&5>zQCb8D*Fuq1K7sXMfbINQnU~g3m-Qkia1A z+%y)_6YdOXB5S=aaz2&jjt6(&2OW4eQ}#Gj!sWlG&vfkef_;zrKqPiaofx6+fA1z< z&4jzxj()Oc+tHE0xMppnzBW0ZGHi}=_Q5uHl_^Rj$2it7pI1pJMM~`d!neESp=C0( z!E^$agO+ZbAQv=d?Pr gBB!8E-5eRwvI8lbvf|Ta_6eCsVw*xL)QJti5U`YtdbgNd4*4BnO+Xr)N z6MxBKW!2%!h%m 4iT_ses5__WOxgCsc$vI*3 zNgZDALR0QV2d03;>+d#6ya#U_r%k@ZcYO$G`19jinGew+FCeu};Try*XI?&7_IFEB zVB|et%c04|NY0}+FOxfdxI-aTi_k_)fN5v_{tzc~jHVc6 %Ejma`9Ch#su8854io ztL%vrPi5gn5bTbWL*L9W*JC_d>ETxpN$X&L_wm7cL5{+n>qGPOX~_qS4EA^lB%jB6 zI;YA(ftyu%$DOUfj<+%Of4|4Pe%0CE!P4+oEJnU)4=7yLGE?>g&6;L8=!xx+T3kNb z=3cctB2;j=-w$u13^;c`mgDEV };n*YPVb63G+>O|G(Ny>0DbkQi@ON6;&=MS<@ap#5w*BNhAMLI;q ztXx^%>;xP0KRs7|fr$D4xpEei;TRwePz3}n)7}$6v1-z^lXJm^bg9I!Cbg-AEA7v4 z3C!&<8` L0*&%fL8@vw!tZ&{BZzx!W*pj(JR$G zJ_Ul(??eLv-CnJtAhVJ$upYPt7=UX2m1W~CT(dB2ibo z9o~;z*{?3s8nDw5Y_pgyE$!7DWNtQHd&CD|;-?LNfIK00Nt|_QanumQw!7TRlr?Qq zAD~6-mZkn)w-pswPB3f`bs9{$LV2A9yaD`un%uzbU_czW%_-@52(0kt!_k7K1sa5% z$@ABk>;uC!AomTSMGckTzkVBr^P{I~@bE5%7xT^0G2R6piGBpTc`R-6@s&KS`yv>! zVJXyD@GAwELAGWM_ry=!?__Hth8YET6Qq4K=Bo7hMoo4i45>!QLp_f+M2Z@dfMS&j zF)=2|;;H(j@UmUFWqN@8d 8@+_`$e#E!!&5(qiD2C?h|aAtK*OETK=eTShA_qsr_u6Du15 c0V8etXL9qQT#VY3H^w$(}X kHgUdToTNbaPyg1{oHPVR!Qt zKq2wv$X8(&s}nz}FFp55e5UoAt!<9H*EZ|pk!#g}w$(@kEeW!%M6`CvD-}giDx%Fg z!EllMPPGq42Cw;bc_vi?3Zx}@)~zO{US|9_VNwss_wA$43-kAFc|H(h@9ypC_EOD) zbKLq!?DV%AuK~EU+0ddMQptr_`+Cr~w47pAM9KqAJ>`GVGi7AqjU%gD^j`nhK~b;@ zxR{1a%~MO9tziUWr57J77n<%!&%P_nZEd wUju^v2nGAg&PA|E}O3f?){bC-)wyFV(we
`PWUChQ^(l4YK E{HP;Fvk@XD#iaWXSjs;nyJA9JI@qs5l*@$S^PxqwfH zK03Q*x-`3l%-nz;rCdt;zfm}G^qYy3J9p =wBWRVuTF*6xqQtUGUCw$NDI2 z^!R>Ld=)A4kN76{Wf7ew%GeFhnQxDgmKW*6x7vieUj-!EJdW!Bey)qntTu-nwyigl zynbFpBr5fW PfotBVmBT)>a#h_e2+NfQZ(Z5n$t sJW1QA3q8+k7@e&j hFK;36 zUApTgoW3_nzUzjk}l{8MGy}35*3%v$=pAjIq6&?&Myr?mOfXsi)uV|Xb=z*)Zd;)2i68Pwz zhq&I?HiQIy2d9r-m!Vm9=2SE<>W~Nrwu7?ypx=?5SI{EzY{|x(duoXv`I~p* *;XTK9nZ?EswsgdHF9FyRa5H4i9+B#Cc|pvAJB! z7#Oin7ub~~F|ZOt kC^b*wzY8>iOro3-ATZLDGgBCWHI%RCe#=t=4>GvaYxVqjq%IN(RA z;J1{6uFwo2ZyAR4N@kUK&PDo!;qJxs(zWhn)KgzhB+=;3lot4J&9#T0@rznW;{0tP zl>ErJuVPga7nfbA==!JA0?^+?(6oUHM`$mN=aLdmQOi}{B&*odiv=@i*v%7n_(Cor z^H76Yh_?Gh60QD+E+5crt>C8#QCV--BGP3KAzi*tJ$Yf~8tHaCa4XozIl~$2^#m{4 zXW6=N>8ucF9K#miXk~2twLsqFytD4c(nUZ2*64Ak<_`GjGo`_SjQ9lId7+) +yL4566~K8Uc|CXf`98}-EwBeqYjZ<8Pdb9+GV4|`=^p|)2uQ*kk}>3TJPvU z>4HI^=}lJhZC$#CCK)rD%0=e4qQStLTv?kMM&^uP-7aV@+l{ T;qP*4lCLZaUsuxE-F|g5j*z7M^lKt~Pwd*NRZ0OT`1^87M++l)Khwxmoy@ zAHNSskTBgrv3sr$eHIlIfSo5SzTEb9^BfcLn*cBWc{2nbhXoXBMZA;QG0RgsGVGRc z$#_@7mn6CKjxAJ2`NRU&Rr5}zNl0~8W4RwX`2-}q9~yA|SHR)pS#p^g%%{aj#G?H0 zS3W$EPMd?~?ZE9@yWpbz@^|4u97q2jRqq+pRNQv`?hqh!f`D{F07a#V2uKT|gGf=N zR{=qK?*W3+dr^8*5u}Bt^ri*~(gZ<3s-gGZ+nf7&pL3scKIB^_vuAc@?fqZZZ>`JU zyaG31rHg5jc%d=I>E3hulnb2C+c+3Zrd9@5WlPOptap5tlDenDfgJg1ztA3dn^sy9 zu`FXe>r_}&!i0N%IOv@mGz$5u9q(-s(I>oII?F W&SaRkiEhuB|wZ zG&k83%V2YOX;$2KcZ PAXX92Ac5hzwPW_hOc2fkE}S?AsSiO*o8FlthM zDCe#7aJ!8wD{&guA3Ca7`B7kPvnYwB><<<++sb2WUhR1TbJxTEDWp)Sy1d>kQXT9x zQ5hV5^I^HJUPb%bXlTEm^20LQawY$4(;Y3jWx;C8vjf9N6 cOSaXZ_6 t$o}$)xX-Ff(i8FFuF@t9R4o3N!jR2?`@! zH4;gKTmS2yH~sf9Ky;uG1Ed1?^oM~^&=BwvErup$A if7&@;j}JwmWNLcpOKXpRP;vrJ$I*BR4++;DnaI9enS1Zu-k7YIYi zb>eeCPDE d93X>Fh-Z>2uJ~fP`xI(mm4~nNMS)-QazeSNG;$hiA0TV7aaBp=5{j^ zYj8BwZA$kTABNcgLrIouKl+g_)d;@s(cM_$Yr{&u2607GfdO#VxDpCHi1ID>yXkAy znc;Ms^CZW$GtF}mOcYm{GEO?ct$*_5NtC7t`b4Rv3gSV28aJX~yV@&rLXR$n9epU< zM7mbP4f;U8Vq_Sa(KeoSgYJmrw@yTHUmq)lT>Q|!;AcYFnKb@%?GkIdsC}H)zDWJx z&jnN!(f=l>GtQb0xkQfB $=^FL=h5zjpj$%#c<@p(Vrn4~pKJJ#Q4R-7q$in4LK azO8ps6}KQgY>Gv*eK2vXk_VX`WQ%))|s)SaT!h z&;^&(E=G@896$C znYSZn&gb 9BMAbq$aIy)M;AF==(%e0se=Yh~K{saC~eEU>AOrUxR`91Oo0 zRrzXRb|_s*y=R2tYE2eE{cC1dg#Prbj+aq4`5o`9p7MFA8)o0E<<6O}x5+Qj-JoCk zeowE$pw^Uc#91ui3a@Ndz!TVD_he7y(p|2@bz?41DfoANX=Y-M-r%U;C3gkg=9!M% zgt}0}pJtb;;;gdUTq3K}n%0myyorxq$SD)3KMg-4lIt@xrP@U6KG V9KzY(Tkn?ZDm$JG=fQ?4(T9%mn<7ytAYy*>SEBF4T_#<#)mZvEbsjO!QA zu3_v?N^Tz?VQgtVb!!bqfULQ-iD!dsc&k9+r&8{iMM^3~Vp@>r)+gj8ZqdtH$ngYlwn_yU_kOS| zNE(%3^w@8zv8^aK=Hv@Gc Qi{ za%(MDHL1wSj0e!5-+2jb^#ajtj gI_*}Hc8Qj z%Mw-3;YWgmMDQ4)*9h*&!z7B&v|LJIfL{<%R<5Iyr2xQ8PKqV<0sV;2EhOiHbVQ?& z4~OWJ7g6Z@J2YgVFru52l@>^mj;V{#9W_B>1~lz1aU4kMQMb$iInsu05`9(&-b9z= zeOI{vE@UgHTfh{Vuyas2dISk;V$KIXoq{`Fx_E(Hc ;?hM~uq{@37n; z${5`05VB;gd6S5Oxbf5{DK9yGBASbDMM<~@a3zjs f!Bd77Mun35e$n3p`Cnzt#qZwkg)EtP@)zQ<(1Q zIa*@4WFbaURc>1)cSAmIGLfNvRy=?;k_M+6T=n6*t5jC8Un)J$bC&|ETm?$*BnmpE z8IRczHCW!`;4 9Al@hXlCMtAKovn;ajLS&Zs+)ITr z6OH+fHtp#5-`cH_nyr58@|xps44or={rg++JeL94uo4xYeYy>&r8zs-Z!GV56jS?Y zq)T0odLgUjWX?na?TKdm$9%sRl+{H?CI76(#2bl#+e?LQYtjZhXAB_;OtVMeHxf#0 z)Naba=dljYL+sCjp)BSlyXDU;2V;Xa5 5k_ih z64ZTVMPwf{@~#%6xTiwHWt~JHB}PjTr8=g#%jYr?$1!4JQ`I0JXCs>Czf-x5IGq90 zy7*v=&LmPUn=;whnA5MJ^f;=?2*-r|4%*z&8)GVx^8s_lwp&Lf7Hj1wgC7&<+!G{K zLswOLAZY+=s&f!``^rE1r%R&hiM-IC%Umo!u?kz-J NQ9T!DbQiu&3 |NYh5}O!X8xYe@m+0r66SUy?*JG{I(|V>JBXDLWGICt1(B}R0pxn>; zpvA8HJ)^JJomAhp;xEV46@AiMYb@?F6?|PEX7UWjO)fOm&TPAtB?jX7g^U@-w%siZ zJCDAv3+a}UeTlytPV+lIrcL5ddG QwF6 z7jgTs+gmsU$Fl`>1FP`7`DDM!dx&EvY;N}ki$(VF+R5MY%Xy4zn}9y%UR|Z)uwKtm zIJ-H{j)Eol>-2QzEZOnRuUFA{PP->z91~V$uQuHczZ_*VDPA{keNkvL!OcPG#wz|t z^JQpl|NL)m<~gIb5cppx*Hr<;jeA+<9~JW+L;@fwpxuEi@Z3j#3I^wc*3pkFDRf|l zAVZ=9kXgCT ?eY#B5r_uIfTgWFwZ$LW*NXi2(&laat>D2 z)p!Qnku;F&B6`e5>qm(r<+PFOu;ljvS^&;vqOoM4ji&?pfQ;CZynxjoVmSh<%1l^7 z0t4i{Y-9JN`K9{?eamj4Ee?n*tN7W#FVU%83YOK`)#yNV6?o$~Qz-foQ9(8J2k2a6 z4$%jYBH9WNx#VhNhfvu_w0+_~XZsJBLzvPT=i(22ov_QnCh&Q7K PTl9y=uM$HKq!il#bPb4&<;P=? +B=r&bL?DeB2 z7}YzrHwN36AYml9{an=HU9D9u)j|ethMsq{j8T*eym C?5`mQmWt&)Nt2awn=SwJ_OH?XM}kLw9|1*V&0hI9i%${a2frm1jOJJZ2Vu+l~*9 zAOGWb_6ZVrS_qphrSwv39+IzkC)k89Jj@iz*M2&E)c?MR@er{8 z$EtffM~W>)Be+?IXF1ozC6#0=jjbThxf8>zKSozY#)6Gzww*0CuuaR}L^`6|uSOJ> zdCC?_rOIjG9!bPY_d>8$DDAz0wPxN3UC0&27;g83nBsNZ^`h?4JVJ?m$nIgQs$&8? z_cG20Q*9=J{mA;WYfClhKIhf@dTT5(cdyJS#n )QmM>$Oh&Go@X+Z)I4) zUa^|(N>_yWit*fH8i9rt2Tk-zI;;2bkV|(@h~G4;m5eM*gcfhibLPdL{BBLk@uHmz zSiF+g?C}&us5;~}EjH#(Hro&MG%#z+`MWeGYEH1m+rl9?2|jaFQ@rM% 0 z)|)?&$-@C|!0`IBi8;>DU^Jdrt$Aj3=mHz}D)>BtNq>qOgSkC +neCet%soOOwPfS8(a82Q7x5=mF{bX8|K|4FuL5OdBUgspk{uFn>K>Z#j zNh5YP-4!NluysRL0>&wupU)(l7<_*Tv0MFUku4p2TA#zrH1km6=9JqrQ?dOn!=_Db zD 62O3Gexlg3)ct-72%+Cx+?^_|BFp-}O?Qz9PiTSt#Adq@~kB@E=b0 zN>n>JR5$5;*=t= y@)2ZC6bke?7+JC%b `%clh8!Y~QZnnW*3+ z7##8`Ni4iv^%phYB%_Bd-2C0@^;E^ZLZ$J>2&Q8SIl(L2Qr< =@PCa4Zdq~%B}aGMM^@@dAVMe3zH$J>eWVHCu5yBMuhLr*laXRr zC{IBxBEGz!4#tz$4F|@mO+Yfpl?*B+#bg<7_^?hgOIU}Zib~1OZdNurmzQmr=n*jk z*pyqH>^s4r=RnF3ofLs}$lW3U;2~UNLsSAyJ&oQ1o?R(%5w%ZHmW1!zmk0yUQMsW% z0IfnQ^iu&~5^ao=@)TzhUog2+w5e&;Bhr5XvW0@s_LeQxAo03Y+Jc1V_xU^_eMEnO z=Uxi0CA<8ro(t@PEx-X#h}t6?C~TDtN9&~+2H#2&A){UfZchAkc!Gp`Kz?P2ACga5 zg#^XE`Iv}kqD~74;5^}npkvX>+Lt`$Cr`APnbg!=#pfuMvKuASX!c@YSf=w~iD #f3xYO!}w49`n!J8QJ@0?4NcRu+pebTepOU4P?MCloHXr6}4XqlnXnQ-2c zJO I(J6id!QmL#R+ur?9gLVHD^idjz zzv*K#d|X~$P#Iv;Zb-)zB`&hvY+3;R&aaMe*Ob^5uv`6Rd(j_)e d`J@Y67J!_=oB$jZwes0+)aNICVnbPj$S&6z$b-Cy1p*0wcDE3D=9&&TA#k9vO+9 z;FQGt@i(*OTu+Y|iIRm66mRdPN@0c8IL_I$uYUh9bl|lZQWt;*?AA9fDpDLkKcb@1 z;Na#o++9xt-&4ihXZZ_Id=AIr#?tUOvZD8T%zn|EW$uuJ4o;d{g>Tm!N)o`NTWQB} z(~KyL@gQ#OFOJ?K*4dUB^9eS9OxxjH$4G*YrDTX>MlH#p%P+w@`|tl^W_szbn+L}o z?(DQv#Vt3q@s!3zkNdoNv*01?H9fqS6a9r rv9DJAZio|*}|JL@a%g_0fXSt$wjWPLX0o@{cyD&|O;{6TD`GK(p3 zC_P&@E=w!rSW=x@Z{YI~?ifh$Z56W^)BH*!S)wh~=z8Ph(%Yl{t|O- g`NU(wV_d8k`12mk6@deacv?Wy+^Jh=s( zdGl86XdDpNwaI}u36~l=y8XrRry*-=_lH1#s(k+~wV=0yck8DRQ~qqt)R*~9n#v=s zT)&I@g&eM3l-b42xfWDzRCb~?;jlY4%J5Y6k$0UA&8F_hJ!4AyS9sSkSg^NX&r-;g zk+;PCgTR~z9~-VGk9WKAbJ>_g2@9`heKH$*BEI!1eG_GLvgb@5PVnNJuNzDVP8hDf zS}pMK@4?DyV9 6^F$|o*8Pue=p+EZt`)rQT;Ttgm_+jzxA!@qyv|Um~{)n=g6T` z?3}twb@pryYE*A2Xk>7+FjpHp{gvPj|2-UtX%jZ*43Z0y3CKV77YJIK!0+1;;G22( z-my03`ruPh_jLm~CfN0NKKyT3+ru`mfvJZ5ooVaEyx%I<7QT8lc5nlx;ZcfaEq`aY z2F0lhJW85>i*$ZVY_O}o?PB!A-u>D_9p}ySyFTI1R=twrY`y`#$HyMTo1vz)pRcB+ zYg6V*UOM#bTmDz%^*~xDKUgf2E4%ku3y9T|HKUjS0L?}QRw8QqOKVD^0@epPA%i-| z3Puq8>A)P)98ESy8%4DM+5!0KOwjt4EqYpNB$g6H)XZ8ETzqp(-$-`$zli9NyrRW= zQq6Umt^fy+4WiNHyZYZ(G}_f~ftT zJwl_TK$W4o>Xw-j9ODt37mv~ExI34ifs E+ zj}qhLUjg=~4=nOO)6O+D0KxD8z~YRFZ9v(}|3jX|=9pYFTxMnYZpglfgiUzxI0fA| zAFmIQ4c1`G%<=o$yflFzc95HsJ-ZUi4jpc#zIdHuyZI{A*?ee8-M=!*nty+e&GNoQ z2H)hQ-HcbNDAM_8RO0@PsEtR#d>dpfc$j+uj5x(THx{k1RTX;*Y#uB(@D@RRo$lm? zP65w@(_#3CO)MLm^kB<4v428`t%1l=v;VGpN|lEK`--rEPjoXo40>k^Rm>B#ND&0Y zuf1^}1)qKn25b&HnGOmPC3`>%jO|r)mZApE(eQvrS{7tBg|u=#AzbBu=Ou`s_e;!i zioCfw0DJF??r7~J2Fk7liT%18QPiEBuszvXkn>`(^Tw@#5$?G=*VV#!ZrB|FQ5wA$ zNkc{~72{j|Si--<*6J+{(g9nJTDs;{8C5u2kL3WmkcMYm>nA-^CshAF2WRr-#(`y2 zpk)&m^1f11=3B!S!7-pKMHl!Fk55_q<5X4miuxu{GGTE2WKS|BcXR`f{=~_G+&(c^ zuZfsaPK+4w+sOOCGM>smu8O|aBQhl3dd4Y&tjZ$Z%29IOwA$8uFM4}~a*|!cV!0;Y zKcQYzoXl}F+7M~g17G-ZmV>U1L?m51(0rFK2enGP{K)i7PvG;*D5bI})4-D_ Pkr*s;+uH|6J%>n0`E!uXx74V2dgE zd3#{=o=w4e(O=gb-iy&!`|K5;DxNU~G)GirI;OSN2vx(}sXeq8rRG_! 3a4R30yLfWkqm3%K83+(fEC)!L zdgiUCN4_0-aeQ~nD9-L`se*RjXD}JDlWCpiJq{IAm)>1*raxfh+jz`Z*ks&4lfb6k zd+H?}nG=|&du5&`BW`lqtdDTft*fEYTbL+H_OG7yiDWo_W`fb^qvktj@KAxX#e6Qi zaXpXmEqgU{5%VheDCyPA@wZoqU7auF&ELcnD{BST491?Z%USn}x_8L>cG1}l#7G8e zCrUPpeJIW_(H7~h>oYuObN5w)*HTJ;f>!?AK)th(z@M0YJ9_6!mF)e7gMUsuoC-sD zo3B?I6s@NTI+IN?2lrYn)%M~!j!_TNHg;?TCb40;qh77QP28uqD*U#ajoo)6jjLCF zR2cegA_ZD@t51GG5Pxqkr}gJIBJ5S6zOfXiNP^zV_rbTHrIeclJUN*5vCS@DXES6B zQf8MGR+c%ud3@BW{N$6s<*Dap5I^UIKI+9)kNfeJN%zmzBEr$*M~`{90O39+U0i!F zUR&FpOYxc&J3YlA!dqIC)+ri=x6JqFPyQIl(aBApzO)XxW&87Hv$A78Otnn13c(^# z%CNj~==25Q?(=0!w(kGRctF8_Wn2t&X2PL>E BnrZ{=V*6W$f$8+!f4tmsxVSK(K9F% zX?F&kC!^8SmeO3n=XmD(Y1W}_YP209A87sX5>P61&YUaJd4~_M(0V*GNooQZo{{nb zd8@Q;qz^$A=oEk%c~d8XItfnp1dys4LWK_J?LnS^*>WQ-)vN%W@Ny#DIxYIe)dANA z`2vXxd>?(flz0Ll;B*tA=Ez%fOa+{N;SVh-v3FCr4RWeO-#x@ZzayNS&GYaOAEK4R z&_4N !U9KZ^de=?* zQ@OJoN6ri3wnahofWL aUWY;!GtI7j6n) z#5jSU#pIeA#aRBPMe4{|K~J$E9_mhTGvKS0HuZfACBAI7*7SXpR2X1f+#eWnY1D|DOuY(j4sSkM& zSmD5`r&rr|0NEsFTu!4f)3`otK&;JuLRnDX0xGmog!!!(Jd{7Z;Y;mC6 F~S3 zQNggLzx*!FsU~aHS-eF_cDPXD# Id2jZN@8C5zdQm9UC)L4SR#e^pC~Wo>`6FejO#4%#Dwc zyHF8=LsF3j1|zq}91qQe$}YQht43cDKWODxXU)Op{#`QvESi^Gzf}vh-ZH`_N|u7G zUViD)QkOY%LEv@!eVlEj>pW%)#7nI=_#&{Bm!`2JVv;3@8n*1W*&=;CrO4CLD;t5t zKYiM{9 jHoM%i5n3ex%>N#bjD rA8MR(= z>6Bfzu`+#^?Zc{sXE*^uS9|&OuOge(@2`T}{+9#$P4>A>zJca;S9>q7gk1S12tle~ z>0re#?3JE<1Nw-B=c#ttkC(7o(wi1j%bIN5ixH`<6p~Fz>y#52VP-LmzRm>qW6?x; zf8CCdGLrsqI$}M V4 z6ZtM`9#PVdUk9@ka`wHx&|Ii|L7ZX&!lzxM8Y=&@h2=l1SndjT$cH=xtB@Cf8)%qG zS%HXTZ8bVBVM|Z}sEULaYy)cb7l5)@(rkc!5wVAU!rj(>VlqMU2;{?sjiUy5(S~Tj z9nMgIOW^>-0X#)J6i_5_M!j%q2YpAI0Kjze(}YjLcw#8fGA4&Kp7m0_0aD1J+<|;2 zgj3DbWar4C(Fw%k;3$cZ0%<}M+EOIL{oAi`6r5b25J@+^l<-6J4YuJosz{@VUV#F@ zN$7))n_+SOz~O{sG=t?=M7cQVr%}ABMHs!#&yy~B51{mfdZiPFW(|uCLOwdnqD8X3 zqE#cD+u6npaj*`K{BsvZGKG$N(%2hwKiO9!Wk5IKl>OZ;P>~J ctG;q-FL$r(YIQ4*bTiRHd=`saA`p7v5ETqqdb z16YdFc?R!;l9}+J0v8llwYEs~qX()%nf2~i-WwWExMPztkh;9A(C2-z;O<|UAqUo5 zsIZ6fNx-K?nn|wpaZ0Ebu&|^iA=#a`0ar=(LF?!5X|+Y`1v70skz++M86wvsay@r< zUu;>?z3*|cdPr{QPvTZsKdu1OwEFp$?~hNf5f^4K-;_hy2S+hT+QTe{yJg0Ij2}F_ z`Qdx1pu2S&o-!&LhqqfE(slw_fkrPB40vzv(5k7cV%tsW6P-%rx}j%Ms!pFZ(C$9_ zxrn6=W-*?gTWHMGKH(R+cVh$;PI}m|nN!bb`^UsZxLna?N7AZspVK!!-{xXljqKpr z=#yLO8H+08CGr&GZT@NYm4dve4R@E;peoZ)=MB2^pvZ1XDg +_XOd^ck_K_9NIO{s1@-nFty6A4Ep9$_kkBY%Pbl> zz5HeOPZaZZqW2GebYbYc#N^H=zWa)c*7sL{^he36)0k}d$AB|Dx*Zyr?Fhbz4H@$; ziy!r_RCsQ&_#iu0#M3L@y;Z5m>viw6ctL~A4QUUP+~>NBnOWOe1!+A@?se}c4G&@3 zwGK~%80pmnyy;F~j=nWbwRXq|Oe=TZqFg93z6)7+7YLKw{CCIoA=jx7*t)?5$KhX; zu<%uZ`nlsM oWHQJ~$3$ IQXQ)zvrqI|2qB{$RP<+S z{$sVhXxRuJHM-C(xe=P<2N-G2;NJQENV5^$pct0p%D)i_(;mIm!xjhD61>$7Ud~DF z_&%}XNxkYb4Fa@?FfH5jZWOkXlU>cO*w3hWqEfb2uPbFTT6d^kt0}0~d>J#IXFgJ? z^3>q*gOAYb`mN6jn7i2P@-prb@w$_1e~DI^EDM?2S+i%jU)N3hO?^lFqGPw#Y^g8~ zy;pLmM>D8zlaXW%R$A|Pr(dNk8~gjZ5FFp=hO!$r)8`g+vX=z&`gf~{ePzcK_QAfL z2EOm5MbwO7{@<0djf;>bHgkm1h+s<_WDJ7&n;Zl!C# T33R!_9LWH>zRX#p!1BmwY*_m#)$uPh=#| zX3vUNLN 0T38#ksxzI0}*^O$r@2 z0=?U;4M?~wf1>2W9Rs8zSuR2IhtXm3TmT;A3b39-bq|!aenBMBQNRd~15^PB)Yb&g zjrWctX*`Ml?1DRJ{H=n!+&SHFQ-C6ltU#iGpt>@6i9(8OR?7P{Q6z=go;Rowp(jzd z_?dwxT`ErgXRu#3!6>D1hES6Gq6mv2QIM(>k %NI2 zS-3mPmd-JeJ&*wU6%7eX^5>~fc?)*oo<>LDXBsvV-@4~$dvXI0gV^4*sgZTuI?rOu zCt~dXW*GHSgir2-B(x(@jos%-$~*1T436=v?N#nU8T&-HhrZP@8!F=Cc5V1~=4vm3 ziA&eU=vQ&BVPC7qX$u7ZOqfv_U)nnzy Xv$ZjtOV zW+-vgEy;V@BO2ZenRE4erFfJ6UI-LRaS5tHy~J1<_RljlRmHdH)dFiy+kQ$MW(Tf9 zWI+qJK4_bAi9#J6^0QV ~iAEe@PJj`mOTwCJXlfPOiX?kn=3eRzZUyR$c9sV506%QGw z+>6?L@S@m)_W5Vcwc@`~PW%$Ix={vVUJtLw6UjWwPab=)PGBPVGAH8mIe6?wY`#v3 z8OdSvFsg0M?K;}47#Etc5gAv&EuM*f(KctKJLV_vQC#C)leKqk^GwHdR?jb*Fpb^o z5$prO9(l&6fsNjr^DQ2=6}k`dJmT)LXt1rG3?O>jdncRhM++y4(j==7|3or1wwi5B zI#JPi>yK_);MBvNv~0{C`;SfwRz@vJ$U*zN+v)qi*?p9}|LDsH7dO3@vq0LeY=nnV zAF9_y#O{lp_jC-x+zdCRFr$ZK;qWkqr*E^$CZgP4=}o&`8*C}xsfDC>O>PNetX&C- z$&E+Q-a1z9;Zm|X^>>0MyF+gXPC}M`Uwa87 dE*gyRMoc z^VCd_Z{;h^_>q;sm8%v0t;RB3gUhG-24Tai1>}Kni{zPb+pRMu#(c_vkJsN5m9JN0 zKV1L1{CK_M{PFrSe&yN&evDxZn|AXsfX@osJmKGrSlAMaH#}A1Y4cU*$-CoWWf?o9 zKc$)Yr}rjRvKvnujpt^YM39hiF$Ze}Wqs_Q*Z3)AWf&7vO+k`IecYdA+E{{=@~ac1 zG@%^g@LvRko|rX>={>2A{B5AE!WQ%hOoxULCV6ClTXH}LHyR3M&!D)dll`Wrl!O2f zXM^vQ(SCD;)Xv=Y;bS!PwHJ=jRVjpwNIPI1a 0)4@N!37S1 zmI0XMSZIII?0{bp>CyK=W 72!o^Q>+ Ng{0nxHX*YJcDA5eCT0bP=2q$ckF;w6E_vQM5!EZv!@qw z#Pe=EadUz0fihtM&MBeRe#Bjk%yR)O0w|I6^~fHn;JFq~a<)S&fu|y&8wHd#uZ2Tb z)Yagvp-oF2zD}e9g^3y9B<_$CZF8vPo%9oIBht)0@z?wG(~R^-di?0f4Wahak&LBO zB`PRPolh&?F_`dl8l|j;=aEhB>tYHQEGAFm k}%pO7K{-o2_j! $vu1Tp=eGB2s~gfh==TkA7iSg*n9$wgre=YZk^c-XCp})StO6$xSOI zzrduSC= +k;-6*bD?)@wI0>1$Pi(q(y<`nA!H(Z*c9qva^VtG&zuyn9NnNAW7F+?K|{ zyKP`ZEM{hrQEyAp)MRveh&BJW@HL-R3cq>din+R=ZhBvDka1&)nZ95t{D+uG%%f*= zBiwCn?fACuvgol&WAf!Z`Ar{cP7TxFqt_f>+biFgj?>$kT}l_vyn{*{mex7H%ceNO z+1|3rK6`k`G4$&2%Fy7kmJk1W7pWK1_EzA@G?;1Qepa48f~DeJBHLEZhOmiI_k~sp zZa+xCxp2OA+v&8~Gth2*YS~xpMV%yWYTTh$qB{@b1UnD7T%05T)k%!uPBCMaKKx|T zR-@B|ZGcOy>8krZk=ECdZETUILF(-1e_9DCmR(FKn^uy~S^16m*7?z){XW5Wg=4Y$ zr0HGJiKquc4V&H7mZwNl)Jy)EkZ1e|4>$g_jggqP)pg1TgV)(*y#Jb@%QIEh{1_Pw zBW2nbtE>3+RF99b1~v$K%5((XcKaE<`z7A obF$Pc6g>Ir>tM(P1()`j|DAJbXN za8bI^8Wse$Sy2EQOr8a79?)M;m^`bR$P38B>?jMNli)v2kPkd4bPk%eKy;Gl{cs)N zie|H@BH3Z11Ij L8(zdE6mcickprkBTw)m@DW? );aGL;soH&^Z)ykCpb zHG!sId>87%TifLBuHL4>*6&V?<*YgiNS4Fqd7tv#PI}>GqjBC3i7s+QEK{BqY4Px< zIO&JP!6A$Bo_(5^o>U5E*ZQl&MmcNrJzW-FH6BV%JBfcaLOegVH!K9&fO5yR;tf&s?vn|WMA 9U2 zbR6Rtd)>m95{N72y?49z^0(ZjuC(y qsJEbyo1rcb{I$2N-OWANd7pJuJb1n%Y&1!`|uFIeG{|9z}%9HToYjeBnX z=l+F+faQPb7x^x^@%huD+|beEp)9{03aw=?jQG^s_zEn`b)(#k-nI7gfa95$DNR#3 z25C;Gtu30ODk)BFv<8tLAt~*5C-S#h9CNL`CQAqSUH4mcYu>dJ)KgzG&6(x_Uy-Y{ zsQKYmdUu-!t0MoJi$Gxv@1$vGO!KYZR$Y9@2|UZ2be>1Alm^YipVPUpJ&W@n!Hg1A z^0_VTL6^JSnV;#-F0A|er#Fg9<9qt1UK+a-lQ9}Iv-ht42rSfiFTN4|pP`kZRKh#I zgzNDE#ml $2J`H?W~@?D !`wuEtQ)+^10`GovM@y;X@n zWgA5e&xx}C-+fRGG{7yS0T&(m<}cE!uQik>lsR8(Aby}Ik{#ezGBp{aWqt7%`V~@6 zkb0;v+OR_r15{@LIGQij-)WL)h#`dkYLXb*9GNm!aO){q4#X5x0n*Gv)DOZ=2a4s; zUDarYre*piGAYjjY0w!s4$4CQ3{;c+U 8iLa0b{nOXyCPaNC zleXPNGE8P)N)`+Dpd!oU1-+)~&|nJg)q8YM!2dm5E>80VR$^(2uomV2mPF^&?@F%3 zt&J7?K;fUx4*|l|!g-Tc!#dPz;L$Q%`OMl6w5iq;XR#D}lvP-9{M3mW>l{zW4t3C~ zAfYam@BHs=gCrXiDY4RD(3a@U@wZ!6s;03a^Jl~d#exe!ezvgOZ{<9q8~r`(;~BDa zH5)G%L2OH=*iZyIslyCegi`%8(X}1LB%)>^@>3NbF-T`l?7tQ=A5A?^FBxZ3SnLT6 z!d}Et>)fOzlbxmOSp#IT3>>3G`w9xXv>$pTw!7PgG(R|B5 z|L7x|`|i$e2-ym(`!uw7tr+qM>bqpu?7Z>Kb>o|_1N=NAz2Ub(eFX)yUwpW&TD+su zZ>LGoUE+^;W)sy|+|cGD^JYF+^8E`zThkv{==Xug+}NKHBa_~+7kpjj8=6nDW7jQ~ z*Gin~n%m0c)ufK$QBV0<=&T8l2m4dI(TB$>Tk;DD>+E~dv&3_(|Neb+nm%(Z5B8p2 zeh(R=qGk2NDLA`V=q2{sH>6pg3RG4pemHq+GV{7XB|HDz^q9E)Y_u|u&jOl)xm?1` zOe~|^OY{5zoER~DX+|DnEMn{u#dd7y(0JB-_{XGvJAG7aKCZqop*q%a*li)J#luVo zJlZ^zHE#wp)#lra`At1^@@6x+&MAjB;&cFps~uR%dHcnCxvqv5C82V|Jz>>}uy{OU zAlT`*wtu$%=&V~swsoX*QMPAU521K&39H}C&Jq~(Y7+jz#nW6*BBt+-SIGX9N?-Ng ziWr^AU-*#eR+q_@)3w(tMnTrMKu7qLnT-9By+`|H$L+{J?_N$o-;0NIp49+D`nZ>| z5$5*xUri;-w%Dx~XF3fSFa1cC!g`u&sJbp*I^u`dVR4CVcJ2p48B9Tx_smmIw|d|k zXQVNw3?Z|ql5LIZz3&NnJ~mf1D;9{_!0%fa`=RTOfuZY@e?xL^rU&&2_iVNm>Vop3 z*lG>k>zk77#u#m8d|iS9sWz@^9X? ?Dx?3W z&RIQ)4=Ju~ZsJbV0MQ3*K`wPX#uv$nk9Ih8Vxd3|(Jv4q8rC5=_jmm_8lgtAK(0f2 zpNd(Y_z^7G8n*Wxwn&UjAg-pxQ9oDs0tC@{kS`Iv3~lXjD-8Dx{feWsA-{|#e?&Sr zME}Z~=0NQRnE45xCDrS1rtBIa3*;SCNgqSzZdgH~KU5H&Ut?!GNX{z+h`qpr7d}Fl zp6{r3I9>oz@~p$Xz6E0Epb9AM0N8j1_9!6=gy5S1??@QbA!Razk?d;yi38s39SnD4 zh=~BuD*GFhdQH!GhsSa)JcR~LbwWDK1IL1((K{j^L=1KxkNLPAb6XDU>3$T{a6b3QXA=d+5OD oC$^HFo*s2xxe@Q`+L_vd+f3I^*+2_&sX=+X%7#6Q}y8Tq1#F$VO%8# z@eb2!oFLD$8+TP}0DNOwI~noyh$fIB_SNB@Zo$xf7JN)-hO{rXz;il%yX$kd&AIQV z&1)upeQ?=jXv@)H{sw1QoOKvzVUkM96?L#Ll|vZ%zBeB3Z(M>GJ)>>yr^7m3?BVZU z&YZP1#3{DM?h} siG)pFc?H%EPWyWTijniGtnn)!vMM z($lZ*WBfj~({`HOw|?H=zGmL`+ifj>&=|`;xTarwx3hrw(EMn)#6s)G^!1*N;i)Qk zs2WrODYQ}BEJGFPnttpDX)>wDV(?2(rXw1cN?R4TYSb0m3D(z}8C!US9yjvkxI)ui zeTavqIoTK+uL3EHCAK2?v!pP%RJ*y9W2=#E!=sPOzrg>Dl3S}jzTLZ%M+BbFyOd7p zCfAQ5;~2)=+YqP;ovAqS2Ew~-#qv$%M9sCb;y<;NM5p>|4Rw*O*pmz8^Ga7&txUvy zJd3Q#d39v#ABg*IKRxrVUgnz0ZaYuB)Y^N0%Kk9ZDs3ZQRa~1Vxt_0m@~;2Wu-^jF zwI98z>|EMUmPWIG^tStIB)WSf4HFXP+x$8=M?*uX!!kbkSah2{^0qz>So2y;dMIkuck` @#> z?K(+w17N>UL?bX0Er|`Lrle8=RS;!h3T=$8V1=R0Z8&Opr$I_uE`gRDwNMcZ4y^?8 ziyWn81eg~ ~T*AeG*Xm$$yZPg; zHJv7b#7bfjq~>~`{5u0mK1QI9v`tpYzVZ`^ cP#h8!ISo6pZ4%|q}=pfk9c@bc84jf(>pzd@^9>llG<3j)eh^~%i3xG zZMe;n>CIlM4~y;o6;Q-ME^}HOv8c50syey^-6|a_?T@_|ZY=ySsq}`49l~bPyC>FF zs=_0S{=vNICWHBYd7%Cc%Y1h8ZQu4_2cAuVz0hl7gzgLY sDVbVze)syn~dHnOBDF4UpsZpGL|v zOntMv!Zey;Z}~>RkaJS5`kC)`WBTxKu@%d3HVLXxH^0(9E~N@fJRYB#i+HksgUo}& zigzbOWVtJu2BlUjR$HA4b6hu{4t_3dZvNhEmGs@BXO39j<<=lC8yv@n`cw-$&ONNr z8NKmj>dE-5F=bk9b#cgboDW>|f9Kw``)#ImvR_JM(tdgs;bS{h7myTY?N xl7p!}zbYD)H^e04@sPf5# zs1jsPWEx1IbR3UNUN^rWggf&|h!@=oc; rH+5NHP>nFuXZ1ptp!Da=(8nBvvcc#QxObL_7wf*b!OMNvIny)C|QMG;sVRJ8W_ z;Aw{t+F z!}vuFd~C?mM@|BxkW*9tN-K&Rbf&`^jDW+Q PB*w(svF8r{wRg3 zgKNaefux+2Q0;DnRC=zOWX FYn;mJAO* z2&dkk-7D|`nupllZw(&kKIHR1m6csct>Fv-^G{pk|4qrwtX_P?Lx{oM@LVIL5bo0x zwVA>LxV-0@55~FgBWX5z >=qgvFBw|L%J;;pL?Lo^3udXKzDYEIy@U0tPGKo~ow89G zcGL5R0ty=`IG>7Klkw$j)0?j?)Ux<8)(xDbDh)_{p z#_rxnNf6BT?h9!q;X#ZpFi~hu7xT#j9a9@2|tMdN-k@0CSNG5{}YwlKE z7W8^8g$`)x)h##`4j_!PZ{fO6-#OON)uuC0Bs8OXReuwj-YYU&R*o@!eW27Q=Oy|x zN!1DUSmIbfn>gIS`3|HV+3QgAX-25H;@$n<-DBO--veh%dw|$}?eY&6`=D)OW@h|- z@DAn4;}ubFA>FFcANGW&>c?vCXwl|BYE4NK1a2w&SKR5(kKF6ieZu(BO1=WT%D3KC znG61IsZ2%?yT`rH9XMQll1~^8?|a;xqffddmbb(w F*Vq%@a^i$a`<1HQ4 3h`H$N9=C8f?6 *Da$U5o@&*zK zb?rnON+?f{*zuLYoP V@?s$c;d6Yv*w&(598k0R;6bdvjp`9F9`QEO=W0%vEaosP`NKS{k2Yi+W#=(0)-jT&5O)WqOQ${sSGeFs0 zJ>`C0iZ0@i&=tX?8#y;CKmax7i_mTaXkCkm>Vlf83Xa33?GK>5pjRb3Dm;g|iSdmB zr&F!RrmEpU7XW_WI t(04D!Q 3eO?ifwnu&Z_>tfYu-nLwQG4@(k(>+r| zX;Y_SR)c!i0RJu;8|&P zr_ePCf35bAduaUxIIe@@F}FZiOd82|8gEe&?&E!;O3%Auk2$A82SHEz0# jNUeN?d>%<6Rx|Bxb&*b{CA1*T~jOC3|uq|lIr3gfvR`!EgsJu&@Z z`ein0ZsMlcD|{5xgb;m~3ihWs$l>jkxBmUjX=WS?`qQ)7CwxJ|4VIndz60(k_`9mG z9l6<%BaJ}Q>8n32>SjYes&BqK;hK8f>>{|VQ)zy3t7^thk@HM>;aB%WJ@>J!#cwXB zCNB#9^j1^5y&c<>sLXu*K>eNTW#zp}O!9i4upP~T?9@S(vU_~YGW(eJ_Wh(7liByC zkF(^~b~uy}vKH#kT4j;k%2%Zy7@Exe9#`kO`^jiJ_=ed*|8-hL4e7n<`mZw2E8YG$ zo|f4W&41>17|I&w3}C;JoH~r`r;9V2chjdju4msV(c}2)(H*bVc95<$@Ut4bYcj2; z`gLbZLjT5NlgrC @RsSQBie>^GY$RWkmwz># zZK_l1PKXxyS@R1EtKL^HLJ=gk&mtrVR}FGWu_ifeMw<+gNIBPL oh57d%CVc-!iXiAY1n~@1{9mE+M^T)( zJ-7F_jP6(}Ky-or9+f0@IM}fA6ktZ$!MxD{02IN2OF8mspJX%EK*(b*12zjcYc8Xc zF%W+wCqMx*EnG^4-1Fc~opiafActeaxpM+D*jP!R<&_{&^E>bs#z_mp!*JPE#y=4J zm^R8L8e9&v(ZX8b3vz$201WJnEwm#Ls{M&i&HN}L^{nbQK=2<^mhO#CQJ4>PGG9QO zabFu~wD-~hfQEd7WgBnTs4_nVECA_LUK>aaw08#jP#AqbjF8N=piY(OgSqbuRpWRr zaU^MSce7v^6Nw8%r^)lZ9-#ixtp#}R1Hqt!u-Z&{#mu~1da4`k`$Q%UTXQN}+ZccZ zI>6O!H_TMI(;Hxs_W(zn0C0u-Uy?6%I}$wrhYMWCtD+cEd|@ih-;yi|T!>e)YDZZy zM(D+_p-f7Dak9NHveYmBtv6_1hzo2n{SaC4zFG<{i?+dv?W88fLK`L}Y@-G@Uhf~w z+* 5ZY)dsi*2IWa=KM}9eHCS0_RR!X5v3hNP3+&SW z7MGa5FOfQ8f3(7d&$`n2GkL|QnNNGUoYLp*mbkAjg&wHS!VhXF5#KMvR<19i-Bn?H z#DV*F`=znhQK{hv5SmFg>=Q;Rubv%ZQFS{oucNXg4Ehtw#M~=|{lc@CJ=m3as)4Fa zFk5XU@aw3Y#2v}7)hBHe6KD+$rn6PnbIbdQ$f?1>&0E;p22oci{moes{bzVSZOGb3 z>QJY f-Z`_vl&`Q`)O6y&vD;w7H}jQ?Xyx5Xx%( zY`3#sDJ5*$2-U3RJ5HR_BXidJjdzg5P?gN-JzKR>*}p4(r{%J5vqr9tisYEH2ro|G z7WW#{{WQ|d#k7?;@^rW``eTUktzhLwgDEYh4n$AgYPn@HugAH(*3O~)EOyC#<$Qwb z@cw464*r3hLQMfQ(L!!E!a<#?%&Tg<+q?4L( c;!?{s>3E{x+-h{$) zGxKb01^+ht^^+Gg*PLEEW1Q?D-eIz_5U{qJ=Ux 7Zcn3-G3oM=eGyg;s*Y3#!|~w#iuAMz``Kh z_!&yt<7PpR@J =`$p3@m` zsgiSfo{SS5{o$i0b3gOO<*!v)A-`+g-~Id0sPQ|$`IUx&xS_ge gDVIs+rLB9!uGN~UED1KQK_XeAKRi0`wJS1}P_oOb%i z@R9KfumtLBc7btF-GnwF#VQU|Z?YHgkRU{U4$Ktb)w2mfyf+X?Zw7WjGXN5&Sd&|M z47tiwL4m%95l7ws{uI?g3*Vso7@OswT~uL;X56q=Dq{FYt)uaEok{nbtSvCTB=>a! z6}HUoMs<@|qzGtP h+N=JQP*JB{|eGsgHfwqk!%oALqOV>Kw2UZc*eFZ5`<*%lFJQx1XAb*piK7Pjoz zG~hE}+G%SGu-@P#yY0m%v67!i(bmVg;u}zB>TZ6o0mEyts^^X>1!$Eh4mF*NNV+sT z4ukGAiPLDjs`F8jq_i-FHZ${0ZoaGW)GJq>@9ugezV_=KE)MIT-}p2V vQC8)>` zcx*-16qTbUY4)}89M*d9nN51{83Nj9-^3~QI4 C!DhlN`9OLrrG(&MSJA6Zw zX3%g4sXj@EhbyPKF48WDJ56gd?y3N$WbX$ FB8GqFR`Z zXY;Bm{eEF9b}&zQ0(|^SCX1Ar(mf)~i|}2EVSl~0m3;a>Wp}{ny3oR=;-d0WG?_3P zpwMq5qs~)Se`?zvdmH6n`M@W&Zqel8?)xlepDMvU@Kuj|9kktMW6o>9nHBb9dJD?M zvW%8P)Y`!Ws<>&v$8+0c2F^-P%Gs=YOyOj|HAf2nf~+aTqZMK~Z!e=!lbr;pI{MDE z vN-Rv-L=f1>IUgo!%H(yRyrzhfL zn~;|ruUtjoJFvX38u~f5OSDye$l^|+5!{vYcH$vv``ucyrs1=i9Ob5)`bVSgUcUxY zdcOXQyq)eU`fc*C-geqX1|%mWa(?h9q@{_{-r!B+UYg(I(`nnMZ+4*+B?mtXR*)Qg zx+B&7f=D;J>W-CBuc0|!ImL~I>AK3ba8k5q5AJ7%4#Cp2SKIoL?%# L_rl} zMvZ3x)if5suJ96~n}87w1N&_xUy_nT(0Egj!8QJjH*mr^Kn6&s8*3&(mSZ2oH{ z_=QpdjOQ8BHt~g0jmrfzLrpOhJP?TST!;%oP7e%wB}1d?Bx<_Dg8?MV0@MQuCd_6m z#xnFMW?F8kE7a1}BqyyiEy$uiA1Uf;_kh->FLOLsLdF7k_C2XDzCI0uxs^>)Y >6E8<^ew28fKA-fU-t}06W3;v` zkQmQtBVId&+NVgg^4q&aI!5S^Hv*f>R_*6(c#lPlSCm^33(J+qNXSNEguIyXKu$#N zlH9XzyQG&9I;tq5GH-NhiH-tQQA>}(;%oh0HM{k3sUg*gZ=j;QmZ*p7I(wgw0lAfE zCBuj=?EZ3UV0z9X%+$?_I57KII-L1A&d#e#WeCpF?ag?yEUM}kWxzeG?b7<=NobIW z@dnZ(HGo)%%#mko7S>+rm>SLIG#^*D=#{{VL`+C2#6tgRDjS7t;I$Uj@>_}#cP9r& z&7M*8Lg-Hy cp|_ ! XU;p<@2<}=cQnWV*%(^`wf)6eWx*pCWHL_a3au9c_$Ti@H0e a#?7IoxtnK>Z4?->`Rm <_3LW8m&1`@UzL)8b? z^AxH(L#^VE+eqq3S$=4{rS{9? PHQdGu~Ag=;2MO4ks>{K6x?sj8XOb=f+fi E4kw?=7Kd>LaA2*-hGfV^ zmL9-8Efiye=U%WXNLJ4RTq;8W3rHmhjy75o1{k8C&cG;~hk{d-kc2#f6qY1RTA}^W z@=tkS6xyE&Be-^h&YtFB$QainV1j1ZoSOP(JulAf)l09mG*S)s+vfnF1oi>CF!tyR zA)u7V-k@uedMbb&y6% SMP;r9zMmL&brwU)2@WD9|JUgazM(YR%B#WgAey1_|)4 zW0?o$U|_e-*FZ!M2$N^0gfD$1h359rc4LPY4m)DRmT3z>|Hh)x*9&@Y*rfOnnHJRE zVR<=$lj%wJEteg%z#5j}7n4B^ m5hAbPNl@b_8|wRqC9t6Cix=k2JeeCm!~6Ps^{41 zTPr^H@XyPTF*TkMj*S7vJp)EiP#0)X%?)_kC6vdV5U3%WQy#uP)ZqQq{~SFwVh~}g zQ^C%bhW{Dri6VYdG12LBJ!ckni#{!BZJ(eCT2K;5*-cM{Lt{N>x`ZWv?z(-=U%Y4C z-G2BpWaeZ!vE7~J`Rc2n!tPRDw 5UKWGp6>g(}>*i8GH z(91n8oT;<77N6^*x|**Drsy&|pQhsTN$Vi6|BYm?uG+Iu2dmXT<5=2zyW5lq>oj)e zJ1)06N~ hRex{d`Iysd7d&ElmnU!`a@#e7 zT@{s`lBJ$s_F{F&VA1yz%@!3HVx3V`<9-7$MKu2|3N-v(@lNw=Z$juTRSQ!-8IC1A zt(;OGmkfpCsumSO>i(jb)U&7XHN%fVf{g@90wl_JJk`i0E7Z7vU>*LEVKddlDf#=+ z9h88GcKT88yaRSlUS!GLr6kMC9ew)=sUO?A&8xfhQ{D33c !b1kA!DwGI$jzJTMGN+~E1@ z_e6;*c$#s8sel69lg)n}#xRAK=8d;Jlwvuv_Fr*~c+~wPH)DTTHa}cwOu_Gel_)v0 zBW##(SbX8Dt7z|@7S~=Cg<4hj{NYUMvzm~#@X wN8q!PFt9 z#{9I;_*xM7*LPK1(*0nlNmsmfJ9mI}o1e_108PF|w!J~Mb}4ZZZ_xG+vbl+7yPgIt zK$>2J `_mu6dY}nRGrS-u?k4jkAr> zXO!q1w#h(Qn@2$GmG9NP_Ua2)+<`F-=dsRb1^LdBUaQ-!dbD=yn={On{Ld~_8%NCn zURR4oT*6HCiX*P!E;-X{P*WkzFs%m=oit(0BLoUT=LNp{4=PhL;a|l3^h)4G9cp$8 z_u~eJ^1Y3^wOvdnsUGSeI11@)XlPC}G=SHjFUB8%<;Z_J*9?*&>Zve{KgDqw9cc`} zNZ{bXe&G=s(+gBucTJxx1u#Q q(TsyxhW6Fps82VY&k90LB0(S;`Nkv zQ}AyUnRO$K3r2_I*?l0TSnBi hnp#v25w(JuSTvd3p4Q!DbU--{$gqX>FV zq|U8vp^YuGZqRh8k- 5Z2{2-VcCDu#1$P2U&$oM--wD2(`mPj<*h!D$w@q|LD zhS}Bf*A@Hjglqo**4*}kuNk~uJ2JkB&17dD#wlwsD1v9Edr#MG+E+K^IuNXwCmg(| z;!cFLb{!FlE)rXEbVJVGB%NpLLJ_!|!+O3~v?u4isoIZNYODxPWbW28jM}w0j%MM@ zutz#9^0BE))>61tv>r-Iu4O#KGjz<*qc@~{jbJs8vB5@PWX^29eNWuRoW$Rk+ta-j ziLupQ+h;u~W7$kHO77*Ox@3*mo@7$U5iU$qJ{}Og7`z;>j42A@D_VYY%txE__~&MV zg&ps{CJnzU{Q2U=x4LX=b(Q<+N3Tsz@k8DA20f6#uFPO3&Zm<>wu2?jidF>_>!eIq zxc>rD2G&=a%&!c8+pybeV3!~)qxQ3glX)o6R>|%PD8ctKwA(8&OLs0c?G!J&TEg=& zZ8KWFv?N*k-EK;ApS;&lriMFg$(HSj@Xn+>w$pPnarU9X#N*{6)Sy9 qiR;!)yUSK2Mj5sny7?wIA*(x<~7F1I*KslC6qO|wUKGQO{;vP!!I z*kb-VqrK&}CCGacGf_h3%=?8B^JV`Qt=d~x`F8NHcYS8{B@epBQuDpFEKS5F*LK0A z_cBxRcB%Scd(oRk9lr||j?1mjT(-ZbahePNil){28<=F ns8|M9{%$q7Jpskp_IjM q2Tt_wmyZ%?3*)}ZnT~?cYXJ @*Xvwj*BDk=hzIFGPy)UZ)Y*u4qtfNaYwMk5?|WEBdDt zv>lAGXZhP*yy-#NY(FSwtsTywD9gW@8WY5GFIUc3nN-)DFr@xN51MrgjQ{eO@no}B zB=rWa7A@H>xbH8XKayU$)6*uux_w@D5dOf=<9|R?PTkboXfvuS&wyJg_Yl2o3s-9{ z#exJV7MM#FEnr6ijVbj2=rHmFzzAR`ktEH_!0X6P6o`be>j7%>67E8jfP?*4F2;SJ zo4GMcyFCgu5Xs~U01ti!I!A4!#O`ibkU*a+CBd^ ^!OPq65Li3xHW FT!Ka7!p~}1-w8yT`T$Ct&^IqGX>yI`CFTC%nn`0OfggfC!8y0` zs6K2DIar4wxl^UP1#w ?)@#T-7(J~Jo-vh!&Z*Tvp|(04IKjOY=p<^g_3Sn`v6$07AVFRN>B+wg%HP0} zqLPj#vfQ%8n%9dXTKLpqthQZk InKyYD#eLo X_^l+CQWtBjw3?BZ|R&oJp-Kp!r!L_8VB0-vkC~K-#ZUR^hs*bsdCNX$@L_O@@&Y z0!zRmEBdN%n!wh}hR5b%CqpJf+SlcWcDIn#si8!0;a yJGH*$CH`o%b<=v z&US6)IB7d%5T5RA`3*KtICJBV#l9_tU2A(WsA4CMQ}$_v!UrwHa^#!MXzcWSykr#L zS2OZr;?Gmtp +5t9>vUb3U!h700Ae5Pp37XHE(GeTpM? z(QK>k#Zgga;F7wOxaYo1HVN#(e8y)qm@+uE2z-y}Y~u-T5d9ZCAhmkOa{P$0}LDjK-nX$W07!?{cKmO0rSFCTquE6-6Pe zJEQT^$osnw6SXtr)>THMf%|Kz{x0IYIpg+n{(kE358M-{JUtSP4>)VT{=3cfQ7~h! z%og7uu=s7) kyBAL)^UDAPMWvnNfiU>K};>*7bW7`e512! z^>We#AJxY!+kpB@pj(gh^d=h3qF)Zkw>WuOyA+ryjb&{%guwmM2DM!mh2qqvE*#F! zH4x=C+&bO=G{^@CEDriN+?QU742dXeo9CcM=H=94%^I=O(?aiD*MfetIOV80_7J;g z10UIiZ~Z*F=QZCTnVq@AF}O1RLu{-pwwkSOpa~0yTK t*WggsLs|d1h zqVmX(58vj!_dDEjuFS3D`ER6yA4=CJTI KF{^5_NO4^l;Kc5{1 zI{zFvT271Uf-<2b^{1VN3B^Rw!~bo?Wov iSsv|&5#X9&WcmZ_e7WWkLY3=uM z`bFZhyb8kQD9#g@MSJhJ9 & z{_4j3Ar%!%yeOYs)V8+92E7*=pUJA%JudvWUw(*kf743^FXBvf39-;~G!c%6n~$<# zZC^0+M-PHdLwg%^paKjS#zM`b`vW!ar?~m=65!MEM@1~DiF2@!(|FS{m3hRQyq@A% zcwtL3(2;u0mZiyAkfVTEeu;hGhUp~D{Zz2~2z);Zt)_sG>MEq5-rR!tg$C%}K6WU& zRV;m$DsC)-mCE5OB1A^yBK^;gQpinu-}$!jH%9amv6Zu9yk7x~Gb_}(wvKC&vbf<{ zOi3aRo2(*vi3w4!Gch+666EDm+#IFb-wV%d#+oVvWk)Y)q$- gDJBj4?ir&<)hv58!py>y#nhM+K6oA zjOmlRyis}CHcI}N{@CK1DE(AKKOU!%!*$8Tg=^kvUPla}IKpwXbwv<5>Fc$@N4@Wt zmkM>sGB^WO!WodD$rV{ESYT~7*Mp2ye~P0w+2O;K^WY}`p~(54H-rh M3r^;^dH2c8hT)%{_3Fv|d``RgT`YfY>8!8dRM$KuPIgK~A@DiI6`E(! zI?KXtaNgRGI5FWKEMH rX1Il5ZCw@31+g-1S);A z_cN%u;OOUcZAw|#w8CMM@0PQ;-xb$8+&w-XL(0RILL;)p1M!udf7mHMR=?f6T6R*- z`{quWQKi)*=kNvA(XE`zgPi4k)ohj5T=ASkKWWRiDykI23)X|SNb4LH#=4=Ovm6c< z#%`^&D~i%peH3C7f5Y|FV?BbP%-k4pROm;J$@^n+*;|`Wj9&Nab=}t15>0aFiFVE> z&a5r6t4S|rSbp~i#G4%Xj &&)n#UO%$RRxiczQr(ri_@98OKWq8K z$FZxR?f1*0X~pCeCw&wu-0MeN*P53XdCP8*Ui-6rd*Jfp-H&p-m;Z##xrz=~{~xJ= z12UF8z4FTSJP=9+(u$161r;hW18@ioQVM}q;MFP+QNWj2#x*bx<~_zl15!963PMX3 z0)&f2P%U9q=MVISY%otHjKTt|4SkcIZC`vn)k+eC9r_fA3eyGy4w2ankj8b&^mvz2 z|6J}a5j+`5)U!tnHqco};(9h9@-)i|_xJ?UiE)`9gamH0Yh0j!cXEg+i8tHj$2t2p zLOZ61gLHTW=xVaJQ#FXh;QKgf9CvRV!i!6d0Sh$gKVZG@q~znz(~3CfN@0g&fusKQ zH=x7TAd`_tw!p1qoGJ#9OpI{@_abEy=~K07fVcl5pQ^9(Q6(W|F{ikz?f*8|(c W}8h;wF=cVkbjn{{ve({il#LINtGKWvZr>PFZu|$AAQZy z&c`eHmSj7@j+g4ikyLTeH{Pgd_NVPfz-k;HmFPPrZ1;6q!{xH@E*5WLlPCQ8xjnK( zhX|>}37DAo3(slLM|`n248&is0{dJM_^y?pwORA(e0uELwcXpq$NHhp?Y^q$KBK5C zsWbolP?q1D3>`U;&jST2|5DiPcquFMJmLIl>mA4QgvU}k1{(Y)e0}iu-bSH8|EdVm zJTv{sT}rM-1wL|is=({${`P2NR>mD$+PEsz8{rdB&?Isu?O5l=z9!E`O0qAMP|G=Q zx>1;1%QQ9~P|h}a7cI?+sE56bs>IWz4+r|b<7Lk6h01q&cE3wmA|ek6%5kVDZtp26 zRGR$(==ro(Vl1oQok6(WRwvEkBmHk68ETO(m2Ut1v(-YaF^-_?Y}1#2=~Dv4ro1@H zhXjdo5@(<{lSp|%*;?6MVU?UrvuJzc5~A?W>A1mVk~70Potq{t5ObEVG0h-BNWdRhjaF*qjq9i+>Q3L z5{s>UxU0zCT*p;bE&9Qon5L_L%HtDe*Xm}!b!h*P`@TB6o2!zx|4nwRO!rfgV4dCX z?mn!^(tE7uQn#}#={5zw4j YI-;&%G>L8 ;kJHmd zPhgqurDTmlM>tenF8WhP?MI^WJj2oKh){>bWYPk34Kxp@ zbBlTEg9^j^cGI;Mxcc=ud86AvhusTpuq=)t?1oaooR|(+d4W)>bV3b@x@n-e9k~Uo zLRW ( zQQWok>^rQ!`S#keh$@Vi&ZUDDF{y=6s@N=zD_1%%2;-Sy+ Wn3bcYU0E*uYnndM#GCuo%_mols4 b9kA zj$6LzB*O$MR#7NZcX`{y64U!3v`bJqqt`b%+ulP*1<#^FNPg&WM|MH(>?0`j*(Yi3 zeMxIs|BJkRPnv)IqCVTSzH8d28PLAQ8{8|c@rEQ44R<2hL;Q4j5Ov4u9TXg67hh}d zlie eS9ntZq7*o4_LSwyM;Qv^PST_YJz9W8Ige{qFG~Ceo^GGpeUe z+P-lYbk _@yD-iR;3FY$nisWeb>%|$F&VQgte1jqOkx^gxswi2` z-p*uKBLADzZ|N@2p42_5_j#EoVg9o6OQaP(T0a=}Rgc0{z?gf6cCX4mMLNkdQbDte zn_SY|oMD-`ZRVNf$)uDI=f_uTmL3$G&f09$@8QqM6eW51=3i%@PxCS($D4Rlw`a?8 z`5|vSc