From f8919197bd53b027b131a891dd7b917e615a6743 Mon Sep 17 00:00:00 2001 From: Hang Zhang <8041160+zhanghang1989@users.noreply.github.com> Date: Wed, 26 Sep 2018 15:59:44 -0700 Subject: [PATCH] v0.5.0 (#124) fixes https://github.com/zhanghang1989/PyTorch-Encoding/issues/118 fixes https://github.com/zhanghang1989/PyTorch-Encoding/issues/111 fixes https://github.com/zhanghang1989/PyTorch-Encoding/issues/84 fixes https://github.com/zhanghang1989/PyTorch-Encoding/issues/67 fixes https://github.com/zhanghang1989/PyTorch-Encoding/issues/88 --- docs/source/_static/js/hidebib.js | 42 ++ docs/source/_templates/layout.html | 2 +- docs/source/experiments/segmentation.rst | 52 ++- docs/source/experiments/texture.rst | 15 +- docs/source/functions.rst | 4 +- encoding/__init__.py | 2 +- encoding/datasets/__init__.py | 4 + encoding/datasets/ade20k.py | 13 +- encoding/datasets/base.py | 7 +- encoding/datasets/cityscapes.py | 180 ++++++++ encoding/datasets/coco.py | 82 ++-- encoding/datasets/pascal_aug.py | 4 +- encoding/datasets/pascal_voc.py | 10 +- encoding/datasets/pcontext.py | 6 +- encoding/dilated/resnet.py | 31 +- encoding/functions/__init__.py | 1 + encoding/functions/customize.py | 54 +++ encoding/functions/encoding.py | 19 +- encoding/functions/syncbn.py | 4 +- encoding/lib/__init__.py | 11 +- encoding/lib/cpu/.ninja_deps | Bin 0 -> 10024 bytes encoding/lib/cpu/.ninja_log | 4 + encoding/lib/cpu/build.ninja | 24 + encoding/lib/cpu/encoding_cpu.cpp | 47 ++ encoding/lib/cpu/encoding_cpu.o | Bin 0 -> 119112 bytes encoding/lib/cpu/nms_cpu.cpp | 97 ++++ encoding/lib/cpu/operator.cpp | 15 + encoding/lib/cpu/operator.h | 74 +++ encoding/lib/cpu/operator.o | Bin 0 -> 488784 bytes encoding/lib/cpu/roi_align_cpu.cpp | 8 +- encoding/lib/cpu/setup.py | 5 +- encoding/lib/cpu/syncbn_cpu.cpp | 60 +++ encoding/lib/cpu/syncbn_cpu.o | Bin 0 -> 125052 bytes encoding/lib/gpu/common.h | 145 ++++++ encoding/lib/gpu/encoding_kernel.cu | 164 +------ encoding/lib/gpu/encodingv2_kernel.cu | 426 ++++++++++++++++++ encoding/lib/gpu/nms_kernel.cu | 117 +++++ encoding/lib/gpu/operator.cpp | 13 +- encoding/lib/gpu/operator.h | 48 +- encoding/lib/gpu/roi_align_kernel.cu | 13 +- encoding/lib/gpu/setup.py | 2 + encoding/lib/gpu/syncbn_kernel.cu | 15 +- encoding/models/base.py | 4 +- encoding/models/encnet.py | 58 ++- encoding/models/fcn.py | 3 +- encoding/models/model_store.py | 21 +- encoding/models/model_zoo.py | 3 + encoding/models/psp.py | 2 +- encoding/nn/customize.py | 6 +- encoding/nn/encoding.py | 15 +- encoding/utils/__init__.py | 2 +- encoding/utils/metrics.py | 68 ++- encoding/utils/pallete.py | 3 +- experiments/recognition/dataset/minc.py | 4 +- experiments/recognition/main.py | 2 +- .../recognition/model/download_models.py | 2 +- experiments/recognition/model/mynn.py | 40 ++ experiments/recognition/option.py | 4 +- experiments/segmentation/option.py | 22 +- experiments/segmentation/test.py | 65 +-- experiments/segmentation/train.py | 26 +- scripts/prepare_cityscapes.py | 48 ++ scripts/prepare_coco.py | 8 +- scripts/prepare_minc.py | 40 ++ setup.py | 2 +- tests/unit_test/test_function.py | 238 ++++++++++ tests/unit_test/test_module.py | 78 +--- tests/unit_test/test_utils.py | 29 ++ 68 files changed, 2128 insertions(+), 485 deletions(-) create mode 100644 docs/source/_static/js/hidebib.js create mode 100644 encoding/datasets/cityscapes.py create mode 100644 encoding/functions/customize.py create mode 100644 encoding/lib/cpu/.ninja_deps create mode 100644 encoding/lib/cpu/.ninja_log create mode 100644 encoding/lib/cpu/build.ninja create mode 100644 encoding/lib/cpu/encoding_cpu.cpp create mode 100644 encoding/lib/cpu/encoding_cpu.o create mode 100644 encoding/lib/cpu/nms_cpu.cpp create mode 100644 encoding/lib/cpu/operator.cpp create mode 100644 encoding/lib/cpu/operator.h create mode 100644 encoding/lib/cpu/operator.o create mode 100644 encoding/lib/cpu/syncbn_cpu.cpp create mode 100644 encoding/lib/cpu/syncbn_cpu.o create mode 100644 encoding/lib/gpu/encodingv2_kernel.cu create mode 100644 encoding/lib/gpu/nms_kernel.cu create mode 100644 scripts/prepare_cityscapes.py create mode 100644 scripts/prepare_minc.py create mode 100644 tests/unit_test/test_function.py create mode 100644 tests/unit_test/test_utils.py diff --git a/docs/source/_static/js/hidebib.js b/docs/source/_static/js/hidebib.js new file mode 100644 index 00000000..25a99f12 --- /dev/null +++ b/docs/source/_static/js/hidebib.js @@ -0,0 +1,42 @@ +// adapted from: http://www.robots.ox.ac.uk/~vedaldi/assets/hidebib.js +function hideallbibs() +{ + var el = document.getElementsByTagName("div") ; + for (var i = 0 ; i < el.length ; ++i) { + if (el[i].className == "paper") { + var bib = el[i].getElementsByTagName("pre") ; + if (bib.length > 0) { + bib [0] .style.display = 'none' ; + } + } + } +} + +function togglebib(paperid) +{ + var paper = document.getElementById(paperid) ; + var bib = paper.getElementsByTagName('pre') ; + if (bib.length > 0) { + if (bib [0] .style.display == 'none') { + bib [0] .style.display = 'block' ; + } else { + bib [0] .style.display = 'none' ; + } + } +} + +function toggleblock(blockId) +{ + var block = document.getElementById(blockId); + if (block.style.display == 'none') { + block.style.display = 'block' ; + } else { + block.style.display = 'none' ; + } +} + +function hideblock(blockId) +{ + var block = document.getElementById(blockId); + block.style.display = 'none' ; +} diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html index 6996119b..90b1cb52 100644 --- a/docs/source/_templates/layout.html +++ b/docs/source/_templates/layout.html @@ -3,5 +3,5 @@ {%- block extrahead %} - + {% endblock %} diff --git a/docs/source/experiments/segmentation.rst b/docs/source/experiments/segmentation.rst index 8e7ad840..6e368a24 100644 --- a/docs/source/experiments/segmentation.rst +++ b/docs/source/experiments/segmentation.rst @@ -23,26 +23,34 @@ Test Pre-trained Model model = encoding.models.get_model('FCN_ResNet50_PContext', pretrained=True) + Prepare the datasets by runing the scripts in the ``scripts/`` folder, for example preparing ``PASCAL Context`` dataset:: + + python scripts/prepare_pcontext.py + The test script is in the ``experiments/segmentation/`` folder. For evaluating the model (using MS), for example ``Encnet_ResNet50_PContext``:: python test.py --dataset PContext --model-zoo Encnet_ResNet50_PContext --eval - # pixAcc: 0.7888, mIoU: 0.5056: 100%|████████████████████████| 1276/1276 [46:31<00:00, 2.19s/it] + # pixAcc: 0.792, mIoU: 0.510: 100%|████████████████████████| 1276/1276 [46:31<00:00, 2.19s/it] The command for training the model can be found by clicking ``cmd`` in the table. .. role:: raw-html(raw) :format: html -+----------------------------------+-----------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ -| Model | pixAcc | mIoU | Note | Command | Logs | -+==================================+===========+===========+===========+==============================================================================================+============+ -| Encnet_ResNet50_PContext | 78.9% | 50.6% | | :raw-html:`cmd` | ENC50PC_ | -+----------------------------------+-----------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ -| EncNet_ResNet101_PContext | 80.3% | 53.2% | | :raw-html:`cmd` | ENC101PC_ | -+----------------------------------+-----------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ -| EncNet_ResNet50_ADE | 79.9% | 41.2% | | :raw-html:`cmd` | ENC50ADE_ | -+----------------------------------+-----------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ ++----------------------------------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ +| Model | pixAcc | mIoU | Command | Logs | ++==================================+===========+===========+==============================================================================================+============+ +| Encnet_ResNet50_PContext | 79.2% | 51.0% | :raw-html:`cmd` | ENC50PC_ | ++----------------------------------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ +| EncNet_ResNet101_PContext | 80.7% | 54.1% | :raw-html:`cmd` | ENC101PC_ | ++----------------------------------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ +| EncNet_ResNet50_ADE | 80.1% | 41.5% | :raw-html:`cmd` | ENC50ADE_ | ++----------------------------------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ +| EncNet_ResNet101_ADE | 81.3% | 44.4% | :raw-html:`cmd` | ENC101ADE_ | ++----------------------------------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ +| EncNet_ResNet101_VOC | N/A | 85.9% | :raw-html:`cmd` | ENC101VOC_ | ++----------------------------------+-----------+-----------+----------------------------------------------------------------------------------------------+------------+ .. _ENC50PC: https://github.com/zhanghang1989/image-data/blob/master/encoding/segmentation/logs/encnet_resnet50_pcontext.log?raw=true .. _ENC101PC: https://github.com/zhanghang1989/image-data/blob/master/encoding/segmentation/logs/encnet_resnet101_pcontext.log?raw=true @@ -71,6 +79,19 @@ Test Pre-trained Model CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py --dataset ADE20K --model EncNet --aux --se-loss + + + + + Quick Demo ~~~~~~~~~~ @@ -116,13 +137,14 @@ Train Your Own Model CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py --dataset pcontext --model encnet --aux --se-loss -- Detail training options, please run ``python train.py -h``. +- Detail training options, please run ``python train.py -h``. Commands for reproducing pre-trained models can be found in the table. -- The validation metrics during the training only using center-crop is just for monitoring the - training correctness purpose. For evaluating the pretrained model on validation set using MS, - please use the command:: +.. hint:: + The validation metrics during the training only using center-crop is just for monitoring the + training correctness purpose. For evaluating the pretrained model on validation set using MS, + please use the command:: - CUDA_VISIBLE_DEVICES=0,1,2,3 python test.py --dataset pcontext --model encnet --aux --se-loss --resume mycheckpoint --eval + CUDA_VISIBLE_DEVICES=0,1,2,3 python test.py --dataset pcontext --model encnet --aux --se-loss --resume mycheckpoint --eval Citation -------- diff --git a/docs/source/experiments/texture.rst b/docs/source/experiments/texture.rst index 1c89f7a5..5b2dd280 100644 --- a/docs/source/experiments/texture.rst +++ b/docs/source/experiments/texture.rst @@ -17,16 +17,21 @@ Test Pre-trained Model - Install PyTorch Encoding (if not yet). Please follow the installation guide `Installing PyTorch Encoding <../notes/compile.html>`_. -- Download the `MINC-2500 `_ dataset to ``$HOME/data/minc-2500/`` folder. Download pre-trained model (pre-trained on train-1 split using single training size of 224, with an error rate of :math:`19.70\%` using single crop on test-1 set):: +- Download the `MINC-2500 `_ dataset using the providied script:: - cd PyTorch-Encoding/experiments/recognition + cd PyTorch-Encoding/ + python scripts/prepare_minc.py + +- Download pre-trained model (pre-trained on train-1 split using single training size of 224, with an error rate of :math:`19.70\%` using single crop on test-1 set):: + + cd experiments/recognition python model/download_models.py - Test pre-trained model on MINC-2500:: python main.py --dataset minc --model deepten --nclass 23 --resume deepten_minc.pth --eval # Teriminal Output: - # Loss: 1.005 | Err: 19.704% (1133/5750): 100%|████████████████████| 23/23 [00:18<00:00, 1.26it/s] + # Loss: 1.005 | Err: 18.96% (1090/5750): 100%|████████████████████| 23/23 [00:18<00:00, 1.26it/s] Train Your Own Model @@ -34,7 +39,7 @@ Train Your Own Model - Example training command for training above model:: - CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py --dataset minc --model deepten --nclass 23 --model deepten --batch-size 512 --lr 0.004 --epochs 80 --lr-step 60 + CUDA_VISIBLE_DEVICES=0,1,2,3 python main.py --dataset minc --model deepten --nclass 23 --model deepten --batch-size 512 --lr 0.004 --epochs 80 --lr-step 60 --lr-scheduler step - Detail training options:: @@ -56,8 +61,6 @@ Train Your Own Model --checkname set the checkpoint name --eval evaluating -.. todo:: - Provide example code for extracting features. Extending the Software ---------------------- diff --git a/docs/source/functions.rst b/docs/source/functions.rst index f4e0cbbf..e3f3c8c4 100644 --- a/docs/source/functions.rst +++ b/docs/source/functions.rst @@ -20,10 +20,10 @@ encoding.functions .. autofunction:: aggregate -:hidden:`scaledL2` +:hidden:`scaled_l2` ~~~~~~~~~~~~~~~~~~~ -.. autofunction:: scaledL2 +.. autofunction:: scaled_l2 :hidden:`sum_square` diff --git a/encoding/__init__.py b/encoding/__init__.py index 5dc68d55..4b0f75f9 100644 --- a/encoding/__init__.py +++ b/encoding/__init__.py @@ -10,4 +10,4 @@ """An optimized PyTorch package with CUDA backend.""" from .version import __version__ -from . import nn, functions, dilated, parallel, utils, models, datasets +from . import nn, functions, dilated, parallel, utils, models, datasets, optimizer diff --git a/encoding/datasets/__init__.py b/encoding/datasets/__init__.py index d1c09e76..cdab5d76 100644 --- a/encoding/datasets/__init__.py +++ b/encoding/datasets/__init__.py @@ -1,14 +1,18 @@ from .base import * +from .coco import COCOSegmentation from .ade20k import ADE20KSegmentation from .pascal_voc import VOCSegmentation from .pascal_aug import VOCAugSegmentation from .pcontext import ContextSegmentation +from .cityscapes import CitySegmentation datasets = { + 'coco': COCOSegmentation, 'ade20k': ADE20KSegmentation, 'pascal_voc': VOCSegmentation, 'pascal_aug': VOCAugSegmentation, 'pcontext': ContextSegmentation, + 'citys': CitySegmentation, } def get_segmentation_dataset(name, **kwargs): diff --git a/encoding/datasets/ade20k.py b/encoding/datasets/ade20k.py index db8cd339..4ad1f853 100644 --- a/encoding/datasets/ade20k.py +++ b/encoding/datasets/ade20k.py @@ -58,8 +58,8 @@ def __getitem__(self, index): return img, mask def _mask_transform(self, mask): - target = np.array(mask).astype('int32') - 1 - return torch.from_numpy(target).long() + target = np.array(mask).astype('int64') - 1 + return torch.from_numpy(target) def __len__(self): return len(self.images) @@ -90,17 +90,22 @@ def get_path_pairs(img_folder, mask_folder): img_folder = os.path.join(folder, 'images/training') mask_folder = os.path.join(folder, 'annotations/training') img_paths, mask_paths = get_path_pairs(img_folder, mask_folder) + print('len(img_paths):', len(img_paths)) + assert len(img_paths) == 20210 elif split == 'val': img_folder = os.path.join(folder, 'images/validation') mask_folder = os.path.join(folder, 'annotations/validation') img_paths, mask_paths = get_path_pairs(img_folder, mask_folder) + assert len(img_paths) == 2000 else: + assert split == 'trainval' train_img_folder = os.path.join(folder, 'images/training') train_mask_folder = os.path.join(folder, 'annotations/training') val_img_folder = os.path.join(folder, 'images/validation') val_mask_folder = os.path.join(folder, 'annotations/validation') train_img_paths, train_mask_paths = get_path_pairs(train_img_folder, train_mask_folder) val_img_paths, val_mask_paths = get_path_pairs(val_img_folder, val_mask_folder) - return train_img_paths + val_img_paths, train_mask_paths + val_mask_paths - + img_paths = train_img_paths + val_img_paths + mask_paths = train_mask_paths + val_mask_paths + assert len(img_paths) == 22210 return img_paths, mask_paths diff --git a/encoding/datasets/base.py b/encoding/datasets/base.py index 7223ced7..d2d476f9 100644 --- a/encoding/datasets/base.py +++ b/encoding/datasets/base.py @@ -37,6 +37,9 @@ def num_class(self): def pred_offset(self): raise NotImplemented + def make_pred(self, x): + return x + self.pred_offset + def _val_sync_transform(self, img, mask): outsize = self.crop_size short_size = outsize @@ -75,10 +78,6 @@ def _sync_transform(self, img, mask): ow = int(1.0 * w * oh / h) img = img.resize((ow, oh), Image.BILINEAR) mask = mask.resize((ow, oh), Image.NEAREST) - # random rotate -10~10, mask using NN rotate - deg = random.uniform(-10, 10) - img = img.rotate(deg, resample=Image.BILINEAR) - mask = mask.rotate(deg, resample=Image.NEAREST) # pad crop if short_size < crop_size: padh = crop_size - oh if oh < crop_size else 0 diff --git a/encoding/datasets/cityscapes.py b/encoding/datasets/cityscapes.py new file mode 100644 index 00000000..9324fa97 --- /dev/null +++ b/encoding/datasets/cityscapes.py @@ -0,0 +1,180 @@ +########################################################################### +# Created by: Hang Zhang +# Email: zhang.hang@rutgers.edu +# Copyright (c) 2018 +########################################################################### + +import os +import sys +import numpy as np +from tqdm import tqdm, trange +from PIL import Image, ImageOps, ImageFilter + +import torch +import torch.utils.data as data +import torchvision.transforms as transform + +from .base import BaseDataset + +class CitySegmentation(BaseDataset): + NUM_CLASS = 19 + def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', + mode=None, transform=None, target_transform=None, **kwargs): + super(CitySegmentation, self).__init__( + root, split, mode, transform, target_transform, **kwargs) + #self.root = os.path.join(root, self.BASE_DIR) + self.images, self.mask_paths = get_city_pairs(self.root, self.split) + assert (len(self.images) == len(self.mask_paths)) + if len(self.images) == 0: + raise RuntimeError("Found 0 images in subfolders of: \ + " + self.root + "\n") + self._indices = np.array(range(-1, 19)) + self._classes = np.array([0, 7, 8, 11, 12, 13, 17, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 31, 32, 33]) + self._key = np.array([-1, -1, -1, -1, -1, -1, + -1, -1, 0, 1, -1, -1, + 2, 3, 4, -1, -1, -1, + 5, -1, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, + -1, -1, 16, 17, 18]) + self._mapping = np.array(range(-1, len(self._key)-1)).astype('int32') + + def _class_to_index(self, mask): + # assert the values + values = np.unique(mask) + for i in range(len(values)): + assert(values[i] in self._mapping) + index = np.digitize(mask.ravel(), self._mapping, right=True) + return self._key[index].reshape(mask.shape) + + def _preprocess(self, mask_file): + if os.path.exists(mask_file): + masks = torch.load(mask_file) + return masks + masks = [] + print("Preprocessing mask, this will take a while." + \ + "But don't worry, it only run once for each split.") + tbar = tqdm(self.mask_paths) + for fname in tbar: + tbar.set_description("Preprocessing masks {}".format(fname)) + mask = Image.fromarray(self._class_to_index( + np.array(Image.open(fname))).astype('int8')) + masks.append(mask) + torch.save(masks, mask_file) + return masks + + def __getitem__(self, index): + img = Image.open(self.images[index]).convert('RGB') + if self.mode == 'test': + if self.transform is not None: + img = self.transform(img) + return img, os.path.basename(self.images[index]) + #mask = self.masks[index] + mask = Image.open(self.mask_paths[index]) + # synchrosized transform + if self.mode == 'train': + img, mask = self._sync_transform(img, mask) + elif self.mode == 'val': + img, mask = self._val_sync_transform(img, mask) + else: + assert self.mode == 'testval' + mask = self._mask_transform(mask) + # general resize, normalize and toTensor + if self.transform is not None: + img = self.transform(img) + if self.target_transform is not None: + mask = self.target_transform(mask) + return img, mask + + def _sync_transform(self, img, mask): + # random mirror + if random.random() < 0.5: + img = img.transpose(Image.FLIP_LEFT_RIGHT) + mask = mask.transpose(Image.FLIP_LEFT_RIGHT) + crop_size = self.crop_size + # random scale (short edge from 480 to 720) + short_size = random.randint(int(self.base_size*0.5), int(self.base_size*2.5)) + w, h = img.size + if h > w: + ow = short_size + oh = int(1.0 * h * ow / w) + else: + oh = short_size + ow = int(1.0 * w * oh / h) + img = img.resize((ow, oh), Image.BILINEAR) + mask = mask.resize((ow, oh), Image.NEAREST) + # random rotate -10~10, mask using NN rotate + deg = random.uniform(-10, 10) + img = img.rotate(deg, resample=Image.BILINEAR) + mask = mask.rotate(deg, resample=Image.NEAREST) + # pad crop + if short_size < crop_size: + padh = crop_size - oh if oh < crop_size else 0 + padw = crop_size - ow if ow < crop_size else 0 + img = ImageOps.expand(img, border=(0, 0, padw, padh), fill=0) + mask = ImageOps.expand(mask, border=(0, 0, padw, padh), fill=0) + # random crop crop_size + w, h = img.size + x1 = random.randint(0, w - crop_size) + y1 = random.randint(0, h - crop_size) + img = img.crop((x1, y1, x1+crop_size, y1+crop_size)) + mask = mask.crop((x1, y1, x1+crop_size, y1+crop_size)) + # gaussian blur as in PSP + if random.random() < 0.5: + img = img.filter(ImageFilter.GaussianBlur( + radius=random.random())) + # final transform + return img, self._mask_transform(mask) + + def _mask_transform(self, mask): + #target = np.array(mask).astype('int32') - 1 + target = self._class_to_index(np.array(mask).astype('int32')) + return torch.from_numpy(target).long() + + def __len__(self): + return len(self.images) + + def make_pred(self, mask): + values = np.unique(mask) + for i in range(len(values)): + assert(values[i] in self._indices) + index = np.digitize(mask.ravel(), self._indices, right=True) + return self._classes[index].reshape(mask.shape) + + +def get_city_pairs(folder, split='train'): + def get_path_pairs(img_folder, mask_folder): + img_paths = [] + mask_paths = [] + for root, directories, files in os.walk(img_folder): + for filename in files: + if filename.endswith(".png"): + imgpath = os.path.join(root, filename) + foldername = os.path.basename(os.path.dirname(imgpath)) + maskname = filename.replace('leftImg8bit','gtFine_labelIds') + maskpath = os.path.join(mask_folder, foldername, maskname) + if os.path.isfile(imgpath) and os.path.isfile(maskpath): + img_paths.append(imgpath) + mask_paths.append(maskpath) + else: + print('cannot find the mask or image:', imgpath, maskpath) + print('Found {} images in the folder {}'.format(len(img_paths), img_folder)) + return img_paths, mask_paths + + if split == 'train' or split == 'val' or split == 'test': + img_folder = os.path.join(folder, 'leftImg8bit/' + split) + mask_folder = os.path.join(folder, 'gtFine/'+ split) + img_paths, mask_paths = get_path_pairs(img_folder, mask_folder) + return img_paths, mask_paths + else: + assert split == 'trainval' + print('trainval set') + train_img_folder = os.path.join(folder, 'leftImg8bit/train') + train_mask_folder = os.path.join(folder, 'gtFine/train') + val_img_folder = os.path.join(folder, 'leftImg8bit/val') + val_mask_folder = os.path.join(folder, 'gtFine/val') + train_img_paths, train_mask_paths = get_path_pairs(train_img_folder, train_mask_folder) + val_img_paths, val_mask_paths = get_path_pairs(val_img_folder, val_mask_folder) + img_paths = train_img_paths + val_img_paths + mask_paths = train_mask_paths + val_mask_paths + return img_paths, mask_paths diff --git a/encoding/datasets/coco.py b/encoding/datasets/coco.py index 7fcbf80c..6bd39194 100644 --- a/encoding/datasets/coco.py +++ b/encoding/datasets/coco.py @@ -6,51 +6,26 @@ from .base import BaseDataset -""" -NUM_CHANNEL = 91 -[] background -[5] airplane -[2] bicycle -[16] bird -[9] boat -[44] bottle -[6] bus -[3] car -[17] cat -[62] chair -[21] cow -[67] dining table -[18] dog -[19] horse -[4] motorcycle -[1] person -[64] potted plant -[20] sheep -[63] couch -[7] train -[72] tv -""" -CAT_LIST = [0, 5, 2, 16, 9, 44, 6, 3, 17, 62, 21, 67, 18, 19, 4, - 1, 64, 20, 63, 7, 72] - - class COCOSegmentation(BaseDataset): + NUM_CLASS = 21 + CAT_LIST = [0, 5, 2, 16, 9, 44, 6, 3, 17, 62, 21, 67, 18, 19, 4, + 1, 64, 20, 63, 7, 72] def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', - mode=None, transform=None, target_transform=None): + mode=None, transform=None, target_transform=None, **kwargs): super(COCOSegmentation, self).__init__( - root, split, mode, transform, target_transform) + root, split, mode, transform, target_transform, **kwargs) from pycocotools.coco import COCO from pycocotools import mask - if mode == 'train': + if split == 'train': print('train set') - ann_file = os.path.join(root, 'coco/annotations/instances_train2014.json') - ids_file = os.path.join(root, 'coco/annotations/train_ids.pth') - root = os.path.join(root, 'coco/train2014') + ann_file = os.path.join(root, 'annotations/instances_train2017.json') + ids_file = os.path.join(root, 'annotations/train_ids.pth') + self.root = os.path.join(root, 'train2017') else: print('val set') - ann_file = os.path.join(root, 'coco/annotations/instances_val2014.json') - ids_file = os.path.join(root, 'coco/annotations/val_ids.pth') - root = os.path.join(root, 'coco/val2014') + ann_file = os.path.join(root, 'annotations/instances_val2017.json') + ids_file = os.path.join(root, 'annotations/val_ids.pth') + self.root = os.path.join(root, 'val2017') self.coco = COCO(ann_file) self.coco_mask = mask if os.path.exists(ids_file): @@ -68,8 +43,8 @@ def __getitem__(self, index): path = img_metadata['file_name'] img = Image.open(os.path.join(self.root, path)).convert('RGB') cocotarget = coco.loadAnns(coco.getAnnIds(imgIds=img_id)) - mask = Image.fromarray(self._gen_seg_mask(cocotarget, img_metadata['height'], - img_metadata['width'])) + mask = Image.fromarray(self._gen_seg_mask( + cocotarget, img_metadata['height'], img_metadata['width'])) # synchrosized transform if self.mode == 'train': img, mask = self._sync_transform(img, mask) @@ -95,8 +70,8 @@ def _gen_seg_mask(self, target, h, w): rle = coco_mask.frPyObjects(instance['segmentation'], h, w) m = coco_mask.decode(rle) cat = instance['category_id'] - if cat in CAT_LIST: - c = CAT_LIST.index(cat) + if cat in self.CAT_LIST: + c = self.CAT_LIST.index(cat) else: continue if len(m.shape) < 3: @@ -124,3 +99,28 @@ def _preprocess(self, ids, ids_file): print('Found number of qualified images: ', len(new_ids)) torch.save(new_ids, ids_file) return new_ids +""" +NUM_CHANNEL = 91 +[] background +[5] airplane +[2] bicycle +[16] bird +[9] boat +[44] bottle +[6] bus +[3] car +[17] cat +[62] chair +[21] cow +[67] dining table +[18] dog +[19] horse +[4] motorcycle +[1] person +[64] potted plant +[20] sheep +[63] couch +[7] train +[72] tv +""" + diff --git a/encoding/datasets/pascal_aug.py b/encoding/datasets/pascal_aug.py index a0b9c9a6..014f0309 100644 --- a/encoding/datasets/pascal_aug.py +++ b/encoding/datasets/pascal_aug.py @@ -15,8 +15,8 @@ class VOCAugSegmentation(BaseDataset): ] NUM_CLASS = 21 TRAIN_BASE_DIR = 'VOCaug/dataset/' - def __init__(self, root, split='train', mode=None, transform=None, - target_transform=None, **kwargs): + def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', + mode=None, transform=None, target_transform=None, **kwargs): super(VOCAugSegmentation, self).__init__(root, split, mode, transform, target_transform, **kwargs) # train/val/test splits are pre-cut diff --git a/encoding/datasets/pascal_voc.py b/encoding/datasets/pascal_voc.py index 8a836fe3..b8b5d646 100644 --- a/encoding/datasets/pascal_voc.py +++ b/encoding/datasets/pascal_voc.py @@ -16,8 +16,8 @@ class VOCSegmentation(BaseDataset): ] NUM_CLASS = 21 BASE_DIR = 'VOCdevkit/VOC2012' - def __init__(self, root, split='train', mode=None, transform=None, - target_transform=None, **kwargs): + def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', + mode=None, transform=None, target_transform=None, **kwargs): super(VOCSegmentation, self).__init__(root, split, mode, transform, target_transform, **kwargs) _voc_root = os.path.join(self.root, self.BASE_DIR) @@ -65,10 +65,8 @@ def __getitem__(self, index): mask = self._mask_transform(mask) # general resize, normalize and toTensor if self.transform is not None: - #print("transform for input") img = self.transform(img) if self.target_transform is not None: - #print("transform for label") target = self.target_transform(target) return img, target @@ -79,3 +77,7 @@ def _mask_transform(self, mask): def __len__(self): return len(self.images) + + @property + def pred_offset(self): + return 0 diff --git a/encoding/datasets/pcontext.py b/encoding/datasets/pcontext.py index 9a981ae2..0e57545c 100644 --- a/encoding/datasets/pcontext.py +++ b/encoding/datasets/pcontext.py @@ -26,7 +26,6 @@ def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', root = os.path.join(root, self.BASE_DIR) annFile = os.path.join(root, 'trainval_merged.json') imgDir = os.path.join(root, 'JPEGImages') - mask_file = os.path.join(root, self.split+'.pth') # training mode self.detail = Detail(annFile, imgDir, split) self.transform = transform @@ -40,6 +39,8 @@ def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', 68, 326, 72, 458, 34, 207, 80, 355, 85, 347, 220, 349, 360, 98, 187, 104, 105, 366, 189, 368, 113, 115])) self._key = np.array(range(len(self._mapping))).astype('uint8') + mask_file = os.path.join(root, self.split+'.pth') + print('mask_file:', mask_file) if os.path.exists(mask_file): self.masks = torch.load(mask_file) else: @@ -48,7 +49,6 @@ def __init__(self, root=os.path.expanduser('~/.encoding/data'), split='train', def _class_to_index(self, mask): # assert the values values = np.unique(mask) - #assert(values.size > 1) for i in range(len(values)): assert(values[i] in self._mapping) index = np.digitize(mask.ravel(), self._mapping, right=True) @@ -89,10 +89,8 @@ def __getitem__(self, index): mask = self._mask_transform(mask) # general resize, normalize and toTensor if self.transform is not None: - #print("transform for input") img = self.transform(img) if self.target_transform is not None: - #print("transform for label") mask = self.target_transform(mask) return img, mask diff --git a/encoding/dilated/resnet.py b/encoding/dilated/resnet.py index 0f99c2e9..e2feaa83 100644 --- a/encoding/dilated/resnet.py +++ b/encoding/dilated/resnet.py @@ -10,9 +10,6 @@ model_urls = { 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', - 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', - 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', - 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', } @@ -135,12 +132,24 @@ class ResNet(nn.Module): - Yu, Fisher, and Vladlen Koltun. "Multi-scale context aggregation by dilated convolutions." """ # pylint: disable=unused-variable - def __init__(self, block, layers, num_classes=1000, dilated=True, norm_layer=nn.BatchNorm2d): - self.inplanes = 64 + def __init__(self, block, layers, num_classes=1000, dilated=True, + deep_base=True, norm_layer=nn.BatchNorm2d): + self.inplanes = 128 if deep_base else 64 super(ResNet, self).__init__() - self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, - bias=False) - self.bn1 = norm_layer(64) + if deep_base: + self.conv1 = nn.Sequential( + nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False), + norm_layer(64), + nn.ReLU(inplace=True), + nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1, bias=False), + norm_layer(64), + nn.ReLU(inplace=True), + nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1, bias=False), + ) + else: + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = norm_layer(self.inplanes) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, layers[0], norm_layer=norm_layer) @@ -155,7 +164,7 @@ def __init__(self, block, layers, num_classes=1000, dilated=True, norm_layer=nn. norm_layer=norm_layer) self.layer4 = self._make_layer(block, 512, layers[3], stride=2, norm_layer=norm_layer) - self.avgpool = nn.AvgPool2d(7) + self.avgpool = nn.AvgPool2d(7, stride=1) self.fc = nn.Linear(512 * block.expansion, num_classes) for m in self.modules(): @@ -270,5 +279,7 @@ def resnet152(pretrained=False, root='~/.encoding/models', **kwargs): """ model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs) if pretrained: - model.load_state_dict(model_zoo.load_url(model_urls['resnet152'])) + from ..models.model_store import get_model_file + model.load_state_dict(torch.load( + get_model_file('resnet152', root=root)), strict=False) return model diff --git a/encoding/functions/__init__.py b/encoding/functions/__init__.py index 1531ccbe..9113739d 100644 --- a/encoding/functions/__init__.py +++ b/encoding/functions/__init__.py @@ -1,3 +1,4 @@ """Encoding Autograd Fuctions""" from .encoding import * from .syncbn import * +from .customize import * diff --git a/encoding/functions/customize.py b/encoding/functions/customize.py new file mode 100644 index 00000000..f54eb2fb --- /dev/null +++ b/encoding/functions/customize.py @@ -0,0 +1,54 @@ +##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +## Created by: Hang Zhang +## Email: zhanghang0704@gmail.com +## Copyright (c) 2018 +## +## This source code is licensed under the MIT-style license found in the +## LICENSE file in the root directory of this source tree +##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +"""Customized functions""" + +import torch +from torch.autograd import Variable, Function +from .. import lib + +__all__ = ['NonMaxSuppression'] + +def NonMaxSuppression(boxes, scores, threshold): + r"""Non-Maximum Suppression + The algorithm begins by storing the highest-scoring bounding + box, and eliminating any box whose intersection-over-union (IoU) + with it is too great. The procedure repeats on the surviving + boxes, and so on until there are no boxes left. + The stored boxes are returned. + + NB: The function returns a tuple (mask, indices), where + indices index into the input boxes and are sorted + according to score, from higest to lowest. + indices[i][mask[i]] gives the indices of the surviving + boxes from the ith batch, sorted by score. + + Args: + - boxes :math:`(N, n_boxes, 4)` + - scroes :math:`(N, n_boxes)` + - threshold (float): IoU above which to eliminate boxes + + Outputs: + - mask: :math:`(N, n_boxes)` + - indicies: :math:`(N, n_boxes)` + + Examples:: + + >>> boxes = torch.Tensor([[[10., 20., 20., 15.], + >>> [24., 22., 50., 54.], + >>> [10., 21., 20. 14.5]]]) + >>> scores = torch.abs(torch.randn([1, 3])) + >>> mask, indices = NonMaxSuppression(boxes, scores, 0.7) + >>> #indices are SORTED according to score. + >>> surviving_box_indices = indices[mask] + """ + if boxes.is_cuda: + return lib.gpu.non_max_suppression(boxes, scores, threshold) + else: + return lib.cpu.non_max_suppression(boxes, scores, threshold) diff --git a/encoding/functions/encoding.py b/encoding/functions/encoding.py index 3864205e..286e87cb 100644 --- a/encoding/functions/encoding.py +++ b/encoding/functions/encoding.py @@ -13,7 +13,7 @@ import torch.nn.functional as F from .. import lib -__all__ = ['aggregate', 'scaledL2', 'pairwise_cosine'] +__all__ = ['aggregate', 'scaled_l2', 'pairwise_cosine'] class _aggregate(Function): @staticmethod @@ -23,7 +23,7 @@ def forward(ctx, A, X, C): if A.is_cuda: E = lib.gpu.aggregate_forward(A, X, C) else: - raise NotImplemented + E = lib.cpu.aggregate_forward(A, X, C) return E @staticmethod @@ -32,7 +32,7 @@ def backward(ctx, gradE): if A.is_cuda: gradA, gradX, gradC = lib.gpu.aggregate_backward(gradE, A, X, C) else: - raise NotImplemented + gradA, gradX, gradC = lib.cpu.aggregate_backward(gradE, A, X, C) return gradA, gradX, gradC def aggregate(A, X, C): @@ -60,13 +60,13 @@ def aggregate(A, X, C): """ return _aggregate.apply(A, X, C) -class _scaledL2(Function): +class _scaled_l2(Function): @staticmethod def forward(ctx, X, C, S): if X.is_cuda: SL = lib.gpu.scaled_l2_forward(X, C, S) else: - raise NotImplemented + SL = lib.cpu.scaled_l2_forward(X, C, S) ctx.save_for_backward(X, C, S, SL) return SL @@ -76,12 +76,11 @@ def backward(ctx, gradSL): if X.is_cuda: gradX, gradC, gradS = lib.gpu.scaled_l2_backward(gradSL, X, C, S, SL) else: - raise NotImplemented + gradX, gradC, gradS = lib.cpu.scaled_l2_backward(gradSL, X, C, S, SL) return gradX, gradC, gradS - -def scaledL2(X, C, S): - r""" scaledL2 distance +def scaled_l2(X, C, S): + r""" scaled_l2 distance .. math:: sl_{ik} = s_k \|x_i-c_k\|^2 @@ -93,7 +92,7 @@ def scaledL2(X, C, S): :math:`K` is number is codewords, :math:`D` is feature dimensions.) - Output: :math:`E\in\mathcal{R}^{B\times N\times K}` """ - return _scaledL2.apply(X, C, S) + return _scaled_l2.apply(X, C, S) # Experimental def pairwise_cosine(X, C, normalize=False): diff --git a/encoding/functions/syncbn.py b/encoding/functions/syncbn.py index 3bc2bec7..cf7dd615 100644 --- a/encoding/functions/syncbn.py +++ b/encoding/functions/syncbn.py @@ -26,7 +26,7 @@ def forward(ctx, input): if input.is_cuda: xsum, xsqusum = lib.gpu.sumsquare_forward(input) else: - raise NotImplemented + xsum, xsqusum = lib.cpu.sumsquare_forward(input) return xsum, xsqusum @staticmethod @@ -46,7 +46,7 @@ def forward(ctx, input, mean, std, gamma, beta): if input.is_cuda: output = lib.gpu.batchnorm_forward(input, mean, std, gamma, beta) else: - raise NotImplemented + output = lib.cpu.batchnorm_forward(input, mean, std, gamma, beta) return output @staticmethod diff --git a/encoding/lib/__init__.py b/encoding/lib/__init__.py index b853fa5e..ff821e05 100644 --- a/encoding/lib/__init__.py +++ b/encoding/lib/__init__.py @@ -6,15 +6,20 @@ cpu_path = os.path.join(cwd, 'cpu') gpu_path = os.path.join(cwd, 'gpu') -cpu = load( 'enclib_cpu', [ - os.path.join(cpu_path, 'roi_align.cpp'), +cpu = load('enclib_cpu', [ + os.path.join(cpu_path, 'operator.cpp'), + os.path.join(cpu_path, 'encoding_cpu.cpp'), + os.path.join(cpu_path, 'syncbn_cpu.cpp'), os.path.join(cpu_path, 'roi_align_cpu.cpp'), + os.path.join(cpu_path, 'nms_cpu.cpp'), ], build_directory=cpu_path, verbose=False) if torch.cuda.is_available(): - gpu = load( 'enclib_gpu', [ + gpu = load('enclib_gpu', [ os.path.join(gpu_path, 'operator.cpp'), os.path.join(gpu_path, 'encoding_kernel.cu'), + os.path.join(gpu_path, 'encodingv2_kernel.cu'), os.path.join(gpu_path, 'syncbn_kernel.cu'), os.path.join(gpu_path, 'roi_align_kernel.cu'), + os.path.join(gpu_path, 'nms_kernel.cu'), ], build_directory=gpu_path, verbose=False) diff --git a/encoding/lib/cpu/.ninja_deps b/encoding/lib/cpu/.ninja_deps new file mode 100644 index 0000000000000000000000000000000000000000..de22220977a8dfcf9205cb8ccd78fe96e7fd51b9 GIT binary patch literal 10024 zcmc(lca#)09LFPe?7b^?EVnCS?8aLyOIJ+x8emLJ%b(ziY8MnupYKY1!t|=x?w& zvgNrM!>CP@Ab*9u*5|sD+LeO&iOSlB%f=^V?Hh9GcESUVAwdUUU9t=vDVTj&Jj{SX&IUVJr zaY0qdXy4OlcQ=lP%t&+aF6_467j#&GPeW!F%y(dK1|d_hJ(2Tg4;wodehvV#51j|h z3+4m!g9X5XU?5lsEDRO_i-N_#;$R7|Bv=Z_K6V+fELaXK4^{vxf|bC^U=^?`SPcvU z(nb)wtn8+p@7vjU=eX}}u!gSpt=f23haWlKHSqKQ95caTyW%dM9RMk`Hh;&CxvbE zUY_&@uPFKOW88l#7IuV})qKPbWtj?jNzsSz3Av4v7Zv5Y%(paQkkn23EZ`WvSi<) zq*Jl#kr8sUk`B%>>IWE7vX9=Rq!S9tco~m;iO!ACX?`aAaS+l_dbvT#M_^m5Uenhr z=`fB*wk&1jIwc*vSBkpMwMsfw?$%YvHA*@L4{$VR!CeiV<~i%50?)iv96wh3^9m(jE_FC=1Pya^xzd>Afx?ml^)e-SA5YVGQG>>% zitOYKkanG08=WI<{5eEN00{yHIH?jQ1w5+Js!7 z(^czZ&sCDAWmF`Uo!~i2F_mrH&GV6; zt>iDVq&Cuc!^aMQj^)@{K&Q%N?RwlkFC!2T=SOvG`zqC9dFdPc!KOoJ7a#X2sQL@~MyaOMlJ1;Wf76L}BM{0|?mh+@ND3~*K z$BgZo3w%Dp`8fl&^{lt_o0L4nr|Zr`()Je2BXrxfTuVMI9Nwzk^gW6Nf)%?vyGp!2 zJ=vzgZcA&h!EUmn*7h`6Y*S%d&z=^9@`39xjj?vb=mRa*Egwo`tcSvOOEuPzhmP90 zI7OpP{HB8!p%4zyn1gh_4Bm?ii_F2nux+%mpEcxwMxj>~)To_`rCutm?0f%Y_}Ndl zp9Tj9X|$i!w0xH>1z}W1q7H=pmiGO|JO%h2m)d8aNwBMHE#~>>M)KV+));e|lk~N) zA9ly-o=f@cAahbOJr7s)KKRgHms!1(dht6(@P&Ep)i{`4Sz~?IB ygNZ +#include + +at::Tensor Aggregate_Forward_CPU( + const at::Tensor A, + const at::Tensor X, + const at::Tensor C) { + auto E = (A.unsqueeze(3) * (X.unsqueeze(2).expand({X.size(0), X.size(1), + C.size(0), C.size(1)}) - C.unsqueeze(0).unsqueeze(0))).sum(1); + return E; +} + +std::vector Aggregate_Backward_CPU( + const at::Tensor GE, + const at::Tensor A, + const at::Tensor X, + const at::Tensor C) { + auto gradA = (GE.unsqueeze(1) * (X.unsqueeze(2).expand({X.size(0), X.size(1), + C.size(0), C.size(1)}) - C.unsqueeze(0).unsqueeze(0))).sum(3); + auto gradX = at::bmm(A, GE); + auto gradC = (-GE * A.sum(1).unsqueeze(2)).sum(0); + return {gradA, gradX, gradC}; +} + +at::Tensor ScaledL2_Forward_CPU( + const at::Tensor X, + const at::Tensor C, + const at::Tensor S) { + auto SL = S.view({1, 1, C.size(0)}) * (X.unsqueeze(2).expand({X.size(0), X.size(1), + C.size(0), C.size(1)}) - C.unsqueeze(0).unsqueeze(0)).pow(2).sum(3); + return SL; +} + +std::vector ScaledL2_Backward_CPU( + const at::Tensor GSL, + const at::Tensor X, + const at::Tensor C, + const at::Tensor S, + const at::Tensor SL) { + auto tmp = (2 * GSL * S.view({1, 1, C.size(0)})).unsqueeze(3) * + (X.unsqueeze(2).expand({X.size(0), X.size(1), C.size(0), C.size(1)}) - + C.unsqueeze(0).unsqueeze(0)); + auto GX = tmp.sum(2); + auto GC = tmp.sum(0).sum(0); + auto GS = (GSL * (SL / S.view({1, 1, C.size(0)}))).sum(0).sum(0); + return {GX, GC, GS}; +} diff --git a/encoding/lib/cpu/encoding_cpu.o b/encoding/lib/cpu/encoding_cpu.o new file mode 100644 index 0000000000000000000000000000000000000000..6819e99365ecdadb3ff6e73bbdc2ec589edc9457 GIT binary patch literal 119112 zcmeFa4|wKjS>O8(v#?nZ-k_+dP&e9bJFA|WRl%%lA%he6jV79{qfWI=j}}lGH)l>in}r8R)n%O`vB z-2!>(-V?PE#eGX{q328SegCUpecx*@zWTLuZ@lk~uln0Z<6BGdEZi2Mwx4#Q_5{Ts zzE}VKH~#Psy#D)BB1hvJduHToE;76)?rX#U z(eh6GwFope8t&)siuh)KAJau`DxW-jZ1RJ( z)4!(9Ihk)wmFG6HymS8g_3Kml2eZ62>#s+$yg9>HmX{e|PsyMDHRb+H{;sUNw3-!d z`Lx5nT(fd==dO3=zm*joIS_uLa_4)T@9T<*tqVk*w@oaI|eDeuLYb+{(7R^xTMPotTKN zCHDb`EgUw;+Io+=?kXjv`K`O&NyVyxd#*&bWlXWAk4`t*l)cKsTJ;g7AjpO(^rI7> z;8$<1Qw{rXQyacF%O_Xz>9_0`1DGb1p4%%2ux}f{G+=r@zZgt;?NX|&{E0KY|BZ^W zuRgLE8(9H$`Y^kDtKQV&krmPZFJIFbdgY^8acRYg`_GccSL5+f23MBP^E-Et6~F?R zPwquFi^;vLac%UKkG`(*KQ`e19N{kQWg2#qyF{^*f(>WI&klc;@-R)T^6^+!Ow#Yp zZD;u-Q^n-=>Fe82c8PuLm5@+po|W zYE}IX_u?boV*Vf^Yg`+qk@@%6j`*Kg+&bIqA$Ocuocvf6>=Fsl?368^j9SZzVVZqp zG@AX)1JfTnz3|}^6g#ry=BL6QnXJG4D&^gZ@@|vOiC%dlf5oN4zI@ehXMIWQ!4fk# zO)^zHQ-zu?ZkwWEPJ8;D%-=gzKD75)O>>7Hup*3d0yO>r_`KviEbbXKmF%4H?`5xlCnyf|FjB8&<)`t|d))(~f|LFx4ouDEO zt`Cvj<^c{-HF}9i;=Vt8>P>tMFx8{(8=2?PAa!ec8G&;G8OXA-u?Ee)WdHRsOp{ ziL{#v--F$9^|hP*wam^YiV05v+bX9|p*lWYg?(-~HXqCFABnz4?+RMC`|siKsZsoW_| z<%z@VoC==;Oolj5*t5ZOeie_IoHj|iRy(^nF=W`-7{=?Y!ftvg7L}AlA*C6Cym`T( zRAF!v26r|mP$clh%&-d;%T}CQIGb-nS!Cr{Vm0D-Tb4ha#b}uoU^#1C9Abj(Y*3-~ z``=26SsC>PTZ0FFbF5T$zp-xfN!CslrQ$BHt;O_ORy;c^&cZc5hpBUNl^FPCH^#94 zEhMGo)EsPHKF=lSB?C7y3aJQ(ytz*P^72>tyZ>`^?KC*FG+xabS4W6vRb3)4R|JZt zL1TlZQ2l2aVsypA-xSE+@5bF;>&-1?pzNp{h0n61{#Sjg*QOZ$y4ocxUPRg8o;K^v zf37vA@H&xQzNQuDm5;h;TLj9>Yy91RM^)TD+Jnc?e(V_9D~_Sva}4dd&?v%zQdQ0N zlB!Cv+bu=eS?>E@>igd5``+&R-mAX%e-ZxhNCNx!A47ZHF|;qREEh@iNK#c!us z*|f9&2RGIc&6E+2vEu3GZteP(=p6McoiKV1k97ad*Gpqt(t#MH~0`X)VlcR!Wa=%=aoe?FbHql`F8 z|G9x*8yn!C2l(@p5ix0t?ABraU;r!re17M1@aTy@hd-mP6p~*)nb^l-@&GnYEaXxC z@eAWOFF$^sK4$)IaKHbXH8!iexC!nFPTuuSHvi0YLmKE}McbaqwJm6Bbkk@WIZT{; zRh22jS*e}MXPWQ5s{u3Mx85)dL23LluxDHvmx#=2yO}aH6Xx+t-mKQ_!`( zpsRgBMJK38!5cC`x5ib%O|O25H_SvS+j8iulcHsMEOlZa!qlXUw&>m8lWJ8Vt$1zGQA-2_G13y;jJr8xKW-E>%qK}9F1NYN}CG|MT~z-Ds{kXEsAQOdR)`s$?c7G8z5b7v-esRDbx z(n>WVx1z1=+~77E;pC;~Ml;U_J)&*N*MRd@I7f4~Em?!`P`PMT%BuK5VTE+iY$C(qP;Yg@FQaEGc-) z^rGChb7x23n8CPf=T>rF7>uOs?1i&^6`rknX{2g6u$Q)c_E$r4Jq%ZQ>UX7*M2ccZ zGS#=zs=k%2^nGcGiE{;Bj6jBm}l*SE8@pHHdqhn176bZ0u zrNhU>0=H&xw%{fNLb(M#bmQ|5780D0{uBX{9A}SOuHXMF8Uv+^$gDW$FbPhZ}wzPz4Ylio*j#p=S$0+*3~~NnUj#7%xMiQ^~9#W z>DLiXGAD-{kU1?eDQF-uOq68N(s;^S>Xf~4v>F~(99`3(JC2qiR_W=R^z1!kqYwsV zPG8_)>Yau-jhpS4kPjhyDyQLpwP(>;DLj{sJI_^n7H>t^hV-BLXXW57go99#)LV|( zg&cQYIa=+P{!GuaD=8U_NB4}ocX++PSRB|7W$bSdC`T*AW+={cb(U+lm!{*^8|rUv zApg{%PrQ+CA(`i>nEH!TZDw;sSyJWrU{)ZV@HTIH8TU$(<)d(I_1NZ3FLMGN4rx~I z9fz-XcY8NK4zJE%8;=ivf#=74{v|ZLbdVLdX8F^y;-y1b{t|>yj}5a6MS8e_Pqwy? z^;g<4=h1!feO~9+^auP^=jTrUMOpsBtazbL(s8VIy=73{1iLJB- zgyo+hRgl6$w0C}KhcfMCjZJ)W&h4}AONX12A7`qoZB8CYim4W-h{g2B^XbR0Z{?3P z_HM!VHf^<)PhPA4UD_=%d+}_2w8`?g?n>9f+(l_(z55?rtpP!lN82K%C_@mN~m?6<<1LV>^ZB%L& z@!rr3lZOnN;@rpcbB|v~&#SSYnn7wD^Bei}Z*-eNnn2`vqrWLWNix%0RbzZt)af8m zXJE9@An`9BN0cVrQJ?g>#@>m>VXfXA#hFr|8v%Ajst(4~CpWL(J5*H|)8o>Q(Qf)J z@p~)3U*|WdzY=qfYi$)V;?EM>x=u$Dr>+yV@|m6gL>f8sb@y|R?)#6;DKXRJAIb7> zXQbkx(6ISoa`N=7eE#n`n5OvtliEu7W*rT>Kh8+y-{mZH=W@|a*^e>TQ~A)WEF z>yGH74EOpQmO6CwHx@D4d{OlFlNITOY=BibxJqL6EHqj; z`~-Vemj742$Vf-Q?|R?q#cgedw4CSf(VoX&?H$0BgnUi0%4Zf?W6fMgz0$_?o2EQ5 zG)7@8;*)r_rBkpC-14akvnSv7Iq%V~quJYpe(?Z!Uz}MF$A1Xc@~}ixEqJZ^-hotWdYq zhUjI~;SAli8D5(!5#E54<-iQ`=2~C|oGgQFHh-SKs=Y=>UnlF0uZ^mdYf*}Z4(aAC z+wv25gE`s``&$gafnvwOOjbVik&n0z*9lIoZ5Z?fbkwKV!z$=l)R;NOIDTYzxur01 zp2U`O+c9_>jGHl(g6zerFqGd|5m??jaDW7EQe(T3gtyU!wIWDxm@*NYLFlD|jffcg z?#YY?&y&ES{@6BT;PTH~e_D-A{ zHC7~Thoqz%06}Jyz##3-)45b$&e|=&z{b4(_GdRfa-#8RdVcWGC>PK~^($XbNy&^5 z7Q7|q#U&=sWgr~b^a_yQSvq04tR|i6_nvn7Me z@DRxFEJLeYz{`FreDc1H^ETdgfIGll;4VpB{q%|K)#!{k3^`O#+Q?fGBwE}Jf?@j)MM)t1hPPTk0|U?4ImMEQ=WR#MGYzD8v&*UgCs`` zVL%)okV}gO$a4f(iU0Vb|8ZF@MMse8-W=xhXiKez7?{~B;w3N0kF~oEEPE! zGvGkoVcP+!dZ{sZ@nR4Pkkt2(h(M;SCaDKWybwVjo<=vjT z2O)aub`E&6{e}fAfLukC8%|Cr*qkd;+l3GcO-J*-`%*dy zw44AP3n9konl|Qt_ z7v&goS|7}tx40PP;eV+{PtlIpI|h0Cpy$$&FIZ@i&>xRpUhOd`Gs~1NJv`eL(99I` zAO!1vK{?=4})p|vf2Wz1?!jf7k zHy!QoboX6uNh!+2y-OEU7X{kuhstYfO#sy!f$LYPG=C)mbtn3X_j1^<#25KzV7Hb< zEZd@p=l_I&)SGtv#r32zzFONRNTq#?uT(9~pf)!YyeI>Zt8&YPjsg3Ha1IcM0(ZVQ zySjh!biT#a3P+YVuMw|jyTYG&^UVaiYh#feR!1eNEtZesrGCFUU!x<}TK%7HEH*`b zz<;&nloVwDC!VI#HP=6-(v{l=t>I4#Ye$hg7YIL9Hs=h=8F|UuV*t9Dz;y8ynjJ*Q zADRfd0bHAjtyvQTV@G0QITvmh!p#^P46y0^*;2#}U*q9ykeAL1S_Kekgav`k=0$)T z2@b)&n{pruIpA$Y7$eD`ToSPeBK$(QtqK%P0bgdj6(1@up||BfZ*K_{0rK{)v5rCB zIS5Q(N^-Q$5cNljHC1ELG00n^!t&0DiGdY?<*l)BI~8umijJ}TTbMoE9(n7~=>$N3 zE-(WIx-eE$)(3jtr~qtvU}J#=U}TndMvvO`1a8aj+{&&Vo+R{!V@4WS`s?b4Pi7~! zps$%8Kti43JNdOtg-QBjXJE&mwJm^Jow4R#U;)bTQjSFG0U|)&8a6Qi%!{zRtQoWp zBNk(DG$MAhd?2?*M}UFfUU&CT+rtPK0g9G=Ui}E=)Mh(|Cq*Lw`AuHKPZXP3L2gXJ zZlmU++o)-bc|C|L2Ce15R_&z~fX#35#o3)7$gX~tG}{aI@(5iig0{{p=Xrb9g-cj- z)k(HiB3I#78oTH8Q2ok3(UinS$G(~u9JDmBoT8iyiUACEU;$;!lb!)GQ426;IO#<(@$3Q=GNToy1^3Y?`{bZ;Oi25_%|32G)ICEzy*=#X$_9DW;X-he=3K9Ab|5B#bUk`m@d9& z2oK~KbM~UIzfOvt`lrLfY7% z9R4gyEk2E_I=|&(|GSC7TpL0ab(d)bm_ z28j0_Mf*ZzKyGwF6Z5o#?ktLkoXlQ=$l??}MbvT>m_aE)MQ4%3rgSLRUhtPxrY|}J ztvM6Q3xFsLyI{LCSDi#tVe!5aSP)BNupuI{?F=_#Wng)^ZDInqF`Fw1%D7{?C7Bub za>?EKNMY8XoB>oh2~u+};+G3K*$vFZvN}3Fh|~hmh|+o26QwTsS-J){HyW;>1=D-5nMGi-TV8Bcgm;uXZU;%EcxIflu(-UB2ei_~D z(_$4-;Zpxi()6nDPJGgJp`F=gQ@hUC4S0JQ$HsFs%xolPKwpa_0z@~}X046>#fzWB% z;fwgjh%{=ShCxzfwEkLNR_+vn=h5_ zu@0C()Pp2DQ30cU6`*fY$#A3s4D!}YU=&M%&axF^H`YS-V zD?lHv0QNV1<#vzU|0og0@}$C-KdyS=f*K(8Shr#I-R)AWdkgp}A^66*r z@2xj}#Aj+v&wJe<%L)=YGK4fns2{Y_%t-7a4*wfoP>ysV+rpyqBMZ-e@jIPCO`TEE zdGi22X(nkqYeh$-%6YHrTt5Gc6uRh$R5|bVoSmrY+W#IznO%Ug^jwWraTI;kLvXAAKh}kM5>%OD(C&%(EmQl75$QGmy;gV z5{v|kOW_7gOw&)`d^E`4c7wQ#N1UAGz7_1sjtg2gUH^G!om_Og4f~)fQxwTgMO&mQ z&#PVM^7+5W#aeucR5`EooO@MG_LsYK`UKzfRn778-qkHZS+47=eE!!&*9E1Kli^|JW|jf(?G32lRU!e@(9P8P0wX!!a#bcv3jy-Zo`{>iBj-Vy4lo&*PCi}eD-Qz; zAQpWm2)hDtBFO}=qeobgA%P+QItvZo;jghu&baQH8K8b;MnjbOEoeUIjLYD}KH?Yx zXxm#P<>XKgin5E}(pcEc%QM80D%ELJ7n)Co}3AhAY1}+0vvSM;24q!Qil_45V zUh8ikYJB)a<5TKg7wI(Jqg5ez+SS=80^&U&jBh$(je}yH16$#m22I}A@wSe)4d4cF z3%Hezg}3=;ZK#f2(=l@BgkvF#+@KDn8@b1n57(ZujlJ~r`$j1nLL2POX1SfCO;gLD4p-rSkBdw^trjq_rNXT$P^ z6dd5n4dT)Qn7TP4F8adRI}gty1wMOq7a^D=5EBt!fEUrJvLFZ$Qj>dlW@8fuIzsOu zBxd8`O91Cn#;iLsUEGTh?6&DU-*py-@+>`KT3l5ql;o`u2WSr)_zNWlVr$Af=JC>&@?Ln zPxCxq&LSs(-ie-JD_S}EHHz-vlN)XSzCTt7J-cRN}0~7LU7F?g*~*^9WVgLp@A77 zH~`yh{sMoal=|wGQm#cQT7M~pyOjEfZ6znSeA5#aUCrg5us9ysY<>u2C?=htOICA{ zoFB+n5ddMav2@_^h0B15Q1Q~qlX$RS1{6X;Ye13Uszq)s6N}?yK%1wPUuK3rS&(Fw zmB^9pG#?}y> zeK@i+1mGasnM%h=ZPALLVJE@<+GFS~)9Ot&i2eR9fk_^g;oS{liG~qZxG+oK1 zLp)87#En_ffFT!{0h2&r0wEu{z!wD>BlaXGsxm`MnK`TUwpt7J+FBIg6e_T#zznb@ z3AWjMBY&em>#NtFi zm5K7HXa2R8=p^;;n20Gs+^P!Os4A?g)L6>BYTJ+Lp>6>4QQQAC3KXOpyD;k<6z(*L zXw)rUh-O8|VZajYD?{5tjm^-6u7Bj}lq)I#TvT8N?BW75i0l5e(}ajjHg*fZWD}S` ze><&Hux>jEA;E?4YLF)ZZ~%mN;TFIujcckJjem-pBA5+N2GyZOoL#^@gqs1g3Hh80 zib2x&gbx>yqh;l&Xc{0YvQOKRKx-WXw56)aHx;_teyhq3xgk5TINo@U$F8spUb3`F zH>DGKP++iCC!Veh`o1&ssEMdNh1-rnn6Tnp+QFM9Cc9RsgV;-Z*KPsO$8rjmQAOX< z0a`gpj*=)nbcs;>cCh5%a((g;L|;5-IT|+a-yVvcKw$lPn>I5SMl-{NBVm@Jgo}Wb zKDYeJq->d7ZFrM8T%KT1PdWZDjtC!VECB>L&+>gR`kv_l`fi0$ysDgWnJw#Uk48uB z$K{#(wIwYp8A%pV8L)#1%pl!nfK4@#Fu_yPtwdniG`(Yt3mjmPdPqs#FXGR*Q*`rl ztc0`1VOwKUJ<_qHTMls{p?fc3Np72#dq*#`_?ke=4itM>+BvWQ+OI7HVNrk{W-RvE z93};3kbgNs8hO@)bTL|M4$zf%3BhUIVUmTYv^x$ELK>%+-$Y3BL&OmkQ*@MuzxN+< zope04VXUNYDLap!Ugfc#9XtJ{7VOP}WOK{e48TGXm_fSt&@VbnTi=iw(%a>GaHp$X zQ`+aJ8xo~9esorYJj_NG$$F&ZT{}dgs+@JTl{0{DRSLn$8_7SZ=x~ZSs;X2P@f{37 z_1)tNuwv7#*3vbd!rmZWOcxN&t4E@y%pTI>Me!M5K(tnavw#25cK3$+tD{}{QCD_^ z`eZH>&}`f1tJ{{~g-jKtnbu;ZW8)j#Ki|RaE?Fl+XyyA@m3{cQ`hl*Xn=f4i2@D;T zXj6mS_R48t%N8&kYPV{xm<`N;;UAblzhFglPY^o-? zOX>!dsq#L3-RMv1OY|XkyRJg1O{*7)D(ULJZ$@a(pMDn4_k6XUV8JcD5Q`CLAFPv3gEz% z6&9yk9edc|3Y_7teX?q|V{VNJ7tw-)a=Wm7=&#h`2{3)0U?yF`iBGR&_51%%Ef%mt zjhkZj(uJc@8qxGHuOWGB=6JHJ#att2`Ztu2G>Kp;0y`wjk1{IT(c%`^IsvCQ%WC`Of!k?xx&#ltbE2LS2PNjm&ZpGb? z)bIaSB9>1b{XbNs$(>*GnO`tPTHP#*=lYy&{_7eYJw-<`wnU*L8TFK~{ie0h2H{-V z)Xk+OPokl1)s-?Z0kto<_P&g2p7Yn>Woo@&kepKGrDqH>8EZzoPA=#X74Hm_=14e@ z$o_lxiMVrow_05ezBT;OD?JL4b`2#4{{O)mK9aiUH@Ffe9R=>dTzyvuonh)EEaB zH3#wRNIa^^o~rMG8YUf!=ZQ!4oJ>}><#zd+23Yrfj?0e^$9Plb=@SNx7ik14nfjF< zl5<*v2YFS*uIMHzSUMA_F4?Bj_Fy!4nLx?}U6ZZd>SC@+9unb!8JMk}bCf{; zDc6AIEH?O&1vMr;(-;>yOiY(#Q?E%!o`I=v=I0jnU+!SB)a2N8hyzpg;cdRRfjT$i z2TAk0c+h!^rB0F}Od3W6qg46?ecQKorv* ziYqGi(isy#+G{}LU_s{2O}EZHVcj3`2_3Zl6H>ZlNDqg9(PFASAIHQo4aNQOWWvoB zXrA!a_>d#3wx6m4dT0PGu-t2Tmvz=o1}nghTVATUEhbcnhrP3osXFZcGa~2Y^&W2t zx=Y@MhFVW@9Hm%rVO zDDg#I^TKj>$Yehxqm#!#Onc8%kLvu)9qlNrP;F$@FjtCexp35Hzeb0F!yI zQfyqy;_r z%B%@9X*tL|VluvP?=YpLcZs)`WgtqdSD8FnrYEfQlvSaptkkLfC>p_Lm*sLk9Un zD}fp053R~e5uouXP597SPz>^i)&mRh-09-EQ+NJu#-*r?OHpS}{0^}%$p=%Nmg13&;^ZzL^ZJl0kl~Bl5ro zq~%Dpr$mJ2h;?Hm@nEq!B(A<%_7Z2WzUq083m%@I&Mgo(KjLnCnj?A+Y&;<)pj$%W z`RR;Kr;LJn_%MilUQA-80Y_8e9-!qsV(Zz&0KUW$mT0+EEQj=>0V>(Xk_p++A1x9V z5`rYx)h+Kz3AGksMx8=1&^Y(>O=Hi%+BISyHA)gew2N}~bcHN>O>C{ApaUwenqzzX z1?P4i&tbwEr5t1F6eU@!R((&1PLqXmtZiwD)2?UF&H*aTxDXZbe30Fe6v*o#)5;da zwB_376N*9DBLdS}$Fy^dZr8vO1x)pkQwl4q5y66D<QNSR8^~zNVQQ3j_7r z7c}#Kp7z0Jv;ix_gfM2Y+1*@~^ZI03=?4i7+UYE`< zPyuk*B<)FDc6tQMM2Zw6qaA>epSbAYQL2YfV~dt}4b(>5lM`{kvXYp!r_irsSPgCq zUW_ct3f}m!AhUkOTF>jmrhm6H2-KgQ-&GZSY(U;->f2)Z-u9HOjiU7}yNt%HOJ@V+vjmP6xZ4NrtluaW#mEWft2Wyo1FqNsd&V z=0#;P&G0{D2iAtAaPTJiX)00<3dOO4r9?_V=qZq({Wp1)x4MnMVfG{A+qb3lC87qkZniAdrlA6uPkd6#0*5M zL~jnuVSY^PiDVIMA&ME8Hs4j(Vm6_|CrjRcT6sh)L!o}#7{`|5uSZ6sI5e&HaL%G+ zkdjAst%s2c`gC2tb{x~&O~BL*=896gxlurYm38b&EB z&5}&Io4sQW*uW1#Z125(ZR&<5Q7=8%mSarV%LXT^a#(dxCw)(@dhS9UCssZf-pqB z;bx3&3^}2v0C~HG?=2v0bji=KLf2EX;y8DKyW%9fGy0(_p;{=?(M%k47h%^co=TJ%fC7lI1jKTjx3B6XC}D2fJY5b52|cQSw2nPjV|pjBT}PP| z*6Ah#$NXAf#5b+FC1*$JkLrJ{!j(N(hD;2Lm?)1q&#Q(_?;x)_FudWu#Od!o{Wnja zJN8D8@d`GyTwTvNp z7RF^%gLYkTlIkJnR|2v`ZbiuY{qL9m@~QJnfG|*T z!%Ej0bI|65L2Fz9omqpx=&a$Eg_|+>eiJ*VTpTTeqkMgSi=ceQA{e0u7C~Fp$jdvN zn4?cCVCNL1^zGVULBw=#sI7Cz)^)|Cl{z_kJO+N&Cqs`?y#*yLde#)0Q65iLRQ5)< zu&J%H+BB9AWn^%Q2f_?G(wCSj-#U&O;8gibr=%1}swzX-91hGnc^E_uY$)7j1W-{F zfbkFP@X(lvi2ca)RNg+X!t!t_z)j3`2~oK47QXF{a!X+HhTCpn_QFn8L@vsSh_odz zd*RYWVDHFPRC0t{lNTyWSSa}7#Tfjli8j^+aW}BVh||GCH4HZsIbl)1U}F<;js&(8 z*pT~7*c@U#v0LdL#6e780oZMZ@>W*`bT%R~pp~5gRmMRSAqcLF!=TuKUKd$4kyR%! z6Vd*RI8Cgqpd_8K=9t_#-HKcZo63JF6DSO(i@#f&%I}=Y-#TNW!F2I;cTD9!z~!6Q z+(9>b6*8sH`G~=QQ^3Fs*ro**;I^sI={}3(f^ZidZ2;Dyzyw&gmxOV7%tV_&nJV6D zEN`#a?XAo9XcD3RSa2pA?;0F9;3~t8a4Mn!OW)VV$ zf}6287elnP1n09j1uF^nv&i>4a2@&<-^iALNH6)`25yJZGtYvdH%)XSE@wKM3=l>; z<)&$qwilodjM+RV#4!^C3poVL%!LS`#UGx=7#R+t_y%GzV5|i3V)!%`8W+T}7H+HI zW-Mg*Br8^dSPVk*58`h4+zU5j$T$NNn9gaT%3?%K1!mA0_=za7W>E_nfX+T45Mn2M z+AX9s`1FdB^p}q)HK%8c5|EtbqT>q%+u%1P^-4lTtXVr9zZnM63s}+t?rIXR*-L9n z;}!Wp1Ta8~6IcKxH$++C0}g|3z|*KAA{Q`_uY+(70O2l{G%a^bB5N@&4AR}3qP6aE z(b}+E0xHC6jF+tlW5A>tn8BvjH8ufp0SMJe@irXRo5g9`@)L}2U>5~8n{$8&)HH>T zB8Q@70CyEw0A=Dr5EM=`x3RVX2J`|m@SSCOX09w}U7O7rK>fU?qH-h{Be!wuvmj_R`ugXs?IQz>-a_ z&Njq~4RY=;UlUjQiqqeF`jw}@@AQ2-QyDl9IsG@qxvs1?(<7TXqR$pRcc8;w* z5VfM1gLQ7EqkuR9fUdcj%^E+Hw7Z+M(I~tN2ruv98h|^NfeA3(%?P99K@`S}Xe{<> zc+tE{)C=KeEHtrjTMW0Qa5ENCU)(jj6PNy6uGgh*;m@h zCZL&O(H>)CfC5_h+B4|v8l>wN>CDN;VoY9#b}MTxZvr|57!?6F&-4K75CRJzwrnT} z0U$_lrcwEd)898JKW1;rAnx6-Z#;fJD<@azI>>1F<)0FxfshF4jWtaW+erI_ll8E? zzK6tem+wO)mPm*s$>jBo&B=|}T}V`*v&-uBpT@QGN75T301-&$!*GpZOQagE^C5PU z@?)}`87UJS)SF-BsTW~Wyxu}0 zI#tyfzjWs&clL+mVRLdPmW0V2RoO(y+q>zh+K$m5@6+c=9tP&eEbd>EDP>T)Kaq4* zu2EM`pT6+tj&H&h9%`6d6p#vVC2T;aNc=Pn`pp_aiS_i;nH$xb)Q^f6UB^1vwF#0x zn%`l^^ZDODSALWxWt&&}=q$f!w5EhBgUPhX=ESHfcmDzYxDJYmQ94$bRergN{c6_o zi#M_@O9UBSC8d8E5S7y(8Oe%^qb_02(^NVFl|b9O0GVIrD#GO)SJDRh*SWS-`;cmWuyCl zIf_OtT^vPP8z!Zd&z`4KA!fI*=xBZ}Kfa*|N*-V+r7e~edE0YZCDJh|9G|fdP%#U% z%7S%YF*0(Tf;LCBo0AA}RN+=7d5(28ro?ATP(!DSGqepPRn%zl?u6hrZ}3|G%TY z@!F5m`u|ht8~@IgaAV4jJKdbT-_%3Jkj&x?nAKKPXE2dxT@eKV%|7!PS!gk}Zf zLZ^FDW8lYnd2@y1WW*)TGK#jC7Hc)6iwpr`=xH}fkBT#34F(8lWKI~fasfPs`klBb zfzT*`C!7IGnlXtJGzh&q+%+b;nlV#3b6!Zp|8e%o>)8)(jGR3|y~Lfu%6y5Br9xL` zJdzX;!(hzEys)Ulff8KHJnGLPJEConbixW0T`}b9H^Qd1xK|lpE z>fkgQ2}|M*9mdm&bS|ZH0Y{e}G;)8aF6l*bahBPA)M!<)V0Xij>8Q5SXw}gf%a7LI zN)6xZZF>Ky>M_oRxvDsaMDdBjvmZH(?784cW%?eg+0?JRno{*yYzQ3@?$kRgsz$XH z)fg?8)REK86Ca;bg=!$C6SsJ1w9*~jcrn|8O*kXvtbsM?2$w}T6T=n{K1IasfVXmD1qg?^2z1dPl!+_bR%&(~JX))HnW4)PgEb;AF@joRF0FyD1J{8b-nND!cG44J zFich`l9HV-cqCe9*_9G^KJx1EhNu#AeDPVG4x=P+l{sW+KUc7-_0Q&fnHE`ywe~w`*|0Rr=uSOYdrc%a&oJFch8)ID`$%0P%TX z24S0GG$;n$!<17ZrY8}2XdyC|*=Kr*D$c`fbo&_F(2RCVKSJ37Y%7Es+XA6(TYvjE zH2&a32f>@V$tOXhbe zy0@DznkMEzyI1ZeF!=!wfc)Ijb1K`n#B`s$v-nBSGR@-0Pg7l7E#fA`^fup`{&s*n zRE>gD27^kF(O-_FB^n^^j8RT4{RAEpI9@TeUo)w{loz6u=K=LAdNGW|h0vyH-8UH2 zuB~qMMTuweUiIx1H1Qb*gKD*dFD6FcKJRSD?ykDtmv)+^A}31*(uJ2^e5#M)UYfWa z%@hiX$Wop4q=-1clT}Kz=7?EMhpDI`SKG`8pTW1yjHdomWK1%{OfvN=@02y1)T?54 zF}uj&vU$8pW6rE#T+bnc-bx^tH~(VLKcCEy%kF$4gxcNjtC68R&95j$NpQ_{Ub=RP&5N9WCXS@5CxrZl*yDzXt3hjQ6_6BNt9g9 zM&brYWCIiEFZKj6cZ)5AVlRbPgUWPCQuB%1w?zUTT;l|)x>}0hLPTIt8B7VgDU}dA zcHPm{7Q(kdCCCnWc4e5bsWiygs(l7Hnd&BK7o47j(VQ6U6GP$Mppxk(yEv^8y9MC> zbYKE~12I`gv>r?S3OuU%+^NXQDTfHtIB{-D*BBG=1}iv?4}TWosH*8ph-3ZB`Qt0? zCV?ngec$!Ij0d9?kJCu+2Ut0Zy~mp8U!OxJz9MGTfthGLCTW9#BTx0uh`<0#8G#uL z2o;jX`x=9fnILR<#MiPM;zz;

0i0D}1{@y+CeS~NDi*p#fH?RyA2JesWX_?IvR&(fFlNYz zD%|3cp8O4CS^kDoENOPk-!M)G(qvrxgY%6)JlViJ)s^w+)uO6*0i+CRi;a2f<&M)x zk&tXGd-We>C;lsv_OTGDUcM%-JPtID?x(u@gBbH^OHl2X2lfxuHiJWf0nb-7#l-o_ z<;O>5>K}Wv?CI4P;sh-(uYjE4T;Ba79jE>9>{&fJ>CuezY#Fz+_ozbR#oajTlT-|c zdLu41HHBC_qtl*ZVvH&Y(&<^$rf_WRj@Z=R-y%=Lo$osgZh9^6W-OMda;I1{20_0=6mDcV&Q@DF)d; z3TH&MKQ<6(g+ToJ8YwBQ7iUlP1!{%N#VHlA@WF?ey$t~uu?Lr1-4hvi`=#qw{ah}+ zCD>4dzWJjZCwq#=RglUiL^*eC>AE#SuzuSW!R=}?t!B^^XeI&~4Dz=O^l&qX8T3L> z3|M^vGpHu)WMNSn{`y6J@04L0SL_?8*c*Fu+cdx0x`J0bzw`DUf$wGUw#FNCil_1q@ z7P>C>nFO&R+(mB)Vt>1hL>mnQy7Rse+HKF_2hFw}&pk8A+2Uu_hAD!Lh`?ZgZ^!#~ zxEVwn?gYgkw8v^#O(_b(Sy5=p zC1G*b4L4!Ylr_@qYsy9WW^3b|#Dw+Lnv$zlyYd~h+TcjngwP%7>20n6jf*4LN4!af zXO+^X_XH`!qLASkIWT(*LuphPn>0XV_>d*5FT>&961D1kZ|IwhhCLY#W-)V=u_ z9J_fI$K-L-?>wa}ib57o$ssgk$l|!;v%XAZ@nA5E;ocIp%HnTPi)ngYpsE|YF=_sE zUsYBUW$1jMy6IGyE;;&f>kRA>ikA&%CoA1PyBGg(mQFtGoTewPe7Bl>A1ZuY+olf= z*-J*mp=gq4nx*ZB!}hAJqaxpSTm}yuU3%z?skxiMJv$`(pD!CHJG;(#JpG=O4-vUV z_#L~!e&iW(#AES!21gT%2q&R<&{4BrCCl5_9Lz|O5b-V_@B<9lSpRgVkm^KEp|fgG zu9#>AADYw{>>os$bS>P3oy}35;@W`~chjPtOs)p&9qT&ToKv97*Tljkdx6&XILylF z;mNy@;>;i9=N?CXjnMm9)5Y0QWH>M(`u)rT6?>*Q_xKq-2fYo4vU~bEuhStm3ey9j z?{fKZ`D5v4ir4R@G%-T*bcgGn)70Ii#d>2zo+R}MvH5DOq0``OL!nzzA#rH^%GI9N zHJtca6=RWWZnO19?1&^jQQ#`G>VrBal@^9IF-9m8e$?U8F@L&>PKL zmV{T*Z*T^;fc(T6IE@Y92C0wM8>79vt>cT|#7O<&akoJ}`IdcIuJRzND8=MksQ(4X zU3-liAoO^xrFr{%V`B8eTGidaYU&#F!B@R;heCF3%)}y})_QX!v2?3KabD2myK`@ zi2j`{e7G47X|4mBXpbJTOXVFB8r;nQ&CQ@@8I3GO4K{y>eC>atmwHd6)%<3teSUjI zr12!3JWJCY)*pUDl&f2mf82|#7i66v{Q8TsCQ<6~MOk$R?^;6ANsx41T$JThl%*p@ z>7})RZ+>S-7Ee^Q?^5b6YhUMa(Rpf7Jp_xTI3Bm(Vf=Z2VokohEoPc&Wf4#W5-Ov4 z`{ILRBJ@uS*IefF{~a4bKu#aJ5d<>yFO8!KDq6x?26@2Jg?6! zlDOQW@^jk^14aqIWKN@xKTIr_g%C&cx*y?+U{w|=w~uKkW39&Z-fzOj(I@u`b_=px1Mh7o;VHv zNB7!w${|LB?=aTh0(3V8Le$AM0US9Ti8GQTCgsi!!4hl`s1gRH2!tzLCMCx1(p{3Y!fbTz1yLE z)wM`S)+hF<8YS!vC~NlbQNA9TH8|Of;h83$)3f_pC8R;wyg>WqehE;IA{Q#xN*Dx?d(~aLfH*aIaUqY>f4egRro+f*BR}l}DIalCoTDE^6nZL10O8 zU)eHG3h8n4J4x=twg>SpNA;{2Fqq?TK}N(RAg4YS(bK|%zsa~5ej0P8y6+^HV7wQjp9DqIsF?u?CY`*iy zNKC}*@6DUPL2MRrJ`$gezyMb{Pp_7mp*&M&aEi2NFR*vJytKe%TPz9zv_5cJ&sg*=Np-*lAd{RR&VEtY>k8$Qs~ z-!5PLmN+4f@pIfUY4ut}3{HfCF@y{_jtWemf8eX@*RR-6YT16N{lvQ-U%;X1KNx{SuR_C|ARi0hbR0 zGpH^F#oeHoQZ~Zfpc1#)g3V)1MSC>A7v-JHeR7KYno@*G32aJEEdCMMUWix?22OVu zxmp*7uRO?kEq;rZJ+Su#VA#3WPMzb^AkEf|&X7{A|MapGPU50U@i3T`P&7d1C!PTQ z*Q`N+O5mETd>kvh4pX8@I}<|S}n!No&=ZBJI^f**Q?D(@?Hf?x2aYlx#vm^3*0Fv8ni6?@Q7hs)(qX zF*h8k#Z#Nj$3ar+$_U0vRjKvzUu#6(kRw|u!BR;+xx(gxUob1tHtUX8Hxr~gbMHuY z&%L8G{Tk$Y^XsC7LZXBjv!JHye+9*PF8rBKekct7qs3vr89Uy)V-BADP%-%-_yOpw z{rp9`tZ!RD%saopw>=jAJURpZFgYk(-o_TkEGqc^d(}cwJ4YI?sw_2U^&QQ_-M6!J zQ$10)T&3s&ByZ7*7*VGNU-@Wc&Q6QXKg2&H`VjCyAo=v(=H&0!CTg|C$&XTTdrY<; zrP7wFm@QcS+NeAcxwA4{fB4)-IXx_%^DBSp-0#EdOw?PO6N{an#(BSv*Sj_ruP&BG z!Do#KGbz^W$khtK;? zQ8x?3LkBYOUKDv#uTxyawc9C#M5mZZo#Ks9Qm4>oKKbtbuW~NqjH7_NfX_C2I@8xb z-o5{PM|E@{$wboTq$WrDG~W*Iqp`J2LYhX$gRtII?Nxo`mgpll41%Jiu0shWtjb$& zjQK!F5eOox{M;21S}bNpi}@?JF#&F-*W+BM`1Acypd(|R1&Nq8o4PB5Jc0b0z_GE? z30?5K^%^0e5w0}a!f%ocan$x-r%Kd@p*K7A+Bh|Ir`y%#lNYb&2xMPJ*88r$>gRu7 zjcHQ4t@Csg@d5MwN7Fa(Ki&9m#xLl+`-$K0xcH}fzvJ@bW6TS^n*e#b0+1~F#jZHn zOplAP4C+{Kk{ueaHwd`3-XQfhi1h|+1Y$hc=$&eqRSV(wZf~}!miTcu_2)cAWj{5B z&&}r7@mFo$=;*8WV53}%QZz=+<}KUu6FBRBZU;Zu*ef<%J=qD({dA7(YXj2^8ssc} z*9K@^L@<}OIG5p&ew+Dgr>`LTCd3}Xp*@5{^Z|aA=|6-;o0A`MhA(}H+}_S7Lua>` z{!m{&r$3}jPJd|sgXI619FCFX3{JFz!SQFkVT1Vzbk3dsWZ&Coi|+nJx*bJWBI`?J zeHq9ih?Z&?=3d;Kyb?vY(ksGZF@2@K2v=N$D=q?C2z9aU`NeR>9|Qk~>x=is>ioe2 zPh+$mEXvXQkOp~`(Fd` zJ@z!7n!)k_svt}$H;uM;pQO@{`TXaff%HCCLO0xh@Lp#i|IxmmZ~B-e(^hD3!iM;4cGv! zB%hn{#gMyh$$ zw})7`J^yF{*Bqn^DPk&gum!9%wsq^5LflKCW+>FzR47E@xrEI+I@8tPK8A&3z%O(T z>g92)m&dWRJ~J7er!e@rV372utBGYU+{7^u)>v0<`}n-4`;0OX*vEWV$Bw*EwmJFc zn19~fn}3#y={NUP^z@rO|Gc@Hf7Hcd{&~{gqnG5gGBfNXoo4YKbpt(V@A14Bzw`vq zllC5MPU*j^y+_GHnOdX|{-nLfQFy)K8PuhD-DwU}AO9Tb?Gj0&rllC6fONuA$J+hd5hMIWN-XojZ zC+$5xY3~u?)RXoe(FGf2etgp2&Ceev zSOyMKyJIQmAC4lQncuB9zXPG_`VfDThSy!6_%&mwym7Zhwm<=s+j|n$a;%V^-$}Y- z>2YvBL7xst%5<@#K$F5|I1w?YQ+CX7+8QLPZM|##VtLkIa_2KiE zbRNaij5%jpFXf2gSyt%CZ+TXh%j%Kpjxz!pYJN9m-DYKYrs*nY3eeN*jNQ|#CoI1- z0BN-&8h{PXHYzPZDzz`mIu1WlpWKI#o=W#|SRJHr-aBsjbUPC$Ev9xfahzYqvCqJ< zjg;hW#rFeE{p~7&{3BhqD3hj?%_Aag^Z`b7j2c*_|DH}G!VXNbwU~6)Whnh+D94~c zFrcsAJ>THonogYBE##aql~t5!q17MWg7;H-@}E|LIpPvh zEDBkC*JA5IFnmZl`#@9383#%C36hP+c;X@6nT$#d#mTDY4y#AU+mQ4lW4_eEy*a(M z*4&Mhqs8b=Qpeh&NIC|SMiZr3D$czb%1VCjEnq8F#Cgk2G-Pyb)ygrWq??z|XB0YxNx&?b91uByg_-ByVxtif*wL15#b|4eQ@g z?r1rmOGP$&afJ7HV%=2vM2F`}mC~U7&TY~-vq+t#XA6(9-HCW;mB5S5x2dEJd!+7@ zYQ9>XtVl0n|4Q6@QirQLsV@~Rn-JH@lVU4^Y7-}z{gQ#90QdMRdi|&LN&%fJg8FsP z%-8Fel+ZKexak0D3;eAgO;a^XW(8{XpZc-z<;sM^$V$)qeWyDFO`gmjannlAmz{$vuqYKzmTjBY2_;NP?6q3IV;_@?98g554RL|xKx==rgx8>-t*~2X0b(9yCvWfkQ3`m%UlxTj*zbiwP#%Wz97-P zE0vk%V|==#lt*=UH5p!un-UXvscg3;?<;s)!P_cu6}Sdug1Cx)zqFJ1RTo36n-R%e zmR589qI+@8)5$ZR`r&WsDT&3>yh10C%9_-c)cm1w?xB{(Td1Jl<1UG{Lv^>Cb&E~k zysO>L<*XDY*s{o& zT|Oq4rx8(=(>2JH*(1iob5Vqw43!vH)lkuFPS2_tv<@9nVi!^^B^8x$b6tIYL*;Ch zf5zUb;!UdGn_jeC`pSfC#*|TMzwg;AlvsWUwH=u)Tals?$FWpQZ=;r&2 zVaI~|CvK%XW7z)6FqJ{a6oWd{;~bCR=6P@AAN$JFSC*cB6#47d?~*>q786z&2$GI7 zbD@Ckd&=}46@Fgiu)+hcHUn0_^6jdHcvJ}$+gW_AIBgz;@nmdt-znK89JVGz$Z$364PPIzHA9t9J#&60RA!fDLpt@6#>B=fq{JlAE*KhX?al28Pd5q-aRW0(D_s{W`+UnLGJ0{2qC zeH9bxnY@ljC#QO(Y2B?JU#0{bBLOODI8{=583*i?9bPty&2iNpHpi*Chcv{Y7~~HP z2WG%Ss(~4Ft&`8XF&@4}JT`GBXSN)Vc(CGHp8Vk&TZDe2uVFPiq z`8;TveH$Hp^;Gyq!HQC}KFu=YR^gL_Rrsn!=e%-z`I=@Ezo}l?9~1vcDdzCc>~X;$ z?CMuNB#1@le0W(ji05hxzcK*(v=DB~;kG2eGhGH@pZretaq-xDk=}f7t%kF~k@xC5 z>)~b)&nA(`hKSMCz;*)L4W9-{JABhyC&mbw=&Z=S@v-6TZgSunuHUb_1&rx&$534W zG@te5^~Pd2&N^N{+|^xht65Tmk~Tb)m#ZJ)UKLM`q*XY;!1bny#j)Ut*fjZtXlYL- zlK{TVA$Cg|l)A5@4X6zQ?f9o%lO-jL8m_w>AqW>i$LHz!qLWI(v> zvMd2tnPpd_=SOigg*qcujcTSQFSD-7B-Lmci<=b$h&er(*iE92nPD6vYa5is4@wZt zctkT14P&hFE|)bNw7TGJCNP6sF-7erL&3@HSK4~`%}By%dd8>05xZ(LL)GZIpt9X# zzOsJhTU{TWD6dCPngcu!!!{^m4v{J)DDuC*rqrsdjE-X9a$?0$_3pcl2A3n|_;bqB zU|0U^zfWW#rsE@S8FcR>C|(MyI}S;Bi`8BiuzRz?7plG9abF^P_2g${fxmZO zJcYkWe;!@jb_z(W?>MnI`7tNOUi5*`6vegqnACtu)BJ^5iTzItzU^HF*&}cstekHm_qih`|tht!EVz3=LY*9xl{k!V69lE ztZOM%=H8!OzK#5sdZdlc#MZ*^Zei_gas|)J$)j#F&9Bf+*YLxtGP#_dILpeEFK8Qd zmpQTu$z5hApZ@JCd>rib+Iy_ZUtE7ux7o{df`c3avpFf)^a16Dt^rcf@Q}vFf~wy2 z;Xcj#H+r+rr$w?orQeIl79A(8#u8yn4DqV^$qYj$cXMHNeyiS83*NPnUsY?^uucj? z+N}3Q&Glx(LDN(tcBmaA6Q?B9M3@~OI#Eix3m2Q^y zMVwQK+Y9n~_hVh|NN!cn>t2?=9l1p~{C9kxYnc1m)WyyX8rQOLq^Zqw7 z`*B8+w|Bq@w=Lr3YZ_qP`*7HA)_5z^(UebHD4*h{jt?D8M2cA~6FGklF?Kvy?hY4k zj=@=vL^zcV%z*8tVkue&j!d(Z%nD-U%aT)uz>V*3r4Y_V(RME4{jDtGpD?JZKbj@Z z@a{1uU0F#?*UnP=={Z()0{U-&FeIGp0ap_2)&+Zs8&>H~RqA&+PtN(-Io+I^UZN26 zZ{4KgJM1g20#`kHBslhaBQhW6jnftdjWvzY;1a~^R{msv<7D-^)ez^J`(G=xYL;N$ zF;wZM8$5sU(b|9|t*r4PrW>2ss$ZE`^ZtM2eJf@*uF>U#_M6ju`YwoBgPj);JwBRH_)&qgDg*;>P2j<2@^iuYb4G zfYA{Huna)eq%GhKr)f96AbM$ml$mnlm;^{k_0rmoIO zS!{Ao3lQuBvQ9cKl~Tj&UM5>wNpMV=J(=$N5X~0V2REhzw>=5pxHkUEM{Cy~{NzII zH-k%^RF7V@7#WDTVw zl!w zFSW-&Bq90xLjGg&_LD-cCvOi5`AUuj727WhktsHn`cWZLz<3?-)*=2(!D)Xd@E62! z{}cZFIIjM8{lTBTOk?~sS*+FcY@PqhDetqrq z&wgToLj3PUP^vm)=vugorF9C#=rIn5XE-8kT0bqLcUSRzXDM;{-%(B3!>I}zmWf&kUtc1CuiOF z18;pge|{B25jXiBQvrnn~v3Y7_kW>NJg$?~;@L=|d?l@T*Anh|VBYb1xC{t<*mi zQ2QPse~~Yp_rG4q-vd!yI?N_jA(vm2%kP6|z4X8955D`v$w!U-ZX(t6A&>t;wfIqf zzpEjte-A^E4uD;Llp3#tn4$j%OoQs7KQFt2U-CuavMS`OL59et|9uSkWq6{<)@{Y<&+^Jf*;+eK+oI1yJg*I!n7fIkO3@vHp#=E~*W zYz5`=AAu)+n?DbO_+OQ`x71F4#UT_z)fQCPTX6Z+Dg)g<^Q9F&K z{k#4keSeydZ|AqguiN)kPW4k+rhT!(jq~TlK>zFJ=jP@7K4nb*u0QzC7nt6@;(j7{ z5BS&64kymxI$QNgMK6DsPR@_=@eCU6-{O8kQ?}~4aSO;FgQx}VQiJji5dV7zh`jxv zI{PMwQt`ikM1ZTr?lAu=&WfM~Jn<&}{FUUZR;8cjj6YSqUB0{^?%=NyxAJsBx&C(~ zpc>b*_0<2jxpRS!tE%?@DQzJjebFz}_g2^(q3# zX*}i=#7H^ngoomvY&lS_uR6*y2Py4K{*Y9cNyJ?IBMZKLW!qBbKl_qDM7L2wwfMhA z{*WuHoApNikQpE;;wAY1eECDJtZtKrbzYZ#c%ej`jsLSbW_8{BQ+C|M;wz$Eq2$^` z)H*5>kF<2gTO(CRwa1npl~|i>>x@^;syV7VmW);;A}y;TE2G^6O;xlP9&eKbSS>D{q&qoJfHeuQUQe3~cP z+Y*hoc6&M^y|Ip-4o`PaBGK8EL~f#I&FzV=ig&Jwdsfh+W3A3av`ef`KVk<9*_n_c zM%q!vDTl=3v1BaLjyzkZA90AKMLj;!9rdhfq|Y;>J*2wwf9mt3!bR;)5-LfNijN z`sMJdA*-f_EZdyP*U+S|Ly}lasIxoS6^pOLztKoXLklwZ`C8f{=rLW9ShBkT+2%VW zez0V=Nn>xe+Ea7Odsb|ZEiq{9(ZATv@^2gK+f;YfpZxTuzO!nkdb9d-mPcB%EgWh= zi)yVoxg z&*0`??sG#z{M=r-Ge2i|d+m-k-OkD{q+6wWlW+f6 z-64fF-kPNXvu!eJRv+bZ^M|WIK1BI?m1phGlsgApDcRxSx&Z zy2w+Mce==jC_lhOUe0a${(94p^}m_^d-v9aVVCrMlyjR}P`&}m-TWV-{1UCFZ2oh{ ztEm)jD&=ymKY2Wci;su0>0e~}H04uNO^xc`_P>LayVcJSIOUugIw{P5arjYJnMfG{p(}HxV6s! z1@kq1f&KESDsW5hr#$GAzK?R;jmuSFKTY{#F7ni2DsXGRH03R-mMwpZ<@d1TGFXtm zU+(pAJ)y{^-^c{z(^VjM(zpuJ2Pya4;zD^JQ~rLLZ6KTfX6E1TQ^WGKa*Vmb z*C_knV6_I^+AmG{)!NUq_WfmA6u&$>z(qF-%9o&gi>ejKgM7;2R=xz~GhEWADgO(~ zhpWJT-(pSZ*8c`5cgsIRxm*6_r>gyLO|V(7A>1bIuCDZdS!0e@6!>Q?Q-Q}t?xFl; zO<9;etO=W4(uXN`D_@Fo-sBh99~dEhW(2wY(!s*#YP5ao@uz*=&iWZ>(QtgM&8++x z%DpR9{yp`qz`wyZm4B)zl*d$HIqYwx{lRvX@8e>Bs6*v^SW#f#8&~;ViUN6t@?T<| zEjIr8lbX=2eFiCi#ZiA#_t32R*J%1byZE18tMYFmeX;z#=atSsNV(hiPEmf}9+vfd z^Z0tTW?4b{=f6~AZbki1v34n_pP~OyIg-m&Ah#}8!P$xed4lreT;%uFx3Q@=+dj zvENVmxmvQU{SCD5?bC$zlf|6<&nC(TzOC{zwf`2>kLN0t->Z@WdFE=ByN&P6MwPqu z&+=HxOq35+47g|saXyDOyzFlb7%y)XS1ev%imAA+xnau zA$^+igvwo?c-PUij-)YL*UHmWqoeJF6H!tOG<3C9GG#C4+5z?nAcU!-$SJkjv|MF7q zT)#K5{>w8Xl#g<^^*c?uTmKlK{Ck>B!T1TkrUJM0sS)I9%4fUS&rt5xKg(Y)oxgVk zd64phUGh&*9&wRpC_mms?tMcA4dZOqYsj*mVf&@t)|jmhdDC8FY(eT>mFw+w;rf@R z+|B<1%4ew=HL8Ex{|-?;!9{MprvkV3^-wq8Z|)xU@GN|*Fu$_G`R_3w82=lw_puJuc~ z^ZD-S`;2x~|EHSpryA6MHHsEB>J}GyKW~|7rXc-+ zv^D13;G!kgnBMZ;u9KOjynL_S%KD%^6M?Dtf8+D`;5sh8=djA={94~+)lVqDWS9A$ z;bVUL6OCeW%(=F;F~*MAki7a|R6FcXxZ}%DFM12`{{z%J3xC^}Lr?sA82=AZZ@p?+ zWd{`ZOYB~c|AW8Q{4bz>vHUkfb_?}RDxoJ+;~@38J{K*|MAYLJ>UEZ|Hy3)I|5pFl z-wW(Xx#a_6lc~q|DpuL4FBWYNDfb5Gm%XZX!zJ>U5B2z|$H&$M`HQ_bp?4DX+{!I} zp0x|=n|haw(~Qe5clT31%-H`%>GrY9552NCRgdG+Dtp@9?gr@hQh!RR@}V>!|mI zQsswUihA7d7TA;WRHH3>sdt?zPl@`U2>o}ce`tyP=R$9Ydh<)@wL)+5U$s6<*;@}i z5B2zdsGvMkk&k@r_$2Ca9V)8#GW5LEd#r?ibdnJ>BpZyFHAkYpR&sS z;-3G@&>x(r?(u%3Xg!VF4f8$qwun=589y~-`#BSOefz0Bw|bKDG(m42^=2@AF?;9b z*yH=GqWS&+dd>1BHC&~}&oj^qQ?HbN@8{Ta^H1u#y=>SX-*;i%auWp{XD)z zxs%Y_N2zZY(@67rQhT-=XZWvhzoOTT;<_WNh4KHsku ztb(iQw%KY=-pkpSsn48wXfHg|FF!{0-1>)<`(o&oQIGHGtg?lJn$Xx~ zHd3Be=sz$|#e8p9)X(+MOC6_r^8C-fjJ+H`H$m^6dew9DQ}TTodKv0Xr#a_5B(M9M z_8PV)&jsZw`_X@C1}6XDMZ2xv^&8`_7-xS?%AODBB3)vUs>$<0`!f0`Uo`Ft#M$MC z-qur9kLHV)A9@+;olSF2f5o1BiDP!`=)ibegK3B45c z-1b}IUla6tsW+`ezUPh7-Vbu@EiGYBzR%LneBIWGnMl820{SEM_#DzId*j*s_GUg( zo~h7p`=W|V)z2d6g{c>H@l*2O482>Z$M=kw=Zfbq`tL)3>k`eM?;SDE71tMkUxa@1 zQq|{s$fE7}3H18p>!rB(KC-CZ)V6a)pHwfqL+kT*(mg`hhF(8 z^fp6pGW9Uz^)lm-LCN>s9RK+KvS@iH;^p1C6k=9cwX|fMmY4P9;=~W z_7&PK;pbV<^HLA4=vBnt#n78fz0a4>yB&Jv)LT?S??vd{mu%Zvph4q#obv7447h&@2DCmbcV+*#N!CqtJU8dY)0}y$QYP)GO7$_L?|s zuasUj^o)O1cIU!BMf%rS(DO21OtpFy(YrXu-q9uWZqKn-s=vqyPPNRplz*QXq8(C877u)v8x&pLadb>iwGb%Bp^z-`8 zJ@ia@4=&-~X6TvnMoQ?t3%xM?b9+7`{iYR@|2pdNJ-1c%*`F2dH=@4*`nP;n#eDCL z{dsYHsi!3Ldv8*GOkH{v8DG~!FG0PM<%eDy^=6f@_im28QtQz~G)gn=m1>{45C*9? zzeK*R&@<&NWp6!%ro5%*&rQ%X{>giE`!e;H^PJ#i==J?b>zVJ}k-@S<<}ZK_wQ z|ICD5f_kOO(FDD=QRtlqy)gAkjr$)!Z_6m`Jp;W#>Xqs*??dm&QP?~1Ak3fCYb?>e z3!t}^dZ(1oYloiacCBZ(ek$YXP3TRhUYz#IP8`sLrkxot<7qSW+o)f`_+s%kEIITRV!~y+la8 z8;spG?sngW-30X;s9&s}Cc^GU>RsxhC;6|J{O?phv(H}-bT=!4ymcw9sCOH&-fSzvmiVP zT#4`>KGJ+L;4uhqCez>?gx7$g7XVKHXM*C_LEsF8$ADh&^$*q0K~VgB9Gncl9t0PF zcY>1;e!Pxbpb@E+(r0savj0PlkQey|nx zt^>vI2zWc<>%nWmN#IPxTi`7S_x}TDoPZr<%lq1{d;A?I^FS~5SG9N9kbRO-MJFis z5BZCR%RmW#?LF+*VCPHVnP3BW3b+r5xvJui@8UTL^nM3Qz5a@Pn!FEO0=*qeSg65Mu@)-l>B<$#=%Ko6cqha!Dg@qJObR0;XT2l5&rYX$6*Uy!c?F=xyx110{^aTnH1Hen#|K^ke&!j?cNb9d-TjyLK}Z$9 ze9}IFtKt#R2ffR{Gr%zTC9nxR3fvv6z<>XGLc>>pn-E?G-UrSCrF@4m-2V%$*Y|#| zoJwA~**gjS&`f&>= z{qvLmR5pWGBHfQ3#~uLj72uHwZym6#Z-K9XlI{gi`uk5n>F+-VrC-OvKIlb2>9@6@ z*!${ZDnA|E7kW!T>F;wu>F+;#MCXUkl1HO6Ohx<{a1qj<|B%{i2gUw!hIa?&K<{4< z>Nxl$X&|Yz$+1+cBi&$)9uQ;Z&O<2>{~Vb)DM+Q z$oS3LPom&0uu~60wc?cTtN%}3V_Ap7|79SGRxt~F0Q~nxP4^E_!oRp$!$*QLpKQV8 z{5kMH!K)y@6O?iNIGmO7br&f8=7->yz$-x+U*~`_zRm_^e6@fwzD@^ae0?62@wEpy z7E$s8gtGp|!1IvL;h@;v3%nNLH@}I)vA{<`bj6B&F!-e3ehY(f5aA=h7r?KsSO2Gh z@1ft{f4TBz5M81o49a-AE8+_JdN+W>Cr*0HvI96i)Jqg2=L>9$W_=bgt&RFDUt5xmNWiuhIO+ zf|CD7t2O_(K&j`SgOdNFpyYo)DEZ$8O8#+B@?Qo@{*NauD~d5s{}%Z*P^PFDcs;ljyaC*EmA2PwXImDER$pN;C0B?T8@+Gz`us= z6V)o72QgJv+z!focs=-2gf9m_Lex26Gr}i;o!~qWU$L#2!SH?{s;1&EA^bWRxX&^T zzw>1cZvn4D{CB}GBi%Ki`11|$6vTfGl=0I;#=!d!KM!1na@K&NcO>OsAom4DZ&&aR z@T0S|pJ%`e5dPpy)C>3mcqr^Y1d85Wpp1v_fTDNR890Fv;R^6&gr|^`$-TiXi0}WB z(o5ceN_h$KSAr4vc|LeL>}*}C`MwB>-3LKwmz&^B2KpD1QSt%b2Y_8o>?Fn*$z$`1`-8_WD4P zUkEM)libNI1_9KKSI<#p!A>dp!A;)k5|3~NU?er9T}4{tWtk$Lf93 z#bhtp4n7b0XUL1@Y53Q3ai%ije+kMs`w19Eek;IVBL4YW%W6UR0(7bnSP4FZ_`9pM ze|#N8SFTtFjz#^Q1WLav&lQWs^145h;*}O*=PS$8~}=)y~(lU?<-XAy(4k#F61wO;{QWO;H*f5 zzX?kCBCrE~uL7qa{#a1T`}gU3fBF_E_b1;6YY=}4h$2-?2jNP^SWw2_`_uG(_xGTz zKQDr^{`?$7mCkzLh$XP%fWvXfCBplHOTkYM)A9K_ zI1}=RLGj~C@I>(Hsd(N4e=h-NAbd6`@*}_i_~D`Ufu$9dgU2AeoNNO3 zM*K109K;_0t^_w9g7ycm11CU!DJbzlFa#b9ioN|ovG>sw<-1@Q;opK{??zDi(G8&J zUkRd%SA3mZOLmaQlFxZm??Q4lc{aI>{PV%8_d`(pxR&87$P3BUx z4?eGa2^@p)gWw+EbzmFV12%$3fh)iCvOI02wwn-pLL-4`R_@}UyzrRt3XM=h^zp`{sd6U z`_BGq=SEQMbb-5r&0sA!29$Z|wf%6PiSYg4(cm?p^uIn($}jJng+Xu*I1QBkecME3 z7byL<1C(_2px8YclzKXX^pF$DGV+tpvK>hI%z@;$h2j4q$B`fJtNMQ--yr{oe42a= z6g&4a{9{r+Ya;b@5h(qp4Qv4yfl@CuptR208^LZ+>a~`Xh&N{>UAGb>T^8#;RFpo zMV>)UAa^1E`5D#wBe{Y+gIr7=Pu7q}l3yTK?WO*x$AR~P zVo#2}7hXqxle~yrL#`s_V0Fo-4wQU84@y7ycsI5``7=!2ptSoK(2w{#$Llzf1WSBgiTucVYI+=&RuRwS+c^L^^JAIJEFtp400O|$b z$D7a(-i@XC7VsW04gL}A0|St+1OI_=3T!}F>iY~ZLAH@$vY8B$Cy{>eLfEMVF9N;b z??Dea8T>oK<)GwK2L2Uc3p@egt*HOi2+KK;YruEFZ-N={Kfo>E<=`OL3qA?11qZug!@3rcOAGGlzwt5*b9nY7(5JY1`h{=jIRZELD&nv20PP9 z597p&mEDe#8~_kvXj%jcwKgKgkZU>Lj!@@DWxFbGy6{v^-~`oSL|z81U%^n!l?JzzcL zlfe%VE(h)Q0fUIQz%PQ*Z$&Qy9tmy%XMlr@e*naN%7lLKb=Z;jZ^AU=H-fuD-Us44 zT@%)U??Rpc?+4q!2f#4nPXgaSxE9B%&i zBEzJgtOcbUUQo*6VZ23Vu)nkIk!dnThDkqJ3)=QT+aBXBGJ}1fZI4WoDKbp@Ne^j} znLU}FOp_@xOg4k!M-UW0{EV+}&54FDql=Oq3q#s~>noN;l(ocFwi_Gj! zf5|kNBEw`eX!{G={xaS}T4V+joLzt91E8eq2PIva@hLJ)`biIIkz04u^zwtjl0F04 z_Q^CUzoaDj^ns$6Vtg;-6O0ct-cNc+i_Gjw`(&C-kzrDP*2S(5(5?^0dq|7SjHf*^ zO{T~&=_hMJvFioJu7~j!nHi_{wt%8H2#VeSB5YceUZe=zR32U z@g7oyVs|V4!JoShfuffIC4PW>02FyYDDpJpQ)HO*ld^viec8W=zK8J^nZY`aKX)A< z(`1V51tom~l=NZ7`$-Rp<=oc8G;8Ytwp}bM55zR6M>9}P_J_B z$*{DCTxAO&N5x8*qq1y-YiXT&Ncs6y~@pb=MPq@+?@L?&&$Q0Igk9U<27u~ z7r&bN{ZlmmUm5#{Xt;s;=DhG4hRyll2h+YekNbxVoAarEMSXML^q~x!^Pj&>eRE#( zs|=g-iJMp-<~-hKC^zTtZlyhQUT!`0&H1yBQEtwAJ&R#;{^;kaZ_W$-Dear{K`)|y z1}|ges-)bUuh>bsIgf5{hRykC&BkBc49m4A!{&Ug+o*5OGpaD<#lvH{E}*_S59n&@ zoAY~mr2WQW4=`Udkk#ciR zyq9uw?$k!wAEdr~hhEZ~bDe%_(qo?_*X0bGbFE}QCi>=g3??zXIrshiX(~78OdVsl z$AD$drT=f1*PL^;nEK}2smav$vwi-?{NyF8T(>blbB_Gm2S|Qs&uW#g$NXda$M*Rq z^E2m;wo-1+fxHCe6#evOO@ABnOED~G2Z-F9OZTmM&Ci@eF1=LpH|M^mZ2wc1Ij3v! z43(R6;U=Mfi@o4@&F^j2uQ~VXI`%(v&gnFU&AFWNokmF?4r}^P>7RU9M6UPPzsxzC zcQBl0eN1C|bB^3oOmEJOlb>~y@|fQ-xNR5B&zviNy6rE@_mSpzQI+Zk89s+{^E(PZ z7)N^_sD2Ce$}(qV&i7PKG?M6no{UC%M*AZqDhK=a(Wk=LQ}II}$eM1l|k3By7%=Uv1YH z!eJQ0HQx3Y^T&D(d+h!?V3~8W-?jb4_`g);gVZFVL(aw%sJ(cBE3Cc&Qp0S>`U03 zqyA^k&*t3k<@WrH`I7czp0e$|tm*MC)()FYLwf$S}Vk?Y(e> z%FVfrH-1LK1K705%!Kj9u;(f}XvupGyZ%;c_``!Wy*XDuHdE#19R67hn{)ByJ(r|6 z=jhL;J##Mp_wD|O@p6sYKXi)fn{%~qLRi}0oTFWDmj~nH9@Xz-*qoDn_%52>oU45p z`@cEIyPW=bZ`SnJ?xp(Xoa{fs|AWy!skc==nd^Z$hw@zdU;cs0n zhy7J-%tb#HR{dS5Z_erb!$BfP|9nH`cTUr=IS2or45!(@|7yz1`k2e|1)2Zpv}ev) zl<#VYeRD43jR$IY;M zH2Jk_c(*#WZ+`dT0_JCar{of&A5(pK|1SOxv}pME_WXwVVxfjNF)S~Ct_nG;Q{jG!fc{3_+I85_1zpHZV z(Hb_tlQM^4b8h?d(jMan?3YGjVHLxveaF}a&#n}C%F67c;ZGSJDA(|7Z|xt7k;aDwvvsXs{h!g|W_;cL0>lKwS5W%==;Ou4%J8V*wbgyS?kNdK16pCQ^m zQNrU=){MP1{ht|5^Zq0y`9qFIvE*7H{*J?X$?{ATe-R#_{;w(bQa-L;<$X+lh4>5o zH09I$8qQGu@v$2AGrWZUOlA0GyF3_QEPub~qdmeb-z#?c2COZ#f4W_N1J*j0_dvV8 z2dsBkKfj_}zRV)m2Flwge?;cEtC{tECH+}Pf1>oKocdq?9MWU_n{(}F zOw_Rd2|GLMEj*AAxnDXyuFE;!l{38bP{_w&|IPZ9pXjy6_q(>b^*H1*|CeK3mg^;i zwImuo5&q_eU&DCG4Ue0ZA1=p!HCO)qvH9V!BOIEOFMkaC$=vjh?~)%r8Gh!<8?k=m zhFcx-c89#|zp`WzP0;)W@+7dvETWpFZNS_eXS#-26_)dXgJ{#nFC~9qs!k zDCg>b%ab2&a)bvR;grMvvwP>Km(PXd+P}n+|2~+{bLICq{Q0V*e3wqi*Y`Q>pSpX# zyw#EZW6W>4`90wXABFLrEB`L`Z@J-3j_@xI$d^Cs7;o=8!Us9V?@bQ*phLdV;omw( z{_@@KT>rWq{pWL^&6kgH$baPMUpgfDZqx)%lze|r zclh(S`T6qWJ^A519OGRMz|5^5`8;55_(O*r(@UQG0f)T9k$#KA|2rJ|Rw#6a{6=<7 zYcv^&wb!iZ?COXl1HpwY=USnTXh%z8t*x?9bo3{;tL10E1HI1Zx}JFVIX%%RWucbd zNGRFX)wxC#8q4Q3DwohJrRb{~{;_S>pVE?9R1;9kC-tDQYNNX;t_M z^X_O871+8wmJCJWts(rde0QuBKQ-SKwHqPdgW+EXNBLcU9Xv3*XGMGG8n>kQopRTt zg+Ee`?g4jl>`9NRH9zp)fCTJ+7qb?yN0X>t%|w*EOJo*^N0zroYtV7x}P&qBt#WLpSsJWa*AY+Oa!mHBBV7zya>nQXh{7 z#A;^GMEU)3c#dV7JYNOj8}KBjhmFiEx4eP#t@~e zPi{<2V@sqx(v^+MU5v7kITcZ`zGaoz0p?&G&koHHjJIbsa?Iq^XRf%e8|y=4Wi*r= zVNK0VG}@^oH;u2#EZ+8NG}_$QSZ3zoo-S*6yT^dTLTzpaGDscrZJjlWn5ff*1UuK_ zt4KkXquO>l{bZsT_6+WORcE464kc$YmZ($rC#7=CU!zdH1%)-WvomZB7iZhHZ;tY1 zSLmIw3vk%>VS|>lH`_Tojgj&go}rv;w!PDEBsaw`zQb@RJIgb=MAf(tNwnh@1UCjN zqFoKa)l2ewitc8ddC98N*^%gqc6UcxLy1TXDTA^z%N@nKdn)K>#nKiewf9iYq?NUy zP=xvmgSjo~3vaxrx#8`Tt5 z(;bZ`yVfr74X=va*=M5~lfKH_8<|`|RaZ|u8S9AZElyT~@^fv?wac|NH;Vg-f=bF- za;>DSMowMlE~6$FHBciP-1ca0qR}s|#uRcI_2O#MX!f~%-(G$4#f}9GYf;p!r6m<* zT^wnjpDl6`Pe$*`$eJVil53)AwKo*6tPb&s(dZuHI7+f@iCl6VVJUXh{^-_kCUWMu zLkqIQk)2`7#XqSLtjycEVJPn3PO-IJ_xR4fGCYND=t2c2A17Pk5n(_Y@W$7__Xy<9k&Lv+N^fRR_Dj|2ltJtpc1tj)I~rZpxnf127o9cxqQZ`Cjdcai&Z)Ajbao3~cIC@_!_~PR`6oKNW#*|~ zfj=62nA19zqv4WxQxH%O=ES-q-7T?LEXN(^xb^v>=U}GitFwI7?e(s6J_x-4HyXP&Bu8hU;&L`Q@W}>h#nTXZh@dWO)lPm1c=5(Om zY^C-{e5L)KD1_NuKV*aW)8cDlNV+82-P56Z_(V=?6tB+k-rowzrXpu2CWeN`@yZ^o zbci~`JZ;&bO+iP?j+-(IK%OZ_lU7F03AB__sKKr)wlW@A(z0Y>p5FEqFBX?QT%ZF} zM@|9TyX~j{4&U&Kq}VdrBAW<&t{~sU_B|*trE}PFI8vj}Wo5q0ae8}m%x=_Z%jawF zT#1jk*dJfP@EFd;m%m-djrg$4-m`C48`LFwy4&onY#^^VjzZ&&f9LSDrJ6Y(X{*8z z3G^<{t&dUNUGhmd%Ce-fDx~RMo;#o_WTV1I5%TUoP*NY&Q(RwHR(azPi?8lng>|Yk z)+!(K>gkGy)^y4KpsQhZ02^|7bKG$968%tzy|oRjUXVDoDU?&rZMTCRa;_N^SD&NI zuWUFOA~WJ_Kjb(H_;W6HMBt{X47d49yy{~_%Jsn@DUsNoNQ-L61#u58(*1$ z>*V7QdiRoh>#ujevT!4R^icdK-6*?#59GTwx_jdM^(3dx@=9gTA}CZ zZCz9uIv%%l_S3id!P9b|#x#0E+5hY$mGvuEc12gpeeH;8G>_o~C-4bh(L8KrZ`Pp^ zb?uK#bC_}5!Ber4C(+?%BvPL`GSPUbW+ss6>>X!*G_~y-FVuEK*VcD+Mb<8fu4w4R zMnp15`f7$hw%LWx!*rt!0=AdPG*92Q<*whV=T&CrA5yld&b2@J}(~B;~}Q~Dciic&CYVE^cLNr+Gd*?oe>R= z2)lW7y6o;*o}VkG%N&PnAM$S_L?5kIN-5E~Cbv`tMx;kL(l;$#ESsm6-rmUa*lPQW z0NpWs<`=iKcDZpYu%c6om#^uwKhQg}8opDL=){8s)}yOE`3i#U+vFqqj+~_y3%+Fl zuZO>9!1mCo%UAfG)wItD3b?!0mppD3mQrDOEw<}8f67D+Z{PM9ZIk^ffNbVvqHmH0 z&wWH6iMYQuXq=yYZ_wR^;nzLf@niVng?!6EoSSXlGng+nNV>Z0&J+7YzI%{(-GlzG z6%#iY|GyPedYh~o-AQ{N&zCbJd$n_ukGwEGlp<}tbpYcl7dYsqKWvt#%sRg}|( zesoOdF)o1oI9Xi}X1W!4^_i6CLEcbhtX0+2tJy6wkKs!!+4!9KrFwNNYR?_H^D;9* zeQ@QdL4{yzD|1K@Jr+J^9FOM9AJY2vR3hIpz=H{I2&wFkBj6SHkr{85l}9QSlzQ34D}MkM*mKEu#AhZWIW&3$E}o9wfJ+ffQDsUQRox9My0}zm(Ix8v=6vnqhGoL-y zn#@?+aZ}l5-f>geYT0p9&br#Z`MYRc?SLs~UG0D=XI<@pscc;h_r`5|H}05&cFat+ zU^`?+Zl(C-rLrm!>5?6zd?((%EtB&ZUbu +#include + +#ifdef _OPENMP +#include +#endif + +template +inline scalar IoU(scalar* rawInput, int idx_x, int idx_y) { + scalar lr = std::fmin(rawInput[idx_x*4] + rawInput[idx_x*4+2], + rawInput[idx_y*4] + rawInput[idx_y*4+2]); + scalar rl = std::fmax(rawInput[idx_x*4], rawInput[idx_y*4]); + scalar tb = std::fmin(rawInput[idx_x*4+1] + rawInput[idx_x*4+3], + rawInput[idx_y*4+1] + rawInput[idx_y*4+3]); + scalar bt = std::fmax(rawInput[idx_x*4+1], rawInput[idx_y*4+1]); + scalar inter = std::fmax(0, lr-rl)*std::fmax(0, tb-bt); + scalar uni = (rawInput[idx_x*4+2]*rawInput[idx_x*4+3] + + rawInput[idx_y*4+2]*rawInput[idx_y*4+3] - inter); + return inter/uni; +} + + +std::vector Non_Max_Suppression_CPU( + const at::Tensor& input, + const at::Tensor& scores, + double thresh) { + AT_ASSERT(input.ndimension() == 3); + AT_ASSERT(scores.ndimension() == 2); + AT_ASSERT(input.size(0) == scores.size(0)); + AT_ASSERT(input.size(1) == scores.size(1)); + AT_ASSERT(input.size(2) == 4); + AT_ASSERT(input.is_contiguous()); + AT_ASSERT(scores.is_contiguous()); + AT_ASSERT(input.type().scalarType() == at::kFloat || input.type().scalarType() == at::kDouble) + AT_ASSERT(scores.type().scalarType() == at::kFloat || scores.type().scalarType() == at::kDouble) + AT_ASSERT(input.is_contiguous()); + AT_ASSERT(scores.is_contiguous()); + + + at::Tensor sorted_inds = std::get<1>(scores.sort(-1, true)); + //at::Tensor rawIdx = std::get<1>(scores.sort(-1, true)); + + auto num_boxes = input.size(1); + auto batch_size = input.size(0); + auto mask = input.type().toScalarType(at::kByte).tensor({batch_size, num_boxes}); + mask.fill_(1); + auto *rawMask = mask.data(); + auto *rawIdx = sorted_inds.data(); + + if (input.type().scalarType() == at::kFloat) + { + auto *rawInput = input.data(); + + for(int batch=0; batch thresh) + rawMask[i] = 0; + } + ++pos; + while(pos < (1+batch)*num_boxes-1 and (rawMask[pos] == 0)) + ++pos; + } + } + } + else + { + auto *rawInput = input.data(); + for(int batch=0; batch thresh) + rawMask[i] = 0; + } + ++pos; + while(pos < (1+batch)*num_boxes-1 and (rawMask[pos] == 0)) + ++pos; + } + } + } + //see ./cuda/NonMaxSuppression.cu for comment about return value. + return {mask, sorted_inds}; +} diff --git a/encoding/lib/cpu/operator.cpp b/encoding/lib/cpu/operator.cpp new file mode 100644 index 00000000..a74bd991 --- /dev/null +++ b/encoding/lib/cpu/operator.cpp @@ -0,0 +1,15 @@ +#include "operator.h" + +PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { + m.def("roi_align_forward", &ROIAlign_Forward_CPU, "ROI Align forward (CPU)"); + m.def("roi_align_backward", &ROIAlign_Backward_CPU, "ROI Align backward (CPU)"); + m.def("aggregate_forward", &Aggregate_Forward_CPU, "Aggregate forward (CPU)"); + m.def("aggregate_backward", &Aggregate_Backward_CPU, "Aggregate backward (CPU)"); + m.def("scaled_l2_forward", &ScaledL2_Forward_CPU, "ScaledL2 forward (CPU)"); + m.def("scaled_l2_backward", &ScaledL2_Backward_CPU, "ScaledL2 backward (CPU)"); + m.def("batchnorm_forward", &BatchNorm_Forward_CPU, "BatchNorm forward (CPU)"); + m.def("batchnorm_backward", &BatchNorm_Backward_CPU, "BatchNorm backward (CPU)"); + m.def("sumsquare_forward", &Sum_Square_Forward_CPU, "SumSqu forward (CPU)"); + m.def("sumsquare_backward", &Sum_Square_Backward_CPU, "SumSqu backward (CPU)"); + m.def("non_max_suppression", &Non_Max_Suppression_CPU, "NMS (CPU)"); +} diff --git a/encoding/lib/cpu/operator.h b/encoding/lib/cpu/operator.h new file mode 100644 index 00000000..4e1a48c5 --- /dev/null +++ b/encoding/lib/cpu/operator.h @@ -0,0 +1,74 @@ +#include +#include + +at::Tensor ROIAlign_Forward_CPU( + const at::Tensor& input, + const at::Tensor& bottom_rois, + int64_t pooled_height, + int64_t pooled_width, + double spatial_scale, + int64_t sampling_ratio); + +at::Tensor ROIAlign_Backward_CPU( + const at::Tensor& bottom_rois, + const at::Tensor& grad_output, + int64_t b_size, + int64_t channels, + int64_t height, + int64_t width, + int64_t pooled_height, + int64_t pooled_width, + double spatial_scale, + int64_t sampling_ratio); + +at::Tensor Aggregate_Forward_CPU( + const at::Tensor A, + const at::Tensor X, + const at::Tensor C); + +std::vector Aggregate_Backward_CPU( + const at::Tensor GE, + const at::Tensor A, + const at::Tensor X, + const at::Tensor C); + +at::Tensor ScaledL2_Forward_CPU( + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor S_); + +std::vector ScaledL2_Backward_CPU( + const at::Tensor GSL_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor S_, + const at::Tensor SL_); + +at::Tensor BatchNorm_Forward_CPU( + const at::Tensor input_, + const at::Tensor mean_, + const at::Tensor std_, + const at::Tensor gamma_, + const at::Tensor beta_); + +std::vector BatchNorm_Backward_CPU( + const at::Tensor gradoutput_, + const at::Tensor input_, + const at::Tensor mean_, + const at::Tensor std_, + const at::Tensor gamma_, + const at::Tensor beta_, + bool train); + +std::vector Sum_Square_Forward_CPU( + const at::Tensor input_); + +at::Tensor Sum_Square_Backward_CPU( + const at::Tensor input_, + const at::Tensor gradSum_, + const at::Tensor gradSquare_); + +std::vector Non_Max_Suppression_CPU( + const at::Tensor& input, + const at::Tensor& scores, + double thresh); diff --git a/encoding/lib/cpu/operator.o b/encoding/lib/cpu/operator.o new file mode 100644 index 0000000000000000000000000000000000000000..fe5bb8bceb4c26ea2feeaa321612847738e449d5 GIT binary patch literal 488784 zcmeFa4|HW&T_$?V?t-A;4JsN1B|e%lt&XOBwxnr=GD25F*8F@s$i z2Ww1w>)<_g++)^^XUtPevgVCr-i&W1@6T-m&S6qby-wi01pyg}V~++@$dwWT(>4Lr zdf%UY&OJAkbb~YV)_QMst;+q*_uKz}d+*=gXYYN^dCzD5;%}bbXq>vKfgk*x#@{N* zDE{#8EAcl%e!{<@DTZh?Hlv@CBhCi@oAzG82Rd( z8()CSvEMn}Xx1aD;`^~5dEj+#e9ezdSa3AH)|B%#6#Wm~b91Av7Q6N%K6ft0_pQgr zHxuI*DCan`TTo%`X}Jn)*nU*oMn79;8{cn8DkH1dAQ2xR8viyHK(BjY8ede8xZ|Bj|>K9+sXw2KizqbHXchCGn z32u#i7yc<{;v@Lm2fvD_$8LREW9-bS2I6VV;E(_QW&C?Bbn*=N`CUmrzn`ei=OEbISb1-K8UW!T;gb)Gi`y^u7ka>3fmF zUON3~HudORsq8zp)2ZKXCTE#ic44oKWV==6w3SVP4Yjg+hFj@{RcPn>d*KxqkI1)^ zi}{af+2Maeq#H~+oBr)Pt~8UYM-tf?NCd1*WTi|57f+VRmm<=tR8E(o-gsT!yN9B? z>mqw)efIS8b+~GUsK#t+x4nNVo%-eGFMJby;1BAVI&8JCF5LHy%3Gx|W?(CwURI>d z=TA0b`x=GD4qNGYw5hZE@O{|IaGjlvmDRpFdfz*m&qu!G|G{gUOefs_kG@l_@t_r+ zmGCmw1><5I>C}M|>igV>&o|5GcQC7GnAP;c{+VxlG7aZ*#JYT%Q^PWAs2#EO~ z+Vd_v|NIS}ckQ`T#{4t(yaUfmH+bH$=cUr~d3)Z5XXPlX=XqP6lf~=Tub2K8?0*yf zfBJ^VH|>9+=Kn|Se*^yCc!U29`=77*zu*4X;s3{P@V{>Vb2b0>+5Z~+zwQSAYxX}| z^Z)(!zY727Zt%Zq|1*{UG-=_w_25;u1-6bQpxcM)f9mkQcig``W96hb-=d4<*xuMf z)kf^xzB*iQU+rsigYEGwTFD2{G`HnlKb@&9wf0L>TWhD9$u}bHvV+UL_f?Qo{Y|zZ z-LlI#y86aEoh?Aq+fdl%4{XBYM)TIm8T?J+ZybN4_#1BC+GuS}&LL0(-eCf!cUoI_ znQz@SEBV%4viy*@BA<+?NeATY>xk#Bv52D)Ig(I59C64pFXO>-xj3^x5coJ8%C>nDTuEuNm5H_*-wKL#_5iqnU6f(F))3D=)p- zKBMutU?roJYnHUH&cT$FHurx0`=;jeFEaMhemr4)Dj%1ImOgl@`OpHDX1$ZJhgw{JIeN}{37zhQpX3Su3#vmk-a9j#FZhqhK06zQm=2hsh!Qp%q=& zzrLfe{U0c04$mmbwN*x&UO?LCG5ViHrA+Ulv9aOe8r?VJ-?soT0$kW` zrCl1%R{Hy`G}KJu1@ymepJ#Gwp<$v_2OUTXATiHKbSy5%U z47cM}ktKA2g`{@eET)#z>0j)r5|CB3S4!-^Os90;IpUsOST38?VE=ha{~m*%(*INK z|0(+e!}gT@88iq_+5e};Uro!N8hzt|4-ll zwy$}8g6r3D?bn?7Ivldbd+s{~k&;+hoJHMmTkUhpFD)MlJ-PhcG)u9#!< z0_#b8Sax|n23#y5?80p?`a2I2m)W#T8O2z0($eDjoyr+2F`HP4nH*eSEz4W2Ki#l? z`uUb`9ED-wV!@`d$UcR&b1ZU3eQNb3gB&{!m_EcijP}*>ZWF!`~C<_*DO$$zN@u$^A7!(W9>GTLvI_d1BFbduu^#X@83>^O|77bucC9tHNG^N ztm$OkI-v;N9&3zPhs16Hy8k+|w*kHmj)r=66MP$d6W2RH{2gCjbm(${7W-oebpU<{ zME{%G+nRcmMQC4m6lIj@z*=lJ{b;Rp(~q)f(~tH(&@WfPjH)H5pj_}pzMn0qCz1@yu7&L^{fr7hzI0yY3z=}QLf=TZ0UrO``$Ka}PW z!D;U~tk5)0l?J?A9>0HZef)DDe?~E$d_M8RKQqGp3jUps`wm*8zj1u4{A#@#;hv=Y zN8Q`kKELz^{8wqV(oebdXe{&&a(_P~u8O_?BdRN6SJXt4d1`!CtkJ$fD8e`#NvIe9#sMn>z$8_uE})B6|~F*b731EY8P zkyiR5Y&pPX;P6BH>P*}Q*iNUf%7BzTsGJX!`=hr#zA*&dq}b5&MrkYkG%nEy-(#hg z8(UBg_D-Rkf&I7zL_Q4v(8KvXSfpK8FKlm5VZt)KiH5uZcevGe-7l;`z79G5rV)ml zH~f1>D{W20G^f{F>0RR})eg#OYnM%LpzQ1R5T3&dz3Xi}LNb+Ne>*l*V_ufkx(wao^7Ervw`g^ke-@Z0ZwQVdR z8>NC-)+>;rWVo8%Rfat(U`m5zFq6@K%jd9v7>k7@PHsQtv6O~No?`x&vSnby2{nM4 z*w~udL-t@XH8tE&ezvCmG|H&pQWlkuZL_}YTJ(ouC;G!26QjogKd@XZQ1z=^R#oRtkcz0Hi&j>;ba0Y`US(eC47UZRU2g;fZ{V8T) z7rz|2;Lt8#6gMF5@w^M##NWF`GZl%LU3lbmZG>==PcLI=FAiZMtpTfyb8#pZvasKm z54A$F!{kEfDAnAVdUyo$mI+G)n3R|qFi7W16ktozazPqaB+Qp3G|SA=ux7VayNPAF z7`NY@0c>pAS&)WtGv^_!n;Bj)gbCL+C278C2SJ+bNf>q|G|SA=?}x=F|rWBSxQ5r$CQ&p`xMK9SegvWEesK|$dVB=1FFx?X&9B2PFs%y2I25e`jerCvf8agadKGV7U5ZsN?(wRCE=bTjfDr-K_1!GX`@S&*G3yT-c_k9XO#j|NNU9 zo#c;E45iXOY+TC8I{6>rw|Vh}*7>Yj)dT`eB0%Z2nO|A@#Pv>~*Gmb1W8Al#y7Y>B zmp=42Ll1Xef#Fi=UApVuTTY=>U;IzcP&G2jORal1d#7;t=J6}&iC6m$t@h@8*%LH% zpB#biccDAleiklGW?e?HtPo3+1-TXTb~8&B#R@@MESU+?qGKjVi)FH{m&Y66mk+7Z zeG~@se#M#=J1i)=1;ABU$dK8 zvLTiWV1La7X})bHNb?=C6K~xWD{tMCO=UjcsAXQFtV>aO&#va=HdT(ito#|AWQUoY zV`)Mjt7Bc^>gS-M4|E)6Dx4l?ur%3Kh6@RCF9v4yrO6tiaB3l0WaL?(h#g;Hash?` zvNS)C&!|}JiiM=Lc4llqhqL~2e7iIzs3Se8w^YF`7 z(k^(_zxk(Y7AULT9*gVdpANn4{!MbC77~I=pN1Bkp|MFqmWX9R4DDWSVMcC+7?wR8 zV9o&qX)Klzn2lH zwBIp;jy(y|uxuv4{KiaR`@L$30COBO0m3pzSt3BUlv~$O>Sag5M1Y3q-~tR<#WhTJ<#}N>a zohzR?)q3r_Vk{^!BZFO1HDxi6(fXjinUV}xNS58HC_?B_MVbrH#>ueuC!twph6ZT2 z3A>49xfmAQB#dxEr?S^%&fx`UmSzHE(@cQI4YrlM62I%dAgG+)!$nl(ldG}cj=Tmbt`mgdV6nqm4aVX|ho zRlA8{`fcI5gt_4ajoE9m?eGH3xy%G;zGea#bg-@D-{IF9<5gc{bj<=~b&ZirW4sv9 zn2HU<4LRDEAWino1end431E`Nf&k6+FBr~%X_gtG)|Zkw=(OrYB?QussRT@5}^f?`8s;kMV%dJbe?VZqJR;+=@R6j-{QT|n|I4jL1M5RPG>-M55L!qC{b$x17GV3pOpu0ou|kj*3uc0} zSTrNtgIz9kWeU(F$~r(}<26Jp@|p|MeAP^l=4)mG^jom4tnRlmp3H4Bv0 zdkndH3@_AUT3g8^3dbmLIZ<9>DCS%lW|bw+1kJY(wRy_V$6wHs78T z+Mjt-Gx;j4fm9({r`n(X`nV1BT!&*CD6i(??JHP22K%++9gG(PLz0SJS&~IKqj4?}FaZ(-u{7z(Ei6e`h-Habnk+kj z03*1WAWc@qf&i0CGXc7o8KE|Y6(g#IWEfBlWhBVjo=OEW(LiS4-lxF8OI1l?MaY^F*5-MZ8JiBX3SVn zWP__Ii+QYO#`{c(O|u-k*~HxA+;Mf?n}m-e17t6boeXfq>BjIsi)nsLp)@(O%rs`A zUo1l~qaqX%V$U3`?Dl&DIqW^>D;kaF+Y^lxhZma3>rWjuL0ID9fH0>RBit|`e5HeV z0I3fM3k-&lSfLX!8E^?I1HNn33cT#S4Gs0}=`w?d)1P{l3fEC&g@m{_0V@l)Gf+5G z){PljWnhfyiX#zIh9symwCxNTTD2i%)4?|syb#0s23VPe&4DbqlA=j(Z{om)tb6Z& z#FkN|_7^sj7C5ro(qdTP=#QtnTJ48(nFHqmO@F%}^dDvWvuDP2vobOH3Sm#< zMA%V|lWpZV5zBHh7!Mt1O0O zjqzdjqMU{y#*pQLG#MdF^I-|iGBb>Ib{n;u7{)pa$0f`SCyZzInzS5VfHBKVfZI?r z0cJvATgkWM7wsM68L#@|8C|nLS$#Ys*LcQ@`gr!7kuQ>pSFjp?gNhfe;+1-xBo+p# z3!N14LB(?f9CZ)`F&wLqTUd~=5W|CB5++L$8ZcCv2{1T{1p#`UnIH|TW`vsAFV@_? zRsq!jNP?{GID`Nrl9>RDEoKG{fGY@84uCO&HG2|Z1U3_3(rPB~0Jv$100X3%07YSr zvP6K+Ah)ie)XRZ{i2yCv!3DUT{RD#>Fq)bXYMSUSyVM;(r3n&fV$2}~7_rO*X_zoG zXaHP7pmG3=5e(at03)!O0C%-!g!%x;SWskxt0{|ltOmeEro^@k5vi|YUa+*g?i zun;L01ZmhY6QEO=5o%4TSWqP-^MI-;ksxbBpJdj805dZ)0osL`L7i$2fyz!5BiM72 z0!&NI1elhZ3EZg;EfJs}H~|4B8O#y(*PXFZxpfVtUdAL$1h|27Z~+>=nE(yWj8M}= zch-^504hz8KofHgA%HnF6JT50%%D!yLZGr!#RyvVBuK-InE(qFW`ugDVk{`K!PS(- zJXW1*k}0ui#-3HMXL;NU1#Nh|E)SVt1*R|5;97d{;fmh-pLyf_a|Q7qnRxaVkCAb4 z?5MvdSPNoSk9hwt$@$NJo8}oeZ1G)ua0m|7efm$5Rnx!uR(Q_GZ{pZk@Z{u?o9AL2 zS;UY@CuOa)Uy@^Ch!q!3zm*ZdR;U^a39-ft=Hg&tSJo2et&9?@BCK4j@hVI%fGHru zT9JfiSmTv2S+m=!-NdlQYvH z!hwW^7;elYOd9`L0R%8^W&#`y5DNkrH!}eSJu^bhTomJ~!ek6knF|tRZN?!4=-*}n zSTZw%4Cg2Um4-7$FlJ8z4B}=2*fKML4d%|lDw#O z$yn@aHZsbTa1Z1J#j+rVu~cqhOm2l(mWaW&Ie-9nEoK6orWXqW40>h)bP6*s=%%D!Sk3eOoiV@7&lK_LbnE->hnZTWD(Gmgrftdgn zh&h7kamH5V)-{xRS(7jkU_R&I0yKOx0UDecp{9xMo+IA{RGJ`xCiWacfXtW)Fs(8( zs8j7AP}!+s1l#r`z%E{Q7`2jSIg`EpnK;ri|^bQI5K(Yz3CX)tB;XA!JLE{J`!!2pTB+n_y4Y@90!OS zm3JupM!-vFLTJb#?Ej@Jmw>h<2zwSD3c`Wi%#z0A^ce)WQ#2D`vTi28ct&>8>y#WT z&-EpnYF}Xk;9_TmL|Dj@i)(tIikW%&Ev>0aQ<;@9ri^nd%1$Dd1u>WoxrK4L6=K-C za)3z(5WtX_2{5{f1py3+nE<1!8KKshigjf_Sp!s^i3C|&b_f9siJ1TcjF~}ZWfg%+ zvl1hiwSL7sh4#L69H}o9b5oIVkW?% zgc+fxiS9})HK5YOvSs@YA%Gz<6X1l1nL%cygFvNOi4pABlK_UqOn^lgGeX_0FcuWq z;A+ZZ9xJo5#FWsFE3?vXbEA;LhEQ-^x9$D-*T+!3Z->uITFvh75D$F{hYpjcOgoiiD!0Owbk49w{K%rG`?&seCj5j zY%F_E+$J1Wo)Q5DWitW#l9>R@Jz_zCnWC8hGet8(tqCNH&}UWmB4!{s%?C5He=v-OeE4Ve)>mc}r_Uir`4+J2e}Kz+ z;jzR>$Ex5}7CXuJl<8d3WmP7=`s|nhDAQyaqF*M8sxsX>u$xmQg^&~dkh)fx?itQr zG2H&pD2`+|FYQJIwv&8KKSf?L#ovYB!S;_GBb@d)*SyGAS(iG+=2ZyG zQc+lDFTxaBohcE+V;L5%3c`xr%#t;+Ai#Z_nE=yQGXX{&vJ>YPidA!qVJSKc0o;+Z zLZYlo(J_aB!|Att@+K~<>@WqKCsa|AZKYlqa=O*eE;JBVX-n5!rxc;->?&L?K%XH? z^F0a8@cgud$)Vj2>?Vd2zZNzQ5e{d31t;9s*=sT)KL)fpGXbo&nE-crU|Y#E@r!1I zr50ZG@9XHA1zCQUXIlKUk)l7hCnwbEL6ku4F z$FDWUtG>qQigkJX)-^^hjq#$cv4X{xI@EHGbl4|)aDoAkc(+=y&RWKaZqAnL`2W+_ zmh%zjn;W)p!iMcT(Qh|KA*vqFOAg9<(OXzW%NMW4^H}L!b1+#l8QDE^=z}AQ^u)es zpLie(p7mT-71Yluwo>liR}mr}I^z@-8xQ(Y z=_~feVzE>LnxW0y*1V#@qabB0mvmW;Q}3>gHrXUBak8K^3PHI2_K(HcQz!YOeiXc{ zqWGUxf^t&8<=zGYA-5C9EI-aO_Ryu1t4sRC(y--pcELj-uP{wzDPxQd_H5+)NVDj@ zi)C@LN1PnyU)@mFIhR!sZvV5tsLJ}Depz{0l{LSitm_DbL`RM(>#WN}r{l^>=d!Hw zikrh)S^L38nW&REwp{OnyPAO8L7b*!i*vB(YB6cCfr!hA80vVvEFK0jUbl2=Ky{FI z*Q!k52vyA^uUi@Be~v91@VHX*$mcQ9$SFGd=HCp1@C~{smB75(@uQf#qr}Y6jgEii zW0P*+1JlLjS|)g{NlmWoq+f{FIXfq*mO^^hs$D+Jtu3d~R;c3ZQX zS+Zd-0z4&TCctW`nE+1-k)8aM5MA|@(2}YoUj$eeI4>_fbtyV_g78%z)!??rII5!T zD)qvU)2%*1SU_CSBqiH!3lw3v+gG?;fMJmgkNrq!hDm^g$lz;>CL0M@`vfL;W)m0ZNHHO8yH#^{;_%IX>;m&SNe(^#}wd^|8b z=e~WJ)I+|WWALbLu}tNJE}f*i*vyI*%jsN~Czc246mx7OioU)^fZ-DaF_>Ptg$)S{ zF`TxNFxi&SfC0%&fE_)tAixrgnE=h*j8Iz~DCShP$t<8+BOpQ6Mn1!=1pzje%mg?m zX=czG!3+YGYXmWZ11BlKl9HJK+x?C}fJ#8?8nQ%y1x+&nS{`$RWADz`xZJviQZEw{ zCIZ~pI=BGK6J`QLW=5!KqT6!hlYmMSB+$fyLkO@wVkW>&ftf)oWfKTgu9U?HX6;FU z^$#-vHYUsn^_4Qlf+8DSO~U#)dV_28y7u1BJ;2IBWP>Y3xu!vn&_G z?VH_(2zV!eAQ!`%ugnZ5Xv|)dafcUR05B7v`I-rE_XxI?+=5?gj8}b)(KQQ{)ip*g zjq#$Uv2sF&CKDFfWY8br1C6{zKpztXF&KHdg;~3qCG++o!1|q;0G-23fX+d7@(l`I zwOBCfIv4@Crm{jJ?BmMi#*K(XnGZpgi0wf|m2{MPB8CMI3s(hUMQ(){j_cWNO~OQg znU9$OtLunjnEDW*kC*{Twp^Orx1W)^s0%N^2S;n6M`SmMzQ#F!g4Hx;14i zD6+xTghRv1Pi0N_m{PQ+W3~+xp@+;X(p-SW2(mO^kkBkM!#u=pOLh~>axs`+2_u|f z8|*b%b9e!orI`TD(oBG95ZG4o>-e>8;8ou?&@~H`)ola0Yy&T9wjn;X6oN{RMh<7x z*CruL#Ihg;n=ZGoBey~fyTuN$=KunjW-|d6Ud4g{a{@B~+T~v}AwjJv6}zg0WCu_+ zB@$$9+#v+m|1cAvU6>ivskRZQ>{Ky=A$t;F(qSgRE~=Tpoodt)0s4WN0Lo&Hpe1Ll zCAY4j)XR*7i2(Zv4lY2$Hxr=2nGtH5=x#dl4M3#{5@@315CWJ(GXXX^%?#>P>j+eK zsu;n7JqcjO%>*#xW`ugDVk{`K!PS(-JXW1*jVZ-WHR(=85jxeXBFzP8<79Y>QbM!L z3=Pn38+H@RaxvU{N*LjUPGzsjp2G{!EX@RHmSzGpZm_N7{rGjK;#I#>(KQQ{)jJir zIu$SKovL85`!@ndR!w?$9)MOKc6{ljBqzk7`A*N&0B=@{9okCEMZjO@9P zVM&AqJCY@j-l3ocDX%$Fm)9Jw%WKY9%j=ceb#>!}B&wS$T=T}M^v_%Q@|sH;y6(LJ zr&-s3*21*LZ_)}Jh%L(FKYhp185 zB>}7yZv|ncbVnG5#0o)L44VnkV#JJakDe?Zr2s7-9l)8-bPdsjyygNt=wl|p61?rW%ag6uC~bwwau;M2T?Z0P%TPdBvrOL$y>@^dr6no zk|STv2~ApMoI*wo%_3f?b%ucDUV&cF>SAI!zl^Z71rF%<)P~brf1r8$oWsvMJXsmO zb3*tv%lxA(#?Y&0H8UJbS2^+jHTLy6b|+64g( z>F4mls;Hr#DqyKwrn^SVTq6%uA$va+lTWz^D@ z6;|#XT*QOKjZBqyeKQrJXN)3ygNqXhMW~S#r}iXS%(R&R{nktXGYtka zjbBtJw9c!3SD-7*G=A${f!t;q7j-l3VplDC$Oo|+K7l2MK5yZOAPmc`5W{_--9{x$ z1h{iE6JWL?76j<>W&+Gs%m}p+znD`cB(s2O#3w=3E;xh$1Gbp}1GbsL%QFa6&V6D8 zEqfB+F3?PXS-F|ObDud&1elEHr1F*5<4jW#3H zG|_E2@<~9Y2@+^x+aUxv-e@MkeUh2M%QFa6&V6D8>-HqTez%zb_kCuB`rK!NNn?3N z;A+D1jPg^>ea4v*n`Sh61$&lzx%;fHpXeE21~_7;ZVdlpwc{N+1Ymm;4)jcCvhN2a z#jsshdr)$0Fn_2(_54YkOeqcfy5m@L9AemOsl_om7zd6SGY&i}!4To}$opk@79Czf zJS$NPKQa*Bii#QW^S>YWMwzp=7~g`2MVK-iDuLH6yjHIH=7-@~3hE@y(!(WPRtEYl zeJ@|!_rgnhnJ;VWagPMei=Y*k!){ z-hQ&p@{n5 zFCFIlm|I!-z0@P$t9bC*GUE?;t$LWo)BP8g;dQwc-wC<(@*%k51wiqYk?CjfIshDw zJG+9{?5_Y$~}fS6=h;9(W6O#*dlfWpH@OclnZjj4zH1oyl zYFmOW`ow+4Ch7!^cWg)ZX7V593!gjKn&j!p#v_xn4eV_%eeiEOt$TIx*X6}$%ZpEy z7auP#{Pb5)4%*ih-Y)vjITMu5P^7RVwyFa(@`Qm%@qo||jAGBWG+#^NOTP7)S_iQwL?C`K{|1zAC$Y>qXr7DySHu zJU}mIW$5GZrS!s@oU8YNKl%IBS^tNqOtb;^>+=VS;1;}`Y11`-&=}h3{~-K z)W^L4f3kz7efFR;^>O5NTfx}|xj=OEGu7et*F3^W2|ivNJ40+}r{Ni`?^y;q4}Ph7 z4P$+D~)JCa*cu{@HJ4olQJ*UP76`Y_So;`Vh3#%A|wJ`D8+vJDBU*UX60>7l_f0QHsEHpZB3Q!rgU3}TWtDI(1y^*?>MO1 z5HzYJ%I`BEw`lh#j%Q1(%=E$vysQ9MfvdnZ;2ID)z#keG5H-!uDWKxxIKknsw5t3B z4#y3AjY$eAy~H;GbI4bR-%O|f9X!+m#%6Wf=39s82`^PVkEk9QmIcPAs9=HF9M5*r zsf$s0uxK7wVaZwjAIQ#M#8s#6Jc=MFIR1`x9&@8X!#U#o;MFB$5L9?j8?z!wPr7?l?vBt!GMNj`cKJ{_TN52tjSL?zv7x?l-tjp=m)*V-x z7n^m*_+b%sYLwNsLiNS3SHKLn+FOW&BNM-(ap7=l>M;(vTT=(LbdVlme95LCOQ)}0 z-%cNH@81kx(8Jm7bn0sJ7r5rw3a5Wmelg8^?U!|0ryjokp=|0J=0!UPO?=g8YQOh# zxE~R5ESg&`d??b2s zl<4lxJL%!~_RfUMGw^*Ty}UrkF4GlkUczKf!X&y;*vc+T=>0kPT%s_&yl9^tfrQiP z2;!rBI-MOlgL#qNW(nTtMDPg-we1PtVt9lcs({-tAw-sp{S83wVb&W^(4GX2BWm5)AcI^4W_cW1YQB3)+g@Axo;bR@WHz2CQIrBC5oKKJqKG%N773NI_*tH>n}PC&&xym2N(Hnju~ zOGlKui4*ZGQuCpmsK6HtD3DY9IMas$(d`OPa0PumP=T}bx~2IsKFvKa{kukZ;srHg zrVRn)g)9HZ)C)D$ua{c}>SY5#P%nb^)(Gll(-J~oy{wmYI&39t06s4PM@prg{~LMW4$m1WORXmY870bk)rmx_!jUd1=^+HYc>*a+5 z^)iAWs24$d3ue9;wS+KOFT-@$N`?R!j94#}ScMeEdfB+4URGgL5pV?%15$$GQi9@c z3NCL*DCS5dc%pjQMhNJ4-_h(jnq5aDA-W{JWS1p`F+?`m;3-TmAK0gaX!l^Q7s_M3 zFa>0Ehk&!hg3Id?nq_8ibG@v)Ud)nZ`y?DwFVs}OUjEKNy$mA=>P67r8biH|SV9=A zmmxZAB@F=QNpPgZDx@&h%jymFvceR%l4U>)NC}Ed35vTRxV$c*m?M?oiRxt&A)wzq zN3-i_b{vg_s1SOAV(sP7UJfWkeUjm;sDjHBx?U)c^}-a8(QN{{o8a=Agl3r;HpX2q zD=z)4m`;%)cn1OQ$)4NFo_o&=UX2yc&o-Jl2N|k(z@v%VRlw5$&1ff-kFd!FLK6; zg&c;yG=%3=I^pMV`FO%#tQmL%+^dCv+@6;uMms1|R z%DLsMd3SMlwLZuDi-525aRmm;YP`qz2qxed**W1ZKM{!g4Bj`!1(a&Df^%oxO}6nV zEPl%`-qbuYg`I!ga`$KjKpK~(q-HMZOb({MaBTWqJHW%k=T|lKGrecTn0mj22~De9 zuxphI%#GF^i$n1PNPR~>VT=sof86fk*4D);s=EiHn(!?RN_yL72g|S*cCUA)-_lHY zZ(}D0&#ypuY3eQ1)CV4)`T*{X{sJBtgse0D8_kP+oUm2iXgS(4=xLa-NRR-+mzaRAC(s3^Ex>@QRH-?+wzHL)>WAnhu2i7p*yrw(P)%~A` zZR|{4wlhLpn!3y!#Z|#?c0$`)4cJJh{}0FYcOYg>_Trr$TEp}z)~{LBwc1_aCs*UA zDvw>y`Mm4#9s z;q{vu!0Fxlo8Pppwt#mEH=39J|JEYrp0GvSX1yP65n~XmMevOr(2^FGR-sKwPizxM z*9(W$*D~rAMg~hU?H4yX)1PWyd>#d5AL0_txEf;Fk3My2>Qm(^F&0p&@8?b`60Ik5 zZ5<0GfqMzGO;`=px+MC;yU``e6%2Hta%HgW68lODmMyMKqF?Q+KOD0JdSrcixSu{V z9oMtH%EJyZSbi3`%IixI6bokD4tXiQw2Q!I>5FRR8|}v*%csqdPtmt?UEePm_`YrQ zA0tOg_~}TDQF~{e7uX1?1uuDn2R)Ul8umlV7JXy;nxIr^xVyyboeF0NY=F z#C5A&^Zwg@pV{r9%Kwr5O4dL1dGhyz#|P>gn_b*_Nx8@!*AKP2?FGJX?H?o1PIvf@ z|KZ3;t*>Zx%RW2mkK{tX*W`0hzbEOdwjPU4QnvSg|Bq>3 zdiikKHgD{2()Y_5=EnY5Yj3C%s2;kWsx=)J6#Cd>gVMUEKTpdqPfESqY(vMT zH-kiWp-WUKx|J5_5j#@A=GM_=(WygxM>#wDSbFx_b$oTC-HXej9O2J%;l5fL{VnAa zYqPbb(N7_n>FsiH^n2g~E1`qJ7au2l;c>#@V}#hWZSS9=flfx@j8g2li?YLs(WY!& zf5lL#pc$;Iz6q06{w?E=6W6!luiRh5{_wEc|4HrP!D|i9;h;KvT^Iqe_BJC3Trk0k zr-WG)=JOIp_!6#3O3)SHv%O}5^!yTR;u0q6UUR`Nb2349oEsJdF3`9IX1d0r_$u}@ zR zh~`n*57zeq5`!*m8|$6?hVj$wL^Zos@W8D`{ zc29qLNKNqWs{M`2n6O&V9kn~jEh>DAb!s>E-Uf#R^;S>NyuAhchK|W;!{B&0Q^v=A zJKbp0)Z^Zk`}1)_8s*$}O3RsPl)oxd4DXGM^}542hBs&L@YD>6g`d0{5?m_yz3r@V0l}rSHm3rB}{` zvzuqaeQT|7nKv3R)eC~0YoU)Mug5Q}j~J}$BHge-WiK5w0iOFa6X2B_^5SLx^zt06 z3u6)JKoF{M35m&^XS_TMD|WtvQmz8A8FAk_u3;e0Z=Au_B24|bV*C_u5ArcwpW%tE zWE59X9ohwgdiJ92Wjol9O$F*Zi?b;2r!i|^ct4KMKOa|Y7uodt5z*GoJ9Gl!srl*m z$ERirD#Ov2kA3)A4xH0dSqvT#U~y$*v3M&t0~V;v2+fC@?GNH}bf|=ci@nvcNfO9f z^6K#s=Pk_yIIC`ExMv6hcSr})vvaglr|$}&A0Bpt=xQEF&afwc;cqOF+;;_d=ISffb7hTIG|if%@z zA2V%`m!Cc-L8J%vD!>CwW&%9GW&$5G-Lpi1 zS524+%BQCiFKTOeg&K#HhxlldG*>pJ11t$kU+!b49#>bxS{iTYd!zWFok%8;MjF0d zDfPqm@H!qe-o5};HA+7ZUS$u*Dk2;iRH>EcGatM*hf!Tep7fq5tScoiQU*oKoT<6C z)oQUG_oYfHPFNDQo!+m~h~TAbxGuwtGMcs;d_RnYC<4RWk1&rbH7un4Jw*^_Y_ZKp zV8?K?k1)x@+)^V>0W+1jV~CGtkl|G<7A^>)h4GoZ@_1&xC^s0H%Fw{rq&Nq&^)gPC zapp80AC0F;qoLx!;>L(YcxpGz*GrwE{1Rndipsb6K)PA_h(o~b|NLL^F3)?(A!kXX zwCJ3;NmKB_F%y5dfd|4?#?zg!rCU>c7!Ee`(Z0>x%cdW#+ssF0Gar@B zj2SJM(SAYImK!L08vcr0VU8Dnl80=%-oOi(_08K1zE zIb1-X(j3MJ8V(}B`x?vy9&LF4K%vdyh}{KvbB>t+H*d@lUUv)7$9_YgevrpWd6RW2 zUqFf;KFlf$zDEh&1NsSiDi*umYY2EIgmr?~BG32<>N9w~5Pf=Z;SQAI)vykDuG=W%g5v^0`87Eo{dWu0U2!# zwF|YvX7Uf&6Y%b^sB?LAzbGdeHn>NuEqYI2YldxsO__pCxxgvdx_cdtDZ@=Y+&?_) zrM|ifc^ZD5uIs$#&)fwGZf)Pk8-*3xdL3_0|#x* znBlQ#ArH-qFJV36Ye_1>)GEBK!uuK!f0zVj@?Spv!O@KMv6Y6qt0Fvjm1c_f9tF#F zq`qP?%a+n|;}%pi-Wdv!b;6cTY`B=^L~lWXf&fq7m>JM2!>k2!1UwlemJ8CnV$>avk@nUaI z-2h#I!>pHUhEWA*#%#jV+d%x`*j}S`>y_4(PoG8vY}5FgLH$7busdQtOfzWLCl za$DlE5uQeAUtz3!xZVTq1KA1CCO9OsGw=ncL#m)cppT*y|w6gDo^pi>ga|Tqh zXF}Z>was^QF7YU=s)XHox58xj=5K z^^zxBZ@Iy}iq^ZUc=n+d^Dy)5}Uv&Ux$6)LB8y#F)ggV&)tzU5x`8CDMW^+ z8!?%uq+Jm32yA@WMRXs5dxtN(paqO?V;6U+{7P1I4MtJU>F~B&(6?f^YLp)d$J22T z#pbM>T$;HTA0yWze~AV*=WpPtt466xc0Q$$Ojgn09kRH-?VYy%6LiLqFUb48a8#8d zbQ`%Oqh>Q^V`j5v<7RUN*h>Izxp*oXjKR^Yc^;q@Zhzo6FyfZGwB7=N{a&LV{Of(+ z_tICt$koY_f2QEc5L3X$EGh%4jat#`l4CbY__Em8esm45!weJ(Lg{Ov@)ZijiD@B3 z+QbaZjXd_Q!ss9*eaOBvcoF`pd{DFZ2XI$n+LR9YA>3P`yC*32_*`dNtdnI1yn@$E zfTNCP1izcANsWFu3VBG09Az;DZ!kQ5+t)vh8ZDQCWiUkfmj60=ZeNh>A8Do zxfyOSiXqAKVFEtIE5MZQ%Ta+#@Z0EwpMZXv7rF0_!Jjg};swWBn}2?D*}A{PY0164iEIyeZQZ-sJB{tv z$FJb_{mH%F9KDtw3Rz?c?o^D;H`CJe-hmSY$Xnf^eTTE4Y_~VrrAMTi2z=JH8e{Ja z%xSZ&$bL$Zbz^xKm>6a&Drqi2zXWqLLn3HqNsCeA-GKBQaupk!M-<|Sy^;-LSuTdv zGr8pxb~EG_CUXuqYcDhMl87P47=S6b^Hj4P7gH1%2VvPS)Nygks-O}tCLZ;1dm#%6|G z31)9ZQM$8oh)}4gX)zk?~C1Pzx>#3w6x`AG6z@);fOOxRnR?Wfl^Mg*HfZ`F%W<8amZ`j3%P&vMyKlz&5+?INWPGu5!Qw2{ENht&z>%snWpX>$I$? zDjZJbmdRhzWo2#Z8Td)rD8Gjygazi`PerXtxyg!^E0)3Trw^mItx0ImwtT*Bi6G54 z%mjDh)$_>+aspMlpD)uDO;er%ozMif$+nVBKS9>+Kb=8PBIK+Z|eH?zTnt^kMgZ%hx2fh3_d(?eOR)R&)TO9ju}DR7 ztal>r{#)i6P@re*y{A#7&a}dHl8=|MUeaY{7ybyjd|Zmd=GXF#S>7wdAyr%ih&~_gN5IP}g6G+`s<{l<*0GPNc%uZI zKoXR;bzCeL*tSksBEXv(%>=To8%VOUts`_rKa;0GC)m~*vaO^Ah_==4T{+;B3<{Jl z2)grvVonemczzcnuE4f+$PxjzBFqfA60~hSWO%&sSWu{_n9V8D?uwvTmYaq6CZl~y zm=tjo^ zIx2a=QubSL-L`6;6nlPYLO;T`9J+*BAl)tDW~q8O<*1ds3sw>_4VbSZ#_}z=W3dUT znU+rSJIJ2ImvmV*N3J9u=|O9ySER&iO;FxEMvtCw>ANrG?WUgas2S_$BmKI+@?P`e z_rlC#=?5>2DRu{n4HG#BJB4+TpHLcJhQfB@pf9}5OiC+u2IY6K$H%~~Xk|P~nBb}8hogq$V;*E2hlitb+=LS8ZT@tWq zaYEFUJo-VvWITnA)($ei|MdZ z<1$-pDm)t{dA~KbLVRMWOwChyM%&|=Q@S6~r~}W!!qwZqlO42mG!WQOlbuM$1eL85 z%f|`P)>$$^fUPqV$kr{PIDK0uPsyx21v=5zk-^phN?RxQt{henHh2=7;PQH!`2B36&1Elk#<)E#j@Nigsrnr31REZ1h91q(A^Le z>vFRYZOT3+gsn3Zz}6{1cTZ64$}Nb&*4gbq!mgpTb&?%p>m)B&%6>zs*LA!n{4F#R zn3XQE?-4Cf)65Xu5W|RdC(xUqu$URwn`5uWvT~Kyv)Rh)#^bIpR9E1{ZNMa5ysR;H zU}m*|Xx!GZy2)0-DOmC%N4at&n+l2SS8^oFYc$#UGCL^DC=$jRm8Z^nYs945L^7dr zC1Tb6QA@&H;C4M@i2z?PG83p>&mpO*U60chFSC-T^6iQ9WZ3Zo#CC1>t{kxcsX%#0 z&|MM~i-Iun353OGbqH?Pqm~G;$Yy59nU|K^^{^behC)Tf%SxTZx|3KVV833_l@M=U zwA-eHg#c%`B<$|kZCh>@W)@$$zf~+rL}G+P?!Ea2m^ND9zt_#j9YP@7S)s`TWv1$33)IqGX!^ zaco17FRFRx9mQtAkx?@N9t%plD-;~}PQQFjF3BnYS;Pos37!(B1MdC>U8RQCF^h#w z0>0%Sz*cm=W4W@1f{5W1X}+tgWKSL=U|R>L^0>f`JI3!*n*Fu(ccaI$NVmPOUQJz< zveNx%Io&_@7U=_+jUkqW!K#i+PF{~?&&R2JY{a8Z%a+0bs5pvfFT{cXZ^pzN!x_4S z3?CUis~ubfIpU-+S;EbhxAJWdsMq90%wJaUrcTaCYz`sgh8c1`3sx&Vrj5)CvQ&oX zw{rNdr0~%yi{p3vT|sS9)Vkvfn-~8slJ|5EMz+zr1+%*2GV8eiDO{^M`Z#iq7hy$9 zMXNn|>5cbJZhq`(lS>~yHTn1>r7K1ghK&K0B^=IX9duh1vnpf<VJ4s}o)_9I8xt z!{Nm6>0i4|Fjv?JAq0Kx9R;#5OiF1%Zg|g-6qL+57K0X1UjPe(6V5ohqEYgsK7r`> zP(>I8HNK_YUBzU;B82kO-FNh2*#cGqF@hI|5*oZ;*Z9wf32l{t{WUYeoi9Rr8$)rL z?Vmv{Fjd8zjLMUo;A4brD;Wm3{RF$`a=;oK-Lc#z$j1fUF+s9Tz+wP_&)Q=N!4d(M zDa;Ikd1+->Z%*@lIYe+#)9#*x28_pKX+AIL&dJR}98$GU32~C%On_Ov0^}V*cS&v* zW)|XI{B~QFTUUVF2nq9byRFHs5W_8jgvq9aU4rdGrP7vMtWr)(+E-?ID6L%nS6!mL zI=rEvI{HSWZ!voH+f?;Q-yZO?cNgBCAg?wQ=v=gu>~qLW z@1McmRJLo8#wYd&G=3}6Fx0xUg|{bszkCds(P89B+31^5ifBfYuXBWRk1;o4M3Ae) zrTK(9vLVe6l~@EE5zdF{IqeP+vRr`anuPhNgk~@(5{3!8joVEOCQCw`T5{r;QrTI6 zDU9+ElogmoyBXLw!mNVlGj1SV4=;W4I!}@$T84Sh)<}9l zzjGW0%si4&Wvv(i^t%VjAvrdK%hx``>rmMY(Cp|Rz9@pruvid$j*|mMaNKx%WODEa z>~e#XgCT_MPYxPj0~x0PS*oT1YPjx1gOh_sS)6{Wrbv^67qIv7MJ&f<(J^vwEJm9| z?CdN<*;V3|HUhI9LMm{CBFl^~Xza}iF8z7u$)+#7fqHbjJ4f&1rdY@;}; z*!j_KIuZp|*%<{^=l>)|A&(FoKv0#1A7}_+M`g+B(t7aW17-)G^HmlM7*@;#_;Qp3 z2r$t#6Quc`nE>I;1c=j203%^$z*&_r#O$Pb!=H^1!z4n&Fk-i1yNThLi-n^S<^p}K zT?N9Y{gmn8OouUfP9_Ok$pioeh2ky2jKuCUg3uyhHb>Z^NBSJMmtDq-l_`N!fw_4c zLd>`bh*J>6Fu9UjXxU9nn=%rV-MEu~92J0_4E$D0o$WZkU{Y8OcLV~=oXrf_+TDbW zOZHfa*wr9l(qIi3p20&Y%5&F^gU_Pu%45C*a0|h+1&)F)@!$p;F9W)*{U&sAq^rQ4 zG0NSZ_+9rH>WJSmmHs^dRwS7#JoBKuB%4)UVk^cfY9+$GwS8@rBKHLz8+X#ItDatK zeO!O(y#~%46_sQ%Zyg#m)x>EE$qeaG?@M$hnD!M-V4@XnfBdHh?n;kK8@)L|=g2CfKLSu-2H_q6VqXeR&R>zKNa5AxvDP36HU-&_}KEKa7^A6dacB45W1 zBD9A!&L;lt*H>}z^iv2e=^`w#+*rJ_O2qJ3riIIbykj@BWJN3&r1`3u06Xhu0zBhH zwsq2zQHoVhMySBZDCt5%tTci} zH6$zWUfRAnX)I8LFxv`~3oyGT!=1f^W|VK++CX%z(!gFpo!|@)_kA z!LU6EaPq}WfQQJ;2=!-_84HSRa5ZHykJV?WW|)#JKh7B1U{HiSOe)e`fB~Ki?OsAN zESpG}%-L<$Zen=C)WUfQbHfP+!(NjmhZmq(nhDS>%>*#uU|Y!p__e{{Ro`IHH4Bv0 z4F@X(8(OIDiLR!w8e))+<5*qS`&0^FRCVPKHZ zEHlF?VY_YGO)SgBV3Q?`aDv9{HQ9G~0p`7C0yJMU0VYjgTgjvNwZ?eW*BD*1Kv`X5 zKZH9P^=CdVyj)#E|qc)xw^*pC~>JKhA$doc*Z2e&T|zUw`$fWJ+-_rb6T|st3nwJZ4JTBVy(TjbFTh0COaK#ZCcr{3*j92k zeqC9->Q@$Bvp`wBvdC3gyr@@JqGDqe0CF_%e|f)jI8T-p62c~cmCbvirm-Pb1@0TH zu|VCIJ^J{q0I#7L1qHH7y)c6 z`EC4KW4!8XjILRrtgbO~X^a;G8dI@DQ0K;|1!uIg9Rgk-EeK+mD# zAfW->(M*6w`xz-#fYHQEfQD&CsEsYfxGEtT15{%R39>fn5CSYQn+dSVZf4LZJBmQ% zC>tXfu_pmW6Egvpu*?Kd7kbXPB?1gyW&(s|j+1q!yMs7 z1kTu|-1L%pXd;%Np%+!>f;1lz>k4pg+)R*mN6ZM#hhEbD$f-8EAEt4NJYNS?njnFb z-ZHo(|ZMk+-m5Ccp?N=1<#T zu4N{`O90FW^`5&RErfYM)m};1locWf(0R-RxLGzcsOQchP}y^11k3g$z~X?J06o)8 z;GVl?i2#PeOn@gTm?Nmb8QYOtVhCjk`1%6u*Xk_>`GQzifNfhd0iI>jHL%A;d8U2FAe#dI4h`U=1@y1*UMgQDktu z8jg|St#Aaa{0q!5rL^0G-3EZ{y zT`uPjCqn4!8-j$`MQUrsJG1LeWm)K2Mymj^lsH3KXU)QEr!XXz8ze~iRo^tp>l;AEaBSx_62n3i}nh7woG$Yh| z3S&We49*JLl#?1)zwsOGALHh?(ZG{A~Nreak%z(@UxDPcmsIBZF zP}x>u1Y`CjzzoPtfJwL+q25*)3(8{%${gp=S5dd6J~Hi9EZ4vp>e0p0{Kobu2(D|lcIES;1YsgLZJ`~t*KQO-3-ooPIJo^g@E@l;4Z7UX*!iI;_3 zD>K5Tglh~76!8FWo8R|(Txp;(Sd~ujRPSC~AF0pYAM#9mrH57P;p<;5J*-#{5~8Rz zJ*<|=*D4D8mC7#OFc!Ll1&1bDa*H2#jao`1#0tJj+ZEs#Dp*aI_-eU=c8|&v%ge>E zb!HakR(bZ<&Y%+uHwEz}aq$yr@fDDMVI!v5_POKW0`xwy_%gV#XC}Z1VMai&Z-3}i z`!l#HD^^$w*cAX&Mu-Hm5sa{?Cjsuf&HOqzztcRv+C0-Fw?h@{%L18VLj(s-Qh*W9 zOn?#3(Fr`s9kN7#wL3EbZf%((Y)Cp|<8td7O1(@-SiaPA(!m8Vpk@LXP%}bJ6Wt|8 zz6fYu_)*@p&j`H~B9Ttl%)aMy_sq>6SJKC#{>3mNx&NBTY zQG9OM={@7=P<8bb@q$i$m(M~^l%*=|6a^^NB@R}VR_5X~ ztGBcw_!MS1eI4f;@dB+*E8|P?&Uc)7+-#PSS=-ec2YDnVK5P8jh`ArxDkAGTvK2(O zipZ)rsm=^U_DJ#~6a&xqCM*szK1Rs80=$T5u#A;_|Cr~^?HhKia_l7EQzmvvm(?+$ zkHFkQg;n&pahar+unJR#fZ})!(JmkN#vPH_9vI%D%Um}v`h^N4jh$vB80&VabjOLcSsB(iyjI%#)&Xqrj0MJJVNPo>*QQ z+&Le}n)P8VF|6`gxF87gax28LL=0P`4j{lXiJ1UfH)276n^!Xd?kvm*H8WF;S(T%J z%HWbf+1m~wz{{Y`1Xxx!GsxhMP?OQ%#t2sJNr0PiGXZYK%>*{M8=OZI_e^*X`(yq$cF%xCP<)(QHKyk{EQlS&8v1#N!@X58GC`&#FLRd z4wg&}7IvpvHVi%A&pG$qC#m|!Hc3`Ut$Nyb@AJRU-#+K;vrig!V+0L*62N3)OaNsw zMo=Gi84HSR;A(>2z4B8HyNA0+hFvx>H*L4l^j)>&{+^Z}R1suY?;aU2_n%^XWkN^1 zr?jee-`9iq_)O{`z2P$6G3*gaF9p{v%~SC0>q3%*lU2TZQ8Tl?5Fr53*q8tgMvV!e z-v|o=cyHR60OoUJ1hp2?*#mcw#x6kBB8Y&t;Sd67ti}Ye<7Ui2ePf4gj(sCWuxw8P z=$ys`u;XS-Anv%CvPOWuV@v>DkvYPStTVPPx3+;wP-mbdYHU_gnGO{aZx(^)RzO6J z^;rJ5JTOU%=jD;CtEdjhvZA<|7K8y+Ayo1;s)v*+-)Ume3nXo0$>H@f_LLZZ)vQ1LYW_h~8 z0FAd0gPu8{hkTTv>fYi4R~Dz%hv#^222EHIdljC42d$TV1Q$5V+Lx#t1Mq^z%hbtM<9Sk zXiNZ`LB<5!fcH!hz)7Mp0W^2!sLTbhbD_lB1|625&cKdZ*Bu-1{yWf=(Jtxhq~U4* zSLj};5++U5*E>{JZ_V2Ivjt0KfXXM{%v@#zhxQeGV}Z_H1X6G@vDP2#lidtH4PJmm zkTy)?!cy(MIDE)gP`IJ|mCh0oSc9d_P+MtFnPE9_PVvsf3FU_7SdZ8@4c7C;vobJ! zdV+hh2TQ}T1zrLv`DPJ~$X?u2lr|n)y^r#=?^)3vYk=Y1^o~tZ)vcM9T{^@)kDj$x z+GMTAKl(-4n@{hbr8juavRs#ThptYFp|qeqmWBA7jHezN^XFDJjS3HD#G-zD@&=K6 zr`C^4L{fhm?IY%WdjD;rDtqE(?fphPJ7Bi*#j86!+Nc+1nMc;~ z4GM;ox> zwT!0h@T#PSkbe8g?B{If#Z!$hG)zs?&l+D2IJSTbhpr15r|$3n0o>4d#7CU*LY)00 zJWW8xS2xgMi`S#2K0JcAt!gnH9sg85BtIFGD-qrgd!_z5oyME=UY6`KysS>j{uP#FBd6(+yW=%-fe_oG(-8j*ljlTdWA9)^jHNXKkZNY*+C7GOHWAR zJ>opV;AwX-?NAPrc;jQIZ2IyHQ-sh6pe({9BtF%DVG9{eTympc6F>37n# z45Xo^)0Z0%jrcoR?{*TS5ayn0{^6*L zVU5=P@wVUQHN?cM!d>$^|0n!r3|c-zvGm@7Tn>Xf}rZPeBRjX8r&^ZKn*@tEzB_6S>ilu&IYU zM8H-9e4t4RRBuS-^LoZ=!^HHOyh< z=SEOGmB%+X`MLj1?514S>@uY4H6~{+e=AC5PQs218H&1z)l#t(B{+nOGlnay_=Zr! zc|#Aj)ZBV6YNhww()^|MfY&yTC%*1^qP$?4rsl6VU#0k}`LkbMs^-IIp5AEY^%CSF zz6Ux4!Ei4b+{gT%1PQf_C@fuNlS=PZADPeZke@r}ceJ-&p`4c9=SITuU9Tv-&}xVB z^IQlDc%mc8PhV{KD*u!bV;!fDSeU-@j%M+-h9v4x6#SfCi=vGAK?vCZlY38oC-O@P zG8RLbKDIu7ru7N_J;VG}<@o~JkNK(eAga>Dzh6y31AxrgWrINRb|a^_aDe;0$lc`L zP|lIq)f3;4&@sIVPbxCC0WMmu|g1X#`k zU{VqWxs@2!0(K(?pA+^XfE@{A0>1f(;L{*lw4I?dYb0N+vI@K5pkg8ufP!kzr9)rX z7$GTbAMgLbUuEa%G!RH-z2~-`7A_tJ(f3a~vyM;zdpE`eiq5>SAb_KNV*)s{Fh+2` z*g{d8Bp{5^|Dai0#>@X`UX=`v(dZLQCmc1AbCf&k73jS1i!#F&A$)@KkXZLP-$2JA@yyYa>Zu+%gr;H~u$ zQv{$FV@v?ojhQ2C4mo2Ja%&qTzGNcmY9iP=?cf3kZcG5$BE|@6oM=xw@(iHj1QB*4 zhY-Lin=t{LP8&1Ox#;0%YgL=LfU5VmrXbOp+Coj;aZJ zm dZaU7C*c@XMQ@eBjsjTo;-F21eg!}&S*K_ICSy8GT5uwxt%;7Nb^hLT)OV1LA zc}=|tkKphA*JCwwloppXY~)9r`ZKiuM^N^FV#+WYB05>aos1Yd*$P6}IvFws0_dy# zbXe99y?vCPo1XgEItz)@D3w)rFtH;dEZ)|{g&^~xYcIl%-rd}`n*dZdgvCpj@nWR` zl)>yqP-|J8GpaS6zE>@4)U**_WD`K6Hzv@3vC+z4BdnUFo8SlzoTLCcxiJBphB-O` z_pyd40_cOr1k(9^&NRTYF}bx3e9;wClZXbGlN?+C`zgi*`fhdBIME(<CCnH>y^jqrY3L>hxSF7wp!`&QjLH^$ zeT+>kHtAZsoH(JI#4f=ACwGZj_#N!gVbz|1D-V)tdsgVj=}iD;4bsPZOE{)_w*$$P2KBEPB(1O~v_ z&uCWu@GNI!R8);SxRKAA!)$zne)^Tp->PyOEz884xSbVcx<$A8wfvowg$#vdw0H{` zB#g^z4j_Q(n-~NU5#y~^V;Dv3*0q~3+U$wpuClm5wyOoCKT|h$)!- z5ekf)Bf#d9fG@y8&X8MJ5V0c++ixP~O%V;yg^dZ|0A5%SIC=$j&29v>k+`#^y2+`R zR*l3~QuCciw08!C4ur!CV1_ehAh~aY^&aP>7{R(d3G`pNg}#T~1Ux5gnIZu7JYxa~ z%Ut5(kTZKAx3)pz%b|$6=E?TSTTifpLjaM}m;lbiBL?kn=qVom^d$D%is=u&0h1S!waBU8WTXXG$w$? z4Qw<2N&IzR<5#b*(X|Yu)%zN8^)-I<`dTVBL_5&4aQg3jjYg+cVlY%D&?c5fr{x;Q z#?3L3z_E3CZV5mtBnGqNA{v9)aS?s{6P6e4wk==`_mGVl+<{{Xh!Nc31**G%ha{r; zYS-GpSNJybPr%6><5!Pkd%(&-TAgFW#W8-=I0juI6?+1+=lmQ+?IXifXH~;g`zSp( zJ&0@QM9iTtaiWz9ix(j=w9Cy`0C7a;vWz&u8Dj$ISjGe(RtgIO=vc-C(4dVG)H-da zsmhbuaMfuSO&q1h8~7W}x9~fej-LUonDddlJCf(U<_lN@D^ZzUE93!02vF z08%V-gb~~sTb5hfARi%yJ|dz)@l3D%);Q6gcjR*b6(@)w?rmpA0IF@q1p4ne`j*8k z0;S7)jumj+QW} zGiW?x!)df4iYIQyu)va+)|}mh;qtnP3nI1*?!Ym7&6gcs0L|B!0Gh8c0jvjrZRS_; z*Bs+lk7IN#18H@R5f{h!(Z?|rJ5(!sIHSC41i1Pp@SotCSEfpk`P!=UO_(;KwbL`b z<~7a=j>Axl;J}3v_~Nd4F>-K80F7K3`%+)?8gr@7094n!h@g_JDPb)$jEUxpK>szb zQFu=SPz*X_&jJ{HjaePzPS?B`3kqw{Q3YY|TREu)pb@6TMj1z;4jY&I8s0gseRJ+h zbWVWjn?hImuH=18s*M_*>!;*rr}~DuB>2h7pT=rhpZlLVKo{V7^A;T92HT$?Z%1kW z_5|8*ok07=6KFqi0_{yF(9WJf`zp{-G_vSOmQv~dTsgM?5YkVx#esMiniN%vsb!o) zHF1u&jANoEzE`1JvI2Y+_zJ$)0P#C{Je!1%$u4|%0dXRRA9P3nw*facC*Ol)2bM0s z2P4)?$$@fmqnvtAZLFGl4+pBL_w4^Zo29o|6GwU*H+o6+PL8lBjEJr8j5Fa9r%bd) z&NmX!gqK;$E06FriszCYwhtwsKPR0}^5D@~DAUJD=g~OUdpdoAsQ-v)a8`KeI29EY zvQN;rJH){(4$K&AGGzg3$qsUY|EEje?mUE(X2$3I)>Nf|=!nyKKGdVBgOc)0{b?M2 z#nYGSVbS9U#y*}wTw`_pzGLU>N%iFp<-c-kmGyY8{OjC{=K&JA0`M9L*K5poS%X-d zdVgdXzTksutN2+@6l}SOI>HYss+*G^MXu4nKiX^HyXDkJ>ka&)YTzG@4g9iCV$m=) zpkH}}#ny4`GRK9iwklG+|~Q-kf{_e8mYHI%Z@X>bOkr z3!>YKZSXdD8^59aYmuXU?qw=Nd&SfzD&PAy_>{u3n_Fug3Kxp|r2gbYV;8zV>aX;b zWs*w7&#rx6xwVcnkKcr@77EvCO2$nVf`pZwMM=D`V=DseoPq;lF~6LzqKwFGc|h!~E3MKp$I z!bFrJ7uV}{>k1gdLfx3b$&)}bNwe$l0$6t&6NsvkuKr33j`5?;u?{y;>J2Z~u1W}E2gX?VM=i|~;Ht4e5Ej)Lu|yZK zvtTd6usLN+z$+I7Z<2^jEx82&TyZoefR<{EptdOQG*v6w@~YMai>7Tmga8)h#ssh^ zH)i1N2?R>(f*8S?Jqcj@%9sH5ON|MDe`pw8Qv|SRHzoj4oH@drS1hCg?%s{)Vvj~({2Qh+S zdlJC;r7?j%yBdAl6HFT069TR#Y)>dZ)#_k|DY30)A|EsB3fKf(23!W~77zvOi{r2R6u)|XimtFP zj=%LjMO=M~AN4-vLQ;GVr_X&khakwtM@=l$crt6AOp7N|Ymg|`5M%>V#X7PFSp|?H zHLns;thOYw4T#^#<&_W)Sw)K62Y?V2Aks;-c=B1xDzWm~&ZxG7dqak2M%3*Q#qM^haRTj4C(zEGK>I4tGT6QW%a0`QIRO@R z;FP|%QW+4017f0cnn~XSweQ2^|F*@2ekJoBM<`oxJVa0|NGQH&K|=m0+P?dJ$K>ZD z^3&3-`JOUaK|MeGipyw6`0*YROdwc;nrWB#^Wvkl+D;6g{)T zi1*)sc$2tRiyYSlU;F*fKQ#am?6K4sc3GoI-MDNE4VHhTKe&=2N5QJ^X8v;^D}`(N zWWgqpTagsCzG68ywGT~kcYpOcDlr8swdNguNIY_=n!9s|sW>H-+Fv=IoEB?N{KmMr zMG$&dIXG0-eDS+l4`N+bbK*CAcjY5)Zrem*Xs^kwJ;n;gxt&0?J{N7kwM4{SdV9(2 zWkR4mPEgw1+>s62xKkUqXj`-LX@L7i#O`DlK-3*cK?-8^#2RcGnm|-Nxyhc-NW*7`pmSw(HRT zWqHj>1Izb>#fxgbKUc?kKZA63l%o@@jy3W6Vs5_rZZeJxkb`PzRkzUjX0Rx+OBEc&7Rc&i zM?RyxM-Uuq+-N=N-W#r>7q7+%Vgq{&7^xPHc(`Mc%YpRiV_eAs-3wghgTQ9zG(RXBLsVg_~Joous2B6hmUI-P+yLV z#9hvNzP?j-IBY5K#jN=E@%{`lmA2f{Dm-wr1LJu$zE&4``{v6lw-(t13v74%1{$v~ z+!mPoboH9DFBSMT4{lkOn%WVwN≪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 xEWcT5;1pT?NUj zx^Z<17EC~fWtL6jAi>HDOzaJytfLd-G|~pyLX8-6%(F9kg?aXe02LI-StBS=482Z< zjT~HP6m(LDkl83eEu6rx^$)t}lsFwtnJG#ZY97ke9S&2nzu7$Q(|nefJm~hi&x)f! z%2XVxs8^OH+&YH)K=T}znjq6hARi=IDTZv{hrgwSO1AIoJUiVj(+5K0mQQ9xUbj#q z5N;9DNF*6;oV2Eq9#%*;Ss;XIkj#-T)Kno|_5doROBP;`3S#K`$8L3@QBW__Ws-;~ zS>@!o7)ZLP3Nx@-x^z;ql5|oldmbbO~_hD$mJQJftcR^v!OI+d}}KC`aHpx<1%q%&K@CZ=rY zRK^B+`2E9PFF<#`V!Z(4rErjPc>MsmD!?HQTq7F}12MSU zUC@!0MLy)|n`Gigo_;QwvdRND|8Y)G=&l zs+<>&abI)CK8Uvs#9x3(_qCLC_x(Nm~r z-u~lm-(zS{k-HDChok;PZ~2+7w;Y8E^p9%2B@cm-1@7RL6B{YmXf>iM=1 z9vQw2C~MDXolkqV18rAupx(7-yf*%TjGL;oXX8i@jEm4ra6-N0eA}f331}T@d^^@# zINIU*0{&NXg`@?TlJvzBk{;rfGHf4TjM zl;%Gqw|t>s2h1H18QqzZC7B%H9NaMy#n~^nF2aNEVb_1emSE5_;k3IWQFNixp-V9P zKvP)Bsidh#&O@&AF6A&RXtwH74r!LMlLI~#SH`>t(UyE5hdujCpm5PfaN(kjK;fbd zAuu*UN|XTMOJJ-ymB4i+aEP2Pl&DW8X^ibMC@0V!F-G@HE2KtRD0RJOKM_;IGcXWdfb|}6 zk@X%r+Ig3hSe-|tN_reqH}E(D!#s)$E?Xl4H{^+N?<5Tzq7g$5(ZJ?k@5$lv#^Uv! z-Jr(2**+TBsm>c7u?!=QGUH`aiNMhjQ+b>Q!(7~~7!&*HI65&y2xkq%5J#QLqq|Mz zNk9mJB$%NULod=DQz*PvHwjfhh#?x7`W8s14#&hOn_}g954C6OJ-a0eT<^&;s8R^HdV&+z9RtCf1tce0bf%P!Zd#n; zh90q8fy`~+(pjggE-AQ8VihI>cvAfHcuK`IE2Y;}pRBCv*iHBFBxhW(>XQSb{a`c$ z{64tv$CA#B=$(r{a`q()CoT?QrAJnou<|3f;cujrn8l`nS!{vkyz&P%)~-&V3-i}^ z0xBp_$3=RmDd>Uo*BnhEKeO$yWgEJv0_o`HuPIEi0%ZjU9yzaO9uLBb z97z{2u7_ttCPG7dv&vK)swh?ttRVBo#eeZ)4%mpJ9>hVWk15GU4XFtR{INR$N^!)c zs(>`p)7>(CR7l(%P5~jiUSD?;BNBDuI2@NWm`KpTI7ZpO_JYZR-XSy_jsE{sW zfC}j{4KG+%5JT5{^6EkZEQ>OBj);n{38)Hp183>7k|Kp3k+Jo&(o@3;(AauUlQ4Hn zB}qcznKnYov{T&u8Q$GozeDKs)ZJXGI7pWfCCQ2*7YiX>GKBOa0nqOxAWav#Te=Jh ziCa4t9CevW@6<;mmr6=Z#Ax7?-U8W@kX_8Mlhl-Gz#ivx^TG* z>P^DnydRKs(R87srAr+}DoK|*^%xX9(go>47iKjvCZggfkTMmAYV}wQK9KL5lubg} z#FdRW>SqK{Uc?bqv68gFfInSiQ%7BE)AB-hD=!QIg02*QvUdgLrCuBr!tK|JJBVfr zxPY-w4=W_!@qsQ5vNurqF#>9;kS^~iNtYpbLAr>c-y=w=3yp$$nJ!5paNZP#eUE@H zR^`BE=`u?JOVTA!j|UO3d87-XOBXPX5<+yvQ6Ob14%O0SDH+?EWVk}PQrRTLn23`E ziY0{2Z(>Yrq$ARk5Kf_rA�ZbhmV22oN+zBfMNcB9401KDH_-Zbs8(zr|jl;aNry z?I?`7)<>zTlJ(JX5GK-l*oWf!=-mWy)SCttreSd60Ntt#U2sxQ@VoXYqz2bV_knK# zomaFPZhbT_B*Q{^!7!n4eUxstKKhTaJ~_;HxIX$ha4TIO6&|`iN~^7pk`C*mG~D_q z`~05u(ImQjx<1N&0s59J#c&_o7RT^55$PV-O(R?-wHu!8f)d%f8?^!Ay!!v9|VSJyYtF6@Pz+*^V#2Ce0B<_$8h^OI42a{=C zs>P*)VF*~)7M8s61Vd9@LoFHEsw8?G*Rf$Y2C>RjvdMrAR&@C-q|&{Kj?L~OY1l;q z8#*aElx8>Ba77nGqtdHS9h+Ln+y50;czl2KHMj%BuDdxKHc8I(y-`LyEeqtRH>rkoX}bqr zas@8gW?pDG^mV6k3a3(~_b|TBPQT91RB?8O;Oq=k?K+IX^e?lxE$`L?JDNTL?*ltg zpgF>qPPVz-Hiz4FbGThFhucfwmPqfR>}717XwM)jdnmZ@!DVlEAf#Zv_bFj=f%8h9 z^MOU@y;Ms7d9Ye|Rw6Q?QnCJvd>zhjaQ(M<{^Rg^Fw^pnXFZq`1plM+=S_+Kr_Ph@ z)A@cjby@&*zCVP2r~B5YnTmz`)_J2&&HL6dEXUb-IZ<9d-(PXx`lm7955dFv{@n?8 zTe_R)RblOYFGWit?E5~85tFS~D@t~g4e@v}8+y>Fdy6a~6-6ngXvoJ#In zcdmA2(9=F|{q)aM#47Xi|LFO&__Mk`zx|1d^7==1-mO;q8OMBl9g5TF!)R!=3s%H9ZuVaTlls=8sGGY`#!v^!{M43Q~(%|B*f6m0>|tILG69JTLP^U zZa4QTrF<}W(CyTRumMXB53rA=S(9L*{;TNBvyTCnIF0Rl(Q4zmzp2J?rH5TMLh|4g zFQ(uzcg)H2eu|$Z_Wt;}iS)5wYZ#er_IFDBuezFLvG{^_365MPe@XhOy8Kw``n zgTbg^P`8pU?wVxXMrz;8h6Zs3b_u#uAvN5XPdn|>_r;!ALgAQ?zHQ9+6x^R2<~tno z@$g*fm`_5}F(0iq<|7@(d^FscPd7D|-5253^Qda79Qs@jz2cq%`Iz_BNDlsa83|GW ze}1Q0KSqQU;TjWH{r(Zv><;)IH%&56zzr)C>qpdy<09^##Ts$AQ`6S3;I;#u@)UHd zN7rPTEj;Ij`dv4~&g=W?&tm^o1%KN8-xyRvZO%d;RM!mFB=)V7OD}aj7aFB1XVxA5 zK28jYi=2j1ETwrp5Bk{d=QKYw7Bu_=r`r$rFFE@f@Wuodx$~zN4RiKR2MLl*i_UkV zn&vQ_`OgMfZq?fzK-~UlG`wF4-)AEywn)041Qo#H`w9-0Xl``2+#gZt8m&H|tlYRdza``3ZvT!6;4WdAzj zE6Q|p0d{ms=K`1j(lK?e_U*NK_D9(sYkzQl`}tx;xykNFIz0Y=T(b!~i*j>(4jml* zqySE)m*FrD9n_-+Q;qJP9_J64cF=V&x6Mp7&eE?jD3oFBNj-nIqK`Gt^lm|VBbYhw z)kZ&;<9kk6;V;twMHfr3S`O#MCowNx#~h0oS8PUnIi!|R(ms5f4g^IW?aHFUBm~bD zZr>ZhNW=zgwkgm)Pggn~Jp6q+4)d|T%t1dEq&d>(qB9IEU5lgcAjTqjf{us3PbZTj zEzkugN1!scOi&qJF!CM2ZoCvmbtAHm0M^P)1WD|sIQ)G&Oc4xcb(cvUCVa3Hll#tE zV8z_iMi{mqkSWe*`v|ju4Ux=K(fz1kdDR{Eys+*A4i~b1?{yh3yyg(2N&*CO;(5j#nI} zJRbo-vab^JC|)7iw*bW8cm?W=qmIIh2NdjCuG;2z7Cwh|{^`Q!M_{2WWVryG@EqFt2sF?@ z>pS@3LkmeyC&`ELKO6t^3H%!WmOd%ED`0tr zIO7Fg&t3SJoB#~YWoP@Z(4Cc86x)42L5(LKKks@ACAOoPqi4(ja2)+o%m%t1T;`Kb3 zcQCN?@glb!1pW)Jdm)aHrEwCA+>Tg`*H-{AoO%L};iw;L;^hQp0gx)A+Xe!6 zM-}K4GQ1dsus-Hw)y@v;dp!WMzMnwLDN2?ja2o(B@i79^rz*Go1YU+0!}&FV<4~i9 ztXBYNLli!B1~5mifaK7D1p3Xhg5WYZzLwSI+wuAZOu;;Gjw)o~d2sphxhl?y1b+5P z%|NFUxDBswIX^wG0uumhSK{?c02Fi+f%o9`T8H@|0AgNwK3;!;*G`AIVpR}axxgg& zC+vrN9x#2c!Rwe;s|0T&@bZf^a_=K>J^*t28v?I0;9&yqkE=qyNnn42GWaop_pVk3 zza{WQqmsRV0yeBw;LixO0?nebiojL?ABGa;iJ;Zm3qgM+t=ARGdgMyrUX2%1;XVTY zjn{u5I}6zlULORvif_D%+MriBH(EZ6{~#TX0(XY}7chf$criN&=5+wB10YWkpR=7C zE!4;jbU@^^i6!tZ{Mqh)k-#VMKZh1RVcG6b80|PLb@=zuVJrThqJ>XLd$GbnKnp`0 zpu?5;znK<3A?+r{gAXl>VctiFH{;b#S3V)_TZrOA%VLVYXwQF`B?u|PV*LhdPg-vxkKu zF1Qb`FpetGp`8x}3(sX9V?j6pjthPQ3z;n?@LO13hrc{mz=b0A6N5jVFm0Jt<)MW{ zQrZ|AY6E=^?VNt@!cR#%BbQ6kl@=yZft3koCKxsbFM?vk&(BcH!*8(UB_Lag7sGiE%p*78 z#j5M$1UBLI4gm7}1OOHBhL^(6@pu7N23r9@tYG~Huzyulxe4nV78ga~no|_`6wD(B z`GTJ*nz!N=hEumh!+9gjg)8u4IHH3TfH0h40B;3Go}*4x%(s>TJQl!eLj+eC=DyS5 zauZ%`-1fp;@M*k4ZVv&t$@w`OT#^99&xZi4ahM+h2>tv7K#Qh3ng~>Z#9Y^a*P`WG zN%s-;KgXdm45hq|ieK}sg0zd{|CXm33 zQTQH#_u<8G*enGXp#|Ci1|rV8nh-hwc@ChUu?czeRRFKTi{BfTXF-55+_ocN=bw+% zam!$d;r~Yb%gZAYL?2pM*GhS0g^2{wCoH>LoeM3jx>*kd&wmbjNDdRyUh7Q61rOrILMxA&pQpj`6{W8oFM~=5hz7b|vuF-dS z3EU_xb8P6Y{+Gas3jGaH%+(|Kb453#d)Ms<=Wdbk8^Fm!3pM)ZOj7n@sl*-vET8wn zl;?v0NR}h!?+tj2z}Em!*ZT>47r<+2;jOdY?81-}Mun4bKb*6m9ZQ&Z{ObaD@6YHqE@Ml!V349fQ2Kx~L zM}QThyqmzWP#A^(3V|!(%zABxz#Uevv9Sm)!iy@ctHbMN0NseEJf*VSV6IY5D9c~c z_ut}ASw2DFKk*k85ID_b`6mKrnk)wh44N#zA@D(y<#+~v9b72OsRZr;P*lg74l}^W zlfk=&zUS438{aSmXrbEf#$U$9TWH~PXy+cKEIxrvjBvc+YQ5-J{T{?%ot`lF+VNFT#Ot=ZYL0PIe?Jc z`wjDJWV7HC#hBk1=5bY0!h5+B6rCj^Knn4~~7Q=QH%!MbR@G+*{FU0GsNOuPMC+Hgn{|w+F zLVW6hrv0x;w#hw~$36+`68t|yNzcXWBxvs+3A95S7lNT+{vo21 z5)H=#^La?q4pW{`FL8R}Lkojth6%30>(_O3#uL*1iKC^3v~6@?MgFYE=PKP3O-@p_rVxUm3dp*hLX3vLUJ z#_R2lLd{Uu^U5=YKIJ8dhdi{fQn-~O+=>WM>~|1Y0UI;lhX`zk*o@tS1pXEgA=#G* zbm7G~{0o6Wy#5uYJUY(%I(TM4{W;?M@Tzk`hqA9V57NOUubb(>!F5rZ6~^Oj#PXqq z#e5HeK?A-_;H~(FrPJy1%Ltgdd^IFm@FM^pAt@iHOV%CV0dN-FUP3WB?{O_D_=NKQ zZ-s+^7Uq*D=`e%;YiZ%5ac6FMKKQXc3QonVTabdc@-(k>Gg`K?^3cMhxt$J|;gu6- zyh0hDPWgX2w4E*_T9{Vvp+i}J!{oh;C_c21mW3?13jg0v{rCceGzzUhfI;c+@l<9{Ol zzasEG{2OWEqvdcZY#+w|b~>?wIDr;EieCd8h2Bi3*W)iz2hChK15_kvxVTmr%i)1+@KOwgI>wrabJ--4=vQP zzbMjSg?Xzo?GQ2MeV%y`MNfv@nKWW|aTU z0Cq5gBF;fKHxtUg#x z5~wHeVFQ*E_^|Bb%h;9fd#V(dIx z`27B4`rYb$@YhQmywCR?C&fcErO&xvxUA;Xkf>JoTwX1(?>p)xQs_53<_QCFs=*_MD(YD^L z$*%5>_UM-0KIbzktd~b`?gS<}(Ag2~>KW+hOZIi(9qsCm_6_#*boJa&O1~oL=xOWj z+T7fh91PadLr-gWw4<*NjJtX)%vGy2?4`?szTU3p*6ywwdYTdR&8>ay!If96jW*DQ z!q6qUbam6!#gv;{+iol)bxuaxdczHU9XGTNbc9?RoJ$$k0;OXS6iA(uaqVwwg^rrL z&kng>B`)jEE@N6CbR2?^(zzJV%}k4)-oC9N&r9fXIb6z^7APHypg`)JoTP(W``riw5U=r!y&x{pZzWyr1Zs_Rg@9n#! z6`Akmu7S>I&tP|Va-c8T(>oBQkKVq4j`pB&DA~~lOLRcUPwS7i-_p~%wW}>Ua7z*y ztT(#3BYI71Usvnq?vCZ+Ss4eX_OCj1a8(pWw4uMhqi+DgM_W$qZ;5VcWv*QnRYGLa z)&a}UJ;?tOSZ~NvFqEmaZD6pqyZe@CYlsWZZ0%`n>+NZ8J?Bj1-ZPU@h|XDY-kJSf z10821k>gr#=;%LlptrBBQz*KS%?H~%%%!crukFm%!GYcz`dZu1yvbs*qBC$23cC86 zopW<fQVo$g9zlgOfE!w?+%7Em9ZuBlsv~fH)>fTQb?~$X2Z?hL$c{ z6!39nzNlz#m^?196zquF1ikShWr4<>Vs|P?&TZa+#Gc`&| z+XYtTsynD`b~hIZxE1*)x}~potCKP) zH*; z%_WHo$ceJLb?}BxMSsYfM&6?MnlYK~)zJXgb)0H_kGj}l$YNy2{ zb`{g&>VDWDvC=~Q*L2GUFlxRM$$QJvWko`49_)g$d-@AvF+}ByMxhl+(Tu|1Dz*Ha zf`nL;W=>!85yeW4ls&aKZ$>j-O9jPIvo@G9_Oa2ZmGl4In_O2$&TNO4y1LI4MWL-- zY;q-9`!7Y|LOLv^W|t!=nm06Ex~}1}tC|xH*EBX?wt86*7Gamb=u^1v1Y6Az&*IV3 z8@7gLpks+91BIkLN?l2bpR=&s+|hbtp*$@{+jqHCXDCF2ZRkj#g_IsZQPwaWwReb6 zO#2OeUFFSF|#&`9qD`=qk2o2L>8wUbND;Nb=pFf=2X$XrO%w*l*kR>f09gZ7XWGTnq1nK$$_=#R#^wdr-Ont*$|1(2GT`LiZ5E z*Q1|TltwP^yK(tzdZgNN8s%VS$o@G1`7H%!kY}66=8@g^nmsaSkyERus z)ZT?=Ps{D%fL+RtzUJ<(Egdpe!pMheFYamt$W|DDqubSv40ZI;Ymi-T>6MWgiYrTc z%PBUth+gQ*=b8Nj-77jTYPmRiMPHFCle4>H2$D~)G)t5X;GL>-1ei3#sJJR zh*n=gwc3MUQKUl*a7sH2L#Pl69WK|WuuezVrHC#?|D?B_A$0Zk4|Xh@mmgO&nTM7I zjWQw8J}BK8mgz2(+gqWA-X2VhxXw^Xfsxu8gjA0Xb2$V!xhOti1>KDXBFqz|GdP#8 zbZL6ltE7X0*$?&rZ)t`$8iS>`wGOOWrNh~6%cErwauOZQKvmcp!pm6j3Vy>tn=ZZl z?AC$vlw;$Sue~bPeAU^_LqqLDjY3lc9nkk#G4;L{xlMhA>XQK~Y%%K@YXKQcI+I5@HBH7a0PTeLNyu7!kqYydR3i6%TiE_~0 zQ4n3|itt+VoE5W1yfxXi79u%s6sDA)(Vv~X#e593-cZK9IQ+9?VT0IQs>wL`^8a)) zPia;vh31v95FQ_2{u;u=Jm$$?4qaX3LVniNDZmQg$VJc^bJ->aVAt|DK`zwVL@tzVTLjHR^h;!RMohr=OnaL!icbRdc&& z;c2H|sdqWCQz#t;n{C&qz;lacwWl0k4ww6VrucGxzi+r){&JY|xOLt6$X4#Z94_Zk z3tvuO3-g(K)jmUk*DuXO`p3VMpi8 za(p@L=*rFOv0(sDgLB{bBJ4)O)1vO~3V@2NH*R>@c|J$h%qkl>!3}m5{_}=^iG)^( zpEdjk)n_Q*L6a|o&4PS<{&R-su01>z;m;VpQ}H4H`WI`!O-Ct$i}ZLZ{LA?(AkGqW zL4Rw^f8PB6sp2dA&l>*u9=vQJ0PZawe2QBP@c81NG5mmr9LDc@lP`L-`Y*?S#PFG8 z=Egta{F~tA{?C1h>W??_@F~Z?$?#Fjpyl{So&V$K_J6FLQw)NrC2YC>4TjIy4$gA? zea=6(N8>5SKkocN*BK~>9ukzp{ixpuTuPXdE8QzLn1wLu`l(K11 zBM2U{^qn>Qs}xb;KX$SrxZx2`1wLYm_2v)5+a|*>{O?%!p)?^n*Dd`lOre92S|{)S(wWEJw| z41bpg-*meoeELlq{&6J>^*Vf-Jr_@}sG8PDg`P%Wul-Ysue zz^C6-4SdG%H=>`hz-`(u;vadJ0=KDA5&pR0zpt23{)n6#!R|>rA;22~@Pzma4L|-K zWqi3B75rxmf3xC4|DEQ)<-LlqYNW#dxZ%C!w+4RJ@J$~1;yV;!&1^;ZErz#lTm?Q^ z1D~paAFqMW)WByAe|MRF?=k(x|FXJ%lZN-@zj4F+>Zh#XKUNn0{Vsjqr-+No@cRs( zF}yGQoZ(-t{wwr9WB3<(@WD<+yugEx8oo{O4QgPUxXCwecphWHQxSglKK0>~KX3Rc zkN?d3)xR(Q=WF2gJ{G-mYcBa>A5i|ssZk++-0(jAwba0;YVaSgfzQ;yXKUbdhWF`z zrUpL9sA7Enqc!kx!@pA5ROmNV!+*x`FZKA(8UAG+{7enJ9vRc)gJ4l1U-W~@c&&$j z((vzAe1jU;d4I|gVvT%AU3~R(-0(jAwiy053#I)E`JZe2TmF~wKMHs}A-=`%Nvn9y zSF#0a6y90GPaP?4q5nzqpZZ&+-L5`E{1HnmexFplH-3h<<6jlwXAHki*;L?X4e!%W z~whA*FAS!43&48M7UFbLz9GCsPwN6GN`!jIL!HyQq7 z5C8ER_`KnL<4?Kr2)x&L_&4!NBs@O(lZN;4&(!drtAU>}{N*0`VkfG6HhykUG8{Lz z{E;)fPyQLhd->N_kAG7QeA4i*R5qb}k!4!`vOJfL=l&Oq5#rYwzU5rS`}8|*cwhXo zhWC|^8N>U^cf7Hg!~5cwH@vU^J6pqlga?N4?5h(a4F4_*e>SdoU-*#*#eZD9SL!!s_-I-E zSbw_eXXY~X|0<9FC{GaMc}npOYT!_@`H!wu{Qb%<#NT1~{CdS}_N|m}*6`l=U9SFp z_?Y1@D3kAg3qNW2^7*+(o%~JJ`NwPETMYlN8on}fN`l$+I(2$yS^x0~3xD<+#UJDG z?`{%~dhpTLtAF48Qq1t*RR0ZX;06b|x(zpYZdCui`Xg?5o1YE+=gj}C;eGwD*tN>v zmwru#_m$6-;eGw%8N+`c`5W_Bp?pu6eEAj)-`9VcHM~#0NUQqi$$dNus9oMohQCVP zD)3pu`@+u~-mac+aIEXk&;k{0SC-}Y4Teu{QT(>D@;7eyS-CeuZ%qO3kbkg3J!EcF z|GxCk8{Rj5aW_%=(l6Gn{C)k`7Q>%fRzEE_`NsRye_D+Or=Ld+A01FcdHrm$7^=D*A@xJ~;OAUP1@Ebk(D{_nSe}xAhH@vU?O&b0O zW$jP9#joY9%HNm1k##JTjJ@HE!{w*GS z@J{vbi(ky}-%$SzYT)xXCST-T>Obb;A2+}2lmIi9RVSOK4f)?;{PTu?Nd1KPtl`H$ss4TCBWrkH z{PTwYf<`Cg{~hC>c~JR#=LZe%(|_=3^?$QRKPkgMP!_+9=cqvO-Rl2eJ^o{VulTeF zpEJBKf6f|ys;qv#!@|!!r2K7~P5})|-pM`cbe$*syx|{QCpJM2!l3=3#Yu zfro$2@C_b(G#V`8LieIJ^Od)>hxvKxB|4_WI{Yx6&r{9d>ef4|3 z2LD;ZU#aVhp?pav-!D|YFRQo;s4nCk{jEB^K>G(3__$q~Yig*#M{VKpe8p7YTjWYC zTs`SY-{>)#vAR6zGj4cWCaB;aJ68Q)>+^4TU-`@${%nu`S;MdJ#4mcBhL6hw_%tXR zd{D#mALZ4SczpRIZupbbV@3R0YWPnX{sTVwUaaA7R{o)U(48OvI&l>(Q4}Scm)%BM(ysv*fV|br@(U&QI-}oVJ_^oC7+i&{I8s67Goi+Rg zp7_OHuHiR(@Z*N}<)3T~e7**L*6^3A7!~<1zE~07^)th7_Q*eL_}6*xO(!dVU;UUe z{0W}?ojFDQ%lBc5%HIjE)Cw=ZRPk%nsEA*b7kT0tL;rYj@SF;K_8i6g`VV=-myh3< zTlgvYyn(;`XAJKvA9=(3^dEVp%IBN^ZZf=Y{E{-fPyboNFY~0|jNyIxGdNG>^YM=v z-q$}$8s67FW(>d9Bj2py@AlwRuTuGZ^A9=0FY)-FHT-f9KDtu*H);FVpk$cev;2{~ zpnCXO!+#O;TXXFnN;Rl|pZ+t3Kje`wV;5Y1SJ{N}-Qn`D+`5RbJZj0PC?Cno)MLGe z|G44H%V*a3=hmtJa{WvjKGLLkU-~o|-lw1FHR|7|pSa} zPs9!u_`;7F-q(KTH);63{2hIR;nk>2A3MO{D}Nco`^J}f!>{(FZ?shd{xc80$?(4Y z|4GC9+V70vt^KWtUu3g}-{uKFWq9BCAY*th|2F0ST@U|R!~53HW9{mHN7?*KRBn;R z_uRS_@ADs#yG_GSP*%i0Zungu`Si( z#N+dyG`w&9V%+fAg~Fy%{(j~Ed~NO5hmp*S%yj8;$ z;YZ%8cwhe^bEo3pr-D`ZkB=$dS3k5EzFfX>lW*J(Ao=WoX7vH;eGm#{B?EtVuqJ*#m=$5lKO=D_u=z@tN7QU{LDE& z_o?dPH`Tx=Yv9KX{|+rV73C+oOT%yW;8PDO{$bDjN7H8%-{aAL&hUTb!6zS5|GxGw zWB7NLt-p-FN(G8PqW*20MMeBt9#wp^C;Xh@6CQlz^Xfn1!AA}6>tD4P-q*j1eOdYY z>aQllm-nA`Tl`~RRsX*JWi+q&Q#|rF8QxdE#|`h3FIxkjH~cka<@a$X|F=~>-}t1( z@Nf3CKQsTL{(a-W$oHz_eWHf{jOw>R z4V>q+_|F*Lmp;*1sN9xJy{xgQ(qGVzCN64p`vH#;2ia1?4hxjFikN;Bf z?<|wQ$?!A3QM@mI>NiEh57|`6AOEfT_toDKxnkcxKNT~4dHtPq@w1KJzWQ6g^I>1c zD4L(U&-p(_Bfb&y8^<5R%jQd;WJK}rQs)YMul-Ba@Sm;Wf7b9m z{X|Yv`F!O&Zg}7R>2brCuir*jY5m=_RQdb*S1H5$`o|f=->&)#^}Eja$M`)OJb5)L z@<(unBII*-bM&9{=PF)4<2VO?T)s3M1Ucng!M|yx`k(gTqpK8Oofzb}0=hWF*)%o_DyUO#-qx>4 z8-C^r#rLZb`d@m1wf|Qs;uK|Eq5pBiU#Mgi`201B@aZ@5dd1Ib|GvV1@CL>E^cyw2 zPruP-_5YYw<6-_FBT0fEs5i9&>4e#q8&lvs`Py8ZX8osZ7Y%;uW{vdDoN0fhq8aQ`r`p^7@^7qw$ zdBc0>Z~4_-Jl_77;Wul*75d2=-j}~-Yv3c@)#Z;H-k1MVhWFLaS;PC*U*cOe{7-58 zLjB(F@^_EoPw~V*W_Vx!G--HW`eY1$frtN$;lEcle&1*EbKc9Ro72so8o=_gP7rc`fsX%Pa59WKa9LZ!~dSfFVycom;Q$L?LUa5 z)W1)EF~fWFzu|rH8?S-S*1+ctzuJ?2(c4x2R9XBFTKqGH|E9-(>aFVEr=N`BZT($y zvGS64({}amYd=zkpDN4$^{=-6=dk+s=`U$`@A{SDZU1S7e6hDF|4V%FH+;^6kH5XT zd@Y9mud?{HSo~T>)&Jv|Ae?J`x#@k1f36x8^0!P>4?k4{pQ(Y*)xggf-q(Ic->>qm z_UJEZ_>|%s)ChvROh2=R_vO$02b8}rf6f}-m;a&}^>5o?!|?C7@N*wjyf6RF7~Yrv zA|F!!zWmijKRlTJ z)$uXIztH2q$?!h?rwsp^viJ=-`3&#V&uk5R<`LV2ppUhU*-?-s@`9EiPpL{ch@6qfS z=Ffu`e$y@u-&cMzhWDj!t_FU_@F!}{55w=gSOp3`t>IsQ`K!6==lEw8e~Kr4lZN;8 zAI1&ulRs;CYrjJIvL;{ra~i(Se~aO-)c#xO|2yVCx3{|g8N*Mie)O$zmG}4~)%|B{ z;PW-`vo-LMM>YJbefl@NPe0>^U+wWfWB8QfL;c0#nm<}Tui^X3XZ#C__vQZE5C8Wm$zR{Cg04o`uFvJl3!GO&J+LGmlVI+gHIVgrT9=kJ1qR*%j(~!pBcmZ z^b>qV{rln{GkkgdwcEx2f2)6A`UhWCys!R>8Qxd_qzv!lpE3NkT5>}D1PxZdV$`e%Ni{#SeAKWq5% z_`k{gCm&b;zW6u&o8o=(Pa57A|BT^xmBs%)3%}(@%HNm&Q-=4IudLyH`V00~=N~nE z=`kbRe_`^?9#H=akDa@G1y3s8n)$GNu34?=llw*W@Mmh^gI`wnAFY9p8-CD}KXNtv z&lrBS$A9csDqng2dcfkJHM}o>jsIHx`|?-T@V@q~ zbJp=<(I%iyQs{O~0`G)iH>M-A_r-?MX_!Gc?x)?FDa7>XRVlMEw~ zV~?ui(fs8Amf)XQ^{YdC?6^fxclT8SA8I%*^0w!!e!ed61i=Za2k;Ya3mhNvU|1~| zDj!=n6X8l;3f%%QFYRdw|$zbe2ym_%WWxBEC&V_d(eC z)Lk}rJSpcB_~$Ry@O^wqcLI1#8{MA(mZwZkhJP36az@u_WOYBfa!x&yZ#QL(Yxox% zeYrl#_i@nG8{Jt(SN9n|-$y~e)ad`z!&l-Dx~S1TzY5(6&w(C|?k!d5E(BeR(fRbr z_&fl*jYc;>9pI^3ICp#`{-B?1P(6k8FY(iJ%hSP2%IMGW)2{)2Voe$SYyI>Cpx@D0 zM&IVA{|M;!Us^_go1gwM(63CC(ZAbIf5Z_%FtWCc{;&P?Ji5E@HD&aV`03k0zjR$0 z{kQ$}_kezAeHs0K%}u`qX+Hq^>C2V=6v+*Em6b2j-3Pk-Jm?+;-9Domt-|jK(8aIN zaDDle;huoG{gp;nO}-03=lHFz5^g)_I_DAYU7%~JLC5lO1S;>1MrX}kQ1|86dF!3W z!8dVL?RXyxzHy^_QI+_d3%Zp?=h~yW((iiE#f;ASD|4pv9Yy@A>HGd7`Ks}I40JBO z)%g7ybWQV!?-I;KJAT#lvJP}kf7QYrDB^cU6+PbvIwzl19s*tdZ))><9CXu0=PNgi&-FO-;rQA7Zculczh1cz z^iA2?;kSb>VRTD9`eeNCD&lun6}ksNx7+yn(vk65ehlulF}lZ%U)=|qyy33KL;yNbepp~*W}{&)a%oyO1Zd8pvW_$&vq#pv3M zU)@Rm_#6xRjM3X?5GwdG-sggD$D^9xtEJQRpc^+j+iy50zdJy;@(aqZTK>2nbn$u6 zJqEhOJm`K6x+bGTm7q^qy~+44!6eZC&)B)Z*;KWEe9r|ZCPtKdhE&ASkXsqI5S2nM zjZ7JrL@9(QVq}C&8Br;!ktwRF+$xEpC`E-7l|oHYsfH9v`G0?VJ>&1yIoq7i`|0~W zYyI}Ko@d?m+Iz;F!uW#5r4zbj^B1HWiLQ1L+MS0ku?V_#=#H#+{TGzSessHwpexTy z!40Mh8s8LjYl@&7fNn(*bXn*Y7eTii-6GT3d(w#WYG)DSi|3_TVSGW)v-Qvw#%JGu z6S3Xy=nCTt(v3rxV|fJKH*(R=Bd_uRVH2Gc+4_gs6&T{`<>?XG5i+J*P#1l zE&LYF|7i0;?axD3=+AzP=!Jg2;(yz>bBpwwv`9X5^R`4B|DR9jw^Q`4*gxmwq2Im9 z>FwY5{-^)tw^jw#gX!#hcD&H8$bO~oj(*No=hyz-l@}@%z26$i|EEqL)Ng6%ylqZ* zo#hc}KhH$h%ygXsbb08~O&4t(G_KpwrJAlu6o1OQ6Mw{NnCS+YF4Qf0KaE4b*!1@O zJzl7DWWC}@Ltjw)m_Pp9vfL-a{+fwypXurb=<-hT7nuJ^{(|0{97E^Taq$Jsr;)t$ zsc5?K7GG#V$N!zzR<3@{ML)>&C;D}Ibp1B;iwfi5-M2oWDUtQ+*X8I})OB$O^}|kd zi%rK?=spqpKc3BJk?DfQsUEr<(*=!F_mlj!4`_EBy3OV<+PO&mc?jJG(`__=p;^(_ z=Qi|%>YaYx9YdFHx(fp0tVJgm`U~psPE;293(}3GGS%9Rrc>VgsqA7p`#vHs^nUd9 zwh4VkLzj2ZIqDF)siuq8zUEm~Zia>af^_ZBjVVIALr(J7ETF$4M&8rJ-A6x^@BiW}@3yggo-l?Jh#Q+luJ#*h&6^ z#v6K7Rh_Ga9-4?faEDM+D@L-s|l2LGvgVUC4An{j}*M ze}U`&B!5BsUezR?KZ?+9J9LHdT}(>;C&GPZNYelNyV_)t*5Pb)&8*#Md8oe=YVbU0 zy6T1FGb8f&XnyR+Z*rI5{ZW1g;J2CS#$oFd>L1;2JM>dcA2dEg&}EoTM_>OFp`T`> z8*>_ee*QI?pQf|#d-6hGT^@0M`uXEGsh7(?D9-Zut!TOzqQt58v(V+3E{UI|=@a@P zdiz7rXZLaKH+Axe=l6Vci%fUebfHfo`&As}nIvPq4&A=f=oIHFbh}O0+?owt9XU?v_n*}MiJ!+@5?QajHlZIj@bu$* z2;HC}=&Is0-E?~_-c#4B;+&1H_ARbm`(7%3p3o-hJRFIB&Jd?B6~(Xena6)JOqUd( zTZe9p>39|HKBtVQ;!HW8d`$Pb$wCXF$5|D9;_WWZX#2CDKe~#h%PaIdCA!~{=;xUJ zQq!N>uYUgM7Mm_;{;fl|$aD;?`<#-${Ov!FZDetrkS)Zn} z@5k~&6;8?9+L1mBy*JwBeL{a;bp0yyNq0N_z46Xb=;G-5{pg3?>+}n(-O$j;diBR9 z^aZAmHeaPXgzkvxg5F&|j0C{=|Lb>d1Qa!!i7~zTfGC=3_1VHam?@?axD(Y`S5w z6z~Zhj+~#`AE$Vyy7r^p52edRH`R23=U;S-i=aD%Zineo6{J29_KT_)(f`w2eEeVc ziJ)soWku6PJOBCPk1ok{_WfY|9$@tTnTLM&OxJ$U{9A`^pXs8_KlS5&bOol1cK%YF z18`F4&%Q^Db4|;L^HhG@p`ZGMi_^YW>_7eK=Z`LNp3|L}pH%|#N59+jrCsm;$FJI7 zhi-%Ex(Dd?qw}6TeSelG*iaF4Dd-YUqf_3?`AiOzf4<-;Sd`$Sm3@!SNCn7?Q`{}jgAWm1W(;tv*kD1Jtq%B7EIlbQ3T)6et8%-63X$024N%gIl8H)v+q^&LX)DmKO24RwJu&>UAa$Tf0(KE zSD{NXUC=zpN7u}BqwV@gI0vBHXSxTYv@cy2 zx-RcJf6?}PfBYLTUrcA;(-w97#pQc?ue;yhN z>-JUAjRp<*$AMO6??|tmXx}zUsdj7BbMCiwHRA!s5d4Mh# z-6GTV3D9jqx7&0<^ZO9G9YxSp<%e{ei=bXX{&yWjny;(SbunGn0Dt-DrkXD3K9snH-P@IsNsbSw~ z9CbbnkiL%7+xON)?4K-ji|aa_zF+5m+P`X2{6p;h>c{2iDmHSub4+$>|0vFSmvO%` zU7qPey`#sOfIi#wLGz~(x~ZlMT>mHe3mWfCbc@U%L*+iF^tRB8bmdbRU(<0^aGwa{k%BJAbQ&N36G1ls-6GRzUPjWXd?&iart897*C({S zVbpcC3jOA0E}x({^U>`yo&GMk|0#^~q;p_G3+8`I=dX&%PF+{Zvk|(fraK-*r+o6z zrMGha?C*v1{xW(#S?CilcY6E#;Su+V<>+>_b-IcH?bmBb|CsK?KDDbs#Qs#gY0`Ie zeoyrO`;qnPhZOV$S37-B{|`VH>g04m{hx(yaaX4c>i^~F7MU)n|Le8le0ZJnccTB# zi=Iyc`s5x?AJqSi&>iV{`Z&|j6__sCxljF=iEdD;^B1&!^U!4%>g@e;r1`lG-HIEW zzo32S7`ip4;}O|?3fG01imz5{_EXcfwem>qc0yNRI)3EhJ`wzlM0dn=X9wu!p)2$k zblzKclE1?9S)};(qifyU^-KSNcFXf{J=Jt)1?W=HWtc9g-2vz}nC|NUe_8UE=Hlbl zNPWbFg3O67ZX3x}f__BXqS*_o|CB;y#sz zZjI@<^|;R|`S|l6-44@b2k7$9wH|o-dA$u?N7Ds8XC6bBUIc%&nDm28S2-ZQPUs5# z*`L|`PrqpV)}h-s*yUk=u7#hk){8oRx#&0C?)2BhSpcW>qx@EF$9fs&bSL)v*Q5JA zhTnowPTxE>vR{o)E&PVYI9<@Wv=h2v8BX_vxr?+8N21$jx@h~n^4X7Wx9LvgbG&ZU ze%d7cWY>PsIr|X0(A3lCQ?)($WIJ87IBOASJ9L9gS3Mx_A?VVJ;BPj%RMSPfZ>WC~ zuHb$>)5Uk4wHrEoQPlp~k6-U$r(YWoXLfR4#h^<)wep6G_QbcxS6oxab~{}k>Ur~F(+eiJ&f zzdq~qL2))h*Q^M-G<3;D&}E{lZMuhIPKnb@#g~UJ`#Be1p8(x9bQ=~r-8=Eo{T)L$ zEYIogFxjc|NA1?)P`1K!3j=hW(6xTa`MV&BPUEr;T}RW|_hj<#>}y9|x4CM6ne%Jk zn;CIlY(lqqxzqKD(!TOJgl?bd=mPgCoEK(FSM@69&#TT~qLoMLpLXbun7^RsfFURO z3wjQijc&zC*KV|VsD4bintV-n;(TA?!l?bYAHV6Vo!_AIeR=%We%tBJ3&=YKU9#z- z?SJaW0q8oKE?Pd)Wua?*8lCcv@5K5uoqbQI7ixY%)V%ZYJLm(KPu+lg67W0BbmLFm zFYG$S*$CZ+P0nBa0Do!dHWxvciEfAK0^k3jD==M9KW;;}&vfSo#CHtc?jp2X>l)^_ z>3H?#J`vVSCv=G)xqj&upc{#|2yr~I|d2h-_$LG=mEt@Xe2;iUH* zhwwXfhl|q-IQLb>Z~B)`7ZhhZbg4zq4MEqX2)fzmIu=2<3SH|W=iKdIT?v;1 zub}q=%h7c--MlF6YkYR1TXPzJe*XOMV1?-h+Ik3;iX5l(ndpz~adF!BobvZWYDDZW z>6c6Yt<&50p7MKvk@bpuHu^z(ojz#YtwNV>x}f`RKDsfc3%c(n@DP!-Ke$~#e{>a1 zcjCU=A$mRo&~NzF`HgnpmEWD{vJ0H9MBzA8iR@SURp^Hubb5WCs{bimFV{{go^|54 zhTP9h_M^`^?DP+tJkoilJezmLBTg6e^Y#>Ui~n{y`@YqP=duCl2F3mB&yS$-%tDuL zx}fL2`0nf{@y_3g_q5fMBF;a}r+oY#DdF_TqvWmrOvCTy&?$a@uO8KJ3i^UlP9Jnm z8i4MI>4MHlS?CfIPVa9yx{9U?^0yORvgv~QJ-!F$J<~6x)PBrFzt8m1o*UGD9=Zb4MSI><`?Y%hzrTrv?N5r{emwe)XS#mh z9B}Tdhpr&e>FoPqBhL5k=;l;(I{Tj3h`Mp;k}Esint9oWG#?HWFP&(*>=^{pgY| zbpB54v*n`avkCp?I!+(7&$dfteCj%#eGe_aA67MDKdE0wqVJgO^e6s4!Y9%7x#$-) zaC-ZmTK@h*WWDBfCi)|doIYqD$wTKgcDj^+dAbc<#b!7M60 z|8vUzB!7p{?K2(!*L@=Bs@}l-Yk&H@+o7vyI{RMSi17_UxA+R@&%PfwqHZ?2P)Daz zy#6Obe5=qUny!9;E?@DPE@*xw^d?`^H3{(72wgMNjgF$zy2(SAZMt_&7rMA|)N#&2 zpM0guJLo>NTfnr9=? z?J!-?bKX331*Qw?pLOVtn67z1J}Eb`Uaofe*!TPL98w`_K2_1L>FV^!0e)wr+hDrG zh5ghtdOwXsKj%8oT&3W9(|~n z)0Z`Uq;p_Bbe`#g#-%&DBi4S)0Dt4qC7QpW{>eQlz98MEli~}~9Xct#wo&5q_djk1 zNfuv*=|Vr0kJ^u&&<`{H=TZFXem)Xi?Hf+t-}BVI>GA^ntwXoSbkWWM$|t2i>)&+m z$2f&P>gVEOZ5T z2FEGga&-HOpxcRVcM){)H?w~iL01pmX43_or@EsnjIXWbcj|tk{@#QxafHh^!*ro4 zXGQJrJoM?N4_aT_&{Z7i{08l3$JD;*g4WH*f&cGscHz32RW@pz-O*1Sb^37}hc3f( zLF*>>B!5BcVH3K=r)gLH-tHFm3)6j8*zcvH=Ti%P>Rm1$eXo%JIc1*9?>uz7$2y&T z|1*EjFfnRBjYB`|UZ+3L+@3m5G~aX4jVXd|6S@r3ofF{i&`JJ+#(l`3|M$m_PW?~l zmgxC(LZ4&p2kn<5(akA>ZXUYqBIwqkn_2|jesqQX(>|bo$`59K6v1B#y25;e*7<&rV>ie(#&nf$ifB#eaSxy(UZ|p?3*mObX)%YR* z?=M>3%4Zz99P^iD;e}?L8FhZ7q4ypPo*(kN4c!s*Yu}sA-`k4rcRBjnGlKo9pLU{4 zGM#;&cEsnN_}j@p`}F?mq01?Pt~i7NV5+8MbgZjPvFxod=P=98jEA)5belewF)czcTKKn7( zezbi?{W%-mRMQ3BFIJu8FKA!SN4MfM?P`9c4QIbJoqc~e-%D{u)O=FV@1EoG(f5h_ zpTc!%&p-0J9NnDxPG{ds&F>3D_d6SX$K2ER(<*eWi=fL#*Q^M-ggaTkMbI@uSKD+v zLby+1-ezhX)6fkog1=03gG?8+E{>tgHeH6b6Dk+IA9tePZTg^f5kG?QdDiuJS@Rod zpQ(o~+jK$eBKIVJIfc(l(?e0`Q6~D(Lf3w@d8~PK3|+G6PRygR(f#g3zx#RTH>h8` zkEHz;c!-*iF!dh8^B_WkIrqY_d3btn3qJlB5E zeiJ{6@i$%2ep3%!p}%P3qc^=*FR2Qv_Wux)r9Q3;s86 zGu7{#&?UZg`aBMy3z;rxT@2xed&#EDuy#TVibtJqozQ2P{&Uku+DAsBYxcH_Gibk= zr}j;k7vOIlx)r7iT0bdc=x@`VIRE@!ENVVg(I>AxeSfw?SKD+!>u1PG{;~q{nT>9a z`3qWStI(}6-I@S@`RG#LIei`pck?`JI(}s0K85ScOwF@K=%$)3+TT0S`p!eQc%5r^ zv0We97$3F2v(P86KYg6b(bX=3ZYR2=BIx4p;eA69boJ0B7D3k?UC4An>uVFb!hACd z`)7Mx)c(mspJ9H3*4H+4&E9kU7_`2QseRK0t*=^RSzo3LT3_?ftuft+^>tzNe8!4NsPcIXQI1&!kn zbZg9C(7rZX`Is(fyjP*i_$WA!dgRw=JnxT9x7_j#wTg*4-sRDUK6ZYiz3XjI{6?CW1JI>z3yxF%ve0!Yf^Ipwjz!SzMAy0qy7&p~7p7|*C6B8s#e09odf`^^ zFf0pyga<0dd;8!PczK)`UIN>|+hKLeTf%CvjPdnz;=L|0UbyPn=-_AN9Dgbs@4X%G zg+G9U;2Nm*o`aX8n__GL=jnR50$mJzBi?KAS7N-^m-3(BSzPxqOoU5eY4|8ifVV;E z+rh`shoJoYQpT05{1ktwQ1Q2b zil+%w{1-s}_FDKX_yznoA>P{oe}dcLn^5}4;Ae4OwKVu(oY$fU{KE6Xt>G5Rli^la z2Y$nKA;|Z&hx1F}A6*_)z6;=J;+zCOr92JZgi*qehyDOpMd;w~_9)cRL zJE6v_C)9Y=h0<4q%JZKyoc$-%^?RYl>2s)YdJk%xUV|E^xlq??&yE1=?i5~}@2 zq2j$CYFzGw8kd`(#^oxgaj62OKU_TCTT8#Mg8bW&7Oz5$OAh3B@~dUTx8O9G&vk>~ z8rp3KH7*Ho5bei7jmvk%;=NBOUj;QTcf+5s8$hkIvhW9ZAfA4JTVQMaErG4zJScy& zp!#d1F%@zT3%7@rx$f^+x1Jw~aqBhlUsi?{?5%w~+6a4Hx_q=hiJ8ioFyLgCpS%`ZE=&##j=qdjR`s)@-{}}v|@lS*NU91*8pucY6=aeVI?XV8q!}TH6({BFX zT*r9jLFKyu`s)^MqdX1z>lXfoz2&bsuN~LF2sKZhhKJz{cmz&>N8wQT7wiN7h7I8F zuq-?X4;+p2uBH7gkiX#&UIx|vLa6yP*EqvC$vE10n{nq6r{8Q`XIx>-Gv*i@!gFcA zB-A|p`EZ;+Pv3>IbIl%S_KjvYHv1g2fBn6>W>$p-_OwRXXy7c^!pjApDRJ#7ysDjxW)LAahmZ~ zV_BGuKL3X}Z#sM&^4;p;;qW2Y6{`J?a0YfmV=bur*6(|n50rlfWxoP7|1;q`#M2#W z{?~^5?tR#U+8?)mALnIZzXSOj_Tfy3%kcG(6slE){F|^AXG6_j5Awfi1^IDuQSC?g z3)gLcq!7-9n!iJ!;_VMLf7`m_WlHn%Gli)|N68wqlj(i>GeZqCypvGYyRJ^mG#$g29N_i)!aX15VHV&`d>GqMi zQ1g2R)cl?RHNS^K&F?-?^Sc534VHz!!UJE$d7Wr?3%nXGhiZ2bRKLtKW*ak&V~qcN z>2wE--y6R$eq@{mHE!up1OvgySv#xY zE5ZBV0S0pl+yT|zCs6b34daWjGyQun)I95Fb}Oj$ULLL?j(jFZ1-Jrgo@GPLvwl$X z>^i7{+OJmJONG)iNMUq1xS0^DGszB*TrM=Gn0iUA(_T z&9hG-&*I@csChOR9>A^#bzdzDb)PH=`{M7f590ju#Wzsri+7;T7tcYRFUCUY2SHtb zjoDW~UEc)iTyX)^x#AqCb478ebH&da-1VEG`t^ONbLJA`0;qX92L4ESf3w>{t=}3@ z=gdRzyY;&P?xQ>hs$cJfI%oESI%n2`>*${l^v^->x%T%$#rrwbIpQ6tbHvL~=ZGhu z&Jnjk<=X-BziJ8mXFB6j3~F2o*0Zluz8k&^zkok*-3rM6!n660#w86Z-)>OjatYi_ zc>>h9d_a&tV^4%SNA!l8-<_f6cPps*eKFMhj)A|xJv6!>u7y|8-h6l^919oH-cJOl z{@k)Q&ifbL%TRGV16BU0@d4v_V+CU=?_PZ*X-kOyZY@= z`VD4xg=t*Z3@V;lW|xG2qALdZ6}qzv-iq_CNB14n_-=i~TJeFFoW|79I_Eu^v+33{q+gwO*1Sx0-5+kXu%@ zGoa2b`(BFkzTvucQ0pZND&9$OH0|_??E21!cW5&@i!8_3M;`^;Eyk`Z*biQQ1PsWS~ts$FB(&g zosCx*&w-lvzdY~O!FnkBd9x>)J<99~i`;x2_nc$nXC1Fz;M%_&D$gcRd6tBlw|k#) zeBU@6-a~miI0_z}ALosPTi_VD7%HEIQ2Xyp<0Pp3Qlai^ZOuLpYQDul?Z4Y{-2S@= z>b{l%H6N~n+J7&H+JEEW2lVsyr{lbhjQ2b^o%!$x)O^T*Y|Yh%LALg4gQ4zg9pQ&u zR|{%B?0?F|`yJGLco%M=d>+(%=mqyMHpUP18&5}^>A%4BzofEb~ofBSzIwwqr(hq=q z*HO4Ol%F%Lyf~DfKPS8M!d|HJ!sk%uh4-M&3ok+z;j>-YCqdO$<5!T@Hy;f;6gY6aw`ez$L5MZ3>D7> zQ1PDwb$*D2IzRk+pF2N%33YyW5lTM=u40OGf)6nN+e6L&WXLV5S`vH%R)X3Ok4%j7 z-lW}aQ1gEtRKByI=Kl!zG3A}0=KmS+N9?s3u6{n${dJrT#vih6gS=6_K$MBa7Z$K9db^hpkZ=B~* z-Ug<^^WpU{2EI(5AB}UmweThEIcDDuS8{z#_%i;=n0jky%wTFqs-vX+g^Nkgu&H=Ho4E3*!cISaU@FD!vfX&n&uqFKNE*IBFQ1x#cAA#3% zeHNTd`8c>0zr`R+soGbgxL;wv2{n$dz^3?{Wt;+2xW1q9uaPd!Eyfq2;!K8@qWfb6 zla%X!gzA@#(2vL1%lOruE}t1t{Wb}{<9XG_L8e%>!SHSRZT@hcr>M_{npby2nhFmz zyCPJ-mVo>?CcJN$<4aKIpUa@mJ!isKV8Kw=?+c*H?}94tW#wD$;J$*NnQ#r|>5zYG z5v~L)!ryLp{j?XZ#s1K^3O-7E&%gyR8%}{!;AV7TsPQgs955u#n}>Zq)VVDNeu{3- zZJZMnTPstKk;P%fPL0?XC11oB=g%MhU(uGsCn5KYF^fX znwOQK`tSJQIPW_6J5+nG!rpKZyb;cZAILvczotOVb3|{}xn!FGJ0rvNzFBv|r{1_7(ge?aBQDdp_(&JQ>EjyRjcp-x;di zwlD!*bz^CG9rlm>M}BUEC9#)x;hcp20k{-@pLBNT#D(w`%ICtD;0#z5eQ)DckS+>0 zgq2~dYu$AhK)NElB(!_974x6+$63@>u!lhTPlt*>#aI_A{<~9L{6Aji`r&Q( zD*aFjGQ{EjY+8!@Yc?63L#j4${JbvbL+omGoc}Xm8rS_++vWWeR9v4y&ASay^L!Po z$aRlE&8Iy8L&-?&vnbMfh6>wLj)Tt&6d+ z2W$Xq!WgJHcV6J)dK}gwzag+E<#phB*a=Yac(6SEu=9M!jj%U%C3qH`cAo3EK5!Y= zud3m$TMTtwmT@f9bsr?T>n^SCt}6p|UB7ePyjaU1#h`nxoa^82iNuY4Zkaf*1YFFd zT|``Sq2hZOs=w}q>aQE2=1otidD9tc-n509H^rgm&7Wtu@kxc6|ChqoiQ_=X#WAM1 zYo|X{J2ybJ(;ljw^P$=~7pk2KQ0Q% zPproh4}S#J&N6r>_AKMwkS#de7qaDs(~iY@+#147|8n|jQ2qQ5FH6MzQ2qP`)cC|g z`TdiJ#2c}Hg7W_j)co6Od>#&^JQJqD!LSAOS3`gP7;pYF);r4lXbJy@17elpQ7V5e^1+m^-%HM!ZVK!XGbyJ|$cL%r>yATG-Rzhu9rKT^;^(pc`SGvM+&sIQKP;sA*Z@jb>C;$mI`+>@LfQFH z=gp5H{|i^(KkJEa+ZNh^Z$i!UMNsqn5qJQOg@42T@LjlebF8-k&WCCE83%j9OJRRl z7V7>t=c8DEADaY~|2O$==zix8#=^Lo{+iZ;W$}+Cc zzaQ)0|E^pg>#=2qkF1OJR-u0hz6qa#9}`CnNEd_;y&LNhQn(__q`nG2ywf^;jvpRr zolad9>s2G4PH-*domR$rv(TT(ATES`UUT`BebsS0n>k$^o(Q+YzL22^w}EVp;ZpD~ z)W%w%jCPL}YfNZ7Jj=mD>EkSn(>bkv-w}0?b ztjAD?XF=Vs?|#wQZQpJ7==c&hD0xv;76>5BL zg_SA43F^A*j8_;hGuAiOG~UI{S>t*u)N@0Bm_a^Wpq?B0(wUl{9iYbZ;^*8v-ofEK znfec&arLi2)ju}h&EMOg>^^V;adv=ZiLWxu#{NCW<_VmM{R|9KKNV_Rhgx|XsOP5Y zQ1dYX>Urb%({7*p1uFlqpz_!Rwa=}G+UMSY+UH(?+UI7#HgFPbPLmzsU|0;Q|BpQ7 z_K|$}5$(JU>H6>l*qQ4_LXG24cs2H|@G5u{)N^|;sJxTSu4{Ip*`>@r{3Q29{JsS> zub+e$!pX20{`*0#$F*}~y*TFQvv44s2phsTA9nTqVFT=DbmAWTmCkbh4o;8t>Qmko z-U82oimxnGd`li+{nO6DiO$cMj9Bk`uD^DyyS^dR^|MCDdf!ogB~<^P2jAej=SIeQ zec*K1ocf#LBd|Nvcr`MXgqKnN^@vz+5_}y_hEKu-bQv%O`|&$ty?l6ZXsq`wd;zMT zd%^c%ZMY8pcn5jF2Jk)j^X+bbYXJYiJ~G7RwGJxYSD^Z>rSUEXdpG_E4UF}kr@lW_ ze_U_uVw^i5R=0|9SEx9W;HBvM_Crs37kCC!HS8Fk*f-XDobqDu5c+r0W4+(uGjpxdU+Tlhu**P=S4pV&{=SKMO#L3Hc2>eUa6WtjJ_L0? z7z1@b7zA}cNQFB0w1v7KymO-)?;6|$V(FI{sChQEm)rM8K=s?LQ0uA()VgX6weQ!5 z+V?Mn+V`tK?fc)Py7l!jRJ;wixp1@&KYpFt_b=@d>v5zGCqVhv%MOm*;hmiwH^RZ# zl^{p%@U&~(eYp>0D-XBq=;mcjsCn79gNu7#I~Vt-P<}sziu+ZlxF3Ov`yr^fr$EI$ z7Ao$RQ2RoCsJLggiS;Vs|7Z&r_v1|N3fM!S{HMc;*eS-kFdh4DCiyw=$7U|>x8ZW) zE)6xmbDPF`Pr>exOTx{K<&C?Va9?12X2KOP4faG=30{VMpmD6%5C0qCHt8T+M0hZy ziST9cb;=Jna$EzONe3C?@EEB02bg^o)ck4>m3K0{9^H9R@m7F0qAvv%@86dgFLM39 z0A5TyW9r0uUsHc=E!#Js#%JVtvEEmdzg{!e`w|X@@54*r%kToY1D*l3zIN1L{&U?r zScUR|Q2XxHNsK4;d(L&|{vS7_%Z*nskqP}j#m`TL+M_b2T6@DX?i{2KOv-QZ>LU9PVQE5g4CekbL>z!dDy z;4JJ7a5kI`_mk(XQ0?@EYUe`uEBC8JsQ1!;SB~|5!rl*mhTp@l&~JmkaQ$Ma_tN8` z@)`w4(_Rm_gYp!37xr0D>w7rC@u(N>0dIv3U^47f*3~a9!=UZ zI!c9FM_r-TQG2L$6oOhuM@qZ-H0Dei$A1gs*$J^;J?z+0oaf<}B^@U~=}H>kW>b~D z(0DlH>}kgK#;~zdiCDb~2-k>HqT6^#3i$N1N;|2M{~L-`!YE0XZtkXIeyc=!#MR%TP5 zM8EyQO+x3(N7$srfjq>jeib(b)t?77jtkk`weH?xb6lS|Q#;q>YxW#UE z_WhqYd+(+gZztE6fExFfyd3@l`x+kdbS`WFwV$5HL)=Zo`~5pHURT=5gI(aIP*eSd zwK1NS#FbFvUk5(Sc-Mqa!76YjbYaQ@pf1fd&--{Py2)56zuDv{5CRs*2)-v|NG*N z7>_OrKLJzG-wGSU?yx`C9b4hbm%?_~j~E9-^-DUGE*UDGs!;2pBvgO=&COBI1&5%X z3-&=h7kmNLAM4;1a5>a`nrG||uOyC*uQ@&pH7@r-wKEXvee|`)CdPA(#f`u6!wBg< zH@;nB4#p(xIeVWx@V)eDHzJ%5P z!%Hn)zYW%<{STo0FM{$v&g|Z1cZJ6ow{}*3J{*UxG@K2K!Le{1hlGQyo2gLaG!kk) z^n;oY-Js?}C#d<*8frc?fSM0gVF#E1H6M;GcHD2=0OfZ%Or!n@sCDxQ)Ock;ou@`X z?Ju`L{haoUN8CDJK0C&%ft?HG|1qfjB*W|xW~Z6m+3f02^Y1LE_K(hT{j|sUfpGy; zKTbA#EL6YUVdd9B^;>hOerpKTZ-*Xc|D)fw!9?slm`YqjVQ<*S*bu&geSD^iV?We$ z`7Wq@KZ5G-H=+7F530YPh3fCeq5As)sQw-bFNgi1`lFk%666sh{7bfLe>c1ldlTG9 zdrw216Gy-vus`eu6JRU2Z-$F+8`L?6}-yj!pjK*h5isy`M$#d9CjI1GT5i6a#%zD~y0#zw~4 z#;V3)54iCg0QG+8MyU5gS3$iWx^J4>*M~#JdB@Zk|2<~~eyO${^`XhG{?|-b|M{dC z|NZ6*Q2M9f1me9PmLhI zF5m4?`EG>D_if`c<3i(H;|$|>87|-LQ2B0#%6BzXzJE+``Q$^z|J8VxZ{|3c@2Ih^ zzBg2T+k0HT6`<@=Q2G9Tx6AiEsC=J>%J%`N_hC0e9$~^=q4KH&7r`3vL3m&c_f_)y z99D;Gp{|<&wN6_><$ZLto7ca<-q@R<`t3!id65mZKF33k^*I`9eclGOK6^pM)gEeI zHH7_Xuj?qrgMFd_n&A1+3kA4-@{Lh1$|Bc~e%-c#(*Zn)fU6*fM z3kPuB(@@t&=WUISC8uWej2+^v_#jKiV! z-#>@N_`iSgCT!02uR!(FOsM{xY^-jaKh*Vi52${;5^COD4mEEshMG6G+~MX$PpEm& z3Xa5oJ+r^L-SzWFuoe1;q58Wz)V_LfNQ~DReg?Hqb%pPts|4Ssyx_JNk0V$34R|h` z0o4!p!?oDA8q?sTTz@%S0F&Vqcp>}*-H*4r>%NAXN9&9Wpw`nQsP|@fL*;R&@it>q zDBT5cDA(7p@}nFU%TxXf)OCBH^7sI%UsoHaLH+z>124I!Q@#jFKM!{2`n%xuly`^x zQ@(1gR1YtL#o?E?#CX@C`wa3=BwKt4b^U`->t__ye7V}}OUy0{bzew;(!Vg!_4^Z0 zaZZQ2FWd)Je=}77ra|>nH>kWDm|e~6GEn_c66(5NZ+89gJ(T?gl>hgk+JDE&=R@T= z6ULI)1Q-W9K;?Hi)H%5+l%L{I`5j`>D8Emk?6qb;3*|otN`C{?_1$1R{;q(E<1(l` z>qF_zhRQPmD$h8mJom5($o>e*ej6&!l~C6`1vTD}m^}^3|7fTlECc7meW@;v zT~OolIn+3K}f7OtTEf7ry8-yv9+@}Hsnz69SO zpXaRnNh{BS8>t@;r@@VDIHB@^$pxS#DN;d`G!u4a} zh450yE1U3HP_KTD^@#CG!o%F{AV7Al^Rupf39cBS1I zcrE;cO{WtqfEU0oVFPp@!xykuK#l(-;{d33dPDV3XQ=i|Sosg#T={&cIH$lrh;sn+ zue18jP}kLgFTsj19qOgyGw>HS0mc6*RQzv1UH=qRyQy#;>!2Z&z9N+V->y!-70$qZ z8){uHhUb&VLaV?f)!y5^=06FFb*pI-?`a|-x)uIs(;JumyJ&ur@=GuH^6uu)O+S;Q1|uf zW*0Mi|Fy1uD^&ezSc2>4LEU%nhfA23w?M_$8POkXuD~-Zjq7?BsMqVHWkp z;1>M!xZ3f~tK2yz73zKOn^*Gw2`+=p(PcvZbU=8V+1DA%L9K&-I=Xg#Gp>g$o$xB? zpR3?j{4|DB_*KX=pvLuR2e)4yfLhl(q5Q3a>ffiK@|kjli~mlj_(M?fr?z*zt(|LU zPFolEy-;yq4;6Qc*>$1({&9JXSB3JeQ2qHH)c*TCR9sKMUewQonlDqJ{0)Zv&|PG9 zNvP}7+qiyf`FNE4Zs=yD?mw_6ee=l+NK`6Vmv5~R1v8plA7;ik%&|SCRxZ7CT z7;F5qfz$nD{KmM|_?~g4@nz#oIF-0AukXgUHq`hghF$p^7jX`zpXNjL-=sQDHyBFS zwYJm!bw2wne!j2e>Ni5wFFVi8lPOU4SSWw}q5L&~mvCJSoC1HS>2$ZjWbF2^I=ldC z-dBd|$GtV2{UwyW8Y+*0)!jV27M4Zd1gf1xsQY;dDE-SkJgB|dP;pIws=o`Wz893g z*s3vJF6F;eargDFq4Zmz{5FBw=iVo{4UGG1Q2SI4)IN1TRDCI^>;A6f-UI#)>oH&U zLzRC8lhCb&nn&}Y=G7di`#~1eely8t9cC-$Ce75E8z;LICIT@0P6lY87i+~Q0uQhRDWF$ zyP+=w)!r5s+281wK;`)m)H<6C|DwFF)n5i{QeFzG-+n#Y>Ar{TXW^A_F?lS5ivJ$? z3ib$ig!(qH5$BH@kgYBJS9urD8?ZU{OvtYshlj&=(O(JIz&cR*RfCsMA7lKZ9M^N* zX5%2Jes5*01a-fC<}CM|eXGdzH$m;=t)cR7WZc1I)j4_-)c(94YJXk|wLkyFrltA% z1ysMj1NB_}irIak_SvOn+$mTr^7s@g?sbrTvD#{=_bN-E-m5$V71tEl3XX+(u4xV5#9tMt zxVDtyy*wNR`4yIMU&zu5w})!K9@M<61hu|qmvr-N22_9C0ZUQd9xDEZQ2HuR^YI)g z-GMV)e|!&BzZL2|YaUd7Pe9Gn@$d%xT?zHv5f2r|-ywJ1cgD{kLml1#pF{Tqj6pXF z>Rfg$l)q9?evX%L{kjgeMYjSf-kIHMP{(2eeT=FE8znM_; zKEv#hW?u{CKPlGvKNHITA8Z~MpwBmc4A~09?-*Z$^4kx-MtM&wzslIs>TAHM=ssi< z(m7)(91pV~OER1RHNO3!^106JBTT~C=st#*!X+>RJ`V4L=}`L4P}i3;JJ#&?_+i3C zbgx3DUie9<@f;1$gLgp9qkd3%^@Pf2ADf!$cR|&E3RV9;RQ(H3`Sd#K@@WGbQ(pz< zpf3*ZhhMOHDZaO%;=2K6Qhp|6>4g8_C6eOV0;PK$N;k^v-exD8UBF^c`(GLt!fDud z!3W{xW*7g%>Hgw}#M1w2eAsxu@gCzH#&|dy{r3mi4q!j{5%FCCKjBwf^7+TqnqOo1 zBV>wmIF#-ff0RVO?|Knb{WttEEY)x39~;Ww5~%q(7pi{8J{Q*};~L`vsD8cnd)FV0 zp!`(L_x$&41>ZS4AL_jF3{-xP8Xqu@HxB*Q`RM`Wr^O!Uf6q5Ao{doPTnJU4WK8(l zU3YM&EB_iQ{`aBuXF~0R#h~8r9R13T;~_YZ^|=~qT`z?C`QS8TCLD)-6V!U^1=Vh6 z80Na)`DGdD^PzNG;JxrII1A=N{k&|B)sKPFk1)HNu_4sY$r5*X{_|`psOyhxch~;_ zb=?MdjHX|;@<(Ae#&NQ-4V11JR6f6b?(FSQ^L8~!X!bK!C3_r=@nI{t`V z8uoWk`^k2AC++mJ@}5?Hm6adg>UmwMUjb9m|F+rle;>a-e{@ap?BgG`DxR%S_DZOD za-iaw2o=vYM*SmL#q-ri&d(}%2mUu~^8EYDyHMlM9_oH_HXKg*+R9s;WEh^u? z*6{<#pOy^gLO(yK{KrG(KM-=u2ww};&c%>FK^ZO&rT?14m;5~oRewLMKz(1R`X;ai ze4fLR_T`ySx;9X8ZFf1y4sq?1OdGH4G#ouuCTUR(<51&9k@OAv) zzdC=c;SzX!x#Qk@t>uh|9*5U)bqh|sB^%JQ0K76q0V6sz=_mPg7e^LIDzs3Q0K5@ zcr)d7p!S~{Q144(;2^kli7WpQs(iKCubQ0+|75*SfXZhu)O*_=@I~~=un#=G*zN1v zq1M+%sCD%=3e@=4_ z)cv&>RNlWXx2_AXRBFG0og#&gVbI2ry+eJ`l?QlQ_S@#kmVedV28 z)-m-@EpXSjf;z8vea7?Z;U^hZ#n18i&d;B4E%ukjO;A7gS_&7y95@9&20upE8`i;Y z3pKtobNId(%5Q^zV)ucEVFP#omW2iIz|-i+cMEI_E5VhN|M-;Wy$RodSKy};Ov0`R ztHZrdI(xZsGOR{OJJ&kMMnDjKi-``^UGi1?68t?Kc}#j=je0 z1yI*L2&=-$Q2SL^n1tO3#?gOg!v@%ALizc7HtQDqH`oyUKB)4oW^XimA=Ey82UOgb zLB)NZ@z+_d{U4#?-U$`=X4n$_1}ML;sT})dvu8rZJr*kN5m0frf{MEqYznJFT^|D# z?;j7lcz=Z|-(mI^vtNgbcM#kJJ3y9lxT)1wpUJs`I5TIk@5B8MI=g$8LM6Y)|>P`&~cW4mD4@Ld}zgQ2Tfi)IMGoP9&d_a31`3GS54dAB39E`=Ivo zg>WSGv*2s^nXGcy7p{P9AX{>{24o8j$H2D4u_x25`*)%Cmt5my<3M8y)VN&&wLesX zmvjAJlN{&61=v|o&#A+q?oYSEzLehp&*Hk4FcH>-rD1WH0KdA=>DR(##5Ws0#dTev z+U)>WQ(o6t4QhUrfa%o#JCXf|`tPCtJ_7phBcS?kHB|pCf$G0!VKMq|F7)3^z?EFz z5i0&LRQwk}#eWVgt9W4<_-BUa|J|6quoU)2DE;&BN%Rxpd^iXy{yy*->?@7U;ZoW= z6Yi({-~?xX0$XF}!u9wa3D1EY;m?#OLFIjLyyrcQ{VmLq9=^(cJQ22{zAMzZsEu*U zIKKA*-5XH(Er*J05!C%hf74zZ4VA|*vj-S|AM5V-ze4rf8rTT^UGN>)@gD#F8vgul zcYj<3rJDwCCyo(TevvWJ>c1Z2>Q_VER~JBCcfFN2f_HHJS@2!zzZ~s!YoK&bnBCJ@ z9V(A<#$9(g`%~jsznp!|g449b7G)0KY;RXz(U{x(*Ap_Rv&{ljplTMHHc za47wk!`ynR0~Jp-V_9P{sQ1iAhr0Ixzd-5s7(X+9U|elnQdoY6JCCfq-Q{!ht&V35 za_5fUZ*k{_D+ju`E{4jd!p%;%rk{J?un6)hG@K2yh-V^{Zb)Bu-E~m*W$Es@xipmh zPaoIL4^Zcu%c07f8^5}V^B(2Pp!V0fP(RLG@dfF$2oq?NHB2J)qvdwSdZ}K9s+I(>(7s?4wZjHYh(i#@SGQCPMic4CSXQ zl%LC?t~(3L&zWX_(%ao9-iCUA`7-Par@{i(V=COlQRllGI8T$$N~m#u+4zj{26&kK zukPjgy#Z8vl~P^5|8l*vcS4;j--GJ+pL^mDehSsU?-=if)3IB^JQ#+&@(rI2wSRom z!}aIG#@k?N)_tnk7ed|t^1D0zTTtWh7}W2nXPVs&N>?BDAm4wwx&2_9-p< zTKU^%FEc(0wI8&(%JHWTe6KO>ZiO26ccJ2c`3g5Kv!U#JpmhD9blt4{Y^Zsa0M)){ zJk;Km{|r^W%h=n>uQU4!<7HOf!0hvlA*gtlwQ)>q<>Kf76-QH8jQGMWUA*O>?4vE5 zE+0y_4XS(t)O>gVrZYbp!atdZVXI%BV)M~h0qVN$&0YCrP<9olyvi8Ay3E<17?(oD zePdHMpBq43cRtLfd`}bS?|rE9zGi1Ma{Fi*DF1QB!io~d5?ns50hOSi=pC}bFquNZ9P|B2dcab)N|j#y3XDQYqKu4L(Si{Q1iF4+3{xg zzsRklzv}S3hVBn2f1jAW&g}6}^Jo;*zBCvr{~A_a(aIC7{M*`Y-Mw+4=Y5ENKAZ|4 zf*S8JQ0?@BTCYu@?%O4eRnB+zZ?$awLh0{-IuE2mof}%1-301fbOHPlo&~>v|DMNp zw!xp^cK9aL^^d{V`8h@=9E6{-a3Q<_zKE_JT!dW%rV-!Hn!JzZdFnIx1@mzYT*mc_ z;d1yq{E6$P!q*sw0q{$7)u4WUQXc9&yT68O?>jh}@^|4!=;pz@uv4Jse=^j25D)f* zzbCo!UGQe&e;ew4HVvx264cK#3#xP8hi|}^FdJS6YrsEf=ihU!eRvu1d<50bdMJPM zq4w1Wp~iQjag;F(594P@H8=lyLyb#nRkw~Vg0jzpns;YH&AT2|+jGDhf#kSycJf44e)=Yg6B1X`{2be8>+o2 zP=1F&y_dTMs=cOGUI(hZkkubQ$JHNz-*DX~sPbetuBp&qF^ytDj)?!{Hw4J3&7`=;sIh z{K{H>(9aM0`9b*|2L1e?pP!Z2fqs5ge>~ChgWq%ACg|q}{rsSxAN2Esetyu;5Bm9) zvHYN)AN2EsetuRz!Rm*@UCIyo`9VKF=;v43@`HYU(9aM0`9VKF=;vqUb)cW0)gM38 z@`L%x5Bm8*KR@W_2mSn@pC9z|gMNMqmLK%7Sp9IgTlqmhKj`NN{rpN< ze$dYk`uRaWKj`NN{rs%F4)pW0`r{=nKlrusgMNO{&ky?fK|ep}=Lh}#pr79vmLK%< zgMNO{&(G>7Sp9IgQ~5zZKj`NN{rp0fA5?oYq4vweC3s)U{bn_cVZWIVU#7erR6Nb0 z-Z#{Ndf!kP>U~2wsP_#epx!qeD(>DhtcOFmehJib`h!q@MnT;VZh?OiXFsTYy#;Je zeLBV>*cmFWv!R}g6QS;Rd*VH>Gu#LtLC3w_f6vbU_c~Bc4Bnl@Ga7Qq*+Y!n+q|~a zUkgW2-@(dLjGXnoj?|wEhf`m{$`g#m;MLUs8b`lU{{xi&oyO1L71X~4X|n}q0KY!Z z_$<7N`iI~h)bqQXe*HM(DA9%`3fUc>{eOz`eC_9b)PE3W9;5^z>J9D7Q z7cowE^CzC?z!zXPe1@-{nhGDq&VUQB$H05A2f-I%I+QLIK8D=|N~d{pFLpEdGE9cj z)rL=CCqd~dLVT7E!RMjoi*!dAzelhOpmh5nbF}mh_!8U_$>7u zMeNp4`eyJhm<*5eilH|2Vu%ki&ZQIKGU`K6I_kWm=#C)!3l>1-u^ZkSOMgS@Hp8{l zZ-DQ>H82C+VkrG0_$17M$}bzTAD5mApQ1d&%7?-6*n{9am~Q2%a4vQis60AC_M+0w z;Bzn;YJ6(Lhq05O`aKby}Q ztUSkkjPXUyl-7p5fYm7O@3}d=66{`I%P}jA#@+6~Y+%{)^Jmn1 zK>5iqrW;!un?XN5=*MT}o^f|?^JmO4&Vjlv8|u0YD^EAJHYOQ8GpE1Wc2m1b? z@6XE9jj2}O#p+vId6LmH?(Sv&jVqx1E{5`(W91pfbYp8{GwAmR^!vlgJ>wA;km?Jd z>UTqbz8P0o{bH-nvGNRKy0Nvf8T8wOetTB#8FycA{*5`t3}d>nwXqrW{XyTKm3zh` zJuQCd#}EDZjXA~)W4bXF%5N8_>sniRlF>8n?&19HfYNV<(yy`d9Akzt-Kh7ozJKWZ z|G)CS1ST`kpUQPEnQTK{%^|G(#+@9&r2E167YW?Ad+ zGrK3}oO{nb_uTiJ^eAD3us%Y42@e8_zd1nhS0lYjm?2COMhPQ?^o(GhV&%i0QoWUqohX&>)R=xuts20Yz?= z^bBE=FiIF9tZ$|~!WvGOC5#Z(H_<=g zK|l#_4p73YkzOUt5GDzugb~8}MwNF6P~fj}k@*>(8Wo!WvJLRfzWa^5;pf5gs6ami#K|8NwuClrTbAzku=qjeI~OpY$4Gl`un?B#aV92nkn#v?gjK>BK=EG! z6#p60lY~*i2w{DI@(62$Rl*rSBM;EXBRxqNC5#Z(`zepGMpz}x5GDzugb~7eAN>>7 z2&;rMfJPrcqYvpx!YJW7KylXvDDEPp*Lx|Sutsi~_ufJR@^>)q<_5TNiE0EItKdX2D3m?2COMhPQ? z^#t`LtPxfTX8Qu_x(C!YE;cu>NGqBdift2{VLA!YE;cuzo)M6V?c;gc-tJfTC9#Q1nWY z9wm$r*3VOUhXBR>0-(5`C%r~kCCm^e38RD&!upe_A7PEKN|+%`5)J@LxG_KpH%fYh zu>M5KBdift2{VLA!YE;c@bI}Re-TjR*8!!R&J)%MtArWCU4X`ZfX05LM+qZ@^(Rms zVU4g#m?2COMhPQ?^~bCKLx9HqfX4o$*9fbG8Nwvt0HCoSps^q6>qze+Jwkf@9O_G0 zBdii;2$O^ZfJQ!`kxzPruzoh>5gr5-_j7>azD9bLFhiIm8~`-(0gZgpBZT#{Xdggv zw*V;a=1D(D`W)#s(yN3S!d-ykKMg4UlcYxpBZT$GF+PMf!UKTE!v8Fw_^*IABl}Q9PL=cwVUlnF(C80n^e25C>0P8pNI(2o z#s|>E2k;n)4`Ge4N|+(s1t{U90mWaE^a0Xiq(@0#M|v0O5z-Hz&hPBKyhCJ6!#g@lY|50$H_8p1; z4CzV2C}D)K{s_t=tPvgn6gjhiBBx6F4Cy7(GoNp{M_3~~0BFJk zG~tmxLwbqy4CzV2C}D)K{xHfXJP0Uq=Kw`+jr0Sg&yrpxJwv#Q{51JV(g#S7ksc*I zLRjz8@DBk>xC?+1?mX!=!YW~gFi99Cj1blz%J>l02&;q{!X#moFhW@WGx{g25mpH^ zgh|3EVT7>$5c(%P2x#mHXzWRPl`un?B#aWS0~CK^=LU{Nj$^$g=0F6At8ex?% zL%0i2;+F;#e@W7#gb~8}iIhip5K#Qh0gAsG=~coEVUjRPxDL?R8&Ld3NUxtj`Gf}n zjeI~OpY$qWhA>Gu04VNbfZ{$%dW5ikJmnMC2&;q{!X#moFhW>Aj{XU2gjK=};VwYY zBMm5eBuS4FMhNQ{< zg6}mO6+cY;&CgVP-D%3N(ETRjui2#hXA>Wx|928!T(A6}5`Rll@g8g{m+#K4ieE=O zL-}_Szj{ddCqG8r_hCbk=sQCE4je8Q{8r*Gz+qa!?;(C2^*jG`^?y0}Q^cnk{>O>$ zqP)9_|Mv!!cam&)#P@54f9{!ze~0`M@lTO|kofNzpL>XZf%fToocbT8{|kuE)82cD zKVqAP|9avHYygz_-9}vQsTBMW@&9Ce7Ks-a-;>VL@IJ)!=pnw9>5(D6jrzWu_;YCg zE7 z@l)ylHsZI_9(NMIn*M)EJjwXqh|B8ayNT}SiT^F-|CIO~?Xm93>VAOwjSwGVd}oRO zn(>uG@A5tNag6Vj#&?YAcRu+Kwe=-_E5keasp|h_Gx%^hPRFQ zwRAs2T<+l&d(9HBFn_$8_~WSGJn_A>&rgY;Y5VWj@Ghag2Z^tv`+JCgo%P+wfVzJ^ z<2yxsGxe_#|2h5NP5d_cKWAyt$Jcf5G@qea$K2Lm)ZBOC{ z$&Wrw!}~nbcN_6rnEz&o_tSpwBz`vaSs>m^d)-U?hm7yJPgnUbV|+%4KZNe9#NSBw zw-J9K%jcqOl*e~2^*coV!zizGfyzID@wt`whZ)~P#DB^7947v0#%JAy>i-?IXNLGY zDeslUExNyr_=_m-7sN;CKlTij*G2ox5dRI+cb51k8Q(eL$5G$I#P6kj_C8bP9Y^_d z#M89L-NYYBJSH7ye3O))Ccc~b;{fq_%0Ec_?F?TI!^!tk#&?AHM;QKxi2p0?e;4rs zl=qko>i;XW&vnEfVCPTbLo9#y5TB*JH*Hk^vf*0F?}v!LlJUEX`1#cLF`Lx=RSfT9 z;y<$cAH?5l+ne|Wl(#_qG={$}rSiV;9NdSYRjaW_B_nt>GW$k@Mm{x)`+rWXMxvwY z{(%^u3cvbN<^KxfYvcYcia#IYO~Gp~QT*`#3O;W(_Y38k!-{AAQu)8yrFe|)KS%dz z90rhYBi+x_{l8wQ?$dPt^^JuE1hVK6Y@fUplQR@CY3}ntYrW)yzfe*fm zeNtG)O+$h`^$>guGjEh@GpvYouK*cXJ1o1^I7F*i7(<1fPDY7 zK>z=)_y@kO_}qil|ATG(H_HFSSCl`CLm%>;ME{X5DgI09Q@Tg-++B2k8VV`C3nvxN zz|Qi$7XvwoU+gT!zj3~{e|KESmzZ}rQ_n~_guRlorzwSrGf35tl)fJzY16%lh_R+P%)&EfWQKnav`p1}F z$5HGGG+D84W)c^O?XOa4!ME#>ouV-(ff2@Packpb*=V<@Qn+2~%>X-+T z?*bf(7Cg=TI?VDo%kuV4mcJ6qS3l*aiQhu`HR7jYe$(WS*Q-6RM}23?hfFl!vrwN2 zULu~Q{0z(6_KnIP z7fIk!JmJgXG5P*Gqx#OVeyJJ$d}NOHc~?Z-&qwNv{{#M@`pBi8^1bj?iZ8rcQ^QHH_T7-b(Q~gx=)gSBHhms zzmfT^cCN~+Q2#3P%dymdk?x;`_E_{wlAoB-_$EK8{@-zp;&a3wNO`lw51d8!pHlbl z`K`*Eqdk5``;-{p5754{UMOFlc#QewI_A$V)~By${#=L}=aC1bG`t1!-*k@RvrLbV zpj=9PqCZo6-TO$jZ<^t~)y5g#mB%Vy9`lp$y^Mbi?&Q0j_KuN%8u_tPm46BK>0llEA=QStj|kFJ*}{#k~f zxk>TOk5_wCiQj_tKan4MoATwHk(8&|$7+1fdZyxY#7D?qL_I0r7341_75_EEnmokO_}^lw|Jp~?{pC#G~+Ao!V9qY^a`xO5lmaq9SmG>F6I}%>{QtD^N=N0PyeDqI^ef~!E*>auYwF8P@ ziq5<6qZpUScbCb3sNb(q+@d`*(~AFw_M2a?_**t;{JMz0>;mF!Uv6jm)ZeT6yeq5W zN1v{oxg;`%=SA7%cD(f`}+^m@4Fmz%z* z`p=!J_!ZPY{ZPf;iZW`GO6-1Hz_~I@RG#uc|7?ztS#SdY>(!zGic;awnug1FHNcYMI64D z?=ke>^#aAO_@?SN&-t+Os})Z&eczf?{|ijNbFWpr_B{3f3ARrQS1F!-KHZZ)|4hXf z$$$I<1+PXTKiB*+PxmFZ=fB!d`NY3)lDePkRrfXG(JPLT=tSbMyQJ4F$Jbx{E5+q; zM)|65RlLOhX`T2S-A~bdjP9f4*XaIbSI|G*e`-+i82eM_mKffo>h~h{N0LRwPiFee zEhv6>QQar${%7pZmFWH_7b<^VF5JNP(S3rWePRFj>dz=X`vkRj@w1A@UaRr_82Jlx zihr2nt?1iS-t~)gf3os#vF9hQQv7(E&-g#%cy*s)`hAe;6D9u33xq!(=^9skevV=% z`j&8sihN^qzsU5wAVdBqG`y!gPRmQJCa!S@+L?;a*9;nYA@Mr#81cy4m474sC*|T0 zeE*JglJM)Se}+u|pc+|Z|Kzr)^5;(0`eBmptE^wXa+>lpUCKY#cK=(A-ydK%kzZx| zIBdI*sQY)Gq5Q5B6`!U1*#|0q2>l}QALaZ(l8p6<@qu^_yV&Exb?T|HH#{f4%BA#PpbF zf9%!dM_FE9N&D13t@2*|5yt<$YX5T%Qr_cKe)a>3%Vm4={SU_VFR9R=$nI>%{+Sv+|SgFzm=A{V#I-kY;&V_=NgDo%kZl z(_W5u7T7*r{RxqW^@NXWe7?C$Gg_#^AVb#Vzl=G&Zl&tA0%Ie{8`%j z9O8B2H_dB!Nwyz%d`j_ZzlMJg`L#vG&m^8^f8f#+)%_geCEx$cs=l=|6u;$JikDuf z_+8|uql*9CGnBsoIr4p%_E>re`{y)l>{m1gXo_Oug6u;QU zAFTKOaE#E$?iJ z`w06ZpJ4u}qF*oHEtFS_8WeYxs=OHS_fp;>z3G!u(f5y&&H!SU=A(y&iqG zhCj>ntQdKyZ#n;Xg_(au|Ag}&XHcIg^Z(f_pJ|rI&-_N^rK$f5eyeyF>)#6Pk*5Fe z{9gH0>ic&3?|PBeSC`U%jQz9Im>!Go*Z9maJ!UEISM0xK*nfNYFGW83ACFe|k2UoH z$}9Ez5Yumw@jnAl+?QBi{FwFWJni=rGk*_xAJz2vG}2wtFGhRL5bruu!~2JyX#8sT zF+GVd;PF`b-nmKn3*^f^xgxL1`RG$v-WDnE5YwycES3LRzx7QxpeT*150&dK*3_J1<0Z(cxq z);M4IHuis_3er7USt0G$tx6((*Fj^pJn=g5B4(g{ea59;cbe?u2A_?)OipM^q{P{aHzMo`&Zh`j65UOZls<*ZBU{#y2YeRQgX|uKXV$d~sjuQvN@q z-4pq-zf=5r#&@3mg*x*`2KiRLML09@B`)V|1Ycl(WXB=JyCTTQ_}+Inan2_`@LP(f zIp6mU;<2x2_<#5}<(D|0H~p`Q&;3aG!~ag4{nht>Q}M1Y4etv2&)i^wkNoYol^^4H z>jdV9Io6-gK|N;T!}{U-wbvgMig)3$6!{*7@+|JFdzGL2hT@q!mETYP!X+yIH8;~g$4@^iD?U&6pU0)PqEGb0 z>i_n?QT?hPQT(#)g3m`vA5{FcPf>i9_~%$3RoS1P`A_w~$o6FDuNAMIrT#B`iiWo! z7dhg4!<6#Vzfyfqd@}VpPx-H7{Tux=#jEdRd~a6#=9um;nVnU<{8Yv3uT%V6HOjw6 z@i)JV`aECpk0hwiixmIsxZ+(mDE{3MaX*hsLp6Uqs$cOa@h|Qq|Ix}nrk8x;@7bbw z>^4oWC!MEwn)tcoFLJ*6Jx@}87xxF$hA5Bw8O|g>N&I-5k9f-W(kH6>WX+(FzZ#~0 z?svG0`Xz~PzgYS6oS*#axr*0`cVD3R!i_5LJ5N`#Xml{!G(vK`y+-C-+&%{;2vtDsIJ9UNW!vF$u*>oFDx-+)H{bJVyDm z;EVs*vo$@B?^b-_EsCEKQ}(5 zr~b2jiXZT|i`J3gs+)KdIgI~0$we$G>$$f)uy`j4{wzlZ+QUs8RanpXcY;&;%0 z>?_KD>!&II_lkeUnKwi*D4+(elq#d-)sH2mH9Ku{CWfH zqa^Xa2j8R@^Y=aA3qDKyYgaJ+vF<6~Ni2``Qx!jf{3O>yFC0|&nHlBZcsbL9`#tVp z`KS{AJ^V{}RgOn{Az$!1`}5u8M=Ae#kT3i$%6~uP3%^x<>qs+tmFc`#YyT zS@AB`f1AmlV}1Tll)pgyf9&wNJ~(BE&-!tY@vCz^uf+I8SYKX_@+$VIvH$Zx){pb7 zA0P7;(Fgqrxj-D>_9wADyF&4sSYOmRKl%vPr%~3Y*B`I`tM5~Le2xAytbe|4*Eg(h zKS=*+;xA$N^N-W;UdH}V>9LB(&sTkttdGv!r{Q(|O3TZuSRd7Yr}&FlpU<&A{~qK^ z`Hc=J|MK&=zm)aUnXFIh9KT$VP!%FM>q|bW;deb)!#kesVdkeQ zZ^NsVUuXaF@U-H|Un~DA*w55w4^;pEP4}69Q~r6|g^&Eg^xpFn#kgjb?I&%%1F37>fXaj-Y|^TZ!3>$SjZ#Gm|Iqz~{Y@o!?kvEVU= z_f5%<;yXUxwjrp#5fv&(l6p z#`jX%Z;tq}^aK+%HtHTTZ-Q-;e%g)uHyS8{lxuWDE=3s5Af2n6~9a313XIii?FBhPklB> zdV)Vs{4_~V;E`SG|JTPW9wq)jNl);*=>C0@p1_mOQuptY^aNg~`*S5d#Xb4+lAgfl z$^Qk$=@NdD_$EnD;8o(!r2VQ4@BOr2o$}76{W7%Qt+Zbk?e|c|zef9gj`pk39-pB7 z7U};Tv|p6=J44bF`Ycf1?<_{e*SlYdH1{U4$|-V3_O8z=q{%B#Nk0Y<=2 zPgDQZ8x+48^Dfd}rODs7SN*4Huk&tH{87X=|Gm0j6xu-|;NpgMn#b^fw?;?K-|2x(1Axz)d-zt97 zn<$U`myQ8P{SO4d%Q@lj{{eP+WEb;H-+I(dxhcCB@%#K=BCqAI+)%d8SADMT&1H9=}E1*M6Y>AAhRiY2tUhQSlP- zspkPd5%n+2WA96WBmRHG`sH`buXBHXtSKM2ouU4tHvWFatHe+Lu;L3`za5P#9y{?E zv*t)^}fhnYLeJtY0o5 zzL)q5r2av9o!9Ig$xSf4k7<0i5>Fmf{NGs~UO4|BC$ikEr{X z5s%2k9==CHU;Sm!$d9k{;y-$U7k>!!b-PD$d+=VLyq`bE%fH#je>u{_E$<8kyG|ELZ;{Gw0aXZdh(?3Q;D?BT{EzW9B_r{76edELLX@Au))_TgK6_+MV@ zjqk^O>G=d7{x+X~Z142gYs}~V7e0IagU_BPy~6ANCqDn@`Rw_oi;wYycau;5XZqZK z!57}6eEi?}@JIO4;Abu_Yd&J|1&;&pW`bZ zU-jW9`_kh)pZ!OD$CuuNKK{5bJwEVUZ+ac_rN_^G{0CTG_mA-5-}kw{)))U9eD?YM zFTL`<=EI-n!!ca<H~QrLFV+d&;oa)PPxpnl|7@@PTlabK zDPMU``pU!4CcW-|^hz)ODxdpT7QOt(-R#9JUw!%a(_a3UeDVF^C0>5nS0BC6=l`3} z^17c(dGSkOJh%ONeC0)!CEffI#^-MQHlO}K@%ewP5C4P@|BA1?eBH<2;45Fh^yS}A zPk8k^=QUpZJ(xdm>$~8?f91nReE17}_}}{UyBFP5xBTOM`Qt{Pe%Jc&*ZJ^g`{MgX zU;4h)XRr78__z7w-R-Nd-|#}OzUlp5{9kH|k5TdwlY) z_tn2~pZ;h0^m(FB|Ef=)FZkIF9K7PcquH4=`RV?TB_KarByHn|{x#vf$ z^!`nS+?2JkSlS=4Ch`+Gf$9Atd&~Lkn3co-Q+eyEyb|}9^43n!fmv53;yZT?tsgJ! znY1=!b5~uHEst3n)0gesx;>Gd>Kn;VR*L1+aKf4~-`I@t6Lb7*E|yz(5$Eas8#hl+ z=B5h8No#xlnv@}>%4O?{YG5u?dGi0S>? z3Y97G?ZlM7HG;!a5 zoxjt$#{N zRbdPX)g%GpVdja4m}j_OGtAg{I*@Ug=i(+b$v*k=(9FJ6Zr_fXlsa})V*VT`Fwe*P z$MQ_fsiQMj$$u!|)XUvYAL|6pMf)!zilc9YnK$clH5 zNp0FxgeGy{K2%UrGy~f8X3Kk~B}j{WSdEWbNDJwAoDWh$E+&sHi;tl1s2^P5#EOi0zMm7-Ocv``~9^A;EV;0AeSGCLk{ z)n7DqawRcP*;~BEny%!_qETToKW2>;b6U$a_tj&3?Y3K?3L3lY)O0x?@WC9|4NVz8 zSEkk68O~FKFQ>W=l@pIinbt%ZtiU2Bd2CfQZzN)tr8&<+9iK-znV2X}4s9Ear&7W* z4XHJrpWHLG*W=%xvaL&PsYwDS`pWq|XpQq_*j}2BaZ4(rij^xh^Ahj#B(Bxc;bgvy zcFQi_Z1-$*$QjsKm-FL! zRB}vVUw-#-9nbN8&FR^3X~Fkfs7T?og0|Wge~RrL7C@tD$w&`^4KZ3ot0s92N5Nhd7FV}DMEK|Pb%i6GbR z8`|TEHij3260KY|x3_a?@tCQ?q;j7?CvO!5Cidi=_99HAV~!Tpt>pv93Z^?hIYy-%X)RLI<=o$NhO%r?%}O%{P36MAysbh*X1K?gwVXa+Dz&8e z!0bYNfF0^w5d*vObZ)AJazWo14(SvKjBa=1>@;swREZwcEBmtLf^;jaV%eIu`#dhU zJtL^(mUQV)w1nvlqLV5UQ>N~RaMUWftDJba<^{sWyJKd&+dM@AkAMIVxf9w8bo;pmDszb{r7gt-}>mre^rwbi$y`*@$hUv6Lt`JYX@FLG{)q$Q_L! z1CjL1P`IZOjUiX`Rf=Kjv+&=V^*g;8k zEYRB+u98e&dl(}1%0{oxTPMdca4$?1vg7Db5A95EX0~)DIU1D%I=Y@&z2RQzsHS#q z#gF0j)>sPJVhetl-&NZ7Pk# zt<kdpQ!32^YG=+#ia4tMO32c{TtONdx>x}np!gY ziQZL>{>YJyejwie*CUKX4a8Y!OQ&u>rbtOjbARtt=*zGOj<3jEex~5s%T16{U?69dBIzWMa|1 zswMyaN{ht(8H+0$J)A^0rV`Ec5mrNX7p=x5iFd9rb=kP9+mo{PNd=eMvSd#RgNo2T zlvIyfmM#ZaO|(0SP|eJVq|y!T#Lx2Cb=)R=soFul6;KJC2i8{QBStUZ0T zF^&xm=;G~zbMx zge-)GxQcTzO%}|8>>inu50dS|9OU#(6vwb>Dag>36o;-mzXr0?Q^h^y?AXBNoTGCb zIPtI}>#vcUyKFIK)2^px<*3c6tbM8G0X6niXz3Z+;@hI;>hG^q40_egMJ#8ksA+VT z6ylv8T7ewjlu%Pi@iq)R*1{|y{@s&zPA^%1pelDXu`>;}E||S~vTa1R1I2qC$|M%j z^Od13+a_YEZ4+_)OWnlR56585?fSsoLw|YvifjpOT!FJTy(`UCo&8oLmkg`${o%?tB)Oy)o z00`NDX0KEtycT@fFJuC>Yy^do1-Lfv3zqIO8qu_lEtU*>RVzc(>8Rkg*H1;nlS% z8Ua8He@uHUt5O;-Oj)DTyRmSN(Oip2$ohp_qSvbe})TV(6$_=X9|Ht-#8Ljt5)|5~wXj)x_mE7;xGMCp z?R4(826c_u!sgR7Ahc&Kgm$lm(8O8@jjx5!*ouTUa-6+8YYlcbI@ux++LAkxH!f=h zCg?h31t#dotiS}xT!9H;sI3}{evXsIa*RU?#5u6HHR#k?JaJgzOCJu~2_)>;PKYb_ z(npYOmOg@Ho~#RDFz@)+DVn=%F^$`s+-+|DWwbU@ntQz2V93#_nQKabiqA9winz3471>FQv+L19Qlp9 z-yt=sW!Ir#hYL{&_ZeR|!+W^f;WPw>tkK&lHXM1^=mUIvQO&myA8_X^#0NNG%}t21 z?&c=Q)|K{vY-goCz_C}_0}LJCIw3S&X%Bd`E#Ys$98uW(G>XG6<}L+?=os4e-lAD+ z*OR@H>c9J=?Ix;uKy9@Hvfe^cU_6nG&)NP@h!hMi_g+>{LAmn zB!w)W2ij{{eqVZLtTkd;zOnr7P4HC-&4j$_Avja8t7jr- z`F)j)0C_Iu0{f&7$bDqA+-JXyRTf_Gz-~Z(`d|ylh zOLu`eOV>sO|2mwm;d>?8R$k7uf0&#$R>!@a?v!6UUC;Jv*M`(a8Ed)b?v-oDI$%Bj z#sKd!0Y=`a=9}cxpsavu%a9gWpK))-&{f^=D7Cy?#ix3}yxvg#DHIlLP=vke(~)nN z5nC=MhsWJryZUS$rh7;(Xtf&b4YM)T-{#iOjyecVXDyKJR@QKHA$NsQTGNGGuWlVj z*Xc#zhv}zqA$K*z2NI?2OtJjT5D3d&X7%63WasMT)adp?uGc7b2+Lf^^$Ix}7IM8B zp;~UWkQ?rJg$udiLK_`MXZ2H-O2ZwmKm)hXMwnc~Snfh@xONS(PE=&gHQezEqT&|X z2$N9>JEMfdc)lZGAycM7N4i25@N2J-1sQ=AGTTXGmNXcOJ&hJzA+vouaSWSy3<`|9 zCmw@rUFQnOw%56WjArXxwoCUcVK6v5dM#!N+x66#xX0p`9lB$j@PL{PM%tcPY#z`G z7QdP*-{>y3v-q_#F=p{=B_d=MYZ?CCJu_$hN`J^I)+$I^1t}{P(YgO+`fL=2FpnYi z+oo8c5_GT61i2xLEyhuL{}9enTex&>yfBK}Q>V>U>AbvrI6t+qh-1MsQ*P=-9;&(V zeYbAHU>xNZTmD>ie}Fb^Q$CQ$Dw1uHGj15_%wB5(<>@g9w0!^-yP-e*7^H)D;v znJRREn-Llk())MldkGs63H>Q$>k@OS*jhg})}U>lo*2d9^UY`(P5DdVDF(daFd>*L zl{d;I-y$Q0TEarX+@R;WL9?z5mXX%iLZ|x7P0_~hMVEK(yAe&vE?3})_s;xozeY>k zYu4yQ%9UkneNj-al;UM-sGTi)@ab;v!*5=byF0vpYjz>ok&cdi1CIuSe^>Q%RXei-um^{%Eyod4E$U znKCRjmu;(-_c3*%RR=}IE*@N z5k^DH4vUPfbPQw~UyOHf1-w3u;ap4;*p4T@*^j~q0v#O(MgK>JTWO&>#GUz)IMU%% zVyrlU#~1X)4MV-$zelg(*1dDjp?bINIplabJGqAswV3G52f(;;-QSlxWg@Ir@j?Kn!GNy*92?7v<2g&i{=t5(Qk7Z zH_l6kaT}gz(axq(8>zKXdToNZN2eqTRZfL(D;BSsE=8n=KPE7&vl;fFNGhIi?$N>v zBSt`O0#Ni7=<3^ASCq4$I0IaH)J9$jkcYI2e)=AVZds$x&x-@gLf;rK1X$&%Nx|g( zGPAo+o*{Fxm(AH-&U>jQ!wkCIX>d3AwF1SD4x_hvA1Na4|{`ZimLWX6run;7s)Sy2-0@vs?QrB?c`^ zu4vL50}v^zJ7HZ@z>_tVe15EwnlPVBUT25D`Ya4<@wgK1Kbu&F!Isj_FRnx7vZa#T zZg1*ts^=t+JzWu;&&J45ULSBC_Cn+YckNbaUlsc_11(boN}pri?rxERdSV4K;{76{8(Gti3a)%a8iqVX*iMe=!P8!2DUVV7 zuNU^?-K$a-FS;N^j0=`yKU9{Ly@lO)%&ahzQjd+>rpVbu~Ci)_n5W-Mv{}3Sio#~JBsaTz`hn`$m4O)8iG*B zKqf5bMKCRBCpR-z%R6BHkC*`OZ3PT4h+=@Qi(GB*%^IC-a=`BQcyyGKFo~53Jf(z32FdalrI6=L-P`WXi-l4=sIA(-FJ$Q* z_?}0HFs-`kB5^Xesg~wo5&{iZzU2#i&3%P+348%)Efy4^`oufZr>U=?J^`oHN9pqH zOYSNwxA!VM)ma!fEuTMK@jB+}9~_e>ip<>o^wjR{(r`%FruSaLYmnByLU{@|M1|`U zJZ;?U#Zhk_Yg?S}Y-d|i+Fm{4W2y2N^)i-hrw zO~5POMVtg(UlAbwX+ays{DPPcGcRa8g}-6{6y6nGUujV6$(j7rdJNSAbae7V=@(}! zUb?-8x4B%LXh<;&>KrKes8~Xm@kE1aOITk?O-hv_OU_ig!Mbc@^FV_xvoxEh<=t6% z`P?|JRC3wL-4a(>%ffZV@+3Kyw1Tl;KRGsxey1>unl_>~vat~z4jG4-_n0W|Gd^^> zMP@kU4S2m!L?n^ErwM(`>>F@mBtmn~sxQ!Ws@)26O~A^l zut?WwR9uF}sL=iAUEonp8|x-5pQc$&Exr2vQES$6!R~&wS<6McPBrVG_}E3Jjbv`Q zaCf9sUX#VUPLw|q3wUn5v=6yc-)8o9Blrfn>88lK&`@P~dQ>*k2qkU5&@4+vLXfu{ z*g3RGW`mPuV;eW)mC-3IqBs=cIEcNJWji0@UbsuJW>_4fGwZU+YcMZqLEq^K6RhnG zoyP50^|E6ymY+Z`Y24srl_I8EC&vWC;+X8ZLYH<^aT<3dOYg(%#Y_~Zr>x>`%PjRp zELkxU*I4GWHk;p`zEra(v{TNKfOw`khAMVkw6CMZDUO8^JYBIL9epf0?U~Gv$+duX z&n9G^r!jLV$J9o#an*kNcxc3u``dPHE8swZ%q`*;eH;+lg|{3XqMT00MapucKfG2f zJg;qCgBzV!s*;2Vy;oT!*ZhR((uy@@aIzzplRdQ18o2C=8_w>_YaiMnt&iqowyIi};Hmmyy!3(zJz2@KHl{E0UGI>vAlF%uEb4B)vAmgHv-3&g8W9Oka))g9 zE9Qi6zhM*4?ZrmQsd5%}9?GQ>`Kwd8Ru1~Gl!*+G+L_zA)#I+C%NDVzSQbW86PR34 zt@*?C5R$#J)$GY-%)Kw)Q;)3OI{ui+JBtDhV(Z|{OvSsMe;t}^fUz+gOw#Bhy?Nf7rcMLYg*uDK(BI27EGKX)!@ zU&LB06NIbZ{$!RnySn2QzJGg7y8lbRoIRexYuQq~|JY)-#FOBdNXOBp;#K*zkDoL1 z<@{K)o%d~}=g1nTrxQ*5um{-=t&P!k%dPoGT@1q_DYO;tkUv`DX(wPC@o70raQ zy+M^#lp0?XsiLdf9Ohk_Rn~efAY?& zp4hq7(?+zpje6_8U?bwp&>*#zil$uUyX>ve##586(VmvX7(0$__PUK)&u~pl<7IS> zw1jP*kHQaG7BP9iSyC}GLul~~brRc)hOmN?Hm`CG??g-CSj^dF|0k4-%;5mXiqiHO zGoheiYg;Q5Zlnnv>-`B9L9g-J%6Tg@TlkH4i6&2%wJx_`g@iCzpHl3-H%7FwOekDJYs4yPXlamG7f1=*Y~aSdHV(SD9r6gx$>>^ z{86ra>zuzf${&e+tSq#+0oV5$LkfpvQo~!8QKugfb&aLNv~{hQtJ1EST%9$?G%AMH z#S&(=njGqH^B!R7A;)k5z?AI06`}buWP;Lz4N5paRl!Cq;bBi{sz-jL_TgR^hOQjh zLXj{#V#>M!2dAd;W!$2QIg$;wuT*Nsa7sJRfr;A$W_-=gqw&I3c{@h-d|DfuHDvt> z(J(PCPL361Q*m~DXji3-`Q)+qV4-4-`!z#!QoV*KZ*{;c_E*W_EF6}ym6z?Z8s#0s zn;Snjg~!545$`V2Rk5#0;$|ksyq0PyCf*RP9JbUIkYAkiH3HkWuoxJZaaN{e(#wpg z>?7cqI+YPt=)>4lruldXbAw1LbHNU-NS(}=3puj`%bvmYKi?-?tM;>#invC9%|p}xU8QuXaARYI3Kqw39~O4R+RHr1b@(&-BMUYu#@%6;bKn7Z z9`Y}iv7=Z%6aDq!sqbV>cUfBdjEOahM#dae)ij!=@gdnW=f6Ni2J8Nai8Jq};Smc3 zb0m&@h@8-8VdU(QZRhHc9rJQci@AMM%F=*Lf_wJQqXQ-z=(kOn{qshx z{A3o#1FXVssa9l$6P1cxZ8_<*ioLXZ1pvD30RVd<`EXPy`9CVOPD+{BsL zdgU)WVRPoh1WqihvQ(VA_Dj7#&SCgsWzK;dxlHZQMz+Y+R}JfixUqtIcQNd z6hCf$7qILZi7zOypIkH8-)qtWI zL(f2K&c2p4XHatEy^dBd&vho-H#!T~+SJTzq_tbNzog-GmYhJq2&nBVR+`^|l0su? zYBzrO+zEWuSkxR0b`QQn;+ z;bKe%T~VCckkD?NLPVj;s^H?-9PS349>tz5T%8i$qnA$li03YRQyhZ3b-K-n&c5Le z(>3A8fY1dEEl*3`T!xEQq#JJ!TPIL8wk^lAkFDD$N#|;fEX~6$#veVEYqF)(PCIP7 znX-owGDIIj(-K%*6wL$GdOT(}U+ykrHxqjD&En&G^+Sd`c3YOx!(f|6a^pHvlIo`_ zGTF1cgvP^>8%(ffXGIf*iY$eyKCTv&gZf09PotQ|)s&L$V z{2SC>)Zo4%UjO0fY{c4uVV7x%Fc&wLpFxqs4lS8h+p?=tG9zH5&`6?5P7X%JJ^U9{ zU4Q7txnjc}l%18~a61~ZKo(3%l-GOYsLat+%Xpg};~K}+I}3+YG0EYMBvkb#nLHTL zEV+(^%FU8z^^zt$en(B*8Y$w+nIIlzYOhjy1Y>n15+YD8FrZwboI7ICuuo4pk!oW*Osm~&%ToM^Y*Jg6Mk~w_0c*mn zZ5XuGc9CVN?=mQ=TKAuK&y#ZQZ_T_q{n2UDYOBeLQ*GI{X|;D`S=w|^1ng|zMiRC< z9=)b&V(5#7vh~)ODvQyP{9bY^7uMcn9aoOTr8i^Px5mn1hp*LcgXq*Icd>;QHgye2LtI$=MR$RXS#;w*)@1(+(gwRnh)M;k{-8O6D&Z zmv)5tW;Fvc{Byr8@0d3e!*=KapRF(XksTVUJ0?z=?8#uf0>?Sy!Rdmqp@Mmm)xSjt z)%=W#Y)43J!Y4l@W)RSB%r07S%vLKVsDX~K=1!WdRuo(U@3*)> zhgFY`k$EIy6e#u1TqcVPGzlpQ=mwe%A`RD2)~v&}T@F$mm4gs{F_G7%uHy0t>DjC% zT2fVC2YdPY;&C9|U$DZOvu#Z~uxW=ny60H5GT-OVn;p%zgc`X;-3)K}TML&Wbj3ba{F*C@DVaa^O|RuDU#K3h@Y zc2cm7*mV-z3Sy_zaYrn6odk#2by7RDVQZb_TXy7+B5gXcO=E^}$(D_?ifeD7)zV28 z7J?Tu+xw>com|_gcRwj6BX?IXb?91n-aDb}il_qfKp4YtpZ|st@M~y>Faub!gY-vcg6>gF9mNIt5 zoh^Ewmt0&Td;So2_tjN;fe`3&?U-|=l{JQEU*v2QjItf4s)mM#MI62ftV_skb1|KH z4bF;tcLO4+?57kU2wSOx`}16{7o<|T@u6*^Wau%%c*?n^4{rMORyT8F-~FYHsKH&p zdQs8Qp%>CAToI>^i^nFiSLLlKd6Tehi|a$NZi9Cs&uufA9u-wrMe>rO?1~-FlaG(1 z#9P_&9y}I=#fElFAjdq+VStM4^UR~@86hYkyJU4D)GQr`=r$5g1s%z-I}1#g|GLNW zyRmWEJ+T#>*Rm&RcEXxiuQL@2RySFOoh-#QLtEr>`)clqBWL>P$OoO;(pi|UjTH?G z%8bJLgicZ%QR{UYtSc7I&`E!+ro`2jr6afH88|GaqL+@7mM5!&tS&+6AZTraa&_<5 zb4;;BfPq=>RoI%aMe3+#^@cCN_|RGh*pGuCA#XXMcJpqLX!0-=-6i6U2Y5E~7K|3} z?}+F`d>H2r@?+Z)Z9Umzyd>7|*;CH%$xh{$_M>4mJhW8HIF30AE$y~@czVJbzIr-~ zC#_q*I3%JQN0fTT3b`py|#1T+O36~ES zb!UUDmj?RtL?djJtgyfvt4-4Zf}A)E@a!9%f-m$4Jq+{^EjQA9WUpzWBcVD zcgCa*H$zLiX}EB-QGIa%4per|j|5PWF8fbXDipBvmwDXUiC&rZyVyo@hYlUDcg0IDp%Nzi_e`apS(NH+ELS3(cFO$ zy4PhXOJF3-tr<&LB0PxY#HNj)%=3wuJ#DIsD>F8CWTc=qTv|#iN3`6M^GM!vp8lsW zo9fW9c+c!;Q}L#^;+=49xW&|05l@j?T4gkfI?XQ*8_#vHtI*7k2Gm5UW}cWr%L=H` zW$}2!Okb1O*dBbkHTTfcX7Si)5zm2MlgIH`Q`0ug=E{^F)$FOl7GAY`*esn|cx-9* z7XBPveO75-*X4V(6T&n7Z|1a`+tl{s3k@^5em{wXt3%(U`){iDUd$@z_6}sHr;2;Z z*|B(BHu+}D1-VvBI#P|ktfsHe{lMjPi_}_Q>I&{eqae8PRkOf*pbY(-lBGz`T6BT*#H1naPe8_QezUT`6FJ2X7&}dZ1!jbWA)h zG1b3sqAjQKegv?zM0qr$R5XiNO9U-9h}o~YH@eB*rrmB62gh(p+RQyeKbHMHtq}5%!SMr@4XC%>T%y$erzN^St*ub z!wPnjjpwI|lbUw!gzX=lF5szh5~OH<5|{ z*jk6O#v!bw__dL}ic%Y0tPX`HdQ0VEE|0oc<^T($)0kO9ZIZ=vLPJ>HNcER+Z5D4M zL#x?N*=Z#b9UEu2>l(=Bp4v5#tlM@w5gZ=~ShzB@Cna+x)*&drS%My*_FdS_#d!8L7Pmnj}Apueg8UP(54 zY{+Z5B=zih0B?6Lj_G{U8Als4W$CzB1eif7QrO(i+r(?X+bg=>xF)?#pG^YgNU@_} zSK?Rl!xDiu(b%zbYfClp%{?nE{p#f9sFtP?+)&*#DOYEjvyFgQ+i0IvN;Q(b$t|gE zmLROf>bSu3bbz^>JCz#V)GF3NWky>|lUu%<+Au^K_w0ByGh>(*j~bZ8oF*kM7{p4g zx7r%oy+fTHX;yP+Kd-eLN0OO2Swd1vH?*4fZHXK%MmOiw(Mv{>hHv>>Dy`#d(Hf{) zSX?zPigu`&H_N%^wCreO9NBra9vSj2+`jI@ZRe-L^n+)coUYv$TR=WO*(Y=JA7hf(Z-tC__1kzE5J?nmkg znfkY6e|0L2C&WD~0N$aG-K7Yb?nkUU_KKYztwQzgEgY~THy6Q z=%nEC+O2q+WvA>j#zv3uOt+dkn3}tp4n0Rk2UCSN)1mvw=wRydW;!H}j1Hz!Z>B^1 zi0GiTdlMbNTxOocG@NfbU8d2|X*QW2xqVqVJVcti2Wg8%2}-PnV0IZ-TdejGJC_Q_fuym?*4wIc6pd-3uZ z_7q|Ynzmc1j;oafDre>9r<_%a){4ZeifLBGE~{Y_#- z`>zqJrQG=TSG$Gax1;4!V!S)g9VxB#pmgtsbf4cL&-QLFvr+Zv;6@fS>~TWtbH_5N zoNyggmIIxp%j|7=mUBKFr#4fm1Rl|or(Ue$^puq=lylSL*|Ie{y&Esf%J|T%zg0@( zST9oIko4rG#tRF+5kEUtzTE^6=su!uPSaMNoSxH1_%Md9bxt!uxKLFFex? z=^)YxL|W1;EZOZ{_G=%MD~TIBf8A-i8qo_|eD)sOccy>U9-h&^eJcmos2%1W58^s0z zJ2GBh=053$rvdwcGq1;fT(z-Y0`Dw0t?xKD{m~evqYSeGyI>m9zXheSD|v|=f*a<{ z5+ZYmw8u;55ZjY0hj=`?TLW_mO=nqUp!2M9^qZukOwzGMq*jk{lRI8#!)&lW$GFD0 zOJo@{d9odqN#l%X8+pd@75BcL*zoGkHT_b-$d3Op?D~4#yUD>lSYglXy1S_6#d_&+p@*?z83C)uyL03DEVjd8my1;xlMZ*2 zoi1%2a`eukyANt?97@PlUghb@slr5_M@M8-v8n+Ej|?1{a9t--HV(y)Kt#Kpy>z>? z1@{Y}31rAZ&eAP_^3W)@RQn!+K=X@Z9?aOdoyW;zNDN;JVWp@=8m&yUZis1;{L2yT zuS?o_pP_MhBR*huHp-Hk>($6+j~fQwqQq86UB(QIG!X^N9|s?!l)6R70BtDV`dr2G zbw|)^sxNYjd*;J_OLBRNuC@6-?Hn zO{-S=thsd>8zzpN8X=nx1`61bm&FO!;4TDN?lA3vGw?Jm6+4c&L}WW=-CPY9#tyJr zle)BJ(1BeHs~L16fwr}A`V;3tOPJN1LhsRYt)|`CY985cPh@q-ZTfqR%^1za|=c_3iFzQG_Z5kl+h z54!6Oq4oC#-Svgg5nchFsT2rV8$>rI6C4WY$@&iX@Wi4akzTO}$5kl+j32_%f>+cS_>j|MHg3kIwXx$;SL>MjJElv0(#j$Bz zTD%drluhLmAqEZ7`h%qI5LzOH))(~J8$#>p4RO~Kq$NUV@eo=f=(Q(=)*lbLOT>b- zzQK6FP~8JTT3=s~*4-PV_4Nd4y&<$j2rV8$O9aKmgQV^dS|Wtj8;he$ZS9$vu}avj zRh-OXTf%;8Uo2pQASE#vqzwc~y&<&j5L&!H=(ndYNK1s!dV|h-LuiRGT2Ih#B1r0w Q2WjyTT5l}G9cYpN54`2|$p8QV literal 0 HcmV?d00001 diff --git a/encoding/lib/cpu/roi_align_cpu.cpp b/encoding/lib/cpu/roi_align_cpu.cpp index 41cd1c2f..08d444c0 100644 --- a/encoding/lib/cpu/roi_align_cpu.cpp +++ b/encoding/lib/cpu/roi_align_cpu.cpp @@ -377,7 +377,7 @@ void ROIAlignBackwardCompute( } // ROIAlignBackward -at::Tensor ROIAlignForwardCPU( +at::Tensor ROIAlign_Forward_CPU( const at::Tensor& input, const at::Tensor& bottom_rois, int64_t pooled_height, @@ -409,7 +409,7 @@ at::Tensor ROIAlignForwardCPU( AT_ASSERT(input.is_contiguous()); AT_ASSERT(bottom_rois.is_contiguous()); - AT_DISPATCH_FLOATING_TYPES(input.type(), "ROIAlignForwardCPU", ([&] { + AT_DISPATCH_FLOATING_TYPES(input.type(), "ROIAlign_Forward_CPU", ([&] { ROIAlignForwardCompute( output.numel(), input.data(), @@ -429,7 +429,7 @@ at::Tensor ROIAlignForwardCPU( } -at::Tensor ROIAlignBackwardCPU( +at::Tensor ROIAlign_Backward_CPU( const at::Tensor& bottom_rois, const at::Tensor& grad_output, // gradient of the output of the layer int64_t b_size, @@ -455,7 +455,7 @@ at::Tensor ROIAlignBackwardCPU( AT_ASSERT(bottom_rois.is_contiguous()); - AT_DISPATCH_FLOATING_TYPES(bottom_rois.type(), "ROIAlignBackwardCPU", ([&] { + AT_DISPATCH_FLOATING_TYPES(bottom_rois.type(), "ROIAlign_Backward_CPU", ([&] { ROIAlignBackwardCompute( grad_output.numel(), grad_output.data(), diff --git a/encoding/lib/cpu/setup.py b/encoding/lib/cpu/setup.py index 03cf8711..87f9365a 100644 --- a/encoding/lib/cpu/setup.py +++ b/encoding/lib/cpu/setup.py @@ -5,8 +5,11 @@ name='enclib_cpu', ext_modules=[ CppExtension('enclib_cpu', [ - 'roi_align.cpp', + 'operator.cpp', 'roi_align_cpu.cpp', + 'encoding_cpu.cpp', + 'syncbn_cpu.cpp', + 'nms_cpu.cpp', ]), ], cmdclass={ diff --git a/encoding/lib/cpu/syncbn_cpu.cpp b/encoding/lib/cpu/syncbn_cpu.cpp new file mode 100644 index 00000000..e512d3d8 --- /dev/null +++ b/encoding/lib/cpu/syncbn_cpu.cpp @@ -0,0 +1,60 @@ +#include +#include + +at::Tensor broadcast_to(at::Tensor v, at::Tensor x) { + if (x.ndimension() == 2) { + return v; + } else { + std::vector broadcast_size = {1, -1}; + for (int64_t i = 2; i < x.ndimension(); ++i) + broadcast_size.push_back(1); + + return v.view(broadcast_size); + } +} + +at::Tensor BatchNorm_Forward_CPU( + const at::Tensor input, + const at::Tensor mean, + const at::Tensor std, + const at::Tensor gamma, + const at::Tensor beta) { + auto output = (input - broadcast_to(mean, input)) / broadcast_to(std, input); + output = output * broadcast_to(gamma, input) + broadcast_to(beta, input); + return output; +} + +// Not implementing CPU backward for now +std::vector BatchNorm_Backward_CPU( + const at::Tensor gradoutput, + const at::Tensor input, + const at::Tensor mean, + const at::Tensor std, + const at::Tensor gamma, + const at::Tensor beta, + bool train) { + /* outputs*/ + at::Tensor gradinput = at::zeros_like(input); + at::Tensor gradgamma = at::zeros_like(gamma); + at::Tensor gradbeta = at::zeros_like(beta); + at::Tensor gradMean = at::zeros_like(mean); + at::Tensor gradStd = at::zeros_like(std); + return {gradinput, gradMean, gradStd, gradgamma, gradbeta}; +} + +std::vector Sum_Square_Forward_CPU( + const at::Tensor input) { + /* outputs */ + at::Tensor sum = input.type().tensor({input.size(1)}).zero_(); + at::Tensor square = input.type().tensor({input.size(1)}).zero_(); + return {sum, square}; +} + +at::Tensor Sum_Square_Backward_CPU( + const at::Tensor input, + const at::Tensor gradSum, + const at::Tensor gradSquare) { + /* outputs */ + at::Tensor gradInput = at::zeros_like(input); + return gradInput; +} diff --git a/encoding/lib/cpu/syncbn_cpu.o b/encoding/lib/cpu/syncbn_cpu.o new file mode 100644 index 0000000000000000000000000000000000000000..ccbf4142b0d22d2b518fab202aa45154f9d2bed0 GIT binary patch literal 125052 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�Nb3zy687UZ`)S744MOE=p^OcQjuXcNM zqrRR!i3XSs!#1d3j={R(Rpv3zRKt%hAt;&hN^$xbt1SNF~`p-PlH|g!pYO3 zPM@bdvw3@3OcXL5czfLs`OjBgdk^{MnET_i@sk5G-NgR(?kB4=X`CVHt!{qDhE z<^-!Zr01%R@zej_-tS@9>fb}`Yc6X@51T2r&^p;DPBsVXbp@*uNc$Vn5NL{M2@m_3 zYRdTd_RJbnN;GBz_jTMid5gR8&YzcK{lb@=29z_m?$XG_3W}t8u%FOJ1{^RyRN{># z12C_A$dWxJJ*v~JmH>-PWpxP%UE6(UD=^$wr6&EO)Gh&iJydwlwzHy zweO=8TM)>tLI^Hd zaWPhl&3_xmXi8U&owT%WC?^h{o%_N@lce;YlK`hh=NC ze24ut*a}U5T_Ax3_&tJr1!aDRaQrC6D9O7D@%)WHVSt>Ip1)z9jUIfp(A+Pvy3==5 zYjFR~{m18cKZR8E?;!}uMIh7O0J0L80ViSjM~wrNSEwq#B$flNfv*8K7)cit7k1fz z2TenIM&!zdbfXasqS-dX#o+i>U{!u`1Vp4AhaAe^yjHTLln2C(loru^P_g7X4#~u;wuf1tyg))Euh!+!WLz&Uk&3(6fV{+v z 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 literal 0 HcmV?d00001 diff --git a/encoding/lib/gpu/common.h b/encoding/lib/gpu/common.h index 59e9f85e..c0101bb7 100644 --- a/encoding/lib/gpu/common.h +++ b/encoding/lib/gpu/common.h @@ -77,3 +77,148 @@ static __device__ __forceinline__ Float2 warpSum(Float2 +__device__ T reduceD( + Op op, int b, int i, int k, int D) { + T sum = 0; + for (int x = threadIdx.x; x < D; x += blockDim.x) { + sum += op(b,i,k,x); + } + // sum over NumThreads within a warp + sum = warpSum(sum); + + // 'transpose', and reduce within warp again + __shared__ T shared[32]; + + __syncthreads(); + if (threadIdx.x % WARP_SIZE == 0) { + if (threadIdx.x / WARP_SIZE < 32) { + shared[threadIdx.x / WARP_SIZE] = sum; + } + } + if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { + // zero out the other entries in shared + shared[threadIdx.x] = (T) 0; + } + __syncthreads(); + if (threadIdx.x / WARP_SIZE == 0) { + sum = warpSum(shared[threadIdx.x]); + if (threadIdx.x == 0) { + shared[0] = sum; + } + } + __syncthreads(); + + // Everyone picks it up, should be broadcast into the whole gradInput + return shared[0]; +} + +template +__device__ T reduceN( + Op op, int b, int k, int d, int N) { + T sum = 0; + for (int x = threadIdx.x; x < N; x += blockDim.x) { + sum += op(b,x,k,d); + } + // sum over NumThreads within a warp + sum = warpSum(sum); + + // 'transpose', and reduce within warp again + __shared__ T shared[32]; + + __syncthreads(); + if (threadIdx.x % WARP_SIZE == 0) { + if (threadIdx.x / WARP_SIZE < 32) { + shared[threadIdx.x / WARP_SIZE] = sum; + } + } + if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { + // zero out the other entries in shared + shared[threadIdx.x] = (T) 0; + } + __syncthreads(); + if (threadIdx.x / WARP_SIZE == 0) { + sum = warpSum(shared[threadIdx.x]); + if (threadIdx.x == 0) { + shared[0] = sum; + } + } + __syncthreads(); + + // Everyone picks it up, should be broadcast into the whole gradInput + return shared[0]; +} + +template +__device__ T reduceK( + Op op, int b, int i, int d, int K) { + T sum = 0; + for (int x = threadIdx.x; x < K; x += blockDim.x) { + sum += op(b,i,x,d); + } + // sum over NumThreads within a warp + sum = warpSum(sum); + + // 'transpose', and reduce within warp again + __shared__ T shared[32]; + + __syncthreads(); + if (threadIdx.x % WARP_SIZE == 0) { + if (threadIdx.x / WARP_SIZE < 32) { + shared[threadIdx.x / WARP_SIZE] = sum; + } + } + if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { + // zero out the other entries in shared + shared[threadIdx.x] = (T) 0; + } + __syncthreads(); + if (threadIdx.x / WARP_SIZE == 0) { + sum = warpSum(shared[threadIdx.x]); + if (threadIdx.x == 0) { + shared[0] = sum; + } + } + __syncthreads(); + + // Everyone picks it up, should be broadcast into the whole gradInput + return shared[0]; +} + +template +__device__ T reduceBN( + Op op, + int k, int d, int B, int N) { + T sum = 0; + for (int batch = 0; batch < B; ++batch) { + for (int x = threadIdx.x; x < N; x += blockDim.x) { + sum += op(batch,x,k,d); + } + } + // sum over NumThreads within a warp + sum = warpSum(sum); + // 'transpose', and reduce within warp again + __shared__ T shared[32]; + + __syncthreads(); + if (threadIdx.x % WARP_SIZE == 0) { + if (threadIdx.x / WARP_SIZE < 32) { + shared[threadIdx.x / WARP_SIZE] = sum; + } + } + if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { + // zero out the other entries in shared + shared[threadIdx.x] = (T) 0; + } + __syncthreads(); + if (threadIdx.x / WARP_SIZE == 0) { + sum = warpSum(shared[threadIdx.x]); + if (threadIdx.x == 0) { + shared[0] = sum; + } + } + __syncthreads(); + + // Everyone picks it up, should be broadcast into the whole gradInput + return shared[0]; +} diff --git a/encoding/lib/gpu/encoding_kernel.cu b/encoding/lib/gpu/encoding_kernel.cu index cc97b2f8..b9ed859b 100644 --- a/encoding/lib/gpu/encoding_kernel.cu +++ b/encoding/lib/gpu/encoding_kernel.cu @@ -1,5 +1,6 @@ -#include #include +#include +#include #include "common.h" #include "device_tensor.h" @@ -64,153 +65,6 @@ struct SL2GradXOp { DeviceTensor S; }; - -template -__device__ T reduceN( - Op op, int b, int k, int d, int N) { - T sum = 0; - for (int x = threadIdx.x; x < N; x += blockDim.x) { - sum += op(b,x,k,d); - } - // sum over NumThreads within a warp - sum = warpSum(sum); - - // 'transpose', and reduce within warp again - __shared__ T shared[32]; - - __syncthreads(); - if (threadIdx.x % WARP_SIZE == 0) { - if (threadIdx.x / WARP_SIZE < 32) { - shared[threadIdx.x / WARP_SIZE] = sum; - } - } - if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { - // zero out the other entries in shared - shared[threadIdx.x] = (T) 0; - } - __syncthreads(); - if (threadIdx.x / WARP_SIZE == 0) { - sum = warpSum(shared[threadIdx.x]); - if (threadIdx.x == 0) { - shared[0] = sum; - } - } - __syncthreads(); - - // Everyone picks it up, should be broadcast into the whole gradInput - return shared[0]; -} - -template -__device__ T reduceD( - Op op, int b, int i, int k, int D) { - T sum = 0; - for (int x = threadIdx.x; x < D; x += blockDim.x) { - sum += op(b,i,k,x); - } - // sum over NumThreads within a warp - sum = warpSum(sum); - - // 'transpose', and reduce within warp again - __shared__ T shared[32]; - - __syncthreads(); - if (threadIdx.x % WARP_SIZE == 0) { - if (threadIdx.x / WARP_SIZE < 32) { - shared[threadIdx.x / WARP_SIZE] = sum; - } - } - if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { - // zero out the other entries in shared - shared[threadIdx.x] = (T) 0; - } - __syncthreads(); - if (threadIdx.x / WARP_SIZE == 0) { - sum = warpSum(shared[threadIdx.x]); - if (threadIdx.x == 0) { - shared[0] = sum; - } - } - __syncthreads(); - - // Everyone picks it up, should be broadcast into the whole gradInput - return shared[0]; -} - -template -__device__ T reduceK( - Op op, int b, int i, int d, int K) { - T sum = 0; - for (int x = threadIdx.x; x < K; x += blockDim.x) { - sum += op(b,i,x,d); - } - // sum over NumThreads within a warp - sum = warpSum(sum); - - // 'transpose', and reduce within warp again - __shared__ T shared[32]; - - __syncthreads(); - if (threadIdx.x % WARP_SIZE == 0) { - if (threadIdx.x / WARP_SIZE < 32) { - shared[threadIdx.x / WARP_SIZE] = sum; - } - } - if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { - // zero out the other entries in shared - shared[threadIdx.x] = (T) 0; - } - __syncthreads(); - if (threadIdx.x / WARP_SIZE == 0) { - sum = warpSum(shared[threadIdx.x]); - if (threadIdx.x == 0) { - shared[0] = sum; - } - } - __syncthreads(); - - // Everyone picks it up, should be broadcast into the whole gradInput - return shared[0]; -} - -template -__device__ T reduceBN( - Op op, - int k, int d, int B, int N) { - T sum = 0; - for (int batch = 0; batch < B; ++batch) { - for (int x = threadIdx.x; x < N; x += blockDim.x) { - sum += op(batch,x,k,d); - } - } - // sum over NumThreads within a warp - sum = warpSum(sum); - // 'transpose', and reduce within warp again - __shared__ T shared[32]; - - __syncthreads(); - if (threadIdx.x % WARP_SIZE == 0) { - if (threadIdx.x / WARP_SIZE < 32) { - shared[threadIdx.x / WARP_SIZE] = sum; - } - } - if (threadIdx.x >= blockDim.x / WARP_SIZE && threadIdx.x < WARP_SIZE) { - // zero out the other entries in shared - shared[threadIdx.x] = (T) 0; - } - __syncthreads(); - if (threadIdx.x / WARP_SIZE == 0) { - sum = warpSum(shared[threadIdx.x]); - if (threadIdx.x == 0) { - shared[0] = sum; - } - } - __syncthreads(); - - // Everyone picks it up, should be broadcast into the whole gradInput - return shared[0]; -} - template __global__ void Aggregate_Forward_kernel ( DeviceTensor E, @@ -225,7 +79,7 @@ __global__ void Aggregate_Forward_kernel ( k = blockIdx.y; N = X.getSize(1); /* main operation */ - AggOp g(A,X,C); + AggOp g(A, X, C); E[b][k][d] = reduceN(g, b, k, d, N); } @@ -244,7 +98,7 @@ __global__ void Aggregate_Backward_kernel ( k = blockIdx.x; D = GE.getSize(2); /* main operation */ - AggBackOp g(GE,X,C); + AggBackOp g(GE, X, C); GA[b][i][k] = reduceD(g, b, i, k, D); } @@ -312,7 +166,7 @@ at::Tensor Aggregate_Forward_CUDA( const at::Tensor C_) { /* Device tensors */ auto E_ = A_.type().tensor({A_.size(0), C_.size(0), C_.size(1)}).zero_(); - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); // B, K, D dim3 blocks(C_.size(1), C_.size(0), X_.size(0)); dim3 threads(getNumThreads(X_.size(1))); @@ -338,7 +192,7 @@ std::vector Aggregate_Backward_CUDA( auto gradA_ = at::zeros_like(A_); auto gradX_ = at::bmm(A_, GE_); auto gradC_ = (-GE_ * A_.sum(1).unsqueeze(2)).sum(0); - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); // B, K, D dim3 blocks(C_.size(0), X_.size(1), X_.size(0)); dim3 threads(getNumThreads(C_.size(1))); @@ -361,7 +215,7 @@ at::Tensor ScaledL2_Forward_CUDA( const at::Tensor C_, const at::Tensor S_) { auto SL_ = X_.type().tensor({X_.size(0), X_.size(1), C_.size(0)}).zero_(); - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); dim3 blocks(C_.size(0), X_.size(1), X_.size(0)); dim3 threads(getNumThreads(C_.size(1))); @@ -388,13 +242,11 @@ std::vector ScaledL2_Backward_CUDA( auto GX_ = at::zeros_like(X_); auto GC_ = at::zeros_like(C_); /* kernel function */ - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); dim3 blocks1(X_.size(2), X_.size(1), X_.size(0)); dim3 threads1(getNumThreads(C_.size(0))); dim3 blocks2(C_.size(1), C_.size(0)); dim3 threads2(getNumThreads(X_.size(1))); - //std::vector size{ 1, 1, K}; - //auto GS_ = GSL_ * (SL_ / at::_unsafe_view(S_, size)) auto GS_ = (GSL_ * (SL_ / S_.view({1, 1, C_.size(0)}))).sum(0).sum(0); AT_DISPATCH_FLOATING_TYPES(X_.type(), "ScaledL2_Backward_CUDA", ([&] { /* Device tensors */ diff --git a/encoding/lib/gpu/encodingv2_kernel.cu b/encoding/lib/gpu/encodingv2_kernel.cu new file mode 100644 index 00000000..bfde16b4 --- /dev/null +++ b/encoding/lib/gpu/encodingv2_kernel.cu @@ -0,0 +1,426 @@ +#include +#include +#include +#include + +#include "common.h" +#include "device_tensor.h" + +namespace { + +template +struct KD2Op { + __device__ KD2Op(DeviceTensor x, + DeviceTensor c, + DeviceTensor std) : X(x), C(c), STD(std) {} + __device__ __forceinline__ Acctype operator()(int b, int i, int k, int d) + { + DType r = (X[b][i][d] - C[k][d]) / STD[k][d]; + return ScalarConvert::to(r * r); + } + DeviceTensor X; + DeviceTensor C; + DeviceTensor STD; +}; + +template +__global__ void Encoding_Dist_Forward_kernel ( + DeviceTensor KD, + DeviceTensor X, + DeviceTensor C, + DeviceTensor STD) { + /* declarations of the variables */ + int b, k, i, D; + /* Get the index and channels */ + b = blockIdx.z; + k = blockIdx.x; + i = blockIdx.y; + D = X.getSize(2); + /* main operation */ + KD2Op g(X, C, STD); + KD[b][i][k] = reduceD(g, b, i, k, D);; +} + +template +struct EncGradXOp { + __device__ EncGradXOp( + DeviceTensor gkd, + DeviceTensor x, + DeviceTensor c, + DeviceTensor std) : GKD(gkd), X(x), C(c), STD(std) {} + // DeviceTensor s, S(s) + __device__ __forceinline__ Acctype operator()(int b, int i, int k, int d) { + return ScalarConvert::to( + 2 * GKD[b][i][k] * (X[b][i][d] - C[k][d]) / + (STD[k][d] * STD[k][d])); + } + DeviceTensor GKD; + DeviceTensor X; + DeviceTensor C; + DeviceTensor STD; + // DeviceTensor S; +}; + +template +__global__ void Encoding_GradX_kernel ( + DeviceTensor GKD, + DeviceTensor GX, + DeviceTensor X, + DeviceTensor C, + DeviceTensor STD) { + // DeviceTensor S + /* declarations of the variables */ + int b, d, i, K; + /* Get the index and channels */ + b = blockIdx.z; + i = blockIdx.y; + d = blockIdx.x; + K = C.getSize(0); + /* main operation */ + EncGradXOp g(GKD, X, C, STD); + GX[b][i][d] = reduceK(g, b, i, d, K); +} + +template +struct EncGradSTDOp { + __device__ EncGradSTDOp( + DeviceTensor gkd, + DeviceTensor x, + DeviceTensor c, + DeviceTensor std) : GKD(gkd), X(x), C(c), STD(std) {} + // DeviceTensor s, S(s) + __device__ __forceinline__ Acctype operator()(int b, int i, int k, int d) { + return ScalarConvert::to( + -2 * GKD[b][i][k] * (X[b][i][d] - C[k][d]) * + (X[b][i][d] - C[k][d]) / (STD[k][d] * STD[k][d] * STD[k][d])); + } + DeviceTensor GKD; + DeviceTensor X; + DeviceTensor C; + DeviceTensor STD; + // DeviceTensor S; +}; + +template +__global__ void Encoding_GradCSTD_kernel ( + DeviceTensor GKD, + DeviceTensor GC, + DeviceTensor GSTD, + DeviceTensor X, + DeviceTensor C, + DeviceTensor STD) { + /* declarations of the variables */ + int k, d, B, N; + /* Get the index and channels */ + d = blockIdx.x; + k = blockIdx.y; + B = X.getSize(0); + N = X.getSize(1); + /* main operation */ + EncGradXOp g1(GKD, X, C, STD); + EncGradSTDOp g2(GKD, X, C, STD); + GC[k][d] = -reduceBN(g1, k, d, B, N); + GSTD[k][d] += reduceBN(g2, k, d, B, N); +} + +template +struct EncGradSTDXOp { + __device__ EncGradSTDXOp( + DeviceTensor gstd, + DeviceTensor x, + DeviceTensor c, + DeviceTensor std) : GSTD(gstd), X(x), C(c), STD(std) {} + __device__ __forceinline__ Acctype operator()(int b, int i, int k, int d) { + return ScalarConvert::to( + GSTD[k][d] * (X[b][i][d] - C[k][d]) / STD[k][d]); + } + DeviceTensor GSTD; + DeviceTensor X; + DeviceTensor C; + DeviceTensor STD; +}; + +template +__global__ void Encoding_GradSTDX_kernel ( + DeviceTensor GSTD, + DeviceTensor GX, + DeviceTensor X, + DeviceTensor C, + DeviceTensor STD, + int N) { + /* declarations of the variables */ + int b, d, i, K; + /* Get the index and channels */ + b = blockIdx.z; + i = blockIdx.y; + d = blockIdx.x; + K = C.getSize(0); + /* main operation */ + EncGradSTDXOp g(GSTD, X, C, STD); + GX[b][i][d] += reduceK(g, b, i, d, K) / N; +} + +template +struct AggOpV2 { + __device__ AggOpV2(DeviceTensor a, + DeviceTensor x, + DeviceTensor c, + DeviceTensor std) : A(a), X(x), C(c), STD(std) {} + __device__ __forceinline__ Acctype operator()(int b, int i, int k, int d) { + return ScalarConvert::to(A[b][i][k] * (X[b][i][d] - C[k][d]) / + STD[k][d]); + } + DeviceTensor A; + DeviceTensor X; + DeviceTensor C; + DeviceTensor STD; +}; + +template +__global__ void AggregateV2_Forward_kernel ( + DeviceTensor E, + DeviceTensor A, + DeviceTensor X, + DeviceTensor C, + DeviceTensor STD) { + /* declarations of the variables */ + int b, k, d, N; + /* Get the index and channels */ + b = blockIdx.z; + d = blockIdx.x; + k = blockIdx.y; + N = X.getSize(1); + /* main operation */ + AggOpV2 g(A, X, C, STD); + E[b][k][d] = reduceN(g, b, k, d, N); +} + +template +struct AggV2BackOp { + __device__ AggV2BackOp(DeviceTensor g, + DeviceTensor x, + DeviceTensor c, + DeviceTensor std) : G(g), X(x), C(c), STD(std) {} + __device__ __forceinline__ Acctype operator()(int b, int i, int k, int d) { + return ScalarConvert::to(G[b][k][d] * (X[b][i][d] - C[k][d]) / + STD[k][d]); + } + DeviceTensor G; + DeviceTensor X; + DeviceTensor C; + DeviceTensor STD; +}; + +template +__global__ void AggregateV2_Backward_kernel ( + DeviceTensor GA, + DeviceTensor GE, + DeviceTensor A, + DeviceTensor X, + DeviceTensor C, + DeviceTensor STD) { + /* declarations of the variables */ + int b, k, i, D; + /* Get the index and channels */ + b = blockIdx.z; + i = blockIdx.y; + k = blockIdx.x; + D = GE.getSize(2); + /* main operation */ + AggV2BackOp g(GE, X, C, STD); + GA[b][i][k] = reduceD(g, b, i, k, D); +} + +} // namespace + +at::Tensor Encoding_Dist_Inference_Forward_CUDA( + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_) { + // const at::Tensor S_, + // X \in R^{B, N, D}, C \in R^{K, D}, S \in R^K + auto KD_ = X_.type().tensor({X_.size(0), X_.size(1), C_.size(0)}).zero_(); + // E(x), E(x^2) + int N = X_.size(0) * X_.size(1); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + dim3 blocks(C_.size(0), X_.size(1), X_.size(0)); + dim3 threads(getNumThreads(C_.size(1))); + // calculate the kernel distance + AT_DISPATCH_FLOATING_TYPES(X_.type(), "Encoding_Dist_Inference_Forward_CUDA", ([&] { + /* Device tensors */ + DeviceTensor KD = devicetensor(KD_); + DeviceTensor X = devicetensor(X_); + DeviceTensor C = devicetensor(C_); + DeviceTensor STD = devicetensor(STD_); + /* kernel function */ + Encoding_Dist_Forward_kernel + <<>> (KD, X, C, STD); + })); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + return KD_; +} + +std::vector Encoding_Dist_Inference_Backward_CUDA( + const at::Tensor GKD_, + const at::Tensor KD_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_) { + auto GX_ = at::zeros_like(X_); + auto GC_ = at::zeros_like(C_); + auto GSTD_ = at::zeros_like(STD_); + /* kernel function */ + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + dim3 blocks1(X_.size(2), X_.size(1), X_.size(0)); + dim3 threads1(getNumThreads(C_.size(0))); + dim3 blocks2(C_.size(1), C_.size(0)); + dim3 threads2(getNumThreads(X_.size(1))); + int N = X_.size(0) * X_.size(1); + AT_DISPATCH_FLOATING_TYPES(X_.type(), "Encoding_Dist_Backward_CUDA", ([&] { + /* Device tensors */ + DeviceTensor GKD = devicetensor(GKD_); + DeviceTensor GSTD = devicetensor(GSTD_); + DeviceTensor GX = devicetensor(GX_); + DeviceTensor GC = devicetensor(GC_); + DeviceTensor X = devicetensor(X_); + DeviceTensor C = devicetensor(C_); + DeviceTensor STD = devicetensor(STD_); + Encoding_GradX_kernel + <<>> (GKD, GX, X, C, STD); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + Encoding_GradCSTD_kernel + <<>> (GKD, GC, GSTD, X, C, STD); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + })); + return {GX_, GC_, GSTD_}; +} + +std::vector Encoding_Dist_Forward_CUDA( + const at::Tensor X_, + const at::Tensor C_, + double eps) { + // const at::Tensor S_, + // X \in R^{B, N, D}, C \in R^{K, D}, S \in R^K + auto KD_ = X_.type().tensor({X_.size(0), X_.size(1), C_.size(0)}).zero_(); + // E(x), E(x^2) + int N = X_.size(0) * X_.size(1); + auto SVar_ = (X_.pow(2).sum(0).sum(0).view({1, X_.size(2)}) - + 2 * C_ * X_.sum(0).sum(0).view({1, X_.size(2)})).expand_as(C_) + + C_.pow(2) * N; + auto STD_ = at::sqrt(SVar_ / N + eps); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + dim3 blocks(C_.size(0), X_.size(1), X_.size(0)); + dim3 threads(getNumThreads(C_.size(1))); + // calculate the kernel distance + AT_DISPATCH_FLOATING_TYPES(X_.type(), "Encoding_Dist_Forward_CUDA", ([&] { + /* Device tensors */ + DeviceTensor KD = devicetensor(KD_); + DeviceTensor X = devicetensor(X_); + DeviceTensor C = devicetensor(C_); + DeviceTensor STD = devicetensor(STD_); + /* kernel function */ + Encoding_Dist_Forward_kernel + <<>> (KD, X, C, STD); + })); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + return {KD_, STD_, SVar_ / (N - 1)}; +} + +std::vector Encoding_Dist_Backward_CUDA( + const at::Tensor GKD_, + const at::Tensor GSTD_, + const at::Tensor KD_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_) { + auto GX_ = at::zeros_like(X_); + auto GC_ = at::zeros_like(C_); + auto GSTD2_ = GSTD_.clone(); + /* kernel function */ + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + dim3 blocks1(X_.size(2), X_.size(1), X_.size(0)); + dim3 threads1(getNumThreads(C_.size(0))); + dim3 blocks2(C_.size(1), C_.size(0)); + dim3 threads2(getNumThreads(X_.size(1))); + int N = X_.size(0) * X_.size(1); + AT_DISPATCH_FLOATING_TYPES(X_.type(), "Encoding_Dist_Backward_CUDA", ([&] { + /* Device tensors */ + DeviceTensor GKD = devicetensor(GKD_); + DeviceTensor GSTD = devicetensor(GSTD2_); + DeviceTensor GX = devicetensor(GX_); + DeviceTensor GC = devicetensor(GC_); + DeviceTensor X = devicetensor(X_); + DeviceTensor C = devicetensor(C_); + DeviceTensor STD = devicetensor(STD_); + Encoding_GradX_kernel + <<>> (GKD, GX, X, C, STD); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + Encoding_GradCSTD_kernel + <<>> (GKD, GC, GSTD, X, C, STD); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + Encoding_GradSTDX_kernel + <<>> (GSTD, GX, X, C, STD, N); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + })); + // d_sigma/d_c + GC_ = GC_ - GSTD2_ * (X_.mean(0).mean(0) - C_) / STD_; + return {GX_, GC_}; +} + +at::Tensor AggregateV2_Forward_CUDA( + const at::Tensor A_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_) { + /* Device tensors */ + auto E_ = A_.type().tensor({A_.size(0), C_.size(0), C_.size(1)}).zero_(); + // auto IS_ = 1.0f / (S_ + eps).sqrt(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + // B, K, D + dim3 blocks(C_.size(1), C_.size(0), X_.size(0)); + dim3 threads(getNumThreads(X_.size(1))); + + AT_DISPATCH_FLOATING_TYPES(A_.type(), "Aggregate_Forward_CUDA", ([&] { + DeviceTensor E = devicetensor(E_); + DeviceTensor A = devicetensor(A_); + DeviceTensor X = devicetensor(X_); + DeviceTensor C = devicetensor(C_); + DeviceTensor STD = devicetensor(STD_); + /* kernel function */ + AggregateV2_Forward_kernel + <<>>(E, A, X, C, STD); + })); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + return E_; +} + +std::vector AggregateV2_Backward_CUDA( + const at::Tensor GE_, + const at::Tensor E_, + const at::Tensor A_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_) { + auto gradA_ = at::zeros_like(A_); + auto gradX_ = at::bmm(A_ , (GE_ / STD_.unsqueeze(0))); + auto gradC_ = -(A_.sum(1).unsqueeze(2) * GE_ / STD_.unsqueeze(0)).sum(0); + auto gradSTD_ = -(GE_ * E_).sum(0) / STD_; + // auto gradS_ = -0.5 * (GE_ * E_).sum(2).sum(0) / S_; + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + // B, K, D + dim3 blocks(C_.size(0), X_.size(1), X_.size(0)); + dim3 threads(getNumThreads(C_.size(1))); + AT_DISPATCH_FLOATING_TYPES(A_.type(), "Aggregate_Backward_CUDA", ([&] { + /* Device tensors */ + DeviceTensor GA = devicetensor(gradA_); + DeviceTensor GE = devicetensor(GE_); + DeviceTensor A = devicetensor(A_); + DeviceTensor X = devicetensor(X_); + DeviceTensor C = devicetensor(C_); + DeviceTensor STD = devicetensor(STD_); + AggregateV2_Backward_kernel + <<>> (GA, GE, A, X, C, STD); + })); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + return {gradA_, gradX_, gradC_, gradSTD_}; +} diff --git a/encoding/lib/gpu/nms_kernel.cu b/encoding/lib/gpu/nms_kernel.cu new file mode 100644 index 00000000..d28b6ffd --- /dev/null +++ b/encoding/lib/gpu/nms_kernel.cu @@ -0,0 +1,117 @@ +#include +#include "ATen/NativeFunctions.h" +#include + +template +__device__ __forceinline__ scalar fmin(scalar a, scalar b) { + return a > b ? b : a; +} + +template +__device__ __forceinline__ scalar fmax(scalar a, scalar b) { + return a > b ? a : b; +} + +template +__device__ __forceinline__ scalar IoU(const scalar* box_x, const scalar* box_y) { + // Calculate IoU between the boxes. + scalar rightmost_l = fmax(box_x[0], box_y[0]); + scalar leftmost_r = fmin(box_x[0] + box_x[2], box_y[0] + box_y[2]); + scalar delta_x = fmax((scalar)0., leftmost_r - rightmost_l); + + scalar bottommost_tp = fmax(box_x[1], box_y[1]); + scalar topmost_b = fmin(box_x[1] + box_x[3], box_y[1] + box_y[3]); + scalar delta_y = fmax((scalar)0., topmost_b - bottommost_tp); + + scalar uni = box_x[2] * box_x[3] + box_y[2] * box_y[3]; + + return delta_x * delta_y / (uni - delta_x * delta_y); + +} + +template +__global__ void nms_kernel(unsigned char* mask, + const scalar* boxes, + const int64_t* inds, + const int64_t num_boxes, + double thresh) { +//A pretty straightforward implementation, analogous to the standard serial +//version but with the IoUs computed and mask updated in parallel. We access +//the box data through an array of sorted indices rather than physically +//sorting it: unless one has an inordinate number of boxes (O(10^5), whereas +//for example in the faster rcnn paper they feed 6000 per batch) the +//data will fit in L2 so sorting it won't actually reduce the number of +//messy reads from global. + int col = 0; + while(col < num_boxes-1) + { + for(int i = threadIdx.x; i < num_boxes-1; i+=blockDim.x) + if(i >= col) + { + scalar iou = IoU(&boxes[4*inds[i+1+num_boxes*blockIdx.x] + 4*num_boxes*blockIdx.x], + &boxes[4*inds[col+num_boxes*blockIdx.x] + 4*num_boxes*blockIdx.x]); + mask[i+1+blockIdx.x*num_boxes] *= (iou>thresh) ? 0 : 1; + } + __syncthreads(); + ++col; + while((col < num_boxes - 1) && (mask[col+blockIdx.x*num_boxes]==0)) + ++col; + } +} + +std::vector Non_Max_Suppression_CUDA( + const at::Tensor& input, + const at::Tensor& scores, + double thresh) { + AT_ASSERT(input.ndimension() == 3); + AT_ASSERT(scores.ndimension() == 2); + AT_ASSERT(input.size(0) == scores.size(0)); + AT_ASSERT(input.size(1) == scores.size(1)); + AT_ASSERT(input.size(2) == 4); + AT_ASSERT(input.is_contiguous()); + AT_ASSERT(scores.is_contiguous()); + AT_ASSERT(input.type().scalarType() == at::kFloat || input.type().scalarType() == at::kDouble) + AT_ASSERT(scores.type().scalarType() == at::kFloat || scores.type().scalarType() == at::kDouble) + + auto num_boxes = input.size(1); + auto batch_size = input.size(0); + auto mask = input.type().toScalarType(at::kByte).tensor({batch_size, num_boxes}); + mask.fill_(1); + + //need the indices of the boxes sorted by score. + at::Tensor sorted_inds = std::get<1>(scores.sort(-1, true)); + + + dim3 mask_block(512); //would be nice to have 1024 here for gpus that support it, + //but not sure how to do this cleanly without calling + //cudaGetDeviceProperties in the funcion body... + + dim3 mask_grid(batch_size); + if(input.type().scalarType() == at::kFloat) + { + nms_kernel<<>>( + mask.data(), + input.data(), + sorted_inds.data(), + num_boxes, + thresh); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + } + else + { + nms_kernel<<>>( + mask.data(), + input.data(), + sorted_inds.data(), + num_boxes, + thresh); + AT_ASSERT(cudaGetLastError() == cudaSuccess); + } + + //It's not entirely clear what the best thing to return is here. The algorithm will + //produce a different number of boxes for each batch, so there is no obvious way of + //way of returning the surving boxes/indices as a tensor. Returning a mask on the + //sorted boxes together with the sorted indices seems reasonable; that way, the user + //can easily take the N highest-scoring surviving boxes to form a tensor if they wish. + return {mask, sorted_inds}; +} diff --git a/encoding/lib/gpu/operator.cpp b/encoding/lib/gpu/operator.cpp index e4cecb55..3faae98d 100644 --- a/encoding/lib/gpu/operator.cpp +++ b/encoding/lib/gpu/operator.cpp @@ -1,8 +1,9 @@ #include "operator.h" PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("roi_align_forward", &ROIAlignForwardCUDA, "ROI Align forward (CUDA)"); - m.def("roi_align_backward", &ROIAlignBackwardCUDA, "ROI Align backward (CUDA)"); + m.def("roi_align_forward", &ROIAlign_Forward_CUDA, "ROI Align forward (CUDA)"); + m.def("roi_align_backward", &ROIAlign_Backward_CUDA, "ROI Align backward (CUDA)"); + m.def("non_max_suppression", &Non_Max_Suppression_CUDA, "NMS (CUDA)"); m.def("aggregate_forward", &Aggregate_Forward_CUDA, "Aggregate forward (CUDA)"); m.def("aggregate_backward", &Aggregate_Backward_CUDA, "Aggregate backward (CUDA)"); m.def("scaled_l2_forward", &ScaledL2_Forward_CUDA, "ScaledL2 forward (CUDA)"); @@ -11,4 +12,12 @@ PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { m.def("batchnorm_backward", &BatchNorm_Backward_CUDA, "BatchNorm backward (CUDA)"); m.def("sumsquare_forward", &Sum_Square_Forward_CUDA, "SumSqu forward (CUDA)"); m.def("sumsquare_backward", &Sum_Square_Backward_CUDA, "SumSqu backward (CUDA)"); + m.def("encoding_dist_forward", &Encoding_Dist_Forward_CUDA, "EncDist forward (CUDA)"); + m.def("encoding_dist_backward", &Encoding_Dist_Backward_CUDA, "Assign backward (CUDA)"); + m.def("encoding_dist_inference_forward", &Encoding_Dist_Inference_Forward_CUDA, + "EncDist Inference forward (CUDA)"); + m.def("encoding_dist_inference_backward", &Encoding_Dist_Inference_Backward_CUDA, + "Assign Inference backward (CUDA)"); + m.def("aggregatev2_forward", &AggregateV2_Forward_CUDA, "AggregateV2 forward (CUDA)"); + m.def("aggregatev2_backward", &AggregateV2_Backward_CUDA, "AggregateV2 backward (CUDA)"); } diff --git a/encoding/lib/gpu/operator.h b/encoding/lib/gpu/operator.h index 8557532a..67e2972f 100644 --- a/encoding/lib/gpu/operator.h +++ b/encoding/lib/gpu/operator.h @@ -1,7 +1,7 @@ #include #include -at::Tensor ROIAlignForwardCUDA( +at::Tensor ROIAlign_Forward_CUDA( const at::Tensor input, const at::Tensor rois, int64_t pooled_height, @@ -9,7 +9,7 @@ at::Tensor ROIAlignForwardCUDA( double spatial_scale, int64_t sample_ratio); -at::Tensor ROIAlignBackwardCUDA( +at::Tensor ROIAlign_Backward_CUDA( const at::Tensor rois, const at::Tensor grad_output, int64_t b_size, @@ -21,6 +21,11 @@ at::Tensor ROIAlignBackwardCUDA( double spatial_scale, int64_t sampling_ratio); +std::vector Non_Max_Suppression_CUDA( + const at::Tensor& input, + const at::Tensor& scores, + double thresh); + at::Tensor Aggregate_Forward_CUDA( const at::Tensor A_, const at::Tensor X_, @@ -67,3 +72,42 @@ at::Tensor Sum_Square_Backward_CUDA( const at::Tensor input_, const at::Tensor gradSum_, const at::Tensor gradSquare_); + +at::Tensor Encoding_Dist_Inference_Forward_CUDA( + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_); + +std::vector Encoding_Dist_Inference_Backward_CUDA( + const at::Tensor GKD_, + const at::Tensor KD_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_); + +std::vector Encoding_Dist_Forward_CUDA( + const at::Tensor X, + const at::Tensor C, + double eps); + +std::vector Encoding_Dist_Backward_CUDA( + const at::Tensor GKD_, + const at::Tensor GSTD_, + const at::Tensor KD_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_); + +at::Tensor AggregateV2_Forward_CUDA( + const at::Tensor A_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_); + +std::vector AggregateV2_Backward_CUDA( + const at::Tensor GE_, + const at::Tensor E_, + const at::Tensor A_, + const at::Tensor X_, + const at::Tensor C_, + const at::Tensor STD_); diff --git a/encoding/lib/gpu/roi_align_kernel.cu b/encoding/lib/gpu/roi_align_kernel.cu index d8525a44..528fa223 100644 --- a/encoding/lib/gpu/roi_align_kernel.cu +++ b/encoding/lib/gpu/roi_align_kernel.cu @@ -1,4 +1,5 @@ #include +#include #include #include @@ -346,7 +347,7 @@ __global__ void RoIAlignBackwardKernel( } // namespace -at::Tensor ROIAlignForwardCUDA( +at::Tensor ROIAlign_Forward_CUDA( const at::Tensor input, const at::Tensor rois, int64_t pooled_height, @@ -370,12 +371,12 @@ at::Tensor ROIAlignForwardCUDA( auto count = output.numel(); - AT_DISPATCH_FLOATING_TYPES(input.type(), "ROIAlignForwardCUDA", ([&] { + AT_DISPATCH_FLOATING_TYPES(input.type(), "ROIAlign_Forward_CUDA", ([&] { RoIAlignForwardKernel <<>>( + at::cuda::getCurrentCUDAStream()>>>( count, input.data(), static_cast(spatial_scale), @@ -392,7 +393,7 @@ at::Tensor ROIAlignForwardCUDA( return output; } -at::Tensor ROIAlignBackwardCUDA( +at::Tensor ROIAlign_Backward_CUDA( const at::Tensor rois, const at::Tensor grad_output, int64_t b_size, @@ -417,12 +418,12 @@ at::Tensor ROIAlignBackwardCUDA( auto num_rois = rois.size(0); auto count = grad_output.numel(); - AT_DISPATCH_FLOATING_TYPES(rois.type(), "ROIAlignBackwardCUDA", ([&] { + AT_DISPATCH_FLOATING_TYPES(rois.type(), "ROIAlign_Backward_CUDA", ([&] { RoIAlignBackwardKernel <<>>( + at::cuda::getCurrentCUDAStream()>>>( count, grad_output.data(), num_rois, diff --git a/encoding/lib/gpu/setup.py b/encoding/lib/gpu/setup.py index 9f56552f..924b9998 100644 --- a/encoding/lib/gpu/setup.py +++ b/encoding/lib/gpu/setup.py @@ -7,8 +7,10 @@ CUDAExtension('enclib_gpu', [ 'operator.cpp', 'encoding_kernel.cu', + 'encodingv2_kernel.cu', 'syncbn_kernel.cu', 'roi_align_kernel.cu', + 'nms_kernel.cu', ]), ], cmdclass={ diff --git a/encoding/lib/gpu/syncbn_kernel.cu b/encoding/lib/gpu/syncbn_kernel.cu index 599c1df3..5ea1db37 100644 --- a/encoding/lib/gpu/syncbn_kernel.cu +++ b/encoding/lib/gpu/syncbn_kernel.cu @@ -1,5 +1,6 @@ -#include #include +#include +#include #include "common.h" #include "device_tensor.h" @@ -180,7 +181,7 @@ at::Tensor BatchNorm_Forward_CUDA( const at::Tensor gamma_, const at::Tensor beta_) { auto output_ = at::zeros_like(input_); - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); dim3 blocks(input_.size(1)); dim3 threads(getNumThreads(input_.size(2))); AT_DISPATCH_FLOATING_TYPES(input_.type(), "BatchNorm_Forward_CUDA", ([&] { @@ -214,7 +215,7 @@ std::vector BatchNorm_Backward_CUDA( at::Tensor gradMean_ = at::zeros_like(mean_); at::Tensor gradStd_ = at::zeros_like(std_); /* cuda utils*/ - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); dim3 blocks(input_.size(1)); dim3 threads(getNumThreads(input_.size(2))); AT_DISPATCH_FLOATING_TYPES(input_.type(), "BatchNorm_Backward_CUDA", ([&] { @@ -246,10 +247,10 @@ std::vector Sum_Square_Forward_CUDA( at::Tensor sum_ = input_.type().tensor({input_.size(1)}).zero_(); at::Tensor square_ = input_.type().tensor({input_.size(1)}).zero_(); /* cuda utils*/ - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); dim3 blocks(input_.size(1)); dim3 threads(getNumThreads(input_.size(2))); - AT_DISPATCH_FLOATING_TYPES(input_.type(), "BatchNorm_Backward_CUDA", ([&] { + AT_DISPATCH_FLOATING_TYPES(input_.type(), "SumSquare_forward_CUDA", ([&] { /* Device tensors */ DeviceTensor input = devicetensor(input_); DeviceTensor sum = devicetensor(sum_); @@ -269,10 +270,10 @@ at::Tensor Sum_Square_Backward_CUDA( /* outputs */ at::Tensor gradInput_ = at::zeros_like(input_); /* cuda utils*/ - cudaStream_t stream = at::globalContext().getCurrentCUDAStream(); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); dim3 blocks(input_.size(1)); dim3 threads(getNumThreads(input_.size(2))); - AT_DISPATCH_FLOATING_TYPES(input_.type(), "BatchNorm_Backward_CUDA", ([&] { + AT_DISPATCH_FLOATING_TYPES(input_.type(), "SumSquare_Backward_CUDA", ([&] { /* Device tensors */ DeviceTensor gradInput = devicetensor(gradInput_); DeviceTensor input = devicetensor(input_); diff --git a/encoding/models/base.py b/encoding/models/base.py index 9e7e08a7..1b54ecf8 100644 --- a/encoding/models/base.py +++ b/encoding/models/base.py @@ -24,7 +24,7 @@ class BaseNet(nn.Module): def __init__(self, nclass, backbone, aux, se_loss, dilated=True, norm_layer=None, - base_size=576, crop_size=608, mean=[.485, .456, .406], + base_size=520, crop_size=480, mean=[.485, .456, .406], std=[.229, .224, .225], root='~/.encoding/models'): super(BaseNet, self).__init__() self.nclass = nclass @@ -99,6 +99,8 @@ def parallel_forward(self, inputs, **kwargs): elif len(kwargs) < len(inputs): kwargs.extend([{} for _ in range(len(inputs) - len(kwargs))]) outputs = self.parallel_apply(replicas, inputs, kwargs) + #for out in outputs: + # print('out.size()', out.size()) return outputs def forward(self, image): diff --git a/encoding/models/encnet.py b/encoding/models/encnet.py index 2e60843d..b69d2913 100644 --- a/encoding/models/encnet.py +++ b/encoding/models/encnet.py @@ -14,7 +14,8 @@ from .fcn import FCNHead __all__ = ['EncNet', 'EncModule', 'get_encnet', 'get_encnet_resnet50_pcontext', - 'get_encnet_resnet101_pcontext', 'get_encnet_resnet50_ade'] + 'get_encnet_resnet101_pcontext', 'get_encnet_resnet50_ade', + 'get_encnet_resnet101_ade'] class EncNet(BaseNet): def __init__(self, nclass, backbone, aux=True, se_loss=True, lateral=False, @@ -43,8 +44,6 @@ def forward(self, x): class EncModule(nn.Module): def __init__(self, in_channels, nclass, ncodes=32, se_loss=True, norm_layer=None): super(EncModule, self).__init__() - #norm_layer = nn.BatchNorm1d if isinstance(norm_layer, nn.BatchNorm2d) else \ - # encoding.nn.BatchNorm1d self.se_loss = se_loss self.encoding = nn.Sequential( nn.Conv2d(in_channels, in_channels, 1, bias=False), @@ -140,9 +139,9 @@ def get_encnet(dataset='pascal_voc', backbone='resnet50', pretrained=False, 'ade20k': 'ade', 'pcontext': 'pcontext', } - kwargs['lateral'] = True if dataset.lower() == 'pcontext' else False + kwargs['lateral'] = True if dataset.lower().startswith('p') else False # infer number of classes - from ..datasets import datasets, VOCSegmentation, VOCAugSegmentation, ADE20KSegmentation + from ..datasets import datasets model = EncNet(datasets[dataset.lower()].NUM_CLASS, backbone=backbone, root=root, **kwargs) if pretrained: from .model_store import get_model_file @@ -167,7 +166,8 @@ def get_encnet_resnet50_pcontext(pretrained=False, root='~/.encoding/models', ** >>> model = get_encnet_resnet50_pcontext(pretrained=True) >>> print(model) """ - return get_encnet('pcontext', 'resnet50', pretrained, root=root, aux=True, **kwargs) + return get_encnet('pcontext', 'resnet50', pretrained, root=root, aux=True, + base_size=520, crop_size=480, **kwargs) def get_encnet_resnet101_pcontext(pretrained=False, root='~/.encoding/models', **kwargs): r"""EncNet-PSP model from the paper `"Context Encoding for Semantic Segmentation" @@ -186,7 +186,8 @@ def get_encnet_resnet101_pcontext(pretrained=False, root='~/.encoding/models', * >>> model = get_encnet_resnet101_pcontext(pretrained=True) >>> print(model) """ - return get_encnet('pcontext', 'resnet101', pretrained, root=root, aux=True, **kwargs) + return get_encnet('pcontext', 'resnet101', pretrained, root=root, aux=True, + base_size=520, crop_size=480, **kwargs) def get_encnet_resnet50_ade(pretrained=False, root='~/.encoding/models', **kwargs): r"""EncNet-PSP model from the paper `"Context Encoding for Semantic Segmentation" @@ -205,4 +206,45 @@ def get_encnet_resnet50_ade(pretrained=False, root='~/.encoding/models', **kwarg >>> model = get_encnet_resnet50_ade(pretrained=True) >>> print(model) """ - return get_encnet('ade20k', 'resnet50', pretrained, root=root, aux=True, **kwargs) + return get_encnet('ade20k', 'resnet50', pretrained, root=root, aux=True, + base_size=520, crop_size=480, **kwargs) + +def get_encnet_resnet101_ade(pretrained=False, root='~/.encoding/models', **kwargs): + r"""EncNet-PSP model from the paper `"Context Encoding for Semantic Segmentation" + `_ + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.encoding/models' + Location for keeping the model parameters. + + + Examples + -------- + >>> model = get_encnet_resnet50_ade(pretrained=True) + >>> print(model) + """ + return get_encnet('ade20k', 'resnet101', pretrained, root=root, aux=True, + base_size=640, crop_size=576, **kwargs) + +def get_encnet_resnet152_ade(pretrained=False, root='~/.encoding/models', **kwargs): + r"""EncNet-PSP model from the paper `"Context Encoding for Semantic Segmentation" + `_ + + Parameters + ---------- + pretrained : bool, default False + Whether to load the pretrained weights for model. + root : str, default '~/.encoding/models' + Location for keeping the model parameters. + + + Examples + -------- + >>> model = get_encnet_resnet50_ade(pretrained=True) + >>> print(model) + """ + return get_encnet('ade20k', 'resnet152', pretrained, root=root, aux=True, + base_size=520, crop_size=480, **kwargs) diff --git a/encoding/models/fcn.py b/encoding/models/fcn.py index 3f1dac00..47f4c129 100644 --- a/encoding/models/fcn.py +++ b/encoding/models/fcn.py @@ -101,8 +101,7 @@ def get_fcn(dataset='pascal_voc', backbone='resnet50', pretrained=False, if pretrained: from .model_store import get_model_file model.load_state_dict(torch.load( - get_model_file('fcn_%s_%s'%(backbone, acronyms[dataset]), root=root)), - strict= False) + get_model_file('fcn_%s_%s'%(backbone, acronyms[dataset]), root=root))) return model def get_fcn_resnet50_pcontext(pretrained=False, root='~/.encoding/models', **kwargs): diff --git a/encoding/models/model_store.py b/encoding/models/model_store.py index 7e56ef0c..0d6d6651 100644 --- a/encoding/models/model_store.py +++ b/encoding/models/model_store.py @@ -7,15 +7,19 @@ from ..utils import download, check_sha1 _model_sha1 = {name: checksum for checksum, name in [ - ('853f2fb07aeb2927f7696e166b215609a987fd44', 'resnet50'), - ('5be5422ad7cb6a2e5f5a54070d0aa9affe69a9a4', 'resnet101'), - ('6cb047cda851de6aa31963e779fae5f4c299056a', 'deepten_minc'), + ('ebb6acbbd1d1c90b7f446ae59d30bf70c74febc1', 'resnet50'), + ('2a57e44de9c853fa015b172309a1ee7e2d0e4e2a', 'resnet101'), + ('0d43d698c66aceaa2bc0309f55efdd7ff4b143af', 'resnet152'), + ('2e22611a7f3992ebdee6726af169991bc26d7363', 'deepten_minc'), ('fc8c0b795abf0133700c2d4265d2f9edab7eb6cc', 'fcn_resnet50_ade'), ('eeed8e582f0fdccdba8579e7490570adc6d85c7c', 'fcn_resnet50_pcontext'), ('54f70c772505064e30efd1ddd3a14e1759faa363', 'psp_resnet50_ade'), - ('558e8904e123813f23dc0347acba85224650fe5f', 'encnet_resnet50_ade'), - ('7846a2f065e90ce70d268ba8ada1a92251587734', 'encnet_resnet50_pcontext'), - ('6f7c372259988bc2b6d7fc0007182e7835c31a11', 'encnet_resnet101_pcontext'), + ('075195c5237b778c718fd73ceddfa1376c18dfd0', 'deeplab_resnet50_ade'), + ('5ee47ee28b480cc781a195d13b5806d5bbc616bf', 'encnet_resnet101_coco'), + ('4de91d5922d4d3264f678b663f874da72e82db00', 'encnet_resnet50_pcontext'), + ('9f27ea13d514d7010e59988341bcbd4140fcc33d', 'encnet_resnet101_pcontext'), + ('07ac287cd77e53ea583f37454e17d30ce1509a4a', 'encnet_resnet50_ade'), + ('3f54fa3b67bac7619cd9b3673f5c8227cf8f4718', 'encnet_resnet101_ade'), ]} encoding_repo_url = 'https://hangzh.s3.amazonaws.com/' @@ -52,9 +56,10 @@ def get_model_file(name, root=os.path.join('~', '.encoding', 'models')): if check_sha1(file_path, sha1_hash): return file_path else: - print('Mismatch in the content of model file detected. Downloading again.') + print('Mismatch in the content of model file {} detected.' + + ' Downloading again.'.format(file_path)) else: - print('Model file is not found. Downloading.') + print('Model file {} is not found. Downloading.'.format(file_path)) if not os.path.exists(root): os.makedirs(root) diff --git a/encoding/models/model_zoo.py b/encoding/models/model_zoo.py index a79bd454..3870bbfd 100644 --- a/encoding/models/model_zoo.py +++ b/encoding/models/model_zoo.py @@ -3,6 +3,7 @@ from .fcn import * from .psp import * from .encnet import * +from .deeplab import * __all__ = ['get_model'] @@ -29,8 +30,10 @@ def get_model(name, **kwargs): 'encnet_resnet50_pcontext': get_encnet_resnet50_pcontext, 'encnet_resnet101_pcontext': get_encnet_resnet101_pcontext, 'encnet_resnet50_ade': get_encnet_resnet50_ade, + 'encnet_resnet101_ade': get_encnet_resnet101_ade, 'fcn_resnet50_ade': get_fcn_resnet50_ade, 'psp_resnet50_ade': get_psp_resnet50_ade, + 'deeplab_resnet50_ade': get_deeplab_resnet50_ade, } name = name.lower() if name not in models: diff --git a/encoding/models/psp.py b/encoding/models/psp.py index 31e0e4fb..89047f64 100644 --- a/encoding/models/psp.py +++ b/encoding/models/psp.py @@ -58,7 +58,7 @@ def get_psp(dataset='pascal_voc', backbone='resnet50', pretrained=False, 'ade20k': 'ade', } # infer number of classes - from ..datasets import datasets, VOCSegmentation, VOCAugSegmentation, ADE20KSegmentation + from ..datasets import datasets model = PSP(datasets[dataset.lower()].NUM_CLASS, backbone=backbone, root=root, **kwargs) if pretrained: from .model_store import get_model_file diff --git a/encoding/nn/customize.py b/encoding/nn/customize.py index be680238..5df9c638 100644 --- a/encoding/nn/customize.py +++ b/encoding/nn/customize.py @@ -40,7 +40,7 @@ def softmax_crossentropy(input, target, weight, size_average, ignore_index, redu class SegmentationLosses(CrossEntropyLoss): """2D Cross Entropy Loss with Auxilary Loss""" def __init__(self, se_loss=False, se_weight=0.2, nclass=-1, - aux=False, aux_weight=0.2, weight=None, + aux=False, aux_weight=0.4, weight=None, size_average=True, ignore_index=-1): super(SegmentationLosses, self).__init__(weight, size_average, ignore_index) self.se_loss = se_loss @@ -62,14 +62,14 @@ def forward(self, *inputs): pred, se_pred, target = tuple(inputs) se_target = self._get_batch_label_vector(target, nclass=self.nclass).type_as(pred) loss1 = super(SegmentationLosses, self).forward(pred, target) - loss2 = self.bceloss(F.sigmoid(se_pred), se_target) + loss2 = self.bceloss(torch.sigmoid(se_pred), se_target) return loss1 + self.se_weight * loss2 else: pred1, se_pred, pred2, target = tuple(inputs) se_target = self._get_batch_label_vector(target, nclass=self.nclass).type_as(pred1) loss1 = super(SegmentationLosses, self).forward(pred1, target) loss2 = super(SegmentationLosses, self).forward(pred2, target) - loss3 = self.bceloss(F.sigmoid(se_pred), se_target) + loss3 = self.bceloss(torch.sigmoid(se_pred), se_target) return loss1 + self.aux_weight * loss2 + self.se_weight * loss3 @staticmethod diff --git a/encoding/nn/encoding.py b/encoding/nn/encoding.py index edae581c..a92377e3 100644 --- a/encoding/nn/encoding.py +++ b/encoding/nn/encoding.py @@ -15,7 +15,7 @@ from torch.autograd import Variable from torch.nn.modules.utils import _pair -from ..functions import scaledL2, aggregate, pairwise_cosine +from ..functions import scaled_l2, aggregate, pairwise_cosine __all__ = ['Encoding', 'EncodingDrop', 'Inspiration', 'UpsampleConv2d'] @@ -90,18 +90,17 @@ def reset_params(self): def forward(self, X): # input X is a 4D tensor assert(X.size(1) == self.D) + B, D = X.size(0), self.D if X.dim() == 3: - # BxDxN - B, D = X.size(0), self.D + # BxDxN => BxNxD X = X.transpose(1, 2).contiguous() elif X.dim() == 4: - # BxDxHxW - B, D = X.size(0), self.D + # BxDxHxW => Bx(HW)xD X = X.view(B, D, -1).transpose(1, 2).contiguous() else: raise RuntimeError('Encoding Layer unknown input dims!') - # assignment weights NxKxD - A = F.softmax(scaledL2(X, self.codewords, self.scale), dim=1) + # assignment weights BxNxK + A = F.softmax(scaled_l2(X, self.codewords, self.scale), dim=2) # aggregate E = aggregate(A, X, self.codewords) return E @@ -149,7 +148,7 @@ def forward(self, X): raise RuntimeError('Encoding Layer unknown input dims!') self._drop() # assignment weights - A = F.softmax(scaledL2(X, self.codewords, self.scale), dim=1) + A = F.softmax(scaled_l2(X, self.codewords, self.scale), dim=2) # aggregate E = aggregate(A, X, self.codewords) self._drop() diff --git a/encoding/utils/__init__.py b/encoding/utils/__init__.py index 34b8b97f..ac5b20af 100644 --- a/encoding/utils/__init__.py +++ b/encoding/utils/__init__.py @@ -10,7 +10,7 @@ """Encoding Util Tools""" from .lr_scheduler import LR_Scheduler -from .metrics import batch_intersection_union, batch_pix_accuracy +from .metrics import SegmentationMetric, batch_intersection_union, batch_pix_accuracy from .pallete import get_mask_pallete from .train_helper import get_selabel_vector, EMA from .presets import load_image diff --git a/encoding/utils/metrics.py b/encoding/utils/metrics.py index 720c234f..0ae87b88 100644 --- a/encoding/utils/metrics.py +++ b/encoding/utils/metrics.py @@ -8,18 +8,70 @@ ## LICENSE file in the root directory of this source tree ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +import threading import numpy as np import torch -def batch_pix_accuracy(predict, target): +class SegmentationMetric(object): + """Computes pixAcc and mIoU metric scroes + """ + def __init__(self, nclass): + self.nclass = nclass + self.lock = threading.Lock() + self.reset() + + def update(self, labels, preds): + def evaluate_worker(self, label, pred): + correct, labeled = batch_pix_accuracy( + pred, label) + inter, union = batch_intersection_union( + pred, label, self.nclass) + with self.lock: + self.total_correct += correct + self.total_label += labeled + self.total_inter += inter + self.total_union += union + return + + if isinstance(preds, torch.Tensor): + evaluate_worker(self, labels, preds) + elif isinstance(preds, (list, tuple)): + threads = [threading.Thread(target=evaluate_worker, + args=(self, label, pred), + ) + for (label, pred) in zip(labels, preds)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + else: + raise NotImplemented + + def get(self): + pixAcc = 1.0 * self.total_correct / (np.spacing(1) + self.total_label) + IoU = 1.0 * self.total_inter / (np.spacing(1) + self.total_union) + mIoU = IoU.mean() + return pixAcc, mIoU + + def reset(self): + self.total_inter = 0 + self.total_union = 0 + self.total_correct = 0 + self.total_label = 0 + return + + +def batch_pix_accuracy(output, target): """Batch Pixel Accuracy Args: predict: input 4D tensor target: label 3D tensor """ - _, predict = torch.max(predict, 1) - predict = predict.cpu().numpy() + 1 - target = target.cpu().numpy() + 1 + _, predict = torch.max(output, 1) + + predict = predict.cpu().numpy().astype('int64') + 1 + target = target.cpu().numpy().astype('int64') + 1 + pixel_labeled = np.sum(target > 0) pixel_correct = np.sum((predict == target)*(target > 0)) assert pixel_correct <= pixel_labeled, \ @@ -27,19 +79,19 @@ def batch_pix_accuracy(predict, target): return pixel_correct, pixel_labeled -def batch_intersection_union(predict, target, nclass): +def batch_intersection_union(output, target, nclass): """Batch Intersection of Union Args: predict: input 4D tensor target: label 3D tensor nclass: number of categories (int) """ - _, predict = torch.max(predict, 1) + _, predict = torch.max(output, 1) mini = 1 maxi = nclass nbins = nclass - predict = predict.cpu().numpy() + 1 - target = target.cpu().numpy() + 1 + predict = predict.cpu().numpy().astype('int64') + 1 + target = target.cpu().numpy().astype('int64') + 1 predict = predict * (target > 0).astype(predict.dtype) intersection = predict * (predict == target) diff --git a/encoding/utils/pallete.py b/encoding/utils/pallete.py index 20a4eaa9..0d757969 100644 --- a/encoding/utils/pallete.py +++ b/encoding/utils/pallete.py @@ -21,11 +21,10 @@ def get_mask_pallete(npimg, dataset='detail'): out_img.putpalette(adepallete) elif dataset == 'cityscapes': out_img.putpalette(citypallete) - else: + elif dataset in ('detail', 'pascal_voc', 'pascal_aug'): out_img.putpalette(vocpallete) return out_img - def _get_voc_pallete(num_cls): n = num_cls pallete = [0]*(n*3) diff --git a/experiments/recognition/dataset/minc.py b/experiments/recognition/dataset/minc.py index d13eb270..69b64381 100644 --- a/experiments/recognition/dataset/minc.py +++ b/experiments/recognition/dataset/minc.py @@ -94,9 +94,9 @@ def __init__(self, args): normalize, ]) - trainset = MINCDataloder(root=os.path.expanduser('~/data/minc-2500/'), + trainset = MINCDataloder(root=os.path.expanduser('~/.encoding/data/minc-2500/'), train=True, transform=transform_train) - testset = MINCDataloder(root=os.path.expanduser('~/data/minc-2500/'), + testset = MINCDataloder(root=os.path.expanduser('~/.encoding/data/minc-2500/'), train=False, transform=transform_test) kwargs = {'num_workers': 8, 'pin_memory': True} if args.cuda else {} diff --git a/experiments/recognition/main.py b/experiments/recognition/main.py index 4577c7a1..6a97281c 100644 --- a/experiments/recognition/main.py +++ b/experiments/recognition/main.py @@ -96,7 +96,7 @@ def train(epoch): train_loss += loss.data.item() pred = output.data.max(1)[1] - correct += pred.eq(target.data).cpu().sum() + correct += pred.eq(target.data).cpu().sum().item() total += target.size(0) err = 100.0 - 100.0 * correct / total tbar.set_description('\rLoss: %.3f | Err: %.3f%% (%d/%d)' % \ diff --git a/experiments/recognition/model/download_models.py b/experiments/recognition/model/download_models.py index 9383e4dd..131e6882 100644 --- a/experiments/recognition/model/download_models.py +++ b/experiments/recognition/model/download_models.py @@ -2,4 +2,4 @@ import shutil encoding.models.get_model_file('deepten_minc', root='./') -shutil.move('deepten_minc-6cb047cd.pth', 'deepten_minc.pth') +shutil.move('deepten_minc-2e22611a.pth', 'deepten_minc.pth') diff --git a/experiments/recognition/model/mynn.py b/experiments/recognition/model/mynn.py index cff34b7f..cd68f36e 100644 --- a/experiments/recognition/model/mynn.py +++ b/experiments/recognition/model/mynn.py @@ -86,6 +86,46 @@ def forward(self, x): return residual + self.conv_block(x) ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +class EncLayerV2(nn.Module): + def __init__(self, channel, K=16, reduction=4): + super(EncLayerV2, self).__init__() + out_channel = int(channel / reduction) + self.fc = nn.Sequential( + nn.Conv2d(channel, out_channel, 1), + nn.BatchNorm2d(out_channel), + nn.ReLU(inplace=True), + encoding.nn.EncodingV2(D=out_channel,K=K), + encoding.nn.View(-1, out_channel*K), + encoding.nn.Normalize(), + nn.Linear(out_channel*K, channel), + nn.Sigmoid() + ) + + def forward(self, x): + b, c, _, _ = x.size() + y = self.fc(x).view(b, c, 1, 1) + return x * y + +class EncLayerV3(nn.Module): + def __init__(self, channel, K=16, reduction=4): + super(EncLayerV3, self).__init__() + out_channel = int(channel / reduction) + self.fc = nn.Sequential( + nn.Conv2d(channel, out_channel, 1), + nn.BatchNorm2d(out_channel), + nn.ReLU(inplace=True), + encoding.nn.EncodingV3(D=out_channel,K=K), + encoding.nn.View(-1, out_channel*K), + encoding.nn.Normalize(), + nn.Linear(out_channel*K, channel), + nn.Sigmoid() + ) + + def forward(self, x): + b, c, _, _ = x.size() + y = self.fc(x).view(b, c, 1, 1) + return x * y + class EncLayer(nn.Module): def __init__(self, channel, K=16, reduction=4): super(EncLayer, self).__init__() diff --git a/experiments/recognition/option.py b/experiments/recognition/option.py index 722d10f7..3f8eef79 100644 --- a/experiments/recognition/option.py +++ b/experiments/recognition/option.py @@ -40,8 +40,8 @@ def __init__(self): # lr setting parser.add_argument('--lr', type=float, default=0.1, metavar='LR', help='learning rate (default: 0.1)') - parser.add_argument('--lr-scheduler', type=str, default='step', - help='learning rate scheduler (default: step)') + parser.add_argument('--lr-scheduler', type=str, default='cos', + help='learning rate scheduler (default: cos)') parser.add_argument('--lr-step', type=int, default=40, metavar='LR', help='learning rate step (default: 40)') # optimizer diff --git a/experiments/segmentation/option.py b/experiments/segmentation/option.py index c3ba2aed..eb5fad56 100644 --- a/experiments/segmentation/option.py +++ b/experiments/segmentation/option.py @@ -25,15 +25,21 @@ def __init__(self): $(HOME)/data)') parser.add_argument('--workers', type=int, default=16, metavar='N', help='dataloader threads') - parser.add_argument('--base-size', type=int, default=608, + parser.add_argument('--base-size', type=int, default=520, help='base image size') - parser.add_argument('--crop-size', type=int, default=576, + parser.add_argument('--crop-size', type=int, default=480, help='crop image size') + parser.add_argument('--train-split', type=str, default='train', + help='dataset train split (default: train)') # training hyper params parser.add_argument('--aux', action='store_true', default= False, help='Auxilary Loss') + parser.add_argument('--aux-weight', type=float, default=0.2, + help='Auxilary loss weight (default: 0.2)') parser.add_argument('--se-loss', action='store_true', default= False, help='Semantic Encoding Loss SE-loss') + parser.add_argument('--se-weight', type=float, default=0.2, + help='SE-loss weight (default: 0.2)') parser.add_argument('--epochs', type=int, default=None, metavar='N', help='number of epochs to train (default: auto)') parser.add_argument('--start_epoch', type=int, default=0, @@ -68,12 +74,7 @@ def __init__(self): # finetuning pre-trained models parser.add_argument('--ft', action='store_true', default= False, help='finetuning on a different dataset') - parser.add_argument('--pre-class', type=int, default=None, - help='num of pre-trained classes \ - (default: None)') # evaluation option - parser.add_argument('--ema', action='store_true', default= False, - help='using EMA evaluation') parser.add_argument('--eval', action='store_true', default= False, help='evaluating mIoU') parser.add_argument('--no-val', action='store_true', default= False, @@ -90,10 +91,12 @@ def parse(self): # default settings for epochs, batch_size and lr if args.epochs is None: epoches = { + 'coco': 30, + 'citys': 180, 'pascal_voc': 50, 'pascal_aug': 50, 'pcontext': 80, - 'ade20k': 160, + 'ade20k': 120, } args.epochs = epoches[args.dataset.lower()] if args.batch_size is None: @@ -102,10 +105,13 @@ def parse(self): args.test_batch_size = args.batch_size if args.lr is None: lrs = { + 'coco': 0.01, + 'citys': 0.01, 'pascal_voc': 0.0001, 'pascal_aug': 0.001, 'pcontext': 0.001, 'ade20k': 0.01, } args.lr = lrs[args.dataset.lower()] / 16 * args.batch_size + print(args) return args diff --git a/experiments/segmentation/test.py b/experiments/segmentation/test.py index 3320ecbd..6e2fe89f 100644 --- a/experiments/segmentation/test.py +++ b/experiments/segmentation/test.py @@ -21,10 +21,6 @@ from option import Options -torch_ver = torch.__version__[:3] -if torch_ver == '0.3': - from torch.autograd import Variable - def test(args): # output folder outdir = 'outdir' @@ -64,58 +60,29 @@ def test(args): print("=> loaded checkpoint '{}' (epoch {})".format(args.resume, checkpoint['epoch'])) print(model) - evaluator = MultiEvalModule(model, testset.num_class).cuda() + scales = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25] if args.dataset == 'citys' else \ + [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] + evaluator = MultiEvalModule(model, testset.num_class, scales=scales).cuda() evaluator.eval() + metric = utils.SegmentationMetric(testset.num_class) tbar = tqdm(test_data) - def eval_batch(image, dst, evaluator, eval_mode): - if eval_mode: - # evaluation mode on validation set - targets = dst - outputs = evaluator.parallel_forward(image) - batch_inter, batch_union, batch_correct, batch_label = 0, 0, 0, 0 - for output, target in zip(outputs, targets): - correct, labeled = utils.batch_pix_accuracy(output.data.cpu(), target) - inter, union = utils.batch_intersection_union( - output.data.cpu(), target, testset.num_class) - batch_correct += correct - batch_label += labeled - batch_inter += inter - batch_union += union - return batch_correct, batch_label, batch_inter, batch_union + for i, (image, dst) in enumerate(tbar): + if args.eval: + with torch.no_grad(): + predicts = evaluator.parallel_forward(image) + metric.update(dst, predicts) + pixAcc, mIoU = metric.get() + tbar.set_description( 'pixAcc: %.4f, mIoU: %.4f' % (pixAcc, mIoU)) else: - # test mode, dump the results - im_paths = dst - outputs = evaluator.parallel_forward(image) - predicts = [torch.max(output, 1)[1].cpu().numpy() + testset.pred_offset - for output in outputs] - for predict, impath in zip(predicts, im_paths): + with torch.no_grad(): + outputs = evaluator.parallel_forward(image) + predicts = [testset.make_pred(torch.max(output, 1)[1].cpu().numpy()) + for output in outputs] + for predict, impath in zip(predicts, dst): mask = utils.get_mask_pallete(predict, args.dataset) outname = os.path.splitext(impath)[0] + '.png' mask.save(os.path.join(outdir, outname)) - # dummy outputs for compatible with eval mode - return 0, 0, 0, 0 - - total_inter, total_union, total_correct, total_label = \ - np.int64(0), np.int64(0), np.int64(0), np.int64(0) - for i, (image, dst) in enumerate(tbar): - if torch_ver == "0.3": - image = Variable(image, volatile=True) - correct, labeled, inter, union = eval_batch(image, dst, evaluator, args.eval) - else: - with torch.no_grad(): - correct, labeled, inter, union = eval_batch(image, dst, evaluator, args.eval) - if args.eval: - total_correct += correct.astype('int64') - total_label += labeled.astype('int64') - total_inter += inter.astype('int64') - total_union += union.astype('int64') - pixAcc = np.float64(1.0) * total_correct / (np.spacing(1, dtype=np.float64) + total_label) - IoU = np.float64(1.0) * total_inter / (np.spacing(1, dtype=np.float64) + total_union) - mIoU = IoU.mean() - tbar.set_description( - 'pixAcc: %.4f, mIoU: %.4f' % (pixAcc, mIoU)) - if __name__ == "__main__": args = Options().parse() diff --git a/experiments/segmentation/train.py b/experiments/segmentation/train.py index 1e3c90e4..a3f3345d 100644 --- a/experiments/segmentation/train.py +++ b/experiments/segmentation/train.py @@ -36,7 +36,7 @@ def __init__(self, args): # dataset data_kwargs = {'transform': input_transform, 'base_size': args.base_size, 'crop_size': args.crop_size} - trainset = get_segmentation_dataset(args.dataset, split='train', mode='train', + trainset = get_segmentation_dataset(args.dataset, split=args.train_split, mode='train', **data_kwargs) testset = get_segmentation_dataset(args.dataset, split='val', mode ='val', **data_kwargs) @@ -60,16 +60,13 @@ def __init__(self, args): params_list.append({'params': model.head.parameters(), 'lr': args.lr*10}) if hasattr(model, 'auxlayer'): params_list.append({'params': model.auxlayer.parameters(), 'lr': args.lr*10}) - optimizer = torch.optim.SGD(params_list, - lr=args.lr, - momentum=args.momentum, - weight_decay=args.weight_decay) - # clear start epoch if fine-tuning - if args.ft: - args.start_epoch = 0 + optimizer = torch.optim.SGD(params_list, lr=args.lr, + momentum=args.momentum, weight_decay=args.weight_decay) # criterions self.criterion = SegmentationLosses(se_loss=args.se_loss, aux=args.aux, - nclass=self.nclass) + nclass=self.nclass, + se_weight=args.se_weight, + aux_weight=args.aux_weight) self.model, self.optimizer = model, optimizer # using cuda if args.cuda: @@ -90,6 +87,9 @@ def __init__(self, args): self.best_pred = checkpoint['best_pred'] print("=> loaded checkpoint '{}' (epoch {})" .format(args.resume, checkpoint['epoch'])) + # clear start epoch if fine-tuning + if args.ft: + args.start_epoch = 0 # lr scheduler self.scheduler = utils.LR_Scheduler(args.lr_scheduler, args.lr, args.epochs, len(self.trainloader)) @@ -172,9 +172,9 @@ def eval_batch(model, image, target): args = Options().parse() torch.manual_seed(args.seed) trainer = Trainer(args) - print('Starting Epoch:', args.start_epoch) - print('Total Epoches:', args.epochs) - for epoch in range(args.start_epoch, args.epochs): + print('Starting Epoch:', trainer.args.start_epoch) + print('Total Epoches:', trainer.args.epochs) + for epoch in range(trainer.args.start_epoch, trainer.args.epochs): trainer.training(epoch) - if not args.no_val: + if not trainer.args.no_val: trainer.validation(epoch) diff --git a/scripts/prepare_cityscapes.py b/scripts/prepare_cityscapes.py new file mode 100644 index 00000000..19722cb8 --- /dev/null +++ b/scripts/prepare_cityscapes.py @@ -0,0 +1,48 @@ +"""Prepare ADE20K dataset""" +import os +import shutil +import argparse +import zipfile +from encoding.utils import download, mkdir, check_sha1 + +_TARGET_DIR = os.path.expanduser('~/.encoding/data') + +def parse_args(): + parser = argparse.ArgumentParser( + description='Initialize ADE20K dataset.', + epilog='Example: python prepare_cityscapes.py', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--download-dir', default=None, help='dataset directory on disk') + args = parser.parse_args() + return args + +def download_city(path, overwrite=False): + _CITY_DOWNLOAD_URLS = [ + #('gtCoarse.zip', '61f23198bfff5286e0d7e316ad5c4dbbaaf4717a'), + ('gtFine_trainvaltest.zip', '99f532cb1af174f5fcc4c5bc8feea8c66246ddbc'), + ('leftImg8bit_trainvaltest.zip', '2c0b77ce9933cc635adda307fbba5566f5d9d404')] + download_dir = os.path.join(path, 'downloads') + mkdir(download_dir) + for filename, checksum in _CITY_DOWNLOAD_URLS: + if not check_sha1(filename, checksum): + raise UserWarning('File {} is downloaded but the content hash does not match. ' \ + 'The repo may be outdated or download may be incomplete. ' \ + 'If the "repo_url" is overridden, consider switching to ' \ + 'the default repo.'.format(filename)) + # extract + with zipfile.ZipFile(filename,"r") as zip_ref: + zip_ref.extractall(path=path) + print("Extracted", filename) + +if __name__ == '__main__': + args = parse_args() + mkdir(os.path.expanduser('~/.encoding/data')) + mkdir(os.path.expanduser('~/.encoding/data/cityscapes')) + if args.download_dir is not None: + if os.path.isdir(_TARGET_DIR): + os.remove(_TARGET_DIR) + # make symlink + os.symlink(args.download_dir, _TARGET_DIR) + else: + download_city(_TARGET_DIR, overwrite=False) + diff --git a/scripts/prepare_coco.py b/scripts/prepare_coco.py index 55ee5299..ecbe5c35 100644 --- a/scripts/prepare_coco.py +++ b/scripts/prepare_coco.py @@ -24,10 +24,10 @@ def download_coco(path, overwrite=False): '8551ee4bb5860311e79dace7e79cb91e432e78b3'), ('http://images.cocodataset.org/zips/val2017.zip', '4950dc9d00dbe1c933ee0170f5797584351d2a41'), - ('http://images.cocodataset.org/annotations/stuff_annotations_trainval2017.zip', - 'e7aa0f7515c07e23873a9f71d9095b06bcea3e12'), - ('http://images.cocodataset.org/zips/test2017.zip', - '99813c02442f3c112d491ea6f30cecf421d0e6b3'), + #('http://images.cocodataset.org/annotations/stuff_annotations_trainval2017.zip', + # '46cdcf715b6b4f67e980b529534e79c2edffe084'), + #('http://images.cocodataset.org/zips/test2017.zip', + # '99813c02442f3c112d491ea6f30cecf421d0e6b3'), ] mkdir(path) for url, checksum in _DOWNLOAD_URLS: diff --git a/scripts/prepare_minc.py b/scripts/prepare_minc.py new file mode 100644 index 00000000..fc37d91e --- /dev/null +++ b/scripts/prepare_minc.py @@ -0,0 +1,40 @@ +import os +import shutil +import argparse +import tarfile +from encoding.utils import download, mkdir + +_TARGET_DIR = os.path.expanduser('~/.encoding/data') + +def parse_args(): + parser = argparse.ArgumentParser( + description='Initialize MINC dataset.', + epilog='Example: python prepare_minc.py', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--download-dir', type=str, default=None, help='dataset directory on disk') + parser.add_argument('--no-download', action='store_true', help='disable automatic download if set') + parser.add_argument('--overwrite', action='store_true', + help='overwrite downloaded files if set, in case they are corrputed') + args = parser.parse_args() + return args + +def download_minc(path, overwrite=False): + _AUG_DOWNLOAD_URLS = [ + ('http://opensurfaces.cs.cornell.edu/static/minc/minc-2500.tar.gz', 'bcccbb3b1ab396ef540f024a5ba23eff54f7fe31')] + download_dir = os.path.join(path, 'downloads') + mkdir(download_dir) + for url, checksum in _AUG_DOWNLOAD_URLS: + filename = download(url, path=download_dir, overwrite=overwrite, sha1_hash=checksum) + # extract + with tarfile.open(filename) as tar: + tar.extractall(path=path) + +if __name__ == '__main__': + args = parse_args() + mkdir(os.path.expanduser('~/.encoding/datasets')) + if args.download_dir is not None: + if os.path.isdir(_TARGET_DIR): + os.remove(_TARGET_DIR) + os.symlink(args.download_dir, _TARGET_DIR) + else: + download_minc(_TARGET_DIR, overwrite=False) diff --git a/setup.py b/setup.py index 445e445b..f7d4ff1e 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ cwd = os.path.dirname(os.path.abspath(__file__)) -version = '0.4.5' +version = '0.5.0' try: sha = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=cwd).decode('ascii').strip() diff --git a/tests/unit_test/test_function.py b/tests/unit_test/test_function.py new file mode 100644 index 00000000..c29f0c67 --- /dev/null +++ b/tests/unit_test/test_function.py @@ -0,0 +1,238 @@ +##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +## Created by: Hang Zhang +## ECE Department, Rutgers University +## Email: zhang.hang@rutgers.edu +## Copyright (c) 2017 +## +## This source code is licensed under the MIT-style license found in the +## LICENSE file in the root directory of this source tree +##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import numpy as np +import torch +from torch.autograd import Variable, gradcheck +import encoding + +EPS = 1e-3 +ATOL = 1e-3 + +def _assert_tensor_close(a, b, atol=ATOL, rtol=EPS): + npa, npb = a.cpu().numpy(), b.cpu().numpy() + assert np.allclose(npa, npb, rtol=rtol, atol=atol), \ + 'Tensor close check failed\n{}\n{}\nadiff={}, rdiff={}'.format( + a, b, np.abs(npa - npb).max(), np.abs((npa - npb) / np.fmax(npa, 1e-5)).max()) + +def test_aggregate(): + B,N,K,D = 2,3,4,5 + A = Variable(torch.cuda.DoubleTensor(B,N,K).uniform_(-0.5,0.5), + requires_grad=True) + X = Variable(torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5), + requires_grad=True) + C = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), + requires_grad=True) + input = (A, X, C) + test = gradcheck(encoding.functions.aggregate, input, eps=EPS, atol=ATOL) + print('Testing aggregate(): {}'.format(test)) + +def test_scaled_l2(): + B,N,K,D = 2,3,4,5 + X = Variable(torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5), + requires_grad=True) + C = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), + requires_grad=True) + S = Variable(torch.cuda.DoubleTensor(K).uniform_(-0.5,0.5), + requires_grad=True) + input = (X, C, S) + test = gradcheck(encoding.functions.scaled_l2, input, eps=EPS, atol=ATOL) + print('Testing scaled_l2(): {}'.format(test)) + +def test_aggregate_v2(): + def py_aggregate_v2(A, X, C, STD, S): + B, N, D = X.size() + K = C.size(0) + #e_{k} = \sum_{i=1}^{N} a_{ik} (x_i - d_k) / \sigma_k + R = (X.view(B, N, 1, D).expand(B, N, K, D) - \ + C.view(1, 1, K, D).expand(B, N, K, D)) / STD.view(1, 1, K, D) + #E = 1.0 / torch.sqrt(S + 1e-5).unsqueeze(0).unsqueeze(2) * (A.unsqueeze(3) * R).sum(1) + E2 = (A.unsqueeze(3) * R).sum(1) + return E2 + + B,N,K,D = 2,3,4,5 + A = Variable(torch.cuda.DoubleTensor(B,N,K).uniform_(-0.5,0.5), + requires_grad=True) + X = Variable(torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5), + requires_grad=True) + C = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), + requires_grad=True) + STD = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), + requires_grad=True) + S = Variable(torch.cuda.DoubleTensor(K).uniform_(-0.5,0.5), + requires_grad=True) + + A2 = torch.from_numpy(A.detach().cpu().numpy()).cuda() + X2 = torch.from_numpy(X.detach().cpu().numpy()).cuda() + C2 = torch.from_numpy(C.detach().cpu().numpy()).cuda() + STD2 = torch.from_numpy(STD.detach().cpu().numpy()).cuda() + S2 = torch.from_numpy(S.detach().cpu().numpy()).cuda() + A2.requires_grad_() + X2.requires_grad_() + C2.requires_grad_() + STD2.requires_grad_() + S2.requires_grad_() + + E = encoding.functions.aggregate_v2(A, X, C, STD) + E2 = py_aggregate_v2(A2, X2, C2, STD2, S2) + _assert_tensor_close(E.detach(), E2.detach()) + + input = (A, X, C, STD) + test = gradcheck(encoding.functions.aggregate_v2, input, eps=EPS, atol=ATOL) + print('Testing aggregate_v2(): {}'.format(test)) + +def test_encoding_dist(): + def mahalanobis_dist(X, C): + B, N, D = X.size() + K = C.size(0) + # X \in BxNxD, C \in KxD + R = X.view(B, N, 1, D).expand(B, N, K, D) - \ + C.view(1, 1, K, D).expand(B, N, K, D) + STD = torch.sqrt(R.pow(2).mean(0).mean(0) + 1e-6) + KD = (R / STD.view(1,1,K,D)).pow(2).sum(3) + return KD, STD + + B,N,K,D = 2,3,4,5 + RVar = torch.cuda.DoubleTensor(K,D).zero_() + X = torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5) + C = torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5) + X.requires_grad_() + C.requires_grad_() + + X2 = torch.from_numpy(X.detach().cpu().numpy()).cuda() + C2 = torch.from_numpy(C.detach().cpu().numpy()).cuda() + X2.requires_grad_() + C2.requires_grad_() + # assert numeric correctness + KD, STD, Var_ = encoding.functions.encoding_dist(X, C, 1e-6) + KD2, STD2 = mahalanobis_dist(X2, C2) + _assert_tensor_close(STD.detach(), STD2.detach()) + _assert_tensor_close(KD.detach(), KD2.detach()) + # check backward + loss1 = KD.pow(2).sum() + STD.sum() + loss1.backward() + loss2 = KD2.pow(2).sum() + STD2.sum() + loss2.backward() + _assert_tensor_close(X.grad.detach(), X2.grad.detach()) + _assert_tensor_close(C.grad.detach(), C2.grad.detach()) + + input = (X, C, 1e-6) + test = gradcheck(encoding.functions.encoding_dist, input, eps=EPS, atol=ATOL) + print('Testing encoding_dist(): {}'.format(test)) + +def test_encoding_dist_inference(): + def mahalanobis_dist(X, C, STD): + B, N, D = X.size() + K = C.size(0) + # X \in BxNxD, C \in KxD + R = X.view(B, N, 1, D).expand(B, N, K, D) - \ + C.view(1, 1, K, D).expand(B, N, K, D) + #STD = torch.sqrt(R.pow(2).mean(0).mean(0) + 1e-6) + KD = (R / STD.view(1,1,K,D)).pow(2).sum(3) + return KD + + B,N,K,D = 2,3,4,5 + X = Variable(torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5), + requires_grad=True) + C = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), + requires_grad=True) + STD = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), + requires_grad=True) + + X2 = torch.from_numpy(X.detach().cpu().numpy()).cuda() + C2 = torch.from_numpy(C.detach().cpu().numpy()).cuda() + STD2 = torch.from_numpy(STD.detach().cpu().numpy()).cuda() + X2.requires_grad_() + C2.requires_grad_() + STD2.requires_grad_() + + E = encoding.functions.encoding_dist_inference(X, C, STD) + E2 = mahalanobis_dist(X2, C2, STD2) + + loss1 = E.pow(2).sum() + loss2 = E2.pow(2).sum() + loss1.backward() + loss2.backward() + + print('X.grad', X.grad) + print('X2.grad', X2.grad) + + _assert_tensor_close(E.detach(), E2.detach()) + _assert_tensor_close(X.grad.detach(), X2.grad.detach()) + _assert_tensor_close(C.grad.detach(), C2.grad.detach()) + _assert_tensor_close(STD.grad.detach(), STD2.grad.detach()) + + input = (X, C, STD) + test = gradcheck(encoding.functions.encoding_dist_inference, input, eps=EPS, atol=ATOL) + print('Testing encoding_dist_inference(): {}'.format(test)) + +def test_sum_square(): + B,C,H = 2,3,4 + X = Variable(torch.cuda.DoubleTensor(B,C,H).uniform_(-0.5,0.5), + requires_grad=True) + input = (X,) + test = gradcheck(encoding.functions.sum_square, input, eps=EPS, atol=ATOL) + print('Testing sum_square(): {}'.format(test)) + +def test_syncbn_func(): + # generate input + B, C, H = 2, 3, 4 + X = Variable(torch.cuda.DoubleTensor(B,C,H).uniform_(-0.5, 0.5), + requires_grad=True) + gamma = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) + beta = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) + mean = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) + std = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) + N = B * H + inputs = (X, mean, std, gamma, beta) + # grad check + test = gradcheck(encoding.functions.batchnormtrain, inputs, eps=EPS, atol=ATOL) + print('Testing batchnorm(): {}'.format(test)) + +def test_non_max_suppression(): + def _test_nms(cuda): + # check a small test case + boxes = torch.Tensor([ + [[10.2, 23., 50., 20.], + [11.3, 23., 52., 20.1], + [23.2, 102.3, 23.3, 50.3], + [101.2, 32.4, 70.6, 70.], + [100.2, 30.9, 70.7, 69.]], + [[200.3, 234., 530., 320.], + [110.3, 223., 152., 420.1], + [243.2, 240.3, 50.3, 30.3], + [243.2, 236.4, 48.6, 30.], + [100.2, 310.9, 170.7, 691.]]]) + + scores = torch.Tensor([ + [0.9, 0.7, 0.11, 0.23, 0.8], + [0.13, 0.89, 0.45, 0.23, 0.3]]) + + if cuda: + boxes = boxes.cuda() + scores = scores.cuda() + + expected_output = ( + torch.ByteTensor( + [[1, 1, 0, 0, 1], [1, 1, 1, 0, 1]]), + torch.LongTensor( + [[0, 4, 1, 3, 2], [1, 2, 4, 3, 0]]) + ) + + mask, inds = encoding.functions.NonMaxSuppression(boxes, scores, 0.7) + _assert_tensor_close(mask, expected_output[0]) + _assert_tensor_close(inds, expected_output[1]) + + _test_nms(False) + _test_nms(True) + +if __name__ == '__main__': + import nose + nose.runmodule() diff --git a/tests/unit_test/test_module.py b/tests/unit_test/test_module.py index f44c9d3c..90d05793 100644 --- a/tests/unit_test/test_module.py +++ b/tests/unit_test/test_module.py @@ -9,10 +9,8 @@ ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import numpy as np - import torch from torch.autograd import Variable, gradcheck - import encoding EPS = 1e-3 @@ -24,32 +22,6 @@ def _assert_tensor_close(a, b, atol=ATOL, rtol=EPS): 'Tensor close check failed\n{}\n{}\nadiff={}, rdiff={}'.format( a, b, np.abs(npa - npb).max(), np.abs((npa - npb) / np.fmax(npa, 1e-5)).max()) -def test_aggregate(): - B,N,K,D = 2,3,4,5 - A = Variable(torch.cuda.DoubleTensor(B,N,K).uniform_(-0.5,0.5), - requires_grad=True) - X = Variable(torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5), - requires_grad=True) - C = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), - requires_grad=True) - input = (A, X, C) - test = gradcheck(encoding.functions.aggregate, input, eps=EPS, atol=ATOL) - print('Testing aggregate(): {}'.format(test)) - - -def test_scaledL2(): - B,N,K,D = 2,3,4,5 - X = Variable(torch.cuda.DoubleTensor(B,N,D).uniform_(-0.5,0.5), - requires_grad=True) - C = Variable(torch.cuda.DoubleTensor(K,D).uniform_(-0.5,0.5), - requires_grad=True) - S = Variable(torch.cuda.DoubleTensor(K).uniform_(-0.5,0.5), - requires_grad=True) - input = (X, C, S) - test = gradcheck(encoding.functions.scaledL2, input, eps=EPS, atol=ATOL) - print('Testing scaledL2(): {}'.format(test)) - - def test_encoding(): B,C,H,W,K = 2,3,4,5,6 X = Variable(torch.cuda.DoubleTensor(B,C,H,W).uniform_(-0.5,0.5), @@ -59,16 +31,6 @@ def test_encoding(): test = gradcheck(layer, input, eps=EPS, atol=ATOL) print('Testing encoding(): {}'.format(test)) - -def test_sum_square(): - B,C,H = 2,3,4 - X = Variable(torch.cuda.DoubleTensor(B,C,H).uniform_(-0.5,0.5), - requires_grad=True) - input = (X,) - test = gradcheck(encoding.functions.sum_square, input, eps=EPS, atol=ATOL) - print('Testing sum_square(): {}'.format(test)) - - def test_all_reduce(): ngpu = torch.cuda.device_count() X = [torch.DoubleTensor(2,4,4).uniform_(-0.5,0.5).cuda(i) for i in range(ngpu)] @@ -82,42 +44,8 @@ def test_all_reduce(): test = gradcheck(encoding.parallel.allreduce, input, eps=EPS, atol=ATOL) print('Testing allreduce(): {}'.format(test)) - -def test_syncbn(): - train_mode=True - # generate input - B,C,H,W = 8,3,4,5 - X = Variable(torch.cuda.DoubleTensor(B,C,H,W).uniform_(-0.5,0.5), - requires_grad=True) - input = (X,) - # SyncBN using DataParallel - layer = encoding.nn.BatchNorm2d(C) - model = torch.nn.DataParallel(layer).double().cuda() - encoding.parallel.patch_replication_callback(model) - layer.train(train_mode) - # grad check - test = gradcheck(model, input, eps=EPS, atol=ATOL) - print('Testing BatchNorm2d(): {}'.format(test)) - - -def test_syncbn_func(): - # generate input - B, C, H = 2, 3, 4 - X = Variable(torch.cuda.DoubleTensor(B,C,H).uniform_(-0.5, 0.5), - requires_grad=True) - gamma = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) - beta = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) - mean = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) - std = Variable(torch.cuda.DoubleTensor(C).uniform_(-0.5, 0.5), requires_grad=True) - N = B * H - inputs = (X, mean, std, gamma, beta) - # grad check - test = gradcheck(encoding.functions.batchnormtrain, inputs, eps=EPS, atol=ATOL) - print('Testing batchnorm(): {}'.format(test)) - - def testSyncBN(): - def _checkBatchNormResult(bn1, bn2, input, is_train, cuda=False): + def _check_batchnorm_result(bn1, bn2, input, is_train, cuda=False): def _find_bn(module): for m in module.modules(): if isinstance(m, (torch.nn.BatchNorm1d, torch.nn.BatchNorm2d, @@ -163,8 +91,8 @@ def _syncParameters(bn1, bn2): # check with unsync version for i in range(10): print(i) - _checkBatchNormResult(bn, sync_bn, torch.rand(16, 10, 16, 16).double(), True, cuda=True) - _checkBatchNormResult(bn, sync_bn, torch.rand(16, 10, 16, 16).double(), False, cuda=True) + _check_batchnorm_result(bn, sync_bn, torch.rand(16, 10, 16, 16).double(), True, cuda=True) + _check_batchnorm_result(bn, sync_bn, torch.rand(16, 10, 16, 16).double(), False, cuda=True) if __name__ == '__main__': import nose diff --git a/tests/unit_test/test_utils.py b/tests/unit_test/test_utils.py new file mode 100644 index 00000000..3f78ca6a --- /dev/null +++ b/tests/unit_test/test_utils.py @@ -0,0 +1,29 @@ +import torch +import numpy as np +from encoding.utils.metrics import * + +def test_segmentation_metrics(): + # check torch evaluation metrics + rows, cols = 640, 480 + nclass = 30 + # numpy data + im_lab = np.matrix(np.random.randint(0, nclass, size=(rows, cols))) + mask = np.random.random((nclass, rows, cols)) + im_pred = mask.argmax(axis=0) + # torch data + tim_lab = torch.from_numpy(im_lab).unsqueeze(0).long() + tim_pred = torch.from_numpy(mask).unsqueeze(0) + # numpy prediction + pixel_correct, pixel_labeled = pixel_accuracy(im_pred, im_lab) + area_inter, area_union = intersection_and_union(im_pred, im_lab, nclass) + pixAcc = 1.0 * pixel_correct / (np.spacing(1) + pixel_labeled) + IoU = 1.0 * area_inter / (np.spacing(1) + area_union) + mIoU = IoU.mean() + print('numpy predictionis :',pixAcc, mIoU) + # torch metric prediction + pixel_correct, pixel_labeled = batch_pix_accuracy(tim_pred, tim_lab) + area_inter, area_union = batch_intersection_union(tim_pred, tim_lab, nclass) + pixAcc = 1.0 * pixel_correct / (np.spacing(1) + pixel_labeled) + IoU = 1.0 * area_inter / (np.spacing(1) + area_union) + mIoU = IoU.mean() + print('torch predictionis :',pixAcc, mIoU)