From e51f9c488744855dcb0ed3c93bbe87ec235c4f47 Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Sat, 9 Sep 2023 16:54:10 +0900 Subject: [PATCH 01/10] SI Unit of um and mm for gwy added Some of .gwy files raise ValueError in io.py. ValueError: Units 'um' have not been added for .gwy files --- topostats/io.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/topostats/io.py b/topostats/io.py index 7a65b22552..08ccc35c6a 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -853,6 +853,10 @@ def load_gwy(self) -> tuple: # Convert image heights to nanometresQ if units == "m": image = image * 1e9 + elif units == "mm": + image = image * 1e6 + elif units == "um": + image = image * 1e3 else: raise ValueError( f"Units '{units}' have not been added for .gwy files. Please add \ From ecbc120c5a695658020a8733d2b3574174d43cee Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Mon, 11 Sep 2023 08:58:54 +0900 Subject: [PATCH 02/10] Correct Loading gwy channels and units # XY, Z units correction * units for px_to_nm to multiply data array should be picked up from 'si_unit_z' instetad of 'si_unit_xy'. * Non-square image should be considered. Probably, px_to_nm_z, px_to_nm_x, px_to_nm_y will be necessary. # Channel index Data key of gwy sometimes starts from 1 or larger. (ex. '/4/data') It happens when image is cropped on Gwyddion, and it remains after saving even other channels are removed. A loop to scan indices was added. And it guesses whether if the channel is topography or not by si_unit_z. --- topostats/io.py | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/topostats/io.py b/topostats/io.py index 08ccc35c6a..84d4bddf6d 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -836,19 +836,39 @@ def load_gwy(self) -> tuple: # dictionary output showing the object - component structure and # available keys: # LoadScans._gwy_print_dict_wrapper(gwy_file_dict=image_data_dict) + image = None + has_image_found = False + import regex + re = r'\/(\d+)\/data$' + components = list(image_data_dict.keys()) + for component in components: + match = regex.match(re, component) + if match == None: + continue + idx = int(match[1]) + LOGGER.info(f"Channel found at {idx}") + channel_dict = image_data_dict[component] + LOGGER.info(f"Guessing if this chchannel is height") + for key in channel_dict.keys(): + if key=='si_unit_z' and channel_dict[key]['unitstr']=='m': + LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography.") + if not has_image_found: + image = image_data_dict[component]["data"] + units = image_data_dict[component]["si_unit_z"]["unitstr"] + px_to_nm = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] + # TODO: xy units and z units should be separately considered. + # added parameters for xy conversion support for non-square image + px_to_nm_x = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] + px_to_nm_y = image_data_dict[component]["yreal"] * 1e9 / image.shape[0] + has_image_found = True + else: + if not key == 'data': + LOGGER.info(f"\t{key} : {channel_dict[key]}") - if "/0/data" in image_data_dict: - image = image_data_dict["/0/data"]["data"] - units = image_data_dict["/0/data"]["si_unit_xy"]["unitstr"] - px_to_nm = image_data_dict["/0/data"]["xreal"] * 1e9 / image.shape[1] - elif "/1/data" in image_data_dict: - image = image_data_dict["/1/data"]["data"] - px_to_nm = image_data_dict["/1/data"]["xreal"] * 1e9 / image.shape[1] - units = image_data_dict["/1/data"]["si_unit_xy"]["unitstr"] - else: - raise KeyError( - "Data location not defined in the .gwy file. Please locate it and add to the load_gwy() function." - ) + if not has_image_found: + raise KeyError( + "Data location not defined in the .gwy file. Please locate it and add to the load_gwy() function." + ) # Convert image heights to nanometresQ if units == "m": From a68de1c9a88c711f216b0acf19465100ece644d5 Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Mon, 11 Sep 2023 09:17:51 +0900 Subject: [PATCH 03/10] Update io.py Unit is set during scanning of channel index. --- topostats/io.py | 56 +++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/topostats/io.py b/topostats/io.py index 84d4bddf6d..3a2cf401ea 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -838,6 +838,7 @@ def load_gwy(self) -> tuple: # LoadScans._gwy_print_dict_wrapper(gwy_file_dict=image_data_dict) image = None has_image_found = False + units='' import regex re = r'\/(\d+)\/data$' components = list(image_data_dict.keys()) @@ -850,17 +851,36 @@ def load_gwy(self) -> tuple: channel_dict = image_data_dict[component] LOGGER.info(f"Guessing if this chchannel is height") for key in channel_dict.keys(): - if key=='si_unit_z' and channel_dict[key]['unitstr']=='m': - LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography.") - if not has_image_found: - image = image_data_dict[component]["data"] - units = image_data_dict[component]["si_unit_z"]["unitstr"] - px_to_nm = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] - # TODO: xy units and z units should be separately considered. - # added parameters for xy conversion support for non-square image - px_to_nm_x = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] - px_to_nm_y = image_data_dict[component]["yreal"] * 1e9 / image.shape[0] - has_image_found = True + if key=='si_unit_z': + u = channel_dict[key]['unitstr'] + if u[len(u)-1] == 'm': + LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography.") + if not has_image_found: + image = image_data_dict[component]["data"] + units = image_data_dict[component][key]['unitstr'] + LOGGER.info(f"\tUnit for Z of this topography is {units}") + if units == "m": + image = image * 1e9 + elif units == "mm": + image = image * 1e6 + elif units == "um": + image = image * 1e3 + else: + raise ValueError( + f"Units '{units}' have not been added for .gwy files. Please add \ + an SI to nanometre conversion factor for these units in _gwy_read_component in \ + io.py." + ) + px_to_nm = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] + # TODO: xy units and z units should be separately considered. + # added parameters for xy conversion support for non-square image + px_to_nm_x = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] + px_to_nm_y = image_data_dict[component]["yreal"] * 1e9 / image.shape[0] + has_image_found = True + else: + LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography, but not used.") + else: + LOGGER.info(f"\t{key} : {channel_dict[key]}, not topography.") else: if not key == 'data': LOGGER.info(f"\t{key} : {channel_dict[key]}") @@ -870,20 +890,6 @@ def load_gwy(self) -> tuple: "Data location not defined in the .gwy file. Please locate it and add to the load_gwy() function." ) - # Convert image heights to nanometresQ - if units == "m": - image = image * 1e9 - elif units == "mm": - image = image * 1e6 - elif units == "um": - image = image * 1e3 - else: - raise ValueError( - f"Units '{units}' have not been added for .gwy files. Please add \ - an SI to nanometre conversion factor for these units in _gwy_read_component in \ - io.py." - ) - except FileNotFoundError: LOGGER.info(f"[{self.filename}] File not found : {self.img_path}") raise From bbd301bf4c7342ebb088b8e28d639dbb076f214e Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Tue, 12 Sep 2023 06:43:36 +0900 Subject: [PATCH 04/10] LoadScans gwyddion routines updated # transposed np.ndarray corrected _gwy_read_component returns transposed dimensions of (xres,yres), this corrected to (yres, xres) # load_gwy updated * load_gwy can find multichannel data starting from any index (ex. 4/data) * load_gwy can guess whether if it is height channel by unitstr * load_gwy supports m,mm,um for z-unit * px_to_nm_x, px_to_nm_y were added for future usage, stored in img_dict --- topostats/io.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/topostats/io.py b/topostats/io.py index 3a2cf401ea..cddc356cbd 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -7,6 +7,7 @@ from pathlib import Path import pickle as pkl from typing import Any, Dict, List, Union +import regex import numpy as np import pandas as pd @@ -778,7 +779,7 @@ def _gwy_read_component(open_file: io.TextIOWrapper, initial_byte_pos: int, data for index in range(array_size): data[index] = read_64d(open_file=open_file) if "xres" in data_dict and "yres" in data_dict: - data = data.reshape((data_dict["xres"], data_dict["yres"])) + data = data.reshape((data_dict["yres"], data_dict["xres"])) data_dict["data"] = data return open_file.tell() - initial_byte_pos @@ -839,9 +840,10 @@ def load_gwy(self) -> tuple: image = None has_image_found = False units='' - import regex + re = r'\/(\d+)\/data$' components = list(image_data_dict.keys()) + conv_factors = {'m':1e9,'um':1e6,'mm':1e3} for component in components: match = regex.match(re, component) if match == None: @@ -853,18 +855,15 @@ def load_gwy(self) -> tuple: for key in channel_dict.keys(): if key=='si_unit_z': u = channel_dict[key]['unitstr'] - if u[len(u)-1] == 'm': + if u[len(u)-1] == 'm': # True if m,um,mm or *m LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography.") if not has_image_found: image = image_data_dict[component]["data"] units = image_data_dict[component][key]['unitstr'] LOGGER.info(f"\tUnit for Z of this topography is {units}") - if units == "m": - image = image * 1e9 - elif units == "mm": - image = image * 1e6 - elif units == "um": - image = image * 1e3 + if units in conv_factors: # m, um, mm conversion + factor = conv_factors[units] + image = image * factor else: raise ValueError( f"Units '{units}' have not been added for .gwy files. Please add \ @@ -874,8 +873,8 @@ def load_gwy(self) -> tuple: px_to_nm = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] # TODO: xy units and z units should be separately considered. # added parameters for xy conversion support for non-square image - px_to_nm_x = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] - px_to_nm_y = image_data_dict[component]["yreal"] * 1e9 / image.shape[0] + self.px_to_nm_x = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] + self.px_to_nm_y = image_data_dict[component]["yreal"] * 1e9 / image.shape[0] has_image_found = True else: LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography, but not used.") @@ -968,6 +967,9 @@ def add_to_dict(self) -> None: "image_flattened": None, "grain_masks": self.grain_masks, } + if hasattr(self,'pixel_to_nm_scaling_x'): + self.img_dict['pixel_to_nm_scaling_x'] = self.pixel_to_nm_scaling_x + self.img_dict['pixel_to_nm_scaling_y'] = self.pixel_to_nm_scaling_y def save_topostats_file(output_dir: Path, filename: str, topostats_object: dict) -> None: From 0b7ef85d385bea641a69210664a685b9b910f420 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:47:19 +0000 Subject: [PATCH 05/10] [pre-commit.ci] Fixing issues with pre-commit --- topostats/io.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/topostats/io.py b/topostats/io.py index cddc356cbd..521ed524d2 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -839,11 +839,11 @@ def load_gwy(self) -> tuple: # LoadScans._gwy_print_dict_wrapper(gwy_file_dict=image_data_dict) image = None has_image_found = False - units='' + units = "" - re = r'\/(\d+)\/data$' + re = r"\/(\d+)\/data$" components = list(image_data_dict.keys()) - conv_factors = {'m':1e9,'um':1e6,'mm':1e3} + conv_factors = {"m": 1e9, "um": 1e6, "mm": 1e3} for component in components: match = regex.match(re, component) if match == None: @@ -853,15 +853,15 @@ def load_gwy(self) -> tuple: channel_dict = image_data_dict[component] LOGGER.info(f"Guessing if this chchannel is height") for key in channel_dict.keys(): - if key=='si_unit_z': - u = channel_dict[key]['unitstr'] - if u[len(u)-1] == 'm': # True if m,um,mm or *m + if key == "si_unit_z": + u = channel_dict[key]["unitstr"] + if u[len(u) - 1] == "m": # True if m,um,mm or *m LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography.") if not has_image_found: image = image_data_dict[component]["data"] - units = image_data_dict[component][key]['unitstr'] + units = image_data_dict[component][key]["unitstr"] LOGGER.info(f"\tUnit for Z of this topography is {units}") - if units in conv_factors: # m, um, mm conversion + if units in conv_factors: # m, um, mm conversion factor = conv_factors[units] image = image * factor else: @@ -881,7 +881,7 @@ def load_gwy(self) -> tuple: else: LOGGER.info(f"\t{key} : {channel_dict[key]}, not topography.") else: - if not key == 'data': + if not key == "data": LOGGER.info(f"\t{key} : {channel_dict[key]}") if not has_image_found: @@ -967,9 +967,9 @@ def add_to_dict(self) -> None: "image_flattened": None, "grain_masks": self.grain_masks, } - if hasattr(self,'pixel_to_nm_scaling_x'): - self.img_dict['pixel_to_nm_scaling_x'] = self.pixel_to_nm_scaling_x - self.img_dict['pixel_to_nm_scaling_y'] = self.pixel_to_nm_scaling_y + if hasattr(self, "pixel_to_nm_scaling_x"): + self.img_dict["pixel_to_nm_scaling_x"] = self.pixel_to_nm_scaling_x + self.img_dict["pixel_to_nm_scaling_y"] = self.pixel_to_nm_scaling_y def save_topostats_file(output_dir: Path, filename: str, topostats_object: dict) -> None: From 4eee70ad1e23b6229b5f24f653c25bfabed9caf8 Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Fri, 15 Sep 2023 18:58:15 +0900 Subject: [PATCH 06/10] io.py and testing added io.py modified - Conversion from gwy.DataField to np.ndarray was corrected. (dimension was transposed) - Scale class added to conversion of units and holds conversion factors - Initializable by configuration YAML file - This object is copied to img_dict of data accessible scan.img_dict[filename]["scale"] px_to_nm_x, px_to_nm_y values in img_dict from Gwy Testing and Configurations - Resource files (file_landscape.gwy, file.square.gy) added. - conftest.py modified to read file.gwy and file_landscape.gwy - Test for Scale class - test_scale_config was added. - test_scale in test_io.py was added. - "scale" configuration was added to default_config.yaml --- tests/conftest.py | 4 +- tests/resources/file_landscape.gwy | Bin 0 -> 299881 bytes tests/resources/file_square.gwy | Bin 0 -> 525814 bytes tests/resources/test_scale_config.yaml | 5 + tests/test_io.py | 39 +++++++- topostats/default_config.yaml | 3 + topostats/io.py | 127 ++++++++++++++++--------- 7 files changed, 132 insertions(+), 46 deletions(-) create mode 100644 tests/resources/file_landscape.gwy create mode 100644 tests/resources/file_square.gwy create mode 100644 tests/resources/test_scale_config.yaml diff --git a/tests/conftest.py b/tests/conftest.py index 3e89820a03..496b8b714a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -370,9 +370,9 @@ def load_scan_jpk() -> LoadScans: @pytest.fixture -def load_scan_gwy() -> LoadScans: +def load_scan_gwy(loading_config: dict) -> LoadScans: """Instantiate a LoadScans object from a .gwy file.""" - scan_loader = LoadScans([RESOURCES / "file.gwy"], channel="dummy_channel") + scan_loader = LoadScans([RESOURCES / "file.gwy", RESOURCES / "file_landscape.gwy"], channel="dummy_channel") return scan_loader diff --git a/tests/resources/file_landscape.gwy b/tests/resources/file_landscape.gwy new file mode 100644 index 0000000000000000000000000000000000000000..93bf04cbf2b4725b0cdfc90fd271dde7e9c925a0 GIT binary patch literal 299881 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 literal 0 HcmV?d00001 diff --git a/tests/resources/test_scale_config.yaml b/tests/resources/test_scale_config.yaml new file mode 100644 index 0000000000..730e23bb91 --- /dev/null +++ b/tests/resources/test_scale_config.yaml @@ -0,0 +1,5 @@ +# Test for unit scaling +loading: + scale: + nm: + {m: 1e9, mm: 1e6, um: 1e3} diff --git a/tests/test_io.py b/tests/test_io.py index 3098c367e0..6c172cd09e 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -19,6 +19,7 @@ get_out_path, path_to_str, save_folder_grainstats, + Scale, LoadScans, save_pkl, load_pkl, @@ -359,11 +360,24 @@ def test_load_scan_gwy(load_scan_gwy: LoadScans) -> None: load_scan_gwy.img_path = load_scan_gwy.img_paths[0] load_scan_gwy.filename = load_scan_gwy.img_paths[0].stem image, px_to_nm_scaling = load_scan_gwy.load_gwy() + assert len(load_scan_gwy.img_paths) == 2 assert isinstance(image, np.ndarray) assert image.shape == (512, 512) assert image.sum() == 33836850.232917726 assert isinstance(px_to_nm_scaling, float) assert px_to_nm_scaling == 0.8468632812499975 + #Test loading landscape .gwy file. + load_scan_gwy.img_path = load_scan_gwy.img_paths[1] + load_scan_gwy.filename = load_scan_gwy.img_paths[1].stem + image, px_to_nm_scaling = load_scan_gwy.load_gwy() + expected_sum = 1.9190233924574975e+2 # calculated in Gwyddion + numpy 1.9.2 + assert abs(image.sum() - expected_sum) < 1e-10 + assert isinstance(image, np.ndarray) + assert image.shape == (170, 220) # (height, width) + assert isinstance(px_to_nm_scaling, float) + # conventional parameter, px_to_nm_scaling + expected_scaling_x = 1000.0 / 220.0 + assert abs(px_to_nm_scaling - expected_scaling_x) < 1e-10 def test_load_scan_topostats(load_scan_topostats: LoadScans) -> None: @@ -409,6 +423,21 @@ def test_gwy_read_component(load_scan_dummy: LoadScans) -> None: assert list(test_dict.values()) == [{"test nested component": 3}] +def test_scale() -> None: + test_config = read_yaml(RESOURCES / "test_scale_config.yaml") + if "scale" not in test_config["loading"]: + raise KeyError("Scale is not defined in test_scale_config.yaml") + scale = Scale(test_config["loading"]["scale"]) + assert scale.is_available("nm","m") == True + assert scale.get_factor("nm","um") == 1000 + assert scale.in_nm(1.0,"um") == 1000 + assert scale.in_nm(1.0,"mm") == 1000000 + scale.add_factor("nm","pixel_x_in_nm",1000/220) + scale.add_factor("nm","pixel_y_in_nm",1000/170) + assert abs(scale.in_nm(220,"pixel_x_in_nm") - 1000) < 1e-10 + assert abs(scale.in_nm(170,"pixel_y_in_nm") - 1000) < 1e-10 + + # FIXME : Get this test working # @pytest.mark.parametrize( # "unit, x, y, expected", @@ -431,7 +460,7 @@ def test_gwy_read_component(load_scan_dummy: LoadScans) -> None: ("load_scan_spm", 1, (1024, 1024), 30695369.188316286, "minicircle", 0.4940029296875), ("load_scan_ibw", 1, (512, 512), -218091520.0, "minicircle2", 1.5625), ("load_scan_jpk", 1, (256, 256), 286598232.9308627, "file", 1.2770176335964876), - ("load_scan_gwy", 1, (512, 512), 33836850.232917726, "file", 0.8468632812499975), + ("load_scan_gwy", 2, (512, 512), 33836850.232917726, "file", 0.8468632812499975), ("load_scan_topostats", 1, (1024, 1024), 182067.12616107278, "file", 0.4940029296875), ], ) @@ -456,6 +485,14 @@ def test_load_scan_get_data( assert isinstance(scan.img_dict[filename]["pixel_to_nm_scaling"], float) assert scan.img_dict[filename]["pixel_to_nm_scaling"] == pixel_to_nm_scaling + # Scale object holds conversion factors of image + # Not all file format doesn't have it yet. + if "scale" in scan.img_dict[filename]: + scale = scan.img_dict[filename]["scale"] + assert isinstance(scale.get_factor("nm", "px_to_nm_x"), float) + assert scale.get_factor("nm", "px_to_nm_x") == pixel_to_nm_scaling + assert isinstance(scale.get_factor("nm", "px_to_nm_y"), float) + @pytest.mark.parametrize( "x, y, log_msg", diff --git a/topostats/default_config.yaml b/topostats/default_config.yaml index 8038a57333..994bc446fc 100644 --- a/topostats/default_config.yaml +++ b/topostats/default_config.yaml @@ -5,6 +5,9 @@ cores: 2 # Number of CPU cores to utilise for processing multiple files simultan file_ext: .spm # File extension of the data files. loading: channel: Height # Channel to pull data from in the data files. + scale: + nm: + {m: 1e9, mm: 1e6, um: 1e3} # factors to convert to nm filter: run: true # Options : true, false row_alignment_quantile: 0.5 # below values may improve flattening of larger features diff --git a/topostats/io.py b/topostats/io.py index 521ed524d2..fa7c77d190 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -7,7 +7,7 @@ from pathlib import Path import pickle as pkl from typing import Any, Dict, List, Union -import regex +import re import numpy as np import pandas as pd @@ -17,6 +17,8 @@ import h5py from ruamel.yaml import YAML, YAMLError from ruamel.yaml.main import round_trip_load as yaml_load, round_trip_dump as yaml_dump +import importlib.resources as pkg_resources +import yaml from topostats.logs.logs import LOGGER_NAME @@ -449,6 +451,47 @@ def convert_basename_to_relative_paths(df: pd.DataFrame): return df +class Scale: + """Hold scaling factors and convert value by multiplying. + It can hold conversion factors for image like "pixel_x_in_nm" + """ + def __init__(self, config_dict): + """ Instantiate scaling factors using configuration.yaml. + default_config["loading"]["scale"] should have the dict. + """ + self._factors = config_dict + + def in_nm(self, value_from, unit_from) -> float: + """ Return value in nanometre from value and its unit""" + return self.get_value("nm", value_from, unit_from) + + def get_value(self, unit_to, value_from, unit_from) -> float: + return value_from * self.get_factor(unit_to, unit_from) + + def get_factor(self, unit_to, unit_from) -> float: + """ Conversion factor from a unit to another unit""" + return float(self._factors[unit_to][unit_from]) + + def add_factor(self, unit_to, unit_from, factor): + """ Add a factor with the arguments. """ + if not unit_to in self._factors: + self._factors[unit_to] = {} + if not unit_from in self._factors[unit_to]: + self._factors[unit_to][unit_from] = {} + self._factors[unit_to][unit_from] = float(factor) + + def is_available(self, unit_to, unit_from) -> bool: + if unit_to not in self._factors: + return False + return unit_from in self._factors[unit_to] + + def __str__(self) -> str: + s = "" + for to_key in self._factors.keys(): + for from_key in self._factors[to_key].keys(): + s += f"1({to_key})={self._factors[to_key][from_key]}({from_key})," + return s[:-1] + # pylint: disable=too-many-instance-attributes class LoadScans: @@ -840,49 +883,45 @@ def load_gwy(self) -> tuple: image = None has_image_found = False units = "" - - re = r"\/(\d+)\/data$" - components = list(image_data_dict.keys()) - conv_factors = {"m": 1e9, "um": 1e6, "mm": 1e3} - for component in components: - match = regex.match(re, component) - if match == None: + # How can we get current configuration["loading"] ? + # default_config.yaml is used to get conf["loading"]["scale"] + default_config = pkg_resources.open_text(__package__, "default_config.yaml").read() + config = yaml.safe_load(default_config) + self.scale = Scale(config["loading"]["scale"]) + LOGGER.info(self.scale) + + reg_gwy_idx = r"\/(\d+)\/data$" + for component in image_data_dict.keys(): # component is like '/0/data', /4/data/title' + match = re.match(reg_gwy_idx, component) + if match == None: # not data field continue - idx = int(match[1]) - LOGGER.info(f"Channel found at {idx}") + LOGGER.debug(f"DataField exists in the container at {match[1]}") channel_dict = image_data_dict[component] - LOGGER.info(f"Guessing if this chchannel is height") + # check if this data contains z-height values for key in channel_dict.keys(): - if key == "si_unit_z": - u = channel_dict[key]["unitstr"] - if u[len(u) - 1] == "m": # True if m,um,mm or *m - LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography.") - if not has_image_found: - image = image_data_dict[component]["data"] - units = image_data_dict[component][key]["unitstr"] - LOGGER.info(f"\tUnit for Z of this topography is {units}") - if units in conv_factors: # m, um, mm conversion - factor = conv_factors[units] - image = image * factor - else: - raise ValueError( - f"Units '{units}' have not been added for .gwy files. Please add \ - an SI to nanometre conversion factor for these units in _gwy_read_component in \ - io.py." - ) - px_to_nm = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] - # TODO: xy units and z units should be separately considered. - # added parameters for xy conversion support for non-square image - self.px_to_nm_x = image_data_dict[component]["xreal"] * 1e9 / image.shape[1] - self.px_to_nm_y = image_data_dict[component]["yreal"] * 1e9 / image.shape[0] - has_image_found = True - else: - LOGGER.info(f"\t{key} : {channel_dict[key]}, maybe topography, but not used.") + if key != "si_unit_z": + continue + units = channel_dict[key]["unitstr"] + if units[len(units) - 1] != "m": # units doesn't end with m + continue + if not has_image_found: + image = channel_dict["data"] + LOGGER.info(f"\t({self.filename}) has topography image with z-height data({units}).") + if self.scale.is_available("nm",units): # m, um, mm conversion + scale = self.scale.get_factor("nm", units) + image = image * scale else: - LOGGER.info(f"\t{key} : {channel_dict[key]}, not topography.") - else: - if not key == "data": - LOGGER.info(f"\t{key} : {channel_dict[key]}") + raise ValueError( + f"Units '{units}' have not been added in configuration file. \ + an SI to nanometre conversion factor for these units default_config.yaml.") + + m2nm = self.scale.get_factor("nm","m") + px_to_nm = image_data_dict[component]["xreal"] * m2nm / float(image.shape[1]) + # scale instance holds the scaling factors for image data, then will be copied to img_dict + self.scale.add_factor("nm","px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) + self.scale.add_factor("nm","px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) + self.scale.add_factor("nm","px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0]) + has_image_found = True if not has_image_found: raise KeyError( @@ -967,9 +1006,11 @@ def add_to_dict(self) -> None: "image_flattened": None, "grain_masks": self.grain_masks, } - if hasattr(self, "pixel_to_nm_scaling_x"): - self.img_dict["pixel_to_nm_scaling_x"] = self.pixel_to_nm_scaling_x - self.img_dict["pixel_to_nm_scaling_y"] = self.pixel_to_nm_scaling_y + # Copy scale instance to img_dict, only gwy loader has the attribute now, + # attribute of img_dict is checked. + if hasattr(self,"scale"): + LOGGER.info("Scaling factors are stored in img_dict[filename][scale] as Scale objct.") + self.img_dict[self.filename]["scale"] = self.scale def save_topostats_file(output_dir: Path, filename: str, topostats_object: dict) -> None: From 5b8a5cc41cf5c95647c8a93b55d3196114874654 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:58:49 +0000 Subject: [PATCH 07/10] [pre-commit.ci] Fixing issues with pre-commit --- tests/test_io.py | 22 +++++++++++----------- topostats/io.py | 41 +++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/tests/test_io.py b/tests/test_io.py index 6c172cd09e..15c7898919 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -366,14 +366,14 @@ def test_load_scan_gwy(load_scan_gwy: LoadScans) -> None: assert image.sum() == 33836850.232917726 assert isinstance(px_to_nm_scaling, float) assert px_to_nm_scaling == 0.8468632812499975 - #Test loading landscape .gwy file. + # Test loading landscape .gwy file. load_scan_gwy.img_path = load_scan_gwy.img_paths[1] load_scan_gwy.filename = load_scan_gwy.img_paths[1].stem image, px_to_nm_scaling = load_scan_gwy.load_gwy() - expected_sum = 1.9190233924574975e+2 # calculated in Gwyddion + numpy 1.9.2 + expected_sum = 1.9190233924574975e2 # calculated in Gwyddion + numpy 1.9.2 assert abs(image.sum() - expected_sum) < 1e-10 assert isinstance(image, np.ndarray) - assert image.shape == (170, 220) # (height, width) + assert image.shape == (170, 220) # (height, width) assert isinstance(px_to_nm_scaling, float) # conventional parameter, px_to_nm_scaling expected_scaling_x = 1000.0 / 220.0 @@ -428,14 +428,14 @@ def test_scale() -> None: if "scale" not in test_config["loading"]: raise KeyError("Scale is not defined in test_scale_config.yaml") scale = Scale(test_config["loading"]["scale"]) - assert scale.is_available("nm","m") == True - assert scale.get_factor("nm","um") == 1000 - assert scale.in_nm(1.0,"um") == 1000 - assert scale.in_nm(1.0,"mm") == 1000000 - scale.add_factor("nm","pixel_x_in_nm",1000/220) - scale.add_factor("nm","pixel_y_in_nm",1000/170) - assert abs(scale.in_nm(220,"pixel_x_in_nm") - 1000) < 1e-10 - assert abs(scale.in_nm(170,"pixel_y_in_nm") - 1000) < 1e-10 + assert scale.is_available("nm", "m") == True + assert scale.get_factor("nm", "um") == 1000 + assert scale.in_nm(1.0, "um") == 1000 + assert scale.in_nm(1.0, "mm") == 1000000 + scale.add_factor("nm", "pixel_x_in_nm", 1000 / 220) + scale.add_factor("nm", "pixel_y_in_nm", 1000 / 170) + assert abs(scale.in_nm(220, "pixel_x_in_nm") - 1000) < 1e-10 + assert abs(scale.in_nm(170, "pixel_y_in_nm") - 1000) < 1e-10 # FIXME : Get this test working diff --git a/topostats/io.py b/topostats/io.py index fa7c77d190..bbe48cfec3 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -451,29 +451,31 @@ def convert_basename_to_relative_paths(df: pd.DataFrame): return df + class Scale: """Hold scaling factors and convert value by multiplying. - It can hold conversion factors for image like "pixel_x_in_nm" + It can hold conversion factors for image like "pixel_x_in_nm" """ + def __init__(self, config_dict): - """ Instantiate scaling factors using configuration.yaml. - default_config["loading"]["scale"] should have the dict. + """Instantiate scaling factors using configuration.yaml. + default_config["loading"]["scale"] should have the dict. """ self._factors = config_dict def in_nm(self, value_from, unit_from) -> float: - """ Return value in nanometre from value and its unit""" + """Return value in nanometre from value and its unit""" return self.get_value("nm", value_from, unit_from) def get_value(self, unit_to, value_from, unit_from) -> float: return value_from * self.get_factor(unit_to, unit_from) def get_factor(self, unit_to, unit_from) -> float: - """ Conversion factor from a unit to another unit""" + """Conversion factor from a unit to another unit""" return float(self._factors[unit_to][unit_from]) def add_factor(self, unit_to, unit_from, factor): - """ Add a factor with the arguments. """ + """Add a factor with the arguments.""" if not unit_to in self._factors: self._factors[unit_to] = {} if not unit_from in self._factors[unit_to]: @@ -891,9 +893,9 @@ def load_gwy(self) -> tuple: LOGGER.info(self.scale) reg_gwy_idx = r"\/(\d+)\/data$" - for component in image_data_dict.keys(): # component is like '/0/data', /4/data/title' + for component in image_data_dict.keys(): # component is like '/0/data', /4/data/title' match = re.match(reg_gwy_idx, component) - if match == None: # not data field + if match == None: # not data field continue LOGGER.debug(f"DataField exists in the container at {match[1]}") channel_dict = image_data_dict[component] @@ -902,25 +904,32 @@ def load_gwy(self) -> tuple: if key != "si_unit_z": continue units = channel_dict[key]["unitstr"] - if units[len(units) - 1] != "m": # units doesn't end with m + if units[len(units) - 1] != "m": # units doesn't end with m continue if not has_image_found: image = channel_dict["data"] LOGGER.info(f"\t({self.filename}) has topography image with z-height data({units}).") - if self.scale.is_available("nm",units): # m, um, mm conversion + if self.scale.is_available("nm", units): # m, um, mm conversion scale = self.scale.get_factor("nm", units) image = image * scale else: raise ValueError( f"Units '{units}' have not been added in configuration file. \ - an SI to nanometre conversion factor for these units default_config.yaml.") + an SI to nanometre conversion factor for these units default_config.yaml." + ) - m2nm = self.scale.get_factor("nm","m") + m2nm = self.scale.get_factor("nm", "m") px_to_nm = image_data_dict[component]["xreal"] * m2nm / float(image.shape[1]) # scale instance holds the scaling factors for image data, then will be copied to img_dict - self.scale.add_factor("nm","px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) - self.scale.add_factor("nm","px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) - self.scale.add_factor("nm","px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0]) + self.scale.add_factor( + "nm", "px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1] + ) + self.scale.add_factor( + "nm", "px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1] + ) + self.scale.add_factor( + "nm", "px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0] + ) has_image_found = True if not has_image_found: @@ -1008,7 +1017,7 @@ def add_to_dict(self) -> None: } # Copy scale instance to img_dict, only gwy loader has the attribute now, # attribute of img_dict is checked. - if hasattr(self,"scale"): + if hasattr(self, "scale"): LOGGER.info("Scaling factors are stored in img_dict[filename][scale] as Scale objct.") self.img_dict[self.filename]["scale"] = self.scale From de1ac769a4ab9e7b6405fc3a1d8063722374227d Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Fri, 15 Sep 2023 19:07:55 +0900 Subject: [PATCH 08/10] Format corrected --- tests/test_io.py | 4 ++-- topostats/io.py | 39 +++++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/tests/test_io.py b/tests/test_io.py index 6c172cd09e..f2cfe49b88 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -366,11 +366,11 @@ def test_load_scan_gwy(load_scan_gwy: LoadScans) -> None: assert image.sum() == 33836850.232917726 assert isinstance(px_to_nm_scaling, float) assert px_to_nm_scaling == 0.8468632812499975 - #Test loading landscape .gwy file. + # Test loading landscape .gwy file. load_scan_gwy.img_path = load_scan_gwy.img_paths[1] load_scan_gwy.filename = load_scan_gwy.img_paths[1].stem image, px_to_nm_scaling = load_scan_gwy.load_gwy() - expected_sum = 1.9190233924574975e+2 # calculated in Gwyddion + numpy 1.9.2 + expected_sum = 1.9190233924574975e2 # calculated in Gwyddion + numpy 1.9.2 assert abs(image.sum() - expected_sum) < 1e-10 assert isinstance(image, np.ndarray) assert image.shape == (170, 220) # (height, width) diff --git a/topostats/io.py b/topostats/io.py index fa7c77d190..c555fe6251 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -453,27 +453,27 @@ def convert_basename_to_relative_paths(df: pd.DataFrame): class Scale: """Hold scaling factors and convert value by multiplying. - It can hold conversion factors for image like "pixel_x_in_nm" + It can hold conversion factors for image like "pixel_x_in_nm" """ def __init__(self, config_dict): - """ Instantiate scaling factors using configuration.yaml. - default_config["loading"]["scale"] should have the dict. + """Instantiate scaling factors using configuration.yaml. + default_config["loading"]["scale"] should have the dict. """ self._factors = config_dict def in_nm(self, value_from, unit_from) -> float: - """ Return value in nanometre from value and its unit""" + """Return value in nanometre from value and its unit""" return self.get_value("nm", value_from, unit_from) def get_value(self, unit_to, value_from, unit_from) -> float: return value_from * self.get_factor(unit_to, unit_from) def get_factor(self, unit_to, unit_from) -> float: - """ Conversion factor from a unit to another unit""" + """Conversion factor from a unit to another unit""" return float(self._factors[unit_to][unit_from]) def add_factor(self, unit_to, unit_from, factor): - """ Add a factor with the arguments. """ + """Add a factor with the arguments. """ if not unit_to in self._factors: self._factors[unit_to] = {} if not unit_from in self._factors[unit_to]: @@ -891,9 +891,9 @@ def load_gwy(self) -> tuple: LOGGER.info(self.scale) reg_gwy_idx = r"\/(\d+)\/data$" - for component in image_data_dict.keys(): # component is like '/0/data', /4/data/title' + for component in image_data_dict.keys(): # component is like '/0/data', /4/data/title' match = re.match(reg_gwy_idx, component) - if match == None: # not data field + if match == None: # not data field continue LOGGER.debug(f"DataField exists in the container at {match[1]}") channel_dict = image_data_dict[component] @@ -902,25 +902,32 @@ def load_gwy(self) -> tuple: if key != "si_unit_z": continue units = channel_dict[key]["unitstr"] - if units[len(units) - 1] != "m": # units doesn't end with m + if units[len(units) - 1] != "m": # units doesn't end with m continue if not has_image_found: image = channel_dict["data"] LOGGER.info(f"\t({self.filename}) has topography image with z-height data({units}).") - if self.scale.is_available("nm",units): # m, um, mm conversion + if self.scale.is_available("nm", units): # m, um, mm conversion scale = self.scale.get_factor("nm", units) image = image * scale else: raise ValueError( f"Units '{units}' have not been added in configuration file. \ - an SI to nanometre conversion factor for these units default_config.yaml.") + an SI to nanometre conversion factor for these units default_config.yaml." + ) - m2nm = self.scale.get_factor("nm","m") + m2nm = self.scale.get_factor("nm", "m") px_to_nm = image_data_dict[component]["xreal"] * m2nm / float(image.shape[1]) # scale instance holds the scaling factors for image data, then will be copied to img_dict - self.scale.add_factor("nm","px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) - self.scale.add_factor("nm","px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) - self.scale.add_factor("nm","px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0]) + self.scale.add_factor( + "nm","px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1] + ) + self.scale.add_factor( + "nm","px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1] + ) + self.scale.add_factor( + "nm","px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0] + ) has_image_found = True if not has_image_found: @@ -1008,7 +1015,7 @@ def add_to_dict(self) -> None: } # Copy scale instance to img_dict, only gwy loader has the attribute now, # attribute of img_dict is checked. - if hasattr(self,"scale"): + if hasattr(self, "scale"): LOGGER.info("Scaling factors are stored in img_dict[filename][scale] as Scale objct.") self.img_dict[self.filename]["scale"] = self.scale From 9ac1a9280ab5726b2e569210651969fb9e127fdf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:14:46 +0000 Subject: [PATCH 09/10] [pre-commit.ci] Fixing issues with pre-commit --- tests/test_io.py | 2 +- topostats/io.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/test_io.py b/tests/test_io.py index c5aedeabfc..15c7898919 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -370,7 +370,7 @@ def test_load_scan_gwy(load_scan_gwy: LoadScans) -> None: load_scan_gwy.img_path = load_scan_gwy.img_paths[1] load_scan_gwy.filename = load_scan_gwy.img_paths[1].stem image, px_to_nm_scaling = load_scan_gwy.load_gwy() - expected_sum = 1.9190233924574975e2 # calculated in Gwyddion + numpy 1.9.2 + expected_sum = 1.9190233924574975e2 # calculated in Gwyddion + numpy 1.9.2 assert abs(image.sum() - expected_sum) < 1e-10 assert isinstance(image, np.ndarray) assert image.shape == (170, 220) # (height, width) diff --git a/topostats/io.py b/topostats/io.py index 3b35549831..bbe48cfec3 100644 --- a/topostats/io.py +++ b/topostats/io.py @@ -475,7 +475,7 @@ def get_factor(self, unit_to, unit_from) -> float: return float(self._factors[unit_to][unit_from]) def add_factor(self, unit_to, unit_from, factor): - """ Add a factor with the arguments. """ + """Add a factor with the arguments.""" if not unit_to in self._factors: self._factors[unit_to] = {} if not unit_from in self._factors[unit_to]: @@ -915,14 +915,21 @@ def load_gwy(self) -> tuple: else: raise ValueError( f"Units '{units}' have not been added in configuration file. \ - an SI to nanometre conversion factor for these units default_config.yaml.") + an SI to nanometre conversion factor for these units default_config.yaml." + ) m2nm = self.scale.get_factor("nm", "m") px_to_nm = image_data_dict[component]["xreal"] * m2nm / float(image.shape[1]) # scale instance holds the scaling factors for image data, then will be copied to img_dict - self.scale.add_factor("nm","px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) - self.scale.add_factor("nm","px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1]) - self.scale.add_factor("nm","px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0]) + self.scale.add_factor( + "nm", "px_to_nm", image_data_dict[component]["xreal"] * m2nm / image.shape[1] + ) + self.scale.add_factor( + "nm", "px_to_nm_x", image_data_dict[component]["xreal"] * m2nm / image.shape[1] + ) + self.scale.add_factor( + "nm", "px_to_nm_y", image_data_dict[component]["yreal"] * m2nm / image.shape[0] + ) has_image_found = True if not has_image_found: From d8103c8292cbc753199dff9064fcd7e4b670adf2 Mon Sep 17 00:00:00 2001 From: Ikuo Obataya Date: Thu, 28 Sep 2023 08:03:20 +0900 Subject: [PATCH 10/10] Uploaded /gwy files for tests --- tests/resources/gwy_landscape.gwy | Bin 0 -> 299881 bytes tests/resources/gwy_square.gwy | Bin 0 -> 525814 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/resources/gwy_landscape.gwy create mode 100644 tests/resources/gwy_square.gwy diff --git a/tests/resources/gwy_landscape.gwy b/tests/resources/gwy_landscape.gwy new file mode 100644 index 0000000000000000000000000000000000000000..93bf04cbf2b4725b0cdfc90fd271dde7e9c925a0 GIT binary patch literal 299881 zcmeI5d7zb5x&IHKpiDA3)HOxYOhiBw8T4#$#;F`oQ&Bj`0Zxno3Y1f+S91(;Dl0AO zs;HTi*KWCQiW8bsiW4ddP6((dW@ZjQ@~jWu`{noCwcfqocb~oYS@p-)=ULz9d56t@ zzWZ5gz3(~j*y9dAaOUiNr%awv*D$$$TFvme8`bpLtSqT-~p-jnu@1yK5bHp!1F;UoIuPqX ztOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;UoI?!C}!2NMQW!z6%#rtXb_pO-K z_r27|$?7^vm6!Wfs^g`7oJv3cLhna&z3xKqSB#5&M(i_UpAqXotOKzQ#5xe`K&%6? z4#YYT>p*Giz_rWvUcOo9ck;~*EB)Wu&i4#_;HUG~5BXx`VZRW`^UG)J^t+A&O08u(RzH$ z*`cWMKD$(T|LCsoufJ`)-co&TSsiEY4%?sC{e#u{!yZ5UqTY+%tIhp<>mT3t&Ih$a zJXq_-_X`#eu}Djla}*u)Drfk7~SL?vvgR%Py238V3)w&IA9n zQ;(ROAHE`g*25Rg{`t6;xhKw?e9xt)ZoD4<>THm4Y{kcgueS2BLwSYruX5Mdc}C;) za{XDM>KUJhg-Q84I(@rh!h#{kbZM1aIsEQN7M$JQ#5ZLeThW&%t?^ZIKG-0~xz1hR z;wsU%E1kZ*cKQiDmhAqw+QYAyv!>&IEpmn8H(fmF%LDHlE-n;(d3C(=8I9NXY7^_5 z^?gg~|G??n74vo+xL`_6uKn=O#$MKH)5O?L;;=h7KemM&x2=&!Q`HNYAb(~yS{EE>fiX@WQAXq{!Mj3 zZFJ11C&Y#Fluuk*h`v2uCH>oFWukAEE~Vw~;`+Cyo}WzX`}dxqZ|DMuZuk&BI;J*$ zMe^duCf-8jVUq_$-)2^czA--;s&D8BNPh(42hkbJw)`5WJjNB_fsEHVed|&peY@AK zyJdx6ar$=kg#Um4hQ1W5Kg5INRrHO+SDxDN-g5opFWmJ#;`A-6`&p{Imx{g};pPcL zQT0dTezN<(OBS?lll!ql>`)}`YRNPAA4kl+{KS?yj>mamD|U7I_OhFwEcf5}zVmxm z^bI{wJwZ3{W5d_^K(*n8BFFLZISzbmjYHpq;^Bt};))3?_H*WY?wzt!}s?$|7+xU=DZum6aN z9R_vIrHyybJ>j`o+jPqP^wZPFzkh>&y^#J5eY?;7-m6sf^>63|dWMeRR~vnS55fc0 zPd@zEAaNjm`1mcn-_5_ya{XJSeqNmS6gNM)x$AGx2lRse2R}9l4}_-(U;V`CI5y*o zHK8cI#q1GFCE|VuoicQ>l^cvS$)q+l~?;W^a4aL)K)*^g<{ftn|am=b^HORZ!?^} z`Q|n5adoa-akA*!c5Z#GhePxOzt2{l^2rMiR6lW+U*q6geJJALzg9t+*)Rw!nr zYvk?e<|paj&;xV|#IK0HfQ)1N;^8aOcY)+np4#vfo14C|?}PcjnTK38WAQ_+az0#n z>mj3iZ`mpJzU|4L?>+Y9$D6D_{xiewx_rUWetlzqNww(PJ8pjRDp&t4ol(8ORxDKC z$kWN`7Pgg7$FUWON8hG8ee>1Havz`I^lb~*zo8EnqBn~0EuB%mWq;MsRq`utFM3Qo z2!FJ@zD=CI-R<-%D=btOL;t;OE$eUSiG|EJTH}^KG%l3KanLc##?SiO!)|_(-|N2L zm5dk44~>Hda$E~pe|tmbCw2WQsf`|X?{&wRwi8=ypmS^8yd^kw(FvFSVb6`$ucvW5 z@)U~XVSW;Qv*v5@GrwD|_%~zyjrl+H&SzUZ{7I4HB}I-y9QM6VzfX7imi4}Jey?YR zGo8Nid;s%%K4hKD;xRwSxZ3#1qp0K9io|Jt{P0MfR6`&*@va{@w^ZuTw?e&;<~EC>7HG z`SeQ9NBKIAzHRCBjo%BQzfY3#_51A8wWR!QUH^u@(GMx2Q|iZN9DTu`7LwmWj%$t6 zzp=lB`{cWyj2FuH#ZfntV$%I3p?U%@RNp2!ebf0+@)U~XL*Lk6lJq{68(*J)vvh@h zBi1;2!g#0+kNk?{!;hU5*M^?2~qkFDd_=v%4wPZl~q z85&3b_TveS{hOr^>VMnqGeK9dLFE@}lixz}>T#7%|Hk|zx)nN)TPQy?4j$+`FXksh z_fH<_o*z(T9#C~CRNv^|tUQ`Gsg1rZb|koif~4fMo^8W(C? zdDD(BwZ2*RJ1zhA#{Nm&mx!&pfgfbQq=oQ6c$SUdhnBwKXaD3A1wQ{)Zgn!P_j6hA zjlA)X@69}WfB!y8`Zv|3v~1l!8TxxWl;_jOw8we=jrBKl1>FJZzZl16Tv2)OErg#G z$w!=?2R8lNDQ^8O?fY14KK(xX@=bO6rq91w`oj7cLUp$CfS8h-8PESq^lMfjT6XLEdzJZh`onxC{jH{ZYarXFX1 z$qLy&nN(NkvzWIeUiXn$cGmjF{3QKbQ{VSukFWbnES<3Y=!YV@gI{g@J8~UKS;j^tc{Gj%2()DlU`gw$&7yC=n`uhRs8ffVm{4*FG*(5h` z+>pas|ENWdK9KlAk$olP>nQstL+?+y^7#7!yk8leQPlVPqf3>7=v%eypJe^bdjF*A z4eMa4Gw?v=!Osdgu9ctpN#3uV_4nv5_xwRtxRslqWPb^L52$@tQd{$&NB9-Vk6&%e z@8i+GEpqx6y8k9AFSKtimUn^ie1P_2^kqI&JwnG6S%;f5rsj`h+qKDQUXHK%LT!y_ z{q04!zl46f*gmSM;#q&Q<|pZk=%ajgC{OdClf+xL<+tKE4)c@rZ#o~C)Xuuj`ttks zPofv9A8Nw`>7y)L{hANkLgGUaecQ^-Po}jF)>L`u-*|sXXrF{mDIY(56a5)HY#$cN z=lH~fy8h<-_gwDdP2Io!$mv_CZYhucO#NzSz5eF=oYU4pU4KI-6wwv*LdVsHS8CMr zz((JAKUdoKvDEqW{gce+`H=UIuui5(ypN}G@Imqvi@cwU_fPVBGW7R&QhsROLV5c4 z1L%(|q)$ReEVSY+KY6Ti%TFBV1rI++|EA9e@Oe}kUhMie*5BxR(!%S;KEK%k`?t%1 z*E)OSGxj)Z{(h}Dcwb3c$JIRi{Q&LX%I)(Ct+)Dofa($NyHdYp!&kKU>PL?(RDP(9 zzVY|FL+=l~q@OQwS>bnNe+hbH>4`N?zXlSoHhv2=zuFo{ehcBV{?^s?Z%OZGx%K6t zfB((ux9E=);iE4)t~NZ4!(S}Yzs0}*R@~qDroYm4ve15v_)u)>{>|6lB^}rGH=nL( zABVmW$9S=*dC9M6@$~rBw*JQZOIUyN&G)H}`1t6H#;FaDeAvoU8y=|R*ytP2za{HA%il;2(6U!ppK4ryPcw#HdD`9S4qUbU64dDMo_^KbNT+^3}Xlenfr&X@g@ zzUSXqAJe|bXZ!TW7w;Pn&EL)KFVX(3sqTNV$JO^MqZitLVe2@yB07X0y}_R~a$J!9 ztz4fENcuVI-w!}1LJ|E?BpyGuh43uYIC$!}Z009BOaDgQ@geus$75Wd7x%5xCFSpw z@cB3TD@E4ZK*rU^Uo4WBzt@AU$Z_c3s%8J=6~_J&pKdTtzvV;tO@-_);r+@#A5$Lj7AhZG&jTBM z>mbj+g|2_0TcLcuS6Sgb&wu%hBOhFu|L>94{QIwVT2Y%8{?X-A{4eLXF!cE3C6C(p z6)$(!o7Md+RooN|1h4X{fao{tq`8ke-KK@l(G+u90-S4!H zJN=Z_Kkc~P+I(}vwC-P1iX)Tc5q_elAteTerV{yT`@gIEV*9f)-x)`3_DVjYNeAl89c z2VxzFbs*M(SO;Pqh;<;=fmjD(9f)-x)`3_DVjYNeAl89c2VxzFbs*M(SO;Pqh;<;= zfmjD(9f)-x)`3_DVjYNeAl89c2VxzFbs*M(SO;Pqh;<;=fmjD(9f)-x)`3_DVjYNe zAl89c2VxzFbs*M(SO;Pqh;_iO18KhpPrq+z=a2aszt8bL#QX5Cx>o9c2c`agH`Vd7 zzOPBI3qI$iHhv40j|~#%v*Cpz`9R`)HoT;0<;5Q{{vM3K2kqa3@%s_KAMyJUzaOy< z#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jn zu@1yK5bHp!1F;UoIuPqXtOKzQ#5xdC2UhnOGUKcr*5<)g&bAO9Xz}pF1M&MXYksAF zKMwfW^B0c%>e_sB!%F|$<__(;&o6qc%{Mo!^v~_O3!WamrsvvxbHl7Zx1`S(B;~)@ z_tEbxzh+hbqG3<}YTPrg)cWvJ#{(}h;x2Ihr06?7<4N3u3U{+O=kc5gpm zRle(IoA%h{&DUyu*xB)X=*we``||3zmB;eG*roA$%k6VWdOnvtblIs-bzPO8d-H*J z@AAvnYcDhKa_7IIKrEO1^B-)y-mL3n*7+a$+Sg~!{MqXK&_}PCb->my)`sE`$KTUH z#zXTdk9f^L$T;3!hF{}v-=Xn(xv%^?#|G7p9g6o}-PnhM_c%K(Tryz9ZUdiuGyjVl ze$jEkRxNTPNB{ll4zF&y0iNXiql|Il7*}N6hgluxCU<>5a@U*I{UgtP?sF*>KD^7& zzwCX|oB1yPxpTi~+qTTTdB%RH{BDPKxs?vpUSW*G1ARQkL;3JP&W}85!vp)e>s#8Z z@p?;jf3rHy({0YYxS{`>`D3oyc=@=IEpvO{u;sR`uWYvg4t0JDHEti_V=K?fXZeX| zJT&hQ-Sy>KH(oFGuu}2hzPs-9=_f1mFP(Yflwl)2sr~+cy)|vpZ(8TTABcUHvy&qE zh{Fas4&#L)`4o>otMU3y>fLy~54yUxC=d(fCyfg|pEq2+zt+|N6PN*M4CRscMt6Nzy6d&RXQ974nws~7=o>nLE`aJ++v2JIzymdp&(=8jzT=>8 zFFJkWcV4C9<4)g>b@kuU3;ZBDqqgPu@fcSmAINy94evsCeFL1nEp+vh;cOSAss}85-f6dkV>kNHE zPtXa~6~-0eX&knq#n-%Q6Neugq+guxu5YeK-_p9TS?5{o^sSThZ|DfR5sDgz{@@2g z^I1IdGOjj$5Pe(j^eyZAnst8d-{_0b1rUFwBK_MC>EE*Ii?hyqi1cq(-=zK3c3*Dw zQHKvU&9xhL!7Y2e-8I+V;T^N4&i_ig4!Pa8I`)=7f7)g}K6%BSJ#Vm%NG>xc+UR(>LbvOND9oi9@A-(|!nB`={-lzIAZA2BLFn&%XbvmOuGs*W8%< z{_UVAj_tG_pS`c&$hX?H%~{96Uo6&@xPQ}m7~j8-r2H3MKg0T)uABL6bVbn@7doC+ zUR{6Fx>ITP<1#;q9)RdXT1Z~SV)LWBR$ltIm#TFAEmUXFEyd7yi${ORIQg*)Mf6Sk zH{b8Javj(Hjef`Ki)zj|>9LVtYqP#zL(jlFHtu!OyA54)4Rg-FcK2Ri^mX)kio}8M z|9I%~caPb`?DO=ze0?AMq_;|F!zXIpXT zALEKi^U}Xn+x(>VZ|DVxZdkVEN4FSP^ySrY;w?YpihW%F_DYq`PogL23|Oo`X*|bO z)I2_$`14)=RxSE=sAql>y+cPnJH1ig+Pc0Cy?XfGjZVxR)H(O&VL$m&!|R*m;OBnZ z_xN+J?~nuEYPfDm{|h_ihzH4oee=UB2Hp0ZPPs`J9Y5%kc^%g0)AQjt7Q$zL$%!fJ zo25Tlt?QBZ8K-aa-1-!{Qs?%UfTNx5LyKQ*UOg_y(Y$KIqkp@|U2od+7gj#{X|QE%2!+SVrw4O-)i0bWZLt}rOtPl=-Zjb{z&}jo9a=&zu$hwX@BduUWeeLFWBhN ztz-L*zo?;OZrcm~^qZr*bj@u(a<|WhF6z30?wvDa@UU}cbUrq6+O8i|`5!;>&~JYGip_H5Rn&ag;21YQIn4EM z|LyKyJ`j`gv#z(k`APJG{f^kGA8Nz%A#w0Rj|1OA@`CK2EH`~C^z%jEbbbwx`p#N?YI7^F7e% z+cKwbtXElRt-tkm`ewx?^|KxaKI-(%mxq24gkS3YlZDp5&^VqC(Eg3}Ef5_+FVv3> z!nbV8PaFsjRKL$Aj{fb6GV9+$KPTn+_D`Y@=!?%*-W&3qxW+5rvMoRPKzJbj=Cb~# zI)V+NBiNw&eYO>s)UU_U_-b2!)BPpP=b;ZEe)NrXGUh3Flle*FK;p4M^<$Hl<2>y2 z&5HB+PjLO)h!U;8Wi{_f-dBwINpu4}!$vPa{1z&Yd|+st#lx@1!OwBfH=ge8_x$YpBy?5Tqr+u zo;WFw=eu9^%un)s0P~adRp1^@-{3KS$o!z%mj8^?X0FXG`C@*O{Es<(BM#KOYQy9C zfFqo~={#=Qw)V+s+b=nN&~F#49pC$^OZ-?_;6)3f0R; z-17mU`%BO}eI8A1c%ewYrF`;&Iv#4HZ=vrmDfDyDIO=QKsQQM!farswHBR3IA6xlq z!?*m5gXF~qi3jncZ-blY`L|FV4dw0Y<|kiq`i4F!GS6q}8}pMI7i#zT;EJthUAI|o zk1H4b+v3;1(6O*(M~c2U-?-*e+ZV6n^lw*H>HH+~iHgkg6^mKt<^1$G)iyu5#Px4SyZt51^Z9Uy zd#+m17v~$#$$Kl)%Fq7EeVo1(TA#`#j`_)@PT$y%2r^#?vVW3!L-43KSFBypty8Xj z-Eo_r(qhvMu$}XR@UFRj`ML8Zciv#%EPEu@91Qwef=w{OF)dZ+Nwxw_jsE zlJn#I73trGr`*3;^TEFQTkN<#AArs%qA&O@+wyCi@^oBn=H_0y%e=bb z9oOre=JDAaKdb(Y`_AVA;?D}7bM-!LeM473pRU29FT>V2_51k5^Zt@c-St{}Unzdx zU&7yiWBp1IJwY$j#;<7cG(Wb*xAJNnwj%mgDes@;cM*NKqW=8=^hl9)GyK>VqA&2( z#&5-k`mMa=2U&lsmiKe9{>I;bW4#Jw-#v)FvFse{rd|$Fc`ZsI; zr0R*<=m&mK{g$27Pkt~op8k#J-?F-&rONwP>EBdGuvK@|PRa}AhaQJ~7P7x&S(U86 zslJ8k1-fDJ)vq@3is+ifSHI6Df3@oydH~WNVWV%XtKH=Ejd34pKKS@gch3iqA6$0A zH*Wmr1zmC*Z};2tSNyT<`oF)5juB5j5I@hq>H8;(T?ez`=_iu*>!JMTT|e_ndH*E( zp~yab{MaD=Vo~#9gGuw#zw!6GL+?*kykh%to)4gZLr>De)$aLj_Vcpa9>OHyD;=PoEJDu z-e1Ceo+3JdzJcf)BIId@}I)ZuCMCeM*X&2OB-Y_T_~KYCL}QP2bOD>74qx zkLtH<_@KqZ4-dp|Vd(lB{SZ0>-t77}bOwD=8@~@Z&V)JhU*BfajvM^_)LRUF^W|mz ztyIsyW&OR?=exBpLLU_2sb6h)J|qqvwuSIQk@-pVE$#cN`h?B*CvDXIC6->Ou7uj; zgO9CHd za$E~pe|y)~E+x<(mCw5tnXSCd_#h4r0t?%D7 z4x79n@1H!*Js-gRsZ?C*`nQ)xbvV4or=QloxayZ3KG>^eZmC1;Lea`&`3pUcFYYyW zeV4iG&HD2~-$38-;DJ6q>u){x?Kk$W(KWdxKRRsZ$%9%|9q#6?kM+0E`@{V!lph)g z59GKOqHli~f7d7bEU3v{@VCA*ub$H?HT+@9`8dC%c^A3stG4|m`yI0D-m9m5RQtk` zd2ehn^|RXN1(!G*gqIXGui9zlClC1>-1YJIP1BkuwDR!1$qLW>)9|-PUHwkJxnWlK zvs8JvUAzA|bB}!|4{qaZ3*mtl4?jE*zYnwKm+Jmzb(~js7svP-Op0xT{GdVd*0t>O}@EdsqSw)PTOva>elAteTerV{yT`@gIEV*9f)-x z)`3_DVjYNeAl89c2VxzFbs*M(SO;Pqh;<;=fmjD(9f)-x)`3_DVjYNeAl89c2VxzF zbs*M(SO@-h9ngKo*7F(o;U~S1#F58Bc#5INBhJc?-{Rpf_ITvscotg6@x^PLkI#5$ z9DL9h2T#$8!w;YPgAL*biNglrgX+ijp~k1p=lm5pE`H0#4=UgCa~<$O_@MeV4xS=$ zip0ZLTlv_^4~>JTNL~v$o{r;J)ck5|o=}_PXg;;!fuZ@~5vO_8RvtEaLXkLMejWGa zCoc$JZTu1cxBkcb5${Lri(+3C>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6? z4#YYT>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp! z1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe` zK&%6?4#YYT>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`!2ewbcKz>1kJ#*qxAV;n zEB)Wa;wg6?ef_m-^34q^{d4QPW~ZaKyyESAbHhsi+*<$g*YDi@hc)@;hFO1Zi>_|e zr?imsCEp7>|Ln*6Pg;?myW?;6cyU0>+!;4Kf5$a@Z|uc0g@+B2M{WE*BuZWQ8wbbDBm4|(!yS~nQG+r;)U#a-)p2zLecc-`VXP-6ctkEsrt8Ml0{RJnIM-cYwdvcIFRWPdAJ6`+_J9@_Uj6KETjan4 zojt5TBu`T0xJlzI{=V+|s!iW^T5wVKE0%4P`}Z*y9NcDA$J}{>=X-4BTQ>PYc-VRz z#uefD;us(0uCGg#=-X3H-=^NW+h-H&-mm?}kNWm!Kd8y?JfF- zUQ`-B@A{b^y84e^D55{3zwzPN0k3q(b(z0n#Pz@KydL*ydFCmf?%rv=E}|=nl!2d>^fG^}`2s zTy1!uj;sBU>)+5fbfZvI9l|Cbwhu%3@X#?UFZ#Bb=$qaT%MQJt#KQ;iTL=$?r+?oJ z8-4qZtN%VdL-#*D$Dgo! z$6V`2{x)s!DO;|`)6cqpqdQx4&7mjov&NaOpX4}d<451lcGt`GS1MlN<|n`8>OZ>B zR2ZsbNzd;9cYVv8zE$ey#d#O2Z|Dk$zF?z2_b-OBtEpSL*J@(ev*F2 zLiC4m?6i^mX(9dFD^B0|on5K;i0j|J?&j}QFVsdie(2^abzE(D=#GWt^&$D;sf}L| zee2}cK>EAB!%umu6p>Nv1 zp>Ocep;<)*1vpQXw>$MtVtE|dN(X_G`p}mM$?~ENXt{Cw=R0e6Pw4_1_Ks8|!cAfg<{Z zUv0~ezFE42-=}x*6p7>b*vcE}`cw8#>V2p*oBfmM8+sFpK79$zqdZ?;#t(D*U$8X~ zHu}c=B>NPz!Ux^@TUMBKeiD6SeiGF6H}ob{r{IOgDW5nC(YI=ypG>M-mBMF!vfB1f zGCzqRZSh0(P4&pKIS%8T2eyUuZ`HDY@;PJwBzjRTh`#MF{aaSQ8w=IV(DO;E zZ{2164Sn!9rOuQKQ{63utA@naS(kg)%siLJQwp{N%>DETz|9X59yPNMe-M$N8{N)xtG&7 z?i2UThlTQqOBinesd*S(R<|ok?MRWx}Hu|Bc^Mu$IlGlgC!?SGs zAo|u-o)6IbQ7Jb4+mvRqe=@87jed>!$UnRJ$*k~Uw;pw&tN*jzeh~1xV(;Mkw>ED7 zBuM|px*Ev-61DML$nmXl{8nD#!7JVM&2ra!m%Fdv-OjEQyx8q8+1m9t2f63Cs||;_ z>wCfVZ}+&*X`u(<@q5tsyOVMFAbtzsfff({BG}TNa{gpz501%0riwr?$rF zxZ3b7taI0wcK_B?df43bZJ6ud(4(v{>(BF9SMPn#che`)zghby;n6o?XN}sosaEC$1yU>5<99lNEgtz43(e2`%j#`+t&p@@Ei zD`{LPpZ8Bz>-wAO0ya8QD57H)l9zF8j&C76;w(hps%8JA_52%s6Z(SQC^8PeP|PYX z=R^Od&%gO}q|ouK*4@af>u;=UfvjJF*81C-?)@dKhb2XhM;x|=@GR6g*576qxc6&n55+?3XIgQrzw!JVdQff1^8p8Dy#B`ilB~a%tiOf!Z>+yD zKM68FseKgvnD%RG!y_JBd1}M+A^n@~pUnFHW|cqb`kPN@iX9K_$KZt?uhQ1vsMl6q z)%Q80_4#g}PUyI$hwvE3Rzw%oZ`qm;8?^FOtG-ox|F+mY|4=RZ zmh|~I^bK9J5dBkBzK&zFt_L5#BD^D(J%8bdgSPPM8~s~WzZXiCm;EKa^*8j0ehK|j zTY1=_NIv)+2gIKg_i^*D)v9lnKKS$oeSvq5=lzoK^mz1R0yYwr5SIvIUY zrC_Q1H*0+%E&pQU`2g0nbl#8oNpwYV_=@lK-n7-`xv>YI*YWYCT}y_8kK49?pT1pl zdcIu8vWu^8xIiL|+tH*HVAlHab=;>Urt=m6`vmRMhvEpeKsx z2YSLdHi+NS8RdoA@Dw?o`Z+%1=o{;ArTX2M)p45J`WyY5@3}YD(F(<^J}>ezKgsiN z%ttDs8@r$P?zUIl*C98sNAH1uYQI@-fJ5waH+pQ@gdIDs@1uwVH7_>vpFSRZcp8Ua zvD)S*ugd88H~K5pnL=&!j=V{c`AMFC zj>Gfa@%gvLzw>SNZ}fTPg6yB<`G7+IorlKx^t0IUrLLc0e+ha4PIvDw3ANE5_-boD zc$S}WMa^s3_|Z51en4n_ELR@$lk{)sh=o2~vEs7w`_6}P^sQRoKS{p>(id4ae$^+- z507zBkAvSr&12d4wSTMBew*`7`u<6t2LRDG?6fhdK3d1;`L|O2{kKBH101!Khi?0ze67jJx;Z*zw!P_pPrx>X(RfU zc3$$KZ}e~F`a3c7ynN5Up$u4i_@4S2WEiOFv!CJ+I&VQfaJqBOn zeYTbV-jwp+E$98bUr*UuyLyaw*E(GK6p^&6+I;eb=1vzVaekT6@qGE88P#~brTRSRlP%;t zLgzip-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT z>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;Uo zIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6? z4#YYT>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp! z1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p=Q-VAP?%t?#(*?{_yhtn_-)Y2zM)|GDGZ zd~?G}|J?R}c+&4i?z}eN+_2I=w+ZuyFMe^$wfW|TmHxTiecizEJMFnLKVVARjnC<} zs#bBJ_y>tyEZ-Ne<7(^qsjd7Ux$CX;&+UY_PHT01=hgX3`mbKF@8o5*7YknM>>ml@ zSH9Zh1$CS}njc&FmTl#S&vBNnZoJ+~|J;VY(euSl3s&cU|ATiYFW&cs+G6pJl=4{T zLH^qdJkDvaH(qb0e{MIQ)8z+mZ~kU}$DMz0)A<*zsm&U{=8m(yL+rGUOFk?AfU_H~ zH|x)B;nj_OJ9xLVErbVJJp4VMn0L&UgIDBxOt@;xKaATb_sQ({`hWePj=4`$#*bY- z@`L!(!hgEsXue(C^*#O9#_P52&pqz`mOBnPe9iT*FtVs9a~juYoY;=ru(lK&^}`bK@T@p|=hxz~MO_f-hqd(O-y%Qs(@e{a|^-#_jj zAJ>jKeBygSVn-Ap_GgX0eQ>W8nr zp8w_SS8siJ$2PeaAHS@{kPkbp$E9NHI5s>`{n$QyA@F$Q3FUv_u5Z`+#_Q!iE^^;5 zA3hk!^W{lve2~+(ZC(BU$mzpB1l6xLeEb%oKgtiaiC6uyZ1_97>wDSh8=qTRc)$Bx zONIM8eXA3FLl;2Rt1pSpF|K}%#|DYBQ1ii4zvjaR(YKSGzWMI2*3m+3bTe(d$LZU< zPT$ZE5dBGN69?8CIv1K(dE`~p<1l`R)3-5B-wM5t`g^8Oo8PmA;yq5^e(mZ%`eflt zPJbB3E_eKgyT0w6z7=}Ed~x(w%ENDAQvNPR|3-fWq962i*o=dWD_VT?2SgX)cWD*XLntyyXw& zS@G&e-|7SVH(!6(S|F~`ULr%TAOYXF} z4-OjrNY@Q;*}?%2-qyME`uS4MgX4qh#|95}`c`kuPnOH?jnMPB-|5@0q<=#n=+lZt z`nzKDXnbhizq{+(+38!MeRpV__G3OfG%vi~p8ieyFtzF1K>916t$Eb8;w`@(7an<8 ze_P`EH?BXlA1jm}8V3*bofrMvXs2(?`zg{N`H(nxUzT}H#uedPsC;br)^QlW#$6xl zZ=v<5TzR_wh900B7IwYu`(2Lv{iYk}k)=~<`Oi4)Yqf38X}@0Y=-W6?>-?)l-}pVW z$j~=*M0L(*69f7^&&AaXUcX!S8Jn+_OkJWcrk2{I2D6}Y`z!Q?c=A~Y@4H8@+IvE`4f{j%jl7ChUi8h{KbiQx1=da03HZH=T`WH2^lhH2 z|EddF=}%gDHIH@v^l!bLz7>1leDQovSz&+CH}nfcU$A|se4kA|u>0UB6@*eZJ(cUMa|>0iC3P_f0dh`9PifO%KdZkoge#4&^Po&5#7Mw z#n89coW8*aiSr@+*@nI`KM4=?<*nBJC8}@egd%#Pezik+8kf`#Jr2(Y^mF=V?b}Gp zZ|##z%fC~R>u;=YDeC%{&(`=*Tl+b5PLcIDo`3VLZw5HW znup^k!W$;fzxi~(+{bzTP4x}k09AK%f1@>yA3ipy{|Jhrf4?r(eAMmGzAH$TbqZ|p}5Mf9)Sk>}rb zaQ&NaKchA7yPu2~`@JHMh5e*|LtjEs^&~XT;-N>3Ykq9TLy_m-N~Leq(X5d5H=gfC z4=nw#`ZwahP#(u2PHp@a>T$3Yt8M*_`AO^fZgdL0pg*$aCx3d=m{-p|rbF)NH){`V zdC?}h;SQhv=z-ra-l3h@m+Ah>jUE_vTt43=*UI7ebKBn6XK=?2o`d7OI8O`p{Uznz zpZU)3VPpM`zNcD{=il`GCBFMp=y=}d)8_+xdNFY2k*5#%L+jkU zd!}97W^m_Vy;A-1*-x(h*3()-j=twY-6bRN>O@$3Ag>R6@O z`hKp^dC8=_m)(24lE(S?p?P^eV8vUg_UVU?V}t0G&xS{zW$`UP z@j8wlWPeFrf$MMRVCa2^52pV6RrHO12h@JZr*B)1c=o<_?K|a`wLft6Ggr3H4PSP_ zZfBnV#q-$Y184rZ+n|llX`Ac0)u{cSea?Q53A$(PpX7WkWPiyx*T1EHAC33D7CKMn zi~EbrPlonQ%A=1e6vYR# zSi~67Vedd^ddZgZLE>bNeSpI(-ZMU0ANXyNcXj;_I)< zWt{yb+qwRY{)v9dhe`Q-U(-UZqwFu)&b@yU-LMcHvBuR;pQV1E9U7o=ikOw$@?eSU&8ZmR{w*a{UxFMCy4`z zw-CLfUsJUDH!C0h=KlPfuTLu1arT$&meKQXJQo0F{rgkjKZ&j=GS8>}akInpKb6tN!f2{qJzj^tz{0Vn;$W3gq z@%}eI+d7B75chnC9bN?jA zK}SK3%l^qqeLolTE}-@Pl0tP#=NZ*5G_S@b&CC2`sor0bR^Mjj(f-ZSEA&Fg@hj3l zS$y?llSgg*ZbfQv`_m>pfzw!H^ zTyzk9)4%_QKB%6k4bMXO);NB6Abtzsp^MncXI%BIT<(9N=i$>g^i0v3pG4n6aZI;K zv!^WXl6&dKURUmY%om7GDWX>(=Y?%ywdq^d&%f#WNYI;NF=<}rC($?N5wpUqen*m5 z|9$}d5y<`w5I_Bs`WXk|VS~i^5I#sgbk?%*gY<7N75Mi9xL=|D1ANdI2M_e|50LdY z)dy_V549~`QorU^+scQIS^R3#xBF!OBzob)7q)(C!ilG~$?d#s)`36&XFC&n$#_q( zbH~=cH~f%I%y~!Od7x)L9?u69`}e!~UD@O)`%CDH6wxE~TXyKa65`=oHhv3<2bIV3 z0sQ@dtoy61_hI_9xG zzw54#_fIChZ=wA#dEtZjErbUuPyd}g;QF^2lk&U1zWGPBoBeK+OV@1jnHM`bo)5_f z58H?ELHJ2+;x2L5$NC%hLGMqc*t}o)!+mZUch#;N=Pp}%(dwa>Y_b8)aehS~pK*;# z%MQ)oW#7i@>*MxMX8rl_y~zrPPrYS}-{1arzPVvm_p?-aC*1i_?b27?$u~DF)&0%t zIOA_vGjQ$>Yx3g~V$!%m`Jr*}K;QLbbw5j$H~;?8kL>f0xAM&mOLc$aaoToURJS%C z??b!~@!vuG9>h8j>p-jnu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jn zu@1yK5bHp!1F;UoIuPqXtOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;UoIuPqX ztOKzQ#5xe`K&%6?4#YYT>p-jnu@1yK5bHp!1F;TdRR{X))@MxJjJiG(r;Mwaa^TF_ zN6nblFnQde4bx}T{IjN}X8Pym^qQkut}~`in{x8t!FAIbW{jUyKcnH~um3CZl*tV< zrq}M;t9$*dsnhDGPj8qqxwglcU3>QJ-M@a9LEU%mKBixfUfstwOq^IhZRDwS6HlqH z?fWl#R{fZfjo#V=>LyOF@74X3>GjhZv-Rk))6jl>`|Y+%-$A?Vx_kG&eFyK_fAH=D zdJh^jXxILG9PdtK`pI<@>%U$%eQN#5Gp5zeXqZylxA%Yn{nq`zSNDd=GwP>Jt?k#V z`?`~!I%V3r(aCiaIZ`9L-v$R6G~ zBadttH-5&*eWuKqF=f(v(y0ygGy6=dpSbQcPOaZ%dc$|?YsTzwSi5Ddm)|>tM4xj$ zKk1q=>+a^d|E>8SW78W(uRF}>7VFs4>nE;+h#k&9Kv+g$5Pp+G^?j-jeJmTnecX9fNDWlhYG_&hQ>{ma1!i*_XM@+1nJZ9aY Nr`Go#H*86YJG6de<+_!XmBZ^tw$~2cymEN={~!5pui=)jG;AK- zUfbR{di_^gwl;2E$9n!n+v_)PuWWtOap#BEuUfnD$o9cUHC?rF^ZMa~kIXp4GB&Pn zxQt!rx{TeXlPqIvef!26hJ{@I$p1fg-^$7jn@6u*zw(ivcjI_IZra$oaqY;)t$Qih zIAynK?dJ8Z{k!cOhmNeT9NKr}crG8^aMg8NSFRmd|H_aRQn-INMf>k~-m~_-`<@F= zwC$$)6;HhAx>sEKmv=rgZ{_f_e(b*Q`M@*(@xoj84-L{-?(vj`x-_(bRlbe^j3bQ^_Km+ zKl0TlcKk0+4BuSe+T6TpegD=CYgZos+4p|0ZrWIX<2l!?A79+c#^t*ydQ|h~`jOq# z9iQ^ZFU_%EzU{BR|8;+L;iE?Et!(2*9>=`L{*oTu&3LU}(wi?h>znkqbg%6szaO_e z=9jeQGH%&j#_p~^=8t8sO_y_$D6md zH&?c`q|hA$||y?;v;}cp!Kncp!Kncp!Kn zcp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kn zcp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kn zcp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!Kncp!M7JrCSHd=H?a_4miPS@m0SqkT4f z;r%{%`%*gJ^FQl;em)CZuWwdy`MESc?Y*#8eEhKPT$_2hm>b6%pBta?o?ovYKe_1n z_4;$;S>wGp+Hd*EZCAg!c5b{FKN}s5yhq-n-f3U&#NS!`oyFf-{GA041P=rc1P=rc z1P=rc1P=rc1P=rc1P=rc1P=rc)aQW+*S_$9oxqdlVbOW#T_1Y?Z$9U75A6h= zJP(V$|JOh1DJI_V`nO?e7n~Yrvn#MCOgf z-tkzc6_>osdVgBSNB95euJ8Yy*S_XU2TJje2S19Xey#Z$2iqE-+D=|t=YQ7z(fzCL zI`?(Ic*nlgYXoUuHY6{-_va(Sz5iagI_N+8_nB9H_}<_6(Wk7A#lMyLo;@~>H;=d$ zGM|2I^1yjW@BQx`?xKMW#8aC*TsxeL)Xw+R{C)FK|D*e7eg5OypFVc_s)uJA z`1Y{pcMJ z{ihzTt+zZE&bc=AAAdhbW?c$h6(@Dm8==rJDkDpQ` z59Foye)Nuq&Y^qg;H)uM_iL?}Jdl^(`_VfdI*0yiJzQ#g`divxdwt8U|F!it*I#M< z?#*XC);QJw-hYog2jt?|b)s|V9{P!{r`oCGa{YSaiEAPAQ;|H7m$7-BmwB9XbM9SB zN9WZKc_1%SJFl(19?rRWZ;qaRjhyeRojOkEVWa!_$+ekR8_5HC>2>Zqt2mrVMAnJw!)y5x>l^jjo z>fWj7HuZn+^{D=j&Fj3(v)=!EdYr1e_!*1DVSa5JzaaHLd6}1e9J@~Lxl#90AJ;x|h*u0LNSNr+i^LL)y|3}vq(OueG+4xoL6<77&%ip~BPgVbW>pt<8 zUH{QRFjsGxr|;EsZT#e7X&m&Q_ab9?d|u|EbE^N|dvdQIo%PW3&7E-NWY|-#O%^G{4NB>T@FJ z+?;!(x0deHuE;zK8Bag9g^W{VJnf#HIu1YNf%?DqzV&`?K>w+GRc}50q`qbxcCH`h zE7tO7`O$Tv|Kz3je)NvVdv5ApbXgHS)%IN5^HbW-di3)MseZ}BvYr1^_o9;?qPJ5= z@8{5d^1!)BZ~pHc51pg#MF&Cj6SUg3U+Zw@fs6-f2WbbbcCTOOdG@UJzxV#@=l?AI zML+4sR@8oMMdoQcHvOQskF}Wxs(+q?$V>12$i4ShI$rfZRVP(n(Ou?iKkbZDEbYgi z`tfY$f#^SZ>HR#`J03ch`~H9Gy3hJl-#t6@fA91E);&G+6IA`Beb&hOt#w%apBz31 z$+<`>|8veWZ-~yJd*~tebSORKYFX}%mYjN@l%Sr4)Q==dh2oTc%lD~{l24R(|>f1x>xlQeFy2s25DE+ z{@OP2)GzH|YQO4#Z~pHc51m8z&_UHj+DlREZZA%1zxrjJ)_kj-Jdl^v{YZUJ?2XIS z@6z%5{ePBzt4^x!GLLr4_WUs4Li{RP69zQ0d(65B)c z7xcz^{ojQTmMt+?v1)Fw`D{onh%rgl8;x%2)X{Z~XE zX>V;4rx)_`{N8Z>f777-X9=acU#$1TFvMfxLM6oA-X|TA$w8 z`l~gLJdl@p-(PS2-2dbJzqQ_?v&6wpMdpn~;xJ!ro`cBCyq@c%uG@P5kA6}YE7A|r z-`XZlDY8yQ&OyjaY94EiORd{#jpK7})VUH)-pa;rDv}4j7ok?3YyE!I z_Cx(o-CIjXJwK}RmhJhe-LLvzJKv@LJU!3tN9R=kRTpd7_(?@{x)h0rAInDnITy*z z=e&*QbMAcZ&8wePFMHYOy~fk^YJaXx9>`1X`AzBgT6&Y}NA(|lE=ASbQh&MgiO;-L zyOsW@?nmypQauU%f9!n+^q;(x=6CAzE&WF)$D*aL_)G2A&k=ey{>LKELF8rL^gMUH z=p4F-o+_fFw70VH>mlnoL%WrPG`hDl{{XgVo>ibu^KI&ZR zUQb`W{;|62&C|Hv__6)ufxMLFck1)`o*Q&e>ul^?J4O4}Z06>V5oTFI)Z3JvVf(m9DB^OYgN^=he2U|M5#6$V>0%|K9O9 z=SKH>>n*ygsLu=9-^!-`CojGCqjx;jf9ieo5gVPQ-Lo0j8(FW$)%9vWHu_Iqdhf^1 z@OgvQ$ma{cdHDR_+lTM}M_;L@d*MUFzdNAuTig2caSp=gB1_L#raqtV|9{VL?oFND z8u@&mhppG4{`njv^}n8%jI~Sa^j7n!|IvSRRrR-(jbD)Xtx@A)|H1J2zp0-8=e|$& z##85_d#aPUHaa>MiRU5y8CT2Z^M8CUve)M@iR0~8?S9^KQ}AFhs0rAs?E4m z^$<&tNw4L>#2UJb5;M*Uv#my zjb9ImI~MWJe9tBiJQtp zLe>A&JkIMlOaDEcOzme}Dx%XC;)j0Arac#z_52^*LpQ-vJx!gjbv*HFp|uX$LC!%q z7wKJ(^?q(Z=g>W`KGuG874-Dg>&K7A@oeUmB6%P$srxan{9jxD(R)SoTlL+unb!-^ zfATV~``)ANv3C?N1*&eYKYEdVbJd9jDI&rMCLl zxY+3btlt0ke%|1m8{I=^Eks|f{?_dpf2?i2Z&VN*-8%xJ)*V&#wYaOlHSr5-aoQw44|K9O9=SKHbCrj;8eJ6e{5|??Y zHhCZ~weQ<`{(9*RanL#R-_ljuQ<3}pI^NT79pB4ly{iAU^>%9hEdBTN*Xy_R*wTId z9DwED@|)YP{+X}(&wizzr_b9s?zyS{qldlF`uF+Jd)86g#xJP)Uwc1$>$?|^Iv3qT zCv%Z=0_L^0iKAE=mpq{VUjAzLd(Q{8`#JyT+#7x79KV*W=k56Kjo!~g9>`1W{hgXW z&bg<0{*Nw?Mb7tA=aUEWGVA@Stv~9IIv3rmt*`12ot+ixdekNlJQr!c7R}cC|LA8b za{r$3t!(^)#H)?mgCs9J|M$XP=l;ae^MC4X>S51DPZ{UgjBAZ!*Uvc!=OUbwEGkm} z_xk+5*7<5{UEbT~a}eaE*WZ1uIJx?S&Y^p)blIx!iRbk*PW4)C#*Ibtz;jV;KyIdToEyxzxSrH+6ZgJ$7Dh9BaI`vz}BW59Foy z-(&B1)VZ8{r`E|jF1OCs`BSmU19>Us^Qq5A=eYNVZl>xn<3Vp8{i@rgHt|al{pY!8 z?Eckyo@?!gI+wba`Z^cU&#_26=9k*|@lfNO$ivUS{*r^Aynm;4{?&hP{^$FD(LHoh zbyRJ}DeAaZHhya(c_1&n_oH__bdGcHspv4z6N;_ZM;^#a@BQcB`=Hm-j|vd>sq-`rHuXR6MS9m`z2oWo|J={Z>3Hh;+Ng2Vt`$$~|Jv`{r{-^U z`1gOPdvoh$9k2KNJzMA3vWaUUap=b;51fni{(I~lkMI9at^X}O)pL5!w*1VxojhfTm}u8zgrb&ZX~Jo3_eKYGVQ=g_@WJn^1<_;&+6JJqi>&hxMR zmY>vi^3r=hddE}!NB=D3oS+mf|Jq&}hxzK)vKdcayyvlb@2CDp_r@Z1v*K8P-aL&{ z+a@pbzQ5l3sdEFLy?N~mAK1}6c=?<6{uA~8hu;62&w1QKJ7GUI5A(ji-uk)c7Wnbo zH=q9Cj^@G3-@NyubKG}JMV+U%BI`K5xGVv*w?O|5!b=et*al{hrM@4?RD*{a!q)KR1py-io8`o*()HANzY6 z`G|Z(Jsb6G@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD z@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD z@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD z@IdfD@IdfD@IdfD@IdguJn_K)`>Id;+U1{mU?=e8d06zn=l}V?Uwp}D-ub{z;K}o_ z==*=@AMX9xKYqglJAo(9!=ms1*WU0a_k8fbJ-8Ei@;tEre2;&w&Ah4NCkFk0+b1vm zk;mWuF9%Zb$A|HX*8CqE#*anIuhl*_4)e&%RQIjcy3qZ*KJ|qQE_unZ1F87oVSFm4 z`nATT#?f(>Kd+s<)XMWz{h<3l{h}ZK>)-nHu>&6d>@cnry*Tuj#$i7GJY+n1f$tX; z(fwDy>#one>>EFSU@Trf%(rl?Uu&Kf=X;!WSo5@kobI z6Q}?6lU9FVz)~AOilu(N`5F(~n@1kd|JM63YhCz_q5ek?|MpE6-}vAE?UPqOBdG1I z>{fpHdGHTvJ9$`E{eSU)ea5p+eeaW3FB(v7+C9{Hxwhxu>MxD6too1sz4^Bu*u3sn zPhEY>fR;^rZR{1-io^56(}#1Bj|_S|D{TGz@u@-oe{`tN(Z?%9|2>;|rRc@c{?d5N z$1mpzybtm6KWqIijgQWud+1>+RNZYANByS8y?Z#=j4Xf&TMcWaX_@dv85K=g>XXNpw-$Yum(8tQ`k`io72n52^Kk@BOMB5B-oQRu8%I$kA2!D@A#GPeDG+QLXnev*LFzE_E)thc2d~>goM*Z;<{{RKK1*HXiT)(f`(e z|Fb?%=A0YdyIAi1<=W_|Vy>Uu@x)^u_|oC$Nwqouf6ee-q}4fZs^8Y{oq5mAxwpO# zR~wzx_A{jJFSYRxT5)UlzixOB^5vs@p9kC-^tc!H<^%4zbMGCU%tf9Dn5Q=FwXt>_ z@<3i%^Z%^Tq*X7!*dXM z@t&XOz27?jx9VeUPp$Ly{eWkyUyYk;=e{q{`~R)>*QzU9#k2hKo||)T)zefvbsYL% zYkVy~tcyI57b|az(oX%~>;4})i~fSzUfU*)qQ)T)~%x@?zy}QQG;Oo4)@?Up@Wwbe#Dh<3V&j)lOX(@hr6b^wR&S{QjVGeC`cB zohqW!t=6~f`mg#}+g81;?Z4FDvg$v&r~0~VHhEYU{kQ6WOE`5qwbS~!(Bx(?3&`Cg>d_jI+#^?DD)=iGVk zjXo=)tF-6Z%=0kUua5Wp)35We$-{}f4DbJ1pTp0}f9mJ|YS-DGKb||hai#qlXW8^$ z>;E^&_x^acrPJO#tKafxwOfAB{oH=7|E+WSR_$Z&{f%8u>HR-GuSY#zYU3vtbK_e6 zx&OzxNbl#r+<2wB!Fz7%UUbkx^mA&c_W{SQPxZgob69Ge+RvHXbK`q&xF?6M=Gx55 z#oRdFcq_hlAMlrk_aK)I_y4qBnw8z^{u%fGxc8QOpN@Nein)HQ@w^|vzuL4j-@?@U zgKNY6zb_2;|2{Z8|M$YNIzDzD_uSDvbTL;?bI13JqwDhGF5CG(`p>;LbQArzY}!5a z{N(m~@#xR>%XroQ(&wwx`KkI}dz{|?w{$bLUF&JJQ~l(Q(>Q9U`s4mTd8z%pH8p>H z&kf)IhfbydOLmtRW@BQc< z5B=x12=p7H8L-){Y?!{TQ)$YwJ?Qb2IJSUkFJ;c$(^4XmpqV{-hYq1 zN}W!>a~y!76W-to{mbPs)`F7|Bnn0g%Bns2pxap>1L*z{Ze zt#+M99>`1Y{TTZmf9yQ;KllAVI$Vlc&#PVP-L=G$|7zEY!~1{o();~??|9zl|7v~SPyKKXK)c$sm!kSrTm4Vv^MBsoe=3i7 z<9>Dc_y5p6bU(FTw#L(*i@9-g{WIQ*Lmv42pO-)SYrX&L?I&0Nsgu#w+IB0yx=!LK zE_?m2=klISy|3ua%k5YHYE#GO`qlBX{`}wAI)3at>Rhe=t-5($+tGb*{mWMWQ|EH; z4Sh^S{oI|}seW?DX&kk4{V^W>CojGAn0P!_v_{o`t*diw)lc_Kn zeyZ*H)qc+}_aMkiZ~pHcPxYUBa;ZpNo{H!>DueLSMT8Gu1`ksOLya(aEh~}%7ZQb+B z)tl1seE%=+|5abHLG-ql-8+uzf2qDseZHmts?()5x?JifcYbc%sl5Mht^4@5^q)G} zdk)ZXwQVbY?Kt?Gy8hSFAN;8Pqk|yzvD%hC_iDG+#X3__{pkCD@?@c<<73;cIIY_G zcW!Q%fB%R3av=BQYuWfw>=nm~tM~tVKcDxGcjBJ=aQ=^8*1AupemtA_x#;=Te#<}l zPhQZ;MMX>h(OtbSM|*AT6~|k*p8rp6-)q+soO5&TjUIZaIU7t>>BV#i5_~|J1$I_lt_?K0lw@wX$mS5_B>RwA1RVT;VW9N-sSMEH%|Nj%izl&U| zuT!6o&Y^qgriG>Y>do&J*NQ_PcrKdy{(0-;Ip0F=xpD6u9af#j9*dq|?H?OQ=c!-M z*81Pd+oH5n=Ti4_Pfih?r5!z{9tY{Kg;t!}@%Z^>{rP`Or(3nF{-e*O=$+%2`nTq5 zTx*=w&T|m@-zx9;_4vDWzv{oG&$KTJqW}8yESmm%zdx4#qmzp0EA492UW)41vrFSK zKlJ~B&Fg;k)K&D*LUb8Kj~S2tr`n9OP~)i0ywbR;|2*e$uHG7n*9$rS=l)-Az07zJ zU2SEz^2@q8|0gfK_F-0Wdfoq1y~PI6;Z}C4Up@yxUS_o~z2aK>@98c5Ao|(LZsk|k z%{d5p>9r5DieuIPo}Q}yVyEhF>NwWH^96A%oALMuwcWBg2O%%L_l3H!7gGOw_y5pG zQ0r{ZW?m_JakM`-Uhn$9*FIZutok24u8n#RPwm?A@Ru5g`k%ab_4B;<^ZuW@7d@Oc zs^0fn4|zcUz5VU254Gc=bLbwr=pi}`qQ}fr-KD)0iKB6`OXHFU@=`nBoRhe=sk0T)Q`$>y{3w?C_2z3lZ0i51-~aP?DYc*X+~^*9nOZM19z_ym$#66a{76FY-?U=yBD`s{Dbeh>vJ#r z#?K%4^3lETMfl*5uU_aqAJpzg=g>WLaIAhZKNY<=+Mnvz8>fD;$pd+*o$smnL;pGV zM)$`e`p$f{X)i_f>)EOCcn%^jQ`@iF>!Hr&+#4O$`rEQ;FU4MQy*T87ywuM3)cjHB zzIxC->SqtB!$GV5#t(Hr_E^M^#>J)||7z22A$cG#Q`@iF>p|zxJ#%!51m8z&_&fv>{gf?m*)+|-1TW(@^Hp*E>fD`-1)uF6?y;9 zd+$=c)%obEqK>O=YdmZ(4tZ#O|3CNlqSg2l=iJWs|EKCFx;%FL*m=2eiK7_$|BPpy z`raq6TKY=6hv+-wa&6}2Vrd-opXZ{~=k<9VhyL^4TlLVgX|IjwH-6Qoy*3g@@Bg=c z|E~Uf{~l52Qul&<4j!bv6%q$*6$igw9P-k8KYGXGo;&CNfqVZB5_##pAHCzDbNu^1 zoCDNG{43UugFlUfO}~farT2dHj;H$1a{!1QTFAIkwESy(X&mOOU$tlT@Bj7Qe{?SJ z6Tf!(rykhRJoMg=-to}6z!zWgnRh<0qj~7PAHCzDbAk8%>>t13fgR05@BQc<5B(3^ zyYHuY=)E5iZ|{C;niK{2s*LLGVEEK=45DK=45DK=45DK=45DK=45D zK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45D zK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45DK=45Dz^r(n^m|^%uDdiY^CSMh z&Y$GTLMwmPcx|8heAcJy#%|qDoT(x|zlE%u{#;w<>HOCH#8V`$g*sksYaZ=7UTwx% zsN>bP=F!f0Y(@IVqWa6V$Hrw|Zaf{2pRuU^a_zBkndimRah~n@i~fIIXCpt6pQz)a zjtd?L9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)? z9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)? z9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)? z9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)? z9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)? z9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta)?9ta*-ZXS5=ublli-}$%q?*yJa4~zcy zarQIcaO|x6{%$Am1{lDxbe}CC~e&+t2z?0`;(f9wUUwY~{y#LIHb^=eHheh9i zbpI_s_v-sDx^~}c;9efc%cAc;y8pO8{Ez?sr!Ri^z`WqT#_J_7i@yKp{&)WHH~gRf zaKS07tBnxpO9xprzC9hVy?bv$_>FXU@c5uHQ#U%K!1 zJO1R?_pMG1FB#TlVXgJ@TtQwIb>I29&^dJY?T6p-@+aJP#_C%Kv~1d2qpqWMJo50$ z;ar3~Eh>KXQ2(Qkul%Vyf903nzp`rKr9;1>HNLc6OXo5 zqPMkd{CHS9uIG>Sp#S8h_kQ$_=jnf`-ZJ0Q-Kq5R97JAv??>-==p4F-K9-{Ds%I0A zaca|E8#NAjATPc5qjx-Xj=Hy%u1?LbKDV&GWz&D^TyzhetcB<*{?w+uHKxWT59Foy z-(&4~Ufqh$p?m1Hhqd(Di(~bVjc@s*ec9^&pB(Q0p?m17>Y`;o{@#Dwyx_)Xte!d` zwvNZIqK>O=Ydp^;59Fox-{aK$q5oesyiZqMv~2V;7cGBYdu|*Z@A;>nJdhV_zdT-@ zmwt2(-9v{}FI(C8ZH4;tcyY)Bd6}1e9J@~RAKgO-$D*gpWBoGE^RNBPOGWZPUdHBi z?7X{%_YI{;eDy;+_@6}o(ce@=_Z89ER6ni8-8|e+z`tj!{*$Lg#b*xp|Is~kaB8S} zZrP>lCJ*FgQTLsn3!Ovv(92x3bQM4J_p*tj=&gf1kQeLuZ&BLOIdo6;bJ=XpL1uOT zulMg2=iF}|?*FT9dp7mH>OXo5dVWg#iKB?$R7C&DOYi;Y9gjMfb8kymm!+LNkeA;3 z(K{aRxw)5*UV1vI{me^6=9ePz@Z;Hxvk?6!FTMApcRX|s-5aa-)YoI@Ph}o?;JK*x ze)Nuq&Y^qgCwl4Gs{7c?^Ds4z=STbTn~UUuy!76W-to{m>fY4)ndbo=PkSrWc(rZv zu>&g&DWyYUq77xKOyt| ze{J2Jia+wO?E0^I+)8(e1KuW|Kcu}i^7Enpod5HlWNKKePN2@E?$tV6byoXZ*&27O zox48rKwfI)In__DzLfr6a?cIjvvibpMb&+59hYkB^MN%F-LHkjq5dZ?rTLxud~}Yw zH&=&i)%m$`bI0pCt@-4EbCJ})+j$+wIXAkeI*TsSUfaemsQTa9CN9W(5b`pw`i`uSwnro{+&(8I)<30cMlLzuL)qTrdm-QS;5120aa%$ zz14BGZ1qEY50_Q{x%Z~JxokFhSTy~&o)@Tdse4m(GFPwh3o^eps(-b;c<4WQvGTSk z?bNxPd#ipfn@t|bOYeO~m&ao6dR6~bH$9uWAKk}ho`>i$<4SGygTGWk4~zNTDI!GrT@8pz45HaL;R%Tvg$u|@6>d?_j<^~vep0S z9{QQ9$IMGb{O2P6nU`xb&qDIBEcHL<+|<3P^}eOAsq=Hk5ifUM?s)P*UP|>fcYg2Z zP42m&d#O5!?yCM{(~rNgHuDrq{p$S9;d_F}19{Q?$lcGWk4NXwJx_1FI@+6`+uu7L z^*?!;`u>%!51m8zEM25MS8o|#dXJCz9+t)-59FmZzf+&jIXCy-a`kf2#-sn_rS<;l zIsXTT@BizCZyLV;@2$h<|85<0@P`udiNp8)Dsqn>f4MgEO3{m}{>j5zhP>mQv9{ong{57Gap4WIv4eQs@Qz1iBP&ZX|vy4bU+ z_c`~+W}b)C^^EIf6GxFekeAl^pS6G9b5r-Ci&Mkga|3I<_j8d4@{+qhx%@lp@#q}7 zhaRdfdvXJ1cokRCLeWc&gSF4?QW6||N8w?%2U^q=Q|+DM&^ z?&H6j6@buM+U>glrC*+Om z>y4|`-#Z?8ATPc5qjx;&TyzgT_4Jr=V-f!z;-7J)HhwH559Foye)Nty11Y5SRL&ytK~$to?J&P2EeqJvFR- zo?!ia*7<)bpU&GjbPnA^2Xhgf&PC!dueI&PArH$^|D*rZy{UEbqK@MnguL|rd+x0V z=p4G|>9qFqc|Xqou^Fd`&eGn?CXR>XfxPtIkKXane{|2%#aXwH)&JaelLzwBdp~N& z^Z3ise@`d1-~0R@<9nk%Pk8Y||M%+PMb&rj=bE+tdw;LB`+3i;_y5sn57B!?Z(gf@ z^j-b){eHAtHu_IqYUg`u{&>$#{XbS`7iAuKATLwfuiERO&gK5Ur;oY)s=wIY^FnTa z>bg9?%c}qAo~MtC-cKH8RsYxi`}X{ybLbxWs5+`P{3dEZ}e{hV{7d+1{-diqPh zhv;+axZLsh^?ok$KwiB3&3ivOm#hD(yV#)Wy49}ZEZdrIwOf9?_GQ<9t&h>kRJ3$9 z*RPIOzt}p?T4(C`(Eqgaf9~abdOC0YJO|B6|8u`*ymb6KhVQw*ZTS9w>R|50f!dCZ z4$lj+Zbg1h@<3j!{MNdEJ!?N-KYY(U@4eAe3%Mt6_0N0z6NdVqywrXlKQ({){$KSI z-Bh&3(e90B9B7TFUB_X2sPh@G&kc-2|H;d|Ki_-nN9U+}=S{C`uUGZI_4oa&|K9vS zolD(IeT+VOHgz@nuQu~jF*Pp!#*Sy6B6%P$z4xPcJnp$s|4&uFy}CWO-}*Uu4&q$I zdEZ}e{pf$(|9|Y~|HzA%Kl)Y2$J+Sog}moR z_t3*qef8$|ilgfwu7%`*y!6_KS;ax;&^=2RwLMpF8Lxh7+4wI-o`ZNU8oMvOpX0{H zqs~S5(EC~ksyw1x!zW)dP=UhJ*y*fPC->k-y2fi1v_dbs0 zo3ZoIIdl)btcB<-{?w+uHfo$yn>WzxUsN?|A4O zx`)nEhmW12=p7H8L-)q&uIm3-KeL`k9>`1Y{TO?0Fm~R59^U`! z=l`tF$Dz|+KjS>~{CNGI|J;7`pXZ`^(ebhCME}vfRGpm4IP|{OdY1kEA9_0$(O>50 z+RXDX*Kg{0^nY2-|Ed3Z@2xsMReS7ng7@=~2l6tOSI5p9tK(zmp>yb7u3o0r)%ex% zwCAGwsb!M~@-lWG=Vji!=s!AdVd(#9XWp~+*`3_qJ9G}+OVvrnwML!;TF1dZd7%E! z&F8$0N9WKzPaiGa_2!lKTXA#Sy>*ZW^3uEh@BRD3IXAi&`0D#Ex^~~{vg$wQ0E*lX zuu#XB+EyIeOa1G7{DM3e_5OS89Z&Ti{abb`?)1Cw-}xs8b)4GPd>yB@HBZ}X%}e#exZHfs z+xY80@x^1uzT?52z?0`;-u5$hy|4Y~2Tpm;haTJsJb4~+^Eq$hPrd1*?|ado-nSEY z@;uDje&((ho%`~W|8ec{AKSMYxR(d=lAF(Y8;|a<{n?xE`Te(j@qmR_4*eEd^TxJY zacCdw*PDlb@-lDxnY&(e|06&5hV%dKxBvM-ZTwgxzt%cy#Un4d`JA`$rwsak*^8cd z@T*_>_|;kAr9*tM^*V?HYCQ7Ndp|#X$KE+sFZ`RK?q7Mzp6-3=mWN;PE#G~{>X#d% z#<6VLYvC7$pUd(`9zHYV#oLFwhW(mXylSZb*9U$7y0d=Zy;ofK#8t(oOS@<5{8ZcX ztNp2db)5RaCJ*Gr+Sj{>eV=!H%~1cNn};twa{C94oVr>I4~aj|9vj#5tNm6So-5FQ z-QV7N&^jJEhYtSv@Bie!3;+3RRzEeMWz(+c&9nOdOyXI#)y_If`*{xFJcRszWcYh; zjl}DPoU>E+qmR819j+A*e;)FDqvOa!ulis4rMG?Up#Rjps*|4m_3~Wc*`B}DelL#p zr}{)J#`{aqyV2!KYu5s1AcQ~&gFSYYs%b!=5r1pQ|#=Udz zR}J;|r$ztKYegN04gSU8XROUU#ZtfCeDd&5LtawzSZiGC=L_|Z{(osW|3`=ad8m_X z+4%9WG%o&C*FBqg=s$Vs^&B>@p^%T~RfHyi!uxu{j% z)o-omhg?6Lb93&EPHKHT)@Gif7Y99NoM$u6L(dO+ATPR4i)M4qP2H=yh@NVDZM#<- z)(vW0)&EldpZa{xxzT_0Q1vv|uI0yzTRRSUATO!UfAczybM9K_|2!|`V(Pg)<4TeB z;Rl;MoHpEx(0aMG-TU{5b8hNh^ik2P%XNG$yLVjjKwf(9NAGxA|D%ho@XX91|$SFv^+&I#}fk_YnAd;fdKbLnqPIRP^+^cE7hSeZQ}^&gcAp>hJ$+ zokMcY&AB)F2%^uNeK7H!E(s8Zg zzGnCw#KXhA$UBG6{q@4ufA8v_Iv3reE>1=Cl<}U;xLox7di|;K$OCy9`+Pkw^UyhT z4;@5L)wbGoTrInnA6++o70Cm6nU{S`U8mMDyyxb!@9uSIMn~- zMf0_2Ht)Hqd#RK4UL5VUQRApxE1vpQn>gfwyrk}TtvWsR@3_`DbPnB1)yZ0Vn;MUC zt>cmh@=`0$Q}u(+p?j*6*tHP7#h===*G7$_Hu1;E@ z#=#H%73sIoi>vxyE6=%p&>iMs*GA5{(LHpKbM#i&D~_(;i%TBJOYMA5%^&yN(LGOJ z>9;UdrKN{YNKL zQFR!b`RFnBSi}$h73s&n+O%6p9>~ko_N(@KsB_Uh)yGzLE5GQy`nTG3oZkPhRoAEb z;d!qYqI2jT`ns%09>`1WzqhIRL+8*vbaGjdJdl^E?N{yfpmXS+rHizyo?@#GW7BWV zr`<#KgMUT*=y=*aBoE}JcD|?P&(eQ%u@q5Xbxef9huL z$B#wz&M)9s{iPGE~4}Ej;jB;`PA{Xo(o#};d^dceg2Pno6q54gW6u(CXS*NXHn1pEuP|@ z8~5JOL#sZfKNUScx&5u=nTKYY$j>wk1I6)ip1`Fsvf+p)dR_j&#J zPetM~{`chH|0#X`pZfgL&;ONvj&Ex7m-X|1)VsUl*nV)Mj&%<25-gxSN@=}`L zsn6%08~5JO-Bd(J8J}x2uM}&=B@fG@|D1E9d+0rws++lbJ$5|nYK46N4|%bkvr5lh zrZQju{Xgn)&i`B4_*I-$T-L$&Ao2abQ`x6h>)@UnbuW7B=_LIY=KAr*_liRvsQ+8# zeJXx=&rRKn4(IA=?)a(3ArH&0|DNunzhia0bRO|j(ffJm=Q)V_-_n1ped^Eqx;}mX zPaTX7*RpH*@zzxuhjS3}qWNlV=hpwF+)=C9Q8lKA_& zvVQup$pd+5mG`Omt^NK#wLYg#AB(Nx===ZD`Z{-h>c2Bk>$zmnKL4kBj_zx_W%C@w zdy!gsF7;zQzm>Lg{(tqKz4@Nsf7=%ipr@(zvyLmZiLY4dSLdsLZ0djVQkvh=`K_M^ zTi5^Ss_HE59_Bs=q{h*4*yulbX`O$|f3N2Q&bc}FMpr$gF4u8m_1v0=f2+TCJ9*%_ zsMkIaC--@wbUf$Wtv~_m|r0-?FVZ)c^DH{eKpJq_$K4Q}?2S)Wg_gQT^oF zz2XuF{U#ul(o-PI=CU9^5H?ZlC&m@AH3p zK926U#$ItX&a6NG*ZcQ{I+waPw_euq)YXbQuC}f5u+e|wc{X_F7xt^9iHw&L;}l&k-#&k0i-$2m83FM94F^)llu{ih$)dAYU~hjuRx@hl_{u$^Db9G~pc+AhWnU{-J9M1nwyXm9vd(ofXw^LeQ=gx1%57zH5buM); z`skt7&))cX=_e25#md{FwCm^p(8Xm%?m?25McHTT=RoJsJxdp@cJvngRivN!YSZo^ z^VGlE_+dPDDv}5CV&!d7+AaM@ALkwQxyAc=(0}sMYM-s=f?jnWaj0{td#RHxq^`F5 z=dGRR64p%~xc^6<78R-gse7rLJ>>a-ah}b%T=e{Ef2x0-hs`<2vef^)=WccWkM61- zr`oFTmR%Z$^^gbh(yAVtm0#5}egBU>=ISQ%6xEN~I)1FJe$`I(%Q*;n(R{VGd;eZg z=Ti5o9;VuiD@Ak|Kc3CFQX~#}ATPc5qjx-Xj=C2e9E<2L^F5n!rC2Kt&q3s+_22uf z{Zr>s_oAb@Xz4QTo#*f;xm!kUhY%d=APhNWKaqoEO9J=S}sMSw@<3jC?}ze1Ya5+I_f#J(8{GuQ>UOPptdsR%gW7J{sQBn{^{3wxwfAFTj%tx+I#DN?)kr`pYz_&a}fGpx*xsuBR3v8$GJB; zh`y>#yP}S(Z4bz3hid*Ve=M!hy^dFt0?&bZyqNnRxcYEW<_G=uq$NFWSr~jpVJGJ@f zKe~s`_Cj>Gb-dI#7l3Fx&68h&$ia*&C`C~|1XRF^PZdf zpE|lVqPMe(LwrT@z;jXS=P&i&>RiF{%jf^mfA00CBKphtQX4-WT5)Jc&r@y2kq7dk z`?F{^_uP5^j}EI|*0O8)$z5k^+-22&bkEY!R_&_4o?W^Q{k{S9x9s2lt9pz+((c)e zBrrkrGS899l=%4lX|5`fTsvVs}_s~J~*Rp9xXD!rmxwhrkYtN0tc=AAA7ESN< z=RyC`J#=*}dOADSZ?AdeVcF_`bWing*=(MJ_+F&a_kUBL&-dJ;d+6X;y=9(asb6nC z@pPPizmI2=2fi1n_xJJij;G)Mi>~VVe{Eais7*ZDYau#s`Q!Ybdl9-HQ~!I;T_2zS zaoU;p ztbKMT@(_85--Gx&2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L? z2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L? z2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L? z2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L?2p$L? z2p$L?2p$L?2p$L?2p(7r9ysT`a}KXymw_~ zW$O`hYvuBNyT%QhN3Xo#g0;xU2SwH|os z+L5jGZ#(P8t@X`4Z)czVoQuvo_q^xtKll9o&-?bX&OP^n=e^*9Z-3Eq&p-eC=e^*C zuU%o$+Z)?Q)>pP(v3}L|!B-u<;oxN(S6{Pz@Wn^Bw~tu)?~bN$HL_Qp-? z`?of3U0*qT+T{m-_lGur??N0sGG^<_wOwzAcYn~`{~mf&-`2*V-Ht>1c1LfmA6dV0 z``RX~t~_aX@^x!BuWTI8*g3mD;rjJ!*X=IyyDqrm)w@67 l))hw&?Pl$kwJR=O-}=Grqc>c!^@bbQHrJnf^&4+l`Twbx2bKT; literal 0 HcmV?d00001