From 3b94e2f5dca23d98d07ec46614c1d3f7605f143c Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 7 Aug 2024 10:49:46 -0700 Subject: [PATCH 1/6] Add function to create mask from bounding box --- src/dolphin/masking.py | 76 +++++++++++++++++++++++++++++++++++++++++- tests/test_masking.py | 36 +++++++++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/dolphin/masking.py b/src/dolphin/masking.py index d81c77c7..69f0253c 100644 --- a/src/dolphin/masking.py +++ b/src/dolphin/masking.py @@ -1,15 +1,20 @@ from __future__ import annotations import logging +import tempfile from enum import IntEnum +from os import fspath from pathlib import Path from typing import Optional, Sequence import numpy as np from osgeo import gdal +from pyproj import CRS, Transformer +from shapely import to_geojson +from shapely.geometry import box from dolphin import io -from dolphin._types import PathOrStr +from dolphin._types import Bbox, PathOrStr gdal.UseExceptions() @@ -158,3 +163,72 @@ def load_mask_as_numpy(mask_file: PathOrStr) -> np.ndarray: # invert the mask so Trues are the missing data pixels nodata_mask = ~nodata_mask return nodata_mask + + +def create_bounds_mask( + bounds: Bbox | tuple[float, float, float, float], + output_filename: PathOrStr, + like_filename: PathOrStr, + bounds_epsg: int = 4326, + overwrite: bool = False, +) -> None: + """Create a boolean raster mask where 1 is inside the given bounds and 0 is outside. + + Parameters + ---------- + bounds : tuple + (min x, min y, max x, max y) of the area of interest + like_filename : Filename + Reference file to copy the shape, extent, and projection. + output_filename : Filename + Output filename for the mask + bounds_epsg : int, optional + EPSG code of the coordinate system of the bounds. + Default is 4326 (lat/lon coordinates for the bounds). + overwrite : bool, optional + Overwrite the output file if it already exists, by default False + + """ + if Path(output_filename).exists(): + if not overwrite: + logger.info(f"Skipping {output_filename} since it already exists.") + return + else: + logger.info(f"Overwriting {output_filename} since overwrite=True.") + Path(output_filename).unlink() + + # Transform bounds if necessary + # Geojson default is 4326, and GDAL handles the conversion to, e.g., UTM + if bounds_epsg != 4326: + transformer = Transformer.from_crs( + CRS.from_epsg(bounds_epsg), 4326, always_xy=True + ) + bounds = transformer.transform_bounds(*bounds) + + logger.info(f"Creating mask for bounds {bounds}") + + # Create a polygon from the bounds + bounds_poly = box(*bounds) + + # Create the output raster + io.write_arr( + arr=None, + output_name=output_filename, + dtype=bool, + nbands=1, + like_filename=like_filename, + ) + + with tempfile.TemporaryDirectory() as tmpdir: + temp_vector_file = Path(tmpdir) / "temp.geojson" + with open(temp_vector_file, "w") as f: + f.write(to_geojson(bounds_poly)) + + # Open the input vector file + src_ds = gdal.OpenEx(fspath(temp_vector_file), gdal.OF_VECTOR) + dst_ds = gdal.Open(fspath(output_filename), gdal.GA_Update) + + # Now burn in the union of all polygons + gdal.Rasterize(dst_ds, src_ds, burnValues=[1]) + + logger.info(f"Created {output_filename}") diff --git a/tests/test_masking.py b/tests/test_masking.py index 6e53727d..6d5aacfd 100644 --- a/tests/test_masking.py +++ b/tests/test_masking.py @@ -1,9 +1,10 @@ +import zipfile from pathlib import Path import numpy as np import pytest -from dolphin import io, masking +from dolphin import Bbox, io, masking @pytest.fixture() @@ -42,3 +43,36 @@ def test_load_mask_as_numpy(mask_files): expected = np.ones((9, 9), dtype=bool) expected[:3] = False np.testing.assert_array_equal(arr, expected) + + +@pytest.fixture +def like_filename_zipped(): + return Path(__file__).parent / "data/dummy_like.tif.zip" + + +def test_bounds(tmp_path, like_filename_zipped): + # Unzip to tmp_path + with zipfile.ZipFile(like_filename_zipped, "r") as zip_ref: + zip_ref.extractall(tmp_path) + + # Get the path of the extracted TIF file + extracted_tif = tmp_path / "dummy_like.tif" + + output_filename = tmp_path / "mask_bounds.tif" + bounds = Bbox( + left=-122.90334860812246, + bottom=51.7323987260125, + right=-122.68416491724179, + top=51.95333755674119, + ) + masking.create_bounds_mask( + bounds, like_filename=extracted_tif, output_filename=output_filename + ) + # Check result + mask = io.load_gdal(output_filename) + assert (mask[1405:3856, 9681:12685] == 1).all() + # WGS84 box is not a box in UTM + assert (mask[:1400, :] == 0).all() + assert (mask[4000:, :] == 0).all() + assert (mask[:, :9500] == 0).all() + assert (mask[:, 13000:] == 0).all() From 271285c458e8e2ff20ebdaf97591ddeb31eb9e1f Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 7 Aug 2024 10:50:07 -0700 Subject: [PATCH 2/6] skip nodata in PS iteration --- src/dolphin/ps.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/dolphin/ps.py b/src/dolphin/ps.py index e6defa04..aa3cc873 100644 --- a/src/dolphin/ps.py +++ b/src/dolphin/ps.py @@ -115,16 +115,9 @@ def create_ps( # Initialize the intermediate arrays for the calculation magnitude = np.zeros((reader.shape[0], *block_shape), dtype=np.float32) - skip_empty = nodata_mask is None - writer = io.BackgroundBlockWriter() # Make the generator for the blocks - block_gen = EagerLoader( - reader, - block_shape=block_shape, - nodata_mask=nodata_mask, - skip_empty=skip_empty, - ) + block_gen = EagerLoader(reader, block_shape=block_shape, nodata_mask=nodata_mask) for cur_data, (rows, cols) in block_gen.iter_blocks(**tqdm_kwargs): cur_rows, cur_cols = cur_data.shape[-2:] From 5dda9753b43a7648deb45c782a2810e68c67e2ea Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 7 Aug 2024 10:50:22 -0700 Subject: [PATCH 3/6] add bounds to cli --- src/dolphin/workflows/_cli_config.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/dolphin/workflows/_cli_config.py b/src/dolphin/workflows/_cli_config.py index fdfa5b6e..02984ee9 100644 --- a/src/dolphin/workflows/_cli_config.py +++ b/src/dolphin/workflows/_cli_config.py @@ -31,6 +31,7 @@ def create_config( baseline_lag: Optional[int] = None, amp_dispersion_threshold: float = 0.25, strides: tuple[int, int], + output_bounds: tuple[int, int, int, int], block_shape: tuple[int, int] = (512, 512), threads_per_worker: int = 4, n_parallel_bursts: int = 1, @@ -91,6 +92,7 @@ def create_config( interferogram_network=interferogram_network, output_options={ "strides": {"x": strides[0], "y": strides[1]}, + "bounds": output_bounds, }, phase_linking={ "ministack_size": ministack_size, @@ -367,6 +369,13 @@ def get_parser(subparser=None, subcommand_name="run"): " output shape." ), ) + out_group.add_argument( + "--output-bounds", + nargs=4, + type=float, + metavar=("left", "bottom", "right", "top"), + help="Requested bounding box (in lat/lon) for final output.", + ) worker_group = parser.add_argument_group("Worker options") worker_group.add_argument( From 8cbd34eff49ac41d8c06b802e65ebb8a8074063a Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 7 Aug 2024 10:53:09 -0700 Subject: [PATCH 4/6] add more timeseries logging --- src/dolphin/timeseries.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dolphin/timeseries.py b/src/dolphin/timeseries.py index 0e8c6335..b5c9139c 100644 --- a/src/dolphin/timeseries.py +++ b/src/dolphin/timeseries.py @@ -511,12 +511,14 @@ def create_velocity( msg += f"{len(cor_file_list) = }, but {len(unw_file_list) = }" raise ValueError(msg) + logger.info("Using correlation to weight velocity fit") cor_reader = io.VRTStack( file_list=cor_file_list, outfile=out_dir / "cor_inputs.vrt", skip_size_check=True, ) else: + logger.info("Using unweighted fit for velocity.") cor_reader = None # Read in the reference point @@ -568,6 +570,7 @@ def read_and_fit( if add_overviews: logger.info("Creating overviews for velocity image") create_overviews([output_file]) + logger.info("Completed create_velocity") class AverageFunc(Protocol): @@ -763,6 +766,8 @@ def read_and_solve( if add_overviews: logger.info("Creating overviews for unwrapped images") create_overviews(out_paths, image_type=ImageType.UNWRAPPED) + + logger.info("Completed invert_unw_network") return out_paths From 16584bd7a21150f098c12c8f6c11944904313f01 Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 7 Aug 2024 11:02:06 -0700 Subject: [PATCH 5/6] refactor `wrapped_phase` to make combined mask --- src/dolphin/workflows/wrapped_phase.py | 96 +++++++++++++++++++------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/src/dolphin/workflows/wrapped_phase.py b/src/dolphin/workflows/wrapped_phase.py index 477fca41..a1388dde 100644 --- a/src/dolphin/workflows/wrapped_phase.py +++ b/src/dolphin/workflows/wrapped_phase.py @@ -9,7 +9,7 @@ import numpy as np from opera_utils import get_dates, make_nodata_mask -from dolphin import interferogram, ps, stack +from dolphin import Bbox, Filename, interferogram, masking, ps, stack from dolphin._log import log_runtime, setup_logging from dolphin.io import VRTStack @@ -73,6 +73,28 @@ def run( outfile=cfg.work_directory / "slc_stack.vrt", ) + # Mark any files beginning with "compressed" as compressed + is_compressed = ["compressed" in str(f).lower() for f in input_file_list] + input_dates = _get_input_dates( + input_file_list, is_compressed, cfg.input_options.cslc_date_fmt + ) + reference_date, reference_idx = _get_reference_date_idx( + input_file_list, is_compressed, input_dates + ) + + non_compressed_slcs = [ + f for f, is_comp in zip(input_file_list, is_compressed) if not is_comp + ] + + # Create a mask file from input bounding polygons and/or specified output bounds + mask_filename = _get_mask( + output_dir=cfg.work_directory, + output_bounds=cfg.output_options.bounds, + like_filename=vrt_stack.outfile, + cslc_file_list=non_compressed_slcs, + ) + + nodata_mask = masking.load_mask_as_numpy(mask_filename) if mask_filename else None # ############### # PS selection # ############### @@ -97,6 +119,7 @@ def run( output_amp_dispersion_file=cfg.ps_options._amp_dispersion_file, amp_dispersion_threshold=cfg.ps_options.amp_dispersion_threshold, existing_amp_dispersion_file=existing_disp, + nodata_mask=nodata_mask, existing_amp_mean_file=existing_amp, block_shape=cfg.worker_settings.block_shape, **kwargs, @@ -116,15 +139,6 @@ def run( pl_path = cfg.phase_linking._directory pl_path.mkdir(parents=True, exist_ok=True) - # Mark any files beginning with "compressed" as compressed - is_compressed = ["compressed" in str(f).lower() for f in input_file_list] - input_dates = _get_input_dates( - input_file_list, is_compressed, cfg.input_options.cslc_date_fmt - ) - reference_date, reference_idx = _get_reference_date_idx( - input_file_list, is_compressed, input_dates - ) - ministack_planner = stack.MiniStackPlanner( file_list=input_file_list, dates=input_dates, @@ -135,19 +149,6 @@ def run( reference_idx=reference_idx, ) - # Make the nodata mask from the polygons, if we're using OPERA CSLCs - non_compressed_slcs = [ - f for f, is_comp in zip(input_file_list, is_compressed) if not is_comp - ] - try: - nodata_mask_file = cfg.work_directory / "nodata_mask.tif" - make_nodata_mask( - non_compressed_slcs, out_file=nodata_mask_file, buffer_pixels=200 - ) - except Exception as e: - logger.warning(f"Could not make nodata mask: {e}") - nodata_mask_file = None - phase_linked_slcs = sorted(pl_path.glob("2*.tif")) if len(phase_linked_slcs) > 0: logger.info(f"Skipping EVD step, {len(phase_linked_slcs)} files already exist") @@ -171,7 +172,7 @@ def run( strides=strides, use_evd=cfg.phase_linking.use_evd, beta=cfg.phase_linking.beta, - mask_file=nodata_mask_file, + mask_file=mask_filename, ps_mask_file=ps_output, amp_mean_file=cfg.ps_options._amp_mean_file, amp_dispersion_file=cfg.ps_options._amp_dispersion_file, @@ -394,3 +395,50 @@ def _get_input_dates( dates[:1] if not is_comp else dates for dates, is_comp in zip(input_dates, is_compressed) ] + + +def _get_mask( + output_dir: Path, + output_bounds: Bbox | tuple[float, float, float, float] | None, + like_filename: Filename, + cslc_file_list: Sequence[Filename], +) -> Path | None: + # Make the nodata mask from the polygons, if we're using OPERA CSLCs + + try: + nodata_mask_file = output_dir / "nodata_mask.tif" + make_nodata_mask( + opera_file_list=cslc_file_list, + out_file=nodata_mask_file, + buffer_pixels=200, + ) + except Exception as e: + logger.warning(f"Could not make nodata mask: {e}") + nodata_mask_file = None + + mask_filename: Path | None = None + # Also mask outside the area of interest if we've specified a small bounds + if output_bounds is not None: + # Make a mask just from the bounds + bounds_mask_filename = output_dir / "bounds_mask.tif" + masking.create_bounds_mask( + bounds=output_bounds, + output_filename=bounds_mask_filename, + like_filename=like_filename, + ) + + # Then combine with the nodata mask + if nodata_mask_file is not None: + combined_mask_filename = output_dir / "combined_mask.tif" + masking.combine_mask_files( + mask_files=[bounds_mask_filename, nodata_mask_file], + output_file=combined_mask_filename, + output_convention=masking.MaskConvention.ZERO_IS_NODATA, + ) + mask_filename = combined_mask_filename + else: + mask_filename = bounds_mask_filename + else: + mask_filename = nodata_mask_file + + return mask_filename From 2787be513a8a871ac734cc429e7a8499e42514ad Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Wed, 7 Aug 2024 11:21:53 -0700 Subject: [PATCH 6/6] add new test data --- tests/data/dummy_like.tif.zip | Bin 0 -> 57683 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/dummy_like.tif.zip diff --git a/tests/data/dummy_like.tif.zip b/tests/data/dummy_like.tif.zip new file mode 100644 index 0000000000000000000000000000000000000000..77e5ed8f146a39bc38a8037ac6b1fd1a3476ad16 GIT binary patch literal 57683 zcmeEvdpuNY`+mEUU6L&!(`I(5R6@~EPP0oVDd$6osU$T*B?)Ei5_0aKR6^xAGf4>L zG|oDqgCWEWQwqsU!iv?b-?P@xcOTn(zu))$zWr+VN1r}@+JEfFy{_lFulu^M$JA)T z#2Mqpjr(>SFcNzG&9mH1 z`vv`e=n=Ps=UZJ~e2~=FIv7z-bm61#5nn%zf9J1kG~;&h6ODnZZ47^+RgViv-ErUp zJ^!$cqJLU0MOTWKN2q=B;qdpCsjI1*x3hFYE}pa$-*nw~1%tnkb<&dVLLjN86D~Op z>@{=Jps&1OHFS--QIqtLp8vu!(UT_dv1~j{jb?P){FozSt|-PRUSk!row|A-%i26A z+PeHcM=gx0E0_W+w`YVirwFJpgPRe;mV(?6FMeY+dmK|%PzA?wJc5`a{vJp=FRzp> z1NkBHJnss&9AtyE^1Q3TKUYJJ@J2fNf}wGOqp&D2$yeC zK|BGe3utg^d%{s@zQ7iK$w>%?^aUcYY&#`9LDbo96;G|2IUS`UuDsw5N-#7*unE@U zN|ZCj(SwK%zpsiI38@J_z!Dttade455sqpnA4TU1EZ{m0ISkbmEI?%WlqyIXB_WSs zj}B%aI!#ajJMqXR=y#|SV#2TPfFjX(0vnjkfx=LEfdou$N5fGufd|awq9NpOP!D9# zmp}_kB%E5Gvx0UmiF!z#r4-m5Z7!aj)<$C;Uq;=f%~B2^^j!_wVXl{*w$GNuc}-t8 zjii}Mh_?5tV^lr2n9$E~7h%0xMTaC=tW6P%Qiji(rYG0mc}&mH5$uQWb2Cmb)%_(9 zE51!NWdfvv#KS$Dj9{j|APAn-?h(#hDCmUkIUXTQl0Xw~X&)+Oe*-BY4m|Hlk8<_| z$OI|k4^{SsG0E6zw-b*+3k2KXbsS;{v`nxRp1_ML2mdFlkuyA#QnEC909gsUbR~B5Lqya7iQNqL!Yy2I+LDk9cG~4^c}M~&;(@=Bg~-SRo!tcv zbLo&eixfzBac$Vv(v?b5kLZpy_PTp{?X9%jjr4h8gm|lgyVtc}n3nISx7}tP*QM^# zXDKrYlpn#E42s4h~9o%V5PGB|Am zUPg%!NcaFqq98>8BzYWmC^&6-*t?w_0Z9msz(ZW}3G#S!8?ug1EQiKJqd*dj14#@I z2NEwKkVF6=@q_!g;It(LO5n7iAXG#U15>zB<){qmhYPuP_&>drzv(s8JJFU3MQkFqDWsvj2 zT*arJS4fXybX)!?uAJs8Yb_O;cEz1;vyW7KoM66TU~vE_%oT_<&!&PR2T2QYL<-;b zj^RikIRBZ56F;h&{0;Pj;4YlXNeF@_34TDbd3B{^S@bY=+8vZ|aN5pr3>U`{k`PBG z;5Y(6TUrR(WjJUb!9g3Gwj@%`BUVGx(S=AVKcNzuhKgXIjo}DMh$CV+j#P7)QNqgR z;FT@*>B@eeciB{wrF@Ezuvy&js_%RY?rBD8Ej@Tm#HQp)$!4ujXiK|TKgzOH&Jf~k z2kPkgJ8crL(J98Pi}n!@5{El29Nsb{MJ(-Sv&>hEtI>yNSj25e+cwXt?=f|i9OBH6 zsiu4b%|jGm1}7tkIZ1F4*5fypQe>ech&bO*h}k=Zm@SQCwnr3mg1{Ko;r4~HgP1yk zc-XJKFJKHu%5WUnCd83tLL6xaI5J0I2rF?DLLqH|oDj382{F3_$7~}$z-$ah0A^!2 zq9DYPQXEG>Wdj@mm0g5aHu{~Q9FFQB2cZ)M>ycDmLMb?FAH)n+YX>;8NQfg9IF1bQ za2%1uaRgNM3cRwR08~XV1d}=Fak2s`i?DfU2`UAR_b-_HwuPY|Nw?TedSEzUOrJm3 z!oVWUaUtta7~!?jFaxnlcR%L8SRMRiHu_3A$ldPDUV86UPycpKu%j zd<5VK;3F#hyehy)bP#=5r=#yUQ%v9vyTN{(zCfmypco!$?~4NEyAlrO5JQDH(vIUu zCS2W~@DuqvA&!)j<)QBYA93L(gaJOX6%pgBRWT=`>d1XQz>z5^0deJLl!Mb=i^TFh zN`;lp>%%G=!x2D7FdUJ@a0FB~z>z0#T?aWB9WMyOPWu>25cI$v96(1j0UhaU$18gs zoPbsKR1~l5Djc)HX#>m#r)`6<`QWt003AW!%RYcc;{_XFJ)TV|MH~`GNPNGJj0mQxU>Kgo87gDX zgrtxfUS3IG1QW0A4!pJ_@!Dq261;)^I>3QT_*0M%Jc$zWx9C>H9}aE@r!!Yz27lm- za)@D&t{?=Sz&EJ^d}JA-!K+}zFyM^# zs4BomR1j|-yPOz+ej`{16S+_*SqAk))cDLYbP{Th^zon)bTaCKPtUEjC9#dHJk_p?)+-O!tb|l@n?lUXB?xh7en-^uLO}1q{d`-8R zM$%6uoVFi$z^Hm*5zx<25Me!CMKzUVwQh=7m@?^{Y3l~sqhu?{8}Jc;B$9xS03_+i zhy+Mthw+gBrUKw2vpPJEGnWcd;8?E5QRX~B1Khxf~yqDUOiyOb@>6bF1nk3Uq! zmSw651h8la@i_D?#z)$TN1+*lM{qTE*53;z!%7_q$Dt`0-R4S^k)_Z*h$p|UoEQ$x z7Oa6|IFumrM05(m;AK=XWl(LThwo9rltX0@d%j0Ca{{W0w8KLjb}&R11i*?sVihz4 z-G?ag)^(60(5V7XSd9}^22Dn#5gR_G8k&G^Lh|_RN=O1df=KYmW#~j`vA_kUaG*ex zBv_7k@R`-9B&vk)VP*#!go+9%uoSOlUJQF>#6HUjQVe&)`d3MGXg`Z(8?k{loNS)H zyZ+8ix`!{Ta!tgYv}Se2xogxznyhNN_6y7M{j@fp`s0PvG)A}8j}dBVK{KrN!sz?l zseAXa5|aq_8^jj{06wxGR_0_JXQ~Sp!Hjlr+VlNQgm)xSct>*aJ5uI1cccb>M*=<~ zDsTjR1Tbx);2Z3;!`Tw3DN@dlsvu7Rx8^wE9VsciBRhV5M~d;(Dws1-bHKILu*PQ5 zuNoT*RAk&Z6a@`4ioR~O^0wF(5Y)cCGE7vhGE`PVq3{r&QyL&D;iwD#P#GYrhW$+k z_=AMw&SIZ8D`H8^pS+3B{EA!}eItjO1v=(1@^W>rrCWhjxg$%SW;ev}7iHZspuafL z?PnRWJmsQE8ehq(aX0CCc()!SD8VK`jlM6&@})EDfYrbji*on0s+pw0bKSQRC$(BQ zyzp1le$rxv@C#d-Y`NlZE!Ii(j+Y^px&nu_S4>sV_jS~ za6ffzr;Un;C3_Wh^)}WP%LuiU{7}a1c~%|^NrmBrJr(%9aF%CNw+CA z*-URY>&83!(nLb6q1O)UfGqmbruv89Q!g_KaYi{Wtv~eB&E8wuU#2cd%NL_H%%M}3 zlGNAdM4KKlqDdxOdDhYPsi;uaEloLYhp=T|MN>F8$zuHR{h1>dBcJON#dY#S2<0&zhAS}1q{+i)l$ zphYFYjqQNO3-thYD7b8edH_=tTrs11z_snLk>Zg2{f<^I0~I3FoM;%%usLv z&-x!dppP3>IjRSc%kipK#;Y1!!4m`~Fqw;nqO$~jFw}uYpb`QmER}SQe1&RwZFrb2 zX=vrSpQInyy&*XWrnP|%RhzUtfDmKmMX?x2d;>ORbW`6^%L*n@0LJ((4e{4{JNHPwYz?Y~ZPh`qt;tMaTj64Y_1N&el zP68&qEQC`#5+ZTw1*bX+KzfP8&iWW62hO^jpHapnpmyLcDBywHU^2K3T6pU??4yvZ zpcNkK7`+W7xa4EPs^;KTE%)iF&V-5W&@ptO;3ak&gn-)sC;`H%HW5~}@HT+BpsKk_ z(0JCl^`yCGt;KZGh3=SD5gTcC=8WRq^(Su9R}>KvR(d&`8>lnd7K6)xv^Qy+qY9k$ zETos0SM5>Co{WhBeD7+HO12c_0ferh@{jE);3-S6ldd2WJ~m9ks^;NUy-`@z7_AJ% z&YDXJ1!pY{+jIZ~5f^v?1Y!RKNeOD;ypE_6@N+Xp3V3~`#1J8Zw39IenJGk&2qA*- z0fJ0Moe>)zrCfOL^4O)2EVy^UNq0cu;G|t(J>ktO4$hj3u^dT^zQwx}BsQLN%ceE7>HPr{!AGOHI5|&C`7tu|*&)q?JEZ!WP5Mn%}}3 zss=oLD`LQF;Y(D5FMk2z1k^!dFwk5=K(FM@Nf_;x+7lumtXl#qzzIlFFb|IDphN;$ zWdp+CX8_HGDTeWP9^midKw%lx0n|oy0MIP~IZ@axS&S)M{Xla8L{e^4;UWeA{;mV~ zJMWLS;}Lq>qm?O4%`m%LHNatGQ?xd8OTZDOzTTRd5C&dSis) z^`I2X2^Ne;{&c$LuIEd7r zTt&~jL2$GbpJvVtWz?Rg({6SzObh}DB5X~qcmP2#tOf}3KdtF+s~STPP}R7^#rH19 zs~VGUhsr(5$>Y!;kO00!C3&h)zO5op5{kDa7{C%-Z!S3qT`o8So3uxj zK{DtHL>6d?#0Xs9%HT3XP#HlB+}FX30^B_qmF_J3zz%0qO9FWX|)~z_@#dM=;O;00C+5JH}MT`Xo+Y$gdmI zJT5T^l%pBEjth=`8hQwk0$oyH2^rIEL7!Aco`v^GY9-9?M*Ae@_kg)8^E0ZLlK^v9 z9qp6ASxaKNE!HO$!HQU)^d0Dv*71myc%K9mfGTJbIO&&sN`(+X_!xo!=3@`{ahSoV zzF;$^+g717K$oN-tm;XhtZLu)QuON;)Ds!qo9IDGHUSp&eKgC|1nLHc?_=6FIZLPc zq=w7dS!pYq>d(KX?lLAVi6O{eZthPLysu9~=;=RE7bNA2F&eJYDJKcGTXLc;j~Fo| zoh`E^g$Pnckpl=q5+cYfA%bvm1Q{Dj2+qOw?H&P46~Q2^f{6g(;H2H*^wFLahyZRd z=s!<@|Mm*pJF43Pjl5cjAh_Cc3?c}+fZ8Ha4$gWD($7~b>BD$Crjciq5F^Jl@+hE* zD`HU1VFy7vLIeo_0|}u5fGz<9BoU6{0s=AvOd?$P3FXi?=sX}GW^kB6sJ0*$Od{A- z0IKy62_Cru{T7u%>UiX8bUdnzWbm1lfPm-;qA&t-j2(vR2#7G$js}nwm?o3JL7xh- zG@8!Rq7hDQB&C{iE$G+lsp|=>SZi^Uj7ct*TyK;|rDSPpI z*g(Qy0;}X0^}uYFd)mCT(#Mm>=J<0GB{Q2P&@AmlP7RDDSEgr|o6LFQWoBe3c`D_3cnYvv9m5?i-NP(9GymhZuH=2 zKMDxQ6D&k#CvBmDi3)n0|IQfy~cy~}RecTx+wi4CMDcGQmpHbac#*{>NBKACw zazH?)VFaX#DU0eOBYa{7G;>VX8V?j(Gi=Z%)U^`%lu}3<^+L435P}(jsseqS$bn7> z_3?Hna#SCOurV91YaK_${MEo&1AROYob@3@|H5;V%R%$aC!9%pR7}^q(4DXfwEoTJ zjG$$7opkD^jA3U>MGe}y2Xqf>(wtao?UkyRmJYY;Pd=k-MG@jn#HU$ur=-=cqSH=v zFQf;F(5kRu1k21$d{G$tgb=ISajgEss-6nrRgH}`r-891Ue#4>X;9VRtTCNM6wG}- z@Fg%+>pR4N4yC>!Z_(MC1+w< zu}}SpLi!3OftZ#*g+4pb%0rS=c%HCjON8a~ zsUXV%0s`8_Y9MDVMzZ;JrDO?oJf_xSjX5}LK4{ECgpGMS-k3|_jd@fFpf0LN0l%-3 zDS`SUqI_Z%G!ZpJs`&{e&=hnx;=%)1t$?lpSdF)f+Tg5ltQN(wy1D~8j!qV&!US3%!t4%c6sy~Dte%BoHE0)sbW1@dtKUypA7OM&`-qi=I_u09aU&YRf|2P@S4<~F zJ4W2S9JJGXf(GM}t)-tPiyTZiYXN3H;|>)7w!R>CC}_-aMJpIw zVdH=@F3JV?JX0uwU=ubVf^ZXlf~Nb6V(p@EQojW81vu+(kYFGk_EiA_0>ndpLKQI(T`T~!n!`K}IwonP zkq5*>OsK`hLn4ZcAXVr@Ac6n_!ea`B+I`p%;smN8$bgH+hM5>t58a&uts?24>fUfQ zXs%V!-Fn^(>fS{p)tv5)i5DN!;@?p>uVw8rb<$u&UZa{GB+U;dbZ1POl=kShm7gPv z%p^#sHBX@jmC*}MP*qZfrEL_A($1OFJ>*DNv8uk0&*?Gka7?KU5Q-oZgd)f>;heq$ z7eW5Gs)2Smrqlwt37^vk0tLtv%rc{@$&%;}p$Jk<#zc@$R5d1ofU3sYMIbj}?cxNi zT?AE448dhuiFPv9nByWypil&9$3>7`NDQA+3CW}4$Wqv&of&{C2u5HhOs1WQS^&Ap z4CYWgZ!X#r z!JoR`jumSxUUzwti#c~HT_cQoFU2>Dv2BHwlPXE`N3Z8*0dHwb-?JW$r*551%D+Jc z(=}0=S*4Y|Aew-WUg1Iq} zn@%t_FuAEcuLIC(S%E&7_vEz`e*&|dQaHE+5NqK8;(sLZ_JnXCiSGuIctQvuAh;wB z#t)!9*^U_T)XJIT0q9tP(HbQT*dfGWtqyiLBqkg`U?PYfE`op-CP3K2g#Mit1`|Q# zgdzwgiDQj9IBUEy=i!YxW{7}z)!d=G^FdWFIz=_QN;pk_vx80w?2g$Gu`%85aawU# z{fT$<6^VodLoa7*gDiU69_pcKq^FdK`^nm#jFdvUQUXCHjUYzP476M)NlH6UkV**0b2$X|@#{3{))>lGUKT=5AG^)w<;Z(~+bDAcZb<)}^)goQalDUzZv@&Oh*^z9T zWRsPrF{zE>C6YGV$I`=*q{}31`7uH{OMK~+OC!p1xpZ_I^*9xznH8*^;X>P-JK1MCoMij-FUTXm>#)%~>CRGqbqOSZE3gtYg^ zf;EmH$At*O#SugcM-VVE#_q-7j}v3yJOKX2N2Qk|anFR;B|RQ3OY?N#Dz4=@lOU?3QR00z=Qi2w}53wFS;`Z$i&iaflk z!C8Z<1_<)Wss=JDKE4!IH70_5BNRa}^BNcx3-3jH;k_t{-;2MjYJLp)DAhQrO3B)x zn7Uq_6}v^;gf=PFnu}Pv*s-D=2X>g2tJA6;Q1{5NPHhsucctpNrGY2y=xx?3UAkUE zH!*Fkw5eO5rGX^N!7^tO&3|6~ozwK>o89&@Oi9QG^Z1leL~s#=#A~de%mQ)~VAj>_ z?*OyL8}q-^!r<*9Fr8q-OP)kEc{;ia$f!}3fUiyjHbEWCD2Q3tH2A?Et!o{iT?_|P z80Vi&VK6h5(BQ)kg+{Gwfx;FBx32xg76!LLU}mb>nB{~61+fD_yJ&;EGJysrirJ~E zP-)PGdSe8n3>5{mTL1QQ6U-;5cqZ)}<`X2%38We)SEX7z?5;m~fUZ?Uh&L10Oxt(C z%Eg)$?J)4%v>dT=`iT&0@Acd&;4Ne6QR=Rhq#HK~4y$uyEk1-X%;s6zFJzgn^@^#a zjCoEx$|%yc3A{1i#K#(QrkrpJQ!bpsU}Jf4p$JmO#zYWJtX%{) zLC`LO#+(;bLY@N78f(mnn7!vNEH9kGOcEMS0w8P(BP(polZA~r?tle`6TC5>k2U6% z#G_!g7zcZE*l(BY)Y0ZWDLjm3QfC<*3sENh9MBS@OQvK1Z(`?gw zOA~+6wV$Yqld5E_<=3z-|3FH)MX)m$U!1-+#;o^OWDl4GakDY6v66xf)1uiOuSf4=W{I^Cz5uY?vA?_WMUSOO569u>g7h(j1=FUjBTD)+{2`UN4xiq z8QE+&xGaL(eX5z?2<<+V%u)Ao8BmW#Et=oqbq?%4Hk*{J>K2j~Zy?26In~i3#i$SG zk>=Zp7hRp?Xpxw~7>uQ(Dx~gQY5{}rhVe#}K5(UbXmiBjt9J3UVg*u1Ak{y)ifQfO z!8*B|HR33arcHvZxy5P58PwtHzW!<3n3h~3Nz>5_ehx;Egfr{KH(i}H-zxD8ZEyx1 zT0~luLp4eyJf^*QM<*qAyO=6!0GkBe;~;5HFm=#|>q)!*jJn>D6>B0cVHqGs-xp}< zBFVDcI53g6ZJwpmLQ?L{?ge4Y?}f%q%m9uz@JEFWJP&W+4TKH6(70Ix+jD{1DleFZ zsjdH_u8sSD%7{VuwB{ujpVn03(;DIR3H(2hyuU0)z}wlFANgBEhu2rZoCS{m)R?zT z5pEUQzmqOY(K>SbhHx~9o#mkIN?s*xVq(6zs-htkhxIdD>UNn!_rLM_G zZ<@AQdtDTbR+BQ-z3d#e_8Y~KmVD9Z#ggiFd5K33s9Rg~KSI%K!|_GKQ2x9tw};WT zi2ljD%=Hsydp$NJX=^pHYtC!b)Y~~+JV$X+J?FD?Z)3_xLKEZpMT?!XrMNCV8T-RLy;to8vZzjm-V`4Sbi8c@K0_y6N-v>@6R7oVRju$aYkjc2Mj3uENYU zednYbWAk#Fsc)XV<#>*qiqx?iBH7&QE39`iY4PtgXjLS>W3$^D=#PibdzVkMAI{T$ z_iV)Fb*AwIJDX__F8SW0sZOvb=AOI1Vq{O=O1DJ?cGC6-9%8J%+FBEz zh2B>S4u8G1`60C>J#WxV{+U{kybrt$-etrZAEsIatM3ngu`B9`L>okuZ_exvvhOSY zFipON|0>Lbi7FVyE;2mkar6-PyV~3w# z%CmZWX+sSCV7`TCnC88`+79LCE*RJKw#@dl%)ii@y|$*cS#9LP0omYAU5%mM^IKe= zq+Yx<^1AS9_S<+P_TFs*&;H@-3-;=NcrXxHTi24Gekbsq?2t^slD^(k9kLop+753! zkgG8pDEA(gEOmdzj1%;}^GJnsKD2bPjnRX{c~5@zS3|}>H|IPuGMPSZTnXRK^SX%C z-g|aV4byGKI_>Uj<>xpmFHYcysc^SVqNZ$}-+iU=8(Guc!CT}HA1Gm6Im{hVxi)Q+ z_VaE|;)LtdHt9U=wm2|?7MH2b%xYytxiuf5K$^?W?p&ZfK9)(4Pv&+WWf7UN7w@b4 z2H5Kq)TlUfV|5B3rPar76iKNj?`0>gmy*`(%DPjQy4v`?U}21n*H*jd6{d$!Fv&j#;j}L-elr zyRv%cj4Mx9G#%M$uAT+|-PNTocOGU$LD!eQnD3pw zS&Fhn^X0U3Hyw3HXRGJQcM@A_yV^+IXtLpcyHy9NEYiY{n)m}RW{pr`#-d>pW&O_gRPi1%fWiO7=*`lX;ncSJE z^%K+6*H7lwWU$&6%4sMa^oJcA(@gqU10p^hAxiOwKF{BVGfae*F`;klUnose9Zt7t zG5>>%b#RLOelOmmZ*0IH_6#h{{{T_im0~@EzhBxvz4U`(zk+kiBax8K3x`Xcx-1uO zxa3eRqgULm2G7HfPhEw#ZIhaW}K~LTIXN#EO6*{UeBH#6qp6f<2oK?`yiG zb1S#^&Y`nbr()(rx$E@R+&-nXW8;!^ zxHaEldDb=?CbHSgCMmM#&g=JcIT;U>|cBB zZq&tn_U|6sWR(?y% z!5xM7=y!}xt;>rW>I7h-{t--)ds**q0x)TC6O4h$E(}bj%^I<0h;9I2;&9)7@Du+sCehwywF))c89uR`b8);WGW|3beU|j0pTwYE7eM{hiYBi;kGMU0#d9YY+Nk)f z62LzuZS7pm2~j_!KJ#~=e&!g|`{XR5^*q)!Kr6flUbH%FL)J-Zgwm>!s@1(CEjR6qE@Ap2{P zVw~Sy+td46GcP*A?aga}yzJz%!N`K>vLjM+Hd#JBet(Ww!i*I!MwA@%Sw|O|aiaI$ zlQv(sF-*jz@`=0i&gbEq&k#x6+*irj+f(_RYb`E%%ao?o-|u`jd)zbabt8s=a*0goti& zy0>Xi)n3R4uK%15_<6cN7nhUL&M2&9JTU2cJlK7>IrhClk0#XAGCoYxs!d@TQ#p$o z9C3ufnlR5QzyA6Qm67$kbKDOd88NvJvo0RHxtzvpKkz>Nn54zk*ZjgOxl7XRQ@g)C z?=Bm;=PBzm1xy^v|I?_cHT&`Ez)iOnXYmYASrkK$Us%YWpDtoNId#iyuZo+S_gd@r zTJKz2dxG@dOv63QQCs;SL83PQNP~fA$B#t-Yxh??Fq$|N^}sLMZJq4gi!N_PqLM8# z7wkJIZ~qSVjZ!ll-)ChMf0C^K-NN%LBoAGk8b5WdqwmA!=KPm?tqN1&Pf*SMCo8gr7h3mu4Od%S_uoul-I`5uB-7KoulYB3F zKYt~c(qndkPG!F^s4!|xOv`ZnA-XhY85Jp_GddT4p^%ByeP1=Iu4krzg6AjF-j zdjFMeQ*)D>`@&f*&ze5ys7^oLEAPE9aAf{xN;7ft`=92Tvf+jAF6ZQU)wN$sdb(>H zT=b+dYtq!oStr)H+{->wXm_^W=Z)`R$;D0b-2AO~3?tp@EOm=^J&;@Wu8W)Cy7Bhi zHfPt4FaTtwId1E!7}r7zXZRdw@$smQD4gxI>gCx+pR?~5Iy5`RnfPfyyIMQd{Oq?r zO1ak((CNL#y7xw=M~?61qzy66Ifc%|h3O6A86N0vIi{V(Nf{++{Y$D$t0mhvMC-+C z)yt%K^;pV(^Q5=zlz74Li|Ww)IbK`V?W{|^H#~<)qoAGHmDbnZIA#t)v@F>Xt%Kh? zv8jX&kEI{y-p*;(nU~mEQ7ggjAq;fs<+q(Z*?Zmpz>_!VaP}42GJ{8w&k)wD1@an0 z83MSqe#^N>*;zT+Ezis~b8lylcyF2eZHvwu^C?4{PAoIqCZ!s-_^|o&=)Qyl?=3}B zRv)z}TWGUheyNJgC25m_vlmWjY~SQxAKr2`rZzOLDj?fEVc_AV<)&)?8mIr7QM2)X zdE=;&t?Q*}Ty1qfeX9o9_nSTpYbB`UZuc4r%|WpalnFY}k+BXm9qT|zA3M-T1J{r5 z@g7KwJ%~0Yh;K@k3ytJ-W~^P;b^bOqP~Df(nH3&?Ha)Jj^ITSDO(xQg-nUvYrCWU%$(H2Xwv;u?IrW3T$*ltdsX*|-I<(ghnmwLgkO?$eVe%RnM~0E z>(|Q^E_sby3+}8q>I<5$=O-@2eEWuYVQ4{1r0V)*b1P63O^T*bbxr&^Ap41$+Xkfy8NJyWozuW_S+OC9(wSMIn4Doa5A*3yEpj8(aGZO)6O?-?0u(x z*n!?MTBy4O@PXqS0Uvk>_<;Z3$1kkpFHV17_igHym0nK)A8-VGASJl&1nIWqM?O%O zU(}%JxgAOeeBi+98l#C4Q8j+iD%F4w$YOlJ0pkO4o8KMsGf_1}h?Yk2m(?1Gs^?d{ zfKsMjns%JxQ~&H)Vc)K`s}3YBc~{yrOKw8Ya@(h8Jd-a>j>BNSeP<^I^Dpvu+}RNB zzR5OdF=KAcoUs-$2euY8RtwC3h{r5c2 zUgbkM*pII3#?2M26qZTnNS9EORjD@n3XE8Z0u>EX^&573B=0e zew4J=n(kYX58_SE4y^G9Wm4l3mcAJ;6LV*^L66tVtBP66qLY-9+&o=#yf>_^35g>X zHQ_5(&X~isKrU^M2Nbye$5xU%a}~@PnG;hmFV|eS+Vc3Bhc(uZ10?RY-C-h-o4yJ zvFSA$?~heKrfrU1UJ$9`d$_1NX714;`aF|6MB<4V4=#PHYc|PblA@a4E;qMU&*jPs zHSK2l_r1%x_Km@@#T}0>ynQ`4!+M@qd_lb4jhzoCI+oP3}BG1c+)rO|F-g|IIE^|JaTIKf0$feSlTWXtE z=ex`J`!qnT4Ie4?^?UmaWBzp&uyxQwdVG5K!{yalY5%sRr+iKA{ zB42Z9zWhgM{G81LUvp{Nd=4fC&o?I(cRJ3w1?^}{+@q8K!z0}@YKIobg{}$7?s(-ra?!iU{j#m+VHDijDz|y zAA|1!p^xF!&wUI-n2(`u*=3=RVd&as(PH3ZI0AeO{4pN`g83Mhe)KUoIP1|0)xnzOyER1!TvQL7y*2s#e0ko9LD&->p$iL z%f|RX_v29~Qsb|kNGGpQgifS4cB5uOyPum0`|bhD>7I|4Q~Q?Gek!n>X0#msrR8+# zV1~8CIm~hzllM9iSWZu2mQ$%Q%c%xtIo$*-r~aR-j~Fv}2zs87|BhjE;U{hGzawle z`I(2odd$P%&cHkjr-6rIWX!|hJ?3HXiyreZRMP<+a7YQrR!Hc@JPb;){l_nj`jQ=; zdq#c9uDzyDXcVSkfWl@xL)ln-_E3hIdVOAv9DVP;$@Td2f z70Cctk$l{M6-f_Rk$@+O{S31rQPqGI>FF@jQacn_k!mwNSOb_9sppwp4X`2^ZLbdQ z%Ml;7BKZR=QWp>UV9VZo@yadH*KP=nkO^Yz98qyCIr&4_ zp#v$pI(HdXhH)>iyGUGXs{fiVbek{0)%aiWtKs^9g?pBF(?z%g>|5@2YdOVV#F(SD zMxpg!Tdb#|$DQ*JeO`DuI{R;k$+da;r0oIGF7N&QHkr*gX>SKFzjrugrL*U?eY(N| z+T-MdDjrSiHZZ)(ds-m7s*HviNtK3y5~=sU1YtAlFJ&bBRy;C!f-l0h;$Py z!hBjpk^_q{ZjSZg)zp)JA|eff<@UcWB3&3;ZtwhQR7A2iPc_CxB+`wYxQLW}28c*A zfQXbO8gV2Dere}vJSHO50ujlge^Ius7AO+mVvUitZ%!_}!oAn?EVM9M)ghxTwcvu* zP4}Ug;+ni}b6iAP73(e(k+#rGgd&nHvh&YVUW?|i??#F{&h0QIu z%V%j)?zZTLY1+0CmN1oXP}hZja6KEaMmqob^|NjxyY?l!?{Io)@}y#bUKl--#^Z17 ziYqj;D16;@?20xy**=xpAN!^gL|m{k$mduy(wv$f`{|!;lz}%+z}MMUc@5D)ph{rT z!@S!)AW+51CYpXS_d$jYTbZCyod>~=78xeJj|S2wxeX0C*&k##CU}Wg?hef@X~^(h z$G_#XwYR!2sOt31cvf#~QB}%metK&WQ) z-}YfY3P1j8{kB&D*;?w8#P?<0P~90 z(HHuk+1&qVW9t8=1O5N0O#T~jH(zsU+WZrg@i~Xzea#)Z%YS5l`X4&*Z;Cbhl2LQ> zFS1{Mt83OV{`C#&B;!8~&w5__dznb@Yw;9>iG80@CerG@|8;oQzkjH7=!%AJpVwEZ^E0MZ4dKXDZ`pWCGaviC15p_x4&|>>%0X1d5huf- zt9l?RqtsS?XBQTgkx5%7jLM+hKJc@sjP$XnjAzzZRK`3Il`%LLmGNdQDr0Z~9+gp; zCKtu}Ax^qrqo|9XehU_YV66Fa@(L$m2tv`lhS3m&#^x)Vqi^Md5QHORAqXD72|?Js zyXhAp2u^qiLe}dfa7hAD} zH9zMTeLIk_*7Ou4tnC|1ShD~LYxN*uEo&>5u=a@7`~f7afpo(GMiic~Hu$d11WQX+}puWe#&+7E3reSOE*f0qT7CC^YeKV$ctRr4KW zY+ps+Sx8>~;w^pJrZB|i_>=3GwfC%Dyj(4QpYoG(m&%LJPS|46SyDO?!7raV_MGy` zWLj(XP1C+AGxu0=u+L?+q{=4&ek@cIm~$-5x&yk(&B4@pY1CH9U-ac@H>hBaE6O7~-`rSwo!gDAIV8EeCDbyC&T18I>pu(D9Cn>~ z;r2~`!79eVill#WSf> z%t74EL1ve6>8lin58WUucfL3Pl6W zXE(QwU~xCE6F}UJD2Tf`aT1HW(FSoh62>6z#^A{uR1U=5q)m$jaW^3#?k1cE;%?No zkHy`zV{tb-9A9UgsVNAEOcM#=lxB)VWevbrSNCrzewBG(!MVA$Z={SAJlJ=gL(n2> zlhnJ);c}7eRK(=$(I-cF_a*G@>;~m)HlB!_5!7b#HE&e@^JLG*xSP-M8O|?ehW;;f z=r2G2cb&ed$>ZOAtM!Yx(Eo6w^3&px(m#Q#zX@FZj6|fbxin+`dHDHH9)A9>ib!AX zh5i?&LI0+)MZo{T>)^j%B=Y}@VD0YuKYh~o&6eMLmAmhO@a^_n|K7LT@BR9%oG-UD z|6Dv$`sdx}Prv*8Yo+o3_7%?mpD*<0>9%5>cK5aZaM}0I#3OBurSr{a`l1g89;`Gd zT>P5fly$8_6|=8HimrQGQzG{}EZd<}dDD48yo35q3Ay2ceQL{6E+t);b}hTF+lzZd5!FjzhxblKv0&}zqqg)OkEog6%r%-R`3NV%Fu}c>g{3-;4}r7iZ3Oc%<-jn8jHjrzLUi1Kj%5@?u*- zeo;XqNHQtTDH!rzHTT;la=ZByiH)JlBxj!iPx>C%@I0D$X}^zEaLO9Nld|15rt+GK zGMA+4Zk?5D&)&YtvEmx`q;E)ERk^2o#=xDzrxc}FZ&7(OvFW7&iDC-QJ(+7lI&U9- z=PNF94!$6jg_Z`XXA0(fVvp8nC@($6v;_u4poinaHUD0%8&s}b> zPVr(s(?-Q_Rq-JxY3tx|G(>%``gcBED_vcC;>&FfGar@l{8BTV#s5}9V)|w&$`=>-|lw{@?~TK~|oKj2pdB506`&HfzM1A-aKnfaU<= zR!=>qw(^Dt6VCA&B$3_w%-hSm-r9>OrmSw_;&H2aqj9TEHy9NEYiY{n)m}RW57V!y zy^S3n@X)v4Q`!AS+7-p(Rv{3#I{1sYRqDsM)x;hLEN*q3R!qYM+q3SJDXlSnFEETT@!Glpi(B0vSTFfxd#Nq|ru_xE z6^a=5mveHw>e{a*J>9hpE_%|KHEHVPtP|^8?q#1T#Maq9{Xlrz?`0tPcwn#gIIVj5 z8D$_;)Jm{>2m@Vu`E6%U_Fnft@Z`^SRvh(hx}@{` z#D$n|-w-bhEog~UUB9gExtM}c>UJ{_q?!Bb^NPa#qPga8B=??QJOtQ6r!}Syf@htM z<&I#_I`y^G(1T~pVXn7t*P8N5ccD`w2?>qIw4)m7M zLfs{^AFn>X@z&z3hlZyt{P#Y7VI_ZY`uny+TS6QtXc8t!3^ z+R6tB5_S1S4T_%Iq4Yhq@&{Je7)_Ljs_~0fsg|94QTDA!RI)?nf_(?$HorUMXQFC| z5G{@3FRL{WRnM<@0i{g6H0?OWr~cWq!oFQ=R~<-N@~*ULmfVD(<+e}HU|Y*^qg%@* z7e8(-Z?e&4g01Cc@4B2QRIs%icfSF)0b9!fu3&39QnzALfItWmSA@dD4SEv1`rSoY>SCKVnYuRm&@v9+Cgyq^epjALZXctCqLloRZ;A3Mj}bCbC!VXw`DRNn-&5`_vsZiSxC|jC(lL63{mn{YEa(Y z(40l)(LZxtNE~w)F>c&(R%s033J*zv)4*MMNenAkjx4wFN|L4Hn6LMd|J!TxxEUP} z;CGHH!cH06sSmik$^QeY@xZ^|_95eedR%JTH7@ghQUaBx8@hV9w|pe?Tk`OU1T3EF z(c{v6F_6ItGMBX}bAr-u_WMR#ryKcy%zLCu6V^3WxA#T=V6^+7WcX}EUYR@jdiCT* zy7HjockUTG6o)F6t~GJ1w~d87r0h%+3$};8(__tczhUTaVd`=>fp}x=ah>%p^~1;{HMWiy7S@nU zdf=<$flc=|4ZcChAO{rTaqG@E^6b<`o?8nJ*pLLpoZAv1IZTq6flHOkNaOf<181(- zcGee^X1$2_i}Ml6`D-pW+`v1U+fhC{-Pxb^V4ZclKV8#~w0~_;-;CdC@Bx16#G^42d> zWQnG3^%3**kJ6>b>NXjB>PweQ;P7f=(FV@livn7S2axFmYC3~CJ3uokD4~IecY!*dVBucS z$_sqt4Tdp5T^4x753~;eyV+p$5pY>3Siu3EkAku&FpdkZJONh6fwYt0Xd-y(G-#9z zHk<+Xq=6GW@N6ddbvD>~9`wlv)eFGPOW+0pD7p;#m4a9ym{$Q>RDvDX!JwPqf?BYs z9<*r!yIR2TRuFdwEWHal{tEVrL9P^B-U(9d)sWCa15R5NaJt!_sG~81#?O5C$ZN;H UM<2yrLlb>d4cX05yU