From 63fa55802d0d4e3277c816daf94501ccf72f9d56 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 20 Aug 2022 10:20:21 +0530 Subject: [PATCH 001/375] initial commit --- enhancer/__init__.py | 0 enhancer/data/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 enhancer/__init__.py create mode 100644 enhancer/data/__init__.py diff --git a/enhancer/__init__.py b/enhancer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/enhancer/data/__init__.py b/enhancer/data/__init__.py new file mode 100644 index 0000000..e69de29 From 86ccdbb5cb1aea7ed3a738a34da1ed29992d3ac2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 22 Aug 2022 10:05:34 +0530 Subject: [PATCH 002/375] dataset --- enhancer/data/vctk.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 enhancer/data/vctk.py diff --git a/enhancer/data/vctk.py b/enhancer/data/vctk.py new file mode 100644 index 0000000..2e46662 --- /dev/null +++ b/enhancer/data/vctk.py @@ -0,0 +1,38 @@ + +from genericpath import isdir +import librosa +import os +from torch.utils.data import IterableDataset +import torch + + +class Vctk(IterableDataset): + """Dataset object for Voice Bank Corpus (VCTK) Dataset""" + + def __init__(self,clean_path,noisy_path,sample_length=1,num_samples=None): + + if not os.path.isdir(clean_path): + raise ValueError(f"{clean_path} is not a valid directory") + + if not os.path.isdir(noisy_path): + raise ValueError(f"{clean_path} is not a valid directory") + + self.clean_path = clean_path + self.noisy_path = noisy_path + + if num_samples is None: + self.num_samples = len([file for file in os.listdir(clean_path) if file.endswith(".wav")]) + else: + self.num_samples = num_samples + + self.sample_length = max(0.1,sample_length) + + def __iter__(self): + + + + + pass + + def __len__(self): + pass From deccda538944fa4bb1342588fe1b795453c644c5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 22 Aug 2022 10:32:12 +0530 Subject: [PATCH 003/375] utils --- enhancer/utils/__init__.py` | 0 enhancer/utils/random.py | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 enhancer/utils/__init__.py` create mode 100644 enhancer/utils/random.py diff --git a/enhancer/utils/__init__.py` b/enhancer/utils/__init__.py` new file mode 100644 index 0000000..e69de29 diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py new file mode 100644 index 0000000..1a68021 --- /dev/null +++ b/enhancer/utils/random.py @@ -0,0 +1,40 @@ +import os +import random +import torch + + + +def create_unique_rnge(epoch:int): + """create unique random number generator for each (worker_id,epoch) combination""" + + rng = random.Random() + + global_seed = int(os.environ.get("PL_GLOBAL_SEED","0")) + global_rank = int(os.environ.get('GLOBAL_RANK',"0")) + local_rank = int(os.environ.get('LOCAL_RANK',"0")) + node_rank = int(os.environ.get('NODE_RANK',"0")) + world_size = int(os.environ.get('WORLD_SIZE',"0")) + + worker_info = torch.utils.data.get_worker_info() + if worker_info is not None: + num_workers = worker_info.num_workers + worker_id = worker_info.worker_id + else: + num_workers = 1 + worker_id = 0 + + seed = ( + global_seed + + worker_id + + local_rank * num_workers + + node_rank * num_workers * global_rank + + epoch * num_workers * world_size + ) + + rng.seed(seed) + + return rng + + + + From bcbc82dbade8318b0a8c0b30d20cb3c8e884ed79 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 22 Aug 2022 13:25:18 +0530 Subject: [PATCH 004/375] audio io --- enhancer/utils/io.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 enhancer/utils/io.py diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py new file mode 100644 index 0000000..510ab86 --- /dev/null +++ b/enhancer/utils/io.py @@ -0,0 +1,68 @@ +import os +import librosa +from typing import Optional +from matplotlib.pyplot import axis +import numpy as np +import torch + +class Audio: + + def __init__( + self, + sampling_rate:int=16000, + mono:bool=True, + return_tensor=True + ) -> None: + + self.sampling_rate = sampling_rate + self.mono = mono + self.return_tensor = return_tensor + + def __call__( + self, + audio, + sampling_rate:Optional[int]=None, + offset:Optional[float] = None, + duration:Optional[float] = None + ): + if isinstance(audio,str): + if os.path.exists(audio): + audio,sampling_rate = librosa.load(audio,sr=sampling_rate,mono=False, + offset=offset,duration=duration) + else: + raise FileNotFoundError(f"File {audio} deos not exist") + elif isinstance(audio,np.ndarray): + if len(audio.shape) == 1: + audio = audio.reshape(1,-1) + else: + raise ValueError("audio should be either filepath or numpy ndarray") + + if self.mono: + audio = self.convert_mono(audio) + + resampled_audio = self.resample_audio(audio,sampling_rate) + if self.return_tensor: + return torch.tensor(resampled_audio) + else: + return resampled_audio + + def convert_mono( + self, + audio + + ): + num_channels,num_samples = audio.shape + if num_channels>1 and self.mono: + return audio.mean(axis=0).reshape(1,num_samples) + return audio + + + def resample_audio( + self, + audio, + sampling_rate + ): + if self.sampling_rate!=sampling_rate: + audio = librosa.resample(audio,orig_sr=sampling_rate,target_sr=self.sampling_rate) + + return audio \ No newline at end of file From 54a4364fb9687d860e2e69bc28c5881f5c4ec3d9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 22 Aug 2022 13:25:43 +0530 Subject: [PATCH 005/375] vctk dataset --- enhancer/data/vctk.py | 57 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/enhancer/data/vctk.py b/enhancer/data/vctk.py index 2e46662..53e6e54 100644 --- a/enhancer/data/vctk.py +++ b/enhancer/data/vctk.py @@ -1,15 +1,18 @@ -from genericpath import isdir -import librosa +import glob +import math import os +from scipy.io import wavfile from torch.utils.data import IterableDataset -import torch + +from enhancer.utils.random import create_unique_rng +from enhancer.utils.io import Audio class Vctk(IterableDataset): """Dataset object for Voice Bank Corpus (VCTK) Dataset""" - def __init__(self,clean_path,noisy_path,sample_length=1,num_samples=None): + def __init__(self,clean_path,noisy_path,duration=1,sampling_rate=16000,num_samples=None): if not os.path.isdir(clean_path): raise ValueError(f"{clean_path} is not a valid directory") @@ -17,22 +20,54 @@ class Vctk(IterableDataset): if not os.path.isdir(noisy_path): raise ValueError(f"{clean_path} is not a valid directory") + self.sampling_rate = sampling_rate self.clean_path = clean_path self.noisy_path = noisy_path + self.wav_samples =[file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] if num_samples is None: - self.num_samples = len([file for file in os.listdir(clean_path) if file.endswith(".wav")]) + self.num_samples = len(self.wav_samples) else: self.num_samples = num_samples - self.sample_length = max(0.1,sample_length) + self.duration = max(1.0,duration) + self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) + self.files_duration = self.get_files_duration() + + def get_file_duration(self): + + files_duration = {} + for file in self.clean_path: + wavfile = wavfile.read(os.path.join(self.clean_path,file),rate=self.sampling_rate) + files_duration.update({file:math.ceil(wavfile/self.sampling_rate)}) + + return files_duration + def __iter__(self): + rng = create_unique_rng(12) ##pass epoch number here + + while True: + + file_name = rng.choices(self.wav_samples,k=1) + file_duration = self.files_duration.get(file_name) + start_time = rng.randint(0,math.ceil(file_duration- self.duration)) + data = self.prepare_segment(file_name,start_time) + yield data + + def prepare_segment(self,file_name:str, start_time:int): + + clean_segment = self.audio(os.path.join(self.clean_path,file_name), + offset=start_time,duration=self.duration) + noisy_segment = self.audio(os.path.join(self.noisy_path,file_name), + offset=start_time,duration=self.duration) + + return {"clean": clean_segment,"noisy":noisy_segment} - - - pass - def __len__(self): - pass + + return math.ceil(sum(self.files_duration.values())/self.duration) + + + From 65540148f7d4e5266382231db43272df90235cc9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 23 Aug 2022 13:33:46 +0530 Subject: [PATCH 006/375] fix sampling bugs --- enhancer/data/vctk.py | 63 +++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/enhancer/data/vctk.py b/enhancer/data/vctk.py index 53e6e54..281ac2c 100644 --- a/enhancer/data/vctk.py +++ b/enhancer/data/vctk.py @@ -1,18 +1,37 @@ import glob import math +import numpy as np import os from scipy.io import wavfile from torch.utils.data import IterableDataset +import torch.nn.functional as F from enhancer.utils.random import create_unique_rng from enhancer.utils.io import Audio + +class VctkDataset: + + def __init__(self): + pass + + def train_loader(self): + pass + + def valid_loader(self): + pass + + def test_loader(self): + pass + + + class Vctk(IterableDataset): """Dataset object for Voice Bank Corpus (VCTK) Dataset""" - def __init__(self,clean_path,noisy_path,duration=1,sampling_rate=16000,num_samples=None): + def __init__(self,clean_path,noisy_path,duration=1.0,sampling_rate=48000): if not os.path.isdir(clean_path): raise ValueError(f"{clean_path} is not a valid directory") @@ -23,46 +42,50 @@ class Vctk(IterableDataset): self.sampling_rate = sampling_rate self.clean_path = clean_path self.noisy_path = noisy_path - self.wav_samples =[file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] - - if num_samples is None: - self.num_samples = len(self.wav_samples) - else: - self.num_samples = num_samples - + self.files_duration = self.get_matching_files_duration() + self.wav_samples = list(self.files_duration.keys()) self.duration = max(1.0,duration) self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) - self.files_duration = self.get_files_duration() - def get_file_duration(self): + def get_matching_files_duration(self): - files_duration = {} - for file in self.clean_path: - wavfile = wavfile.read(os.path.join(self.clean_path,file),rate=self.sampling_rate) - files_duration.update({file:math.ceil(wavfile/self.sampling_rate)}) + matching_wavfiles_dur = dict() + clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(self.clean_path,"*.wav"))] + noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(self.noisy_path,"*.wav"))] + common_filenames = np.intersect1d(noisy_filenames,clean_filenames) - return files_duration + for file_name in common_filenames: + sr_clean, clean_file = wavfile.read(os.path.join(self.clean_path,file_name)) + sr_noisy, noisy_file = wavfile.read(os.path.join(self.noisy_path,file_name)) + if ((clean_file.shape[-1]==noisy_file.shape[-1]) and + (sr_clean==self.sampling_rate) and + (sr_noisy==self.sampling_rate)): + matching_wavfiles_dur.update({file_name:(clean_file.shape[-1]/self.sampling_rate)}) + + return matching_wavfiles_dur def __iter__(self): rng = create_unique_rng(12) ##pass epoch number here - + while True: - file_name = rng.choices(self.wav_samples,k=1) + file_name,*_ = rng.choices(self.wav_samples,k=1, + weights=[self.files_duration[file] for file in self.wav_samples]) file_duration = self.files_duration.get(file_name) - start_time = rng.randint(0,math.ceil(file_duration- self.duration)) + start_time = round(rng.uniform(0,file_duration- self.duration),2) data = self.prepare_segment(file_name,start_time) yield data - def prepare_segment(self,file_name:str, start_time:int): + def prepare_segment(self,file_name:str, start_time:float): clean_segment = self.audio(os.path.join(self.clean_path,file_name), offset=start_time,duration=self.duration) noisy_segment = self.audio(os.path.join(self.noisy_path,file_name), offset=start_time,duration=self.duration) - + clean_segment = F.pad(clean_segment,(0,int(self.duration*self.sampling_rate-clean_segment.shape[-1]))) + noisy_segment = F.pad(noisy_segment,(0,int(self.duration*self.sampling_rate-noisy_segment.shape[-1]))) return {"clean": clean_segment,"noisy":noisy_segment} def __len__(self): From 569fa31d06b694d4bb11569aaebe29b646f18287 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 23 Aug 2022 13:34:26 +0530 Subject: [PATCH 007/375] reshape read audio --- enhancer/utils/io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 510ab86..04015e6 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -29,6 +29,8 @@ class Audio: if os.path.exists(audio): audio,sampling_rate = librosa.load(audio,sr=sampling_rate,mono=False, offset=offset,duration=duration) + if len(audio.shape) == 1: + audio = audio.reshape(1,-1) else: raise FileNotFoundError(f"File {audio} deos not exist") elif isinstance(audio,np.ndarray): From 6cd434c23054bbd2bb7c7bda6244db866b89c181 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 23 Aug 2022 13:34:43 +0530 Subject: [PATCH 008/375] fix typo --- enhancer/utils/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py index 1a68021..512bd06 100644 --- a/enhancer/utils/random.py +++ b/enhancer/utils/random.py @@ -4,7 +4,7 @@ import torch -def create_unique_rnge(epoch:int): +def create_unique_rng(epoch:int): """create unique random number generator for each (worker_id,epoch) combination""" rng = random.Random() From 375cb8bc446d2a67e2e54dbcf2733e70c1b1e640 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 24 Aug 2022 10:11:16 +0530 Subject: [PATCH 009/375] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b6e4761..749849e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +##local +datasets/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] From 74d8e2602c148965a5f615c634362129350ccb82 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 24 Aug 2022 19:46:15 +0530 Subject: [PATCH 010/375] config --- conf.yaml | 8 ++++++++ enhancer/utils/config.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 conf.yaml create mode 100644 enhancer/utils/config.py diff --git a/conf.yaml b/conf.yaml new file mode 100644 index 0000000..e0edeed --- /dev/null +++ b/conf.yaml @@ -0,0 +1,8 @@ +files: + train_clean: clean_testset_wav + train_noisy: noisy_testset_wav + test_clean: clean_testset_wav + test_noisy: noisy_testset_wav +paths: + data: ${hydra:runtime.cwd}/../data/vctk + log: ./runs \ No newline at end of file diff --git a/enhancer/utils/config.py b/enhancer/utils/config.py new file mode 100644 index 0000000..0aaa2e3 --- /dev/null +++ b/enhancer/utils/config.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +@dataclass +class Paths: + log : str + data : str + +@dataclass +class Files: + train_clean : str + train_noisy : str + test_clean : str + test_noisy : str + +@dataclass +class EnhancerConfig: + path : Paths + files: Files \ No newline at end of file From 674bd0d0e2177a7aaf88c9924b3b053d3c3daa9e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 24 Aug 2022 19:46:24 +0530 Subject: [PATCH 011/375] main --- main.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..a626393 --- /dev/null +++ b/main.py @@ -0,0 +1,19 @@ +import hydra +import torch +from hydra.core.config_store import ConfigStore + +from enhancer.utils.config import EnhancerConfig + +cs = ConfigStore.instance() +cs.store(name="enhancer_config", node=EnhancerConfig) + + +@hydra.main(config_path=".",config_name="conf") +def main(cfg: EnhancerConfig): + + print(cfg.paths.data) + + + +if __name__=="__main__": + main() \ No newline at end of file From 2a6f310ba42194f2012d57577f24e102a99c45b8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 24 Aug 2022 19:47:10 +0530 Subject: [PATCH 012/375] dataset --- enhancer/data/dataset.py | 34 ++++++++++++++++++++++++++++++++++ enhancer/data/vctk.py | 16 ---------------- 2 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 enhancer/data/dataset.py diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py new file mode 100644 index 0000000..6b44001 --- /dev/null +++ b/enhancer/data/dataset.py @@ -0,0 +1,34 @@ +import os +import pytorch_lightning as pl +from typing import Optional + +from enhancer.data.vctk import Vctk +from enhancer.utils.config import Files + +DATASETS = ["Vctk"] + +class Dataset(pl.LightningDataModule): + + def __init__(self,name:str, directory:str, files:Files, + duration:float=1.0, sampling_rate:int=48000): + super().__init__() + + self.train_clean = os.path.join(directory,Files.train_clean) + self.train_noisy = os.path.join(directory,Files.train_noisy) + self.valid_clean = os.path.join(directory,Files.test_clean) + self.valid_noisy = os.path.join(directory,Files.test_noisy) + + if name.title() in DATASETS: + self.data_obj = eval(name.title) + + self.duration = duration + self.sampling_rate = sampling_rate + + def setup(self, stage: Optional[str] = None): + self.train_dataset = self.data_obj() + + def train_loader(self): + pass + + def valid_loader(self): + pass diff --git a/enhancer/data/vctk.py b/enhancer/data/vctk.py index 281ac2c..90cd87a 100644 --- a/enhancer/data/vctk.py +++ b/enhancer/data/vctk.py @@ -12,22 +12,6 @@ from enhancer.utils.io import Audio -class VctkDataset: - - def __init__(self): - pass - - def train_loader(self): - pass - - def valid_loader(self): - pass - - def test_loader(self): - pass - - - class Vctk(IterableDataset): """Dataset object for Voice Bank Corpus (VCTK) Dataset""" From 22b017daeab361ced6b5044baa10f2f19abdb8c9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 26 Aug 2022 16:56:11 +0530 Subject: [PATCH 013/375] process data files --- enhancer/utils/fileprocessor.py | 96 +++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 enhancer/utils/fileprocessor.py diff --git a/enhancer/utils/fileprocessor.py b/enhancer/utils/fileprocessor.py new file mode 100644 index 0000000..e7cd033 --- /dev/null +++ b/enhancer/utils/fileprocessor.py @@ -0,0 +1,96 @@ +import glob +import os +import numpy as np +from scipy.io import wavfile + +class Fileprocessor: + + def __init__( + self, + clean_dir, + noisy_dir, + sr = 16000, + matching_function = None + ): + self.clean_dir = clean_dir + self.noisy_dir = noisy_dir + self.sr = sr + self.matching_function = matching_function + + @classmethod + def from_name(cls, + name:str, + clean_dir, + noisy_dir, + sr, + matching_function=None + ): + + if name.lower() == "vctk": + return cls(clean_dir,noisy_dir,sr, Fileprocessor.match_vtck) + elif name.lower() == "dns-2020": + return cls(clean_dir,noisy_dir,sr, Fileprocessor.match_dns2020) + else: + return cls(clean_dir,noisy_dir,sr, matching_function) + + def prepare_matching_dict(self): + + if self.matching_function is None: + raise ValueError("Not a valid matching function") + + return self.matching_function(self.clean_dir,self.noisy_dir,self.sr) + + @staticmethod + def match_vtck(clean_path,noisy_path,sr): + + matching_wavfiles = dict() + clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] + noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(noisy_path,"*.wav"))] + common_filenames = np.intersect1d(noisy_filenames,clean_filenames) + + for file_name in common_filenames: + + sr_clean, clean_file = wavfile.read(os.path.join(clean_path,file_name)) + sr_noisy, noisy_file = wavfile.read(os.path.join(noisy_path,file_name)) + if ((clean_file.shape[-1]==noisy_file.shape[-1]) and + (sr_clean==sr) and + (sr_noisy==sr)): + matching_wavfiles.update( + {os.path.join(clean_path,file_name):{"noisy":os.path.join(noisy_path,file_name), + "duration":clean_file.shape[-1]/sr} + } + ) + return matching_wavfiles + + @staticmethod + def match_dns2020(clean_path,noisy_path,sr): + + matching_wavfiles = dict() + clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] + for clean_file in clean_filenames: + noisy_filenames = glob.glob(os.path.join(noisy_path,f"*_{clean_file}.wav")) + for noisy_file in noisy_filenames: + + sr_clean, clean_file = wavfile.read(os.path.join(clean_path,clean_file)) + sr_noisy, noisy_file = wavfile.read(noisy_file) + if ((clean_file.shape[-1]==noisy_file.shape[-1]) and + (sr_clean==sr) and + (sr_noisy==sr)): + matching_wavfiles.update( + {os.path.join(clean_path,clean_file):{"noisy":noisy_file, + "duration":clean_file.shape[-1]/sr} + } + ) + + return matching_wavfiles + + + + + + + + + + + From 556da7c3a0e1d42d026ec8dd65aa34144fe32503 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 26 Aug 2022 16:58:02 +0530 Subject: [PATCH 014/375] enhancer datasets --- enhancer/data/vctk.py | 56 ++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/enhancer/data/vctk.py b/enhancer/data/vctk.py index 90cd87a..dbae569 100644 --- a/enhancer/data/vctk.py +++ b/enhancer/data/vctk.py @@ -9,45 +9,29 @@ import torch.nn.functional as F from enhancer.utils.random import create_unique_rng from enhancer.utils.io import Audio +from enhancer.utils import Fileprocessor -class Vctk(IterableDataset): - """Dataset object for Voice Bank Corpus (VCTK) Dataset""" +class EnhancerDataset(IterableDataset): + """Dataset object for creating clean-noisy speech enhancement datasets""" - def __init__(self,clean_path,noisy_path,duration=1.0,sampling_rate=48000): + def __init__(self,name:str,clean_dir,noisy_dir,duration=1.0,sampling_rate=48000, matching_function=None): - if not os.path.isdir(clean_path): - raise ValueError(f"{clean_path} is not a valid directory") + if not os.path.isdir(clean_dir): + raise ValueError(f"{clean_dir} is not a valid directory") - if not os.path.isdir(noisy_path): - raise ValueError(f"{clean_path} is not a valid directory") + if not os.path.isdir(noisy_dir): + raise ValueError(f"{clean_dir} is not a valid directory") self.sampling_rate = sampling_rate - self.clean_path = clean_path - self.noisy_path = noisy_path - self.files_duration = self.get_matching_files_duration() - self.wav_samples = list(self.files_duration.keys()) + self.clean_dir = clean_dir + self.noisy_dir = noisy_dir self.duration = max(1.0,duration) self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) - def get_matching_files_duration(self): - - matching_wavfiles_dur = dict() - clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(self.clean_path,"*.wav"))] - noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(self.noisy_path,"*.wav"))] - common_filenames = np.intersect1d(noisy_filenames,clean_filenames) - - for file_name in common_filenames: - - sr_clean, clean_file = wavfile.read(os.path.join(self.clean_path,file_name)) - sr_noisy, noisy_file = wavfile.read(os.path.join(self.noisy_path,file_name)) - if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==self.sampling_rate) and - (sr_noisy==self.sampling_rate)): - matching_wavfiles_dur.update({file_name:(clean_file.shape[-1]/self.sampling_rate)}) - - return matching_wavfiles_dur + fp = Fileprocessor.from_name(name,clean_dir,noisy_dir,matching_function) + self.valid_files = fp.prepare_matching_dict() def __iter__(self): @@ -55,18 +39,18 @@ class Vctk(IterableDataset): while True: - file_name,*_ = rng.choices(self.wav_samples,k=1, - weights=[self.files_duration[file] for file in self.wav_samples]) - file_duration = self.files_duration.get(file_name) + file_dict,*_ = rng.choices(self.valid_files,k=1, + weights=[self.valid_files[file]['duration'] for file in self.valid_files]) + file_duration = file_dict['duration'] start_time = round(rng.uniform(0,file_duration- self.duration),2) - data = self.prepare_segment(file_name,start_time) + data = self.prepare_segment(file_dict,start_time) yield data - def prepare_segment(self,file_name:str, start_time:float): + def prepare_segment(self,file_dict:dict, start_time:float): - clean_segment = self.audio(os.path.join(self.clean_path,file_name), + clean_segment = self.audio(file_dict.keys()[0], offset=start_time,duration=self.duration) - noisy_segment = self.audio(os.path.join(self.noisy_path,file_name), + noisy_segment = self.audio(file_dict['noisy'], offset=start_time,duration=self.duration) clean_segment = F.pad(clean_segment,(0,int(self.duration*self.sampling_rate-clean_segment.shape[-1]))) noisy_segment = F.pad(noisy_segment,(0,int(self.duration*self.sampling_rate-noisy_segment.shape[-1]))) @@ -74,7 +58,7 @@ class Vctk(IterableDataset): def __len__(self): - return math.ceil(sum(self.files_duration.values())/self.duration) + return math.ceil(sum([file["duration"] for file in self.valid_files])/self.duration) From a2f6fbcca29c7d1690ddec5d39afe67db555779b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 31 Aug 2022 11:04:36 +0530 Subject: [PATCH 015/375] delete reduntant module --- enhancer/data/dataset.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 enhancer/data/dataset.py diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py deleted file mode 100644 index 6b44001..0000000 --- a/enhancer/data/dataset.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import pytorch_lightning as pl -from typing import Optional - -from enhancer.data.vctk import Vctk -from enhancer.utils.config import Files - -DATASETS = ["Vctk"] - -class Dataset(pl.LightningDataModule): - - def __init__(self,name:str, directory:str, files:Files, - duration:float=1.0, sampling_rate:int=48000): - super().__init__() - - self.train_clean = os.path.join(directory,Files.train_clean) - self.train_noisy = os.path.join(directory,Files.train_noisy) - self.valid_clean = os.path.join(directory,Files.test_clean) - self.valid_noisy = os.path.join(directory,Files.test_noisy) - - if name.title() in DATASETS: - self.data_obj = eval(name.title) - - self.duration = duration - self.sampling_rate = sampling_rate - - def setup(self, stage: Optional[str] = None): - self.train_dataset = self.data_obj() - - def train_loader(self): - pass - - def valid_loader(self): - pass From 5fa8d4059c3a5135ead3b894e55f541384fd3b64 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 31 Aug 2022 11:05:04 +0530 Subject: [PATCH 016/375] add dataset --- enhancer/data/dataset.py | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 enhancer/data/dataset.py diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py new file mode 100644 index 0000000..4bc5b3b --- /dev/null +++ b/enhancer/data/dataset.py @@ -0,0 +1,95 @@ + +import glob +import math +import os +import pytorch_lightning as pl +from torch.utils.data import IterableDataset, DataLoader +import torch.nn.functional as F +from typing import Optional + +from enhancer.utils.random import create_unique_rng +from enhancer.utils.io import Audio +from enhancer.utils import Fileprocessor +from enhancer.utils.config import Files + + + +class EnhancerDataset(IterableDataset): + """Dataset object for creating clean-noisy speech enhancement datasets""" + + def __init__(self,name:str,clean_dir,noisy_dir,duration=1.0,sampling_rate=48000, matching_function=None): + + if not os.path.isdir(clean_dir): + raise ValueError(f"{clean_dir} is not a valid directory") + + if not os.path.isdir(noisy_dir): + raise ValueError(f"{clean_dir} is not a valid directory") + + self.sampling_rate = sampling_rate + self.clean_dir = clean_dir + self.noisy_dir = noisy_dir + self.duration = max(1.0,duration) + self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) + + fp = Fileprocessor.from_name(name,clean_dir,noisy_dir,matching_function) + self.valid_files = fp.prepare_matching_dict() + + def __iter__(self): + + rng = create_unique_rng(12) ##pass epoch number here + + while True: + + file_dict,*_ = rng.choices(self.valid_files,k=1, + weights=[self.valid_files[file]['duration'] for file in self.valid_files]) + file_duration = file_dict['duration'] + start_time = round(rng.uniform(0,file_duration- self.duration),2) + data = self.prepare_segment(file_dict,start_time) + yield data + + def prepare_segment(self,file_dict:dict, start_time:float): + + clean_segment = self.audio(file_dict.keys()[0], + offset=start_time,duration=self.duration) + noisy_segment = self.audio(file_dict['noisy'], + offset=start_time,duration=self.duration) + clean_segment = F.pad(clean_segment,(0,int(self.duration*self.sampling_rate-clean_segment.shape[-1]))) + noisy_segment = F.pad(noisy_segment,(0,int(self.duration*self.sampling_rate-noisy_segment.shape[-1]))) + return {"clean": clean_segment,"noisy":noisy_segment} + + def __len__(self): + + return math.ceil(sum([file["duration"] for file in self.valid_files])/self.duration) + + + +class Dataset(pl.LightningDataModule): + + def __init__(self,name:str, files:Files, + duration:float=1.0, sampling_rate:int=48000, batch_size=32): + super().__init__() + + self.train_clean = files.train_clean + self.train_noisy = files.train_noisy + self.valid_clean = files.test_clean + self.valid_noisy = files.test_noisy + self.name = name + self.duration = duration + self.sampling_rate = sampling_rate + self.batch_size = batch_size + + def setup(self, stage: Optional[str] = None): + + if stage in (None,"fit"): + self.train_dataset = EnhancerDataset(self.name, self.train_clean, + self.train_noisy, self.duration, self.sampling_rate) + + self.valid_dataset = EnhancerDataset(self.name, self.valid_clean, + self.valid_noisy, self.duration, self.sampling_rate) + + def train_loader(self): + return DataLoader(self.train_dataset, batch_size = self.batch_size) + + + def valid_loader(self): + return DataLoader(self.valid_dataset, batch_size = self.batch_size) \ No newline at end of file From a939b4c37d8d240b059d9304168ba69da3b879cf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 31 Aug 2022 11:05:14 +0530 Subject: [PATCH 017/375] rename module --- enhancer/data/vctk.py | 64 ------------------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 enhancer/data/vctk.py diff --git a/enhancer/data/vctk.py b/enhancer/data/vctk.py deleted file mode 100644 index dbae569..0000000 --- a/enhancer/data/vctk.py +++ /dev/null @@ -1,64 +0,0 @@ - -import glob -import math -import numpy as np -import os -from scipy.io import wavfile -from torch.utils.data import IterableDataset -import torch.nn.functional as F - -from enhancer.utils.random import create_unique_rng -from enhancer.utils.io import Audio -from enhancer.utils import Fileprocessor - - - -class EnhancerDataset(IterableDataset): - """Dataset object for creating clean-noisy speech enhancement datasets""" - - def __init__(self,name:str,clean_dir,noisy_dir,duration=1.0,sampling_rate=48000, matching_function=None): - - if not os.path.isdir(clean_dir): - raise ValueError(f"{clean_dir} is not a valid directory") - - if not os.path.isdir(noisy_dir): - raise ValueError(f"{clean_dir} is not a valid directory") - - self.sampling_rate = sampling_rate - self.clean_dir = clean_dir - self.noisy_dir = noisy_dir - self.duration = max(1.0,duration) - self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) - - fp = Fileprocessor.from_name(name,clean_dir,noisy_dir,matching_function) - self.valid_files = fp.prepare_matching_dict() - - def __iter__(self): - - rng = create_unique_rng(12) ##pass epoch number here - - while True: - - file_dict,*_ = rng.choices(self.valid_files,k=1, - weights=[self.valid_files[file]['duration'] for file in self.valid_files]) - file_duration = file_dict['duration'] - start_time = round(rng.uniform(0,file_duration- self.duration),2) - data = self.prepare_segment(file_dict,start_time) - yield data - - def prepare_segment(self,file_dict:dict, start_time:float): - - clean_segment = self.audio(file_dict.keys()[0], - offset=start_time,duration=self.duration) - noisy_segment = self.audio(file_dict['noisy'], - offset=start_time,duration=self.duration) - clean_segment = F.pad(clean_segment,(0,int(self.duration*self.sampling_rate-clean_segment.shape[-1]))) - noisy_segment = F.pad(noisy_segment,(0,int(self.duration*self.sampling_rate-noisy_segment.shape[-1]))) - return {"clean": clean_segment,"noisy":noisy_segment} - - def __len__(self): - - return math.ceil(sum([file["duration"] for file in self.valid_files])/self.duration) - - - From 7353fdafc2a97d9a8f5295275636688e82be9c64 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 1 Sep 2022 09:46:38 +0530 Subject: [PATCH 018/375] add root dir --- enhancer/data/dataset.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 4bc5b3b..2807aed 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -65,14 +65,14 @@ class EnhancerDataset(IterableDataset): class Dataset(pl.LightningDataModule): - def __init__(self,name:str, files:Files, + def __init__(self,name:str,root_dir:str, files:Files, duration:float=1.0, sampling_rate:int=48000, batch_size=32): super().__init__() - self.train_clean = files.train_clean - self.train_noisy = files.train_noisy - self.valid_clean = files.test_clean - self.valid_noisy = files.test_noisy + self.train_clean = os.path.join(root_dir, files.train_clean) + self.train_noisy = os.path.join(root_dir,files.train_noisy) + self.valid_clean = os.path.join(root_dir,files.test_clean) + self.valid_noisy = os.path.join(root_dir,files.test_noisy) self.name = name self.duration = duration self.sampling_rate = sampling_rate @@ -87,9 +87,9 @@ class Dataset(pl.LightningDataModule): self.valid_dataset = EnhancerDataset(self.name, self.valid_clean, self.valid_noisy, self.duration, self.sampling_rate) - def train_loader(self): + def train_dataloader(self): return DataLoader(self.train_dataset, batch_size = self.batch_size) - def valid_loader(self): + def valid_dataloader(self): return DataLoader(self.valid_dataset, batch_size = self.batch_size) \ No newline at end of file From 936bcd9869570299de16df8970b33d983fa4bc63 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 1 Sep 2022 09:46:53 +0530 Subject: [PATCH 019/375] config --- conf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf.yaml b/conf.yaml index e0edeed..969bde8 100644 --- a/conf.yaml +++ b/conf.yaml @@ -4,5 +4,5 @@ files: test_clean: clean_testset_wav test_noisy: noisy_testset_wav paths: - data: ${hydra:runtime.cwd}/../data/vctk + data: ${hydra:runtime.cwd}/datasets/vctk log: ./runs \ No newline at end of file From d6cd4c36e48e8fefce2b7cedfa636ffbbb46b938 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 1 Sep 2022 09:48:57 +0530 Subject: [PATCH 020/375] init --- enhancer/utils/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 enhancer/utils/__init__.py diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py new file mode 100644 index 0000000..3d9d78a --- /dev/null +++ b/enhancer/utils/__init__.py @@ -0,0 +1 @@ +from enhancer.utils.fileprocessor import Fileprocessor \ No newline at end of file From 761683fd41bd00a82bd721b26e2b37aa928b8bea Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 1 Sep 2022 09:50:20 +0530 Subject: [PATCH 021/375] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 749849e..ae420f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ ##local +.DS_Store +outputs/ datasets/ # Byte-compiled / optimized / DLL files __pycache__/ From b2bae3d4d15cdb73f4c2f2b3dffd7354b73a059c Mon Sep 17 00:00:00 2001 From: Shahul ES Date: Thu, 1 Sep 2022 09:51:52 +0530 Subject: [PATCH 022/375] Delete __init__.py` --- enhancer/utils/__init__.py` | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 enhancer/utils/__init__.py` diff --git a/enhancer/utils/__init__.py` b/enhancer/utils/__init__.py` deleted file mode 100644 index e69de29..0000000 From c34746ac098559bf37719beff360d76fd66c6263 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 3 Sep 2022 10:48:59 +0530 Subject: [PATCH 023/375] refactor dataset --- enhancer/data/dataset.py | 152 ++++++++++++++++++++++++++------------- 1 file changed, 103 insertions(+), 49 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 2807aed..adcdea1 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,95 +1,149 @@ +from dataclasses import dataclass import glob import math import os +from typing_extensions import dataclass_transform import pytorch_lightning as pl -from torch.utils.data import IterableDataset, DataLoader +from torch.utils.data import IterableDataset, DataLoader, Dataset import torch.nn.functional as F from typing import Optional from enhancer.utils.random import create_unique_rng from enhancer.utils.io import Audio -from enhancer.utils import Fileprocessor +from enhancer.utils import Fileprocessor, check_files from enhancer.utils.config import Files +class TrainDataset(IterableDataset): + + def __init__(self,dataset): + self.dataset = dataset + def __iter__(self): + return self.dataset.train__iter__() -class EnhancerDataset(IterableDataset): + def __len__(self): + return self.dataset.train__len__() + +class ValidDataset(Dataset): + + def __init__(self,dataset): + self.dataset = dataset + + def __getitem__(self,idx): + return self.dataset.val__getitem__(idx) + + def __len__(self): + return self.dataset.val__len__() + +class TaskDataset(pl.LightningDataModule): + + def __init__( + self, + name:str, + root_dir:str, + files:Files, + duration:float=1.0, + sampling_rate:int=48000, + matching_function = None, + batch_size=32): + super().__init__() + + self.name = name + self.files,self.root_dir = check_files(root_dir,files) + self.duration = duration + self.sampling_rate = sampling_rate + self.batch_size = batch_size + self.matching_function = matching_function + + def setup(self, stage: Optional[str] = None): + + if stage in ("fit",None): + + fp = Fileprocessor.from_name(self.name,self.files.train_clean,self.files.train_noisy,self.matching_function) + self.train_data = fp.prepare_matching_dict() + + fp = Fileprocessor.from_name(self.name,self.files.test_clean,self.files.test_noisy,self.matching_function) + val_data = fp.prepare_matching_dict() + + for item in val_data: + clean,noisy,total_dur = item.values() + if total_dur < self.duration: + continue + num_segments = round(total_dur/self.duration) + for index in range(num_segments): + start_time = index * self.duration + self._validation.append(({"clean_file":clean,"noisy_file":noisy}, + start_time)) + def train_dataloader(self): + return DataLoader(TrainDataset(self), batch_size = self.batch_size) + + def val_dataloader(self): + return DataLoader(ValidDataset(self), batch_size = self.batch_size) + +class EnhancerDataset(TaskDataset): """Dataset object for creating clean-noisy speech enhancement datasets""" - def __init__(self,name:str,clean_dir,noisy_dir,duration=1.0,sampling_rate=48000, matching_function=None): + def __init__( + self, + name:str, + root_dir:str, + files:Files, + duration=1.0, + sampling_rate=48000, + matching_function=None, + batch_size=32): - if not os.path.isdir(clean_dir): - raise ValueError(f"{clean_dir} is not a valid directory") + super().__init__( + name=name, + root_dir=root_dir, + files=files, + sampling_rate=sampling_rate, + duration=duration, + matching_function = matching_function, + batch_size=batch_size - if not os.path.isdir(noisy_dir): - raise ValueError(f"{clean_dir} is not a valid directory") + ) self.sampling_rate = sampling_rate - self.clean_dir = clean_dir - self.noisy_dir = noisy_dir + self.files = files self.duration = max(1.0,duration) self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) - fp = Fileprocessor.from_name(name,clean_dir,noisy_dir,matching_function) - self.valid_files = fp.prepare_matching_dict() + def setup(self, stage:Optional[str]=None): + + super().setup(stage=stage) - def __iter__(self): + def train__iter__(self): rng = create_unique_rng(12) ##pass epoch number here while True: - file_dict,*_ = rng.choices(self.valid_files,k=1, - weights=[self.valid_files[file]['duration'] for file in self.valid_files]) + file_dict,*_ = rng.choices(self.train_data,k=1, + weights=[file["duration"] for file in self.train_data]) file_duration = file_dict['duration'] start_time = round(rng.uniform(0,file_duration- self.duration),2) data = self.prepare_segment(file_dict,start_time) yield data + def val__getitem__(self,idx): + return self.prepare_segment(*self._validation[idx]) + def prepare_segment(self,file_dict:dict, start_time:float): - clean_segment = self.audio(file_dict.keys()[0], + clean_segment = self.audio(file_dict["clean"], offset=start_time,duration=self.duration) - noisy_segment = self.audio(file_dict['noisy'], + noisy_segment = self.audio(file_dict["noisy"], offset=start_time,duration=self.duration) clean_segment = F.pad(clean_segment,(0,int(self.duration*self.sampling_rate-clean_segment.shape[-1]))) noisy_segment = F.pad(noisy_segment,(0,int(self.duration*self.sampling_rate-noisy_segment.shape[-1]))) return {"clean": clean_segment,"noisy":noisy_segment} - def __len__(self): - + def train__len__(self): return math.ceil(sum([file["duration"] for file in self.valid_files])/self.duration) - - -class Dataset(pl.LightningDataModule): - - def __init__(self,name:str,root_dir:str, files:Files, - duration:float=1.0, sampling_rate:int=48000, batch_size=32): - super().__init__() - - self.train_clean = os.path.join(root_dir, files.train_clean) - self.train_noisy = os.path.join(root_dir,files.train_noisy) - self.valid_clean = os.path.join(root_dir,files.test_clean) - self.valid_noisy = os.path.join(root_dir,files.test_noisy) - self.name = name - self.duration = duration - self.sampling_rate = sampling_rate - self.batch_size = batch_size - - def setup(self, stage: Optional[str] = None): - - if stage in (None,"fit"): - self.train_dataset = EnhancerDataset(self.name, self.train_clean, - self.train_noisy, self.duration, self.sampling_rate) - - self.valid_dataset = EnhancerDataset(self.name, self.valid_clean, - self.valid_noisy, self.duration, self.sampling_rate) - - def train_dataloader(self): - return DataLoader(self.train_dataset, batch_size = self.batch_size) + def val__len__(self): + return len(self._validation) - def valid_dataloader(self): - return DataLoader(self.valid_dataset, batch_size = self.batch_size) \ No newline at end of file From afdf42f05a66fd16df651a3411a929c623e5246f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 3 Sep 2022 10:49:16 +0530 Subject: [PATCH 024/375] return root_dir --- enhancer/utils/utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 enhancer/utils/utils.py diff --git a/enhancer/utils/utils.py b/enhancer/utils/utils.py new file mode 100644 index 0000000..24dd77a --- /dev/null +++ b/enhancer/utils/utils.py @@ -0,0 +1,12 @@ +import os +from enhancer.utils.config import Files + +def check_files(root_dir:str, files:Files): + + path_variables = [member_var for member_var in dir(files) if not member_var.startswith('__')] + for variable in path_variables: + path = getattr(files,variable) + if not os.path.isdir(os.path.join(root_dir,path)): + raise ValueError(f"Invalid {path}, is not a directory") + + return files,root_dir \ No newline at end of file From 7a112786b4e446f60e354eee22dcbb2186b71312 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 3 Sep 2022 10:49:43 +0530 Subject: [PATCH 025/375] fileprocessor --- enhancer/utils/fileprocessor.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/enhancer/utils/fileprocessor.py b/enhancer/utils/fileprocessor.py index e7cd033..6934750 100644 --- a/enhancer/utils/fileprocessor.py +++ b/enhancer/utils/fileprocessor.py @@ -43,7 +43,7 @@ class Fileprocessor: @staticmethod def match_vtck(clean_path,noisy_path,sr): - matching_wavfiles = dict() + matching_wavfiles = list() clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(noisy_path,"*.wav"))] common_filenames = np.intersect1d(noisy_filenames,clean_filenames) @@ -55,10 +55,9 @@ class Fileprocessor: if ((clean_file.shape[-1]==noisy_file.shape[-1]) and (sr_clean==sr) and (sr_noisy==sr)): - matching_wavfiles.update( - {os.path.join(clean_path,file_name):{"noisy":os.path.join(noisy_path,file_name), + matching_wavfiles.append( + {"clean":os.path.join(clean_path,file_name),"noisy":os.path.join(noisy_path,file_name), "duration":clean_file.shape[-1]/sr} - } ) return matching_wavfiles @@ -77,9 +76,8 @@ class Fileprocessor: (sr_clean==sr) and (sr_noisy==sr)): matching_wavfiles.update( - {os.path.join(clean_path,clean_file):{"noisy":noisy_file, + {"clean":os.path.join(clean_path,clean_file),"noisy":noisy_file, "duration":clean_file.shape[-1]/sr} - } ) return matching_wavfiles From b6d4bb52b49f15c32f823f1622613ad69b8dbcee Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 5 Sep 2022 14:52:53 +0530 Subject: [PATCH 026/375] models --- enhancer/models/__init__.py | 0 enhancer/models/demucs.py | 91 +++++++++++++++++++++++++++++++++++++ enhancer/models/model.py | 43 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 enhancer/models/__init__.py create mode 100644 enhancer/models/demucs.py create mode 100644 enhancer/models/model.py diff --git a/enhancer/models/__init__.py b/enhancer/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py new file mode 100644 index 0000000..c1b2307 --- /dev/null +++ b/enhancer/models/demucs.py @@ -0,0 +1,91 @@ +from typing import bool +from torch import nn + +class DeLSTM(nn.Module): + def __init__( + self, + input_size:int, + hidden_size:int, + num_layers:int, + bidirectional:bool=True + + ): + self.lstm = nn.LSTM(input_size, hidden_size, num_layers, bidirectional=bidirectional) + dim = 2 if bidirectional else 1 + self.linear = nn.Linear(dim*hidden_size,hidden_size) + + def forward(self,x): + + output,(h,c) = self.lstm(x) + output = self.linear(output) + + return output + +class Demus(nn.Module): + + def __init__( + self, + c_in:int=1, + c_out:int=1, + hidden:int=48, + kernel_size:int=8, + stride:int=4, + growth_factor:int=2, + depth:int = 6, + glu:bool = True, + bidirectional:bool=True, + resample:int=2, + + ): + self.c_in = c_in + self.c_out = c_out + self.hidden = hidden + self.growth_factor = growth_factor + self.stride = stride + self.kernel_size = kernel_size + self.depth = depth + self.bidirectional = bidirectional + self.activation = nn.GLU(1) if glu else nn.ReLU() + multi_factor = 2 if glu else 1 + + ## do resampling + + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + + for layer in range(self.depth): + + encoder_layer = [nn.Conv1d(c_in,hidden,kernel_size,stride), + nn.ReLU(), + nn.Conv1d(hidden, hidden*multi_factor,kernel_size,1), + self.activation] + encoder_layer = nn.Sequential(encoder_layer) + self.encoder.append(*encoder_layer) + + decoder_layer = [nn.Conv1d(hidden,hidden*multi_factor,kernel_size,1), + self.activation, + nn.ConvTranspose1d(hidden,c_out,kernel_size,stride) + ] + if layer>0: + decoder_layer.append(nn.ReLU()) + decoder_layer = nn.Sequential(*decoder_layer) + self.decoder.insert(0,decoder_layer) + + c_out = hidden + c_in = hidden + hidden = self.growth_factor * hidden + + + self.de_lstm = DeLSTM(input_size=c_in,hidden_size=c_in,num_layers=2,bidirectional=self.bidirectional) + + def forward(self,input): + pass + + + + + + + + + \ No newline at end of file diff --git a/enhancer/models/model.py b/enhancer/models/model.py new file mode 100644 index 0000000..c7bfe30 --- /dev/null +++ b/enhancer/models/model.py @@ -0,0 +1,43 @@ +from typing import Optional +import pytorch_lightning as pl + +from enhancer.data.dataset import Dataset + + +class Model(pl.LightningModule): + + def __init__( + self, + dataset:Dataset + ): + super().__init__() + self.dataset = dataset + + pass + + @property + def dataset(self): + return self._dataset + + @dataset.setter + def dataset(self,dataset): + self._dataset = dataset + + def setup( + self, + stage:Optional[str]=None + ): + if stage == "fit": + self.dataset.setup(stage) + self.dataset.model = self + + + def train_dataloader( + self + ): + return self.dataset.train_dataloader() + + def val_dataloader( + self + ): + return self.dataset.val_dataloader() From 21b837b48f601621e3445319110d447b4565e72a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 5 Sep 2022 15:49:20 +0530 Subject: [PATCH 027/375] relative imports --- enhancer/utils/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py index 3d9d78a..1971db9 100644 --- a/enhancer/utils/__init__.py +++ b/enhancer/utils/__init__.py @@ -1 +1,2 @@ -from enhancer.utils.fileprocessor import Fileprocessor \ No newline at end of file +from enhancer.utils.fileprocessor import Fileprocessor +from enhancer.utils.utils import check_files \ No newline at end of file From 9df1dafccfd67b88f30ec6d66dc77f1ed2394185 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 5 Sep 2022 15:49:39 +0530 Subject: [PATCH 028/375] demucs implementation --- enhancer/models/demucs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index c1b2307..7ec9989 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -79,8 +79,8 @@ class Demus(nn.Module): self.de_lstm = DeLSTM(input_size=c_in,hidden_size=c_in,num_layers=2,bidirectional=self.bidirectional) def forward(self,input): - pass - + + From 409afc31fc6b0b22f26a046eb5ce6d58f0d83dc6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 5 Sep 2022 17:12:03 +0530 Subject: [PATCH 029/375] demucs forward --- enhancer/models/demucs.py | 55 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 7ec9989..618e9ab 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,5 +1,9 @@ from typing import bool -from torch import nn +from torch import nn +import torch.functional as F +import math + +from enhancer.utils.io import Audio as audio class DeLSTM(nn.Module): def __init__( @@ -35,8 +39,10 @@ class Demus(nn.Module): glu:bool = True, bidirectional:bool=True, resample:int=2, + sampling_rate = 16000 ): + super().__init__() self.c_in = c_in self.c_out = c_out self.hidden = hidden @@ -46,10 +52,10 @@ class Demus(nn.Module): self.depth = depth self.bidirectional = bidirectional self.activation = nn.GLU(1) if glu else nn.ReLU() + self.resample = resample + self.sampling_rate = sampling_rate multi_factor = 2 if glu else 1 - ## do resampling - self.encoder = nn.ModuleList() self.decoder = nn.ModuleList() @@ -78,7 +84,48 @@ class Demus(nn.Module): self.de_lstm = DeLSTM(input_size=c_in,hidden_size=c_in,num_layers=2,bidirectional=self.bidirectional) - def forward(self,input): + def forward(self,mixed_signal): + + length = mixed_signal.shape[-1] + x = F.pad((0,self.get_padding_length(length) - length)) + if self.resample>1: + x = audio.resample_audio(audio=x, + sampling_rate = int(self.sampling_rate * self.resample)) + + encoder_outputs = [] + for encoder in self.encoder: + x = encoder(x) + encoder_outputs.append(x) + + x,_ = self.de_lstm(x) + + for decoder in self.decoder: + skip_connection = encoder_outputs.pop(-1) + x += skip_connection[..., :x.shape[-1]] + x = decoder(x) + + if self.resample > 1: + x = audio.resample_audio(x,int(self.sampling_rate * self.resample), + self.sampling_rate) + + return x + + def get_padding_length(self,input_length): + + input_length = math.ceil(input_length * self.resample) + + + for layer in range(self.depth): # encoder operation + input_length = math.ceil((input_length - self.kernel_size)/self.stride)+1 + input_length = max(1,input_length) + for layer in range(self.depth): # decoder operaration + input_length = (input_length-1) * self.stride + self.kernel_size + input_length = math.ceil(input_length/self.resample) + + return int(input_length) + + + From 8a43354cb079d998c836ec95d4f04ede94cd1481 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 5 Sep 2022 17:12:20 +0530 Subject: [PATCH 030/375] make resample static --- enhancer/utils/io.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 04015e6..be59e73 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -42,7 +42,7 @@ class Audio: if self.mono: audio = self.convert_mono(audio) - resampled_audio = self.resample_audio(audio,sampling_rate) + resampled_audio = self.__class__.resample_audio(audio,self.sampling_rate,sampling_rate) if self.return_tensor: return torch.tensor(resampled_audio) else: @@ -59,12 +59,13 @@ class Audio: return audio + @staticmethod def resample_audio( - self, audio, - sampling_rate + sr:int, + target_sr:int ): - if self.sampling_rate!=sampling_rate: - audio = librosa.resample(audio,orig_sr=sampling_rate,target_sr=self.sampling_rate) + if sr!=target_sr: + audio = librosa.resample(audio,orig_sr=sr,target_sr=target_sr) return audio \ No newline at end of file From b42ca28851f172797b28d83dc6f9c26e34b2d4a1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 6 Sep 2022 20:44:19 +0530 Subject: [PATCH 031/375] fix shapes --- enhancer/models/demucs.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 618e9ab..b18a7af 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,6 +1,5 @@ -from typing import bool from torch import nn -import torch.functional as F +import torch.nn.functional as F import math from enhancer.utils.io import Audio as audio @@ -14,6 +13,7 @@ class DeLSTM(nn.Module): bidirectional:bool=True ): + super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, bidirectional=bidirectional) dim = 2 if bidirectional else 1 self.linear = nn.Linear(dim*hidden_size,hidden_size) @@ -25,7 +25,7 @@ class DeLSTM(nn.Module): return output -class Demus(nn.Module): +class Demucs(nn.Module): def __init__( self, @@ -35,10 +35,10 @@ class Demus(nn.Module): kernel_size:int=8, stride:int=4, growth_factor:int=2, - depth:int = 6, + depth:int = 5, glu:bool = True, bidirectional:bool=True, - resample:int=2, + resample:int=4, sampling_rate = 16000 ): @@ -65,8 +65,8 @@ class Demus(nn.Module): nn.ReLU(), nn.Conv1d(hidden, hidden*multi_factor,kernel_size,1), self.activation] - encoder_layer = nn.Sequential(encoder_layer) - self.encoder.append(*encoder_layer) + encoder_layer = nn.Sequential(*encoder_layer) + self.encoder.append(encoder_layer) decoder_layer = [nn.Conv1d(hidden,hidden*multi_factor,kernel_size,1), self.activation, @@ -87,25 +87,27 @@ class Demus(nn.Module): def forward(self,mixed_signal): length = mixed_signal.shape[-1] - x = F.pad((0,self.get_padding_length(length) - length)) + x = F.pad(mixed_signal, (0,self.get_padding_length(length) - length)) if self.resample>1: - x = audio.resample_audio(audio=x, - sampling_rate = int(self.sampling_rate * self.resample)) - + x = audio.pt_resample_audio(audio=x, sr=self.sampling_rate, + target_sr=int(self.sampling_rate * self.resample)) + print("resampled->",x.shape) encoder_outputs = [] for encoder in self.encoder: x = encoder(x) + print(x.shape) encoder_outputs.append(x) - - x,_ = self.de_lstm(x) + x = x.permute(0,2,1) + x = self.de_lstm(x) + x = x.permute(0,2,1) for decoder in self.decoder: skip_connection = encoder_outputs.pop(-1) x += skip_connection[..., :x.shape[-1]] x = decoder(x) if self.resample > 1: - x = audio.resample_audio(x,int(self.sampling_rate * self.resample), + x = audio.pt_resample_audio(x,int(self.sampling_rate * self.resample), self.sampling_rate) return x From 1a64d017b76907aacb3d088f66e75e76d0f51c92 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 6 Sep 2022 20:44:42 +0530 Subject: [PATCH 032/375] torch resample --- enhancer/utils/io.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index be59e73..023a57c 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -4,6 +4,7 @@ from typing import Optional from matplotlib.pyplot import axis import numpy as np import torch +import torchaudio class Audio: @@ -68,4 +69,15 @@ class Audio: if sr!=target_sr: audio = librosa.resample(audio,orig_sr=sr,target_sr=target_sr) + return audio + + @staticmethod + def pt_resample_audio( + audio, + sr:int, + target_sr:int + ): + if sr!=target_sr: + audio = torchaudio.functional.resample(audio,orig_freq=sr,new_freq=target_sr) + return audio \ No newline at end of file From fb6d0afee0df2a733483f24c72e0e7b01d70b994 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 6 Sep 2022 20:45:02 +0530 Subject: [PATCH 033/375] relative imports --- enhancer/models/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/models/__init__.py b/enhancer/models/__init__.py index e69de29..dd12bc3 100644 --- a/enhancer/models/__init__.py +++ b/enhancer/models/__init__.py @@ -0,0 +1 @@ +from enhancer.models.demucs import Demucs \ No newline at end of file From 4edc90deb0f36a17404b2e78ed4faf210ed7c9d0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 7 Sep 2022 10:02:56 +0530 Subject: [PATCH 034/375] return hidden & cell states --- enhancer/models/demucs.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index b18a7af..77e5558 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -23,7 +23,7 @@ class DeLSTM(nn.Module): output,(h,c) = self.lstm(x) output = self.linear(output) - return output + return output,(h,c) class Demucs(nn.Module): @@ -85,20 +85,23 @@ class Demucs(nn.Module): self.de_lstm = DeLSTM(input_size=c_in,hidden_size=c_in,num_layers=2,bidirectional=self.bidirectional) def forward(self,mixed_signal): - + + if mixed_signal.dim() == 2: + mixed_signal = mixed_signal.unsqueeze(1) + length = mixed_signal.shape[-1] x = F.pad(mixed_signal, (0,self.get_padding_length(length) - length)) if self.resample>1: x = audio.pt_resample_audio(audio=x, sr=self.sampling_rate, target_sr=int(self.sampling_rate * self.resample)) - print("resampled->",x.shape) + encoder_outputs = [] for encoder in self.encoder: x = encoder(x) print(x.shape) encoder_outputs.append(x) x = x.permute(0,2,1) - x = self.de_lstm(x) + x,_ = self.de_lstm(x) x = x.permute(0,2,1) for decoder in self.decoder: From 79311444d8c7eb617b1ff7286c208b0f0f8edff4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 7 Sep 2022 13:10:30 +0530 Subject: [PATCH 035/375] refactor arguments --- enhancer/models/demucs.py | 97 +++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 77e5558..ed7e38f 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,8 +1,12 @@ +from typing import Optional from torch import nn import torch.nn.functional as F import math +from enhancer.models.model import Model +from enhancer.data.dataset import EnhancerDataset from enhancer.utils.io import Audio as audio +from enhancer.utils.utils import merge_dict class DeLSTM(nn.Module): def __init__( @@ -25,64 +29,68 @@ class DeLSTM(nn.Module): return output,(h,c) -class Demucs(nn.Module): +class Demucs(Model): + + ED_DEFAULTS = { + "intial_output_channels":48, + "kernel_size":8, + "stride":1, + "depth":5, + "glu":True, + "growth_factor":2, + } + LSTM_DEFAULTS = { + "bidirectional":True, + "num_layers":2, + } def __init__( self, - c_in:int=1, - c_out:int=1, - hidden:int=48, - kernel_size:int=8, - stride:int=4, - growth_factor:int=2, - depth:int = 5, - glu:bool = True, - bidirectional:bool=True, + encoder_decoder:Optional[dict]=None, + lstm:Optional[dict]=None, + num_channels:int=1, resample:int=4, - sampling_rate = 16000 + sampling_rate = 16000, + dataset:Optional[EnhancerDataset]=None, ): - super().__init__() - self.c_in = c_in - self.c_out = c_out - self.hidden = hidden - self.growth_factor = growth_factor - self.stride = stride - self.kernel_size = kernel_size - self.depth = depth - self.bidirectional = bidirectional - self.activation = nn.GLU(1) if glu else nn.ReLU() - self.resample = resample - self.sampling_rate = sampling_rate - multi_factor = 2 if glu else 1 + super().__init__(num_channels=num_channels, + sampling_rate=sampling_rate,dataset=dataset) + + encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) + lstm = merge_dict(self.LSTM_DEFAULTS,lstm) + self.save_hyperparameters("encoder_decoder","lstm","resample") + + hidden = encoder_decoder["initial_channel_output"] + activation = nn.GLU(1) if encoder_decoder["glu"] else nn.ReLU() + multi_factor = 2 if encoder_decoder["glu"] else 1 self.encoder = nn.ModuleList() self.decoder = nn.ModuleList() - for layer in range(self.depth): + for layer in range(encoder_decoder["depth"]): - encoder_layer = [nn.Conv1d(c_in,hidden,kernel_size,stride), + encoder_layer = [nn.Conv1d(num_channels,hidden,encoder_decoder["kernel_size"],encoder_decoder["stride"]), nn.ReLU(), - nn.Conv1d(hidden, hidden*multi_factor,kernel_size,1), - self.activation] + nn.Conv1d(hidden, hidden*multi_factor,encoder_decoder["kernel_size"],1), + activation] encoder_layer = nn.Sequential(*encoder_layer) self.encoder.append(encoder_layer) - decoder_layer = [nn.Conv1d(hidden,hidden*multi_factor,kernel_size,1), - self.activation, - nn.ConvTranspose1d(hidden,c_out,kernel_size,stride) + decoder_layer = [nn.Conv1d(hidden,hidden*multi_factor,encoder_decoder["kernel_size"],1), + activation, + nn.ConvTranspose1d(hidden,num_channels,encoder_decoder["kernel_size"],encoder_decoder["stride"]) ] if layer>0: decoder_layer.append(nn.ReLU()) decoder_layer = nn.Sequential(*decoder_layer) self.decoder.insert(0,decoder_layer) - c_out = hidden - c_in = hidden + num_channels = hidden hidden = self.growth_factor * hidden - self.de_lstm = DeLSTM(input_size=c_in,hidden_size=c_in,num_layers=2,bidirectional=self.bidirectional) + self.de_lstm = DeLSTM(input_size=num_channels,hidden_size=num_channels,num_layers=lstm["num_layers"],bidirectional=lstm["bidirectional"]) def forward(self,mixed_signal): @@ -91,14 +99,13 @@ class Demucs(nn.Module): length = mixed_signal.shape[-1] x = F.pad(mixed_signal, (0,self.get_padding_length(length) - length)) - if self.resample>1: - x = audio.pt_resample_audio(audio=x, sr=self.sampling_rate, - target_sr=int(self.sampling_rate * self.resample)) + if self.hparams.resample>1: + x = audio.pt_resample_audio(audio=x, sr=self.hparams.sampling_rate, + target_sr=int(self.hparams.sampling_rate * self.hparams.resample)) encoder_outputs = [] for encoder in self.encoder: x = encoder(x) - print(x.shape) encoder_outputs.append(x) x = x.permute(0,2,1) x,_ = self.de_lstm(x) @@ -109,23 +116,23 @@ class Demucs(nn.Module): x += skip_connection[..., :x.shape[-1]] x = decoder(x) - if self.resample > 1: - x = audio.pt_resample_audio(x,int(self.sampling_rate * self.resample), - self.sampling_rate) + if self.hparams.resample > 1: + x = audio.pt_resample_audio(x,int(self.hparams.sampling_rate * self.hparams.resample), + self.hparams.sampling_rate) return x def get_padding_length(self,input_length): - input_length = math.ceil(input_length * self.resample) + input_length = math.ceil(input_length * self.hparams.resample) - for layer in range(self.depth): # encoder operation + for layer in range(self.hparams.encoder_decoder["depth"]): # encoder operation input_length = math.ceil((input_length - self.kernel_size)/self.stride)+1 input_length = max(1,input_length) - for layer in range(self.depth): # decoder operaration + for layer in range(self.hparams.encoder_decoder["depth"]): # decoder operaration input_length = (input_length-1) * self.stride + self.kernel_size - input_length = math.ceil(input_length/self.resample) + input_length = math.ceil(input_length/self.hparams.resample) return int(input_length) From 05d75c04858d6ce4eece9409aebe6562ead0c3af Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 7 Sep 2022 13:10:49 +0530 Subject: [PATCH 036/375] merge dicts --- enhancer/utils/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/enhancer/utils/utils.py b/enhancer/utils/utils.py index 24dd77a..be74dc2 100644 --- a/enhancer/utils/utils.py +++ b/enhancer/utils/utils.py @@ -1,4 +1,6 @@ + import os +from typing import Optional from enhancer.utils.config import Files def check_files(root_dir:str, files:Files): @@ -9,4 +11,10 @@ def check_files(root_dir:str, files:Files): if not os.path.isdir(os.path.join(root_dir,path)): raise ValueError(f"Invalid {path}, is not a directory") - return files,root_dir \ No newline at end of file + return files,root_dir + +def merge_dict(default_dict:dict, custom:Optional[dict]=None): + params = dict(default_dict) + if custom: + params.update(custom) + return params From 8230061b7bd60d2cd9d0b648ef453efc2c367e2c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 8 Sep 2022 11:34:36 +0530 Subject: [PATCH 037/375] include lr --- enhancer/models/demucs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index ed7e38f..bcc8214 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -51,11 +51,12 @@ class Demucs(Model): num_channels:int=1, resample:int=4, sampling_rate = 16000, + lr:float=1e-3, dataset:Optional[EnhancerDataset]=None, ): super().__init__(num_channels=num_channels, - sampling_rate=sampling_rate,dataset=dataset) + sampling_rate=sampling_rate,lr=lr,dataset=dataset) encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) lstm = merge_dict(self.LSTM_DEFAULTS,lstm) From 4cb417cdbeafafe2ec5df6f3dcd9683613ef7524 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 8 Sep 2022 11:35:10 +0530 Subject: [PATCH 038/375] configure base class --- enhancer/models/model.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index c7bfe30..a5b305f 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,4 +1,5 @@ from typing import Optional +from torch.optim import Adam import pytorch_lightning as pl from enhancer.data.dataset import Dataset @@ -8,12 +9,16 @@ class Model(pl.LightningModule): def __init__( self, - dataset:Dataset + num_channels:int=1, + sampling_rate:int=16000, + lr:float=1e-3, + dataset:Optional[Dataset]=None, ): super().__init__() + assert num_channels ==1 , "Enhancer only support for mono channel models" + self.save_hyperparameters("num_channels","sampling_rate","lr") self.dataset = dataset - pass @property def dataset(self): @@ -23,21 +28,24 @@ class Model(pl.LightningModule): def dataset(self,dataset): self._dataset = dataset - def setup( - self, - stage:Optional[str]=None - ): + def setup(self,stage:Optional[str]=None): if stage == "fit": self.dataset.setup(stage) self.dataset.model = self - def train_dataloader( - self - ): + def train_dataloader(self): return self.dataset.train_dataloader() - def val_dataloader( - self - ): + def val_dataloader(self): return self.dataset.val_dataloader() + + def configure_optimizers(self): + return Adam(self.parameters, lr = self.hparams.lr) + + def training_step(self,batch, batch_idx:int): + pass + + @classmethod + def from_pretrained(cls,): + pass \ No newline at end of file From 4dbefd51b38ca3e4678951d412ce766515b00bdf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 9 Sep 2022 09:45:36 +0530 Subject: [PATCH 039/375] mae/mse loss --- enhancer/utils/loss.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 enhancer/utils/loss.py diff --git a/enhancer/utils/loss.py b/enhancer/utils/loss.py new file mode 100644 index 0000000..b9c8bee --- /dev/null +++ b/enhancer/utils/loss.py @@ -0,0 +1,28 @@ +import torch +import torch.nn as nn + + +class mean_squared_error(nn.Module): + + def __init__(self,reduction="mean"): + super().__init__() + + self.loss_fun = nn.MSELoss(reduction=reduction) + + def forward(self,prediction:torch.Tensor, target: torch.Tensor): + + return self.loss_fun(prediction, target) + +class mean_absolute_error(nn.Module): + + def __init__(self,reduction="mean"): + + self.loss_fun = nn.L1Loss(reduction=reduction) + + def forward(self, prediction:torch.Tensor, target: torch.Tensor): + + return self.loss_fun(prediction, target) + +LOSS_MAP = {"mea":mean_absolute_error, "mse": mean_squared_error} + + From 1288565cff646b068c6e7749d2c4104beb048b4a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 10 Sep 2022 11:42:04 +0530 Subject: [PATCH 040/375] pass loss as arg --- enhancer/models/demucs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index bcc8214..6172714 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Union, List from torch import nn import torch.nn.functional as F import math @@ -53,10 +53,11 @@ class Demucs(Model): sampling_rate = 16000, lr:float=1e-3, dataset:Optional[EnhancerDataset]=None, + loss:Union[str, List] = "mse" ): super().__init__(num_channels=num_channels, - sampling_rate=sampling_rate,lr=lr,dataset=dataset) + sampling_rate=sampling_rate,lr=lr,dataset=dataset,loss) encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) lstm = merge_dict(self.LSTM_DEFAULTS,lstm) From f8c8884ce9f6413b27967c1392ba6381a9d671df Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 10 Sep 2022 11:42:14 +0530 Subject: [PATCH 041/375] average loss --- enhancer/utils/loss.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/enhancer/utils/loss.py b/enhancer/utils/loss.py index b9c8bee..c410914 100644 --- a/enhancer/utils/loss.py +++ b/enhancer/utils/loss.py @@ -1,3 +1,4 @@ +from turtle import forward import torch import torch.nn as nn @@ -22,7 +23,33 @@ class mean_absolute_error(nn.Module): def forward(self, prediction:torch.Tensor, target: torch.Tensor): return self.loss_fun(prediction, target) + +class Avergeloss(nn.Module): + + def __init__(self,losses): + + self.valid_losses = nn.ModuleList() + for loss in losses: + loss = self.validate_loss(loss) + self.valid_losses.append(loss()) + + + def validate_loss(self,loss:str): + if loss not in LOSS_MAP.keys(): + raise ValueError() + else: + return LOSS_MAP[loss] + + def forward(self,prediction:torch.Tensor, target:torch.Tensor): + loss = 0.0 + for loss_fun in self.valid_losses: + loss += loss_fun(prediction, target) + return loss + + + + LOSS_MAP = {"mea":mean_absolute_error, "mse": mean_squared_error} From 619d0be8ce87291f3f4feb5829a5f6221db6d8b4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 10 Sep 2022 11:42:29 +0530 Subject: [PATCH 042/375] set training step --- enhancer/models/model.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index a5b305f..f8dd502 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,8 +1,9 @@ -from typing import Optional +from typing import Optional, Union, List from torch.optim import Adam import pytorch_lightning as pl from enhancer.data.dataset import Dataset +from enhancer.utils.loss import LOSS_MAP, Avergeloss class Model(pl.LightningModule): @@ -13,10 +14,11 @@ class Model(pl.LightningModule): sampling_rate:int=16000, lr:float=1e-3, dataset:Optional[Dataset]=None, + loss: Union[str, List] = "mse" ): super().__init__() assert num_channels ==1 , "Enhancer only support for mono channel models" - self.save_hyperparameters("num_channels","sampling_rate","lr") + self.save_hyperparameters("num_channels","sampling_rate","lr","loss") self.dataset = dataset @@ -31,8 +33,16 @@ class Model(pl.LightningModule): def setup(self,stage:Optional[str]=None): if stage == "fit": self.dataset.setup(stage) - self.dataset.model = self + self.dataset.model = self + self.setup_loss() + def setup_loss(self): + + loss = self.hparams.loss + if isinstance(loss,str): + losses = [loss] + + self.loss = Avergeloss(losses) def train_dataloader(self): return self.dataset.train_dataloader() @@ -44,7 +54,15 @@ class Model(pl.LightningModule): return Adam(self.parameters, lr = self.hparams.lr) def training_step(self,batch, batch_idx:int): - pass + + mixed_waveform = batch["noisy"] + target = batch["clean"] + prediction = self(mixed_waveform) + + loss = self.loss(prediction, target) + + return {"loss":loss} + @classmethod def from_pretrained(cls,): From 05fe7ec317efd2be43697815244ccd151a2205c9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 10 Sep 2022 14:24:41 +0530 Subject: [PATCH 043/375] fix arguments --- enhancer/models/demucs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 6172714..ce9b8c5 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -57,7 +57,8 @@ class Demucs(Model): ): super().__init__(num_channels=num_channels, - sampling_rate=sampling_rate,lr=lr,dataset=dataset,loss) + sampling_rate=sampling_rate,lr=lr, + dataset=dataset,loss=loss) encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) lstm = merge_dict(self.LSTM_DEFAULTS,lstm) From c06566c1328c51efba663f02b77fff4876e4539a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 12 Sep 2022 10:54:36 +0530 Subject: [PATCH 044/375] debug model --- enhancer/models/demucs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index ce9b8c5..2ac067b 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -32,7 +32,7 @@ class DeLSTM(nn.Module): class Demucs(Model): ED_DEFAULTS = { - "intial_output_channels":48, + "initial_output_channels":48, "kernel_size":8, "stride":1, "depth":5, @@ -64,7 +64,7 @@ class Demucs(Model): lstm = merge_dict(self.LSTM_DEFAULTS,lstm) self.save_hyperparameters("encoder_decoder","lstm","resample") - hidden = encoder_decoder["initial_channel_output"] + hidden = encoder_decoder["initial_output_channels"] activation = nn.GLU(1) if encoder_decoder["glu"] else nn.ReLU() multi_factor = 2 if encoder_decoder["glu"] else 1 @@ -90,7 +90,7 @@ class Demucs(Model): self.decoder.insert(0,decoder_layer) num_channels = hidden - hidden = self.growth_factor * hidden + hidden = self.ED_DEFAULTS["growth_factor"] * hidden self.de_lstm = DeLSTM(input_size=num_channels,hidden_size=num_channels,num_layers=lstm["num_layers"],bidirectional=lstm["bidirectional"]) @@ -131,10 +131,10 @@ class Demucs(Model): for layer in range(self.hparams.encoder_decoder["depth"]): # encoder operation - input_length = math.ceil((input_length - self.kernel_size)/self.stride)+1 + input_length = math.ceil((input_length - self.hparams.encoder_decoder["kernel_size"])/self.hparams.encoder_decoder["stride"])+1 input_length = max(1,input_length) for layer in range(self.hparams.encoder_decoder["depth"]): # decoder operaration - input_length = (input_length-1) * self.stride + self.kernel_size + input_length = (input_length-1) * self.hparams.encoder_decoder["stride"] + self.hparams.encoder_decoder["kernel_size"] input_length = math.ceil(input_length/self.hparams.resample) return int(input_length) From 9ed1b9d3f717718e885e3bf05d1c48ab5a36bb4e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 12 Sep 2022 10:54:52 +0530 Subject: [PATCH 045/375] debug dataset --- enhancer/data/dataset.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index adcdea1..aa98219 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -3,7 +3,6 @@ from dataclasses import dataclass import glob import math import os -from typing_extensions import dataclass_transform import pytorch_lightning as pl from torch.utils.data import IterableDataset, DataLoader, Dataset import torch.nn.functional as F @@ -55,15 +54,24 @@ class TaskDataset(pl.LightningDataModule): self.sampling_rate = sampling_rate self.batch_size = batch_size self.matching_function = matching_function + self._validation = [] def setup(self, stage: Optional[str] = None): if stage in ("fit",None): - fp = Fileprocessor.from_name(self.name,self.files.train_clean,self.files.train_noisy,self.matching_function) + train_clean = os.path.join(self.root_dir,self.files.train_clean) + train_noisy = os.path.join(self.root_dir,self.files.train_noisy) + fp = Fileprocessor.from_name(self.name,train_clean, + train_noisy,self.sampling_rate, + self.matching_function) self.train_data = fp.prepare_matching_dict() - fp = Fileprocessor.from_name(self.name,self.files.test_clean,self.files.test_noisy,self.matching_function) + val_clean = os.path.join(self.root_dir,self.files.test_clean) + val_noisy = os.path.join(self.root_dir,self.files.test_noisy) + fp = Fileprocessor.from_name(self.name,val_clean, + val_noisy,self.sampling_rate, + self.matching_function) val_data = fp.prepare_matching_dict() for item in val_data: @@ -116,7 +124,7 @@ class EnhancerDataset(TaskDataset): def train__iter__(self): - rng = create_unique_rng(12) ##pass epoch number here + rng = create_unique_rng(self.model.current_epoch) while True: @@ -141,7 +149,7 @@ class EnhancerDataset(TaskDataset): return {"clean": clean_segment,"noisy":noisy_segment} def train__len__(self): - return math.ceil(sum([file["duration"] for file in self.valid_files])/self.duration) + return math.ceil(sum([file["duration"] for file in self.train_data])/self.duration) def val__len__(self): return len(self._validation) From 4536a82e90f2fed68e85fc60df7520866b4bf3fd Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 12 Sep 2022 10:55:24 +0530 Subject: [PATCH 046/375] remove unused imports --- enhancer/models/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index f8dd502..bdc49dd 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -3,7 +3,7 @@ from torch.optim import Adam import pytorch_lightning as pl from enhancer.data.dataset import Dataset -from enhancer.utils.loss import LOSS_MAP, Avergeloss +from enhancer.utils.loss import Avergeloss class Model(pl.LightningModule): @@ -20,7 +20,7 @@ class Model(pl.LightningModule): assert num_channels ==1 , "Enhancer only support for mono channel models" self.save_hyperparameters("num_channels","sampling_rate","lr","loss") self.dataset = dataset - + @property def dataset(self): @@ -51,7 +51,7 @@ class Model(pl.LightningModule): return self.dataset.val_dataloader() def configure_optimizers(self): - return Adam(self.parameters, lr = self.hparams.lr) + return Adam(self.parameters(), lr = self.hparams.lr) def training_step(self,batch, batch_idx:int): From 0e72bb3bb8f353008c22a231d09fe02d4ee3a93d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 12 Sep 2022 10:55:52 +0530 Subject: [PATCH 047/375] init super --- enhancer/utils/loss.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enhancer/utils/loss.py b/enhancer/utils/loss.py index c410914..a51669c 100644 --- a/enhancer/utils/loss.py +++ b/enhancer/utils/loss.py @@ -17,6 +17,7 @@ class mean_squared_error(nn.Module): class mean_absolute_error(nn.Module): def __init__(self,reduction="mean"): + super().__init__() self.loss_fun = nn.L1Loss(reduction=reduction) @@ -27,6 +28,7 @@ class mean_absolute_error(nn.Module): class Avergeloss(nn.Module): def __init__(self,losses): + super().__init__() self.valid_losses = nn.ModuleList() for loss in losses: From 22eb9256e20ff401f14fc79c25bf19c47dad026f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 12 Sep 2022 11:33:15 +0530 Subject: [PATCH 048/375] fix typo --- enhancer/data/dataset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index aa98219..797d691 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -81,13 +81,13 @@ class TaskDataset(pl.LightningDataModule): num_segments = round(total_dur/self.duration) for index in range(num_segments): start_time = index * self.duration - self._validation.append(({"clean_file":clean,"noisy_file":noisy}, + self._validation.append(({"clean":clean,"noisy":noisy}, start_time)) def train_dataloader(self): - return DataLoader(TrainDataset(self), batch_size = self.batch_size) + return DataLoader(TrainDataset(self), batch_size = self.batch_size,num_workers=2) def val_dataloader(self): - return DataLoader(ValidDataset(self), batch_size = self.batch_size) + return DataLoader(ValidDataset(self), batch_size = self.batch_size,num_workers=2) class EnhancerDataset(TaskDataset): """Dataset object for creating clean-noisy speech enhancement datasets""" From a1103310f2c814dee83cfd127bcfc053318bf83a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 12 Sep 2022 11:33:34 +0530 Subject: [PATCH 049/375] validation step --- enhancer/models/model.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index bdc49dd..2adfef8 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -14,11 +14,12 @@ class Model(pl.LightningModule): sampling_rate:int=16000, lr:float=1e-3, dataset:Optional[Dataset]=None, - loss: Union[str, List] = "mse" + loss: Union[str, List] = "mse", + metric:Union[str,List] = "mse" ): super().__init__() assert num_channels ==1 , "Enhancer only support for mono channel models" - self.save_hyperparameters("num_channels","sampling_rate","lr","loss") + self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric") self.dataset = dataset @@ -34,15 +35,15 @@ class Model(pl.LightningModule): if stage == "fit": self.dataset.setup(stage) self.dataset.model = self - self.setup_loss() + self.loss = self.setup_loss(self.hparams.loss) + self.metric = self.setup_loss(self.hparams.metric) - def setup_loss(self): + def setup_loss(self,loss): - loss = self.hparams.loss if isinstance(loss,str): losses = [loss] - self.loss = Avergeloss(losses) + return Avergeloss(losses) def train_dataloader(self): return self.dataset.train_dataloader() @@ -63,6 +64,15 @@ class Model(pl.LightningModule): return {"loss":loss} + def validation_step(self,batch,batch_idx:int): + + mixed_waveform = batch["noisy"] + target = batch["clean"] + prediction = self(mixed_waveform) + + loss = self.metric(prediction, target) + + return {"loss":loss} @classmethod def from_pretrained(cls,): From ca55df0fef42f3fd8da951d5f421b5d4866acf48 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 13 Sep 2022 17:21:38 +0530 Subject: [PATCH 050/375] hawk requirements --- environment.yml | 8 ++++++++ hpc_entrypoint.sh | 35 +++++++++++++++++++++++++++++++++++ setup.sh | 13 +++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 environment.yml create mode 100644 hpc_entrypoint.sh create mode 100644 setup.sh diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..4f211bf --- /dev/null +++ b/environment.yml @@ -0,0 +1,8 @@ +name: enhancer + +dependencies: + - pip=21.0.1 + - python=3.8 + - pip: + - -r requirements.txt + - --find-links https://download.pytorch.org/whl/cu113/torch_stable.html \ No newline at end of file diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh new file mode 100644 index 0000000..0b8be4b --- /dev/null +++ b/hpc_entrypoint.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e + +echo '----------------------------------------------------' +echo ' SLURM_CLUSTER_NAME = '$SLURM_CLUSTER_NAME +echo ' SLURMD_NODENAME = '$SLURMD_NODENAME +echo ' SLURM_JOBID = '$SLURM_JOBID +echo ' SLURM_JOB_USER = '$SLURM_JOB_USER +echo ' SLURM_PARTITION = '$SLURM_JOB_PARTITION +echo ' SLURM_JOB_ACCOUNT = '$SLURM_JOB_ACCOUNT +echo '----------------------------------------------------' + +#TeamCity Output +cat << EOF +##teamcity[buildNumber '$SLURM_JOBID'] +EOF + +echo "Load HPC modules" +module load anaconda + +echo "Activate Environment" +source activate deep-transcriber +export TRANSFORMERS_OFFLINE=True +export PYTHONPATH=${PYTHONPATH}:$/scratch/$USER/enhancer + +source ~/mlflow_settings.sh + +echo "Making temp dir" +mkdir temp + +#python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TRAIN --output ./data/train +#python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test + +echo "Start Training..." +python enhancer/main.py diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..adad46c --- /dev/null +++ b/setup.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +echo "Loading Anaconda Module" +module load anaconda + +echo "Creating Virtual Environment" +conda env create -f environment.yml || conda env update -f environment.yml + +source activate enhancer + +echo "copying files" +# cp /scratch/$USER/TIMIT/.* /deep-transcriber \ No newline at end of file From 35da955c073d19cfd958807b042f84681b3a5cc1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 13 Sep 2022 17:21:47 +0530 Subject: [PATCH 051/375] requirements --- requirements.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c279ff0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +joblib==1.1.0 +numpy==1.19.5 +librosa==0.9.1 +numpy==1.19.5 +hydra-core==1.2.0 +scikit-learn==0.24.2 +scipy==1.5.4 +torch==1.10.2 +tqdm==4.64.0 +mlflow==1.23.1 +protobuf==3.19.3 +boto3==1.23.9 +huggingface-hub==0.4.0 From e4cd6ef10e13080f71caf53bf4dda1994982f8d1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 13 Sep 2022 22:02:09 +0530 Subject: [PATCH 052/375] add typeerror in shape mismatch --- enhancer/utils/loss.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/enhancer/utils/loss.py b/enhancer/utils/loss.py index a51669c..7e555b5 100644 --- a/enhancer/utils/loss.py +++ b/enhancer/utils/loss.py @@ -11,6 +11,10 @@ class mean_squared_error(nn.Module): self.loss_fun = nn.MSELoss(reduction=reduction) def forward(self,prediction:torch.Tensor, target: torch.Tensor): + + if prediction.size() != target.size() or target.ndim < 3: + raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""") return self.loss_fun(prediction, target) @@ -23,6 +27,10 @@ class mean_absolute_error(nn.Module): def forward(self, prediction:torch.Tensor, target: torch.Tensor): + if prediction.size() != target.size() or target.ndim < 3: + raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""") + return self.loss_fun(prediction, target) class Avergeloss(nn.Module): From 9a3473ee8da3d8c994907a6450e6822eb01129d3 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 13 Sep 2022 22:17:49 +0530 Subject: [PATCH 053/375] tests loss --- tests/loss_function_test.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/loss_function_test.py diff --git a/tests/loss_function_test.py b/tests/loss_function_test.py new file mode 100644 index 0000000..f08ac65 --- /dev/null +++ b/tests/loss_function_test.py @@ -0,0 +1,31 @@ +from asyncio import base_tasks +import torch +import pytest + +from enhancer.utils.loss import mean_absolute_error, mean_squared_error + +loss_functions = [mean_absolute_error(), mean_squared_error()] + +def check_loss_shapes_compatibility(loss_fun): + + batch_size = 4 + shape = (1,1000) + loss_fun(torch.rand(batch_size,*shape),torch.rand(batch_size,*shape)) + + with pytest.raises(TypeError): + loss_fun(torch.rand(4,*shape),torch.rand(6,*shape)) + + +@pytest.mark.parametrize("loss",loss_functions) +def test_loss_input_shapes(loss): + check_loss_shapes_compatibility(loss) + +@pytest.mark.parametrize("loss",loss_functions) +def test_loss_output_shapes(loss): + + batch_size = 4 + prediction, target = torch.rand(batch_size,1,1000),torch.rand(batch_size,1,1000) + loss_value = loss(prediction, target) + assert isinstance(loss_value.item(),float) + + From 2d415407e93a78ae086b8d476dd2c34da3ecd3ef Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 14 Sep 2022 11:48:46 +0530 Subject: [PATCH 054/375] tests loss --- tests/loss_function_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/loss_function_test.py b/tests/loss_function_test.py index f08ac65..637d6f3 100644 --- a/tests/loss_function_test.py +++ b/tests/loss_function_test.py @@ -21,7 +21,7 @@ def test_loss_input_shapes(loss): check_loss_shapes_compatibility(loss) @pytest.mark.parametrize("loss",loss_functions) -def test_loss_output_shapes(loss): +def test_loss_output_type(loss): batch_size = 4 prediction, target = torch.rand(batch_size,1,1000),torch.rand(batch_size,1,1000) From 6ff1ca7597feb4fa42428ca75050bf0d53bb7bd6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 14 Sep 2022 11:49:17 +0530 Subject: [PATCH 055/375] tests models --- tests/models/demucs_test.py | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/models/demucs_test.py diff --git a/tests/models/demucs_test.py b/tests/models/demucs_test.py new file mode 100644 index 0000000..a59fa04 --- /dev/null +++ b/tests/models/demucs_test.py @@ -0,0 +1,46 @@ +import pytest +import torch +from enhancer import data + +from enhancer.utils.config import Files +from enhancer.models import Demucs +from enhancer.data.dataset import EnhancerDataset + + +@pytest.fixture +def vctk_dataset(): + root_dir = "tests/data/vctk" + files = Files(train_clean="clean_testset_wav",train_noisy="noisy_testset_wav", + test_clean="clean_testset_wav", test_noisy="noisy_testset_wav") + dataset = EnhancerDataset(name="vctk",root_dir=root_dir,files=files) + return dataset + + + +@pytest.mark.parametrize("batch_size,samples",[(1,1000)]) +def test_forward(batch_size,samples): + model = Demucs() + model.eval() + + data = torch.rand(batch_size,1,samples,requires_grad=False) + with torch.no_grad(): + _ = model(data) + + data = torch.rand(batch_size,2,samples,requires_grad=False) + with torch.no_grad(): + with pytest.raises(TypeError): + _ = model(data) + + +@pytest.mark.parametrize("dataset,channels,loss", + [(pytest.lazy_fixture("vctk_dataset"),1,["mae","mse"])]) +def test_demucs_init(dataset,channels,loss): + with torch.no_grad(): + model = Demucs(num_channels=channels,dataset=dataset,loss=loss) + + + + + + + From 783a4406091a057012bccf7ec2ace152c51b72a5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 14 Sep 2022 11:49:31 +0530 Subject: [PATCH 056/375] sample data --- tests/data/vctk/clean_testset_wav/p257_166.wav | Bin 0 -> 218528 bytes tests/data/vctk/clean_testset_wav/p257_167.wav | Bin 0 -> 260118 bytes tests/data/vctk/noisy_testset_wav/p257_166.wav | Bin 0 -> 218528 bytes tests/data/vctk/noisy_testset_wav/p257_167.wav | Bin 0 -> 260118 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/data/vctk/clean_testset_wav/p257_166.wav create mode 100644 tests/data/vctk/clean_testset_wav/p257_167.wav create mode 100644 tests/data/vctk/noisy_testset_wav/p257_166.wav create mode 100644 tests/data/vctk/noisy_testset_wav/p257_167.wav diff --git a/tests/data/vctk/clean_testset_wav/p257_166.wav b/tests/data/vctk/clean_testset_wav/p257_166.wav new file mode 100644 index 0000000000000000000000000000000000000000..932df276a71ff8aeb5ce4eebd04c4baf26f9251a GIT binary patch literal 218528 zcmX_}1$Y&?*T!dVp}4y&TBN|@?(XhTpt!rc6c+bIixhWvm%`#K6o*1_m*ReBzTcbf z|GV=f6QAVdBsn=JC%1mJ%9R&1^0AtfYh0;gkNz1FT9#$=j6R5CkZpOagjV}D{oC{> z%}#rby~kc}Z{*p4S!}N$WU0N}o{f8sJs&f}o{Bxyo@7t7$J*2EfxL&=L+#O+f%X`C zklocDVt2Ir+dXmhwA*60z%;f4?RIu^+)eC8b~C%aU7L3$yMbN8uEJ9ucWJwbUDD2N zSFm&1dF+CAUOOxAEViGW-cD_2vs2p1?98?=?o_zq*@^AnR$SX-``F(s8{@UUTQTfN z>!dV~9|^~#F0o?6c^4=}f}?_0MpH>_~$niXbUBJ7lP$2vslNoybBd#w{zsC9sl z-M9}}TX?U=wbWYAdzG~YvkVi=Gv8Wd&9g$Rk%UaZHx+v#W(sDsHO(4kjmHF8Ju&^P z;Z|2`kk!N39j#7QcdND4#cF4@w;EfmtUyd7o`#sZRx_)rRnMw|tAbU>DrwcQN?1j# za#m^Fg>V(La#_W#oILrg%vK)D&&ptBv;M%H+RB1SW+k!GSt+e}gvZ96#EMBjC$N02 z_;h8w1;e@x{cqzPOeZ=|;0&xWC=c?pyag_ABgH?mIUU`vvaj?o*y8m>2GS z-VwND9=dnj2kv9{GOnxcHTRZ#1N%1qd%VNkaQ6(UFS&Q!i|$SL0(*Pdko)R@*Z>d zyZf*YVh$Mh5#9>f>7I6XxX0Z+l%O=B?p9of2oJ@aaJNzJ0sN=j!{#d|@SVrCk5p&f zql6!%O$YhL8A3wclYBXpa?j$sU}~oNpTU2QFI~qKMw%O>zk>S~p;s}tsb{!pa|ET` zCF}t(xr}|E(1*Y+f>ysG<}qPU@xLNeu2(?vy?MWKU(!eK+)vn_-LIH$m>+IT>nrwe z`u2kx$8uaRp|P#su5Cp(V?Undbz@o4z=nhdBck(8ZpE=ufG@GZp47aBL8&mQtuz>6 zQ&KA}u1sKAIx8z~KPw0Ryu|xke_H9mzbwT1^JKwfA~rjrd2r{zWB_aZt=zctTKNgh zg*(4h%qon(D0TsCe=xid@AAZzH*tl@Rm7?cz83@E%aX1HX^N4$0{O}@8j28Bkx@{M zG-a(agp^{GlqI|*AyxQBIoxH*SDcdb5t|R6Onyo!g2_Y59DGr=%a2Q=Dl2Za(x2A; zLF#0{BK?1`NQN(w8BMW)NkU+m*!tlnv%UlCkHFc+=b;B2_ciZG(=#9FwU_kNBSz8# z`td3D9elTO-2yLefg`uU-mAR-1t0b^B97n+1qV;LyTBV^-!AZWJNUWR-RN#|SG!v< ztKE6-N_VC^pZ7fXZ(LK{iMU2$k8wx4LwQEJgWP_+d%C^c{%&Wtx7*I`&Qs6r>b7>9 zyMb;Sw+XJAZYxY(UBWHp7I2HY`P>|CUN@JU)y?T9bNw($-IQ(u-f`X7 zZVdOgSbKAM>Tyf4j|2ij~6V7qxtaH#g z=Iq1X;_P=eID4HH&NgScv&mWNta29Mo9%=+^PM@)5@)(I!x@Vi>`ZjVID?!%&Tyx{ z(*rZe>Ev{E+B#jFHcoq<0H=l1%xU5@b?Q2eorX?jrzUmgU(l;@cDD8B99f4n=r`*H8Zy^FZ@-aR~;b%oczb*Mdi!|$dAnhc_I4nC zxVI-UgNPa8?SX3mt{&v*NBBr@chU{P9%6F$H}77A4olU;E;LRMgwVm5h0yc@i$fc^@eZS>b#dT_IM zqjw)+`-oTHZSWqZ=MK_?M@e zKNol6R~3Bt3u9dz!vK24)-rAI^DC5%UjNbAgbH z-kaER-{u{GeH)CrmmLp;GwYZ14hpmVm^2+=Y!V^uKogJW8n62JWfn- zIF=I)+>Q-S$9Cd7alrEUV0|+1J~^Wx9ZxnVrSm86EKYXEpK+0kQIgXsL|9SWrJUlt zi!iS8n{idxspOO=w2o7aaaV)UR>f(AyFMeb0i&=Du2xPxrvv`hPCG_vUq)^lr#n8m zdf^(tcpl*N#0(;Ah%*A$C`S7rXS6fY8SnISf}FoFBb{;1WM+g}xFS?nyty_h*=4etHJ1d>txHm8lZNscB6C&!(PcX~Ivo5W4*#>B+HBy|&F zk`R~J&ETdYHY@)0Zfe|_u`{~);& zF{Rx~Zb`Q+Yes3es#_jY%B{k?qT7hj8u)9m-qgi3AhfYrVH&ti-G;0=wcM7hKYE(8 zDz$Lix&f?EE!+;o^>jP2O10(Pg_Wxpu7UVFvYvHyhp}c2WHsyQjv#$s!iMk!xU<}D ztae@9Aa^ipU=S&S+zIY**1;fm6t2F!+8EF{(6n5E=gWY%4!oafFVW|q6yUB+8xB`fm^R^<((TJ0`( z*AurM_YT|}+_mPtj+K42y9WPmVm9;Mf^RGD&4h0?u4T}x%SgSNm46v@>LT(i#4ICL zUs*@YV)88`&q|(E_!YX8l9uD!@L&82d|S!Ao^P)tbOqrX$+eD}Z*xNk-$0GGx=RRI z#-sEhl(LL6RuHQ6i-=pydnu`xk$bf%YbCL(-C$GtCf*xN3sr)sl%<5MCG~nr-$3pS zz-ld_qGA-IuPw*7jF?3xuUfDOdPntKMZWbWek&=q^39E;5qt!pBe=EzyY;|j2Qi0$ z(JpB2wb)w;-$<;ZqUSCIL_-cb9( zZ@hoG(J|UPXip(NLNxXXHoHpgByB4OyaW&X35&g^FaCtY$H8`>iDR=jJ-x0 z!m>c8XM}Rk47Hw~JxCUwtn5^@D@g}+pP6(8u}hJ+1n%7I0RF@jGOi+oH)&Y~JS^72+qo(fid_Ux6-PQ8xRl8_cw19tKaaW`Z?-yGA%YWv^2 zrP<>Lvg7ZD>BOGDC$275Z{9tu{_On6l6DMvhFHC<5xm7M7=UXau0HS*M(`d6XQ96} z9oHnxL~AnPv#_UFe_J!L7xJFZ6JpK9%(B)IvI3sPI(QX(aBYW^vCG;BCu6U*%{qpE zADoY4))DI{{El6V*ThZXa#N?e2_L8_Vv9ROXF)<0S)7VMuL~wAD+v)IU;K>AECzqWTE{`8vp4{+z zioo|NVdt|e+WGBraDytt6)I)d;9U*7JUpYic1^pE-OvuOt74kLJqm!M6kxZq+uLpI zrgjIrrQO|bZTE)P)CVIjRDYhX@TYp(L2#xe__Yc@Qr3HEfJY4Et_ zW3M4>wY?0^*HY{axYxl8+XZiIySnx4jM5F?+4O-`-&#u=m=B2s>dPwGZ1T z?IZR-cBp+8`xJJVecC>6|7&0539~QTcQM!P2>X_O)xL@E9=?b675gdfXP8HrC-!~& zrTxNwMaT>LIqrA3KiF^W_x5Z14Qaj;{?`76|GWLk{%n7-ZO<#)$Mce~pZFZYEYAxt&^cpOsOo-c&|w4-}m`>X9G?GN()!1cxUcs`TchuE*= z_+rZWMYz)ZBJ8L6?oVp)9@iIY_uh`AmY-3Oh5djwzoz|9 zaXsX@Z-9y$bv^B{e=i~d?iFUhqPUguuktLRbjK(`U1o?c3dE%fhF zdkLZQ;ejs4OfeV|0?y3lnQYI*OaqT*fKB7=ap2Q1Fl;24HWkbpWe>JTfQuvTe&FCh zdjRi&gb6Qu+r8|*`1%mm30D_F2EZ}x3%>RNr~AN1ZA)k;{H<{Zg5NE0wZat$rUx)0 z0`S+vZh~oyy8&V1u-3*`3s-GMMO{)fz*U>L8u%(Po|?mDt#6lMER`pu3Ztq5W2!pi zt~RDB;T1@wr#8OIjL909T7=d!DJu|Oi;xQBuF9yaMqcrAo03l7sgJ)2{x;-oh_4Yf zXolT_6wRr7Yr_lHSZz(3X0)yot?SA-R%_bs9l|wYe-bKNQ|HNp1cn>VzG3wu%vHu$QTqEQ?`;%|!6!FH9e1{?3ft4&YH|##$S2ueHI?_F!;FFt;vaqaLHF67R}{l`(5maaOAQ zc41blVys-*S-EmxXGcEI*c{?0`JSL*r!>&FR-ScunuDH z!|cKAWwqbQI==@BV3)N4-+JhP)z~3C!PZJ77xM{Ogj{0|(u=v!5KEC|{0-$Wk&r3Y z3}}tN43#kn+GHa1$6!MHA?paj6=e0r?raT(Mj3#+A5U*28VU&{O;hNWPWYM;-h_8M zWF4KMnK}^C(h7h|>I6;I%BqEAqX|-un#en95>g#^Wn-%Hu0U)_=q^!TB}q{P`m2QD z1eb;~t6(G{e?rxjfkP`AE*Gf^;LnFUKU7>^Qs*&T-Q0%D{3m&H5u4B0{-nxEzHFq( zja?WT@lWh@l#+)0*$vM+tKkS|r<`o?g^Lqg1bQ+LaXFwb3qXAqFqCFysME|QOnl?4 zxYI#>N^&3yUzERxd_Pxo{#J=ez6B}*r|MVKd`^}!9L_GzAx-Lo)99Q_+E!H;twPK7CWd@#GYk07KYJtk{#E6%s=eC&a-nn$Ik9J`>w<6 z2({xoz<%#2yS)9x9c5>@j~(I(cAnZ1?q~0~*@Uez;d_bGZhJNH+suA>8N1bu?CsYX zUi$)f0XycE?EL4ko1MZQS^R<-*mE(H;2Vs`HvxXcRQMEAd5?utFc|*(2*Z;fVR#dx z;CD=cQ!(0bFM47RfQunM{cyPQec^eC*U@;xe817DEI6DEPY6y3yK4J9;t%I*DB~&7H zRnpgkD^%UM%95rMAvGwk8eF5Aq%DJ8-h|X3Zz)nMR_Uw4S*lH#I7_t%tIWF+v64~L zQEhD8RAucrD#mu z>zVSK;;%-UO4!O>oxJ6Fs*$1+PkBNr!xJlusRVy4N@`LR$w?9TV|o|j$^D<5+s%VJ z8~n8V*txJ%!$}k0Eh%AH;KgN!7njk^gq@am65{>f+NCB&T)Tw46T9&Uj|sOghU@Dl z!o-4;7Z09aT=;*I$v94Qj1R^aE@Eu=6I?>^3%?s#jTbqM<$i;^7~TB{Wax z;AWnOCwd9{8otZUIp-9TpHqf^x)(m`0cQ^!)t$VLV79_r-2vBi7yQ@FgzkbryUp-q zH#)1~%x>mc3!ip9yxZ+ah1TKQ2!~f8!SHmK5WWaI7;bL}eBPLnu3`CA9)J$qzOoth9gxPhdr8*9=HbM@9hjTJm_A|K>Tfq?E^2m9bD>;q-l+5 zi?55Z+rZ0i4kx=A5~#+!>k!(&X=%9L?VO5)HX^PekJ8m5ys=Z;JPnac)g*l_;>!_U z+o_JbHr(?%m_~#(MqbsxDFYw92K@EP$g--KcV*t?@z;X)E>n;1)Ff3IN~nxmDaC;= zNk}cY@---@8t+P!sWe6Kl_RzyAw@}D^1pXELMv0U@|7UAG&#$eFIJWB2+EvC~ zi>Cy&t4Vp4XkFF++SCMB17vVDj0{fv|Ej>C7Oj`NKJMy3uo;kQg*ykm+D# z0?mPOYvcgkiR*yr>U8Ga8JPA$CeQ<0vcCb?gOLYF5;%zd8iH#GeKp+l-WcAK2pvU_ z2O&imfm{7N4*vw)lfi{)*t1AA0h}2Qj!Xqhreo$IeH@Pza+<-MC4_G#-5P^c%LrQo zhAjsBLU^_r*D7QP+wpDWSWy$oF6%zGF9!$>^$;XlkHnZhajXU(WM zg%sfoVN=N++634dfp`zOLb5%!8%;IZ?AG9pR$ zj`t_RzLDN?A2V0{WG?uO{{y*RoA56t-%nD0;n588o#&JD3-g}xe=?8!Ei*W`I`VqPNmctp&5Q}SzS6-nuDDeF6}`iSo(b$LnHJL>Y5 zkSF+6^OwdvHZ6E&>idG+%J+gct5nHfBz<{B*ayD*mGB>w^BK2l{EgIVjThK`C;St( zTJ-_@2eF?iMN*sJxPFp5(&YY44Lt4((tIPWLux@xrT@TuC*&8l=3EEk zgIW1E@m|W1d`EJic!c~SJQ}mM<+{x39y9A(%=*#XIL7T^-j9bn1}1^A<6?V|1;t?1 zh)bBl6R^(2bW^Z)BtwcHpLHb#Ph$5ET!~nZ61!T1^5V39W>a2y;Sy`*FCRbri)oR@kx7Ku>G^h=w(HJU3 zG)5p&o_4r8KzVe>^yKMHSRd#n$#S|uHw}a`8b{NG-m{~(*S6!fzVrnagV?~ z0Xl0cQlClKK~Q9q36bn(Chr+gYtwm_5xx+bZVt5CV(7PJgiCS}0&OR$%{pj5$w+o0 zaafOQ6J|NoqvQ?SpcNIe4_fdbGMT-GcH9q*DH`$w)a5ziPC#{@CQPT8!k|U3nv)%u zaNUNJe+m0Gv}+ht>L^W6X2>qKRKZ_lnQ|oD^@cpBujZ zGjq!1i~AG0TlBXBjqQQT_duIRgCj4VyDwaF@zN9W#AO$tvpw2Nh?lODEb-Vqh>xCt zok=WqAMx0`B*dM7JxN0LM9F!lB`iH|KlUNAbFlx(hD(o5&G?!9QXY0z^2uM3JZ0E1 z6=lz*Q#^%u#8cM^w*16K?d8g_ixY>vFgv|6>;SdPt4?eUOdU*Jc8IkItICeDoK+it zJyO?&+g^{NcN5wcz&WKB)I9*XL}Th$pOS0fuSy*&ns!tLI(2}RWD%7y zqX{wYnL=xkMPdLWzGBWEB*Ci*G^sU#uE zM1T3S8}+BBb#hCxlAK6LGSTNcO{iW^&)zgMJIeIrN>AVZft`jFIveJPFB##9$eEN- zrB<$_Jn7h{W*}B)5`|mY!7HU!jO017h>68p_$WC}0^~qCT@}Y*ZG7I**el!QluRfF zX%m6l3E06V!5xG6#Kb0N_ZpWJ@xlG%ghwNlPPN4`_^#NDge9i@_~c1RtoGSD)g_Eq z-I7zQ6yy-DC#LSIacWwek`O;^Ns%N^%7_v6|B0`F;Uo9~|386d4xn5Rm+-w9P|rtS zaC)Z037SsaEaXiSiJyhMX&&ys|C2jO#xxZf(^R-ce_6AM zn*uj!8fFrlr73We#==#a1z%~Vkx9)Zr}6}oa;~+6oXc=6!o3u;$XY;{I8&>v#c-z< zn0GK7t9h7N#4N)sMvk=*e+aS5$+wt1;$bbqUWi#me6Y2a_iD;mgKsTn1L<}e&eukK z;)HD`_f{lh+i-6rZWCrB{4>QJ{Li%q3E6gh8#oDki1?HEj#`J|rX9w2iuVP$Yp1N^ zq>%~bIZj+C9JvG5Kh}99YG?4BCEY*B*)DL_SaP;&_`=}kh2i=a`wl7pg~#{EaQP&0 zyKP;C-*=z*d%UmVzeDIP!wuBA5TL@Bzm8bQB9xkkGzCoBe1zt}{E08WdGuX{<}<1P zB&;Z|9Hs`@$zK3}5$uA*l%}qwc^Af4knc;9S(r8y!>9z6oQpICc>M5ZBV1)=!!AV2 z^8B9G)0R zU4L0Iu|HeU>923})ECR*OnfxV4>-*q=u^(M9# zW)Q9sxcaez4`enR#Jnh}dN;z_^X^1UPq@s3S$TWFoesp;mUmm?14-G`ghrjg@5D^o z&%_3hLbGcx=G?B7(SfubDer%>`}Vl?#Xh|IFgy1mraNEP*V_)WzQCa2B@%w;xJ`~p%g;46!G%P03Ta%kS2!JIe?GaGjZ zv!iCsh4kDKX3?ee=@PODnX3uig?|G*zKTBHL62`|Hr&D!%AC6o_kL!~TC+?<6 zBOQ)27|BB}gR>_vD&sOGUn2ZG_C@B<)0BDv*Lg7fG^JiJsm@?uXV$(*nfmG(^Oa-& zeeWn`NV;;G)S;Aofp(n-&lR>8><^`t`)J!S)`tVYKx@K2TDFU{>lrP3i91Zl4*a_r z9a>Qi5xbL^UF6?D+$x~5g%sNi>^3oK_ON!X$6iai)r4;Zijv=~0s6~uts+J`C>!XJ z5K=70UdAX}Na#GmW)l)@`f)0~ID@cx1_$QS+tUf3K|jwXWE$8p88ZP~mbUv^(Rb3nQx=p4i zwBM)!^;M0M>*A^pW!4Znt~%ck&0dy}3b?96&((&mt4~}x@{}h>6m4Ff@EZ7QkVa>Q z%HgVrOJOQUF(piRUCJm&OevlUgq5c}(eoAfN=aNwRTRIpA1dG~MSM}x7bCU;5-7>Q zw5KWwrCHeQtV)ow46dT2lwO4BQ_072L6=K9CM}1IqMFKRbr4*=g+hUkethg z-d9?S7+>ordtQfAYAkq|pX`Ti>|gATz3hv>W4@X_@&{Z$kWM+sp1vV_`e4q&MH(qp zB&lB^fl`_`ykBE`36VVOBeu@X{l-WLiJin@9e?Ud|gM*Lq2AX-mD~ zblzL`?+@|6LaOzdo&E>Xe_&r9Ns5;y)hG7%((Cz%iDZBO9{(rK`bEjf9uW2#-*?J+ zM~YX*L{iFI+!4gy!X@pjr{q0Z$_WPR3PYuao8uQn_24 zYJ37mlc@jFgoU*kCqcjFWs5%EV(6Mqf)-6i-Y*OA^`MC0WmoRm=VpCI-)?@+={ z6Ly632aw-IJ$nh=Wu$!4k2!(N?gTuU1Gx7Xj?He)Yi_`|8ve{G!gQ*3D`!%5%61z( zoON(^qRy1AL7KM${}MPso8bek$F;$ncwG)pNGD(ynUk;^;TSD}bF`ZCz{}t(Er;_Y zy_>m6{1(D%nhtkq0`kAHh6nW*A%Br#68=%}rA8p@8w>YpDDIK?2IC5XyEPgv)?l7t zaJhQ(9%wjS;(-mqH4vWI0N!0N12_ZSi+4BDb;Pv69Z0SKT&Kq|j%2F?Y17#PA;2lT`$r)Q1yER{JONyp^w*#>qOu6k% zsHBn|$=`=O-KlX`>~7SmEA{P#-3v)(f8PCQi@16Hcm~t+|h)B(yvtf72?lR z2c^5fsJm+HYt;Q5qw^~HZu009@)eUWj5Ip8th)S5TQ3oQl60rdH!hfblE7c1)F=(M z!^{~+$a|iY((lw+=P<_lb&OKTb%U_Gl&PADBdT-G=S+z7-!4*~^g-|7mb72B3M17G z<5sTo_%56GMZzuu1HnkOR>}*+-X#7EC7k1HH_dm#O$pMsyMzA*UsHME_#ggf?ornV z%wW>M49DEYJY}AIflp_trEhtU8Tl=9=nFIVYkhdb?61?*znHr}^A;!ehw=GxYT`F; zotW_9%tSJCl0s|OcWtOy^L}yWLa{ni9nGA=h;G)in5FyZfbRm#wr?z z^)?=Bsn%1S63L34jukr_tE)dNaTeC_jQG++0VIPG_=C6B_v{!y=m0#09?a?_k^M&>OgE@b%llfm+-`{~_qAEluhz@uMmT5hYI^!?B z>nO~;55C?9&%?pcsF89FychnT0>?G$9LIHpc_EY;Ou7q4n9UAhbOuJN;C5!TwajVK zFOghBQV%^^=T|mrNpd66ao~GUtwCg_~g3wIJ*s(8BOb6B`tx8=uO%in)r4egPfRPL}T1Zo6BLy>n^B3A73DZ>Q5=6IBWH@zK`M&0DKFBd+`J1IS(J2E^BOr-9wVj7 z>Hf*%PlzAVt?bCQ3Zmtd0XbJzWL#O0e`Q8WmXQ+|S&)dOLqe7cBiUF2WMZ+9o+ZXk zgxoAP;c-YC9qCy-!jhRY7jcol#pCfIMFMPJ@=6!WBBzUPR&-8f#38kp6Bxe#e@4Sa zzUS-Oq*U53r1)fHeW;ca^V5lj7FbN=e_xOgdML*sw@z;ScB1nQixdj=P$!oV<%xkT zP+#{kwxoYCsF5$+IapPPE6Mk1JuCoSPAkq9ORI;n7H zAViN~_6IOak2?z{3WDi?Y9>)a=<}lVd^ywq1?cHgJkl>KODbucRY9&Oe@Sph z7@~7S!klW@#Wl~!n~T4RU67kWl2_97k3j(JwjV! zHwG)4Ahm1&4mW|`sYhr_!gR7K(43VLb)>UZoe1fMyDPZff;efkwZ$2gO)(9LZAv?uK?ycD^{HFkP1I5Dn-;Bp*NX+*|l6iX*P9-*m?t?{X` zmlFAT0>)V!u-rDI;TK~|qsL)1Y2^II{hqP*j*<5rIlN9Id^BeOz9X;qa;8ACe@XCP zBGLb4P7u5_^8EWq>>rWxg*h9bGY8Mf`JNmfI9KqIx6Vk&^^9^9A4$HCr2bARUd~wj zB=(yLl?MG=N>|)3^R@4eFPil_FA&|F+_Gtx%SkSe8P(GK)QPW{w9}W?#W1Z+OnapH zsd+0FFo_F1EMOow_yV6821bbu^reO4@$Cn;Rr(o=+rdKpirln`n@y@|J zA0t}hS5p7djB1V5eDrfsLdy|W61xOgQW$I~Nza!hmCo6fGMG`5bhW@3of@nNCRPDw z%Y$*184>lt(t5mQwCm`^8WG6up(Q6E+Zoe`96ec2`V%^cryncMU{;&HX6K+?!!TB$ zp{!EU=aeSrY~0$BO=SI=!`d~MUBM#OG-)fYXCJTz-N2Qsf6^e-owFAGAd^{Qj&|;ghpzJEeUudIA3+KTfhlxV@xOb+il>wwSa#oo_0?-ZS67QOm{Kd zy3X+Yy5b%Lx2_Xhz}|59`ort%0tc`Uk2KSU8ou>tY)KNOmo^eEqO{Z|z&o4-=UE)< zIoPuduW>e9|M~D87r;rL22XM>+{@W;Di>oU=hJC)nUy9)(!2HeH^a}|KidBIosjkZYI6bts>PjW7ZjttV-4GK$15~PPx#u zNV3dDv_u@*#e^-uHxC&6O^c=jg%Ip{n8`qA7VwgMRnpQ4q@2b(h%{qy3AW?uk?HVX zM*!==^nhHFC5(VCI}lE7C&DBX7Ee|34{=?^Q|(B9cgKkH+J)W^z;rPDRJp`eZ3`{k z1-@hpc$rOcHwSa7!Y2)c`mT+y5*Va&6gp?2GYK`Zg>iMU#m$r~yBzMi(A~A6%BzBr z;&v7xS0Uayzb`&$Nzzuut^f@$I=>|JeR)b(38k^iVoDh5zXEZ^@K+#Ramv-127R#( zDJ$>|eNXxVIt!w+9Xef6*KmoWG%lrmDJf5&;X`-DHwcJy2ah`gvueO!p41sNV>cgj{jyb zRnmY^QXDq2flFZ2eq;>SkT^*CdlC6UI4SSryN68TF|xSZmk4pw>m^D6OzOMo%n1I$iaVo7O_7OL|?>@M?t4OC9uI>Jn3xcO&$= zq;b~FxLcA>nrF?i+n_nq!Dy)k+C6Z$<>`Q{Gx}QH(Ccc4yDd6f?a}2@NFPiG+^sQ9 zDSH4qI2%bLZ8YU;ONkvRxjR~4LriVzlV=>dbE;)$N*sWuPAlru3!SkJq*lpY32#fD zcDy^FW!D9}Eir+FNz11VuGT#DaknB(OKKlLs`|t>C$16tYIV?7YXY2F@TD5W)BCZUyY-}Xyv5~Mx$=IW#OZy9{yO;h?Xymz(NaBAW#s3agd`6!8033P5 zTbe=-c%C8QiU6B#;SS?{1=;R--%whc) z&7BzGfxH75@4cDZBrDQd*NQozxse1lV>PUAWIsA20JQGUsj>QY*BGmAB()P`optcbr3aaH-MP6U-_W{Wx}q`TkB6C=%Y-33>Q{Njw1 z?Se3a_ycXS!RmyNa#1YYHu<}q2Wto-BKs%Je=>9m}fmHzhPD!LdWuYw!kV9$n zLv2W}sVH&&lrMfz0p{;~xc!OAOLzflo0F3A;4g+t=bWOntV$6dbt|39%VF{qBb~JB zb#GlRAeD`;=ca_b2F5z`SDYGZJ*f=LYSLR(=;P+BL)|bP>0!;)gXv+-*F%}VN72*6 zjeKzk@zOr)LvIfT!lGu{BLmb~sYa}6O@VU*Y@NZYMd=EwPl^WMNmP7o;NFPTfvklM zDX9gx)r@awl~h@xkff=vuM9BXAC7xCxG9=vC>T8)43}(v3Rpf493KwOj{^Ir7|H4k zVzj1f49qdKPY5BwxPyt;&4Iy&QV3zako0y9_FU|>gzFrW;1o-*M58`yPw_#&gMMXtWQW?LLKk ze%gO|%6rDz#XW^QBRz?H+Ia@s`>aLqtj61`J^Otw`lRt0?AgIwU70!dHCn4NJtyqb z)?;=dKOOB1JFp8a3XT2|-f1{IsMzjSbT686+t*bmH9RZvw}Rjw6osc=k(n(6bK_-t z>|MseN9zjAw70I$F1dW4_Df_6=aNf+2J%PP4IaS$K`GzO?rx`%3LRwccbC1M z1KvowD!t15)I}PdS*Yn#cP``6X1+PV44n{+EKZC?id4*!I)~hqHjiiB+|RgP%i4RM zlULE83lg!mKV{6l1=~J=YpLu{jKVvN+1HHJSA@SMUgrr91MM}Ksmuu*8TZ$qC_};1 zRm><_gO{>ahhmnon-~qmhA|UO!>yA{)tQ4cvdffKXG-ScsMFQOSnqTiRX3T`!PZG+ zoy?XltRGM>N6od~RwXPBnCZA_Sq-$a)Y<7k`m~Lq_;p?)BeVV&=9OBEhtl>-YZ&(= z>@xIXD=>W=I^Hk1b>eSN9nVJ3Q%@(4ug^ix7&NkLLwi<77rQs~Q#?jRR%opLjHmAQ zecGAUUI(RY*)M>>V`v~hD5X?TxtE!nt1x0x^5{NGk0p)Jf7sWr2LpB?3A&FwMHr;h z3*zHTXSWo)YTf9fU5d_hNDfiNU}ZgePG_@qs%#pVxr8}*Db$JP=#k*(KxTUBK(_@Z z39a{(e+s_997arM_!_yvprr7W{(zT~5??V!r+C`~;Y`e+PP=%HGP{TuFCEnFVBv9Q zv18QvEcp8X+=@Wju3K=@QNuF8y*jIwWF6Wom0&$BNT2wFP4SQ#eFg({TcqwJm9Far z@H-3%)h)`AERi@K(pu=x<{wbuiJ)Z(`@vhrlQX_ius~HI&4kR zziNl3Rdckg8lidB8NI5h=sX1*ovChUUCri(v=dGQ{HULBq28cZbQS%j>u4z5C-kv% z5uK!?=p2QiYa~6Ueat{7(RjLp{?b*#-@?y|4U{}+usk8;471Z6IAMtlC;1N`_l{C- z@!UX@=^h$T|C(|xa|7Exbf>nVeYF6+t-j`FN8LGBnVL^Wb87+mR&%K9L8D`M3EjiX zXdG_m+grFxQo34mSy|Sjhcyp9tnti_6VV8pMvJDKo8eZV!8HwCzv|p-_a`?&7IBK9 z-BuiZx6;U`KS^ z8l&@8lw0#MJ88IGG6#3$6-7s`7Ntqsa3*?WGtguk%vZ*uowf}49;bIg(H&cDG}A^J zJ;i}&zSTk(FF$oF$QNty-J+O6M#FA6ZBpAOpd;6sS_T^3#No82A2;On!G{XlKN}?SOsJ@nl)sHxgl>E@Y{irp4&`Q zvhLmNOIqE-7X(x%5gtsM5cD{Yfhj6|51N^a2%p1OM{%d$P%v#UITjFh0A0BA;LjcU z`U$*G>0iY}`Vrr7bkoDH6rWUByPiItX?lM-kl742?4{Phlsub~b&ua{;-}EoSwq3gou%!4H7DVTf;O#kR4 zgpVqH;54S(PmIQINO)4hQS_j5A5)p~|s-h_2LGdx$x)v7b*{mEUHax1Z-uQ57GOYjvSG!~;Q72NOS@Q>fo zX50M$#M9E6GH_DIAc3e1zgKfva?=k5m_5`N(y+S^clhpz485lkBMo)T3mC zRrqFosHc$Xs+}P z9r{i;IbH(`cNvXP=@e_kjAM-TU=&RPTUUXlx>-`%#v{;39YyRi!uB!`UN?}u4R)Sj zgzN)X@1j#EJ?b-PJnsW5kAR6EopkhyKgI{x-zPqTdiekcK|cX7@&_-9f@j(3DPQJe zjh{#K>1QK1P;Wh?oN&G;UD8v`i2o9Q$BehjjH}Izp%7-6`8+Fdt!Ms>nq`BSFUB*X z*3!OBgzaQLJ&iu(Yb21d!5uI4_|9B?o;h|U<7h7}dCdI&iAS)x&6l6@9Z5Z;;rNbu z?HH>`2=EAD&3H)MQ?n-M2F*jP1JV!IjB%Khd#UF+O1Z^XzJPJB2|vVF4)VTdupuwF z-GC8LgC0&#KkG)zY~WF8`o1DP?gy5n#wUqjO)yK+yJpD5S|d;BZg8M6w(dF5&6ARI z!;vb3d}4+%HpCDjea(I#H{RdS;ud{CYqu6uZdcaxC#?COp~x~qDMlhkn29u_Ik{TV zrwNS|B>?Jt0pn*S^ujfy2|BCtnpJ))G`pmkMX*~ink0=Y$qZDCR%mPmP)Y@Ao|_Tx z$IMWkHcIL~m>5apCF%IUx06E=#^HwW`a zLV}T+^AVzIr8TA7#&45uDYY4gJVsC%PU++EFQSe|p=@5#zIZ%3N1`((DUofwA(w78 zJjpI@r8#3T6WL8qvpZQ275C27o#I8=ap-JpL&M4G!oHyuv}|?uAlkLJhyHAhyDR(u zuF$gu%&7|>YNgYy;$x%+s+>93JUR;~lPwb~vyD?F|HOQ7k1un&2g3&FYtLbZ<_0+wF z@p*LPer~@1C-hqeFvAy$H9C}@PVyw-WXNx5Ki&4djhxHbhwkJ|hR#1d1>P@_D!rq$ z^U#~yjjU=a5Zg-KbQ4MhICC7IZppt&tIm=qoYc}8y@y{n$3HdaoTQJsn6jtRmURY) zVnQ`1p!Z{&8hjzG?)v`#9z4dab5A-arkm|=nA2GouywL>1CR-Y(tHUns9&EDz8yRn zLB9_NS9J12^6l=7uLpszKM8<{ovV%vsQ-Q&%?f~0QgfK zn!P7`O6jA8z%5t;T|Wt$z7;#244fnO;SA0zMpihjx4;zbT<22u0m_xe)*WtSdI68* zJp0rIl%mt_;!2I<3E|miPW~T-Q?Q@Zd+@EKK6CiaVs>ki3(SE7Dh`meB*b&QL7Bex zcPkP63@^KytL))cP{)yAayzg(N)k5`43}1kWEbrW_o*klt@`W=bT?&gMv^pYD-+fi z-l^_^Tu+U+v0FL~e;~4Ot_0Jm(;85sJ zI^9yI`_~%753j?WaJuU)5MK{C=Z*2@F?U({!MCUc_h0|xAr5(UVyYogs81U4{3Q*L z7FHJabE(Kvg4FHV-;LwGt*My7a8Lr+=~bZ|ang0W-AHcd(%p+octW^uZ!5dUvE*q8 zuO&)1Dfn$fpifzBWv-MZJF-brXj0^!8muegfd{X#82f`^j7m7|m! zR&{QC^Ye`J)b_L@K8szBn0H`*P1jZ?-w@??g93_ z!LOY3|97NCulU+eddAOSTVr5RoqXlNkZip3kg^z~Fb5d!V_!q+BN?>jwf?NL;o3vYU-f> z1(NR9ZgOllEf`2EhSH`1tOLT}mdvk`b{A#+j=FL93#-^u=o;x<=uB@MI8P5*BkvO$ zbY4bsC=-*9pz#;U9gCi=Y3=}{jR$ZO?Cm(F@f-$&Az z{BXEx(ian1kEGSSpSpgB3tk?)(Ji-Y}z;xK)rGX|4Q&rM@C6(>>WG#1l;+v&{}^iC)kAc=u=m^TwQ zi}k1z>rh1`I=Y{r22@x-a&4!65n!jq7yOw2)4PwMux+#i^TB8v+I(nBc^B77@w zmtM8>m@Qje&ZqQd1hu`*vxmBkqZf4AMK{ZdM!gL^^U6)ccZY&kAFZT}R)1Un2K9t* zAE0*9N7o8H4xF3^21w6s33w`f#x2NELTKsVv{(PfGy^-x$dcNSN9QGaLOoREi-LSP zR^sl!Q<56VWHtbad6YhtF)@TMw4>b>NZ*`XT;>TA3kJ3XWeFC#TL-DAC%zl3>UG|=h@O%(t{bJD`Zm^_TEQS|U-W~levf9YXXxybyui9Q(&EF^8!T_myj zFP#WraF+H+2j~h<1YGD}u5M1Oz_%nV9z<>qq_wr^bwOHpSY!tZI&V^xx3Qh_NG0O7|x(?I?*@z!TnFj9CR`~k{Ra_JCAM9)jCf-i2cSE_6f-;vnLq16YM$) z9`1tvlz#9q%IQNtX0RTxUl;~gswDf_x}2n&1B71C?n=o$9VCG4uEvD4T< zZ@uM~jv~NSn$qnUPdykp(r_LEhUh+uq2NIWsCvzmv7q+f0GF%g44oHE2;Fz7TQcK- z>FRg=Gnup=bOUByut|4=R;Cq&=^^2y7n$B?FyuJj-cK7JfUR*UAvg4*W~eY^oX;7h z4_Gn6na}=(+PMW5hA|ffgG(a~{Ck5-DcFaE(Lc{Pua|;e$N;`c3g45uYxhu(G$q*s zXP{;e_?Gt5cab=h1%I?N)qJmeV*+_<(C@lOG%b*o{$)O5vrzv(sC6Q++yZW~cqgT0 z$@rRd5>oTcyrk8>QFoK*j`}KmwHt7355#*jrPw0@jXs;hKJqNs z<;Pmk9z6JqxqPIV1(z{0yHf8&XhiD2r;d=aJG_H7@WbXXLSCVpQh*sPh&ewTYDPCX zWcY8#@DC%p5hFJ#bIyCpKg}NX60@>yA58`Hz4V-j=v4V>cA@Hv9`hW=Gfx8@XqzR?Lc?RFQ?TO}ET;q2K5!Yk5UneB|;o^<#+(O+|c zRybIm#O(Z~5p|6=ETY8fd?hLT+&}oHKb)&fxU*AJ@u%y-&+1B@)b3rhN#|_Ue*M?R z4j?sv6xrZuI&dyO8vdT+=w`BP)Vd%%xd8Yw(liNXP8mx*O2Zp>;e~#NE9-F|Aa^=t zZk5!1aMFYL3LjLr-Cc)Us5>mxYYC}K6L@8f>Cx)curyC8cxDBtrL;N3m)Gg%PjJL9 zknfw5iMs2C)BxJviymDI)c&Q$;H5h=?aX~z&(BrNAnPz&5`hbhk`4II7glV z{&I2Jke4>J26hW+!Bkw;_-++AlP%#n6$5G(81@6%!UOoPbCC<_7Rw9N{)c0s!x8U4 z80xHkB{+IH==*e>1lRuwJ>#q2v7hjk{Nz4R2u8Zn#qgm=z{6dLWFs7is-@zt`@l=p z|Eu*yM&!@`JVkf@KrU658zHMB`RPFDFu2#d;cdU+th#hsGJ`wGsfBL%TnUf6HIk?X z+zeRMMg9izW7dLgwLIL zK)xBcC{5IwU`raLC2`%4#2leF_re>#hm_HE64Ni4Nh>Xpcx1_-CL#Y_Rfjtu*TPSJN;_TwmB+x~0MA_NP!l;?P9%2m9UuP1P5*S| zk6-e>g216H(zE_Z$2I^r-AlTY+DlT{1!-t|bNggxqm86t4nH~>-0|!1%_kXofMjq3k*jt>LN^83z(FLW_vycLnA32%!|1(x^uKOY)oqS9 zfx;U&>7T&?=_bSlr=M`w;{n65-%CoblLY>73?w)A;J`0}hu;?&Ulab(F1z`kt~#`0 z4sF;CB+mhbhvdFZ&(B8k*o=N21dawF4eN}wq6rd68T~s~IV6B}Xjd&Hm+gtu|7NX2 zrZ$XL*8xg(!I0iSegHC!(ZKT{CC6gi+DIQHZ;;-?UF05P4ZH^Pox$X61b)>f-$d%V z7Yvw+G-L+#JCCeDHxDLf=8)!-v^2cH<2Z4W@=j(%Y%>zkt+a71?f-|iKSd7mj!`X* z4}auGvE28-?Ey8>t%|yNb~|61$rn~p!#U=@&ylotq51lC((2~QB;?JG94Z@?N@n%1WWzthl`n4IQ0j>JXtz(}AZ zdFmm0?xT~HF)S{1cjhGBi#Uw=WGwOve>ajE=${l1^KHp9xA3I{_^+})=wHQjXRzdL z4Ul1I?(1NlLP);ek!JKn}3vXGU&73+gE zYEA>GHJn{ahqS$*^TeADxppn;q#3O^bKYQh+tPOQLT{{w?v-pW5wr9YFl`4lUP6A! zv9AE3Jk(HB=du5GB=1?PZn7T|FZ~K@@Df)3Mo3xxz_}RIGCp;XWTXvsybNub-cbHk zpx3ky$Pe}ThE-kvuc?2V+RR!NN-4UF{26Qh9pb_<8(6^xQ`2_H4z}>UhpdX>>=h!R zmc^~sx!GW-#<_+j*LnPc&}~WCZ@q&C3ujdkZ~g}BvCZ1+Z}$AHS(PRAs1G&X5?ZP~ zR8(U^`NVh?LMQWq}^G$|r!7YDHdD3H#x^;XSb({;-hq8*ThMLkH zcz-}|CWfy51KPA8CONqtkn25Eoa*tMRo;hsigwP5UzBN9PF#sAa2|SDx9^C8z5sV; z3;QDyI6QukBn27pCSZ#UQz`T^yfdaOwu;nh876BBZ@Z>3ZLxHkx6OeBH zGjKHp*e^Ye0&o`ee_Z+>XZvlH=O}%8wK*<3 z@E>MB_|yFZjCe&~ucKwUA>;@<#oeZF+c8F_a9-jVb$$-kyr!&I;6oTAdxW8($}@B6 zhS`xB|aHI28&^r6s0Y3M{Zj|7@I-x@ZTpiMr}e+1+L?*+IH{^vWIP zi<5j|KRHDm?*b1GP@{)HtvEOw#J+qhW9bHF2iVkwJ*56kL7Lw;DC4Briyh-F{)lwM zZFXBl`D#hVKqGeRJ;6fBtn_bXg&05I$aj(NtYNH8rZ+e9W&QKqMcRAc>`B6m{80b2 zpnD#U(E7h=weBmCzV|fVZJ0N+L4Ukr9n^^s$<9ZC2@jZi_0JBvk7Wa6Z31&+b>`W( ztWuZRuf!o&3D$ycl(LpOU!f&8Fq$_*nV%*Yin{{TvF;_2#9n%Ux<^FXvRZ+LkxJ+0 z^da*WxnX%jmNZzOsdM+7}Av=h2J0c_xTF!|BgW^w$~2#Tmx0AS-@zYIsP`nd7dr zc70<;kPc!bE68P5M%^KDlQu*EOWjo37*iaq*3GC@S#Jh`J>t>mw0tv0-*WSaQ|iZ_ zU;hcOdygD*`>^gFx>}6 zQM-~>`Ys)4ePh~QoHmO;Tbmkbt&I(bU;l>}NIjY(;mKrw1qxf3L+4?KQpe0lJ~|;W z>qERGKY5V*c;UW!x#=@HQeoX3DZQcuNQVm3isD925CIhTQ_?NC^ABlFxZyDBhPyCe z8-X5y&K%DG_MPCYuZOq%82;KlxZzikxP;Mr>wueXUCzol3uM064S~;*fcsIOj!4M# zFJRfZ!!iza$bh^zKT@`|_H(15@RFLTWoeK9bGyaH~z z;Zz!UGfA@%`Ry5G-#1K~dl~6gOC+N~{FmE2N|;B>r`U7(7sk1?Z8rb=b(a4Pe9yn0 z+5BtSZ~jT_Dse%`#!AwTIP~&6a5a)gchFWuc07Z+yx>2|^dD*Z|HP~OyVwq3Inux} zAET#@f%FLEt)-}IDxj6a*4?wE!H=#6>bsE5&qp#F!01T_cU1DoAIK{u_sL=45*=K- zf~4#^Qenwn6MzYKY41%&SrVXM8aR|PV=o4AI)flRO%J)A1LwH(qCYq!T>Z`%NzS}b z2dVY~zI&1Xp4$!H1R{kG;Cy_4YBY|XUPHU?^52lL_!qX%{Kw%2;MAH_)%Z>aB;86g zhkQCu&>jd4p|q8zclO$c`PanXc6?7pPimkN&WZhr;DvCuKhp4Q)criOuXK^F(VCo; z*#-&oI3T@?-nz|mgzxo29$AW3Bw-XMphfY4OblByiw_tSmsUu(QKK&o*p&r1=^oyU zq|(WOznR&jEq$E+66JT08FvfcyTPdVft2hq_@WvdL{=1s+@-)yopTrkPE4ns4S|?T zKT8t-*vz(J)HOO-?1$v|8#C8caPtRqbVKlF4H)*xPUy*m)ch5Y>q>cPsn>gEEv*%6 zsL3wUTeP4cU#f*9UjN)UkQt&a^JWX4wqRK$=Beo9dd^oRLD)nM=dwQbV|`cwmOEBj z`nneIYDIr_AV(=66G>a44gIAhL?NRqLGY z+1|%jbnmWi5&u8Y)fk3G~D0gpcb@RZdQZ(_*b)H>txDK z;xEway16<9c$Ak~qyZL(|BtA%fUmNA|NnN*POryC4F;oAy1TnuNeMv#X;2ykB$N=9 z5LCdx77GK!0>J_mks1vH)~93pzdz5uzt{iuyv}**e(t`n`|682&x5X+0QT`P`(9J9 zIjTL+@CQ$#!Wf@e%*#~e<1)u95iz?V-#@nn+U z4tnw*143jPmRplqR~>1cptvA>F@5|Ry*QeY8p(eBFciJ0@vYSMPe#6o)vq|Wkto67 zFc3#xlRsq!?LqFagw*mF+K=6PEOHxJ#co9^H=I^<2RH4?4Cv3Uxtub70CpAwLyFmL zt};HKu%FHXCy_r;dFPTHr7UxLfD5P}`jg{uv=kN_b@`VWQ=bv&f~{Rw;Kcw&#>WUR zqXwOjB8iS|%8ctz`e*2`x1dayk-snrmC%O#<=5*`aEYzdMqZaCkFLe@I<%oI7@Fch z#3IdX2>vAP%N5j6x&yB;yIzE{mj&2Fa?5~QP=EyEcW8rk;;beqCxHPhIMd#^7&?JQoX2(UUeR z+Jd|Y2&0mBTG^uYf*J^cRelCuDIZpc!7QW6qa&@BZ)JJ7ol83ggYPC&uN&}TB*#-s zc13vG#&^=&l~k*o!T-nrE4E+-6-~+U(4KB`JPWq_BeUrev|tW?J(8(mYokxAlHxr0 z{Wf?EvUF7BwIOJm&I7Z57OeeKFlAAf?vnEGVltsg3&D;Rt$hs=hcWQ%s>5MZ>>XLy zd;_=iM|huhIEbS)2L4e z>RACDc-biY1AjGw{HOw)-C@wArTz2?r6{_~YfvPL=eG|k`w;Yq{GN#`C~7u?y0wEx zI+W7X>KEuu`A7VYzB|B|uR@(D3cPqoia|37j)0=jHKwI^;FNEJ>pdOYpJ(CtDuR?W zj%L=^7N-Uzy zR;d2!1|KULF5>~}F@qVZxP((EV?QH!gtjUNSwq$ZuGD~*y~4BCsE7QDs-3cfsSh8# zD>Hinv!XxicPwT9!m3STL}aHY--e2i5&}+~r!^n)eh;H78vZHD+D3kg1oQ)A_7ZRR zF`m-Rd6S+Q!dS}_S~U*&bBbcU*Qb_K81-G`b%C*nhYGJ?@O#8-dlQHx`d+hEK5b9X zGzD8 z69BFiQUmF1I1X^dnuBEK?UKPLUl%!B#Vx1#}_V)#z6wVEIoZoxBoq#FmUpGRMB zV$RK>SDzsL8l%6LQ=_D<4>%onNP0=i_79nX`hs^U?Y>eJO z$|ajh`2v)bE0vP!(jPtO#V(u`DYFMx&EQPkMQ*a!$Y%pGxr4}3K1AMh97y&B$8k7z zcZ#F2!^!~8mqpSepKEb&^1kPrCy)?!M;bhY^4A0F4g%}WAPtlBs68XKo8Gv>n12dS zb^s&D2q(Xlms(WdFZX_B%+7b|E)Ai1pO>)Z-sR78pj#EH8^;P#YB^RS!rs?vNPzX2JA*YO8E-;?lRgt+OYhbNiThD$Pg15%S@!+)!D7m zkl<&L@-=#95i&nn(e@*1VoM~-eUW`nr*1zLJ6P*8SZSX!I}S6~CNl%#k!4-t+brOB z2upo=Y?EExeaet6)H}!w|De|-Qya`mSpxKv4Nn((O7d`7T;1mDziIO&qc!pZC~wL$ zSXC`)i}u8C*o$^hvxP|drZY=kHb(e5C8p6*S>-J^R&;;3xY87HP^%K8q^Zo<>db)h zmC$K z{@H@3kFwKFX8rVJK6WI}4&*1B#pXPbzfNn(d1Tn7o(b|3&Sl(I@_U&6Xa;m^N}trA-D$M8IxBn*a8@zq6l+en!DB#T`30XrUt}`Q zvWvb>`5xx6jlPpF!xUCaBl=(fkZy#5G1Ml{TC}(|?d-{yU4e+bDO0=F1X?zQ(NXN& zmO#^4>{4fdjRA1NivvSx*e$VXrg>M@X6-j6a{QpXIQ)uGik1Fw}0U_o&i%25qv zyQ!W3Im(h}_1%=JSjk!?noAwYtt;(QE7SR}M^3HCvk^N`I`dp!Ocni5ULh4pSDr~@ zS;J|3Qr9z#$e{*pXh`~g^wcosllrnJ?H)!==F;|6#<*$k8OIxW zEsET^bvU9bL2Mz{6#U1)gKLL zgCh186w7Nsdq#Z&xQV1{m)S$)bJ~sUPF{$U*)cCM%MXy^1@f0fVh;0TDLch3X3kgO zsinH85pbV4aGDITnhFLjmdo2c%z}Q%?Bh!A0rO@Sx>|UWHTMPQs~n2ityl}v22|9yhF}|mpvxYG z?tKcn{}5FEaU=&@p^6`aPHTZ&HpF-GS1ifLT}nv>4{OU#-4#5w1Nc=H+MWs}DDB5q zym^RcPIhp4PONKC!=eQrGD?~WJu9yb+n^#>!y_L-4h{KMvEUSyT=B{k9YS@h2`=5) zko=UTwl?xi;fZ`Rl}7E8pP)g|aMIzArjStWilTEBe$R2*KF8=c`Hqf&*-CzHj>>`J zzGz^&HbzhVC4Pc<0yXJh`3bN6zu8jOpt~DTQUkE1hj^;^9Py-+@0Ui5M_I-`3XeG^ zD8?vg;p)8ASP!N4i;C-;&P!vn1J4XiTt$ZP=s$y-pk&oG#J1wc3 ztOqAkzxm9KVT?+1+FOV9mB1>qLNjPiIp`T#%?9ZcWsN-8bO}{!S@x z!%f{pnG4}f&7|dHsYQ26ly@{q>9)WV-48!bK5@R}EdQNH`05jWikKw7Ir5^lg;q-5 zC~Xq?0Gns@(U0&-*zzlY$XrD6}~j1Iv^y#+r{e)Oa%uGoH`livZPb3ef+ zje|$p7Kw-?esv8_r9VpkwtnJJG^v}cO8N6kMUEhzY8klEvPq05=LoV7arcYSu`Y)U zL{?b6;5jO~TM0ZxKeD=p2Jd(&zN>a3pLq@G$5OcOHL%pXW60Gmv*xc-dmntqVro;4 z66(X3Rb09uaGf89vn@Y{9mqdA`XV*^ivIb9mOOwrUImHBV7St=u~J(Sy$*ZD)kqUw zhOhr_^pD68{(!IlE|Qj2@Yg3GH5|=v1gS@2leQcg!Bc!C>$|!9=1_Opr%CE5+e7tZ ze{vrJpL{4>NqL5pq^c{WCPjPTnqH!RPBGq})2F}C3iU-iy%A3jHRp@Rk$7yTR%^+B zHvCLkIVIEbY-Y&`c=Okz?1n^YAW|a9>=qlf--M*-8SZCLgC6u{Iy~72jNp%q{yWSE z$tJ(0AFjdiwJ~Or#Edq$r9HTAL_aBNp{$Tjk?Ios(=19m%6D(Ea~L0c^6N}HTd@w)7_~JhsDp^)ugGq5LpbbY50w0RDU)x%|qgpWyx996vFZ-;(|y`;jce z<=3$%Fsci6m}$uP<+pbg{WqU7mvUYUxBEqU=`~>U+nirvJeLC5hQm>60;FvO_qrFO zJPK$%ix$Zyd^ThE3cV_?enDfURAjbBQ6t5VP>uV`0% zZq)umO4VAEuiQU5F4E_x`DQ;eOYv-H)q$U^nRx%qi{b!2~Bgc`nU$#u!>45{rEYG5pyUfHOv(^RV z&R~AZ=2mvr4Ordvkp9(Yt%XPWeYD&hEeTW+ zb;vUEkZkZ9T5y`y-XM+F&|OGmoyx|$9x|=Y)TbjgZ@{?>(orw1lwGc(Bgz)@6K2Uf z{Ekw$TfFfg*{TRk9!2SMfM$=fVmt7yv|FSPqES6Zk9}|SwQMYZX5*s!IiBpNHh(cjl8!mZ&l&Bb zeZMf8yGW;af-_h-(vH~*KJz9u(7IKuNO`)vNY5zhiv}jw4@_<#xRX3XD{|;c zN?Fc3c^f|pq>#3eG!36L)}LZ-OLivAQyx!y0Y&?h&m>Be*YqchdddP(7)d60VFX%1 zeo=&f%MNz}ZCObEYiXq{`{WaJBanDHa6&#-6(u_btXF(@MHg~|y(~v^SNo7PQ?&kj)==SOG zTxLO4KgSd4VsGcz3Kh5qEL-ueK7?B>s`pXykxWOCB@_=r8n0vFC@tViMLv7U;Ji+O zo)ss53Z)FAwD!=#;s?h;c~^u2uMM}M2VbjJiU2kTo~-=!&!tAH$766LdK=Vu3pgUu zahL330JOF|G%5atxEA7`%!Wt$G~dbx<}&yKGs$a|!8aa8$-UtN)P&|wf%6bXGh8}} zh43Nd*GZg+dT?DDLH8>=|cqH|VvlZS-E*!SAaL_Jr<%c(wPCkuD(T~=P*DMR9 zq3|9CaV7qdBGoABMH8N?-aX;-bca77K38L&Hlv(|)J77pict8m@C?erXQ)mcGYzW0 zDJ>mHo5ZJk39kN5=zeKcj)MQv!q83+!Edu6&1nXYV<@d%Li-iL;5BIOV^9T(9QHBX zg&mBCbR&CF3&qSxf=e7jtJ9(Hr8%kIT1F2}pf44_V*>RSN2VM6hfHc&8BR}I>Mf2) zQ+lx>{n8oEh+>W?#z|lDX+p|cv_<`Ef&Y5}PR|$cijTwn%7T|rjxRg%#S}($1EtMo zlr?WU(ub`$6t_q5Nybr!!Q|SSIic7)Rmm|Gj-WVM)kxQner(FqrpDZ<3~wa^PFro- z)s$McryiOgZE0(1pSR>qOJiKC897O2Sc7_MWUKQ={hbWo*9xeTG!t*JvDgIjxpb{~gx{SN%=EpVLo z!y){JKDkJ45qQ=Y41VDkSOa~7T+zYL{(^+;%~I1FS@Yay0fxWTS=xGaaYS|wYXu4 zv|BPI$pI=F{ZfbCOgA`(Hn`P()<+@hd7?|BWR%r!b=)q9=zi z3&o{e&Rl(-wf_Q;K=O&@%rM1unhEbv^2`C$uo^rq83eAjCRQ;BaL~H%Bsnrmt;fOmRZ=IZ{@*lv;l{PQ_?{Cudng+ z5v~<|M=@`tGdrB0VA2d~s8zPcsPh4&Zy#`c3vc2Vc<_=4?}ZC2iK9k*Fu9Fj2GnD% z+8KphT5u03K>__Cdy(4ILC|io0s9nfP&&7ZDPuI{cQj^*^nT;0e>8cdQTry0Qcu1a z%Qwq_)upzeM_9wMcb!I?ThYTddOe#`9?(-U%-TxiCcDn5z{;nA$1AD-W>)_OeoMKV zO#3wAEm$LIjIfEX3i)2=>eRFib?HJLif+`Co@&c=AI{x)(%opQB1g^zrffhuyAk-m zk#;O$#?NF-1oZ~fw~}j4Z z=g4~`C*K?TNt2m@_u$;H6jGb#Xb<`XjP7AamO1(3`4WCMu zM~W6HKd;|0!oPD!@+p0n8}K+~gCSp9r+6zaui_PEv5(6}Lh%CSOF&#GKNx@_2;Rdt zmDS)c+2Dhkzy=cG0cF6yl0RETos+&vJp8uuJQc4+TsrZQy1`TG0FPBXQ~5HGXZL3C z5?er*Gza(T2^KTMI2MC#$v$N@82d&rZ27}b)QrhsCqv-~4dScT;5G7PBJb|w3|`G3YSf1s$%~32(n#~|VRD|#H!BRT(PS_& zMe^tb&D0ZmX%yJh1aP#jl-GckRiu?kh6YY5?Ge9s6nU$@FM#Q91#jO74)+}G5PwUN z*T)+)m^=e?gm-IV&@ z5|39tc$7Q)B9|4NxJdo16)1QO58p(MW z{U9&O@;1?v^LYAX7;oj#U%p?&Yfa#1W1JN?SvaC1G-omoaC-SIfg)c6@6bbMf#o5iEv{-P>`^?7+ z$ZjukCCz~_sibESW$-WkypbitPj=jP< zR=lQn3<>+!;J>nZktNO@^bPJ%x09s*kTtK^v8!04vXk5i)R6AahlZX?slV`RlzN?2 zCckf2k$NbavT7nvdWsb)oeD)HmTd6=-^ruIVRF#_N7P-{diNd2FK7rT#_p z4~IVrJUyHK&Zo!zgAXN3uFkB) zuDtEdwIWx@V|8DCqbXr9`O8P!;~e6zJi+fNR-fcSv-oZ@IrZf0_I%ff)zO8O)QL89 z;)%SwDR%tR#uxGuGL>t=q>+5p#E`4VtGA1JTb{PnVV(D+2Kwqr%8|v&ll)KN8%2ok zL0`2q+TRfP(S|nZiR4p~I!rZShWP4}frb-+M$;&JGW9NvG~e6kW%20M;-2KwpIS;5 zq*xwL+QNN?y>HwXRqnJxH&3?uwq;gz_7aTJ|l9Ga?^K{ecgo=(jO^ zA+El>UUsJU+Z*SGz?$m(GWjl@ac{xcD6YF=w6^5jo;(DN6dhF%M{r-!I26Z2en}G; zQ&FQyjIR9l*qB?|U*uykkCk(q{{mKfF>_XsBm-JEler|@&s1Q8JYPvxl)=0#3ydg3 zPLeHFp&qR$w;gk71U)tl4$N%mY56mfmFGnIuqUloR9M9qQ9KcOxROR%7kZ!zwH(5; zZa~mhoE4$ICFQ8Zs?20XvQ^oV9#o|URrpmk_E!tLZ3@ApcpBN zrP7I>l+LThL~*V3M!nR8@|w|8-57y^)N%~_)HJ@IYNRZU1|yFxs;TBn9r{8Ly{gfh zWw}l>o(aNtBuB-%lI5$k!5RXStJ3GSS%+2WJ@uiyo+>tqq;B%tq=>ij>#kAPeyCkh zbEqrh*OEiAY89ha-a-ZA>+_R#uD)wcI(g-n_b^$!b>y42#tzhh|K8NHKhM;9#kSGx z>Po9SGY>Ui6~{}v%}7ROFm2W>l{T^DkB{OBGpHp1vA0R!ulJqZMt)ryXA^W|zjN4PPk|m_|>lU2i4H)$EYQ zv3y&0C$B!F)Sjr_b2N9esFh&A5XNknvBwXl2C^qr%1(T(KF}y=9dt8luE=D9d0M4{ zVuA(2NP3d4Jzuq=MCrrUHgdg8N7{XYn~IGj==CUhj3dot_*beztUrCiBAbO!(Ocs85$JD0N}eaIJ{aKtBhr${&xdGa_VP6b-dA%*0Gx__Ly zJW4(zNu^ef<$e@T9-&2#aId&bf2YE^WbHnMw4=Em!Sf;9 z$?C8_S8eGHjfnKZC3_!ejOZZ7T02f}-pCh}R+L7p1L-<2x@{P-W{hrq##wlibk7WAF&+&B{aZTVO}~(PVlZT<4&HUn#n!4pB{# z#BYIalC9__u3z9z6s)v+)`1(X=J{qYCB@8K0Tr|mno2s(OTeHOa;$)&dkT6_nm^N_ zm!uIRuTj$5nPPD36q|MeRG6aAiYFnD+oIMdamY8kJj2X@4xGTfB72S?m2@_TLz9k! zTARdAwEGyolAj*wqVsx1_s_)P7gdfOTQ(z}wr znLNwa;F)x>q-ooX6guQ5y8|iOlcFteTa&6Ow0dKxTWC0|`h>+&H=YsfR{UrNWk zI@}cLmY3xahoU^!rH8mD(j!bX_$Z1Bpma9qb8(%V@KWM=CoeyJDb9er=rl9>t_iJf4#nJpb34w>4E|F`Qi^k+eiVmOyqO+wbvkoa4BuAJ z)oppxgr}{z@50mOj8G?oBPUK@H{PpfJCRblXjwU6eo5lWsj!Rk<5old@+`pAzt=G=GqjVjE4#@y_Q|LG#)2Fw?E1ARm|K4 z@QIf)zt{4U&A@tQwfvZ`?4sKN_r#m0-4TTO`u&3@F{8h)CLk(A-!l?QME0Ak=nyLu*+!o zFWo1#w<4N;>t?TWe9LzxWss%_Ecc|SYW@PxpuvWxsx7@V7@TI zDeN1M8j{7?JfF|q4DM#}W|6V;%z!@E&hs>TgJgb+%c&ThiVwIE9gdA)nVXTOY~{QG zJaavi|1-SZ!qs-;PH_cy@q39oS&Xmbx%?E$`brW`+3CH-v)7Qd71L1OWjB#_2mdedFB!SMe1W>{=1TG&)oBN3*^)m8 z&twBFP{cTS$dpf*E!2HCX-ciz^{=Rj`$(s#xbiqIzoU}P9yFwrrLp1mATc}0@g`C$ zMa$ifJWLVf_VHZNXZMl!E7bfI54JCoO(aQ zPtg}wvcEsg-Y$DRVF1q<&zAC??7kL~hhpKa<>@l=UCuYkbpt>7|6RZn>E@JSMfk=XzL2DB0_U-$8OI^Xx1#aM2hMnA&jMzNBD>0p|6z`y z%sY7y5VoNIM-04BIF#_Jk;c57#7q^YJC&4^n4>y|lUfmvuP&wHy7OGuq5}u8QbgS<3a&izb|AgtY>Ce$ zyH8OjZF$p`tM5WX8l zI#Ha`A{cI9LW)A!o3DD2Ml^t8Rtg_($yt=Q;#MjamNXH&KwBtIW2yIHQIDO$#d`q> zM7N5r6gJv{`}X|HPp)Ve^^l@W52sDzNg*EBIQ~Te3o3}BeT35GA9MtD8Ns0_V@f6M z$sx2%JcH7B!|EMTAfjAGQ08FHJ&bq4`-G8;u4%)s9r#>VgZ35H-->sN7u*tDPkhg2 zJm~~}CrvbY^Oa^@Q?5ia_T+6J^3h2BUldN!j*7k6jkn^d^q>aase3!-lxi`QvgE(E zKW_vH6`fOkD~eKF4E46gcPJ?|a%y{TTG@?rXYy&!*l6^{b5NST%xG~HgyZXp(kVXj z03$_ra#P<7rf)|w>jh;6S!DsMxi4IO7I}1|HQj*UqLv;e2T=o(h(F9+mv&$qS~P(4 zBaGGa2)T&9=tsT-IjfDMDSIqyZ#?TxQJ{sX_aW6V14kc8KC<*wjA%uT9?P9HQiP%F z$s=4nPMWzq6ZNn4I{^-=_^%70Fdk>UE84OslgEMbGq@||t4?RNPhsuP;hnI5JumeI zA}W0zQu|+l9z)pQvJB2+8T#OT^gONu;U=-;iHCl%q&4vMgrNm;4(OI1b%y=>1N_ZC zjb=rA^u^vGPcz<@{$K}n0VDI#BCgreq+~7pdWUjGjkQW{~Pp20)CPn;Q46` z@KSM^-=Xf(Qx=D`JvDp{&4Ler;iKq@icyi`!zDAKZ=k2AXnyjX^a~KUT-4X>fK4LDOoYtcUa}-^QQS8irz%d*R z6#tuAtmOMEa3y8GDq2vyhT-(uCIiB1AGAlU1|P~UnTO7Uq6dCJ&Qlq~QV-6e-WAJs zI<3r(s(`-861<0q@OVGU?u>nt7iiyI#$P_6zvuTS_t}*89~hdv{JaUqCdx|~ zo2-Ww$3~ps&*0v^3Wj%xZ+@UBzN3Yu-dx`T-}|2YPC=WAtNjsQ{?4;wTpuxT!QUw5 zCosczn4^0s;UwRS+x#7G-yqim(uw_sADgE~r_@w;A{!Uu)-DRcwCZ(!( zTZKMnS2J<}5mPIJrv8q!aHYTkOHR5zs6nps0F7 zKlO!H5w$8WtF4f)wldPRC$%V|X1tXKge>nwzcw{!P*F~@@@;~QRZ-t0%N0eYcyDzK zxv}&OYC>;{608Q5CvArM(0TH_Sf2Y-Xu|Tyj>|%0ra%?SAEV;2#d0rNQ@Sv*Xjh{S%Ba@b#*;W!nPeC@>CNv<54Luq88kILj*^{}^hY0Zt8+Uow;N9jb+t3$q^H#Rp z+2oLoZ_+%T7NJ3q&sBgcOnNk;rbS1K+D;`^1)fMZC5fLX<8t(h{8DCc zEoxYIy4HV&LBH4MoK9b6(6h4nu0l`C&R4d$&G^Z)pZ>e>(+D$}jd89$%%2HTz_A$(f;mndpm^+eB%Z6w)Kk-SV-y}*_UcRPrznDA4 zuaiydZ07PTL)I)^sdZdG3m;~UvEr5+=e4XHt%GM+8_)c2bx9Jwjrbv zy#=au4=ZM$v7%oE+Q?6|_(XeIr?QDx)G7HC+s(>702TW_(BnNI#|OZmH-Jg+0{OoM zzA5h0|3xvCKWq7)JOM`8c2e*%%@Q&6#~!op>4yRBa*|qDGWpAr&(9@FqW`L8*$9snHtQzktab1Hoj z-BJog7_VwB3QM@Ns2tHf-Hf!Qyj@A>L=%bnk>3|#!F|D*y6}_dk_~)6ojcJ7%B2%; z2Z9~SQ_O5i6*erabpko`p$yR|;=D^LDcPqagOa&-<~N8`!@+VMB}Ym8CsFs|d@ov1 z^wc1}n?Pz&HHz6HTv&1Ert?HJ&OqwekN^Lp)`rls{yZDW{}AYoj^v%+Aq6FnGZ zNxnq;iF%QAL~;<#1W9RI8e`PTz36!BaCyI(d+G42}jxUXR`;s$vV7WQkn8Z zQR$m3u&6B7-#OO5R{T9=($!cC4VnK|){*pEgpZ$LEqahyE3TZPAeKf#jAtc@ZzVtX zH^Ic*tVG3fQ=L>R#eI{P0}KCQRo4bl~1!v`3hjG?7s-2<`M0l^#`2ZP!EDuAZ@}t=x zeI8kiMaa|4lMrdbT>Hr_nle>}WCfBn$P0&bI%LZyY*#f@4~X|?=U-6-<7s{ASrH`c zh9o1Ab3CO~0G1_@uAGPJBjOj@Nue>!3OEZ{-(vYdi+|A^DeSQsj zSB+UKI9G#1w1*^^(qoVvTx;gPVolcP+=SWQf*C0aUhu9V|B^cDS*ZtO*-gmXlI&ar z&zkU7(qT!BL<vw-b0NZsRs+<`+5dAl){;*v2>8Nh2O+sTU{dr|*J(Jwz&LQ_bhO>a);G#)k73vF0e9OdXP;r=u#0pr zlE)j+mphb-eAH(-6y{ya{WgG3!U#X8-4@TAB)q zvQ?k8S)27+2l+rJQwVW=1yK@<^3lJWEY$&25x!TJNyPV76E6IrFag{6w5OYd`>=Lq;n!k zhy0C;4i{#3)sTPQ=32T~xug^wdml_OpS`6BPQ(?S{|gj$0lgLZUcRe80`vThuT2J= z36SQLfvLU+l`L#f@t2&y%m;icS?XzavD?&1kWMhw3A|NA(vv)w-x@($S+Hxz$ffkZ zc_!aNL23~V%#CJe%BL=usX;C^7q+RmR~~AZMJd8m^T2f_^Ob+yJlb%Z@8t_ekyX_{ zy7KUy{G$2zF2uE;)aq3$R?($RM0632f$<0>7(G92;={66ll--YzTwZzL7f3_LlwFdHbCbjTM>6S_3N>RA_Q;QBf7vEMqW#NMQQka$Cx;XWN zS%;%|FO3#)I)#mjw=aI%cvi5!)D9u|J&YW*wx{xqv=yWmGm7`Z;v`FW(pcGpxl)uj z!S*o*o~V?QxKnzi6^t3s>&PoumoETf1_lovl>!N06gEZ5K2Uw_g7COKk>q*Ok3c+GUM-E)UAm06iRO)BZIxnZCQ_M=`DJHj*|-)i zV`C1L`dPOyqvH&iU}82)-$9m8ifvZNwboi5>pzb*RK)*n-YM3vA|>Bp?c89M3KA3; zXF-N5JU!2yd{9Uu^A1lgk?tH%|K)d%wJ4p4v%rbJxcl2!juW8&r}Le3FlLdrtOBH?v7Tr0x+CrM733{H zeJgpsf^Wp-l68iz#C?03)QTdolIumBm+@Q8v*$Rkhc{3v8<$VE=i$G-z*%--if1j| z`<)!}CAgMeQ#`mGTq~ZF_=vjR1UF#^&-WV7_M(Zhi+3+`9N_JKdQs^WYvEN=?53y1 zJJd7z2!EUBlA`bBY6tIjGA7%2qc~lXw~HI89A9Kiw(?{<&(?DP0(F&Dlp-r_@(au$zd;Yxt=a>nTwl50_KFWz=~MztY}TEoDWr zla}tJO{H1}isANutrSo8HSQ1b?VI$*i~PSqUx<4vodbD>SMTj7#e4LVy!tB|+@~Ce z;p9jo;V8c!nBn3X%QoQ${y$@Ne8rmhi)(SMe`ST~C$6_vm2{-#TkafJ(k8#k`7hR) zc=lJhQZzUDsJXy8zQ}i4k%}R3jA^MJEAHDazI>DW{j6`@zeY)i=siVPKf*J4)<4Ye2=$Oxsgu-J{PRDk zw^}QXmNeF{0>|WqFpq2P?ArB3?JCB5Z6Kv~+U5q_m3E|j*hrH?`ooevYqt=Eptv0i z*c}#uUp|L4M%o0ju8@VeERwf?%f1Yz{JJ68R~*>)!Mi`iR`@s)BVpm6b1hFWUx1B& z4DS6Ua-<_z;vUELRaZxi6!N<+j{{%xQ-tTk;Nq`wwIBV0XThO2fhkL#FZurhepA4R zrK6~wSUe(Os?tQ1&apTU!YPE01Zejiu$nuJaSo$@h8ZPk$0uN8k{|4&zqc?Rf-JJT z5pe__%s zYENo;2pmRUrJ7}nfRKttUkbktFwzH^GoLbdq(}E3wG&TK)U}iSE(P328sX9h7bj;X zEq|K67k%$Xf-~}w5i$MS4v7xSk4Rve04|K$yTV5^ZlPU#NWqv_bd3dSb<;V8I;){{~k@Lg#%C0`Pepp57o4rS+fevLR#q! zX_4^CbM*c}`d|Tl+7sS(Re0%PED(RfDsdOy{+1U{!k=1~;%Y=*vJ_`Wev2HB{1|yV zvMusVWJP37WO`(5q%qHs*)UN{`S7rr0P58vg;3Fn2chwp^5 z!-X&_q9RsO){0b)G>){4bc_s+^o`7j%#J)8*%kRLax`*@NNv$XovVW%w;`0jkVtFq zV0Zf;a)Cs~c`&<~c+-;lS->5|5nRm7{}lWBf~bno?eP?`4uAM(qi;sLOl^rLzQMHF zbi#DTWHKk4TbRe0=bK+LA2%17O)+(1`p3+U*%k9@%nvdD#ROuiTB=%lTSi-+vb<#Z z*z&98Ps@EvH0VnmYc*@8wZ651wUM=>wV!o>wVSn)6IR%apoM;>!v46jZ6hZa$ks@`(@_Q zIQSjsfY&`4fm3)(>|C55`8YBq(kOC0{A##YI4%5dXl-b4s7&Zma7S=Kuyrsgcq;IE zU|OJ6AR%zp|Ehn6zq`MIKh1CQ|Lyz3_p|Sa@1SqHZX=MueYzRuY<3tucvRE zZ$8go_I>F)=DX;->x=lJ{Vn}H{m=S$`!D*V104c$$oE{p7ibim5ZoU8F=!683(X3B z5xN^{8D1LxJZz41jjWHHii9Hd@nX0Et7uC*E+-cq4hs&rfs-wz3n608JpWy z!QR(C+5VjUxINFF=xE@W=h*5v=-BLd#j(oqg5z<=G{=)XIqdk|;czy04s^D6_Hw2< zLypfJ;~kA0$qu*usQm-`PWvnNb@tJAhyAi`ldXa6YwNR&UykK-%OXbEW%(thTa4fQ zzPX|KeNzk54ZN_#Mc;=1&=;)rP1ZyNe~68W--`^21Q~^4jKb%kWud;I!r-Cc&|qxv zm%tl=Nr9Guih+CnPyL(xQ~fFa9N%HzHs5UDNM8?MLtkTG4PPT)1z)l+==FN9c`tc? z@}BZu@VdRRzDmB~z5%`^zQv5jTE^oS-@iVmzpeiX|2F>-|80MIpiN*DBfL3qIFJ=^ z2OPn+!I{Cs!EmryXm046P<|*aTr)f>JTyExyd=Cn{9X8;a4=kkHMuSFYUJmLKjLBy z^~5LT2qN;$#RK6XeDBHHwgNIMNgBJ*Mp@543+^YM)>nae@z4a)jb6yE@;dv)O?*ex zXGF*0V`vAl1@1&UP0dY@n6{ganJo0&Wb<}&mN{(h5;HVrPt5Nz$(GLa?s1EgRkGH4 z%6i9om$5x*J!<{Ndd8Y>t!;bNX0tD`pR>n1>N#pT$~*pHoJQJ5+0*P5?X~T-?7i$0 z?6d9T?QQJ!>_xUCw#RJ=w(qQOSeIERkY@v{&+?mPou!`Tt(Z13r_Hm?ea&|BIn#dA z3{w+R5Pw81qEExese;eiO|&qF=zc?hC%;CPN18A9THiO|MSpO7zj zAoysoW6&P_J@9SdYkK>;z=wgi0?z~n2091qfuR4S|8xI~{x2Do6ReTT{ww}}{Mr8B zc>cTp40rZGO=iI~j*WpmfmZ_S0|&|HX5e-pA=ooGH@GwSe(=}eKf%1)@Di-%3PUI3`<04`fF2Kj@CVaYnhKGWj;#l^;0muv2fdPI41`-W!F@atC1m16A z@X^uV*p2o_f5~ps!t|u+Ta(i~+We{cin&?Lq?oNS$6~I>)CPX-viL1kSTBECeT?Am z)<3N8TR*VAYrSFZWjk(bWj}21;n?U{=8+ki@=|4;m+aM@VU_HQ2WsR;7h@g!ITD zgU(RX(D=~u(E8B+&`+ViLZOf&+%-HK$Z;}ki!=j@Ji!RQ9{DM9F=8ujPBhJp>}po# z=yWijub^dJ(A8t%T%L(C<2P&$n1VmLs_8NIi@!|?<}v1X%xBH%th29U3S#P6p0@m9 zsbn2%eUCM@+4dnYb3FZCY;Wh7?^x=1#__15Co8O)qpD+pp^SbjHoL`QP-4;71 zw#2o{)zxKnz3P0#In4Q_^Ht|J&a+Ox)9pOu9P4cC{LxX?vC=-26_{f?XxnOg-?q)x z+x9cN-Z9{2qNDH$kvzYPbc>XW>0lz(m<8KHveFMIsaAPOTL-DCw#Mf-F&SXp|JNa@8{n2-ud3H z-g4d|kKJqcKJZ-d6nKg~Chr~3ea~G_(Bt)_dfR%Zd&hcrdO!4ffU0-Bes3J3IGnLw zN=w!y#bLcBW^7B>W+R7~vK zZ~{}n*~f#K@4}}}Bk-_&(HElarVge_rq4}xOf}7G%{R>rzy`jJae*C-w9L2sZs}uv z(R#@`(Du8nr+vG^kvg@JPy|H&0{E;$c4$Mf?!|8$;q_Hb=-CA!u*M>{(?w>dtwudt7?PqD|?|FK1Exwb*J zXRT!zy+Dkc9q(Q9B(uf*t!by}VN-R}w&?4~2)akD!Q1mU>=$oF+C+-O`@*%td7&+g z-SAMk(3Rln;2Yo_W_G<6frd zo##&RjP?BDN%KDGJ& znD3@jcqixzxr60HQ<+&GhR%lihxdkm2eV9% zv}fMcio~PJbuD};Y+}uQ7`Yi4h6m|@vFo)&2gZ-DhP+}IdKz2U@%o^-mKRkOY<&=3 zi>Be}ZZ}wYSJP5cfoY2QPjkbVH8HnhS^*CaSbny6EUm0_tsh&hwqCXj`vGv1R~%EE zKRLtBd9F*Y1FmG(GN;w~m*bSg+m-WgvvZb0l;u1T)y zuEDOuE_>|Y*jHj#Got5X3thiEn>wSMnT{m8&Hk|cE&FbcpZL9OYhayf`6K2X^9IvI z)61qgriV;bpbW0!Yic$8S*!+%lZ#JA7DkqYzXm^P8Tu!@;I*hD*CsAL%!(G_YVYv zJRQgk&JVtAjMx{!3&Eec$_lmslYAM>rE7Rg_{H!?;d9Uj$HK3LuY|3UxJX$jg0~~@ zN4}1H8M#Ze=ImqNqF;r(heG*K(l zD$_rvHs-g@LGw8Fva*(mmd%z^mQ3p+>+9@e3v79|nfCkkcO4_0*PI(%wPH`kmWewT z+dTF)*8tah&N9yHjxNrDuCHQS#rI0+nb7-!d)r7qGy78%Tv9Tvzw_K0KZe|6> z#1+IXjsGSth)UIv*dDIB&V>$#<9qu-#_YbmzT<)2W9wpDV6A9z#gsR{VQLHoH``R- zzUYy4c9}HIrzYZSqQ!qVvFK{(*AkaK8A<#QeI}j6i+dmPU@@?Na zZ$sr?JfGPsFC{%cTdkQ&jU|I zZ&`1u_kw4!XE@`0(i7vI=Ka;%)py7jABv&}oIyl}*D<-7xVEu5PM>3@BWOQm-)f&}e+|rYqwOPWJY#miyvEdq5j%`uvU~V9y9_^L zT*={Ld+~tCe(<#K!5W_kmJfa!C>z-7ui@Y7n+v{Qn;q;5P_EGXoA-Hd8SgU~?UpkmCTbAvHt&06qdrilSj^~^$T~}R~VpHRPjqjh3o$yk^>iErZ-dL}z zsjGwQSJ(To2jlY+=Oiylsb6M#nSWExCofCdn=ml`>$rMx9b#w14vBjz?xnbX@tqT# zI2o=L_gQS^*i+65j%V$S9W5QJ?d6~(zOO{%h z;&qYCNUiYQV1JJNf%8DY`hK&2kMB!w3L}*5Det-H?%)~fIpx0R-tKAUnc_ZB)T`)b zVY$L-1r-Xq6uet-wP0f5fTA~xJ}YYJp69OaK2h{-(PKs3iiQ;}Dr!+QqA0tlmwN{* za)~F~^MW_xZ4Z6z0dH*LKgb^Txc{ENQ=r6O54zx=K$T#d(5}$M(7WLttfFqLoH2~T zHAbjRqqM|#BFGVcGqW?Vd8a$GZffH5LB!ui1QvEysUbjMQGSvzYDONseyQ@`k&cpz*T^(TItKP`ESx(6bM!>2+og?S#!0(Jbe!R*d>dwak3)b|{5zvh0(eZ1&eQLOuJk{iyo_qcgZ@N#U-7js@m| zx%tcTrxausPAaspqBgqMx%asjxi`4Gy6d=8+(Xcm?+t212Ud^ng#p)NNd3kVP= z;TybsJq|M(4F^$yFj<9LML^GsJi3Lp*{ejGj00Ph=V+_7xP~%i)!b`9olq z_aajmtv}Ih?T6H7Ega?R@ErT1bFdG*q;2$x=!vFu^S99;#av5dcNv%_+dP;@lZb{7&cO?9t&_6LY@z;bv!mo_ki1;~i$79oD z*SR9jTh1-cryOPMS=N^=H_geWx6$$Mg;ufE(2u=YoLT%a9KCNsO@loGtNlxT{k=bW zR(cXV``riJHQk<~SBvrr7Z!FbOelQ4;H`q%g{uoE7p^IMw$M{Bp&*#QIKOs&bbjaj ztNAY$Bor<#yjHloXjM_?q7_BwiY^sp6?Fm+dDQccr<>R1d&`&Np8^G5J>C;hZ*gU$Ve#km;x~AlTUtD_xHnwGXNZGhgU6d!(ga@g)9|zZfLpFuuoa@- zL8rEU$?L@6NG<*#G7DM#tsC(4WE8!9Kyqf*(RH zR1b{_y%D+^>KA?!Dt$AQ+^pi?i#x$jehoUnQ!))Nl-r^+O)=)^7_+6&@{cvwRt&!J zgtN2jk=RCYZ^Vs^Uz_kk;@G4`$^WLLrq)WEo;E3UewnUix~5o@&Lq53k?je54H@Q3XTd)@%QzA=4<8M z&i+%g=zc+N{>J?0^XD=WALs4LtDm<#*ORj+r)|!boYA?5bN|hqkmt|4mDeHfO75Gv zLvmZ>cFCQa>&SaAFFk*2{zxF?rGlpmaNIVB+au-PVbGQlkQfg=mV^ThNCvZ1#6u!+J@TQ@G%h3JZ{1M-AX3H+$IbV{$ zE?CYV{+5B=z}|zw#jNZV;q&2%K+9kxmOST{bYZ3a9i3`EZ@wAxvUQcMx_z0$>Z%+2 zQS5VZ@d-5&*C&3Ql$5fn%-z)Zvfay7C||c+x3V9n4NUEwl9yDF^l{nB&N&!%s!IKIN}@)OG?r~Q!faB}seDG9sdYR3Hax9CRE<3;&}p@P8$H40|s zugVML_R9S^XIRdU55BzL_`dsI=DiQI2WKzNE_?5pdkydRd(bbZN$wm*>Al?TxuKk& zaxA$Wa`)%9$cyB?li#NxQn0e{kHXQw&yDUXo^L!qd4Kc$;D0UfT=2)xq44c+)5xb_ zcIlC5Rzm_(f#>(0X^m+uG{4LAEBwG`@LAm;`b(rjZGj49N|r(Cy_wJv*fA#(c_jlcIaQ~5ee`oK={^ai1I}7gQ+%esKccs2Sc z)JMCL+Qli6is7E2gHZHgXtyQ)IN3)ZvZGnhjYi}H(Vc}96Ne2@8?1ctG;Y625mM(j}AZQ0G2MCX{6#{6L!YJ1p` z>RJ@*iqA?|np7s`TA4j*Gs>MVKel4?N=qvLk+GwyKeKW52i1I;`!iQp9hgz4(xi&c zir-h*UE!IEH7eE2Xj{cmwS8tmraQA`W}T|DtK6%6G<{12OSw-{m!up>oE_gJ_8mvS zX0ay5OfkKn*s)L;@8YHS+o(+?uNGg6EDjy^Px8)k&ndi;cOz%VgL(Hm-#dHv?wvR8 zIPPq|ZN7c#*2A~U-hTXc%AFl|PTqYxyCnPky%zV&-oJmZ?!7J98QG5PJ9o|5m$D1) z72a=_lb~I`#yzAKJSmEdcXYOzN9{WanBl}mjT{e%^W;I)0jrqs?uIU6E zcX2>phL`(Gv8gyN@>eJ-loZ_Mf5&&)`>rR?eY)s&;mE>%1wHZ?45zs&uLHh$>w(-PH!x=u)eF zowaqpt+%(Hquz$P73#QauBrZ8rY-Y{D%UgGSE*E`Q`K6T$ExngY+G$ZX4TB7%pa@V ztz5H`qvDh0dZ#r?@h14=N}PY#(kw$w7fMRP$JvRJ{i}QtuLW*jXYVPu&3&t|dqHOY zwA=*`rsG(y+}-TkAKWtEeEoXt^_;7gt4Ff(u6%i=SJwEf%UJ`j#X9u?U?JR z>DXjnWUB}EdeMA4dQ{Y&;@HR^p}xW2{Ehs#y{~%ayK5J9E^JzGFRxSHm$~^leRFaj zOnOjozuo~EU%ITMzo7bzL zpfJyU(VORw4mFLOD;^h>YN}`+9P^juS?dS3V~$O(`>_||M<=aJ`6{(sxegVEq@S;x zU*&A(x$13db*%GO-RcdB8#Zb*tjW=)H=A~E+M$W3(Y%HW>&>pcwT8L+-pseFykDib z%IPX&s`|M4t%^6}sf_f>AE)n3FH@m)Syx(Y%9jZ}F&@<_F8b4-z(Y zB+|3EeaZV#>81hZKuizYR(m&Rx7hJ<-^JHXT#*=;bUf*F(%GcsH2@y&Rn~7t@8C2*N0s{bN%x5s2jU)ynQ43 z=8Bv5ZvJpq2_h?$J`TUl1 zTm97fdh1oKdbE71d3uv&4eQt2S?lBKQ!=k-l&S2h^i9Q<6}p!%D*GVqYU;Ey^HZuP z*Ga0F@N4XH*C+PFmcizJB{xFv`fIp{>h9)S&t3Ojb!4r+T;uX@7pq=ucCq1w z=nH}W+Fm$&VaUZ{mu_6jyj)+geym`G|I}#x^QjUjh(mp z-Q9HWXik;m^>)?tK@^p6;sNk+)jQxWn9V=Df3g3Qoc|AH0kNY#tF%BeOzrEE?Z+u zl(|Fnx{`sB>!CiuF8-&ySKX(IniPJWpPhF!*PQ!MPQ3@8-~a61wd~xxJMZkg-SF0& z8!^`xT^*D)?n=hxmoF{6wC>XDmp-_Za5?AlzAK%w=3o8dTEiPVZ;rp+^KMag>VxAs z2lBoy7+%!HbIm)`KQY)0+~Ib~0MnhA9P126fvaqMpTwcbL(2?Ft6i>Xh5G4eRVYx47QI28j=~YSgCjDArNg=IvXoZh5%XiPn|d_HSFi&AwJyEiN?u z>7irwhSwTWExpRF^z-HKl&z5FPU(^CNlZ@Y8h78-);Zf=$+p@u&`cbP5=Z2<;6dLU z_sxR&xykn`-Z^;VjjX$u-oCK#e9eE&|Bs`yfNm=5qHx}eyL;+xP@qtx$P7+#cXxMp zhvHt`eel8E9g5Z4G>s=O?)snkS69~zvu37gZtlD1p7ZU!{}_I~__hBR?U(3R<*!!1 zlmB!mn^sP$ykFg=wpe*xMN`W(W3?LX-?{|-dHo7~C&OH0D^tjvWOdq>I=gt*`Q8PV zg!R!~Xd-bRIgOUiT+ANAoy5lp2_mNWsaPmED0wHjAXy{nCb=hOil>NH3%3ij{7<}k zZfEW$&SiEtwuLo|mCUMQHee2A45pV;2UCub#uL)fWZe01|6m(`si%)?l4Fjo(6ZQE zW|SLd=yJ8i>KXMnRGPY+y7W3$-7lq6$*TLToU6=JzNk&DrPl7O=~C0Krg_cW8d2@e z+HuN`b^TSR>#NkOwNBkd!!grl?6EaypWrI<e%@Bt_edDw!nsXQy&%;-pKLa8H8CwxL@;i%EU z5nT9PaBiTnznAZ^hv^nOU)dg63e5HT3ECm`9Oa+t?-i=Dv!(HW8vPy(>onz8@vmRM zCjQPV-Bey(kzYMZ$*QMlJLpduuVQbkvGy~L6jxj5rKfo!9*wugch-*t`QbGYRrCU~ z3qP3{NA^(~(Tka1*$%EqFj*8450RdgjgwOq(-rd+62%pHM3y0|keEdqg-n5;dxX7_ z>7mEa7?gn|0x^Ry7~d4V0XYf4V?~Q2-NX3Mq`+BUi6_(jm$S2dytN2hZMtPB*EQ09 zR43GLuiLGhUE8^)clFDv4OPpl9#=`LPgn1*SyU@iis6ZON%gjVkD8-N*9Nt_b)EFp z`u&C?<6u*B^E|B1BDQU`w|2T+Mi0|(4`hXpM}Hs`LI=`L$_-jMBbB|H`+{#3@+GZh zK6#6n1+hcpoN=?`f5j6M#w5HRg_jYHvwmDnFGh?&mrplhnp)^)4Rsh(VUzI;d7oKjI~)6&?|@uiK+ z_LSQy22{(H^Hn;{BEuH*cIyU**!{_S+V2RA42^{@+eT124}tspecXDa13Ch~g>ZxT zo#Z0VrZ%LXWz1(y;%w!O7Q~Bgi9bq?NTk@9Te@2> zTV*zq{f+&a!E8y?^Pu)a6$2`YA!>!^!5xy4R zk~WneRBVmu6MH-MQ*2S}irCh%hL{O4?P4~@%!v6IvpaTl+>ZDg2|p6MBrizWkXn=W zIo+A@KJ$6jlkDX=e>FJR;9<^(tmPSo6lX$}VukoD?=*7`wHIkQell)-hOsB2O924E&eT#$b!~aG=_lElm<|jW9DeyX3Ky``mIQ%#~ zi!cwUA}z5Uxt?;Ic82ks73OGo?*uZDPgEi9CGm+@i1Wn5M2W)h{Jy+{oP(@t`g-au z(sO(mcPP>$G(2GN#rq1q_dIhw9`|?X2j7Qo;dtjUM~&TVJ7TT0lwzCBxhB-OLr>8? z)U;BI>iMdnbtdI6Wwo+rU1?p8>Z@v5eMvn}ZBu(RzqBFUT!Yd$*t`;3VcBKPvpuo( zwcoY-?M03j&Wp|iuEXv_@N8)3|0mEUbRzsaG89*W2=EsOdr8eHU#K7G$C>f$T^u5B z7Qajo5DpgakqnWVrR!xMp(8X?Hbq8|S)^WRnrxfQDl3-nR20Wth~>ohOE{ExB8iz& znQ|xfSXyCvVaAt?Cz&}}Rhh{d;pjok?%U2?_B87Wv(s>0`=@@RYN7IXO>qsQCck=n^~q{O zwYH|eGElck?bdxVPOt>**IiEUtU&7!J90AG1Sm-|)IUHIT}P&&Q{a{w z3%s42gi!j^S~AA4u5*fbN0?^_OJ<#xw6XwlI9rjnXPKIJHH! zOVvSThjqHCDyYY+FRO=Y8fY_h4&6Ga=@u9tnQF}!ux%EHwXe-)n{K~l583&SHI6sX zD{1YjaZx>8y`y}Yfla}>(5i?w`W}(u_Ys_*ui0NXmE3mx)`GRd zaiWOmqqtt2CTRz!?lcy4aey8n{`_Pg;_^IAv1mptQ_% zM#lS$#+mOjUuTx3cS=2-bUaQcBa8NM1$0rKc!7i@eI^Nr;dd2!`S-Xc?t6Xo{wr^`JFmNwzYqGb~#yZ!AA8U9G>ZZ*AY~pBw{R zHuo3rRey2N6aE|>jU2~|iHk|k$Z86Yc7WEK?xTwtI~fkf8YY#sj^$uSIW{hi@8l;6 zZwL!S7Li1}S+!b}Rwuqn?31KVT9(`-g`RpXbyQkb z`ttPeX%ACqNlW4gim#$Vu9$h1DkWXOm!O|PH*@6w#@q9l*Moy6ZP<8rG-Rp|#g**4AvQ8DBH8cCRv|yr;@k@7H!TtTlhO_H+*O z-1PMbx1x@meJjA0s%dWONy$(S3(3h$z}((z(ij~3&2s0JS-cBj0cO=n6teRwzde+mB< zWs3F*^Mr#15#DI7fgNJrq|c?cCgX`1s>BV827$3+gk}cc2hIeNfUOt>)5QnC5?l(b z_e*?JJn639j@7nr*ih4ALqA=b=1Kj1mA7tV-SxVSs;Tw)>d6|jc96cg;k8k0eqrv0 z4Ys_tT(*3$)L2$nh?a@4T06{}O=+fl)8D4MrY&ZYWsP;9{iXAl8|IsV3Gu`k#FfDHHX<^K2$4)|NBl$_MH)obQYO%{82wqNIR8Qgl`DE8J}4P3Ete+A zII=8RfhZF)f(hFVmD6pZO+j ze9D@HiHb(z>)ca}>l7jJHVP+7lpPVlJat*f6y$`O0$X_uW;cXTB;X2U2hx3`Jx=Fu z`*%x)akhS*roC#S@?=eUb<^tW)yHaH)x;=E>TLC2wQl`K^9I`jC)K;yUl1$~%?ek9 zwV{R~RWJaPhA+MgzGX0DI2KTZE`?3eM6?0HLL5TrPe016V0Yl%6)?mH#p}fXh*{#T z!gc(&TqFBB(@1|v?MpsO^x*mE3fy?`uo(zb+@(MOy2G5v7?}KRfVtrG@ZX`)flpqT zn>h{E1k7x_uJ54TsUA|FsOnP}Ti3m=vhJ;_qP|GO(k1JE8@8HennysV%V;rJ9$7|N zE@NbDn)x4-5Bf6=jZKZY#=ni9j5PBsENCIy$2gj-7W)cthMsx>lx;_m`*1Ps>=cHPSzl z$&&Sw<&qwf7m`iVJef$|N3l5Oc&s&UNy3N3*2(Wu8l{cQc%1n=>s>ZAD>*%s?2el& zJH$W6yhwRT5TKUmr$|oJ7v2}Hf@jxUV6ps>{}Rp4NAE>Ohx0<1|CRTd`?%w+b(`6z zZ=!iomsRVlQdYjH%&gj9om=Zt&aUTZEV^IDeiog5i|d>BWFQi}3KNGzp+mv6z!1OJ zYxnH(oNzyKKk|I@di;|@v}iIqp4fphg4UL)VL#_G1a@Jfc&g-ezEgp+P;TUA)DAuL4A3xiM>#-PW+GRSDWq|@ z^TBvv2>->U6B4MOm|GZl#smIa;Ud8oQL2omh?l1+HpG0Cl}L}vq_XQ`sc5uVB$0}r zO2*5s$(-_e@El|$G)+oKDM2WD5Vq?YWyr&S}Ii6rc zD-aW?d4__n;x;lB<_-1uS3v5F!%NZrh&u8p)Fi0$zH$j1&#YHWd$jx1|JHq~POE-Y zwWexcb^qFux_|2TYVPQNnaV6X>NfJ{ejT-0L6Z!+XcLr@FekI=e!y zR$hy5XK-1Rf?5enC@*O|=0)~1?iv0%;SJGpahxPuJW7}U z729M@wO2U~xbC|bdt>}*fw@6YwS?bAo<@hl9MKKAujj#;@G*E9?gSR45_b?;g&xK; ziCWSHxS8x>tYNj`eBm|2uCS?*Pxj+sscAd>4pB>%`9_ z17x4%i((GM_KnvhTuXE&^-S@l+)90wHZ|>5azTPIcDhW-U&&fY9YE-fI&qs|)}4iP z1oYp&p_c zqW@xiY0j}Va;CaJdV2+TgelQDZ?RnzOPDIHL+x>Zl=J7{RS zL;5)AOq@2iwa{Sx+{RvJe`(KlOm{r6CjAle8$oLX6d!WUzTJrGAv zXps0ak(m5F`CxKZk}JMVT()ApID^-Y8Kj;e8wt{>m zAUTG#8D@4r&_vwh2rF#%v%C*oukDkqN1#5uU>I&JH^!NMn$KFc+2~HCYoO<{?_9tL zYxOPy$v;5SoCc=wc6f9!%g^ih@`*H?cm=->X&Jo|dLQ`XOY?F(gWXSE-Q9;h{k R?pf^uNsc?@O;t9lT(i3uL>Ni>eV;@t^y3bz5N#%MtKRG)& zGR|7|ch()&aaNc~Un@H(KZbO_(JWXQ3G@_XLgvO^IrcI#{X#vot^d{vKw-9#W$Dtn3 z8r}%&15^BTFY0RKaN15=Kfv$)41V`<&a3Wx?;(HhV2jZ4@UlpA(46iDpT6_J1>Fzz z39br^@~`r>^{oS5;fAjSI#LZo&%?8$4Uk-PB>oAZ3lOX*OrHb9El^qYCA5bpo*20l z)rY5t+6JqEx_RuK0t%u5-Z|b(9}d`2kAGBPXW)5YLqH65;6vX6Zy30#70ynMK6bpV zjrFL-kCkFxYyq|uCePKDLDo#$Pt6MLNUevk=sxBFGEBj8wrBxf(f!bVS|(Gnj!h z03D-`_5>y3JlK6YvT{_tnHv28c56`_9h-95=fnZji&(t zsw1}m{XWJHFzYJ0ugpCvM16j z@(AkBLm?ZGm{$VB18)CU{|*05P!c`x-~Ydo=!K8y8{i%5Y2hB~dgzRC{sB6zlk=Cof*!#j+=I#{ibb+Z617`w5e@)yVOp!*V|6nhT536GuFx0KGxpW zjn<&`oNXmccCR`9a^7}gz)L-GZge(wes^?pgzaCUFUWLsa=dkP2b!w4>yFFiYTzF2 z-r;@%G&ceYifNv2o_w#}yTa%8E%yKP_X=zRW!sg&OJFXXfpp-2ufa^eI&>(!JhCV{ z497uUz&ZOKZH+&LuZLUOY63>+K`bNwL)t}NOIZVqQzv>X<11q#lgK*5YR`7Vj%&xY zaDVXD^BV|$3c3mR2yY8F2)79@3T;BJsE25?$Sj&F#>7K`Oxq|mNu5%Q^t^PmlqKCE zNs_!0&l5KnYei>8<3-7$XTraQ6@uP^1N)0(Jcoc@L_}FmOZa@W}9&(5TRjAQBuB*ydmETjE^?Yta#Q#YacB<6rw= zJHu|Wows$jIjkG3Wfq}j0(QuJ+axk=F=iM^#w6ohW0f)0G}g4xw7}HW#50`-^5(c9 z#qe6cRliQZTW`>hG2o4R;R(G2c#nTf=S*`5xytm$sL7lXj6dm-di8kj{`%<=f>f#T>;K zMM#kmlOK~4<5z4{q$|G4`^pt^uk5nyy6h%=ACR?|y^_XAM@YtrlSLw-AJ(TQw=JiT zeT~Hel8^>9YCXL#y@ckWex_8CSCE**PxzPUN@NG_E_jWui6D_x;WE%l?Fc>z*g?bf z3VK6}-P2vmozER&$7TB%dxG6u6J2XIssdPL`us*gV}_4(pK$YZPN@Wtw4{ zZ`x!!W?F9QWC|D`8z&kQj0B^~;5VciXBodh-SFJh*}UIeZbmR37BV}bHm@^>%uTSx z*hj3NrOdL)N(T~7U|$G3$7WYJ8asMA_5$f5cW!fros)o1>f?S0RQ49n9go(71LasJ zP_NwgCi~|2JiZBjjel~$6qp%Q2d9L7gxZ8p!+pOuP@oCm5zq~so>wACphTXI7ZY9+ z2EtQsFR3Beuxti67-H?NDzjJT$jzXO{NjO#19Que3$quPc z+8w42LuCzQDY9nJ<#;I5$QsHw%N_FJibo10COw80^GmTzL07Dk6XnG+lI*N>j&!ng zgw!e7C`pw(7V}}oUoF_l@65Zy=?A3s0_GBii9QY(`sK7-+9#@jnn-Ctrjy&Y+{vQ39Xc>GqZOBKsEmMteVd zoo$w_#F}EAYq^O@v7_cfb0_m8^CPp>+!fo5-NMSTyVxvP!6LKQG!-Z|j)@Jcr#GE6 z561A8eZX*xw9c{iuu843ExlncmIHT_4J_hFYYQ98{?R_c@!OH_9O2A!Qs8N~+A-PD z+p*aZbgXmsa#7vy+_ONRlIQK_{R@cv3eP4_3TTa5z^#z#{po$++w0#SXdcwS>Gd9n zw*WBL|7p?(hR=lyfEWcAK7@+m@nQUJID20c(@493Y;Q@vOirX!QtnZ&(!SHfj6&8N z_7ct*U^V0Twfv2ObYX>XCiHygh_&LL(2cqx`65Y|9+j3tM=D=7Kz0uL^e!0>Vs`y9 zyX?1YgRGCNwahHt4_zw;+^RoH`b!>(JByEtehI4u@A1-)$2y+_a8|a0$ zf;#9NaD$C09m#`9orq#W6e?H~cocw=DN+_51`3)5q1(ZhKyMLi%kyGPO}I*2YgXi%Q(v*ORS|HyNC_Lvau9wB=#69w3saytsQJ1 zZ7txFaLB&IKGB|M_rjk)*y?O-_-9EDH&lGXTwYf%VCMIN(xET#fL@mhs>2NTU3V`} zB5<2*AUbach6FzZ<3hth-FyMu1Pa6VK+7eM#^AD%?x4=vK$uQ!3u`l%Tutssc}QtU zeN8<>TTI`;IL{2ThHM4P*k?KQXdoS)9x%ohwyJnn<$5R$-ms9={u}j&qZJ zftAjh&2%viG8!^A)73x=kwvTZT=*#$(&D z*I0sOt0mTY&nmJFwq3A2hpHlAGuezbJ$z-s&b8QQIjoMEP9G3cpIl6LCag*)_bAW~ z&2nFJtK6+T|9CRJH@#hbuYLWXu4x!J6^ILN17*0ar-()H3q zQipVa>@@6?&hmHibXb`qieHM?iqnb-ijaJ#Tqb`cn;?_Ph_X^?jr6Byb9Fer=dEa zM%DuF+99+eI4dyRpXm#Gp141_JkETeA8$GK1Fb$CXuDiTDV#(lwtQgjepq@~USl(` z9@rRcK6W1qViHS^CC$ROe1>=2X=CSzADdv&S`GvMdEb^`Ukp2Dx_#*Xl?+N)8>21WKGm+VpLg^HwZjY8GlziM z{pP;sJ_(iFX7?3$rMm&FO>?N@p8JaYJp$z52GAal0iucuI*~8oNf93S>0HKLg4_HV z{5`@O;ziPWP%r+b458Yob7{>$)Aj+hQ{P!Gc6aV3-VZ)im@R4y;Z=Jim?TM>B=t$& z!>)NF=_tJ~Es)j77RXyF9x9>=MT{cmn_`l}FP|b;!b~zOt%kmCt&}8dD5J`rK^=Eh za!TAuWD{)ShqyC2t=L^ydztafAE0C%$q+KO)9Yw9>Ql-f@*m<%LM+|@H`pB98Bjv} zmvr?4RP8=cCUprF1a1D~zLDMxj|i&gGSEydc20F3cHVR@bM}PuXS`z{oH!C&PwQn% zCNR(Cm<4NY*=V^7XL_~eie(58k|VGW<^uCK(^69}(_GU-Qw#G`^9;<5jkbKZcr65L z3H*B_Ex8sluEK`Q|dk1G5S%)QMl(^W}gNA`~~j||FPh@@Th2(SS;Bl z(McFkBbQ08fu17=-sfG?GHDmtPw2)Dlovrgum=9hgY&B&Ed)CKDk#SLQ$~;zNFG8N{vG-hc?+I? z=fH*D0-EQUk$R}5FG3|yAE@`=@g4FOc`m`zdzmZ7#dRgPn!55_X|AyIv9l{wZxqLD zd#SCZ?TK|R@R04S-KUe2gy;@P>JIo!9r2)~8U$zA4#!&ibX#xhAj>~kSF9h@ za_=p7t>w0KM_1?Hpj(x~Nnha_>q1;B;pYb(9=PxPa&C1sfgO|XZR>06?-rO6yb zu0#hTL+~4jZgLK79^)nJ2WKVEz|RqO7o|Xjbq8pO4zm6*DS8mo3+#hmByLMyoVq4` zWu`E@CVOa(GDn&-J?m5Yn-o>zv^a~ryL6tID!L)C@%Qud`2Bb+?lE>A>n)=(y)AVS zIgunIs_^^KXGlY26|QB}89o=P59Ir3?hkg4WuE!GVW;k(cC%KjOV>{^;7!-fLF}Pr zm9>ZM6A=5&?bUXHGv9@}`+Hp8IsP+&o55|N#BhB0ZYU*mKez~{asz{xgIH*1Bo+4* zIfvg!yg?cV&L(!U7Bs@kNF~6mBE;^5E%>h}8?6GWDo+4}<8r>W@2A(A^KyPXV zuYr=l4*yRt-otV4gv!VOpVUm(71smMk!^GqIeOblFpu$~-lYAbA!+7nCh2U3cjgn; zk-!Jpy}SLt11m!)q);$%6Od{6zC7bgr0Q7Jll7UbJQ-f(ZU*tv@G=PD5z9TT}9Sd`w>)1S#%;44*Xuj3I zR^_S4sy-^MYG3_y^(IYcEvCJqTcTeI+JW!JGSeCJYHSlsN(b3Sfp&C+GtNbG$HRGG z@w$D@p~tBW6~Mfp5T5->1kk6GW|6ZfhbcJfBI;#o4Y)rzsXwUC;FWcyvZyK4mejt~ zQ`8vRQ`$s&7GoT9CF>Qt755%bEVwHCTijPNTDn(;DykK`Vy?%Q#C?x1PIwGc)9c9< zDMhI(Q@f-TB!%MhW8R3_+k`M!SO3bo(2(0A5*+%0e}v*lv53{$n=)ExCjm~N#)1fj_8cOm}PAw#4Cl7vqt zb|p8WrqG$p3RZW{1a4#AI^GQ44DKGzYIY41qo>oJQs%;Eokki%dP(|0YE0e=^Lzoh zFX;sF0pSpSBb+n)afV2zaQEN?pT=G67-H3$M(SH=KC4zM57+!vJ*SFP6|5vy?XK!s z{k8f{O|Z6Q-7QtI`jIxOpK5xGCEI#AG|skAsj59Qy+rV1lKRblD(Gd5;c?M6$S1Tn zK~J1UK1aDt9SJ?cyL2|F!e_(Ra7KGZ5Zp5=L7_^aiYO?VMe0jDLC7K4@HoQX;NkU? zaEMq;x<}T)Cn}xcW$s~bmwP0rOoZsl};5qH;<{azX0zC!_JZsze z>U{0}v*B#L0dwm-`%`SYp{F)M?N&uq)74a617jcTn$79td3*Yw1eZnzB4-J1@;3Te zRveee?;r>Zss#i=3SY%t$q}-DGAA&qY5!8MP!5w1%1LsV67~az43;^bdGkkO)b532KI0)7i*@@SD)wP%e0}aDkdQ z8{L82!+#)J$w4Zf@sW9*J(26=tp|d8p5(ghwW3=bDREfx%G5~unXI-s=A3>xSF(1c zJCd|9-$icLUGf7YHQX+60Z62yzT+^%8tXN=iOwdr>y{f>Lu?hMwp7`+JG!|z2lYju=%p#y*5q#LG?g&rQWN?v=v3(W^VwT7XS z!8-x5pX;r3%?0Dzwb*>qctb1QK+U##qAFWCqNboarYf!SK*jM2L1k%W$LdbCd+Jp6 z*R*2eKx`)P3ZFb>{ym|LD5$~kdx$u)9CX|tsd!pGZ39gYo<8&FmGoYW$&B%gQ4A-2 zIQ;;88jR2-Z9=|BYEAk?Tu5wB{0g7-p)kj2ig=?tA|t|MLX(4E0*Aq~BP|#X#)LOV zAlDE*L^w-2Lit9kVkE;8s6D?(s1(1J?TVQge=O-?>f4N2+3be98kIHf*|>WyHzy-~ zPQq#FG;R@fCTa`a_FKJ(ce*FdGtRxpSz!ZJqKRyHu2buF8}^xmmI~Vl=MeWwkKF6@ zl(+@18}=zyz4C%KMYtV>v)~k} zlLlp9<)ak5n3XYmW8NuV$sN*GVikWXht3>B{XzVNig2bd5_$?`(?b7sUo!BH2R-B6 z*{&ML3Offr33sqU^9JKNeW|uwy;OBX*|#>i2C3dvb)xE4)t9QF)thTR)OMq`G~-+1_p`n@}R=lq`n z!$UJ7AvL_yAhYxanxR6bz>LJ`%OCIJEPxt!8F7C3fpfja7=fL{9VIk$Pm(7+Fq8DdtUHI zbXW30_FHi#c6B^45lt#c+L%}uKRT8t&k(oZ=dp9>>Ez@1CAdRjZD5PuYIWO54 z+xlAbEJc_YJ7dl^zcaNqtu%Hq5a8WErCF>#R$r=WsajU|RoPrwR=W+zDm?^VKT_RN z_tZ5r?!YQ-J6-F1y+WGkaQr(GkygTJ$`uRvEzX_Tq))Tm&9UbD^Gox_ z_yn8ezVOZ23{OJ>Ub~!KFD$I-ZE7W;)<7%!|>8e)M{7^ou=jjF;-R42o zc>8C^D`y^5;@_b<&U0kiakgyhCCetuQj5!?vQq6d=R>#0H$T)5mrVFTw$sX(w>h2o zCBie3JM#Up4HL#Cxstb~1 z(jL$sG#bobtvF|M&pv<4@IzcZo<`224Pi#u>v?s8w<4?fg`}m_BbgyNFE)u@3v&g7 zd969)VDf#JRE)oj`xd?yj1MIF<9!>wLGb!QJ-6ZBEOaa3CjA-ey%HA%JZtW`2f}PB z1JwS#0{7twv^Y|LOF+95mXTDHFZ8#pGT8Oil43>I_|#-k`l_t`4f-`|llQ#YKg|!c z$Y`-CKO^r$gVMCBxbxyoY#n(WVhVYDIQJ?07)y?^tLBSRRrR{Oq_j=xrm{B`E2_UK zi-Cw08{e3_SgUQxjyZ5E+T@bD_CkN*5!_!$m&w_|`7Zi-uoKTIm3 zHe=XWpSVv1Ps9!7mt!v{bWDy<6{N+c6{Ma^o}D-^&MjXgIVG6Q@zZaUXW^OANFdi+ z=zMRzWu9p4s_&~!(u`8isxMK!SN%|(R2^3()W@p5>W!L0?LbhdUelH6e(3J%w(2T% zGxR+TxyG3$BKFcU(>B_%!L`-%#WyWDA~Fw|OE^i!(X&|*&PiZ_ra(WfmGp|V68bfC zX^!Nk=&PUyFN6J+zK|j(iqX;0#Bd1go*H`}xEH%dImbEn!^H6tFzWANF16l1z=4C_ z$^_7#pLQ33qlwR(;O`sw92^&BMepMFp??ShvYQ%ZHIJeYJ&Ud2yi)DRv;O#TgSlHG6aYanZ~mAcBKWmEoC z{d)at?Vq}`OO?)=391PigYKe{gEg{RZMPkRTz%bt;AU0qxd+a0D)8$l0XBW9KRH+* zazsWUse~V-LBPv52EK40zd)2HU94CT*E+FT@{ANy%Fq;B5-ss1>w2e9GBRk{|MUiBa99Lg@r549(1|E zr$N_!zgce{4P7jORcU40_d2Z34EJ`=Tev~w1=a!4@+DG%W8;q#lPL~bE%O!U3f~|q zlfF}w#XU&el!8y6mRXv8u0dk%)kfbM&ufz2qTxniqz_+7I+ z8@!di&;CJy?}0(VWbhSw7iI#z+8IR%64C<7YZ}JLfa>@j9}(qCj>&2j*4Q)g;e>68 z-4jWa&Qz;Dy$D?tW8mNLQa}*)67q$o1>gB8{A}KDPLS1~xqzNRy-I3GxPsuL zvqHo`iFcKIsB?&Yne_(N!Q9jI(74rj#n{Vag&uAu<^p1QleN$`+5Xtk%Jta2&wI}Q zC+H2+artO#0)eC=o2g>PI#!VLl3y(9CKW2Qv9l9iB*mvTNLPaLsabZ5oQpYa8=P)1 zv%$2SvstgwH>A9a|0C-wc*<--{)M{|5V#xIN=?UfkL%|sKUd8tfB#4L``yofelGo` z`pqmmQE|JvMtQUTf)+E#Fs=2qW0gDP_4|(n`-Bfg4g!I}MR2GGt-}`*&y%t#U#Z9F z!JZCpJW= z;j_L6F1o!fW;Tq{omLN0T~tn~T~KqidPw!|YG%!vnz^<3x+0ZS{Z^yZeK*`R4Z(WD z`E=FM+LZ>+i-!KIfyB_}@P|l4+(v|iA4RYd&ydGZ5&Bcc3D!`~Pwr>le15uMyGB-St1MsORs z!)eUj&82{YnVr{?U(El)H}Nmby30(HPz;yZ9dED;2 zwcueiUbG*)J>;@@`D^($#m<=4vFBo$arfdr#PyAPAA2Zfh5VqTz3@CIiB2IliN5w9 zaaC9q<|c+yT87$N_pNqY^}U9TsPp`3!wOjGK3@KpVerRYZ63eHQTpnFhHZ^YQd@GuTAN3&Y9o$OGMbT{QIX*Ge04vyXnu>wuJgIuTWm}P)jYHXz6qg|r8rJkwYt-h)r ztjW|;bPD}pLyYOQd7ovIZ58NOT6$i={eDm2a_~fGd^js|H6jH!98P$`!z*oH%Ka22|z$88-HYF926y&pT7hOWR z3sdtSl*g1g6dQRdxei=NGl}U0AI^u~0dXM*|C0savLOV@dr^dgn~vDgLC}vGN;*uwP1#0W3bT^a;MKF9Rm{G`S;<|@ z>%jlS?;^M-UPh*55MJH}V%w ziL}8VAl!%jvX49rT&+8T`|d{SUT|<71yrYta)4qb&nN!|pZI6Qoy5^_*Ulm)0mpZe z(1j2JH~RCa3GsmA(c#F@usb+C5b;g*?rlQ&bVuEI&ZkM5nxvjOEBj9p)gMnw^RNPrqLpVx`g&r41!-HR}nrUIZWS;?W1}})U*Mi6zd&ApeqZ^mUFr2|O})$5 z#y-*0Ei@KAN_Nv%vYUY)4_!nT?}t-|BHtw!$=6H2iFXQj@N0PvUI1PZmH!tH<-TXf zvG20yakB)M#SHo87)R{9IDA|%=B#3>oFqdf>xH{{2zvbXQ?*m4HfK7_o`hgV^f6jN{79KX zKfxTxUdSPG!<+-05PLTJJu8ECl*s@W2_t=dg49xdDbu_Z>Go|R6noQN5+U?cEs+x*c6`d+P z6@^u!YZP^J)Es?*d4)~m8s>i=PD8ts8qyvyYdGV8ZI_GtL9PBrngI%!FwBb9Nx0%! zpeCNpoy?xjJWXdn-}E84!^|h8keJ{RlK@Uc5}-l15J|-Ggp)8a_<=kG@|zu55xN(6 zb=zRl&CvGO98nKd-%`)hJl3w(?=wcsXRJFN3)}~MM}yxYMu_Wj zl0VTFFyFGtyb^vtVS&gh@`xUb02vYN;Jdl4IWt((7}II?t%Vq zAhbK|47Z5%0Jplb@aXVAAq%+pzXlKJc7cY0VS#M{3~JdY!2+0>&x2Hhyl5(J1abz= zAZ#Ks;6%Dg{Rg^e6vk-i6?b5eVQ!7l;%FG_L%u{?M?S#o=o)C~Z|3Xm zz3!oTZn&4a*Sr65N8QDqTrbo2-B;%S&w;FAs1$e!O?X)3R)heF7Gojnc_hrd1;CTO zikyv{foJ`Xh#0a@-b9;1ic2zb4e5oN(OdYXg#N@vBsy6IL`6PK2!yn$v^liVv}D>% zaMB+^IZA#&dP95;#Q#=w0P+`35|u!1}l z!iAuHw}1~&f56u?C5J9zMq2>%;G zU|JarYz;JpE}YI^20DPIz$yO;{0);>ap-I)8C+ocfG_$=;IP(1`apl=B7&eD(GBP| zP~LRDVhs&1fj@l%-{J9*Cy{vY zo})m*jT2r+3DOkZk0SU^;I{P&)KWx33L%NmfFLAL2}YQg9S2YC_TY+Mh2DmrJ`&CQ z|7-hr;e+S!bI5d92&r;SA)`(4|17~UWUc*|%|ii-%nu%~ zPLYawLuOa*_05s*_p0{(6mxL_WKL?{>h*Ztr(xPw#T zq>vIZ9~k}jh!NEJjnPr)0&p9HDuNd zL=ur$_?#GVpZ}jwbQ$tH4?`~J7RYf}3K>I7A-!}yq;f8YbsGfVZNX{2DP#=&msR&) z!tsBJf`yQjHwqFsS3%an2}l)v1GzVNNLR@RU+=|`y>k>4OpoCG_>R;epWyoo@*KWS zK_<^ecmf^_h0Vv ze;I@QA^mgy{}V#j!h5?B5<<6w$7B(trv8`6v=MIOI*<}vAN(D>A1nnrG8qWgya*oLPM6@S zaBbjo))f5)yM7y9Mwkg-YY2CMl-LY8F6l7iX$3l66Zs~kFI7S#f?NG~=1$f(Hix^P zmmpXsd?#unIWCxP-6_zXqR8 z=uhZO=tUSp2;g@^&d&=%7U>;1M0rhJMq|@=(fczZ%s=c*ZkXGR7vYZN?&FkzEAe4= zEA}VmZTc|kbCLnS5-E=ki;NGo2$Xw!xVJiH+Axa+=*Ake&tx%*jBgCL3}cNSO>?jx z7Q79!72Eqm7oyBH-NW&%_b&=G36g=!aE7y@7jUJ>NmK+08;kMp@B=|xWFzWHTgfcS zT1p!#iDst#rvGLPW5TJ%lrV3=`O=5MWn806=v`@Isu40Xt`lbwn&2P6Nk@#Bg6V;Y zKA|@NzS2ThfpfLvg?*U4y?w6znmq<6*n04msD+yOh1=)Z=&J@_y2;>6cLH?%c2E~n z@JjHa(*x6biby3LB{9f5V7=YrlP^KV-h*65 z8cwQ(WTY2_RKf=Q8JJq!#65`KkGuhw)L5Xi{too^_xT@7R{`HP@^mH3Oks}0Nt33` zlqXZZ17M zI2Qw3zCI!iRy%(>hk}>k3H0~69@g{Jn*-mD9qc7;rGFyi#cT2(aL6pB9Pqf=j|k!% z;W6P+;bHJwWMkQmzNC||5#|Kl)KBUjSPPb-ze%SOsZj8RRH8DdNU9LMK`+R;&14-g z_e=%H>oI?<{}}uU?!aSN#_nKRGaum(SJ!9s{`TeqE8F9(?CXcNqVxU5TmrkpGGH7K zTyt=7{NXZznA`$vX!XgXq#AN+4dP-0sIJs#Y7Vs-{0+ybi_}NR{Uq>44H6Cz_7G-) zLn=}z7k&pHMlJ9uEEKd5#0m(3nS4fGBWECrR0rm*wm>({22;duZZ;Rh>DVj4xZlXG zMqiW2erFEBzqKwf;4{Gllf;BD1R}btG6QftA852Z()vCiF$8d9fU6h}uECp-y2;@G z+yPm7nnMS?`7VbJo zmJG4F4ML9yiwCl#}_la@T+*O+z0#fZ1Uj$2%+pF8Bt#iPHZV z%q4ZugH{7-@!!a|*FfXvS&3Icf`5jT8w?h%6~Owiz#c`SpKgGjX|P}|^oO(P3oZzr z2(Ckmc#o*f-Ga@6zQD&1z&k%BPeBs4f!r$qui9*IQYeW$^o)c2G2o^+3Z|QWh_y-O zG#o%Kzz*<+y$a0o$0)oC~0jvQDD52V97xX~=$pPT(m=Ec@6n(-LNaC&J67)`^5gXVBk~qA)cX*4h+XZCC zUcjmiMW6Wuta0=4c5VET(BDjuwy%(XSD?Y|1{U;5q^^Ox>=S%kNSv<#2|gZ7k4GT8 zjNlPYLJ!jmy~lp^%(>+~LkgLRr}ZPJW7&dbCra-)%JweoGk4H$odJ8mCFm08P(#;2 zpO}QV>jLhIN@Or8K?`^fl$9M|vL20`tAajWNR)!lE(d%GE5J563c1nM-x*AK^~$;N zszH;ei;{2VZ;s6kmse&gpX|kdQAaPetF*GPw+Fk1FiNdmJ84<_TxD`f9S+= zuD?0R$C1!2Cg3fHm+Kfa%lF4%e+-sU;K><{`acVtnltf>6{r_0z+A<%h8_e@<7IF( zp2wZ9qf|bEFOuh!{D`_;hAmttpfPC?A8W*${J`=H?03KMeLSz_6Vwl$JMt{hPk0{4 zZQvlBg>@#-qi29`ZWP{Q1emgVqNcaTQXkrSZRBDVFy>`qjRvbo82V-@(7-K-$uV;} z=+r-foNwjwaU`EBf^;dwwva19y(|G^nh|~%Mh?mVey8-Sp!tDF0{2m(CIzCdMEXOp zRRb|VjeP;GpgF1E$8oqbhu^#K6cd(0JV%T5JD!k_+E;+o7w#7{=zOe&TrpRQtrkz! z;%YDcv)FoY4*{Mm!}FE6y99AK3SfFi;(s)bgyNZjcyeg@w*}%|Bsdp|Yoh%@emVXJ z;o0CRWv6@dkOUGAWLZc z55zr#%a>?8E#4o6H3q8&DGi>PhPR2s)A@E5iYo&B5qP%B@5gtzIX~`dK(1J@@XyfV z=_cfNK39fq5zg_sRET{ea-;}vP+XoXR-{Yu#zlBzZF#QJxF3z&QTzEE;=eBpc@l%S zNI)vtpM*Ebz{;~FRz<41KLtqQRsE^(k4?c|6`V`MwF!9lRIHImCE;BY@oh=v-;{t~ z#h{l;#q}{b9*_NGq&5DE_&&ZyMd4m>uP)zD$N$PWn}Odq@P{Ln3OQ00=j!3ADCGVp z?kh_A1UH=Ph7s;xV0>#0&c>d=K+l4#tjSg8lt5ZY;u?WRi$biG6!AP7FbwwMMgc2* z43N(jVd=#+;97A_aD82_7WQU>ck39W*AqyuLgWOE()^C{{E2)e@h(ll#kn5RF9$p< z`Dj}_-|`3aO-I4ddkpJcFt`3fpCZTDrVhbQi+#K0`S!(LCRFeU3mU# z1vsZ2_}w4856||@b3uEs&ofI~(EfQw=*M{HE6}D-LJnUjXO;Z~3Go@vdyBKr&=+38 z8)k!*^deF@xb8lr!&AI7KQ1~3S;MpOF2Ta{D9H}GYngv-+3bLGY`^i2_)h=j5>K%<=y4uuTyx3 ztNurLrRtI=|HGZ{;Ayw<{|rj+`2Y7%ThL>zE$4gY zxwz+{rp!g@PelC~TwYgtgW0w{bctsEmgt|GqJ=j^Y!~0(G{%-+`K3j9pIsS!QFZh} zov^mR(e_x{q8D%a|0(`X{rx@AXZ0+vXMB(P5BjoUsDDGsYvd@@zL9wP80aKCgEh}l zy%PQ2I`CTXEaq$QuFJ8v2@L0(VFlQUHnR_9b_n%yD~|I#y%TW#SlBIw;2B-O(%T77 zYlWpg2a?!n$%AE|lvtr?KVPsQ)Jgk0Q-8m+!;E^O*CT=X-JOVeFkjJ?EEG z$XlM7n`baTj=VjK?PaW2a4Z{7Dd-(M|{Prf^l9#@~wZQjW!1r9ldIQ(p z!#n9Z3gy`3vI= z9b~P;Pvaax=wK_dLz3G4pb|n_01j&o5}Jf1iGwyv`;~;#PeI!w%56)Ya?6qkdlW28 zZv2wJ+77AjLW;(gzm89nxCe(l5*jsueu7`cIHyKRf`uUDxC7@G1Gk<}^LKRN`8<=h z1@HVnM)Ak^uItFTOUU6Jkf5tjE2g52jX(rmKeU847_YQMd#Zlx~Q7WTE|ZLTed-IybSr#cV<8@%j52JxeLR zPmgyJ5@A@P!F^p1nsF;I`}T)6J{Y{+qhZ0~nX@-x*;dZ2eI0hHJHSME1ZJKmV32+f z`_MVDkw`3y#`x)eqR13W+bc zR}t>?1?wx~Ev#w}iD!6H4xV)$>n$*FD!_`kQ=6s23pJSVg^k*!u@yxII{|!(3i0vnw`+{$IiDS9ge~$mpaTUM3 z!SWLSpW{xiald!?{|dG`{;vG-S6J>N#plRfJoz5>Fv(QDeGcC8S9rq*IKsbs4!-3& ztaW=ZuH6HR+g4&FMn1EN39!KpLaw(1CwV>S4LrX(uQ@nj-!ws9m!Ji_MSHkY-iD5# z6`g{{zXvjHE#&h~Ed0n~1J>=RM+dOKr@Y1T47pb!=O3e;hqz!3@mOu0k1~1R}94%ipB;(p6<-vwA^kmE7Z9fc>&0L$n^as<{9tP$<|mqVENxM{ugah8Hhzm29XrTvjWmh$VnYmE%MDw z*uY_1g8x6zF8K95TE+(~d01Z~haaN0{jXmBhZ@AMcaihAQF|Vh*PB~N--WIEE=uJ# z)*GlrH?id4|24FM8(8my(LEPe@b&C}*T29OZ*XtE6@0+@8R@sEiEnX~ul?`w48Ep+ zM(Q0}!29y$D_Y4nw8MAUdyDN`?7zV~JxA(4{5lug*SPZ?{C`;fj#rSrjc4XydkWZ5 zmx*Io_rq3m7^xFT?Ltl83E8m|XsR2KUI%;gHb|7!sP_wCBcF*{Hw9as<7gOa;V__3 z4FY;sf2`eM_wR(I3u0Hi~)r@)>Pc7j;Q?@%0##x@e|BM5c?P5JL5 zakU1=gRmEaH5q3kkcve*6570`{7hK+uY<7{iLpiKEP2}3T0Vynh}3{p`@2bPbo z0?SJ_8porM^Lz>M$HP!M5!jEzwb3XOz9yuUpNakdl1sobAW&g18CNCYH}R-P;ix$p z>?Ih$t+ zDL9&md!*wIG1z9{ZWYS+>L7Z!CeGDFP0lPIS2n=58qomPG{dv9u-6FlMYXYQfI8m* z^G3}ex!dEK#`ymi=9Q}ANFDSW4KXYCzv~;8%l4)?))-gRz_vPMeI4ATHJ;l7_ic*% zH^O_i#@p7!UDNSv0G#7!O}uy2|9`8>NT-y4Z7SX_6W3;7FAm3|u|}5v=f5}zcje0= z8ebh;p7T*?Q@mCo!}k&>10~L@P?BVM8OsR~N*Z)T(2rQu2M@Fm67|4?`oY&I{y$$E z38Db05~Pe+`TnZ}wf6_=CqH5=fhNdX#`w|UdmQ1%haaFL@?*X?<>SOx&~qN3|KmqX z&(M3`E!Q`BO*0$%<~3-ZH=%!>Lm$bHnlIrfuX|@hi@j8?haZDx%a6HFLias^|9h~1 z5dV2A;BKrtp!06P$b;{9H=_^UjsAEm(i`xfU$$Vpu@$3~r5KUSM|uTDEAuddT8?q| zI_P8kvBgMlf_}CFW0*xay9C$I#hDr9=^40c0Y(e6u{RsX7Gd-<2mkr={P<@g_9o!m zOk6X${LDNYorNQd%f}e}nUzSb#At)J5H5y}xfI)#<^KZw|&G>??Sb3df9dy~P$eXRm!GlQe#CiZZ zb^ymtBkvAjB(NWOd;)tXkl*L9Ttr!%L+RYasNe?nc+0`9@-fUkj0EnLkAD8cc>W&N zhZrkA#JJ!!O7$uJKSx==Ew=;kBLm)|Q;ItA15)sNdF{|51^?P|D=KeIHA9{{ASa!u zO+M5l-p=AdZ6gVOUYXb9dEGt)DJkl!2VP?mq{1MLqEU;ZAc11A<)uq1R=y>qqa{>8 ztKhBIRUrYYp{1mt?WLkU{;z#k!?}8Bhjq)>YUM4s0a{uuwAb3Wsuj*;p;v2%HrWz$ zFKu9#?udS`EzWd-PTq;=gBITz|NG&Z9yr?`$GhT8UmWX3^u)0NNDsjIKG5GgW7`?W z_}_NGQ)&_?n4yj#_Aj<1YYlj%RyR+xM>~GIHhVcR33Om5vm3aPz^2awN4r~G9kHbq zEQ z3a3het@jWpl4$~g;4AXzAL1hF&jXCTU-{FK4@)s;EfTE5{8$*(lv2R+Zwcn)7K73W1ilOk@;GT_T~7W0y)tN{lGy<{V@04xAa$WqALOvt(IXo*>PN)I3v zje)hYJ8U59an$3l3|)K(FgYhWj!>1PxeB{;JJ>U>ad8+$W^>EYhC(0< zYM^h|`iDa!n+@NCF3@Y&!Ox&7`4wK-H&Nf)Bjz#``MBBtg4>FAc^L2b5f+nXXqPu3 z4KmPD4E|R{O)>>K{2F{`IJEMk&~*i5U$g+ehQ{G8nM7-}w?cmbHyTgwjnV0C*m8B; zEsRI+__v_ER^XSN;dzn{&zOPaZBh?@>=w|c<1sdxhgNnB5`Gk{(ql08*$LVA5A011 zu$SiVfY$!mKLY&B&xpDhQCz{up&LdeEcD9C=-U#A6R;}@F-p$DI5`XXmJP{~4XZ$F z=(b_xb?D_2k+;KuoO=Q2p|O+|J_GfDpF17?I=i9!f51ri9Y#a3WLxxTTQGt<4Xyha z-Z>BXq`@dfkDPN7OE9xJ5hJ2@_(ezbZ+jre#K5Dp}Z#f!A8$zcZPy7V7=YGu8 zWs@xhHk48^d4Zfj%E&%`4dx1IFjS8Lukj9G2gRd4zDG%q@xS91LOy2U*-wZqK>l1I zXeGESm_ua=q{I(cac_ginglM)61Fa^=o9^&QA=uIE)q6Hq7C^Ob+LpfA?5g{Z-UQo zqn?j9-T+jopsO zzcb*3jbTptHo=GEoo@*IGCr~^xF`Nz=(W3JjxZZJ&2#omVrRfpq7r-#&s*|LVq|psT>$!_(MXlNrVx@HZzqk~+aws-7@fbX*iE%AgX+LH=g!2Cszv z=nQhsaOSu=(}eevZ#O&7-;$g`EfuYWC-EU^oczB046s3K$&N{KL~p43Zss;PZy&*8+6gkCKjz|V!nQRE`pHtf?P8=U z_zLA?UeLz1fh^sKHX}xl+5k&y^pAZYO_pIC%G-9{!OCRCIJ*p^Xfy0eKe1m0tuPF; z<9)~>7{^z}2)+s#fM`|TCUhMm&8AqQAnEU-O%-sjG57d{d&-5vMmquhga^{B4YcD_ zKnNmjQkY5yvx~r$9pV=LW#5;U;DgQ(#SR4!KDBKXdaj ze-?}LJ-9*KILwq?Ku!n3*1ZceA(bFw_hRJ!4Q2HOR)M$3FCnZJ%~8I0NdvqQ;{;XF zzfDK)c3tpC;1Niu20&XsLFJ=09u`K4ngQ$diRiZIis%;p{|3(74S40$6)zUw66cCT z;CG{yNWl-AD|JXCfdMg6wpw;Twpcb0p5>2Zd5Dl0fk=co@~!fV^8NBJ@;yNAsVFzg z9EhG^q)O>Ku@;D>LnumMCSLmQ0Ojg2a*be*GYgmw%xGv3-596u7CgO{BL4N9x25-< zXSe4s;KmK0jqc6vRJYOf9XN+uT!p}6%XSZ=Ep$(?u@-xG`5J<|dIIZW+oS%U#KB&MVIC&aptaI_7-r{NQ}%eD0*3MyJ8)aHhEiL6*FC zrMl<1i`)a~%fO?}^Gxy*zIE_g3xO}l5cJGH{kP!n(gL#O4RsKBi!$*MabK|0Z<1!p zF3Z}>!xcvqwUl3#+f=>P^#ZH`3j?bKB?8UwSxB!?Rajow_i#<*ugLdNkHEk@H+G6< zEPT?1395vg_~n|J(diKxAsf^mWD`VELAL)f+W?ZT5S%%yU8|g4u#&fOJaO~|bG6nv z-Fet?-agMZ)*5elWIAD-XUNnWwJ*w2%TAQuE?rf2MQhS^Hb_ljmLOX@$1gU# zLN;G|7f}{FL}9|-g4M)tcnfCx8hf+pWOo~<-}cT@VoEmN)8Ero*FDfa)V|dgX|HK7 zYhBvmy6XC7h9Sn0ratCW%X`ZR>tD8Sw*TxFM=RG7_X+y2XT5iruRlD7mvMLe6_K;8 zs08rH&lPb%vbF%l_lpXJe<&wpx{mk-f1=^+a%Qb>mv^ft zmu^Pia)%@SevoUD>lSdTW85v=8{BW*W-$LYq7MO~X^W?Ux1!JC+rhMlzu!UsXF@I5 zPc;;M6d#rTD>o~w@K|3K)IQ{S=#ucD$VE{{qgTaDj9s9S#Py7O9`_|KGhP|rHC`8I z)HIF_j;d6A1Td;GyJ=Yp-nFT(?1j7)0t}aKnStkdoY>FY#*uu0Q1k|T( zXYH>Y$u76+lzR|;j&A39;#mX?-zvT%@Br1oqdJq-virDZuphl9){%9f*#}T-sOMBE zHCgyr_*G~Zazc&hnQ$n)*lJQcfvvp*x}Szv2knkWVcUtG#>~}2kCKAkVIl62L}ds& zie`xS0li!&tEE_?e4}a~a3^p;urH)b*z54hksYG`LcE73hKU`esT_AJ?o528gr*6< z;*FZHn1&IHf;*@tOF{$<*@~WSt_F_!w)vKWCJyLqMY;~Umbx#x5BeD617K6vH5CAz z)n}M)(CZKB`|2y|zv_PIuIW1KzUs>K-wiQfm=Rff+ZTh6q`5D^-=113$yD4{9}apE zQZdXEb|$<}M3aaL5e4A|VG*I3!S;Zwss)OkGQH%N*eNmzzfl#bnSvSQK8$KwLBGv` zKig1l6kF!2=$%9Va1C(Ivv;>PF;_RL^{>mWmDDWO6fH08P`IOzDmqzow3seAQFdHs zGK@u3&=!Zw<)%x$)0sDHSN~?h0`JuSsL{e5@M+vEJb~?NVMEb5QGj@~__+AHI84$= zvPtq-;*vy2719@yzLFh4nRW=R@M?TQt|b)y$?RueCGR?#bl-7KM4a{z+j(mRtIhJ= z(!iQ$eP?snGo7$3#-8^kkS&(+TwyQ2v-`TJPk+DmFE;+(-%5v0l z&a%SN)$+%D#yrj(X8vw^VXAEYWX`cjY)|aV!JJUy*~pCc9~Hb5eUZ*nd{eRD5j+?? zE2JbOAk-Gp0X&+Dpv3{}RZ|qLWL+flL`T3laRAX`Q@NdpA!x${16ivB(+Jow2bldB z2XnrGz|FYe*-Ni-N4eHG^ftM5vDs)mZ&<0npbOKz)xH5g`exnV`l$w^al3hhb)kK| za~9&-A9~F`lKljfL<#2jUcmlZ2O1e~kL&_Z@+Z)1*OC-i&z)e}-he!9Df~}ZQM6q2 z8Th?Hh{q`t%@chR3WYIL3qdj|CM=wX&0==}x5kP1ieU7!UziK*7H+%$GQp9Hs8GZe z8YI(YE}(^kst*P92S(|U&|cwe#ImT<;7}Z`xfADzpOdgMaePunavr$BsMLok)})3B zlVUGM2!h8b8jCvk`+K{)BJ4i1-LOFSqHJ{O#FFpDbxS6d9w>{@1?km52c2lxVt8y& z8k-vX8ao;*8jB6<3}yP;da)tZ*w%ESEay}sf~tmI3;z?j zJvu73vu1SMpt#){XUu<5(g-%h5!h4xLvca2AL9Wn6(HzKeBq9>?@$(Re5t;{K!tlk ze}IlZ57=EZ?XzrNOAm8TV|Be!D=+O-Tv+(NfX<(uUn_q{ey4&hg(=0IN>g<8jH4~x z?F!dJy1wr$OM>_JFQG%!RMHw4h+Sp9Wz}SVq&K8nq@$&w(tFT|21y!AA|+KM>A;Nw zGQ2n$Xr4D9wcLUzAlFnODF1G@GWZKRc^25U`umcadvWxJa4_z7zww{A4d)stfW>W^7T2&hm(3`wETzSp0YqyKVW8HSa5zw zX_zzOMbxsGz8Xi|+JyW>cT!}^_7q!6a4MH_DMg!{oV+lpIMJT4B0g60E9znx7dTzf zUi6GRNQc^onA+(Mmpm#wpZ`zZ@;{6Il>SlV?{^*sI}H)uGtK9%q@#;# z75&irnt97DA$(*E5VAH4BSi_KR1pJSnv<|U)kK->hsXOFpu|TB2ZEC&RMZVD3Ufq# zMU_Ok!qLKql!P+Fi@c}5kX^(i_`U+|>ayDj9>2HF0nVDv?#_MARxZZ%%Uwc8dOP|y zGcVcd{w46HuR<*r-W55-&7_-TGR1CXq&htCanQMtBVmUk?nj-8nW@riAj8uP#RY~RvPstY+P{TfIwxMthaay z^^2I!%|LCP?>R&V(Rn|H88!Yom z*GpN+R5(+N5EqFyiu#EvihjV~e!8$DG!YT(h6kv&)Lq!Ro&tY$JhYI-KBH$4dceWX z&-Mkj?$$Pzhu|G(ZmwybWp4VZO722dJQcfi>F`dJg1bmQGCYIA{t$Xw{OQsZ5|8weR$dBdU&wlUobUD->2baxTr+ZqFkbqf z;_ra4kWt~+qNd08jr$(|BB6F-SYk%PthoBIEu%Ey?}Psia4D`xvqc4hIYa{I@x=kR zXr&uCm@d}&1$?GEoXedZoTPI&5MtiiK3Tt7t^v#FH{#v@)h*WUDSKY3DSca#UlI%C zmTqO+wNd)7hG6q>tH(akRYWiLnb>$jiHN=gQJnaMSR?5r$%16NARZ%*0Taey(Fn9A zH~N|CqUoYzq7R}{kzA}6e-(8VtweO+4d@34iN=02yBOS^KRnIoTvsP&ZAV@EN?X2l zB@nlF1Cw$+cpsWOdOMG}plf?-`%W@3+)6)9G#1Q2#B!;K0-pI)S&p1lj8%~VHv@+T z_Ya*AzAbWR^tV`3TwcPHq#qieSw!XxA0K$hSH(h`g+>%)l}0` z$2!|O5jhrRsbk)5>S3Zy(UyE`vAq`9Y&!cIaofpOBDeGxT*Q2#t0IeH^5Z&#uWEkE z&y=nycalaYwAb8^3JD(-OslUea-O6^DH~bZw`5PTym(zva#7u)cSYBW6HC9AiS=WQWoB^FIBU>) z?>yGz&k+2f{z7ciBXPW>yQI0KjU-C)O}t+`3T2Tm(u$Ih;&}=`NP?vq(st4sQi*hz zBvH~z+(DEe+#oO!ZT&f{0r4@{Xo0(hbCsR2?Xj!^rtL*w+@_mZqY5K>v zNNkX7Px+cwIel@))QXi*Hm|W<$*hu@Q&E@EEaORqaj6#*y|Mixo(49OttVZciuNkT zjb$&3UgWR%qxik`SMIM_zx)2l&YM`U2`rNf%Y525`Ub`uCc;u;@c{!i(K6C}!sInR z1w+DA^D65Vds~-*KH)pSm5{weA+krx`GKoL>4^Ftc%-90q^PO~D8IpEcX|%ih3w+*Of&2zI~)(CHp=C;c;t zEJU526>I~_NNrIApq;Of0=-l5T_p{S51tt+i0Bw~KgOh~l`t%+VM;*S#tMRr?iJOU z_cLo$+EQtLrPxYOGaI6;u4XK(Ff+v$e=NFxXpxc;H_)8<1};cwh9V`vD6=6qr#9*NN7FMho@ZDyEMQT- zl2JRON5-3USNe!_I;~mCwuGfI3&ZNDr-+YnbKFm@9}OSM))nt7yqE9G`#0}M-jBSy z$cITqy5jYvQf&=gTm4SM7~>9OxUs&WkzS{b*A6NREh{M7uiI&8VD?*WjzjM0-koe4 z@|SRx^px^*;Hc23$PO|4H2=gOOQ@FkBjIa&#klO4O&F2I1=kFSQEmYbXsT!?EcB`f-#|}Sx7hjKHpP->jMbkm+gP%($W!3W|CU!LFDY+A zo;d$!ew{)`k-en5metP&w&D!C*(s!rV9veAEN2@48}u-@9_xNCn@hk9^K4=Sxn00e zO+*&)S!tI1s3Jg>r2e9=7!Vb3QoUZqDx&4zBz;9s1*yawHp)i;*|NLyt9`HS8aP~b zSf&9{J;}Po+S8V0@9JpcjCA>2_uV7t0y@((+q1xP#*^z2c)NqqcnBip8?#F|ig-nS zp=yiYN%tx?s<#I%49$ulqQ}O5j(eFfKPez(Lh78fz7?)in34WH{XqJ!^m*xR(j(J% zSJ(}H!zmR+6=tW*OU%$bh^QNMLe^Yh_FZwlwipagwVg`m7xyk2Sy-j;VBy8WIz=st zx0J}s_LMEwUcrpYZ-lE1)c4U1($*=Hm$oU;tn-c zQdLnmfDUdIzB_VKj7Fo^bc*X6H%XHaJ3TrsvS-+V;Pk*nDxu=FR3jNKa!?hhlZcJ3 z1l#ap!D&Q9_Y~A32?Ew0GIPE=Z&_Z8yxDo(^G6o^ zDm+#^v2?2TvOdn#%QD^euVa9#%smk?r#-+rL;FI3%dYjAFt>Y%8P1;P+7kf+Ep<Zs;De%%g%LowAD5x>z|ZeF8Ng4ytq#B^5PzFS&>1r zs-(}?Zv-;sPGBU?HVrog8Mo;D+P}2FOP`hYD9hG{=}U~;ES2qzoc-u_zOLMHa+WAd z_F1_ka8rmfJSNf?wI{kP`bKp1=+2QN!=plX1f5WKRrZq4k`|zk=Y&DRM~LlzPJN;_ z3Zk*=l`dE3jX4Om_-gLtN9r@buUvakq9m+%7u7d)&929pj%&?xT(a zEo-7|n8Kt?P;0>llp62|>}77{cty7CmShmHOh*$D+$*2Kvx?r~zU&(4igZP}s<=i1 z`?x>Y+d9&3Xbx8K2A(jC%SXbp-r0Q?NQ;x4cCdStImS6Vx=y=CdaB@wCjUr*Nq9ig zLH<(NHehqm#gG$WlOz0*v!c((RMF&Va^t$jcTc#LuqCl|QtjlA$-7b(r)H!LOVd>N zJG~_RMutB@^u_MA3eKhzKh zeiDyyooS&d#>kmk(9NPo5hT*oBHi?aAhUyH>db6Xgs}oIo_3T+<3>6Qrja~9I zMPkQTl*Li-?g&3KaIwelZ(b2B5U$t!n8e_VnzBA7iaX4vQ+eqdV{FoJa8O}sA6ich!G!>lJYhRwX!a7 z+9%6bNJ(*bY7w!9-RS-9PH-NuWmvq%FvBq2XV@jS02@? zpM_rJJM8`2!(eRq#u%U*(A;r~EJ2uF#RNagZ>!GBYxs;9mW zDLg&kG?*m5flFd}aCXRPVCm0~kVeU4j>oo$8<0>pse7_JB|OcUX0C87U6~=T7?bfU z^?Krq*wpYD>doSs{&#e`W0=(q7MZFhC47+XTISiNI(9or_ZK(e5qN&ni)h*v;cV?# zZar%pq2FEhyZBsTox)cIv1n&AOC`Fa2C*f>e#DticVSs#git6AQiiM71uO~739K1l z25xvm`DKY%45V4I5fSg-$@yT%JmUXExJVCXJ1$FdWa|{Gl$Vr~l`|BhWv76gaD@1a zZR*W-6V9WymzHK`2A0Q_;P&w9TN!Q`+JGHuj(IB36VKZIv7fZ(+W)gp$5{QK&29bL zy2!$qJDCrg)|yPFEkJh6v?V*{xK_|-y$_iXe@8M4=mqygmw+6fCV3}L6UT~d@L8Hb zRv;w)y+HF_=sSQpObb1N7Sa;BA{aGC(?`c@a*uQ! z#0cKxSmW5^sO-#fPIl$G8+#1iJmxodmS_%kZ@s7{Y$#`CJAg#@3$r>aluj_RbW_&~ z7!)`s=v46ckkqhg;rk-WBHKon#H4Fl#f^=BkN)j^!o7rr35yd(Cme&++!lW__G@HV zXnXa1$y8#u_n5PXEx}UF{MKC4GT(B`+R8rO@!08dRdxr{$LJ8?lFxQAj(EpA+fHky zWsB*DfzxN|YwGVCa!gUydXA0m7v3axioda-i%=?QBEO;hquv*II4CqYE2u+YT);@x zzY42tq4Y2DDPe&CxF+xv_4%g(yobp{<_cy7otrka+Riot4d)BMcr22-97{5TWLGtGz1 zW>_xwTXSur9HB0=yEafbF^^8>3Z4l4qJPCd#FXSEF#pGjmx=BOeH3sg$UOM(Zu0K} z7D6UF3%E%m!HM;p4ulOb&ixo(B4W44mFBMJ?gv)Wwr-hwyz8nn$vFdx>R$T=yUw2F za5;ipZQQNt`JM>hb7lyaM%*UjsC8htZ6KK@-6AWIzf_bc9V#Et1H*#if;)ul42=eN zNbSfw`HeKV;?2b!_&x~If-#%U#KRB*-+;jAJLxCTj6x%y8G4xEpK^ZOh z$jo#+Yn}?eT z^IOw3v)ek}KEUqP9;ZOZ-3Bbv`})(76ow^qq_g_3d?CwC~GS z0k>~k+1j$pWuMBTv@5l>fLfXe4yqD;sG%y*WCg}H;M|*ld6<)CCG_yduq?~$Lmh9N z!`%rUx3`En$pJS&kSg3P(t|rvE;RxDBUvI5PY3?UKtUUF5xh9_;e9?2o_u!9i?F~h zXzq{U9x|7_4tkDT>)hqIWp8SKXF7_v{pSO(oz)Ds?V#{c+WIGwz3U zYflrfkZfdDu-h>rJ3<^IZ(&Sf0fOuziC!8jf26pnY8g-%cs4jbbWGUV@P-jhBc)Nj zqjXWbqo;xMaAC~cm@6@(W2?ljk2Qmpa$)S%*z2)hV*6{dz*HF*8yw+@h(r{U2o z@OR@*vVb*Z&U-IyVR6-J21NPbJJ$R~h#_pV9<&g_4Ku42~YT__nYh!944 zBOga~j{XT>iYzd?#b}ObXpKcPD6VCkQ}a=CU2`AnC(S|4VogbOm+)7?0qV<=`QU7L zum4^)eVCuEi{OXD>q!p*y&Hf!+9k{SbPQSZj`@*z(Q#!P?GV zKB+bQP;tA+a&WWo?Vt~{* zRI*(1U2;&GFRd(lA#Eh>E;%g{Qp<>RZiUZJ8_}o6+pk!^fkDw|K4mt6w=LD8MXM5- zJD5frPaB#W?&~Y*J-UU^qJng0%sU>}4b~4c{A28AS`NPft2GNg6}hf?bf(t zbAKMOo)p8&^e6l!+fW0j@m7OJ zMOUr^7|0^X27+PWgsUT7BY7!(2HvrQ@H4rsoT(b9t`^WCutCu7;DMoI!*t;XBi&I= zVj9K%7i-bfjT^6dr&*`@5j!>39WyTGA(jW|0o!QaYDUH$kN6q7Ea;+QstEWhTr=jj zucq${aP_LeGrB2odE87QcZSh0SMcsjJp0`%oc}mBIrPxnj#&3w!mW|ewof~LxUSF* znQv?r|0H5JIaqKDw%Es_Krl2@($|<3{4Vi|r;2-H{`HXPhWMK}MiL?&CG~Va6tZub-qIRaXp7so8w>Rjz>z5l=8^=M%Y-?#{C2WEA3XX-&oA9`Z^vw0f``W<^ zQqKFE`Fj%@a3uGImtvqW5A#&(#3D&I$z;iMu}PFCY($+RyAwfv2fL2_$}DHbz|U|l zlgy}?zL?AY=$q`z@WD9a`9RmB+khA5ptFY4hn_ChdDK}Q*ymr}FX#)NYu=5%Bg{Hj zv+BUZb0oP}aD)0Tj2CA~6d3aymi3ocR-^zqSF9cua4WD?@RpEQp{>JTN4$+Jiux3N zET&^@S1jFPvt#zebO%dZy_ksDztI<-(*(wa#&^@WBYObrc%Z6GS}JtHyOx6YV;MYU zpZmXKUVJAv7rura;FTB4N!Yr~DG%ko>C}Tu?2zrV^|0+5c(LEmHai2$^)cT6xAYZ!-heMG^-5Pu z%fJj%DjG@cB56NRnwUYpR^INI$-EES{%uz$EO(s_@RyypC)kEq-hdT&o?(_=3O|w^ zy6w8_I-f2wGYs|AR63JC~XkdkQ`Gy$5~um{J6uzvoW- zHxg6HWrD@j9APa{2(0}Makylw*a%Bn^%h0np0&BE3J&d)j66yL!|rU7s@ zPO|?3b7~zpSkHOqU`8{9UhJk_ZNVgAb&rPs+*;U30^!}e5s1Ej!7p>UUj?7xHRLex zyzdhp60H{ZkW`mGh0eJ_-cXSNKKqO6&jIy{YlVvNw4A-b7uD8XvVX zYIW2nEQ_N9VoGAXD8Zn(@c0XHTFu<(dJz{x#|1W2JdxZI8iB7x0DJ72sH#XR8X)=? z7+(9R5!5aCWrdLMI5kttv&(fGR_~j(4ErWWtm`tg(I)gTaIvlRU1FMoA?zDGXivfa zIv2j-;qcPVg$4B<{PriqyY36+qh<*kiyKSwB)z2Ffed{OrB(yl!)i%eiBCLTtP}R8 zwv)O38Qc%ZfF`Vhoxvo)mi!Djp`AQy>7|&Z83E*Izjch|n`yQ2qoIYts80n7{z*fw zA;vh$m~9L(<(MMObo6|l4tT9L3yXmo>3Eqb2n`*Mjz?4aV z2mCv7gn$;b!(8-j_->4#dQ;V?!(d&R3;h$Z&+vF337^@f@SJT_9-|ZLKLg*;^{fY6 z)SG@CN9P5pZg${Z;wp-Npa3+Kc=&yV(y$Jc%P+U#C4IZI($={G}2PBUq1(=0e zCu&GB#1O=ktoOWjzjrrvy>|9JI=19Oy+c3=R-7@`c>I#e8M$>a}a2|n${ujJ} zN==_kA53-3W6TNWO5k^No5xw2TdUX-fCoFe zLdGGIbr@pHb%>9zfp~;=WHda*>mgR71o0+w;g_93Y=iIaeMG#ZaWB~k>}WQbeajTU zL#PdK)d*NdohY?yKt^2T``fqJH_kQqYGw_uC0bc^SplQLb;D;fPLbimsL#l<24<*AshSd!ZiKr7< z82L~1o#;6+)|dk^8Bxo^_l2GeYOgMkOJySYDcK}hj_f)-7tYB&vJfzC+NBevdd&S^ z7d;as5*ButF9S1~3p_XIKHfZEZQmj9312dB_Ugdf={Y=}L%>crkJ*S`HPx5md+zH1 z1hjsz&GWHdeGx^`feaF42&M^|QgX@+ucet7J4}Jc-yG^MYK!13;;n}P6>I^pLgEoI zBk<3L7xXgL$c$xB zkR1f{Ck4F5f5Uftr@sm!TKd3?`V#!m9q@&Z2O4%Q|2}vXFX!68_qscLhj)Xia0VhW zhM;tN!CU+xyo_71Uzpd-LS{N5Cvu>XzGgc?63_NGfhYEFa=D-eb(RtedkdEfPs7Xc zv#xlTEe!x9hif`Bpzw$=#WUIhT^gZ>y_KfwY!KnPqJAxU_ zj^bt_?(QnSp^&IbUM2$I37AgY_Wy?8|2ZIAHAjSA3-rnY(u){_i5QX10Y21M__&v` zb5Y}ieYCfycQ}|d-@{7Z8Xn0_T>V_h(6Kn6FBjMk*+;^j7;hVgk!m-~WQ)sk#N5C# z0qHnvf^~wWuQlG9WP1Ut{~wg(7>D0+&EazXc3pC9b@!*U>5-mCo(QiCJ;NQ$I9M1d zy8`~e?cm+r4`uTSy`dWTEXjzFSPbN-VxS7vw298e_BFna-gBb%dDgP3|V`DAo4@1x7kOAeT&372$nh zA`s10;$7k>$!2Lk|Sc37QpjDj-0;K(!GF-GvIPVwLKrs)ov@?4wFkG0I~~D;OKP%bUm+NEeGA z3Zp2QfFj!yUCEJxmxzQ-~<(%ZPx(c@j1K58(0=ycxc5<`~lz zh|E!tLlifZt-*FZ z(PnT`HlpjmKV^q|mAkk5FZTqu3jE2#U00l;&i4))T<|Y2)3Dyw!&V=BzjnT>T%a({3q()H=v zbfl-AXMpFEXOveD9=|<4lBvS{24~Ma^d)29nL3EM0-W0JDB)#nHv5wO15fc*z}W1; z%>m};a>Nu?=KkTr5FgbEZSyMPU-}~w>lk7(BN4aM2Rc*&@U%Ar?Rg}Swl4_^plx{t zwZS2BpK2^zCv*#UiNxqZt>OieO3-WP%6`gN*(0!?nB<)mhrxW(Q<Z_`qYL~hpW|_0qoN|qFw{kTY zJ3mO{BzHx}g-59bY8vcjhXr-1#ng3bBlNxo@G|U4E+q<~pEqaYnTqJ+vV2W^LFoGv zz`yu~Ig2y$-a)<`U#YJqJR!Pb#7%?oJ_@~L zns)+bYBtbw>6Wk{^#fZfxO(Y6_#a8%aMy5e$86kQ%;9u%80?qqQ|ztm^})To5B$oP zz>IwjmX#Iuf%dBQOSXHq3$~${XWef52dkx21M;rK;RddaO%;j7SQJynVPtL@&na_M@Tugmnn3o|kH~}TO8B+NM zba=wAhos@7){3}dE*v;U^AN3_1$*9apq3;6kESC$GR~1U#J&y&+PX{70I0UNFgrs4 zQ~4lz&;(Iq(MwSsaVPN`@pNeWQ>0O{H8P8AncOVbC?tK|I_f)I=rQluH-MYI1k2i2PhCORAVwY~Y zrSw?(1WkFy1Cf7_cdu86`gYF8_?j@wm>JAWW(o5aD8L-koZY~_XW@y6GOY=O+dj}A zdZWj@hB&u+z%rPQ*uWP+far^e=VOS@`v5#B1xbU~Cjt?tOGy)KU>5}%Aa@rbLOcc* z!BNnR`UvX_n*+ytj<71U-3ak4L<;xe*lq#UR6 zsjjPU1uPGo732x(6ucA&+BJgK2kcj;tN)LsvjA@*``UQiwO(9`ySv-s?u*0X?(Vw4 z;w%d+vbYy_ch}+$g#wj$GLuQZU;F)^d6G_~Z8G=XbC105djylre9ZLHRG;^(Gpj6l ztwk+!E$J4!WuZA>N->_n`gYZB)O6I;Q_n>0xtF@X`kDGQ|2I|r0~P&g%E^jDvN7SY zp&k<4%izRdE3zZW!AVjX^veH1!}CgL91-NZ;475M3k%x={R55hlOG0pffgAit`MDK zjbKguL0fQ+xKYSP>?Zn$lG!YQ)g9wMMP!jJpbrcRGz$0_$F2kDZFZ?T*#XQWl^2AF`#lhkPajAHj zJ*7Xs>ziN&X)rVSo@9rO*oe{XM0IgRXiMlo=yFH{CuJym=ucROz0j2UF3V2bGg`hK z{dyHD)pKDMTa|5za4$2uHdRekZ8E>7R2KE`>No2AntfEvR%j*d0^JT>PW>eP-}K>4 zFf=keF+>{wF@7}mHg#mQR#+NXCF>jfrIvQPEy5;PPg>fW)n?Ju%2XaKRx?mOk>*L} zvz8C$`R2B);lE8QjfV_}^>cM|>4ULAt)#j$RP_^m@lA99va3(4GE|F{O%%iB3Yjgu zpGr?U>rIIb9uu4uJRaw>4BquQc+tx6{z@1TC9&P(1iP19DJq;00#LymeOyim2FlVQ4Hso|WVIDU@F zG|`mbY_c4t&aAS9Z42#5_WSni_6l^TGF$JWHt)gb6-{Py9ZLc0ddoCRGiw*C%$n13 z*IeIR)|6@}WEiS1p({&2k-O@(jOJW*oMsOKLuE9@$mX0;EmfY7UxqDJE_6_OEwz)X zlHF|w-}<&x8D?!e;(>>fBo&dm&}pT*SVs63NC_Mbyw1v>e8s1#Neupk6)-^@FFJ*x z;JwtsSS;`_a9}R~asNyI2md?22TWK(z#0hqr-L`T;+q0PV1~CnSeLNJLq+1MXB`~& zt)7#f>7JFG_40J|DDjK$QPmyh?(gpGUdY&gaToMV^dxy2V1EnH+jTJB*E-)8yrl0w z2`p34_qYGK-|w#i8nqmw`jTEGJ;i^-Ekvys7}1BKnmu-Wa2MW!Tx>o4`P*h zp_8E`{NE$ozo#0K7v0zWL~qe3{at`h9ZP?QIjD6XLP2_F-e2sh!BQom1cF_;W94Vw%}hQh{GM#*^G6k&d4 zeqkAG^;+-Ry4o+=P4>&y*VakanwCoD1*Q-l)p}ERqL~ECXSxyZu?(_&GmkZoGn2b8 z?lU~q71X`d6w12tH!6@&yT?YYH(P*AKT>d5X@Ae*p15DT|= zyT2@znz{aY{-yr4jIo^^*25@Q@Qc1>?1)Fa6}|brI`1yV@|0(aX9=Bi7JIgM26%q= zl=dWpsJP;u>Ml>$gR<_H+_$G$R6Om8_TyPI_Zl{h4bv zC8WakW~%b;K@};JJKqfzk|#1P8YF+nH_}VQrf8wqqZrm!XVttnq`fo#~#bzqtjmO&e<; z+gIB%dtAgXdt>`VTM3)jqPA={&olL-Yr4mjWUglEVo9_VvW~K}vuwu`zHa(zyk$7A zzoi?h9jW=jc$QFA!&iH(YNPH1({u|~In!ghEAJya5}J(4!5CtaB=HT}4HJVMgEnb~ zG+9cL7E9$>udT@|JrhjA&OnjCSAYFL*TAPhMd3f;H!_|v!9H{8VIk^eVK-mVtKmRJy2v*woq>2Hi^Hcb~i zrCHjX)S8lY8}&c+xecqqmFG9UH&|e6pEKIQEsLfn=9!i_YhK$W+h}|9h=mc^=;v4{ zBA0!Xt+#cUMPV*wT41VZPB34m$D!HgwSKkUwa&2Cw_ay&{a`F)c(3cOJ&eu0rs|-o z2!nJxc=nR;L9%H&s!ytJDCa8L$$tx4N<(FatYWf{gdL!0y1= zfK7-HYEo%!BD^Q_QxScDnp77H_;xXt`Mi-{JtH-ZSf#gjp!d9Yi?@I`*|W(rhW;O4 z-LKtg?5Ibvz_~r6J%4$gddhqMVox36+v*GWqWn%@F@G+9DStiUrj>Zy1p?h!r)8O2 zmxRjV0xBi(!Pd;V#mv)T!6n43%YvuL=vGCQMU=)9*OWuy>kcYrnMzZUC`}nr|8t{w zR}k%qAt+VkL51hMydXWR9E#Ft6)lF7wnLeu^edyeqP_8x4&kxnftR*jovf}2FIS_T zjt|^fcS~1Qzf7++bT$kz978K0x3RDBE<5XaJfU^ufVW$Xwt(%b{W+rbT_X=geu+r5 z-?t@O-Im_wO5~y>(@)bt^Hq!6y4)IT8*8)Mrdm5$ikr=*+{UW^kmb4FU zC6b*YTn#h}l*hOIL0@Ezut4Z18i?`V2ir@((P3m)&>sAY%+xZxrzqiRAd!``mCjo> zp^VU4s3wdTiVAPRU3Xx`<^X}y!}s009wuHRZ&7a!Z%%I&>Iv1nvEJLBrHtWtPa{uJ zPd!g{s>jXGhT(%hZspj!sh( zw7CY6``pFI24(fgB&iks6uT7#$vE6ldX>3hv-RZ|1K#0)>Mi`K>FjKUG&40R_)A`G zE!{(SrJwa34DSpP#<|7=_)1TVld!C(Ox-|kezn}P9_kBhLo zL@@~uu7fHG>_uzEHAXeJ^bW+}*}$wo4x*QLfk0rm(3VwOgZx5|;KAT6vNB!7KZ#y8 z1U~ui`>*@2_-h0<(#tLvzLF~Vnpkix${Gr3Sui1ZjBZb-iEA4Pe^8m~NbkK-LLH&E zutJ#0Dk_W}-x%l|_!ms+P(0ZrY;SRIK~}BCd)H(0KJoPRwDIKi`0$L^xW}=ho}>eN zs@sf*EV-Y%BS6TFL3^VH^C9RR=-UUjFb3TBDE}^Y@3Mj6fgyo40dF7}D1#68RH!BX zK|P`j>LQ1N*YOw>Sl~#hBDI*IbdvH&jfq&Fg`&~EpMXZrVN~KWRo*ILg9o5iI-fe! zZB&nIqto>sJG?~Urh2iAebuUJz?g1ST~*y93-DDHO(nCh`U*bjT1`>dR~zZNDeAWw zY{t>XBx7w;Y1A;9n~s=t<|^i0WQ;0V3);$nkh4Xaqf(;MqX)(`jhPe`71=W4Ut6-} zo@p5M^0TI7xW3!0QMU3nKT@N8Y$M53%rq4<-qH`y4$%}*7f@bSj92ubW~xW4tf1m= z#YS{pzTwpslYatD6Bk?rS~MA7ax~sfG&^St;R3$ULa`C{Yg+ImV|<8M=8mvLFr%W> zHPC|2i3Uc=Eu5t$Tvz&AlA~aAQ|dt#c?aq!8^wm=0I{TaK^Q35$wmD0l4hVgByZ&DF$D^ zwX{vTh!W2}DMPA`>d`X#Z6u&%HjhztV1LVFe|w-)I|mN^Db#LLsK%CKy`t{KnuwzB z>A%FPRaFa7N4TqUsEVj-s^_RTV1x5&x@w+jT5Dfxr|6XWfAzf#uMIoM?w2vGHC@L~ zDr6pIUSzg|v984WYhkYxF)ea>)W+xmF`Hw9vD0ElM>mdK7IDN@-trVwYAa)2x_HjE z?;e{!IQ=p(m4=2s-(*OcCxP zeHYgFH~VkhE`f2nf@d?582AvG zp>$$dwXd9auVJCYc3V7FF#XBym+p^5J(8<{`wZ*1 z1nN2ay`6ohea-!De@XoGvvlXoC!{bdl|o*Q=E7wDZ%4A5a`H?W!B)}&Ml^!x?K^#y zypociAL0KrC)58v6o~@UP`W5cXn()9JXLCDPW; zw!>D>uD7?ajkgpq7d2++s>5h$sTAb%QU5cBTaron7OIK5%7gG~ly76HrhN&?LeIr7 zf#cxu$9wa5OXJG~z3Z`VJp!IUec`h(meKo8G#?G?VmVli#z)gMQ?BH}bUOB#keQNDI=={yKz~yvJcRzBw-7%;M-E*&WcX5|v#20{d zx$5rjsRBZF34Kl#Sc2S)+yrvq7qpnv^bQA z^44~gwN_HOsl>>3rpwFd(0pcQDbO7@G+(+9-z3r<>m(XqJ5f~MgeG4Rbii7p>6Sq6 zE(P_Pkg_Wp7#~z7bq#eza!kY3Ghir()%8KjOxM=crRXl}&luVoUmJIUe28JSrkXog zc7dDL+cwy`*z-`2n;aDxy)C+QtUGpKT#NYDap$AEM%|CtWUFAgYaD89i_+0u(+bOc zdy2iO{fRwfuVgP^eTJQ`qW_@rDi5RJHVh@r!(8)YREF;bqw$S5tAKvnqz$kdE5yEdm zSs|7&Pxj0FKJO>bO7|ewL1!W77e}Jwoud|&o#)P(u3Y4g%Xlt(Xf;nJ!|L7Qagd|x z&-ix5PT%w#^^WkZLm{uC@KKm1UZNWJO7w~*^h7?uWlY0v#)PtqlZ|2MQ_lCd&wNWWe+H zt+sZu&9~*W7mtu4wnUDIniD-FWDFcfFg z>Hjn{bUR2^(Erot@Lu-J_5?k>y&e>Bw1M`*9pSBLWJi+G+o(xshO~+rNIvN>+4K6y zfUf{+7fbG9l{5ktd|{akdsSaijN>^N5*rGqap7Omb#ljp#EHUyK=!~be_nFLfB7%_ zO9Xo0HGc|RV&{ED-m$;8w&x4jh`x@88BH?^WpvLdOZBz3GoDqOB^->)?`4v)e=~d7u1<5e&Am3jW^|USI zytiR@{h<+zT4rAfne07L>nG;h@hY?=C!+~`8I`9B=)X?| z8<2p~|7=uG53=hPpeyWBg@sPOlkt*gWO1XbsaJxCXsbD>8K7-ICFea%hjzw*@u;aP zdESraS+F`AlUW*Rn`Lhw@ghP64s=9J-B@c}p?Gt)gW0@{?#Gyx(aj?5Hoqyupfa>J zTrvzZO}FH*jkBG$UyHbCpO4>M$NWj3OZ!5#Ox{&CFH|kKI(SzUgq_%_f9TD8AK$1G zI<)=h4qQ4Y2(3-x1c>DWK`8x$N0$s#b#5764r>KEdqLy9`3sxue zmvk1T%yLu((u4b@+}Pp=pfDGL!K_CWs}?G_r_epECOd;NCtZF~LU|roLq$jD`_22G zw;$Q34BtWj0-}?v>;@^mzP>4*^R80P3mLD{18LpUhNnG9>y>^zJt-rn^R{y#R=EWo zE*-nu*Ni(J8(k{kyJGn!c z-S~N5$szjihgXw5EKE+~8nauAf_O=^k>i++kEoh&rmM~mY5>EiW@QUK&Z;{b8TT^S z<_6^cbJ1bAAcvc8xCy1!^JE!KDCv4Zay3!O)oaiWs0L!}mTs}WiNR!?Weggdl9iZd z9%B9nCg?-UIBON#E}LYZ75OppWmKn_uCOES$Bl}=mCc{6Nw(5)zG!7s4*OE`9%H0o zj{cT@1sVje&GFVK*h_xPK+9J1O5@*jDZQlHt>`PWgxtYNq7(n6Sl|V~!dJh^|I~XDkL9YnBGKd$PiL>fw}z_btiYo{hOjcY8coMK)YoHBn03(; z=z8c{C>1+!hHCl=={NSR@!&Iy()aKRnws&7*>sDzNiDisxJ#(2v=4NsD&QhFaRE+7 zH6qnqtV6l)uifpJ5=TW%iPdFMWUXwi@rEEl9Wx0B)W~H{Tt|2Or9nk=sYRY4NYj&7F zn>$wrZ;^v+|C)~LS8A7O zQZ3B2$|2pjl@5VQOpMWqhOSrA^Shklzixl+FlYuqXAYf9~?ka(D3b@TvUW z{o@2>uy*iE@U?h0Fwx(~XZH5-d?SinL&eibUf`DZ8!KXYpq1D<_(_V!$CxbJ#E#m7 z4kusG)SeWs7HUsMvN!dhR^VWgVexdAr-H@!Q_)%Romp*?E2;99kVc5#0}cI)y;VFB z?szaSpW&Bub0xU*QdeCFE4eBNir4N&u6>Rk8K2W`q$Yp^?)q!tuS36Dr?yC|jn0P5 z+0K>d{^+^wZSR|p&frRvCQZJpWUc0V?|_?lPR@54 zI??w*iB1ZwmG0s#R}G5z35&^DbVMJv0e*22dUr_FT&Kx0cwfm-KDw?i5}yjwh0Www zE(!z0zHr&AgYB9OLi`lHy8G}A17O8R(E-H@m*yop)l=m;6&VVZstb7Nur@-!#BkVH zgN%cf9R7VWPJ65>>p<&qYc1=2Yh&9hTjPk4kvF2Y#&n7+6Mr&Wy&Qk#Q02UlV`ID~ zW<%sH>kZ>2ofoCDN@NhHX%Ffr8ZR2p8*iCDn%)|1#y|9lnwjbcis4c>v9D0U_rU#^ zYb6|+?eOtxx|{gQlcR4;zG)u$)bD~kkm_6PyYE@*UQ1?Rj;Eieu4jd}7@2^CKv}Vt zv@DcY)>p1mbXMxY`kq!iBT_CY7eLtlM1|E7N|ru?gZdKQBpXA{rmf;QJ=}A`{v0kZ zEbGIb?i0@VYk5bz3%k6II~g}J{$@7~IU7-jec}1tn+qT5jeEDN7M#M)>CMvSrLO)J z_}Tl{)nCD1NvS2%+hqLbsDxz}JxzSI{DtUq--jK0TtF4L>QC~2^aqLAnh2kSDrEdm zuqN|^SF)kxTm%fkQLg(}G;fcfRQs9LIU%$jjB*7jmvknmrT6YhFi+z_Pc#R`wHDl3 zV`7K#q2rQ++I&oK4|Rm~Shqgd`ljSubJH1UyR10)=vvfex6*swq4+^Q`JtkivYqlK z_+Tf%{1RlM=ILJ=l%^Kuah4L+6V|Y8sJ)8)s$Co5w!gDCjR-|NiToo<8(lx**ryEUJ@dLw$^ z2(mTua*8dAt_m+betO7`hF*e_8XOqw8|@KXi<}i5>5T92jMk2G&iStUuBlYq54dx> zz0QME2x;O-W1waHh+P z8ZxADWJVW)=sAFYyf|21N?=WPmzBqI{sdV+8kE@-#Tt~0Ux#;v7LX5L70eyfpnt2U zZ}3pC;)hU*U(6b>Ap4Gu-i=bO5F8bJi4u1XF((zkp5&Y_NY_G(!^7ZF{s3WAjegJ{ z;OG=ol~fg0nN@AG>g_w!6E$tLE5MhIGu$xtH*dAnwhpyDwO5N+9FY;xE^>Y3qew&4 zn5dG`3!?j@O*uUFd~9Oe#`qc860+yXHZpc@bo%9VZMWAw%rn?~)7OOj;u|V$8vi9< z5=gUO-X*@yJlcXX(oDZBaLvCf(1T9u#yma1M3Nb=UDMMW-r9 zAUU9=j;;i2_ncck;fE!?ac0{C*->`M8u&aV6z9>^t|@B^ zr}P2!kQG!5Dg>Lc6Wpa(bQ6ZQteXb(q4kE zlUEngzN8y`4sC5sl=`@`i(*7LS^R+dYi-XnXMJY@JeAuSOEOwIqFk?0ti0iS>hBil z53(he9R6xBX!!#T$OA42=mOLHtH~BG$JSZMT9lK{hDy@O{ISBJOjikNgSL@2j9S%u zwO4gnnIc~?h_p0l6iWpz`mElZ?%K}f8IkE< zQ#+@g{N?_2G_`%&!!$#NE2E@isN;lVj-!X8y5n=kvy3l%)(q5L@;iQW9CYM${^g8u z$=&%q9lhs$*#m2Z2VzR_jr5e5W;1<71`zA5rqk6ZxWIpfU!&Arn6W!fMzJrR&?7L= zRkOUH&S>nPqF=*3l<@0@+ku--2fuiQ)tP_|crQeXRmG=b6MU|@%#DqBjY^R1P3R}= z3gtq9zXRP_v~*-U&)PkW%HvPP66J3yh5E7jf##w1mCgv)rUhPc8B0NH1>0(y+9;NU;3jQYWC;zD75 zV7%YvOY{t4&mHa1WrWglq%BJ=kXktPS!$WI{%MZ1yy>0bdjCrAk+FkS+Rb^x+0gab z)z`hl{SGyXKiQdUdyjcX`@FtC{d)s{qCmSa_y~o7f%I1h$+n=2RZ=lZF`c8XLPL&K zE?+~AbwA@#hyIJjsjQEpvOX7__!Z)x%+5lG=y}tQ9>*71r%yo|#7T3A$9Ic8M4RZw zGqZ_Z#hc ze-(cy_G|RY$kw)E=BtLfx@1ia&1qEH6V=1i`OxW|gwj|q)ePl3?C}E>mS8aE$}4`^7ogwak6p^Ud4Bm*QLH{|6iA3@k*iM}|UAWT-8%zz!G=?LgU9 z4DA80d6O%6mrPA_d3Qx2<#g3DDp=n%qqN7gN3>5hMKp=(S*pfLl_F6#BU~`lKpGtU z$o_X21@~moGL8MSX$~Gkq*KZ>!W{|b|BUlDXR^beu{fhch9~{s^wsJ8(?6s=O4|&d ze+nc00!DG`j7WzCO@=?+TRf}0`+N)h3j+mFB&i^tAV$9v>?*yJHjv}2Ot0C&j9XiJ zjtn3dQ3;=S9Jr;u1GDWTT?9Mz(WYa@?@^ z?Ae}V8=3uW_Ch&KIqqar#v5Z7M$NIWvTQX*=zD2zp_gA*^-kGY875O=RW|0^{0FY% zp|YiNuwsmSgDfsQMRK84+S6}Gk$JKE0WnA+*K?POOnfoV8yN1Ny^no+{3(GSLL<03 zox@e|ESJ!A;tpuNGV-@@Tx#Mktz%a`LN>Y%9sLUM>SRR&rJZc~D^*2!SANwE@UC-| zw-o|;M~y63ICm%<{3h-Zx&_MlpLy4KR={?C>TJutddYDZOS^)5#4yMq7k6euwMpmv z?6`~$WO)>~erDXuxRCK8BadS;YO;NtYS&HIX7@JFT2}5v|6hT-bpQAed?ooq%fl_` z$`UJ2z;AnpKa^j#7k>y4}!swfZseB%*0nl{{z{GZFEV>?9Wm> z{07`&p3qkO=f~{84THbHncU?T5*twK{Z5p3Qff_BYa_jKZlEQuCl2_e$b*MA4~EBM z(2G4)C*T6CP*>JmXZ7~emD9%`!j+adN~Z1%W%arU?=vHN4rM*kkQJ>s*in01UfX#Cf(MSl=ggZ$cdpr4kg zM7pJrxjN;m1`@EiFW%R| zm*}epPVH6Twy<8DNUxSvQh-QsKKg3{I?}nx=qx8HyiV7w`{e8#aGa{p$*L}+AH|5K z@XB&}VU&i|a}{KL6>2S$V211q<_InnzX|1q!GT1-*+1I%llU|MMy9JLk_c$DyO8@c zx+x=FeOx`6d&6Bb@t|dB#qM?saIVgK@_XlaUEUtP`^0ZY{1NyFB|)AR1skge_wyxK zhTaUvS!p@o>huqdfU%JnxRSB2{*JvQ< z;+|RE?moZ-T|>0gkX{)gc=8k&qDgpAk3;YH{!v(o%)Zu(LE2n{p^Ct~i)Sr>d5EFB+ROG;uKUv+I`YqCl@I z4U-H(Ll5Iu;~%C6WGV-nH<%O6N#@(;Yv#M=8)l=Wu4RPfpyi3hWF2bVZk5>@*k;)- z+Md`x*e=*M+kOYzdIL;GKWk-cF6$fK*~TK7x0{=rf0|a9a+%H=^BNBua+2>-==-CY zyI0#uo35FzQE3L?w>4LtK(&7%*6lc$&DYd8GvIX@!lyzlLJ^^Dcu_ZC3EUOyfsX4Y zcmvA=rSZ{b`t$e?_)7au(Ca|qJ?t4q&HWiQot9MbzPcW{_Q4I<;o1uZb&9Kts{$;L zG%_47oo~3MINv$HI~`8Rnafqp)dRlz2|U3cAj|WhWibGR@(*_<&vN=>lw+>!1_!4C ztvOKkAg~5ew1*!g2lMv7s+Ig1L^UKT3|)071M-cf(vFyC%O*Z2R&USIEgB? zN@^f2lWs{t;^b=Zefr~5T?u^;6``y56uSF;3m0G>9FRqUEI%lh-?Klo zf2BU8k0>6|FrsZl_lPDDaS?~@o$SfBskXefUDm&>t*u7uHSp;d%{k0}naY|r7$c4Q z4Gw)9{Z?Iywzzhb<{Q|{Hy{l3s&2}&3ag?U%G3tg0+bXN!E+rhy~dKA5_5~YK;nD} ztOxD*7DU>46q{4LW4z^1ewsvG{fc{pTkbyQLeboH-+37JRaa+atnw?zcE>cwT*nwJ z@Em?lbPRTMaI^$eyIR2{NCasvxhlGQQ_FV| z|E^<)sqG!_y@6(23s9`ze7VtfTkC)2w}2%eahCjX;;0Wz(dX1m5o-gzM!8rgN@b(eH{ zeG~ms{W~y^bLmr4$G8p@)ezHTQz@8}IW4m+LCY}fJ!?DLD%%a)ecK&wTWk|-{cK}w z>uh^$&um}t$P3%M*cXr=$rVu`qH;v@hYQ3)-RSR7Nunw z14jQ>(u8cy!9z4?r}B*;~h zwZCVfB$RPR`I^w-cepTy?kOl^W7!fuU+fd zZ_B!3T>$~YRJA=KW{lNG+r zamgW4+po#*&3CTHs_S6~t#y5)Z{#HRcDDl5>v$@{8J@DBDz|u(z4@s_ZSW<-h93ul zIUcLM4V+bRVFdWOw?aO#CLaF^@e25ays+EnfVWBt#*ir)3Qy<~sstANwYFI9%^vC3mZVC2S0~rE)Gqww5+qtv2?a{xBO+0ox6HfEoy@t-TTBg1Ka7)%IgFbOGQ&XqPTeHNts3pBpJ;Ow1X zB(9)W743fNTJD<2Zd}=w-Q_@$W*+K~W>~i89S0oa9KW-+b2#h{%90sUMkqtiqY{q# zJllm==Q-9oc00~F(j9(BLAZT0od2?y8(e)|t6d&fas0SMcL1iz2+tnR1CQKW#5=&d z%KO8c*EbL^(}haUI{yoQVf1iMP(iCH42HdS5wuQN$O*@IHVB4Pu`tNkiNQ0ByIv}Z zb^ntdbZ4Y1bTWA_DNwN(2-f2+5nKLn12VIJ<8>wBb7f*=-!CQ<3w&yYc;hq~2%xlBHgE}kvI-|$>cQvY8_4ANBm!8Ob!d=1P-5%r~ifIkVA zz)f(iPO6aQJg4!XKe-mWn!03QaSk}QI%gAIRdmKUAE2b)$x*=(=Lix>-N@LPu?!W2 zc^PvuW@IeN*qE`7jMpO+r+#E8Sp6uHIGUhsztVA&c&vhR4(!gbv!ZK~>%1$HJ!6^s zncDzrwkvFvgC2#q0^a&PZw|PChkU<4^NhtuGh^G=;GxBXA!`S+XC3Q3JKb$oh|kH2 zmI$^FE(9f-LYK3KQWvVpOQjt!v(HICr8txk7E&>F!9=MBMqn=M{S$R%BmF+JfuF5O zciZ0VcPC}kHXLBuc zRS@n8*y)qzm*!mX>pS3|AF({M$gEmxAylKkTlVvu!IH>(s+zOIy%$Y8P3>6Y>#^z2 z4Al+K@YOD(#I;iU9slA3Ry{XeL>j5Asy)()Z+~r#m&u9qyU#7S!)QyDq}B z{{~(}OV{MG;0gMInO+Kx>zOP}KiAIkk@5}nbp1w8$|zQQOLRgO(StWh5mw|^4pOcn z2G6bPf|Ylx%7VzetoBhq9HH5z`GGpxDD6&A)Ma%Ob$4{R^z-0|)-~)hD2yGAM~p_( zP#D?AKxp0o&$rjK#x%=x8w`8}Z1ytqM)MK#J=VO*QpYmdGQ`r$Qr_|vf4_-YZa&TL z?K1sk`rTB)bkx|+_|!0pmA*$`Q~y9$5~Z6oO=I$It<@3?h%PE6Y?dO*KNVpZI7vj* z=g>`f3~O>VV?G6p)ON8W$~`ARu8OFkx&6IBQ4Xg5u^j7unR@;g_ZW8pbR^FbZH;$z zBG!7!j@$``{bxpV45OQm)h@86uVlB=k@J6Oy^Zl^l|M%s4I7f&Vc6z^?sJj~F|uomqwSr%d8 zb%C-V$Bw{@izKp`CcL7OHAvhAHeQ8pNI5tU3$XA%gEA>6m5+g-1P)^31vFqAJ#X}@1TF+Dy>{QO(T+LjaGlf(`=&p1sn1m zb>oY2vwStyY+=|>CFd92u+`FPtm0c3m<`2W!eYGkO@XMuIKRs`-e&{xQO5hjGZQw7 z69(B_cRx7W2KP&HDE(NcpRo09oOzup=P$=yGG-5$0Xx~b53$RibzH!cKS2#^FZZX& zu+ap`kqtE8Z09AX##Mz?e#({M%H!_fo)6|$a98$BfccW@$&Q}GHm{5b?LT@M)`$D{ z*q<{{hl<=qFoI3sXB-FnWI;J%B3b@8SXIZUz?8;cpDFD}RY?xFdn{Nl_qMe--P>A2igzKnjc-|0PCPEj9y`7v-1HY<+d z$NeI5D^KJ$Lb+CX1rI(K%$K#QJ1PxqrU~i;@c*JTJv8$)j~VTnFxAd!gCN2tpu!QM zAE!U7FJ>5RxDL90l<}ys0He0obitI)&aO2(OsTN_MN=E|Jo6=X^+Hk`8 zS}t3TS=L+rvoy6>ENAG4xW~N7e8{{O_OO6nPJL4nngKfFDnoI@A$?{2JzZ_xRjO15 z?RD%;ak||!S0|yhzEY`C&dTbrY?aRdAGMVT<`gVx84+85w7SN!>L-KHZ41kEEcuor za8G{vR+Fv!?p+5jDKBh?3ot;7f|Fju3`js{;~?3VVXl_0O0H;^f^3T$~^b=C0_UKQ_HL&BZG8G}Sg0XFo4$dSIMsECWYwilG!EULX73 zOm~#NhjQ(on*OLgoJIqtJo>4nuCPZ^P3-Y9Pp8I$(lr?Tz-^PUOC@_&i(96bBa{s3J~k{f$6`;_10C;-3qq<9`^&cn&@Be;A^b^5C)%EZ5|3Y(VBslvt*f4`&nu^+pw; zE(qgoI{CR5C>%D@%+?&$2%22l+GOdDXdh{9x@x+)x{JD?E}wpZ{s2lm35FJiK8BGn z%*Vnr-$2LVGlmp)euJ@;v6^v&ak25V@hVyZKgr9!HlE;dOJgPSY*ynh!!N^G!!$z! zgTZhCu6i4NcJgjLQ3+h5t*m{ZnFFKzD_t=M!+^e|>aPkb*YW8eD<**4oDVi?7l>|+ zYEC-v85Om)(A}j(kH@2s>M7wffhh@{}10W-wa~P1qWf4;a@-Tyv+={7w_ zzeE3mxGtz?VljsK$&D8`G%@r>xo#ES-zBtsQVn*apZ&pT3>o}}JBAa?lRk!ahQ|2r zacE7v(LYD?Zo7UMeXJe2|8yPcPkjQ5ld0prRnq`vLzntEEP|rwfb1l@q-Firra!<$ zx@g~*uL2Dz5w#4Uf|49Q7#>N!{StVT*w7jH=t`JUZGzc?pTsj@v6_O{k&7po6Qf|C z2VktuA~PCCU$8y?Whle<2GQLNc4AR~L4Oz}>U7^%-!tEFyz%wE-Dm=A_gy4!p5{}- zOm9K%WfshzgLrDGsP5zqR0*^Wj1H^_JPxD>$_xFdk3JDhsLBr@|8^WLL2Ix))y>se zK3f7Y`4+NonZDXmDw!FfoaCK`(uMsL zZZQ1kKzW~2U7?!zh919Kbpm$32|6ze)Vo;uH`Nc+Z&6=KMk6F$t<;zpg+`k0nz@?I znp5aYxKQFMfr@8K?Esk0YqXc)*`;V5WP0qn+~AQ4>DuTTqO#jU*I73d1;Ys(({1UVGfnkm7*tF9Pi9dciW#@h3+Yepewa~=#LiEd|}+@;B%C~%R8)|qAshBQ2$b0 zhB-eCt)W=eeH05Llt0+h#b#j-K34G+6uN|2sfe zF9FL_4t=#`SfxvXO;H;8B3>8QiTy!{MByD?!YZ#8wh1f2WsbyOs12{S5b9pP0`~*E z$gnL9%q2rG6O{jUyz<*{Yd&GkzXp7$S7@0{Cb-Z=goash+GgP_YH4a>jk@3yriv@Y zMDZew<*)Fm;?ZIH1Kr7+?DG-$bwh~S&SCe%V1XN8`DfBqDG?1OQ@B#N3wgPNXn~}m za#@>v>}qhbJf z%f(!+{p|mzi03YYu)K(Wf1R_JibsmiiZA&3GOF6ylx104t*L8|R8FNM{TS3JCKLHh zB<7!_+@SnhnTY1o3FTAl$}go>Wki`LPL)R$ugb>H((KK3v*y`Ma)HZ1TkL?bd>D16 z{dAMuh~-(%+*{8oJ_xgV4?lOHU9v(oi+7KtGSrd^T0>O@RZjkD7!8c~umJBfn|Jen zN8zuOR+^Px6{pcun55{!x3iPAy-CEnLOu}1&ywJQQ)P#^LhWV6WC1+llV}A_MOU#d z+z<^|gA4GRmV|oZQxy!ULLa0%Xw1xzekXEH070f=W?UhAvH*@&M=(Ui(b-djUVaVA z;Xm;Z-Q(AaGdM<}|1pHy?_y_^XPPs@wZ+mfZA*$JxK$Plk}Z8tOCuqAhZldV6HJ2zy_5YOSN0?`x>hz95em;0VFkDu6a5Dt6RnM{&$T zVQ&`|+vg~HrBj=CFoPoTA*x_O+sm7i4IKt@ejZqagLD_T%9XtY`rt9UjRWj%q#_Qa zRh%NPq70e;w&a9*kqzt!l5+sN&^Wrb&1R0x!sGap{%t#%VQ1(ienD}A`)i83U|io& z2Yk+ad!e|4FY*G*^^4;t{^%>kcf|)q8b3dQV!uhA=_IJwv;5{QM*9?LfF zR1D%jH)rfiVEv<6`*Ou67%b=TeEyZM!xI`pxAdy={IGCE*+;(XH5hBF;XL&Rl~6@i zR2DA_hM!|&caaU59v(+6x+_TCD~b|s zD^|!LRB`{LPP+%ik|)dumn=>mFE1*u38SqIbD=Lt{E=|2m*7?IB=diY{pS_v)-U*% z5^+T&O39UoC0h_ncIGx1rt>gHXFPuA^sI@cUyW$D zO{f*NA=5g{Mt5jAp5%WZa&Kbs?h!Y>4yAK3f+0OT-B@g48DiurWQAIT)apTmF$Tu{ zcn}_Qm@AvY|AMsJ8NNyF_Xg`E2|M~K>_*|n&HRzVBK6>CnF*e3v@AQflH8Z%D9gO6 z20Ek!2#_jpfE$xhZbn}DH?mDlxHaO)%sbVGf!K<(I$5_`{7xPC;B{dmR^~61B`;Zu z(TnD%jr&Nvdj(YqKVzA}4F18V`O28SWIiW>3cN|>;yhW+J&f_+u-%rkU(R4vj|q>! zCiVh*-X0`PLsmlt)g6=Eerq(I;JE7+7zq32wsC!sr_ZO)>Fc!X$sH_D|O z;7u;$dM-lqauml%u5W*8rJc#7x1(azoTDDdjyl-o3LItOomB#xSd?4AP%cI$f}c4< zg_ub>LPdB~8uU|Po+*WcB76zBH}u#!uF@~Of|)_~#PnDJ}~Z?#>h1FE^rs8n=g zR`$&r-_d-Id3=(YWcU`b6D+}6{mrhnmCt*SJ>djV<7H}kkErE7L|Z?F!wsTJ;@cX) zyc)vU!Zw(J3D~?c_(zqgZr2F^#x-h&z39Rf>lXfl48w5d!#HfmRIcU{cF}ocm{w(3 zl65G2?gHnr9Zz*D*ZwH6=^+q1C&IURbcNf4tZLGEY)+=txdE={G4tpJuRr7dS@;R# zmpQMJID5+dZ5}`2$b3DCSFV7Hy2tENsvvo;kzW1^)Ii_VrVY_i<2idr%bE z!TehBxYd36-mVFHgbugO@R8P9u$@Sf599Lp;cv9l9-&s1K|{2Q}4 zn+2{o^EQJSIg9^0JG_Yhy_jpch)=hM^MClHE19#~S#g;vxEoLh+sqEWgYR{K<1px` zqkP+gT!T|w--}$2)7&nHZ*hK$D{`0X_lPT(#E$lYTN-Cyv3bd8-h99ZNM_Zfuws3z zT@}hV3VJ=sz^+K-fX%Ec8~0Y3C2JjL=R6+0jU41p^TGAb&uYw$QcgZrbOHRS1XgIV zEDmBZR%khn>ad6_qt8)ER+aNg_@}jHwK%K83U9=H6IOK{eAZgr{|0J|&Z=017Th=C zzBTuaIqxZ}!EV%q_chJ33!V7gcD()@k$nTsy6~>dXBzRi6}G(@f2R|+CDVqq;6F5# z)#KKXztWWdUyY+7pQ;w0sSdc6x_rj!><4wQQ04epn%9cTieqtd@~sN;O^TvXP=xbB z9EJIQ1*w0=a24`!9WpIkHf}bmqDCH>v3rp+J?9$s7ZcBi(ETDP|26A(lX+JPuYAoK?{EBt*ZhqSJoB9AzQU*a!g&(!f5Ee#IQszq=rgPA zJLjKy*GGKC7p%_rJbuOT5xe@7cfI1dq^wo?=6@^oKF>XZVSI;I?{SuCeV;NHlfY)* zX01M8)!yW|!R)-myKeq(-Co2VTx1SkW(J?){22GAnbD`1;|Hlg?%{TTmAivuJF|ZW z=-0hqMR#FsHgPOxRjgr^EGGw+xt{;ZvPScHZa!-^6CeI3t7|rEZXs)N8qa2~+tIAQ zX<0Ro5u6WVjSgfL_XnHY4_tCT{N-NQy567~+wrI^5klszMV80Z3=g4MmNl%)v(3?B zY!q(A4$_G30d=`G%DS%$cP;Z#Wvpj;Uadioh&p%{HK?st$vVoj<5c5a6*wyMY7yRF zgtMaDa$282*Yu3&er)L_(l25~Ki@M=GH zj=@~z{_MRS;p7kE@c?ckI0o@}FrzXIA8a)CbTYp&ld+nNeVoeKL`HEg-31?sp~2HlJZt++Yo4?q&~IBac`CFS1t7Lw?@p zaT2QMFIZ8TxQi!QEAI_!^aHEz1MBu3xA#2GT*sLfJ)M=Exz;~oReoSWQn4IftO02% z9=WkXKR|DM#lQE5l~^P>RvtmlEK3zdwKvmt#bfg#v1Uo|9dv`wL)0SqWo^*EGwFeznqVw41X~* z&d9^#Vm!{pS?2$V<}A~eMsjAwgEaD+h$-=jYS1N@gc#jsFYmqrQSMf;3e{qE4xjG552{2yb(TLa_ zI*q6F3XixPGr1o#XCU)C(hmcvc7Ssi(p0+=gep1kdXb z98nR^N(>qBD6`Sa;76ACl@j_HvWL^C)3ghnLZx96k>PS_ujG`hStz>&p`6l1vi%$A z+@Tg*gVC}EcZ0^OLS*@pLlHUz*ZUe#W)jG}Jmj;>;8)fSDMBgK{uYAZ>QAh&NE!`t zvjP#rJhB5lLo>iwXJZv-dUYE9`yJ5Mi}4JLp?|Ot72Wbg7M%hS;yU4$za5!MSzr$p zuonKVzWm;)zCY_W$>ub4p&q9D=d+h77rsxdItN5U@Xi(bJ9B2_QXt|lUV zMHCW+zg#l>Fm#HQ*MZe=A6DfAI^IA!focrMb>R=B94w& zggs#cquhqf?H5^FGB}dF1>LSY5GD2nd)Zh~N|7diA)iHMV-UJat-&}CMpjq z=P0L>&q`1hrEg6DbVHgVoj$Xr=>Rlcc?zB6WTl+WczMZ~*Pu31p4@4*EGB0R_=2Bg zRgZvr8>Vc6B1%~@uVv7YF8~&*Bvq+wN{cdsJjP9OAb)cOCZK&kk{GuJd6Vz*!&G54 z@`HFgGTA}qT5+N=Enc~qv)owma#;Ct>@YR3rW3F|pU{f!3=V6j>?+DE2VoOlfmpaxaK8LIzO z$>4bhs}HGNpzphD=FHbi&L54ukJqx7z63J&0-A(yc>x@&Z_G-^zx zOl`pXT`{>}N!|wId)t%$CiX821H}zm`7^AA{L(ScGU$PM$t4_;*k#^@Gn;5RBY9knNAj%`4f9 zE3j{^!{c^fYi%6w*b`WS!*r$EYD%mRX|&Q4XA?<_59xjBf3A6 z`EPIiD?r;v`JaHm@8|o?SH>3un$(2afQ?%vUw3rhkD)u<0hQxI)Q)oUxnf}GnLzV> zrx(K&sR?lZtn)aLLz%mY5 zciXDjKhwYFSw!E+Pm!ad%+c$j3&q@ysT_MZwtn2sI8}U|_!{x5_*!vgVsAxjqf$@} zzG!}HtZI0md!T)%5!6prr|I+gUSM!9cBP$3+nu%|ZDQKSv|MyXEalklT#QuMs;jPxGGl4oCtW-JU;5?D23^*C_@-S7?xZ?gomj12 ztJ7Z8j0DYFUp+-NTe(OvQ9ex8F5ECwS}GYVBIXwI2MYM>_*!}gcsA34ROTuN-}re( z-HaFMlhfO#*G?aizC1l8y>7-Xkh#Ob+Q-sS<3Hy|)ZF_}5zg=4>MjXBYZ|EAb99uw z>@Nb2;9;OJh@AIA7ttZEAZME{{S)ed&pv`x*p!uYlhtP-M_Gx?SUWPLy~vDJAh!j> zlq~fgm?3)js;7ud*W%Btz|$Fx_tPIwsviDXWo$qlyrgA#4AHFi`DD4W%j=Ng{ETwI ze<)gRQ`P``*B1uGc1?Bdb!}^E@^ke04VMiQjSZ=`KQmvnthM$=ojXa=i$sLCrH@~g6=;SlwbY3v-yp*DC&m+-_wy+`J)i$d=dM*b5>xtri-UW0&0bB%QWA4_Ke9>uw~;k8-MCL0gI z-QC^YU5iVBgS)$=(Bke6!QGuA#oZkO#8+o#cW3`QT>q7;?dhqJ?9BJQ@;vub#NEWR z#QR*dL-8)f9JF0vEi>%bOi+t)Z)&K(YNIHh&V;WRCw(VPA&rCC;v6>F)zp>n70h7T zm;t>eC-X&_bE&QRF5TNXZrH`#hl$M5zA~Y&LtfWk%-Vm*|H!4~XK?gX49vYtazhaLLB`nRp*~gA}6COs@V?g28;8R+gA;a z<`WVS;bW46tgr)C(Kc*QkNzL2dk3c0lrapWIC97^{LE#M|N=BwdUXE%Q{V}>p%#Ij! zY`55i*b%YCV*AE)jy?~Ppr+j#39b1hR%V;jQ-eTEPsm~{VYs;Co#ndnwq_*%(F zIa7|qYHcoW!`JZ-WaUrdS+C7A%azY*a>+BpCCV2Ah_n;~ZitY9?Y*exn<&yFmi+NX>5bMW_JZnV9PXim#BbnmkbY2aR1r@4slR zUZal)qKC5LP^{#<=9uZ2h+6)hqq_5(bG@sz+w3`u?@;j$72k=C*twZ_$9(cAGIJK- z30*|ZQH|>B3KKg66Wj*$bQ|b>8=_wn*j%JjLF|SKS{rRcf_5eE$P)He!(lZyLNicV z8;6h2uC;2t@J9}_4f~Vrm`;uBN4g?k9{hM&6bdImi4B= z#yW;ooSln=43ae3Ys+g4>Yc%sN@O60H%bfJF%>H6U)(!Gd^TSRxfUsX37+2WzOG}a z!B_A;O>n75X}#=zha&hNXhVhE#n7wegq))ho09b@z?hk$jHbf<8@5J;-#o7{Dvz@4 ze@C*_O%Uz~Nx}S3udWDAM z9wf4rD2Og`0r$Z^_*JLWYp4YmsK;7x_IrHS8mt z*I(w+rewoAeXPE#!0u9ePE%R^Bv?l|;V!>H{p`L;^>6R^Cw>TB>p%SpTIM6(u%lyLpk^Z=kG#)US)1@k!pxKo>U|? ze&P?LZ+_R!6v{xq>_V1rkR7^U+->*+<a{alX0F6oj|iO=a>mlyoZZyTxYi)*E9n~x?MSvyQ0rAYlum&^{F^!1 z{kWhyz)qcnZGCSb!4UUjCDGlZ(Psa zCB0inVm{)_6Bww>4EChPUP!kwMRO0@%}?!4JlXGsc_<^h8fF^)!im}*h2lvZ5QX)R zQB%}Lp}J1jg9`cxTlL#GLp!2UoDiI&jG>1c>)Rr~z*RH`MdEnxCsbqK`Q$FS1lLql zXY(9&9pxOuAuH5&zH+W~HO2Ea-edMoLV;3Byvb+w3L;g!?}u-vzZI&nEY4+FKB=)x zQ#vuv>B4knAJc?}s{JaRx;<~tOLl56;5)9M3Mmh9;j1cxy0nl-1)+>7llIi`@^u=tlmL{cZV(9#^ z{`NBAkHR}cvMCX{DKau@M^uaG95J6_YR7huO^Y$bjEPzw(b8TsG)Kq;OKEd+({(f; zy$n0`+l4B+jSyv=%KrlKexF=lR{iFWC!nHmlgwZuoB0Ddu*In`OE@2*!HjeM<&1fXLa?7|vbqHhwa40W+|CuqD{%-N^zZcr zsD3W+9e7DzcxYH)sAu@D@2kI#%4n9(34!=NHJgmKppZI|*#nMcrFY3;sr1dcR9E@zVuGJgZeO6%FfxT(#XPnk1l=jHum5I?y@JoA zqiO(u_gozKS^n!EXkPBq8*b*a%E|2K1(VkggdO0MOM1i69tKkM{Lv5oQlE8i>wq)F}xbtxZvQ5wSj(pPE+5U`6i^+^?7tuJZ zX~=kRAD5*-=NH_cX(sxz(bH@2>B9&8d7V!<5lH{d3yow3BID)9$CK$p~(rz8-E#JGeQIGHPbt&P;GLb+&YE zfzHrEJR&vo&GR3EpyCO-RB7biPKJAQ08iU>p`?DW{xE5EcEekk@o$6&y7k)rnrQXX zU{j@AU-Ni&Tl!DoIwYXt8j91Y59uW{wn2h0Im*C*sO@>{ z-5_Oy?=h1M`p>}(RT0fhZK|$@zNujZzq$-_QETUrYc_k>yReb=-S$5A9bxA}$JxR| z1gh3SriR8?!wcbWomo3jeI*#L6!OQ)8nKOM4=k^%nHd=kGVZ63POp;gO`DmPmf9=z zO-h-R2gz%amn82?ew92TB`Wnie6JJfmos)|-f=u}-g8Z-%BT#vz${ghm-$}#J1O6T zjnr#3N3~aV+u+>1*EckbK|}VCuV;q;3`=pG7BOtme}=}AqTQxxp}t7%HP^pIR>U>l zU%avXVA|z#xY6=I#&_$@EaYf`^LDu7qGPzTGL-c1?l@}E;nFO5G7L8_i3$Iq)a=i` zcucUFs;hdX#z=1VTtgL;-@M6MnS6j+_Q~N15yvAlqw>VKV?M=3!ixDGzaV?j9CLHb z&QU){wj8swzlpCIe}T-{7IC9lqCc@4ZDHpALTgo1e+wzqbIsMm@ppzUeMrjQpZk+M z-;7`PeE#~m_}32K+I_G8(nr;4KHd>a$NbhNVP3A;Jn=Uk0sA5|VpYymSpLw`(q<)1iw|0-( zu3E123cU2yl&`^-_~4%EigljI?4D60y-C{I)FG+UQ%|L4q%KLzmp&~0WV#`vbVfVa zAu*YKaYNL>sXUT&LZ?e{8{uRdP(dcZQ_hKh_6;<|iT*}`lFUm#2HmPV>T#NAGPgbG zQlrVUI7yWrsohRDXhG3kPJNQePNU#xWniF!|E0WKs)PXh9m!ntU3u_gRd6*V-6xdu zz6)C21E}oZx(%Mro(rCN-r3@4Zp2xf`~#pVet^n%m&`kdYPq@x43#Fj{X#LrcB9U` z))E#n*0wkFd)OKKsPIbkV(!RmQBR|P#6-m9$<`}Ad-ieJA7yuB|B!uVc3t-Q@m1od zWxE=CD%ud)G3yTPsLs&tes-Z!pwj@%hnQ!|s_|J;_e@7vg~e}C!orRmrG-)bhE z`;kBSX=>AqfsQpUt=C9)R9pB~Ujp@%g2A?`Wl-X_YP~vx{(}CPVU4k{sk7;kskphU zS#7>=s%dU#?tyl8nk5Q%;~q;#%SkGn8HNabH{DE)T0J2sD5t0%8pyGdMpQgb_kGtc z=X%FJ74iRoDGBb|ISsyy~S-K?W#W+D-Xnx zoWfd?wzlFc%8s9`0F;s0XqOto+Bu=j2~JRzqdS_wj8tL1svJ06qA-~d`@ZnH`h7MsOz7)`ogpR+mp`S979Ua34DyprGjJ! zEc6xdU-i!jv}0~Qok{*w<{bCb(>3*|iN_1a^(NyAQU8JkNFaFC3bGy&}`M?7sYRnza4MRzAL^!d`h;d*j=1t7CiP zTPB`#Pj!yTY?aR{3WI z?(nq!Qe9Ku;GNp8s{`w;DbB`HB)_yp<7{QR`Py9H{K2F)`%U*vvT3aOFY|01?XszW zDblz}pG{bxy{*0z?57k9%=c;JoKj~|?fvO)?q21Z<9z8D)I}5w@YO44+ZY~SlhH2ak)5F_DRETU4<&Sz2ogK=oA=~q8f5vx>9}{09 zep@z2T*J79vAJT!7%{S;v6wwaTv>gRWp>m`Z5wq-U0ZjoW3_AWuN*%|rdbsO2$ura5WSE_Yykq4G0T@s^i#8RKb&`6arn>MqB+Z-V#g=2H z$(A8zt*M4ZF~yh?EW6E1O#Llq%*9N7P3Q0m3x+pBe%)DW-#B#(6t<^=3zSuXZ2p;Y zA;~80_UgQ^+-G32TpLGK3m4^`+HlIZ2yc&>W7;uFq| zc6}2$1!JX7ykiiSp#nttw)uwpCkK`)7lRj62h@up81~V1q7I&9SY(t%H1|jR&yGJI{5qTZDZOxJ zKj%k}Lwe(T6)2^uplOQ2m*gyJt9FLJd3N5I4w&Cq%2+pA9$2bdr&&Cv4km*s)gb93 z^>y{5P@?}yvcMaiS(l`FrH)rCP&)&Gu)ujH|Bd9yWMS73i$mpT=>1IQTUpOaCQh$h zS6#cwnu>L=aff==dGdO9kX7@Bgg>2_;2p&VY?QaKw>1flZ@jmezK<3`CQn z-JiGYZ+f(jklO2Laxp15ski(Lx!$`-RGx_)%Y=tj|@(Xr8&q8djwwv%XX{vt%HBLV^GhWC&= z)j8F1E@M^Nv6Mc^A-@{@9RB0=kBYxSlgp=+O{979`U6%t{;nAS2U`()l{9%8;}N;1wdjMi@u#_-d4#a+LH`J0EtqeQwXooY0* zCbGf&ylS$u6yifjZq9@$5f^5I1uJcR_Z;{J6mWlHoPYwtO{k_?} zk02#ABOjqUUdk1obDrm(kDOCgyluUsp|Lb(lM;oeH_AKLo0Eho84lM^-w*$vf8in&6VnkjlF|Ct4ZM)1%cKN zgK33x6Rys$&Ng&7VNiXZyJvfJ+uhF-n zdq%sNc0@&oKq4F+y*#>nR7&`m(5;q52BS7vN%T3y)?TY;9pv7U8AZ~br|kRX_+k1v z>gSW6i+7qSU|==>z0bwJm@im7RRO>^j)230&9oJLA`Adi1HlS_kqoFt%$ z%+tT&SDpkRjPGS-975x|5BB#dX#h;e1Uuo`ieP=U9mMdW&n3`+?`S zmy}6qmhYwi2-DM!q+;w-)!_5$!HqdcTT_=!cqmNKA2F;ay>Fy>xTQJ!%4;DHY%jRc z%8@L8CHybu8#f`kCh`e&jb0PIBYJ6chv;0>F9hFlh%v!97Ao|g>{B;bn$Q_bFmqBY1uTn9GmZZko(c)DhMSRZ)=}34 zPM)ZZWgf5BOjjR9wQ-*NK2#YA&-EO&_!@GBzcUwGA-|R4=$Nm-530dEca9S?`gcP7 zVBVV-+*>0^HfrXzlhT;(3CA(F4(f~shtyz5m05qa8>g*>yJ(@PgG%vEcKRY=NQJSr z3aB2apK7L3<4+`QV7!n@mc&DVw)A(3c1ND2-;Hk;D}ohWg>@0_K6%8 zc_{K%Z(ix?GFDmWz17dOO5{;Ug?(zxG&jjpVREZ2T=_-_C-9}H`x?Q=p_unW2lC2p#v{N z63B7L@&%yB*Y!OjXTA+vuSRT>6POAn^0m+FB`@KBq>{IQ5V*(tg&kd9&weIjr>Ob= z2f4d2H=_^Qz%f!$XL1+5@pR`F+%L|N*2yP1B}*w|gJV>U)pp!vRUxMq<$N5^De2c$ zV-6j}75-hP(e>2aAqRF&;G^$|{9Gz74PmQ(9Ck(t&qq9zZ^+8t1h=~*Daw~!BOnis z@eG3}*$T(=8#uoM*Z|xKtX9f1MZT<>sjjAphbs|7PS7EJUot7G8UHrkGqxsCW;{f& zLRJml@ufC36fZ5@uSa2d?KABE*+XEu{TuEMFB7pS;#-~ zBJPUb%G4$YloP6@nm={P!WMmDQj4mYHkceH5sLbF%L(&x+>N(Pn~X7JAfFei=~_~$ zt%r-*Sp64%iC$DChg28Qb*w}mo|R3}0XB6!x7|AbDtJsO=mIjKIF6R~Fw>0iZt^rG zC(}y;d|5K&yLjq&%6l@%nXc?UM1p)3erw`-;_5{r-x^OO4%2>A?4E z2n{C8SA^7}SQJgsOa(`>TiwQqSqQC53}?YPT_0S1pM(I^+hxKz@&>PH_Nw+N=xX3DL7n8uZQRmTR$EkM;UO*zhofZBTZVUbNm2r*kGhR1sG0)}{wL$tRWSefY zgfAA)F3T!7bM12+>TYa5g&NRF&W$~wU_JpO0#p$ zuhhc#djuNGNA9DWnvy7*dXkYABCNrU>BXnpPjKi0+RvIj>O!hB%3N67OMSiM#?m0M z1jOb6uHMdijv+8eVl%Co=FG*JtsQxtF|Ic51jz4I*z!E~o#5WPseB9Gq`U2^31|*z zOHl(|*ZFj@5YcK1ZH4A&ZL~sFVKUq7K6-&1fmq{7V}0lz?aa^2^DT|6SFANd4uq7p zO|rRdS3~QBZQ^F!XuoT}YTplK_nbY&o-KS-_=WIt5qBf1My}y~ToMsye-!f0Ji)MB zn?v=dGCnYlL>QZ|iFCkI+cm&(J|ky(pR{r5hcaenZgw1W&Gqc|&XxN4M+K*-g6f*E zeJ=>Ls9*9KE1(D|2M5My3Qz~XG?g~>G(JYlFb|G)c5O|~V6}i_$PLM+9jb}#s%F7O zcr>!oZu!3P*W?Shm^w@+p{0jk@y)^gSz9hCttBUSWzIJS5;OY<0S2g zp5-+z-Kn^9Q?=D~HMyO?Xg@&?zKBMozB)qnUWrGqQo=uulOc}zTZ*@)cO07qox8p3 z0xW`ij&bne$~lfZ-a2F4**wMBO1+fc@L3i>FPO;W_%lrH#+nFNY~$HUIfYXAxQFRm z>E8((g<8T_-5%Y)PAXq1ctaBw#_A4f5i&zcSX-d5X;(0pOr z!;0DG*q_1HoJf^=E&OJ9bi~+*2N5k`>h6kK7Ii5ySH#!Qzpek6s_CuTgQ}H^DNxN{ z(057lcu%;kuKkW|nKLuKrVq*3mHEqY(RIUP7d=u7e|;rd^-f(tyH2-8XvwF!*YJ;_ zx-p%k`u~|~nLX)eh<5YL>Z*;@E zvJLl>gHN?8xtM$X27j0@PA)BliX-?GPk1)|&e|G=La7Iz<#x|kWMDt}WIK6M=;|*s zJ*?|_?P-m+r>NLZ{8JnQ3w5=4hwQu&bkED#UpFN)qL^Ys?|w{GhmBAX%?9XaMYMfU z0j|O!I}UP6HSJ68%RZXN>hG$Q;5(&F;1}AIoZL2>sF1#UH+fawxzGZ(y33GdZG}4F zat(JE@c2AaNL)C`thA~x9#3Eew4$SwvM9|;s;+PgzgLf8>oyQBOFmSM`|xX@;w&%< zm4&5(UjL6ikKweTBYBBqOd;?W9-F&ZUNbXn1zF>nwF$h9INLc}Sva>n!~PA+Z6D0N z`JB|eFObgrhp!3G#ZJ6;WT~htQC_y=9ae0vj0(VtNO5JlkTkkqCsVRZOkxLHJ3K4EF~;z z%T%+|bikCyRLwZi;9+AjP-oS4)ht$bMVUJvCu){w?-VNH#z6;8wca?OW|EzcJ9 zlg_(%P`l$TEk`a{|6rVw;ZJ9eTu5FlJra9}SLrdALx7yYC%GGH%rMtb*JD>tGE+Ny zilV1ICk`d&^S)dfy7h3snH0CeII?yG$C6r*hSH`py-XdnlxJaXoWTJ-SXU0EX?GmZ ze?Vdx`*%!lA5tSl_Mf@94AhJi~sK{#(O~bE+{$=fC>aGvhUJITNobm7Rm6KD%CEk4` z)qQlBGE>tllFZQEal_f#JgF53EOC)4zowM7sjj5nqo0h%tfpy|DbaM@bO~k4 zQ{4V5^-Betu$4sPx|$~HW2!;im+f#Z>_iE-0yXtdTx^vTft&LWDE1wK1%pFz&lQ2Z zS1fRmyxaU}UM5H##bMs&-rU}o+?-p`5vIdKn*|521T(iouI8@7uIF%B_Bm^~&QeQ8 zLcqL5mokA~z8+|$D=4hw-(Crw!Yd!fr>)cv6>OJ_c4`&7u&yyDAdFMIE z9=9Z<&3kee7>gazMmGuE3UtOr*p0nw3tY6_xpQogIeMYEovRtmoV+uW^3AxR-;j=( z6E<04T=pT_C~XXyq#1BChd@K##Yrfzxhi5@Wwe>Po6c~r4WLt~Lnhk>YeC3pb8S(f zy-C~a5w;luZZmG!@Afg_f$-%_>-I%Xk1QE+JM2No5_1znaa~b$1*HQw>sUFHieQlY zth2sjYUY9rXZo6qE15@}P2JtR8>Jn-hk@a$aLoj*0b*lSLldLJSO=cVQqx&e7tRf>o!^b*d7OPq~n(j-^mju;(0jDPkE zr}0GpeBUU!om8C!;uoG)Q1ui}+q3RvcIOF!BFY$k9a+hwBk@Yqf`jy$M4xHu2e9;C zpy)bCcGNHYV6%fqNOLI>XvOA!Il0$RRE^$IPQT) zVrdc>-;ndwm~6;>fyyW%!_ZVD!6RP*X~_U-U=}>`t?J1r)4QV&D9_W>p8q|Y|L+VM zi=a9WYV`5c#y?O|&(Qv0Cb)+b<6@8rPe4knsP9iQb*jF$VYA^gy~R0WPw2-6%X!NL z@(N<%Vl@v<4(-PqJ>UM>UO4<(6ygsd{9c9=@CW*$lweWLX&v9&VcwZ({7fsWgZz0CG-wM1?6+`o zTXLGbB!Q!a+*v9uZuEZlj6)Ui$bG>*(p?a$md5><^nMFy`T?=V%7m2GnrVZ1) z2bs6_64!9s>X`rKks3+eNlbmqW@sdNT;1T;w!;3LI=+sW2A<7z+2p2bEDP(<5|_q!D8k-6+#R7}wJ@u@F^sWX-n^e<}YL==UeP0LBr zG{HtY98wM9ZNIGKHn{Uc!>*)~LP}YV8aoLsHF}jn zsqOFNn<4)JuQFOZN8gy~N^vc5uX1}m5JIKgK24y8aw8azGPQxW7G3)U=t*IOLM-=Iy2#HDeRE@-9l zHISQx%m==rzQ(8nYcm<&g5GSiNARrSyl%s3-5f5`P~NMb%(9D;7`B2sYZGjw*Pe$E z&^n+cT+h@mN-Pb7Z3t7pL}=AT;Mks!V&o}uI*Fgmn~g{)PS2HiKGLDs z+3}G4f^{|urOZB94J{xvFZQqUW%YukrF9Vf$eMxX6z4g}X)?rJ&ppMxi8}p%RD5gM zid+|0NrU8EzAwIA{$cPF4&s=Zz^9$%SI%-X|AjYg5UQ!l_yEh{ejADR>>N6KH;Jhu zRon5Mw5Im^i847KO724Z>{8XQ(6?=cn6a9taSJy~L6}5eQLd+Gr!gnFFN}nqlw#Ov z>|ioMxth;b{oFNGjxf8urTui+qR`Bc z#g-IfJ^cyoXVq(EV_=m(*;m*XL7!Jt9PB-d4%_KI=-KMs!6b8`@4nxT3N%x75#P-M zQrLF!g7p@f(J4Nl?r+3&I|#M66`Qd%byZHM;;3JnXf&EDYK`U;$~_x9-9@->8bZz- z!_WC3>h%6Ni5e?C17r9v*Y+ilFuzQ?0wcAkw;x$>C8@beadsbuY53FaV!G6rl(;vZ zPTtLQ!av|99puwqPPV4QTLag_aoAgL#b+pcpNmIf(uYe;r6s)UbKzB|_%8Vu!a};B z)a9qXn>Rfl4&H`%e~04uEr`GC44=qddbmS@WPfh|pWG`OILjxqZ9D4i#FLf7TasOA zZ#Frny^p;gym_HHeG}`mhwCb5BPDr~{|O44tQ^hkIO@kj#x$d|AA5`bEe^HaBo9mF|g4m79dX&}DmInancZ@ie7ui~6nW_&m){9OathBlU- z(Dn58<-%`vGSC$#-6A}V`SEx6Q;p&0-<8C@$|?`~#c_CSmMKwa|6>CQ&^$Mz;#?~W z@?T_|`Ng}`_>09wY*_yh_lmE?FQjA*g4MKH`b)|r@nDl|Kyg>xpUCHb1cpWqIBU;1 z>3iW)j^{g_uezst!^}*t7F8~I=f5~@lG$ok=T098Lopd0tE@I?L})l`$UNxCoG()S z1-|A~RLULsH`F56^PuXMDlbn+5me+IZ~`3FO%ZDAO@=Rq4fry0Ff*BJaWVnB2<^Ol z$V6Djfe=?nUE4gKgB7+*5YF?5_6^mCwS{Y+*FM$W*j_U%cW46BlGlcwLJjSC)qZ7d z;5J!K1%1$|q!Qw4?>28S7?#hy1tHAU^F8+ep{xp4Q$fPfzWq zew(#w?On}I>e1?&)zl^VwC!L&e#h;0SQ|twz7U-)np^x&Y%7V_k{C`z8Aw zdlD1w@^)8fa>!`Q4P!N7g2t#i8QAHc>gy)IlJ?Qnc0++~76WX}_K9z$kMeF`efC)m zf-CUXglVeM%l6Rz$6UgqJ;tA9QJvSR*Wn_xsSofS-&3#FRN)Em>sAV*xvf%|*d_`M z1hr6=y`M@OqbaT)ueuaWR1(M@I^>I#AF}Id;~jyMBL}%d&)v6p|A%`{d77}t8!9^R zY(z^oPKxznYw;nZydK`m@K5!g4fN=dXmmUAwaWd-ZJ|aKJ(tkwHli!{%4dCpslgUO zt?tLWnMPG#f|Q^D?BbV@O|pnQD&;*|iCy?z=fmBZjLWVGr@>t~pDq0PN&6e_%jdf; zx0KEDc6Q0Rq#xpK@d#@D>*5tog!*vXB>IP!a&5?5dr4#+h%>1Mvz91GT7~hnuft{S zXXY{x)?&2U#wY#{pX7XM;bm0&qnPTiWKOV#TYfROz)0%Pd}yRI;SXIw1wWoUAd7sF zA7AHZ{NY#mx8>q=NL3AI`ge{yA&>69ZkbR=FX_)4`WW@lvGc>otZsS6c4Iw$$ig9u zp&xIg(~Gdpw%xQ{vy~1V8hR(RXjtR0r(uQc_3b_E)546QkF6!lhYh=Qd(`WL6{vOR z`eu9!iX%Oyc_C%G@E2!Rh!xzV2J+&j$6Vm@*X}Qx?ByB_zi-cq`N3U(JPzn~PsT z%RoCQuSs~Sg8@C$g&#~;7Q$f?@ode7+IAD>T4}iM9pt?7Luo3`FdJE@4|xuvrDoDa zX0(sx){u7hpm9qjkuW=Cn0t8qM?ghriVr*&4u>}Mw<=DeS@avZa5#*i29US~a=@Ki z#4WmrpXW)qR`p@f9l&w>iYFiz4^2#Pqf$~yhuXA|=lK@DzFA6ro`n~xx$2Qr9#&WyFpb#DaB^_EkE})(gEBz+i(XY@SV2xJ@)0r zIi-Q3U}XxjPTieK@F;hEwB`g)vynP~K8o_JDrT^1lgfhw)2EJw0$qUImR^RUF8$;iw(sc=mgq$ZbMK_ajkPLVs19gwI9v+LU%**EuMPMiW?;yeRO_D{N(}{ z0}VO(PvO760`;h+VpTE%_X5kQQEo@pa8p1mtT_I zL08C%lSw1YKP3-6M;&^twdz)+nF)9jy5rnk314@y!GJGwo$-gUF>bqu zrtIb!<|pQ+mIF9q?pQlQN-sZWma>@;~P5-FQsYWvwEr{;ZraP}2CG61eGaNG3U`n6YbkyiJR5X;*D|qV@G-2vo zsuX2sU@15A3uu=gIMaWSJ~Uch%YYwDr`(a zy9$qQZp|gUP7BG!I>FaY>c$xQtV@~-+97xm2WUGo9k`7fSXPf!N2ou*^}PrQp(Yd4 zbr41Zq?LYC4Iq5ZYRc>A_57FmqS4F& zS9&hIjlvLHJE4rOhUfnwnq?gXo|(8WAE7ZH%oM0ToYD!9AY9a53)u&?V_KjkeR>1E zL?gP%EzAeS-|S|>;9wkPP#+Y8q1Y51VFArAI*19>p<~!y-hygr)wCf!?*mnwUwcM3 zL&&X9*Do;?H2%vZ{xHgx1alP3^gZaes$1(=|FDi@D=S&UNm1B{_pyL&4OvPBL+6Hm zBS$Mfw190Z&WzH=%R*aita>@K_%-aZOOhIKncFodOp~=rmEUZ&qA*upq2bY}CNTxb z7hJ_wCdlr49B;>>fR9?eCceN`s!2>dK5BMqJCTL0)=wqT*sssfAA-)mRxk;Y_BHO> za%!zA7q?`dCz~z zp9InKJJsSg$gn&8`}yA@e`X5!m_yg(OnO1G=N#@R8+F5UxIUG57TPd@ZbvrS1LzDT zNH48VuQdwyW^-*JlI5;*YqZf6)_CYH6R9)DQBC%t+ib{$bvv}n?Wjm*L*lfnB{Ix6 zL$~~wX+&0s@d5I?oxhf>?#za2ls2CEVq+mFEYXLcvxqZpVG|f(8bHEU2n6n~} z+_Cs9GvI8Tw0>p+(K}>)$eWNs*+2ru#!%`p|z)M7MO=^MJb3%{JFh-eEcKQ07jr$;O(+ z44@OWk3?clUCx??keDs>2Zx#GXZROTH4frTYs9U58&Z;u=~8XF%^288ANhY)z>-=D zd7%w=NsXX3_!-X64k(ZV*-qx;$vwx}G6CXeN&3*DzlpDzP;tDRIypE`mIU4f1Za*c z;4kLJi#eTtXEf}jxy+3Jqo%0Id%cR=A&KrX8@EFi8FeEXkIx!6DvVG%^tWt#Kf}G< zs96hzWiG7LPD~qfXhKkfO+i`Iio}C-RVnKCew-AKm^k%-11?ip_NR{zBd>OkEK>77$N%C4ZG@&@OqmQWGhVKz2Uox*&!KP)GaiP{n-G*PGv zJy7jC_#gTgQO8$R{t3?HeHg56O;xmnyREslA)VzwZ6X?J0f+Sj%_q7m8IR*SCa{Cy zby_)}n{(>4WgeRswn{hU8#L9U{z@c$`|0q`Np;apg`&>xgEvBrPU9(0K&r>#3D8a4 zXSa~XrtTzkWBJv#WXpV#J0JO-;^Ump zVs%!e=bR5m(*X~X;!Bg?Y^1-6B&a&jP+%c~=wJ>T7Hw1-j+XZly^YiYuCqbwS8-vx%#oF@wlRTMi z=pmY`!h&n4)}IF+abl+UyZgWSCi>%fTgC)tD(j)5b;kYlo(fu4k0zUI65nA?vNn6t znIzJU{He1Lg3vWh?OrXU!CJtYyXRVji*+|C9dUc_`zX z$&KII-wH za5}8x+#DQe%S}0%yRva$HrdZhs8*lTuU-!v)E0H z3g+M^TZbp&AcS`tlg5s`W4mx(ALIAGARhcst2O1>(&eXn8>*?RF>4I$Arzj5o9ZJF zFtRw_fAUW)=46PYs(7O*4*y~y3j8XZ3}eyA+Jt-Px+VynNqx93G}YJEk0!-wiQ$Mr z$8M^WshBCjl$&&hg6993AJTs$@cYu1{FZo2XZq6dmg8o-xwuI%ro%}1ru_=lYLGgH z?qL*PZuLM-f3!~Pc+)Sc3v*6QQ1uO-W|xz~El~=h`DT8)dZ^Y8s4(*sD^s4O!Fgz} z|Do&bu1;n$RZ?AAoxmqeNu1v0yoEV2tP~dXudL0*$BhCW#BOi|2nW>ka}}YErpO% zk58}`=fV_aDEpE$>W)WrsfYQr(@0@^k1De|y)|)~4NR-raxNc+8FrY7nwVYx%=^#V9hpsp?;x$Z( zAL|5e=-nuais@I7)H;EjmtOD~jHVH$yX3g}sV8!qqs=bnml5V_<}}kV^Gv*NPfd$V zKa4vJmp_;)Qv>eaX48HSg)Z|y#iGAlgSAyku4yvnqs1`r~m`PpX zW%pEp&f+jtVH_0o(x^|W(Mz~tq`ZZcvXFjr4*T3~F!CjJcB+b}=z_a5>(8#MPFBNy z(qNvD`;&x%*AFqKGRobZLb(2K5!_;3 zR%{jQjjBC2`i$m*Hi6UJsG|cBN|I8I@B1N>%j|6Z?y;NekH+uxZ#~gfG+SAG%>X}# zXY6nC!@#S-o?;Y)gz}sbr8yl22Q#P{ZUrg__WASD-3|8TWcuTRMKgjPuOfLKucR%U z`gx_}Y$x`j5jcwO`Y=hK!`;2fgW3Systoh#OXTdDBnaK&c$ae7#DuomN(7@h6Q9ilB=tZ7FH!UNezC7{IBd#zsFFEJmlx{5XSchC6j+P zD<>#~6XyYyY z(r2E8VWctCB-P_691}eW9_2~hsKK_dH5;S3x~IA*RA={vV)|udz3eu8Gvva7^2-=S zx8KdQ-gJ@lW)BMeg64{7scYkVZEx;ueq(B5I&XYt7^^Rbr{TF)(kvxiu9r3k(+`C# zn)0N!q_bu2#Pob3pXF_3y)sQXPBp!dnchBFlR-M}cmCC!hs)@yI>SJ#9aIJ5sl2+7 z?=_aKWLLV;WGd{9^mgXpMdhSo3Kr&UX^CRs0m{j&GdV0ck~eNHGlv$`A9a0Ce zSwqI_OWx#vrJH>1lLo>#+DDepcD#aF&p;vfMb`lGyawYs+v;+=>bj?*3bLU$?BQAM z`OFjXo+`62>ic5eo^%|?y+Lm~Hjg&xu#}HfuNU$(Zi;H;d005vkFjNng~4f6!j!Xt zQGpihER2DlWIi>aHkisb5*!3ie08dad}tr5Dhf}*5>A2Gz#act|4Eoh$NVq-*||CA zL;tkF0v)CFg#P{q_e@nLS~~vSYQ;&_S`?+-|2R*QAe{E5MoFTUXwQ~*5GJ58ykX69n%?`$}{Mzb+MPTy$V zFzpli)mrp2(c}d$;yK6*U#U5rfJ#%21h*>G3A@-X+~amE#2mj5DLoE*EXYi{VPex))sd;Oq{Yjz{O{YmPu zC;o>1OJudA!jf6T&M*d*bpiHX`F_6zEWF_-mA6WUk~3JEX-g7SKvp%;R4ER7PosqJ z{4`W1^5mbQ<}tuwYDLZQkzQgiPl8qX$#W(L%Cgsfr8I^m_&u1NTXZJV{IT4mp;S&Q z=;Lcbl#YV~{Rf(db8uzC*{;`NbGiQa?)oJ=Z$X`#3i2QBZv&szL~`R4O-pS(?R@RO z%%5D^2ITa-qk8Oy%cDKho}2JC?xI<2O}^7(Vx_l+CjHXXM@UvGs19AKkY zoNA&yUC9L)ND0huJHW_U#ZT`W(|EJ8o9D{~*S0L4oV~u?RC=R)b?7U;$h+iTIHxaD z4SK}Q=#`FpJ9>+-`<}r`u-P*o563!oUX4&RoyR>@Qmg{?Yy_;)a$SY>fvEfUkk zdeVByg0d-vDfTv>;v4V3=(my_lcht5-(%TEfBJvJHR7UrfAR%<9bSrRz{BmgIA7P0B_G{@9GK zI{em(lVBdz;8Rr;x57MTOfTpqGtmNBnb19Dw{?wEAd$&LmL5z+XEBEkQh-hNAO4}n z&~!tD?o4rZkpw2-z%HvVukWm%L9O^q-vrKzXz1|UueE_Rz*fwi9-8l)JCM(OgWZMM zT8Vte7S=A-($@R*mWk$rCXFdz_zG<)R)1W0%>1gg{$H}Acj~Xf%?i>^CW*g5+gssz%cSOl)zzE$o~^2 zMg?{mwU{}Sla@e`-OQZH0X6NeM?%k5fIDEecRC&C9g;owqH}nQ2IdUY>U-Wt5RQtl z?^-M-i4~+ikX+qTf3_hX@P}+f@6;1LN*e0nxqR*im?2^-hu$DEg;n7M$**+g{XdDW zxH$F90m#bd`M$3+&+ZQusyOwK86|EGW>|xGnh!w&&x^;iGi0bg*-^E}tNV%m`7Cpb z$x!j<(_L=o?Fiwi7{;mp05NZXx>1eWDVg`^C$sT)e0_o@C8LPX#^kXEQ|Pr+^I9g@ zf1zoM!{cyC8wrJO5z3+rT~RXGItep{8^SLkR=->y)Mqp7A{}s|@u#sjBoh^hju*{7 zb22l?Ec)+Qh`x&DPpe|_T3V9|8n86rRJdqMPJ%t{YZoO1$7ovH1&680R=FmDOkpb$xl2m*HdBU@p_hR7HdN9h}I`ojU&^>M8uRo{rwvqVMhB|Q&wLnd@pn4Rwo2aP! z^LK_ZA#Ffq{U`rcGqe2RD4gTzF}kop{)oyYPFo#?Koc^e-?7_{qjG!*i?{%aQ=7h{ zej%#Y1%?}jfT5G|BK)!X+znon-dqUpR)Tr8`LOvG%BVE+e)C{+W%GM7WmCuqtYCa- zxMbKy)^8&E=v9W+hGvFI%rXB)H{Bk8daSU3IyFU`hxy@Sp3e_Zaq-FU#7<(*GLdJu zIkS)qzPH2tBzjR@H0J!Xau3$zJCZmPuJOqa<4I`13GkfkA4!RYVv`F3cQ@XytV9I^ zooY|c;#hVhUD*r7K%G58cH>`=X9K*ym(dth^2MPRI}ej#4YO z-%f6TU*Zk%B3hIe{Qji)2sOKnnzJE1+WAZ(uS>s34vdg1%iZJ&u)uDU?Ov9SWufnq z@1@V{%fVUH4hH)gYC(@b8jZtDx=9~9yAIS03+Wz?avQv7_btF>EWmVXCg+<~L?!?)sPjHTR{qQ#|DDN7cCtjvLn5C>cd(YZ(Lv7sBYYh|>9~(SJH~BrAGU1< z8>(FB8*)Gm4}}vh^5+r?*f6F(&BHe|*$f_cJGCXV5p;q!#& zD4#p%PoQP=;c*OXX?9j$I4KABfARY}dZ-G#yv)C-*mO>Vf`3O}*9rHKS zUeid^R&qg)m`MRApxS#O@d9?WqDf+)SCo1VJ`q!wOH#66Y z74*VYT<{j%U3M;II62f10(NWGLx>+mWm8`BT0Na-B$D*lYRnj)@H6|HlVB{<=Q^CN zrTFwEYOtO7%5viQ98Eo*#xCfre+5s#EOOV|`fH*pI1QJ(tuK%7J(-bHNE&V=*W{DT zk4LBw%9Elz4@UXDbc^K38BoSslRsNtswy?+v!5;{NavYT{UAS5LbVpnmZO+lg~ZAB z(90JydHNyi(XccpJ8~>M;(25bF7+Ms9c7pL#wYp0=xQb~Y5wJppsSccQrK}6bf1_u zWoORZh-!E>Q@1ai7NvQjv+|<%(n-HyhaJLq+>^=eN-Cn)suc(gH|De2 zL0u4F%ACasD#09~E1ki7er_9K#op39VK?C>feHS`xQl$z zmd5->iIZc8p{c=vqN0NSmM}o@vLhRWyUMP6$R2*Ewud%?ZuHV`it0Ah?$5asqu2(n z;Z~^6z50tu)N!)l{>Qujn$Nx@Zr=qku^w<6%<^@EnvffZff+__2Fcx1_{`0y@fLEX z=ajr+GRoHLkP)_^K0AdAY^S&pM_~(U!UCd3{6T&2kX^(tuSv9ub+|rQmLQHptPOzyZ*mJ4Ion+ z7GU~XhjVl&JIP%r+aI#2vf+%&uc?UAsVZCQI{dLT+~f*m^;OX{)3j#CJpy;@I`%ua zm@H+|GsNR=s->+%^*9j@^M1Mq5%*#T-DpxjA2WfjNP56Z;VORg2r9{e`XwX{Cqa_= zlPR>Deqyxo3>gAmV^LEHyn{BApW1S#aU0vsJ;w9KtHuY$C&pBx8n0-$DZ5ED{%4#7 zG2lPLXcQ>{7`hipFYW_>wV2*OYWOK(I48m>rW*h8EApUTScI1}FByYbwAyWKSGuZ8 z;B=UTui!NG<8zcly~%BpnaVE4?fpOIa?LsaztfF(@@xHDnI@>wG3@8O9wxVttICy_ zGZy0Pf5sGGHg9(r>eoYfNasMwXd_nUb1qFK*a1ysQ8dsd*hFu=x2P9)lMQ}?S>sDI zQy#czrO63zE;i*^>Bo~Xmaj#SoDQO>`!33CDr)iXo`#lcw{#R*?@!4EA3K)nv=wfd zt@0r>HeR_hnb)IyQ^+elNj3WoPibB%+cBJ6x7gA~QdNwgsyIi-;i1V>SV$P3&dqAEB0zkcYVmT_Ao{=O@+k|DE{ zBU$ssYV5A&;dpz;Ns$jdac7cb*J?LH+j*gNlQmx#n#3O6H(jLAfl1?i!7UWgx1-iO zrvHgvx{_hIVUOXHp`@{=aT%Xo6MWXN)?zehDa{%{u6wPi_yf+<`ZqtSx}R?qr`;yExz%rf85D= zpoZ2_p6;;?-qI`&@(hS7>-f5c%Hg{hMsh?MsS8e-ozgjOl&@qG=R`44U+zQQu!sNp z2b4^q@ax-Bm#pWcx`EFnnna8?{$6AqZ$>}mL}8nUJjx;57Kvy>Qy~VIC1G$l)3lvT zPu?i963QH{5zoM6PQzpL(#hz+3NS6|!yIXmYAb&B8&p*<=oeJ<2nDFLYT<~Ozzy;j zl|&*tzz1}e$+PSr#ucAl{1Y|4zWBdn9*Q4TMhiGiYshkf_@v_Z z7q3(V|V6U;83KG=)fPy9-ZAT`+%|Om8_c~QDw3=kkxvU zbbJ*WFrQmz9Dt-=h2Q;ywdHljg{lyjui+5a;NF#|-}ol_{1AMuzcMam?YJR#rt^WH zHb2yZAY?)uKR$dDDj;TuZ^&dme&}wLVaG#_jP0js8D|~J);fYNe9vt0f%Y+s4E<;` z#w_SeM*)c#?f&e>j|N zW;)-QBX`)(aT5G#11Z;vN2(oZqZK@1e)#&xkt4^!JUZsxiSINX@~{g+S&4pbPv->% zZk-QPZ7#o0!X0KC2M#%tsVaV3myEkeF^nSe-_IBC0g!(8WUQX8%E(%PIt|*AH{fx^x)!8(wZGxd@iY| z1;y8)E`Nhk|LkdkwM3)hhwU~r(kbP)7Q3X_iN$hr7UfLG+vt%~J?D@%cU{r^>~Z*D z+ejolV?TzoVUlanJL8>E+Zye=$axe+NXxg&*kj%tfIci$u*C^1BcRg*w1pb^2b^xv z3u?LAzDjlNKCmJ8^V}zKh7aRkE;`^@d-wFTTDfBw!RBhj|M9w{jo9ii|;@+OfB+=zO}g-eu$IyZ;8KV~ePho(G2g0+D)=)=yj zjMFwh$B$WV75cXmxXyyfT$=QEhGe{N^|6fX$Igt{OQjNd`L^^n`@$DT)0*1=V=RY@ z-HVLNn^qEQa0QN~J9%Z+eelMwves)IwaE*;0BzWAKdu%<``Y#E8~c7`qU$=_EpSNA zq?~y$$c;JwkURUEYUH7utYXI(t5>W}v8wj{tz~U+hO=R^?635dHOR1>YjSEhui~&+ zJ}o-Zc?eydsIlF7N45AL-a#bo;J!~c67!Z3@5%{qI& zmpXX5Hqk-Iwy1=L2V0Z5Zck@Dc-%7&ZqYW*HS6?tJ?eCV=a><=R3of3zNcw(^x1d2JZ_ zwYU%y@jG|sRlu|81d)CTUHP>Wl2$lj_RsthxHNH+;Jr>%o1|5&C3{&ERdqI`rX4i* zE*ev#NwaOHVRf1{XjlBH=ZqcSm@)QRRaQq~b|OJA7%yVA9X8IyExOkV?hE{>)3t^k z+0W>!zd)aA7Hvf{eY|?yRdg#!+4HRcJ6dI2OV9Qe7~-{Nkw@*(Jvv<@bKccfKF*m8 zVSEO?*)Zp9yQ0i4I-=>~qkiG0eQ*t*N zXN0k?1$~Kq^gV*tnCD!;{_uh#v}2w!i`K%6SV;!%QB*<;GNP+V0zIa8oqS|9ysw8+ zu4v8htG@U#iL^diM5V(=>`Cz*F4RMK%ooy@I0NNa?9eWIC+9(lcF>dj+A3nay}8~v z_#)opQ)Js-GE2P6bvb>8KMw9YxZh5?r+E89{O$H|#s}5+hhp7wDEshnzHvI)??#8O zA$k20diCSOpW;!)+M$`1%uDv6nx@uw;8~S~cs4%L{>Y8?{(a3lVm-O0lSucqF}B^A zH!`8qznCq`!mL}<$#}~8@+aqFl{Xe#4oQtubjQM@);Omy8)j4+>U=AjVIqB+_4cDJ z3U#Pzf2>wUfG5eoeE?zDLPEMSDV1JM2_NY!jMYW{u(CWo>jJW2*BgyT+f{QLT;Uuw z-!J=hdaS?Ds=dH&HvR2s^I6fKaj7eia(l!+C<}6aLO*5{JGWRPbRWrS^M1EtR~74J z5AX+30sYW}kK5({SvnW@6uYTdb0PQUZ1&zrtA*C+g5!(~8)!g%SoC>2EL~dkXxxZr z$VdE*L-96=wJKTri!4OV+;2@>i%joYa+=p`5liK=y*cs^IP5cK&f50<`p>yxQ}Z9j z4L*qu!t?ld_3#;fw*S{7M_QXDDjdl4Kcb^fpV`^iolyco7{4{UjYzP^L(Mnx#}<@))9P}$Fo{=d_v zJdt)m3tWeu>irE!!}lbhi^CV{q7QGuWqPg1lp?_w*+6ZGYlW`hAa@9X2MbAEIR%#oZD_Bc4d zSkq#cY5{Gb$+r}{A^rF8e<8nf%nvoq5=G$zzi0!WpgCX2>6vr6_VjPjxoMBBu0?B_ zA6D3(<$u`?pa*}NA>OirtVn`)vNqe4F4aDgSI?Q%PPBepNItixHB$*E!@YzDTU9Gq zi&7XuDyTdvVV+*}bY54S{o*t#ztqoqA8C7}hJKfEWRrS%o}~Oux!0f=nrIJ4=k6gX zKl|`pL)%YJ|x*Q3ZE>P8H&`Gm#;{LXFD2d-( zoeoSJl7f$-AIBICzCjNhB$d<<{dg}N?hPx4nYd2h(>lr`72Oo=c)yW%JROUTq((}i z3hEn=Z$~-4X&>1YXoGC$Io8kanmsuCWxMBm2XEYOPo%TakPYE~-SG?t6@9Jf=%RCq zE-$)8EBU+VPN#lsHDYWjy0qv|%t92vr*^^`<33c6?}z$qUi5^be?lmx*pH_lo%yq~ z4_UKL&l;gccGofw*c0eYZMGJ!J1!*iYbbr?@pnC;m(N zd?ekZOPm0IlpU&mBxm{ZC7fvJ36j7OCfVw+grof_xuZW3c}JHl3djtq4AX zBQ6!gUx$x{RG%fzZn>8_YifYDHqQEBTJBf5-{t<6Tg1r~)s9?bJh<1MXfMOL=RylM zq6iO}1&+z9l-G#HU9Y@HXkJanrTjf_Ki>Q4Bpc$C`d<0>lWY3OZY*20gA<{r&8;9G zz+HKj9Ky$Bwl+Aq_9&9~jmRPN$C;RDM~5x=4y8$qHZdb~)easb{W%#Wumy%t8rpOr zT}}e{A*+ilvwXgNNcU$Sw9DNN?t8QUaDv`$VK!&4%Knzl{yenc8&0=*0FK=_ zyJGe+*}JngIuCRLiG=~U(iht`<4>~LJw8d1wgPyJ=R0bLc}a(CdFd?QOG%{3%k?mNv~j zaDvbB=8#O>OAd(w5-hwSH1T#V`ekQ0Ei?-hAyIH4N$uOr122&>n4$G=Hvg9-ZE_tB z(^y=Go%Cs|tK}Q~_QT8^J-$=5G z^X;g;3lF-Q8g6Ypa9j2O*UR?Anv^|Ft*x*+*hPA=l+(@1*}JfA(ev#Ne!hFnqILXN zFecP2TG`Gg;a;ogxvtZTo>;V)7Ljk%*qXgY3-~m9RQ8MFy~TXrOwAvSyR=fhKSz4D zjW%~&);~qo*&pW}Xi-o4^Jg0Y4j8d!TKhgJXN_PXdHAQ_7zZA-{%L>{c>uq5LXv5Z z(V!BZ!k?tJCK?eQz|Fe|_rFShj(K4-Y0;1K#_3znlaLNAriyu$^Nv7rml-2IqIdE< zO_V2&^mAQrt#*y82MPPDNwi#UWq%p_wMV)#HypW5n|UzR?K5~rZyyix^}P)Rm_`0_3p}uxd8$0w*2~GL-A{r$#`nqUc@a69-PV1T z=+U**xBo}VaRmL?g{ZwP_I)a8*1r-;_z<4st1zO;KO>h%m-IDepJ=GpaQC@G45 zX0ofvoHRl6m4*r|H_E+90`D%g-(|+W3vm!nPaLGJ}vGn z)no-*keX~|Z`o@}ll6jA_a$|Hw>C78tkK}Sm(c7Z$fJ+UdrNGS)$)9(?=QGS+w=CR z?OZacrD$WFX+O65=BVyQru$HM14vbm!atfq{@@EU*kZKockY`=-2De5JRNuRA~J{9 zqWS;ljP)T#x#=|C7r_e`;kvIPrTPnrl;XJaHAozE(;xna$1=dk|2RqS(Ik_n>M7s4 zHsMY?bOKNE1p8>!G8?our*_1f?5S7vwm!Vq==!AVMP`gz83R2SW~O-CZYwiMy)S|# zEOwt`KK=-GIm?JVpUd3LFQEG)P-o8>cluhr#jZvj#NPxpdOpOztX*Dyl8f?LW?kuWKZ@$kkHs=%ntuqv`Q;+At>>c0&f`|ce+?_nvzUsc+8SnFg5%vZ)q5p1Um?AH z9-pe>D-Fv&!46oZGpn$jMl$s@qxmtud4QzM4v6+4!AD`id%;UN^ zW2WnKG|e2(DN@DOvKlDQXu<@PkL zJgH2E@O{v}TYb32bu-fw{oLNwDM`MCMAW@vxKFr<>v`r;@!anVk0{@vT5go@gro&M zN^1_6x5#viv_^SD`bO!28TT%CaaXFaQ{Kf1okV9f;w{rY1Nt#5d#*PCe zGV|fcA%_&Fmgm_|tyJof)zw2af>rw;|PhzWS}C zmdohLrMXvC_m^t}HBgqP8&N9RtEZgM)r7vpJC#u)RhZK<>+!!J#adq7ADi}j&XekF zsXa=J$7p4xj1FbkPLkr2Qwupo%KsB)i=6(gB=%^h|FCU`X>Ha{*1LXJ27fpm_;*ql z8>2baImL+h0G*UEjK_Or}2H%VzkPZ+LdUZ;aUT#dfqKGZc>y9i0nCs58$ zXg>qoZ`F_by84lOe@?G@0(a&aZQ!BQ&ISnG7hc)NI=mm>2ZZXarQPlSPWC&!eIqVc zcfGTxt2_7Tp*JztpxUk^KXxU~M-Q`HXYH^v_Zzw2LTcy+wqD}AF~!zPnBLy+=fA(0 z`}@`%{_oHdA7p=+Pn>MnUz;2#rT3*YhRf4(@`zmik4(~iu1AF*Bp)wn{e#RCgU}I! zwA64JAk2W&Zw9js65?^WeUSYL*CR@7pi+vmiJtHjzgJQ$Z_<#6@Q?a`P58m=FA6hU zh_{3uE`3i4c@Ou2Yy+kCUS$^P=`Y>~({o?%-{tG~`9kFH9_|my-^1MR_vYO~M*i;g z-&fh*ue1i}v5zT}e%_2LX1_k({U5g|{oA?SlghgnpIbb2=igJw_Hgyl%kNS*H!JO% zg^tqhJJrPPX^e=`A*B9#^6h4nxYj7qBaIU`tHa)EG~&62Te~!dbW^L> zap~my5jvi)Wg_%#>b;A!v{&cdTy3?4E4 zU!S>z269tUe-{efh-_0`zIDkj)HmnUCf`-b>|74LQO3IKG*1;gS8|m>p_L%r*m?ia*oK9{rCU(;o*vT4;2(c&(dAYc)p-Ses5A2UGw)8Jd$}|R&{5H z9GCNc(ec@kjEq+bu0&_e&(F-5mAN$Qz&oh$@+P;NA^Ki!YU`oXn5Z%w&|o7pR>LX8+Cj!nL)9o*AnPHs;@GW%vZ3$(5t3LPR>6y7#yRMSnus??)AGvlG`Ar&b=9eR}p^SrhGecf@`t z?@&8Wc-TDmqh#uBJYw^Vn8oAChhGbYtZq#EcB(&tqjH;;N+t9;ZGd{$D{zChF z0Y{~$UC5hQG1tz@BZIRG!nO|`ezn%Uv!I1h^GL=J6!4FBbvgi}%5qA;S$LM)^M@9U z$ha@_41C$|t$kiL2Uo~=09rkjWY9L}BHf3g?wC1N&3%rJ{kfo5#(83CO6F&E=FB7~ zx29lB#z<#NbkIIBi!{u9GGn_p2W1pB*M5Npx(_|r(U?6dtgg-&Sn!y& z#H|HRxU}O%Cw#xQnY}ZH=y9X0ftte@u0-elB%Xcd*8bL${m{xq3dUHIbhhqnLME>Z z>GzUc8=|4h-k;+;jYIji)w1u-n~+ya zF1HlCXP4k}GW%roDYz~FJ<=St?2NP`zevUlnJw}BenD}}FQ}3KIGpyEyp!qLok6;x zcjgajWHSAtopvT4PJin$x;F#!?S1lh=_r(9O1?yrTWOc^uO2!TrM}7zIFF$!zC)qRG!oa!_y<0} z7LT?s1a=iF=NF^@KR7nEVG_URmo*Z;OeS_AS@P>?SZ^d%I$ADc6*vyk^M!TDGg9}l z5psvzY_uN9v7WdHudf4+Ur*PEMuzQBz8d=f)2N=15sKATOX&FJ{%fPS>O#r7kk5S% zYVy7noqgb?|KE%()KyiI#bYuLXj^9#*~s^L6nzCNwH`2|7m`kM`OKeCr%%XFH*Kt` zR&oq}b3OCP-Dd9>nPI4=Hr8ent!0{NEB%c4C9O^}GQPp3D5>^lKr)IzFg~Fjb+sA0 z7fniIOU6;^*KUU>gP??Jka@|~j&jt(xkj_k(Rp>yof-N@y^Jr-8aG-g{iZ#K?*5nB zKy^D=++ek}76#hL{Ia~DjefY;n>{jKr_VE1iT;$|4u9-ev(jJgH))wC796iVKSxvH zW76oKT3;=&idpOD4B93}e%%Hq6?(ySd^>esz8LZfN;Sa&!k|sgCpI|FF*8rKi@C z`_IfYr<(Jd$yZfhnq*B~M+!m){zhf6j7IP%ZFn|0j8<@~XYnLUTg&zl=dCceV+($w zX}J<#y0P3HA{D(+spinlZEl>uB>!ghlAGTOO%S}Rl18{Fvt07i%j9;LzIc=t@*3{{ zaMV>!#@n>0PW1Iwkj_($*!wbK2f(4y_9^_Qv+pjYZ}fovIze3=m+`d~bvv49w<*oL z3i+MTRGQiy_sG* z#L9fGmNHtac~UN4ryH`$UezaRvu(8CvU>IBzWS7J^<>^xW~-FqbbF5V<8rlf8LbU$ zgbF^|9VJ@puu)ql0hwR;h+U!<*^h#QG9r}%R z*jmZ`Q%2MU#-Kbn+7`9gh5H7z^j`iLZ3+pL^5!%d8@T?6==S!velCSqMwpTL;ukf{J z^83=RZ$K{zJ&B*W67RR05Ko!oZc>+R?SWNYN#^>_w_4L`^Th(K_--8IE0kehCHz&= zZoI^7(OMl=v1>*NC}~ymO=o?%8Cqb5Qm;)HybN8~J7CYhOWmnhJe@)8gC6?0)oNrqJYJ95(zet#lOt$p_MI4Ws) z-@tW-*0OA036;@4wyE=_QW=tAYmN7Z@{gv6Qq8Y?PJ56ADTfL{I+3x8}0SzOV#+~5>(~)1;5&r zQ=*=%I_*9@GCH<)22JxDXR zC=O|kr$aOmGH9>) zoaI4uTYo6cn<%n{sIH~VSTpm}LfwegZ4Za|A6)ABjJKhnpE9H1Hcz0dZ-6LV1$Ahd z_F0Kto&WN^AB>1|(OPq$-gE4%u)*k;b!@~6NUYl9Ns1Wz0}J!TF))e zt+$|`v&H%~^N}wOMh)DH%J1ymo0ZW(*KqD%z)LqKEOmpu+V@z=>_&y{q`k6}`&!?f zBZgU0`4y^ou2%4+989G9Jx*Fbk;?f}xsd-XIebW7}OIrIBav<`c;lDtU=PHTWiH8t%Ficw+XoYi!RCTplyg+(tYp+*m zKmD|cSJG-bF#Zv8I9ysjkeY9BA?CsE7bWEWN9(;nrFY|{9HHG?&RXUwSVhOQpImz< zeOyB8=YnKv+s&ysp2tt>ce*&=l!oz2aDrYFi;S$%@%kEH8Vp_`3$^)bMOG-+h?f3lD_$`T8;L#O74GC ziib0fk-luXD6JNcvJx-h3z70;*t67amb?{XO1dHy* zJ=mL%;F}Kgtw-Qc&pQ3!CGsxM`yXr_KTycNDaKCr^tse(|I+k+cR9P$7w<|T2H7qB zUa7n_@paqVjVf?~Xn&nk{(D*D$4+M<8+tKWf!1=0F`YJE`Z( zl<`IWt0mvc_+)3gDrkKbA-WBe_SNe1e`3Cay@z)%@r}0LjXbyT--E(@s0>C( z#Yo)ssZzgOX???WqA)KAd5@OT#aAPJF^1Gn_0?4BYG~!Pwb%1f8ZXeY>S~u2r9FBG z(pmqEF=U(GwAyI1%C+9;vVmD*?3xF^nqkEMOplJSEI4JWlkWnU)`!NI;9Jf&UjAVW zI8m=URqHRV?`7*p2Qs3+ovg&l>k(DhV(d6q8P;G*DvRJg=BBauFWkGGQm{2~I5+tE zYGe5~#*&Y5Z%3KOVr-r0KAlVOe3uEm*7c>a@FPBB`HdEGig9j=d3>aK|2d=fV6*Mh zM(9_Cm@dAh(!a&Gb{a2t``%yT2?^Q4u{6s#m}Nx!PmXpNWwuJ!cSfAWW`h}GcoVu6 zoT^6?h46?up)cy?CbM_&f4Z9AZ$*t=Wv05o%vBXdQXV~0$(+&DY!O_hekq1G#PR~V z23|)+ML%3q7XaC3^c>MCzqco_vOmvTl44F>|GUew zma4I8eXdpz^Vzp)z8j^E2cn{Gkk2?5sGd9pR@>RU{aB*wVn@e$>TqtD9A!|so1E5Ub++N}>!tAgGM#H$4K^DOyms)mBUccbqHhjO44 z#hz+Gxz+NGqA2toDDREd7@J*tAPku({o}<{Nq(D1^%bb89!en2PP&3yRk3H+NO(?byTvkMyMp$nHhnme`ZzpNm@I7z&_WP7g?Wt z$v(qsYLfNK7`W6Z*GTKASPu@i_Umsgc8ir|H!IynFwYiNV%@CZ`&l>MXSMfW+S{Xp z^=3n>=4w{i=U8pmvJS0n)!fM1xs{b$7szQ}VIPEhJpl=O(U(TZ@%vIR*04yK!R?^V`>t%PdeOMyg{@qdc-cWJBX9N55a zI7Ke~;BwM(AwI|z>Fwa2UL!T}h4Ww~Cs-Hf@q}B;B+SF0v59wh5x}j}?8EG$8m2cEL2(SnJP(hzz4! zad*;4=;FV3{$02+!MFV=$*e7eQZ5m84a{#Hlbddcw-lhlmi8$s$ zBNj>bV()wgrDVMa>rhrTx5Hv+@^%>M`8eNR4K7J8s>_E9co}$LxipVshI)Qd ztWWvQ%VY$H;f{}y)4;IbgGf!1rpd6GD36&^_o>v5lfDm>`ZU)UV)_c}u+igExNB3jP%K5L7d4JDJ^3=l|4T z4ovZQ`%0bxH9QTTR8AYLstul_b;h19O_DzU^ zb&;{)YOcNYsDb+WNF&}>!>Yv8;U(@Wc z-v7`1elSWdb8Tc_58VlkkX+oSQ;oG1m~uwba>m?KjK#&x7Deplx*gx(2XC(t`%mm! zytz|6n^W9d%|U;gJ@(t{G7Il{yRWVjViBZhIizNuxn?;OX&qi?u373NGvi5OFY7Dk z8tH4MvAvm*Jm`Q+&3pCCE2o+VPBFt}8QBk$6*-X5p8UiUiZq_@-D+@xlh{j%@mMoP zac`V%E~zF5=kO`RrAU^>~Hb+Q;rdnZn1C;>q`q||rgLatL*OPRpRmMgmz%Hap~ z^PO4@zTPS!whDUyH!C=rCrfD=-^*nG4<2_2s+G-t3b&F%IOxH($JhV!-WJ!-QuI@b zeWuni6)qPoYP$9?(f_;J$2(j<@jNGSDZiJRRl@(ECYHG`P=8CAZ@I?I`71ev$%bwozIfPugtYyK`lQ`$myGA z)@RI$kKrlaj%U*olG<7C>1*bBR3CW^4*HCl@m<*BaDC$iT)db+V>Z6rY~0)xeJrG5 z+a@%)XX0<%A*TDpbUmNT%yeg)OHb9mk2O0UW3G#FCuDi^(+qNyS@0|&8sgZsH~Y2I z^CQmc`fOR~`!VL#K)X*6M`a^ORXzF~==iy8HN{8N!w4R>oD*H&4LN zvH2xl>tX)~eYtn{hzewKSk zGrWgiG1<)U3D+4${H4CI8DbuLSZ3gQ9Lg^NNB=jad7Y8{2l4-A*59o(_X$%3bx=f} zk3jSzxBHCpfBNnoVK+(P5-I#T#XULgg!PFzVu7@;5M%5b8FR$ml;1s`{$UP^qd1Pk zsYdA2%>ZSUdwn%kLmf3R);CsTjg8ig)kyFyny8T)LgjepKkxo6=3FT$zyT|cJ5lcc zxTV?n7{zc=f`-`VoBv5=gx)ScJJKw-N#1^E``gVPLi$@ z)O2ZSJBd%sG#9vHCTfF@YHe=lh_1U2Xk7|FJHW2`p=Od%Q1ZK2!z2g1X#Sl8j zpGn1=2Wt{p( zE+Rckq-eDiY;)}wdasmi_57o7i_+K=eE4a;IMx45E(?vfq5TkF_`w%qCi_hp#C#Y! z5z9O;@U2BgudmW9yO4dkGFk4873{yW{jNMVi)XF-ao~dkZ??ZYZ}Ik8W|8m4+^|eO*Gb1(+?ij6+b4X6c_er-|0?l4Y3BJs ztntOg%3+mME?4&RygyTV7rVZej#bhT`o6Ie{L5RR1@WI$?UBcSLA2*^{oA^A4Ez zg9o$U`g6CO?(p^PY9`9#Z+SWBy+dZYn7#9*CQo^7NpJrwr9XO#73?3%ZG-3UrTja& zU8df@@;@idU<=uzO@62Beo|LUQteLCY9|X7ayqk>%hFVn-*}qut(oqhYTwgSD~kG^ z8zwzE&Pr<*MtEJ9#%@g-BHNIL}g4X%jeXVpXcP&u2UO!0oCFJ!d7 z@a3;l+7^jni99S;+H1W3lQb9hvTwz_MEFI*F5(m6R{Y-=ew4TG_%9MV+WktU53#ln0cmRZvMnQz73u%C)C=%mQULSf=d(a)l% z?Q(7R_71s;e!NjFE%(19rEYo3$7ZShU4IH(V;%qKkMYL8dPHI03SF7J)GCW;=|vNj zc??;j`?q;V!5z?vz_Z+nNKaqQf5ybM<`CWgpS^;%tB7TpDVOw`oOvFgVEiY-0tUk zFO=+I*jGRQce6*x-dqbM`6Kk*iSrlQ0{6j%?tqr{6nmV^5!~?Bz7{;y;H+LQO>NXe zNA=K-TcO1Kt!i+9ckY6h_4eIs#S&Tp~~S6hMv-y zLPiM$@9+4;wG4ak?;@@W{DXH_h1&)0b@3q1W2@)C8E#B1{I2r&BsFnN&h=CseOK*-Y3ljrqmZ*cDu%M*c73-he%Jdu{0dqV(m4 z-frqUO~qZ;{Tx2k<>fpk{%guvdH;9m9ebUnYe zIEt-=Zz0BuQar7NXvd|qr_SE$jK>xBPQrEaX6wY^yj)Bjgt~%jb9}Ljl}9b6fplM@ z#4eMEE473F?`w`vMoM@e3+oWisePkr$=_0*DkE3sb8 zCF-*+4pS5F)Kx-_#dD!|gDV%MAEg$$iS^m5Gj)9-xMA^JTc}7~bMZA1vbn3ils5K- z7AgOi`%ZUUw)XOPtsHk{znaU{p02}(>`Ftn2hQZ}_^o|#6N57x_B(NI?@Qdu;OX6m z)A+F6HXg?l?$7iQuBX!ODC{+2xmjFy;O5=Q^)~h}p@rL-TX)y>YNs3D>wPPD$u}j} z>%4mv>Flw$qYYutj~g=hj)xwMjL6jkvB=4u!3!6aUWquk?1b!e|?@>&$h++``=C zzjI1qq$#wFuhee3iN8I2wBW*)8|5CH?5m{nD!y&Bm*8-=P(uF~*RiJfCPZsOWya@u8NtstJ9TQK~Z6 zLP>%cYf5rEmFwx=3LV7Ir##wNbgcXFMzNE*A0zJL{1b{Q|MpNmylV*x-2(#y}4%Llk2-o$|P0!NCfNui*Zjg_0@{ zxeJu@WTpao@+5vGU4ea`dsF`*%N2B3Xfn1^i(Q1eiV14?veX_fOEgz&Eh}m~Bmq0IU8+_uP46}q_DWP; zS0RGNjMEja6DICA3)xM(iv0m@@Z5t>H|@5A|7%j4?BtD(;=N9&>(lr9dNbPdRjy9b zaJe|5Ke^@pwb5#Bg;mhE zRyj+pKMU7_Us`E>ZJqR$|3F(}efu#JtFJlMRI_0(3*a-Kd7cgrde8F+c+p54$+w|3 z;~=hM{f~nbzXi7${eN<)!|^VM(RUeY2aOl;NJGEjS(wtxv<-)Hc@nzygliC-b^v{c zz_R*teGInS4@wo1w!vAv8>ZFAbt?qxHuu{+--^50i|NhW#O)T`%3I*NJz!iN+^>Uz zb%lmqnXcBJTEW>aNw{Al_J%OP;Mp{Wy2pcVLiN?CUD!3X$~GvHTZ5N z|5ahS<)F27;d$r7frH0XNx1m0n8JiSaa`qboXWu<&q$bGnIu_V0)}`D%roS{kHN`0 zhU;puxMKxVy+m2R<3d?`~-2uY7(M%Rcz(cK(51Zilq~=6N&J_1DBp4gTCFNN`Bn zZ%MmTY!uHLAvX%K#e3`c2M)c-^;1G?w|EMDg>C7IxVO00dv6^RA%FGdZNlyrOYC>C zO^oZ{%zWu79gNbf5DE4i#qY5!54zL%>gm(aS~p_DhnivO15{c2^m z8a<%Cvea&l+Ae{&aE$s7Dfr`{!zVMRYBS|=4a&lg%Q5AdDyd!7*2Ze$b6%)5*3n+; zdu{^V4xVAO*819R@ETic(HC=Vi6_w~h z(Bhl8-k9*^>k=>jW~lKEde_zP;~SXH2}8a?AL}mEm3mwUE}>0!Ez?m<9g{7>c7tJe zAnSe&TSz2ak>0=3QwQNX2-!iZxCFfm-|do0q?2!V@TE41L)%eF zUa7=FR-!4}MQoSwEgUtPZVEhZz1%Bt{8FZtFd})6@BSEA**u<-HDcLP2B5>KGR2{m4B%Z&Co-pCyvjTWCTLz<})0iPjG*x>J`EHpX~oV{GiZT8OJu7)WC4h zqj87cb%mbkRDP4)#|!a3pO3jt5XU>7#_$Q>kTICTKXjNrW@aR*g2|)|zVP&!5VO7W zrBGj_`$TCBEu0yAqQ^#vkV2T6Bq(O-*I!8YO#i_*n&!>u-{aHs=fVcx>l5zN*{A!$ z6#4m(nT+E!jcvM^CQHdD{wMekJ*aXJyT;8KwR*LhWS>{#i&^ z#GJ6#%(35daOZNFgY5Y@irb7?F?#Ja*W~7Br~6^Hy;|9y?z`FkO7HLX#!j&Y4|1oL zxSj0}zFWn=3%Blo`6<5euMsdfobjdLsqHcI9mqdEaRZBK(b+h1CCrJ@uK&#s{h_@4 z(%eoodzQjK3qIQMX4qo>i>7%rWSLGkpPrK>Jy)t)BaOy31Ji zfY5iLZbA;MH#+DMwgKcu9zz*D>S;{q#7R>IszBW4L;Q*k2-#HXMyL0)6%x zdh8v1yz!{D&_?$I3X+Ce}s{T zuFyKJ!EgEwqP`aL9x@p}^Zgb5xhdIqp***E3JJX((DEILPA!V}b5ufx^I*Y+oS`D_ zIq2FD8WkFiRL{Y6i)o+y`AEV0H21ic}8xv`Z)3s0AzqAx~S$7->eHCiCD zL6f&ar^j0HR@8WCFx_T7TDTX>|ExamV!y{KwLhFS_SFeIIP@?ExSoKd#$Ki&Rr#b4 zPk0*4?GcFTgI2i@@C&)xSD>!Xd;dB9v2V|FY2_QzyAN}D1g0Eu4YUe=())w>4i#pI z)$$N;zr*ECvAvjfvl&Iw_9bRmdU_-6aq||xH>|GT^gl-U5pdiwWMap-LQXa`a7I|4 zzmwMHAHj7$wvPYAdj1Qm`|0lUtm3~&x*{RXIuGu;fad-(Xuw*st()nOgv52A1Is*x z#Pj-$jS$&2kk_C2Y-N7o5+}&*^mG97@K45G_kBtG;vdg@NjV>&EwKk)`w!dRe9w z1J~EC(7Rs@saTrqvE%IWl{;x^p%lA3v0*g>hrhLOY_mTLD2 zZw};IC>#Ea77!A;&+&gyP4?G@p4B2kc6VTEFR^dZL&C)FNRP2UqO}avKJM2B2BxZ4UwMLJ&%;S(FvUyE%oneC1aJsyK?xBQhc91b}oBY z>lmplCd>CM`JKjX8s5Oi!hhs>oDvF@XOics+S-TQ#tJ{4?$2b`q;#Leb+r14P}9UW zP3|VRCVFR-_upk3zGaKYf^7r%YC&NzmukA3Db*It@ZSyYm+Z*NIf?8Df>I|a{@E`QxA<@I}5ey z_VbC|?+zti%YS$-donUJ59-Tqi64@KCz790BI!AmfJvSJ**qF%85&Wg`JK);^rA`& zU7CMjl*gv$qu`%`VFq6)-aO8`rCcY$GE3sE1onBd@DZjYPR!B#GX3WW8BaFy<(KE% zMPQ=A!8w?5d}e|Bp^VTdD~4ke9H?AL&CdKM>6aC1neCJQ;2&iRnI~*k=KjP3ijaAH z;y>2_1yI?lH#gHk$2H zi<>=f;If(9F4qp%PUcU3fAaYwdzUc5w5y_~tHV6aU}+{~~OBH9`eK zyiu$heeHK&S*PW#We?5Im?OT`GeQ^iD}7~}(K2|3F&a$PpT;xMM@Q&^ue!gcXU0et z`X<45e8Qajl+o^Hqts1Ct?S^ux3Kl}-yQcd_Ak3G;kGwI$8LnGg&u4nZ##4{+nd?j z!~a4@BD5?ng9L`Q%Vn^-#zMw<#)0_-3fR`YHP`0uErknv1JpnrG(lsgfmo`GEB36c zi&hBTf`)80{fGWcEpY_;SRdwCg{hs;%D~?O(>y)lan;cs71=7lH_wKDR`z_Fd&rZX z1e*(lH6%j=Z!OL3SpFgF8giqhg)YPYSoRWZ$9gkx+ES3bkR>fG1-hNfh31SLBmuowUt#fbkQYfqC)N8 z;FDjtqZsrWCXN3RIiV2>>IHUJyqxCylWbh-Vy1%P!erWXm(s&-bEzZ$yLwofrW91i#|W@LjSdbm5W2j#_yzZjZu=QF`?td3W89-dMiF2T{Q$5zPW zZOF9|?o!kL;~|D$CHCOS2Di4l_fFwo8MpTA#61cfzv_5smBdt)``Nx0+}zkRvvy_$ zVS?8iI(W6kah@*)kGFhsi+gQvo`d&TnNKyg;7C{U&KbksF+^G|d@-e!HB)~D;V}uUgdXdZ{8Cgt>yP=yLqwZlP ze=uD!vN{}r@4<|m%+UEQYW&SK5*MVp`G@^K{xLGgJNw!9xDKnun9u$(g6=gM#;E&` z@pZc^q!?lk(+$SG!dZE-kudc6zBl83Z=73XoC^)=Lb@{c1CDkx!R#NqrB2YcCK&C4 ziWsKl4b$pEqko9D8B|?p(#7?t(e6&I`JNn80~`7brbj4@9_rxAo^7B0=gx> z!(~RTkQZs~9@_brCZ0g>lculau=?O^d#LieAr zi^UVL{r>K;HXXorpS?Bi^VF9NcQ0$%d#rBn@N`E)_WQyH`@jl&Box0p-0phzK=V5@ zS0#M0uT}Cb+yj9OzwTDgJ)wAac)ra#x@UUpHf!geR@R{z6>;{ps=m{g`})rReD$6r z`}&|X^|MlrmGsl>gZ#%%8IkT-IY(}W;#&-|o_^js`gys0nd{43hy34tq%d#F`784O zdMbrs!j1RN2+zZ;xMS5F611Z|kHHlQdD>yhZ)oC>1jl3ypIDcVRazs3d;$ODMehye zUU+|l+nb)_deyfF{r~1=_P5e=_{EOSgQVpdX&B;q&ilbhD)Q zNsopGOrg~3I-!?H<$BjI@QSs`w$44||GxKT$PUN#Yl=(nn6wf}P|MGr3#g}%9cW-(-(zKm>$PMpw6-o~8 zll#a=|L{|`kP8o8qp8M{$q=Vc*gs~U$rjS$fjUiNrm}_G z4EOoooaO&Z-w#R7uhm{?1BC=@=#Ye5XGmCwB&sTjx%P&Sqp*2h3xwT?qj&d$$Rhl zpU6zrd*l8g-$}_#S9;UL8@e$QmEgy|GfTKH#XMCSK1pTv0o$jpDAf=6PiCLS?-Nhc zJk9W(slM`|@g}ryB0aG?Pw;dn(QAE;{~N*$aevWsjBCNcjj`qFG`c@W{~$DPVuXLl z7~9XtdoOc?QTPV;8{wM0prM_N+1IeQ_umF;+7YfAXK@FYsxcGXl#3v(m%?HjMeAWBelF{-Gy2!}DzB zW47R8Oc8#Tr&&UTwoULQgLCl(-*IC3l>K92K1#fT8F&rBLkJGT6k*4D9>sqm{zYi$ zgnsLUWE+*-Vn@P}xDVsFhI?>kMsXYGe;h8%M7*8Y5&3P;qi|*>(4&15$Kow6Z*qA{ zsG-6SXOHJMy%Rg8judVTjnU!sMPK9|`-%-r_NE#y)cgr< z$3J-Oq34uQ5J&eMjo(&8+i|e07FLojQMA9(xEQA-PJe0trIWoB9!2Q>oS-eAs0E+q zezw+I)BQ{w-P5@RkMA7)pt9B;x};V3o`ZLBK5j-$F6ZF@hNgH(C!CE}5&N*kzPSzE z;|f0I#d=l)T!}`grwdU|!OLjN){0+K|Civ1wqh>x9NOW*J-tS+?C!b=l@)YTa2|qg zx<#+Pf$7I3^i>~5F@+xc;G~cDqLCn|qUVebK?eo>5M$RwT;uNXaj_P!7^j_VlJ^GTJRu3_cJshVh&$uG+)U# z^dmy{Ke!OVi-@^vtvM_75<(jx=J=3)3CiS0Z*KPg8`qfa*P2JydisgkU_K0LWwrS- z_!lwH?lQ0b>iIV_=yq$`8~4zAjUCAUF)JUU32=nTaXL^oZGa5)cn%so z3uS(Sdq~8WLXVf=8vBf$KsLT4di^*q8EnOw66pERl`ZXloHt9Ra5=&Tha@ySi=+ID z`7bU^#GcK)2wUu4R#H5{ONl)gj`p0xCsGtzHHV}vw1^IRJ|tI1o2ABi+KLeSCa1ZJx?y(-(={vibzm;UJ6z-Mgf299!bYg~FMA_z{jv^mHRRz71 z?~Q!Vp$T*#{pXAQh<8J`HOro|nY4(bcD8#rG?_xX>R+ioAg%G;U%a)!8=IBaM)tq> z2KDt1zum&-%g15&{jQ*^4#-u|Q$dC9O}_u}i8STAho)6%FaIkC2Ruc(4yW84Of+x4 zG)2C$)k#s3T#>J$>MG#OXe0UZT9Pd^d1Ezq3@j*9ZHG?sF?>SyE9Cu(TlM9W06U&* z=s<_;-wDEi++(M{6T}hz#|V3rFXcPSsi<#f`$ATF|8Qy_p(z~C!GDV{XLzHy z|D4oPLuWVihNESc5OcJ+Kp;c4!n>m0~f$O6Xh{O6>5pjOo&Z}EJ# zcd7|hj$~Wx*%Gpk@ft2sv$i;=6WoV()g?ZJtK5hh=YbK7md_xYgw(xa? z51oDP%AVF!5GwO0Iroy870Ax9YZ>zUZm z<#yOw;Ab~M2d?KEyvf@<-6~X^`Opt~@c(Iz^nxwi=#9`_=_6Dhwj25O6ZURsL+q{> zC|qcb1bP=S1R59GAw6+7Z}Oc7l1u1~JjDM_VQ+^~+%2xy-R{m5^8j%^2$733X$B@# z<54)rGtA=&?RXJB7pK-d!wgA?Z=fMBB}C*I*xX>(P&}>RiiNEZ%Zr^6hrmkWypLDB zH58um6f*>-_ew(f9%qX^dY<=2oPsjK7vJGuc)gH#VZq-DUg_)7HbNd=mcl_&6DNT@ zBz>_jc<@()ZyVgRhlP6#BJ-%6_2(M+Oz6?{Q+D?$mpEDF9wl%W`#s){lT7aA+gsSa z-1{q;fl4aEMj7{4PN5lezvsTf-tYYf-QygPJKf{-lbe0D4-7oCg(7TUA>!$O1p?d2_qxGdZ_=Kw)=t_axzcO3!H`;Q!!=N&XqjcyKTOamGos$(vj~ltbY; zC%16#DZgE~T(6{iD&L;&x4GZIt*6qwlf6*0>2{?S^5L;}dgi5t?wxAC zuuVieiG8>pO||<3+mm9CmiL(U_LSEBC^J~=AF6k}jAuCvYW)H;hAnuLh5K!W=2h&b z^)|$Mw0<(4dvHppK(nWD3m)uPJjxFsknbd3<%j-*R~ar5CicRL{VgZr#)Nr4ac;j( z{?plmS2Kh8C~-Gur{^!z|L4&1_|8<^-r(lO*=NBCj`)H`>!awkcW6q94~l^xCV|>PFC?`l`^f18T%_0e zy}*6AwlXZWnYaAEqGW~&S=cL|<2RIBa4X-^(n9Jc+T2@u^ULnBhuRSRe5hIE6}>xl zxr>vi278X#=UL%iOY>Q9sh;LPn0uUI6@B0pZMu*<@G|?$DO|Mf_{IyV9X~6*(UU?; zW|Wjg8;{h)?y14^D%77DBE7Ny+iOhBp@YTryiw*gbIefYDPIn`k&qR6#3&SFQgGEC zF)Q6`B)yvn{=@A?$pDhS`l6OcQe;?bMO3rZ@1*$l5Z2PF}H`%>MCh1D+xk}M69wP6DQYvce=b6a)>k=HL6x4*FLo2EfLg7FaxLdxDbctU z@o%amiugqCak@!p?*u&`nmnZwMIBt8;OxXs({VpJ(e|ZK%_pOT@ZtN-k57G zGyMhfRyV!hRH(Xq3UzZX@U74*Z{S`}3S!5_3#7EF+@8xGCw5ik7il@0y^0VKCPEhK z3pEtFLGp{TIaip@e0 z&`K+h_kym9^fmY2ETtvrym&7BZVTy%ovcFJK58iFx{!KoAwbP zXUI@=6h3x-3>q=C;zC!dot(wlozZWu;u?J-o?CH=G{rswmvRZMs;(*3p|##N^{ci* z#9HVI&q0%hc2iJ^ZKOL&BTBF>6De!KHBuU~8?lxOK4)ms)#Xzog@|@`k$S0>(i(dZ z)Z-Ugc7-R6Hul_DTaHslYszN@CR$STxLVpm$ZSO0Jx3hT=VEt);F(4Xt(IC`?CTXf za#itutU;rPoWsA6uNo~dOz5eFZza7fbmT(*qnx#K75CHi!|0JGSxv_pJ67be$5s*k z#jLSo?HtmqMXi@(kCWJ!B=+1o9tv<2{30`B7ZhMS3?TR~2NM1foUlMwVwbF~kcU4K z7PTElwSjFDL@M?RSp%o~3A**Y=hgm$Kd}PWVI?#y&f@)?nE};b1Z@i$-7oPergI7I z$*0h^*tzBt_YWcbasE(n5Jn|#!&n@L_hI?3L-^nJA6(oKLc9UT8|D95ZbNYk2SfS; z+j~N|!qW*}!DS4st>?Kt4e@`R9CG1KcDKU?`#}f$a_N=$yLa;Gm9WEma0Bj4oY_8X zw?Z7RcXh$Vjs5WAREEnSke4MLjCS|~jqwQ@Bzfv4kjlnT%sO}lp(_zG+g1HnWX`4^ z5nO?){;NPl%fd!0a6Jo(S_PU~!M!TiINu>OYGMz-(8{Vx8oVMzwpwzJy}n|vrgGfR z;2-xmo1zv|gNd^)D!^I;jg6f%t3Y4RfyJJ~9((+ShFDdh|6fgK0`F6`{$WGWpjil! zp-_=bxk40~hd2(IXXh9W&cGQG&T-6E0B&s!II&7N>QSOM zjXIRo1e2;JIr`JMo3!=s;~I^53iWsf`t%hoO2p5ot6qoLJ=CjDSxee$NlPs_y9Koy z!`*F5eKA6t^K|73cP6yrsf&S@NN%*7(RQXI@8e-)G<}Hofe+l9R3F}#mUKOlv4KyT7Ea1PS$b@a2R zNUb5ZiQ`uKrQi5gE|l*1_#e1$yoMU~x7 zYBQ;=aKk<4pM2fhX+tUBCi0c{?Ior!kFBA7`Rra*9=qK2&78A=)DHgT9ZlmF1?tlV)K|4SW(1w4fXJm(qYETryCj%qQ?BQ=FLn8)*flrV|ZOx|ifZ+9~9 z`ccw3l#U~aWI3KKjq+4_fD%Dd>RIH{KeXo@Pw&WQL`bK`Fw!HblNNL|GJ}>z3Vkbw zel-k2LhQ%AsjG!rXT~4x;U4BoNaWat{0FJijPoBLjyFxdGrtq@cv};LbNuuwfos?Q>JZQ0!P2HPNX{0aw4Oi?e`}1?3n?r00?{zBgdIratwDuSwllT1yIoZ4)-{eHzq+@?NY2WU6 zYNV6*DE2B!heqG zX|x#o&%|(TZH2WH&g9zmyJ=h}kF#>AH;H^TdM1Q>%A(agt`OTw=U@3BviWD3^y}Qv zmnVcaL~J%JBDH{a=Aa3qe?I;AOyet>5pw2{JC`~O$XiY63TDBl=*^BQ^Z2G5Rg`ck zuirpWSLP|UEqt9~wd!@puU2GDaWtO|Bf_(M_4~Mx{9OXps`QXJZUs3OMJ9Gxkc$<<2b?mAqR>u7k!%!#suG)2T-A!i%0O?G zfxfB))m0veM-@uSQ=%PwS=LZhp~I?>Qw|EPB6M3h=r=J3q~Me$#Fa_hyY{L{xRchD zI8+U54J8krsoi}It|g{{2n^NP*CHO%zR_fS(=gE9&+Ve8VCQimwFxSgZa zYm`Aez_A`QsQzV?Rk-Go>b)Imw>BlNbRvR+)Td}%B6ZcIJpP0DFDh^qYDm;I& zBMS5El`Q&3l<_EqRC-yIHbwI*4t1+sQd!VtA*OUvEZhs&Ybg^l|H{zHb)+xnOz{dX zp>65n-;r!77Zok-N@4}+Ef+$s5}!~xs**X;`;@Lp8;cS9L(owYHBzKOrH}8^<3HhB zP+v=ZA0@?67wAoNZVvE`9pL+Vo^MHQqg@>L@U{JmJSk-Linfq;9#s;p-ptzYEjn*H zlkQ-4cFvU2h`KxGZRAX6Sbb>j<|`KiVLNfrGqt4C!`DW()ojnu%G0#&eC!OYrr#Fk zSZN#EcE6gil&~tq)wo;3=(3hsSAD@H%+~4-#yMJQWi{u`=PL6kSww0TL7GOroYjmz zj!BMA%Nd_mF*>ehY?9h~jPz{A@wtq7a~K<^kj|xi68oGmYHCNA6Gm-kf{~OAXRqJR zB#z09p-GJJB37$U8GVdpl1`>HiE+CxrK6aahjCtbNdYsap(}~ZaUR>MEo4Rt!zYN53?`#^UTncF{)QO1|^rj6RNyk(*??I-`MJ(Z%R4`Fh}1ZT3JLMT4RpNmR##A*%TS28|!_&j_5DGH>m`8hzUXB zI1rAZl1rtEBMHjOGT=F;AcYyrejIzHaHHW^X2VOJOqdD>a~vGbe0Z9ZI8G)OHVV6=bROnN;nX*;(G&hmQBx7Ag& zw}_fcIZMgI3by4OSHkm^zq^Ro97-3!z11FXK4B4?oamMO3w^`Msn%vtD>psaX-hMY za~}_Nw0+P^T;vZgr6gMR&F3ub4i~~r))r3r$0GR2i#UHi*Iqzdj}uc~@;GhyG(}^2 z94VC^SLJW(w?%70-$4#FvUnqT>~qKyGccR?C)Qvl<;pwLhz%wvCzXdhfVZoqhmz)j zaE1B=&X5R9o#AbDfaBW^j*dJxd9wObYY=!r@}|TP+u;#@J-S2Z2tS2?^d;@8aqj>7DdhTJ z)jU%FNwjAv*9+{`ur3TbskE+s!GGn7=fks6gZpRBKFc%oX-Nm4;p`uwUo82Z&2#_Y zO!e8-TT(meHz?`vN&gU9EDY6q1vHOOLG0o`(9-hxs?B9w%1X@}HG+!ql%jTy6xg58 zO7auc4HCou5}xs8fhP0m7Y^}jI4@FV#W`DP!Zq~aqHNa@Gp!!bm3#~O#IZ(Ef;3`r zT9YQco*?ZVvBa+DThYSpI*xu3YWb*Fq*sB`d^~3P8P!FQuk6SopSd|RQ48iK zb#cV{>dDw6hh4dpTxzuvIx;VHVth(q%t~bB(w?msW7j}xtAR6uxk;qe(ac)OjDVw= zwKBqpH;$1n56N04V`dI>Q3fNZc0Re}DWkj+lB2kyk3KbRJv~W(`Ms zXM)9~lyt5or5t<>=PYK%(pTpS<}a97{k zA0i3X#$QSBp&%*#jQFSIo}^5R*w2v*pCtAfd+n1y!Jg|wENPEnLwg##u9JkHu=UdF z_WOV>@GCaCCrO{i3QYWgKd=Y;1$(gH$^VTrwbQ+%;Lqfrhas;II5BDUDQW62>J-7c zuRyRyyMVpfWmpeikKM68Jg>uUP1M+%uwWC-u_*Z^u`ez~>{=`XwOH1=t;9te=U93b z=U&Homvhe5!Q%M_>X)R>^=#LMy4p)$PP`=N7Z0_)mT0x^OH$8Jj`XeMRNxBYLRQ4u zPG6Ox%T^&(gLqZ8TezbdgxiT}u~&guMYhU>O4O`CZWZ!Mkv64A!8^EuWqN_Ez*WSq zy@l9qv{{MGctwJ~C~dVIdpE5e%dnNBtT@N(xG&Kz4L4AB^~HE;j@knjXJ46rswJgQ zuvo!Au@Tqv-Q@+f-Tpn;TmQ;iDp2rbfkFkp;_aLz^*L|%E8evh>!J{=WpIqx(Xju3 z)c>JdpdP||l&MvqZo>Pd4$8G~FetPKv`sN$xH#|pdd+5vC>B+mG@boCS5BhFv zpbl5k+t&rkP7DHl;!3?qovG=jE$R}!`9*x~EBFf4OOs-qz)^pAlPH_acrlT5X3z+d zz7o|TlQH8FMwKkalrij+Nvq4GcArSo{TY|kmKx1i)CWm?TSg<%1eK4s3DRjPaP4@N zsfiOV?bI9^swq-5^|`cRjuyaoGiKHde2IFDoYkOfl%~qnsLA+RgK<}EM)?($IaXw^ zpS4>!I&y2DT#B4)8I{!*ElnsM<^j0}R}#OBRMD_vSEo3JKcEVn%L_4w6%5*}(hT1+ z6RQ8J?*eVRw1fH@I^;Z*hyLonWAG59 zojiUR>UuZgynn=1KIIG%-z|w&QC>T?sW!C!drnc?|8a_7tDdjdyifR_2C?mrQR;s? zP0VY5Lo2TKKIb`Ty`>fAS?*t6ok;ZBY^imvrt}Y#e+_-{D{cMA)0B#lb}7u0{|y?n z5J%~-OL#l#1WT>DQoNRVRZVfJ=3=~05kaMZi-$Kal1|0Y3&fXn-c>FtMIL8gE$6k8 zuSYLa%i%$0VI}0v=#S3BdP#1_mPn3TAPUU+zd^QEzuB*R^Z_+BZE|FZH4A?(EwVdeE}iwQA9539NQ-C(5;~ z@6EsI9{%4$;c4}u#Q)ri5;X(*5liG*cI1hxxuPF{4$wU67pN7g{9P1NM~bMmc|UZM znjOt~tFG$rsbPyOly^Sr1RP5!z3q|@>n314)$zXz!eR7MTr&QtV=-^Ey(`TfU$Ndwn+s!B>&lh`GcUcy!w zE4U)$i6&Txy=bHb|0n*a$fJK@5ha?XmT~97eftwDsc!@3sP<7}l>UzG)bF5${)mm7 zK@WgGf+F`#u%bFm$(MvL$onGLR(%}oti%L8LpTjq-B~QLL@WF>V1fSk!ZB>@^jh^9 z_4WC9G~n2sz;f#d`TAZmoM3;H8XsWAbvWcl%)=Ap9U<={t||uJQLghbcXfn&70>iE z_Fm@*=edp^2fqcg?px}d;EsP{(_huM-0d&?_jAGO>*osv{`Wio@wW>R&9eZ{>>8f) zWnsITlux}hHen*amcZWYTI|2dguQP|KVjubsikl|mSE+1L%!t_l-z{=!Oi59A+G2dYweg7b> z=fc)VJ6jUOy-Xm#BfZltfwTyB-RQmIbtWJ&?LoR9HT0;~hpijmgtFQ}aDr1PNoLa< z+fcrrp?p=TL3%gt;#Z}AwtT*-$Kl&fLE5Lqy3(~tfm<7`-e>b|J`M+4?Gtf?wV2m3 zeo^4mE+pnRyaW#IT57GJ)>io9daYU;xZvyAFNZs8*>b{a&XlLVj-XFC<#qDYmy^Ge zHuWRBg4#>ST}x~0sI@$tEnj^hwH8ogIdzv3TS>4*?_(M9rPMUGjQd%@ec0YY@)n1Y zWjWmUHT;vMq(n$q#8t!!@Q(CQr{;m2d&3&8p(cc$@Rl-e$?sRr|3r`~JcX2CExGGS zt!FG#A3@kZHu$t{j;_HborEUM=0s3P!$_ zyd7}@=JQVUw6~b?a0YKC$`2+}I*oTWnJu3&Qk-Tbt!jM8p?9Pl!BL66Isl{K-siB7 zQtC03CozH!A|w-ran7)CRMs$-yll!uXBb7urk{->H;r@jf;*Jh7+M<1ro4C*t!bG! zj=Cd=52jCLlg_3j7isyda2(5>j^<9h*NN0M%%rw@OEW0bf38|gb17NG(PuN0SU&&! zvG9bHAc&fEvn8#e=+g{0A^F6HP39csoB-=`Qq%q%%Z%f3CP2r7cXT65m z$Tu5hN4tYWX zN-sM45|HMTDswMQ7=7!wmuBoqu2xrCg!F;BlO~kj>k8%8i;xg#zTV{8j+jTr+#-Ht z4~||V)-X1Nbbs3D6X?w$ltfDtDbXQNmReo)q~%CC4&x}DD3z*jcd5xV_Cu+c7FtN- zUi31q-HW{Te%z(k)M`c)t)A5F$CgA+BIR=B2UDU|m=c)blxd}8TsnI=&nAig=~W*l zbS2h_ApI@vDP`?Zh7E6=!I zP})?La636l%Wp&Kt$bbosJGHzZidFZ8G5q>CAUC1x;`yU4cDR->03&?Besnz6*=-Z zvA>a8MFW*tiax0P+Wsk*Q|niG(Q=<}U`?bY?NtOh)mMb|ket;bfs3j(gZ85Z*#E#6 zAezXz@LkB;b(Qud-^|xxCFvKXhrUk(ohhaHF5zI{%)bi7slLV=e3|-fdx@|06~5U6 z1bu_+CvYF%?jC5+=lIsefKZ!kEwaM3q_;3OZ(b%Tk9L#6j%V#{zW_%ndfrlBG3p? zV^eL-7EllE2+fH#AT)u(P-9UnYjLtgbf^tgQXdV;=-*f^O0^}cL-UA~Es}${y>|r- z%!*J!BIn-*AsKuKp|Ctf|3rp2F=auIRAQRqf(Srq>Y;>(JypGLTRp1 zmUKm05KUc8QIY(`=dMhuKDp69XH{tJ^4#;S-1D7KP1T{1Y6ZPawK2u%{s-y0)UL(< zyPcZ%606Tvo8trQ)z4Hfvrf>vyo*}V=ct;QA`1IVny}R;rXSjSp`{u@TQwvu!gyO~ zuXa3H^)%asXPiL3UOd&2ZO{7>KSMv!$>gc+=9^O|Y#8rT>T5W;`t}?`YA|niSa{=E z?8i_yi+7$&shCI3$LeY6VPQ1=Ny&N|y-YvT_N7Rl<**LYH^WrU$$~1I!e&nt+eyvf zeC9x<>w2cQ&)TC`1{&rGW<$LOtNS2k$!=n5LPaE!=v`V*)@m&4=}&#;{d`G!GuF$q zC^KTsh~#pR?Eqh+mi@}QK7yO_K5IsChEK3Y6v^gG_$fcIW)vexUy%B${2K>H6dtjM z^@pL&dz8r(hJWI(AmJLqpZ~iaeo7%`y@HG)>U9?*U!8AJh>I{f7KYh9K+;#_7OZA|EEsiOw!^aZ;%1 zEH#L^X9TsK*|alqP8}V}wYE&5&QRtlEjzXI)c2m87Nsyr@Jsc|7p-Qq$kQfSEdn{H zdg{|FUm7(t*~XF{OIo|%RCum~k)ouK8Uvqo418IUn#E%tM_PN~Z1y7XX49tVl;Y~n z;5Y@&?qs$J#IlgpTLcMe!8@ZamkTNIZ+0;$Mnv zm_=*Ki$wWVdX&dezVHOXq;P)|X=Ng3&Lns}+n1BTD1}n*XH^OmNnp}*pRN1Hc zhRR))R?Cz2r!P)@$9~IvulIBH8?B>MR_q0NscJQ!y5GeU3YL z>vh6NRgCKMyCfEr?lE~G;<_tORv@ru>*a$13lgqgLa2vz-w2p zghdM2wSsbCIkD2zt_p^d>qwh(zD)}n8v1@s0|>RE|1z6#x}Rmh;teTJ}^phf&M z)ZIdUw0i$1$DK&|o}+vxvOX=HmHF);XAe1BIf{_~H2GT0i~A}1rwH>(3tz`x`#+Sw ziVd`W@!q859rmvTsp5N-i=w{|nWH)H5`UAt_mGc&h@|mdWZ)lghKS*R2XVcRAhCQ4 zIp!hcm7jvbd?;8-pFxs&oV5N=k0SLvg6#7oIj7m5A$(6voLVvSecpiQI4OGG}|#oax} z-qysYR>rHRfiFoN=c$O-s>S$;@Pt0+=^WunokfnTCV?Id-$(j;fG74Q&t5e4k9Z5_ zzKY~m&4WXfzD{^E)Oa~afM27Il3?xhUklm{FY~ru;GI9u``*Rd+CquaVf%{xXb*iy zYx(u0*U-PTyWbr8>3X#IR_DO%+Un8Bb4g@$eV7qk zq>q-2;996Qr%of%4au!bohIbU0cg)S9<4ni;`IHYj{Xo~lSS@%fN(GKgH&*BQqC^- zg|xGabagG}8tq4wm1|L2kF>H)wPWiLv?ER2&LMJ$yMC#YoFBE5%GJ?zj4jB7UnSrNpdsSZ%10 zC7H< zazspBL0XM55u+}L3Q(I&&Aq7ie+?-yog)1u($l51cO~uq3e}|^f5d-ML+w)1wsQd+ zj)Jr!c9ndU^H3yWa;X6)PL_0z)RUTaQZHwrjnqUm|19!6J-C}I%9c8bO2C!=sRwX2 zNdC^SKLgeE1)I`zW$j|S9;MvePe{E_JR-M!2Jhx$cs56Kb7cRID@T8kL0z3wz>h*>GpTtUEo2k6OJ)Z)%NPd1kd;nMYT2zVItaX4K{;_c0wGzk)9DePXkQzivo>O=D6xx^eCzb@iDH&eJ z0Mb2z-JF)eU1>%8I&JT?q0{D0NtIT29f-Fju6MZBA-_G`lSDWyUE#=RtJjtit%@Hc z)&_1%B0QEB@UirXXJ}2EEh$klDJ<2FU5II!*p7SXKwN9-?v&{TuNSF)Jhy&4=PvMe5_v0nmm0vWzm%*DDMIG%nhVZU?P&e9g%U=>*JB57Zd@140 zXL6NP>T2sTjF?#4avZhpOy{auw62s#Y0n6b_K_6g%7T(9)8mzr#H4WbVU&1}y*aNh zZD|WSh#+@sIM?nt6PJrtc ztzKr(@-n{gseIEj;m6Hl6O&vSmi|^9N9K@L^0)vF-D<{w<%||9LpqK(%SdZuq$F__ zBa(bPaRKxirbJS?<~CXtkyu&kCR*M}t*3&FYXzg+6O3~D7hBB;w=URDDdjS659z%e zMIhGxSPMw)Xr3pvlQB_F`DXUdbJhl=TPx9T*PdE5W4Y-&iS1;p-N9J9lUmwxXPwSg~)PE_|kORMs+K$LtSMMREh4o(UK%S#0(;^~!g?;3B2VzhEi#oe#OVs|=M?ZX+)E(wgYUdhtdT*LGAHR_n#gYzF^ zRMqdO{)M}7{f_(#{h}s@JAIfuZPK+r?@#`4j(x*Eg|uUs7IT9bI~>j8JekQ;QJSsX z+t^T^?kJvW4$n67=k@nDig%(Gg>&%~-jSL~<3r6{&h^d7$yY``o@-=sOyvre3?=4x zIhwupcu54uZY}k+&2z8kh3G}u@pU9=>w4x$T5vAVE-#(Y$Z^dvN?ScGzO%^JzC4py z7W?R#P&;t#xwSTrw8JRw+CJteHila2hS-Xw6Nzbes5D_B49Um}k9AL0z<4C8Zm6c?LZj}yI!**`~wc~h)%HCWW$ z8WQ&X$r-@g7yDu$!FQ^!#_l``^}56~?@N$=v5uY_d(v(<+IQ|9Lhg`op0vzh&hJh; z+Vm@5Q*XvCdfe?3a`kJW)|l7o!c}^3cYYJ<**KHy4`Tpr_an#q?8*Nyp3Hs}{W60b zB|?64&X>+-zHx)^OT7xEJ{dd>XGLdipSrW}MD9F=QDO*bDY#MOr1HM(ZN4k(`7X7` zbnX#RU_5p7m*ET)wJIi&lf$^9&yR>8FpjZBOVd0?o=nE1@wAmsm_qJ!_M%DX{aZX4 zwM-npoW(p}%T2X|oWo`?Dyo^Gc8c?wemIsk9kHcQgVOK++u^w{Oq;-?4DED%djo`HCQLt+Tw? z$whpMGA<=o>J`Z6ZOp1)DTFqJ$|PFCl~Dgx-PeS`#gLEDgS4DXeS7wY7orx07`g-C zMs|g((jU>+|$DDPa;1Pf60Sz==sCCUUHjmyyJ@eVh=EYW3&A*O>;tax$9!Q$x^7 zXAXSKC6s89qg|#}nvcWLTte&#YH6Ffl)V_4+GFaAaRKpVndx;<9;O`6cPagldeNug zJJfy?KHTesx5)p1@Yd*mHvdq31TNLrtAk^lJ?oV!EBU&n648gfDp z!r^?v2a;U-g3fHdh|N_x!y!)5+0)$Yqd5V?xr4WwTj6l zPix}n#Was^M(j-Gr#Y0S1qrK|%=&wq!hQ^Aq)YSfNy}-(mHMXAqOwt?qZyPw%6>v3$;pKKn!)w_s>QEXE|En& zB@~Sr?V+ld4y;1C`aWi+6(!Q zMW~j0oK2adm8Ge~MiWw~HHsW1Gue!RlX!AUpM8q{ui;$PT3RiRA}5oxd;->1&a9kV zuJklo%nR=(o9j=e?u2mf(f_el;$wL}cfq0Rho4d04) zKAIBc*y-V(lZX!^mcgAxxxe_?O3sF}4Py*XXS_}&Cu+TEk)gyY83~s56k1OVf#*Gt zk-aZ*Z93X=cGP6+%BbI+GNoMV|0(C{4=-E9=|LRTp;I2Ng-H@Y&xM0S%l&CNnYwD% zMR~fCIr;G;$umdI1!oTtz18XSsSo9!+c$kr!^z2}hCN~^&mn3oWK;6DS4=ep#FEvs z^sMl9#5JGHJJ5Pe-GJyHdOCSCDc3Kea*2Ys;#i>*uEkxjvgS*=yCOK8fRvF(sb%<_zAux*1~1i;kf0aJ^rO zU*3nlpafXCaeuzr-o%xqYHzGOC*r|dnoh2iiP{|MwwM}2JAUzwnvR(Ev`5IB7>?t( zVjlITP}4ApHlmktM-Y+8mCve8lFmQK;7rFwbzdgahFU7>pg0nWX)Y$jBh<~NZWbYp zoHU-;IJR6$93dU?^g5jzp58Q$Vh`JQ9K9Un9LIb%j@Ewjj(C3Ke&u4aE8QB+x0cDb zG=gtX#9_bB=p#YTh`ocfz6-LXE>L~q3b%yTQ}?%hkUfbh+KjCY(jz5R9gy)TD-z!{ z;-0odqSJ&jwFc@yJJzD+ozRkccz6I=kGcp^E@VtRirUnuOHe~0qWP+`P>1uydlf0a zF7b#fs};JKtKwNmqejg9`cSQHXh*r2R&w{VX=nAf#Z?pHjW|bbS+y9N(PA4))r`g{wb7EX{mK5d0J`dkyeDMTcDz=BXPPlP}>#As|-!9cMUOy z^$l4PO5Kz)DLr$Pgd*2wKiYXlpR%RUwN(RG$(A&G*>H|H!lGkEkGHpxAN9Fwk#jqn zwqW9@O4&zrth+cvZJ&S8PCfSZ`A>SL^$Ggl8u0YhT5HbHC)k`^ZCRBail^Lxry)EYIbs*KAR5R%Q#>BLXRjW-@=T013)1nx*dPq<=MBNW#YBTAz zpcCotoF~$5CypKIb4`(via#xufchdG!>Xbs{m;IaKn-mhJ5!?*y;b{0?fyi>?oCQB z#$pY0B<|`&i${BJXRaYOf$eFX*PfViRQ)uFzuSsUd1*u^ZWaEwnA2MDiJH>l;=dMk zx*1QTJ;8sgjI<+9wgdmpC+qWRO;GaLgr`)OQZb$FqYXkGHECQ~Y5k`J)~`UjzlcxP zhQ$mYRiUTcPOKbxw{WzV>CdGieaYOCNHA{>vQ6cJHz7-O-Ka+PwMZ(j zBwWQ_nPPF)nIeG{rmPTaPoGzQN ziM{eBD{(zl?x)6og14zd#FhTNO-dhs`uo$bp!*jC2U$CeJ*@H7^nMOL!N1`u$T8Rq zKR}M~HaH0S)Ds0v?t;jF8z|KpL|dL!q!$D(u&A2aC5b*I|3X`*>4BR&iyCq}mJsH` z6L}17$x?VAlc^&|SF4qUa7^aIDVagOT#`pQH;?o*_7ejSC71f*9Oe?T;N!?|Q+oFZ zK^(_aV#-3s!#$E~))yYo7&uF#0vAZmqf(G;_*BYtMiJzrDJPLPI2!(v@*yoA$H0>k z$5HOu;J|f?R$$uk$e9{T+v0Y5u1KB(;JL{Q8%}$2hLhlLd9C4r&owyYD{&gkd7dB9 zW#wtfJMDGxY>&1V`D+6?cVOVLMTF1p)YM~aSL$e;rd%=li`5FVAKctt@YnQu)}44O z_`dhU9af6h3AtQ*Q|r9pX)Bu_>u0XudZKXQ(0Wc?~(LM_!M8 zV>HnpAT%UxdupX?CwebwtwpPoS10JFH{vepa6fl&KecE_4{>UstDo*2c#n0dc_($e zk2;jzLv3q_SFDb^b!wyCUWXdmp5Di?8uh*NI$X=PL@};O5OMhqN~@zYe>b}IccX7# z1D*OR?6pp;OwP>&tyk|vx4tI1>fBpWg}RkWAUK`S*Bxloyv+}QPuSmGRNC8zz| zQ^eL16K7AvL*GLg$S)m{ZN;f6Bb_0W*{MTt}IS^f&l_fd{Ow!A~g@7wlu^uxQ8b zJoR~~d6NB6=Bq=bwLTKZ>lkMoCv}Rpf8?kSWAQG;z4$a})I@}cs7dp6(54ZiO5NtD zwR{Gi@Yl?%r_c%hftgm#Vl|#ce--752^c+L(XOsUXSf)e)!G88Wi4*!d9dq6mr!5$ zQfB#5aFMSkRf5to@P*Y-D#0vYj=1{5*AUYix%jNomfAj4f+j2v#Zek6Lwd0S^hPP@ zjVjO^;VWDM%ji`)7az4JWO**DN{U(w6OG*OlzC3cGM0agwe1%P!%L~w0S_eJNx2X=n24YfFO4G$I*FxG6W)WYz*zOA% zLzE1u{WO!&V-iPg>C}0e#8J!TJo1z^s`E63QBqsxDDTc9C8o9Biha`J6p?bt(wR36sNPzMw>vicYQoWYx~7KWA#47NM2uwnvHoN96;qdfF3xEaz5`dC zuDZ2iNnq{Nl`kjSxj1U{4P$^`d$f<$Dp-AcEpQ|MQ4fB}a70o`M_VKLA~WHo$lKEA zo4mIv@XqyvxB?E@^1w;oNRCKr`XF=Do7)zo2OE$EYzy*&*Mq!9bhZ6R5ne|I@Cwq5 jSJ~b`it-lH3S}Jc5I;cb9poU&WR4;QIf%CO;sXBguZM>YAo?Yt>5rHICJ)T1(%qy$0q?X<3%d@xKFH46!XQE2Y)> z&w+nF{}RXg>3(tFxo_QX?j!dj$1nGj`@{XBV&lu98~c-GtU_N_JKF6=iLKqnZgbbqtxc-Vl-q^VpX+v%@+VJJdNTWf+JXb~B;%+Xtq+8l8<(73Ta97q` zm*8DK*W1nE=5>p5X5?vRH=Ap7d~+<&}PCENJ&vA9jiE$p0_N(*5`N;n#&SmFc=el#pdEnf4 zZgYQ^r#C3?KF4L=oOi;UJy7i$xkrj~&Q0Ka z25c{#$2@)DyfrXCq0aY?%k_KS#C4V9CV8)!+TS>d+;nbAHz%hskR1%1J|MyIyWG*L70yn~=AO-K;z>Mww-~ zD^7}XZbi2~#~*Gj*OwY{yQ!!tyZakG?@M1c;$1U(&d;O?AYD6pz7@3L$Mc%JuLYHK zfl_C=|G4wqzug&7&uD0Of;$cx4t6KHW8E>_kKpM@Xn4Fk+#Lpuk0$j%cQW**R3T9Q zR(BakFi)Dh4WI;nsA#&oi?I>z?sIo?{=*2FL7MsGUG6S%SGpVdUF&Xl!x&#-jIOhc zrHhQIi;S@gJUhxri(#DKX7rt)r1kC|M&l{YX!nx)fYHhTwz66ot@KtpDmJ2ELK`8msQFtX*IGcTJ^0uR%5G` z)rPxPRtX69H?Pz7e*1x)$E&PY6R)veRlSON`FZ8{O5yd&erd)iLt9b=^ zW%Jr@ud`>`)9jOWHm{yuoxED|ESc9Cdy~D}j<65fo9$V4fL+wSWu36*S^cfwtp-+k ztC`i&8g2FAe2W!`9<#qU0BrT7kH5P44XMKg|opLw8WyWyMaJRsLF2IRi z!MBoFO|2SM2Wx;e&Dw08wccA(+7Wly%fvX)UuRSc8DHqt(L-qK0AI z4dQnsHT{qJ22l5(mLKgY0c;lSzU&@@W~M@6?cKU=LB@c~=)c6sJ;-XP!k%vWA;pp1`R3AEWJm z?leZ+Se|N3jwWwE##no1f+oP%2Iv|w!hbijgdb1*7~LM|{mC5!oUJ&@ld1|_CL68H z4rdXE`2??hRA-Sqk`0A^4OxM+vw}HS#y1u0FK65BO-$b!}>@ z4z`+tqd;b|F~B~DRxW02&xb;{FwzgeAtIsFDCW*v{6;gf?=nNjGH-uijQ@nblhV`K z8IgtgEpB;3XZg8H&6&%}Y$&xbW3?F91)HihyvdoD+VXO8>h0}g9L8Gf|96D?>2=WW1m zb%T-8ye|$W^3am}z%D8fE%{hgN$&@~o5PJXH`Iix>d+?@ty;XT#wnT+Uh;BX+$zU) z9$-rjJveSM+MAh{=B2N50Cigar=yRvfTzs#PYP1SC(Tc2!pr(hdo1g%D|-7yd%Sp) zk{&HVPt~^SS#^Q93h-8;CiP}HYA$Z*IzIULM!t8Pl58`PPngPN>P!ROr(+gOZN;?` z@HREq>Hm+?vI9kKpven_IqAjX^jmdWR+SpcLKUfj=@aAi9`ySN$@;6C)WDpWQgTsy zVd%gYiV%dg4RwjW{5Y#xfAFL>^IBan*a(Vg!224U<)|eGbeWsF3WD<@;7s(Ei!$>9 zOBQHERFfKv<)$Y@S@D70G8j{PGLR;np|&E75+7jII4K5hN>f7>uDxlCMp9}svT{&P zKAzSFvOd-rYoYazwZ&R(ZMBwIi><$yjmBBCxZcFmGuAQd0`t~Q>#lXfisJX7^@>^S zCf8?q8^-LtmwD`Br|EhJ_x<|<| zKo-T5)0A?FyEn}8actL$YkvZQuhs|arDyMeFvIkc6{zLk)r~s>e)3(la=?0fjFt11WDza z_11a@ET_QmK8{d&=MZg+q_y`S50eu#)63$`pU6B1HUJ41eCGSngQpZK-ojBKx%0L zd`+yDaPk2Dw}VeLW31I?q*Y@CWHx+Jn(%Wt;~TW(c#N^)jM%0eU4gSBrF5`bGh#}@ zxl+NOEV#r^M)^zTu}A3Hk~&K>Pt+lG0QB448pQ|-q^2s2y3&lPiqut!JIxQ-8JBrE zOYr75@K=R;>T>FRX~t(>Mq>u}nuGj)6@GuzJ&lz6fLZYq8oC$bTO1$@W3>=GAs^+X zfKw%6woSmVtN_VrRT{WPMpCIZ&FPteF%7lnrFG?*fl6~0BTY7Hi*LP$d!1!2S?exE z4%>uWb^`q&n)yNXYKDFTw|xbMZX=#jmGnu8QJ3%PtRC7lNH z80-bFm|diAIId>O^l%stAMyh?U7&*z(9HyRjs8zUlAdY!#7z2c0R7h$?%17ly{(Bz z%&VeK_4f?)w4BVESzk>4qVhheGH7GS=@h+M*d(5sU_Lx@eB`&{qf)Hh~f+QuYLB zd@+1V$0A1KJm`Hitr$ZKI#FYNW+Q13$)Ov`LqC~`(;~(AFm9SaRb5FH$Zrs}k01PvEN$Hf&CdH>NPF;%IN+{%9o6zBa}AZS^$>T(FSqXBXE=(jQ6XYCm6}=!1HjR z>O~Kz7y5(oCBSx+{K=`KzTMgGZU@?b+O6!m+& z8wc(ZAO0E#4w}?JBwUf(`MYeB2zA|GZr;V8?D>J-07ks^@-NEi@ z_qV&CyViy8XR^NW(9sNVE_%{9nZ`L1-Vz9{c7c|EN1iAFeU*pvG~#JTtEZvi1@!AmsB|i+`&sSb zea%TxgY;FH@k_t~GQ!J>GP*ia%P7(XGxN@)uIc0op_cxXCizu5fu#3ujDATM>EI>> zkWI?Nwm}%{T|&i9lmNfrKlo;SMY#vU)_p39s>chNm0hQ1cDXnF`MthHf~5`wgT# z$q|mF(Gls;B70=D%VlM2{_b6Qrs}I@Lm32p-yRFn>p5+T&u9i z9Opboy*J^5PvK?yOO8(S6`uGGi0{GUE|cmE5QYKkW@-;5$5Yg7kBu?_jdd2%`!4qoWgNn45zTxedrma6{4EYyE+pm8L0eW^r|^F{&>eJCq3Z_*u@)0dQYiT0dDr@26@j)09_^iMc-9_RTf zuFm~u|JqJV<^c0_ECmbc)fM1k2KXF+_TC1ZHbsN?1OEC(v-iakSRPBCAMKMBZVEPq zg$8forpJy@_Ce~60^VD+?GzZ@K>y5#QYKT+0&0^r?KUue18WXa{dY#ePpH-9*>&=6 zbr*sE(Lk|^yJ%9mv^NPhpH!s#NpC);Rp+3reNf&!EEyAlcOC7yLh4B9<``|<3q0Yp zd@B?zir)d=*8%Go(vPHFBe9swF#Wy<3fsxM&6Fo9j{?3+{O)!4)0Ry@u@EZhhYhB_ z+t96nC88k~jkdVb4x31~5_ne7%Gu2L&3k91Sq2M{B?Ez$eQvN`!F@vF!M$kohQ)4r7RyMZhG(b~< znth=dAKunyMD-+>?EHU_qbB1j9d`PA*xe6e4cmz&Z5y_=qfQi-^^aJ^($cmvSe&aE zn{#9EAe&PIMpYg+g_{&>k(c`kyZm7+a7(bWt#)=f>$p3PP3=CmrMKA9pYi{@lZ=`& zVINQ6zQ*2k8*Bb$EOjTSCkl(b>!hT#wAlS+XOG1i^&FejQ*7`rSTXp-+vIS7qKxFm z*rD12eIVHHLR*^CJ|Egq07%P%9YoI!1)1_FVD+SYH@nh2YXl-Dq(lXK)$ck6CYdO2j;wMv@hJ?Z5rHc6nNVTUcR}q zyA?FDh3slCsWlu7E`{4@v ziUDKi=+$7j^&rDlhe9D^xSvh#P#}5*S1(9Sl9t*S-LDREd41%X^~j~$(G1F35%gq5 zdU__k)e60K9}<&4dG5h|LyY{87U?Dksd*r9W~C=RYZM>Qe@Y{%Rps=uuECjR0PPyA zzsIP1BbMM9&~`U7=0?Mnj{|#rY@g3*hj@Ajd~7voZ<`r6iue1V{k>rRChfB6)9lDy zm4UiC`a}a-;EUYl&C`^W@|u1=Ms2HrI~=Ne4dyfRE+6uo?7YQki$CoggVx##Z8f_k zd*gBF;~?~Lf;L=+LSNA%iRjt1yq7i?2bA1BofVEfFRCo7saa$*j&d;&!La{!+!>1OAm&M zmeA^O>Uv8X($WIif-UP6PxeyFK=@r1cuz*N)>nbqq&qaa0&GP=Q=hS;eq&_CFysg!=afTU?`At5cksXhOXBqoJo5xP&DB zV^GOS&Rz7>e&Cig6hWUwf$L}v$(Ju_`$KvxiaXKBeM)%FDuFDwsj-DVrM8Wxy+e@* zB`2(;ymLVH$;}I_f#^|V(WE+~W2p^A;cQ8PJcc?XZ*Bwb+2BR;${;97^5Q{y<0e!e z2TIL{{v>Uu6)hJfs5cL0< z{4NHDnP`hW;eK7=AKl@$wcuFZoWAI6bFg}x!Y&YH?1g*KUpLXBsX$uFU@#o~tpeL? z!9f^QexLr8^|}u54gj)QSP_N+Ne$?xFfgRU7V@6)sJZx+>BAEA%{6v~B}U(!N1Y?UV>8On!`SxXj0=1f$TI-_dmi?tdB8CT3{6E7AI!|q znYtU(vWB$2vFWpNU{*Ht6wH=686m})t+XB?t-Ar~szW(h;0+e@L43ot3P3;Epq_8c zIS-)Ecgznd;5$CFPwQ^^xYlY(9W?!+;BPVbU(30U_WlhV6M=dfzroP-Pw;WW1+HGqaIzcBrDunjQnX$q?*MrT_>xNP(8$b4U(14!l&ey_^T*E7 z65CfrXd$~9MOyLFN=_VBb|RslImlN_;EAzlhy}r?#zb{v$L(pX9DSgPKY`sBo>G(= z5+H4SW5oT0YnA}IKpQKG`U+q*G+)f2dFZDeXY#NQrnc$ z2h*X+ScxJT)$6d6&4+&e z2W|tva~0Z<3v6gzNb7LV;SOKju+f`TTHWtPn<_Bkw32!ie!L5rZwI_lGt4V^ zW+LVRal1S~nwC`W;9YkN?~%>G0^db}-xq4=3QZ3O3&ZHyKN&4$z++}cziijCLb}jf z0%|Wx4>vNFPgydT(ds2o*&2>*aEHHut2_Nwo)-9W`hmM%#zwT7_MXJ5b>946iB)R> zIGAkYo{qFg-XiLoT(qW;p&;3vvH*po#QZ#MWNhUl=#fF-wj;gU6}(TR&h=34Vn$pD zc$vhTK*nNK@?=B`je)o9ME+d^pV@(Ya*cU5Bk#LGjbm7!8VyBGp&k3_hYQ#fk3jRm zl;BSqt-Sd_N1_n%L#=bQ20t2MeV~xh)HZ@T#ov8^y@W~CiV@y~92FVcB^lY(Sqm5r zkBPK?St;=wiEm$_mdWJr0^VCwQvmh#q(sS6)6E*zEo_Df;7-ZmQQxd@*brY}^NfYh z$*LGmiX7h~TsTg?dPHTgq7_BUAqW6}QdL->AkMwy3 zN}i7OVj9xtJY?;y+~0N6GINcFZcek#BfI(qIQJUL2p~@hQ|@oHv!@vu!p$IhxC6ES zj!u#lE?ELPs?4eJqP6CR^jc-!7JzqZeLpKHym>2*Q=9g-XB@PELzUsp7shTF6chrN zSp*%OX7-l7%Cnj;PpEi|()aL`&y3TbP*`5LpH^rGGE1(+=DY@a*KFAi7{%W!0fp9X zN>YAN(yAY-nR&JhR4*H<<4UvB`f?^@2FXkE%Zx#02}NF;gWmEFGw4C&o+m(}wP|m9 zG!wH#1`drJtu=Q9KmDnB6qGWGYk&AxZCYQN_gWW{w}`B$l6m4o#VzQE5V+B9#`8)j zX#l<4A8d{T)3WUMfeupEP^qoIY- zqEa$%M<5?AK}(aQ-HzO?k*bFvBX0%Y7JcOhC;JQ9Ka4zbh*2<}QB{xU=?%~Q1k`7c z5O*Tus&}NB{xa>6)lRnQMPOqky1{2R6@8x1U^xn0t_K%0sVA5*v;xj|25I;eG-)#r z8k~t+=cENwmTn-SQ2ZQ5cxs1P%R*(1WWkjHX(Y+K8hE+-xpb&`h)qj<^@zbdmD{y!sTg`*EJ^Wrb-G9B%|I z@q>QK7|AaOcrMDdC`j{VO)!v+IsX%M`W#*@9rObfmk^kX|3_tmpqz>D!eL}vQ*7#KOrd~pZ3jv_ns0JGkt(OS+)V2XjxQ@{-ygWD#Irk3#V z-t=`B=AgpB?m!hSb<3n!Us)`S{ittw!A%kSg4_{m9! z{k;l3-iE&Kfn}sGS6$F`2S5)C;4LQ@b3bVPFL=yx;Fj&bG3h(Qd!_)Jylg+hd89>V zghG9oam6jgCsNagvLDD>;4{>&Ri8xgf?~`bwV`zRW_4tys>@l%jFOtlL_?4UqHJWiaLih{+98bl#x=EmJNj`uYgNP zf1H3!<`3+W1@2=NiDJCIVq8n-Yr<&gi?p}|nL2_s>6?tG(|As;Mu(V5&Z%$+`8Z4f zgMUza7Wk+vqT>Bsk-d5_swBfnmy>Tp1*l(hZ82(-z3LG&%>}4xE7ZA|mU%4O;n2-) zs7>olp|s`(6cz`GQtQLNk-IYaCDRn6Eb)WeP~T!Gr7KiilaVL?^$<#UK+j~R9YvXs z3eiJ#p@{&vs2`6&Nw%2dgq#r5nJL zBedc<+*RW_9h54GK=OJ#q!f8|#?azu>bgoRwXVK{9$1RK^E7jbH)EwYG~y3N%Ohtb zK@&eqirMtW3~2Erw00jDqJaNCa3@DIlK*IOG+uc~B{p-ZMx8u+yEC5r7?ZLqW#cz5 z^KM0+bcZ(kQ%7C!bD36*fpgE~e;BR0i?k`9uUE*P51~l;8yw=i#`B-l8B4p@!!H+c ztb;FyLQnDyI0b*dN~$}wJuWrXWef~vOv~mj3qctqMTfq5kCc)Kx-AK<)PRQbvRbdz zq;N{OL=XL>*HZDc1duid{{D=jo9rDXm!5gzv~lS#ABvOmv%B zO>2Y`m>-4#%V1{Dp`87pOwHIkY3F0;J2iDlyO)KvF(oFoV&Uk!n8`=O?P@U3mt$tB z2-UY^eqBOezHkdroBY^@BfkZbvo1B~0KXS_KZ?;&1@2IhIe#n=8~_qw;51meM^B6X zU(=2VW98WejqC;Y7oh#O;72(A#@!is_EgrAt0H-1XI;aaE9owMpvs}lJd$wN0_A>6 z`UOWU%UoNVcH~3uzXXqK#G318_IF)m4f%*04lTseE3)5)m~mQ{ zS~9q=%+9V~PBPYDYqN$N3{~s|!czvuLtOpMsBg_oP>eO*RIG0QaN@GATMWL?mehmk z{c((dO7)SO=cMpk=KwALDu@5X(zRa_1uOa0D8DY-m0fN;n4e3t4T@KEnx6 zO(-qW3X%45X^nXZFlsl;JW}n2QjSnt7-LJLQ~SYAG84#8<^;5QmeHsWHEq4p`{QEIJ<$WusOL@`kP zGx*$PDB)jdT6{1LH6}(k$P3SE#%%5nm)7dBJYTc%R6hO5kr;oY7V&M#B`KM4Qo@n7 z<|}^vl2I)`+7!_3MQ}D08NUly84X>GgVLs$o}U5+7XaH~DBu~R=sn()o?Jf}U8fl< zTFakG>VIhS6*y5YYOG1ieBoWv5@jjTeu2E;Ck>iTELb}O22X-NjfW%#Vy!Cvf>U2* zZdpXl{pdd(A=It?7t%zuf_I+$agENa9SKLEuc^S?4h=zmH_bVlA_w#V)4OP6Jftf5 z2Fp)W-j^HD)q5iCBw|O~48~YhsINS9SQ{C(1?7a$)8fpTjh0dn$V7)4^j~-jrrUW#LQnfD3RcrYgjfJ9m!=JkGzb7q~kB0W& z`Xdqi4VFH$iZ5BUIx~EHE1dpmLcMty;}xmBE^Vy{WQp7l@SBH>TE{8M)6R?t?Xw(> zmQ)_z@)$pvJU@qVz~2}wrepV+cBWkhs_pF5 zo9Rqo|K4zC7JK&g!1cev^9#bOe4#FRLH!R}oChWM0VCDGeFf+z7kk{aPxzOUfnBy` zN!fz5f%L6BHqsy zuaQKred);}`?p&QJMy@-(*#)y{8uJ!$`ln`dT0ItHh!fDeu!~L5B zeM4x~kJbj!JK884`o)_$C!LXzI5=m%2>Dju_X;hV_e8;@D)~( zU$UPi7VFgs?7%Cq7LG;N3PMNFZiu!(GyzR>8v5okw9vKKnZn>u*Nxx%E-YO$(a!o) zdpm46?Ww;hl4o-y=O*YHLF68Y&1o_vYqySer))s7tp#8FP7AbSp#f=@V;7CaZ{1;^ zOCr3)ld_BEGn#G$_Ovr-yIN(~hmGwe?+fBfp2yB&|G@7%8culzs5Zklx3CTp3?xm^ zNxhj}>VnBpSfZ9&Kl65@fV$G9VnI~i2!f4T27dZ`H z-m{x0JAUE6*;UN0oQinQzh_-$Jt+pDvujPJJJ5Hbogu7^MFW3f{M@T?R0Ymlv?Mj& z;92dov??Ka-_U}qwBtM&+>hn`2&>tT@hVS7$sXT$n^a!*S8A5Oc^vy8_Ph%~9L~Hj z&-8$H(3}D@@9}z%gExI@cITw!zjiG}^ZW#uUI#q$Sq)wS-uICA5M`XkS6zFEqse&{ z|NEEJ`+~aO;BoLA%VCVkt9?8d@IQFR4(?>EVdY>I>nnY|0ehzGxn1}jOR1p-_8068 zN=9G3H#;GYV+qx6s=d@a*4R`ef{{N>6^pFKB1uh{yWa1A65SX9WGZ9?F{xL@f=y4PgANqJ%57lDh-x~BGy9cEX;BTCS|Yhmmlu!I^< zj3tybi*pTie*!c4*cDcQRl7vMCBKc-U@8+mo{#-qqJd2GXCnIp_8Rrx8tkkgP|Y|q zk_G{hFYshQ&PdA4U4iiu$mksf4B^m&T3!kYD#>qJ_8!~-qf3BukfF5!=!O%a_YGj{ zIJ6xOb#DRA7|W*STDHI4p0g=?8ocdPjH1Ute2g)qHJ(5Y`E#`~Yu!VsaT#Olq}fyT zfL<2ebYM-UBwB9<_=VPu<*8B@y`dpFwLUb3G`qp{dBZy%gRO`3h*m6bfV~LXtNnee z8TAW+b39tCcG+kRVGYj@Q`c?QITL^%?Ya1!HdX@C;@qWXjC^6=-BUA8p3{Rdlzo|z zvWNV0p}?t>aLJ5gm$%O-=>l)}(=!u+t{J?wxv>}p!x8q=>PU`z@CloqO3(fmjk6qZ zkL>hbMy?Xr;v!EO3m+LvAE2D4wC4>Y-#zwP=Gz-E#U=ejdHyV;xv;nFD2AB}Gy&lo2ZaN?T6R zk0ZdYcBwXJY!ASSG>ei~K|c|!VJ8G9x!`pfz;Pl*pQz$0bh3h$4rctdLvQX#>9Tt+ zfm-iT{x8N&DpLO>^-~}g&cYaT`&lm#w?E3WvtaZtr}p&SWc)v+u9S=})uWwC;*3iv zQ(hQ;&}a+RO0>!!#+$>Gq&0|GIAwbJF*p652&@T1@_>?u%HM{ot^hA#%p{TE`X?Cv z1;sofpZ2e6zt>S{`Z$={M6OxJVmlSvOcz?DHP_Lm7mom=hi{yM(pQj5K0-68Lmd0K zDd#vmMLjSFdL0Zl{s3nA1WW{z+5sC*$}q6Mjq$4eL3_bM1f^aFL!ytk_AmJI1NgK2 zpZ=l-`F#yDJb4-=>;UJH(4%^FCwK{g(wd@KXdNRTazc6dPDhTu)HfF#t8Le5Wdt~m zfDeSxV_V>c8<{3A!A|K>LN|ANpE{FVmb7G(A!aMDn8$+%>lqJ8czRa}p z%aiT6DpFKgBR?g=ALu(4_!7W2hEiv8j3svu*4$ch2 zepVxqc~hS+(nljw7vYI#Pq(~IwZqa24Ov#0RA5j3AX-n%k5=l%Dx&5Qd0A*@mD(Br zmO9hchG?;Q&}5RK)ygyZcO4kJyJ4cM!Sn|L;Q89{;^)S~rpYzH!Dfo83Z}iA5H?hP1o}IaN_* z79lxmy=fV8qwW+s=JKrP9h{4ttiR>u<-b1WhgrEn> zBIAXo_0vg;ovA!>aUiwKMCw&MC*fB+IWyCP@^8>u>rd=d`_Kv`b;~bzDXAA2t9q!B zkQD_g2Ce=pv#@N=r?9{*NB%15KJm2&rIb0cIYN$usGEM4=vDs`=HZ~M`TJlQqW40=btgdJViUjixJZSeoA2gem)Yb^i!=F2? zCQgN3Hi7M^|N7}I^zewgL(s|`S}sjo)^_cysRNwl(V{A#Kl`Kax5LUREvBBit`8NA z0M6C)e;6g)pwG4M;SJXjJo^V-LwdVT?c(uA4{Lmvtsp(%oMM;FDejJvW(84g`k}pdaXLC(o$j0i zo&H3;S&jaD5k39^ZMw?;V^|u_U}unCALT?);wrTML1_J*$=wefdm6g`MkkWUR$s~W zoCrFHoCTyA?X=}>OJ}gt6Z^mjXQ(rPIs%;@)YjUm=hShkITf7pjvrSooyJZRrzZD4 zP9ewJ$-XP`>X}O|i=3^_4y-7bNqrhj-!`$wY(qb3p@w*F4E0Y3($-D`TGtPV zCjsdKj#c!)N&4w3cHcMHFf42t*`c2_P{<=w-v!>^=IXs8ib})Isx(;4w8tt9{6|)? zjGT%|rf6eDp|s}MH`+sOiUO1tdUS|*lngq~#hb418to;V02fevAw>%dVD!maGK=xM z1M7lpgW|}u;1Sw;)|K*PtN({lwUc*R@z*{|jm!u*(+OkU(8|qybW~Y>mM{{vPT!od zsq(u~o2<2a$RW!|e&!zeX2@O|9|`^|TtZ$QT9GRWPpE~xsSl%k0=cxODi}M;EcogY zIH7hKYUjuvAh`q-uiy<1^WZCH2icy_@GCEfD7cR}<~yt|ig=KidOkB7U1m1XE|L>w zwv+eTX6in~^H;11Ii&rJnV^kpvVzrm}5&ahZbX= zt_HX4g@z=b?n%fSEm{APb>%B1D*BB$;|eUQuKJ*T)@FuH&5WdVEk*x2 zWh{Z(M{tZh(X6NZpp9AyP6&jaU8%`g2}^<%`Z}bd8 z2WAxImPO_k^QKnLRx#IUr`UQhpqyH{d1d5@thDiWGv~|OJ`P;}IdESE7WM9FQeI#N zegI6Vkvys+DfLIH(SB{kIMbS>;^Y)TW~l;Z3IX#sV0>H(R3vF zFqlISLhOnysI|4kXxjMs@q=psH`f+0gQ2eYfv_GRphZ*%+>43 zecbeyD83kdEE&=tYHejGzBDptJa$=W*OGSoiYJ_+rbysb9L=jxnf6B{K+g0svSkcy zl`ov*Dwk;lV%%5$+iR%Ue7A#EWu!hV9wH}shzcHIMJ#UMOMUtKZK z%Om>+M=bEjm+T?br3h1!Wh8q>o3(=znu_MNqEHW>Q68#IV=O+>FeK3)=JyyBBzv(|q%~f&vo9`X%U9XQ zNaML_u~x7Z5A_b|v@-t~>V6H4Jf%+U=ZS`9wEL?7x=3DN$^?bWfBqHgQ@2g;UL}X( zl*xazI#>!|BoD#@0CK@e8C{1w~y*I7>JLm1N@J8{~2lva!Klq)%7 zPh;p`di=hTs0Bj_V?XjGjUP6^&ggVKC{tcJ`RO6)R7q(?9K2tif@QTMG4xazo}*nO zf5HI=0{sZ|%O>PeEGzAtyKSgi6sNU0{Z|YD?e-Gxasg*1YA%M(SQgE!Ch(Qxsbb+3 z0PjAuOEK=sf$KDenm*FY;`id?im>KQtAD5G&EI_TH( zry9sP6s!)V=VkSgFLOo1;WL7@T;!}lt5o|Robn`ZMF~}DO%BuVDHws-DJ{U*u(Tqu z3VYjPW~q7f>P522JW z7T5ym8`+GDgE!6N-558cvDA#iGB6t}$$D%kp;#}rW8n+OMsgeoc3^Gv#DymL~!J3p1``|O|Cy&S(LF#RkwuP%Z*ol8(7t4SZEi-2UVtrNS zsE3WVHg>);SfjFFUyRRchlO3r#Ud6Li(dj#XT%~}1S_D&`kfPdR|%|FxrsvNgT*W# zwWhQ`QR^+>yvO;)N`)n`2z8VpPhqZnsi%qE+-{6Lu@<$}U=^bhSCx2E(#}hX$*{V| zwY?}QH5SnvSVS`!dtZ8Nr5S-MJ#Z8T`l8(BqSo}ln9Tl0Y#!Milae|s?`xR#jxwe_ zTA3+8{kkg;4EZT9J#8#RePxMv)&O|^v`5%~*>i~GI@ca)cLTa&K%mG!$-qY$BILBQ z2k>SxseO>Y$DT97D0(M3v?#cvbVF-01Zwe%`|bDx}wLH6BBN(KEO>)$G@f;$-nm&kdWGT&h5l!f#sz5J5HV`0BQ$=j)6IU`{+c@^nMyCEN8<^N>K znyr?;1v61l;89B4#Ax{kxTZ4iEX0DY_*7F3ub+SgaW3x{l41>3xdn{gd000WGsaeO zr|~=?04bwAfCv^P~uzDLef62RWV^&E!3{yVT59+9fR;TV=xwwF*^&@^dji%F3r&GEugo zN6H`I22#Qe1_%HX7O(nN#t#~`q&oX3O?PJ#JY6NqIC{n?t z6H>m%7h0Yq@?_KQ_ov7ck<5(R5i4)RNM@V6Jd?N1ek96GNW+TqA{|9u_s5Yvq}kj; z3VDeLB^Fi##E7L|$g2 zvhe;|SbYPaq~XX;J)x#HW+s%s&~qeUdAq(q?)gRQ;)C&=Mjk0)YS8YULY%UBE0Sd) zTCGTT+S4sNo8-`A2A_Ap?nnA21^IJ8iEy@og_aU^T&0OjIOXx*f^ebfQ z_wmI5Vgwt<1AQ|deW|@)@`pal^PO0UwZ}ry zw0d%kz}BJt6dwD^VMDX0srv!BWLf{julC{m0;i%?MKepq>G4kcU}#2`4bgfirK{Cv zv1;4`Dy=OfX00IyJt3)15xw$4N8(NLnSX2Wauis%(IY#6{t7i(yiZR$?Vl2bq%lVv z!zl!jJf&rsP%OzGT;By+c{E<4U*x+Y4xyciKk%WFkFsJpDqgO%5P85Pg65K%JH`F= zGT&%WM6w&?c?3`WL`${XSDZon54B@VJA}1TuQ4PkupqRq-CC89H6?+{Tf7v{O2fnD zMK77OB&T)-D0Zam1VzYKl+x0{pT$$e1=7P`v{z9PDfL#|rw+2JBBg6joz{xlGQJxl zoi=6^X+KCKPDPZLx1i$b)@Od`hiv&5v(q%Q78ywD)}&Fy^@_k+%t)AuJFNZnH5r2* zFZcFXjwDmc51^M>Q|iEaO(XJZZ)QoLmc=ZefxaMZDMfjTC!@%vZOvHrBV|L%tOEyX z#+#PBYt5+lM}F>(G_7*GbM%5sjbqg;1j%+Dv+Q471+&^aj5pfhA@4`6lnrEtoWYE{ zkoPlztuHmTq1+nOpgp=(fll!uhmdOst=39oS2&C$aqXuMLUPwmJMA0LezIb;K+%@# za^HiweLA&k-|$N2{RQA`6lFD}eTASoMe)u~PZT%3DNWgr`uid?k3tfefmS-wNZh{U zC`S53Xl060qKKY1p!aA)@$aE1hrKh>Zu5gP#lBW#<|I&)cECR+rK0NGg4&)lw&O4o zG;?TgZaJ{7=n>MBQob(d7Be4Dy3<`I)!vKFvR3H(2=b0yPs*8af&Z~~Bk%6NnK!k5 zB-_$ao*?$ej_Gq3}NFeht=roIXx-@10J#VF!?QM0mDlU0kh#)_+No@k{`@%$9! zehRLmxRWWaTF_U9=PRx;j|JgZ;@+>cYd6h_^xM=#CMF}ju8S&?q9XpSS zsOF8Y;O`uYUFHV{J7Ayg#A;SIeibdPC-(tdw*`8Axuhlb;uf@{5vA3qO}@0TIytq2 zst&F8HE-&0?_)fqi%?rO>dK2HJRiT_JXd6^R;-pOMo(uf)-`~#gz@k$LLPZkmoXlN zwOF@lVX#>Z92R%;le?_RU5oM+@vQ;1b)bwMSj|TObzflZ3r1(tgImm;vYT0DGxVXJ zSKl=x*YA{71~?i6t@hTbj^>opno|1F>S5ru7nlvipD=(`S?ziY;JtiXdxLlNfTBjt z=2z=oOXw9vnbVg&w2nH6Yf;1~%Ij`uvMX&J#@n$#GL?Lad^?+K<RJXZ$_HF) zpo#$Hv4G1)b%Upp1U-9WMCXcaq*F7Q);z`O1{g?`p(R+pO`i7k@5)CzuZ{<$3SD+jW~|-7lF@>l&^hMiXwQ5GNLFo zig{LL?_>rGfj+g~I22kQNZYkiI0-xqGN;CeC|MGzcHSsTlXC5+1la=BM#Y$y9DJ4< z!g#ll5v*MpE3um|=UUMfH}hO}TkTbml&wf8+VT4woarkj8%Qe;=CRZ_h+fo=so}sQ zxD?-KI`z(j8s<>mIwMw%MMaVrdN-Ln03-g{A#bnT%gz9 z8bz>_OmvJ=c7uDxThgwxiEy&XTq(}vBA(49-zwfJ9@JK94x{E9t}S$YNSoxI{}Lpt*lJ1--DYbn~cTHrLCcnN7f>z10!)(Z94)QRoDv z)}{8-lUli2L279kRdRp?o8O+detl=@1CqI$_!p&R|Foj7ygql(3+NMXXzOw!Ea=~z;aFlmOs8AejW zWS%HypGMqlaJ-ar4Ry>WS3gE%cWU;B6IEv&tTcS40IPc9Hl@syj&KQmUuqz*iLNIy zD%4V~=uM*JY5XdZ^3$P7V56NnfBC&i^5t+Cil`ZWOi28@(@wc0y0(>2!K+E)XuH zZ{*{y{gZ-MSoM4%NwRt$%5O!B<$Yd?xuP`C_%Lf!-#C%Gf;uE3inMpwCIncyh;LtkfWFQ+w3j)DyJ8DyW(B}Ng-$LeDO+(<8}YvmPZYnhKEKN8PpL2DzoUR^CLDAXTtKseVkaDeuk1%!(t7cFc+v)QwTEZoGzZ`!;v479m2|%_@_Mwu z1HgY6&T@uw#K$%mE~I_58snk}?OWG(t@avjr1d_n>}vg6@p82$sU4P|mJED&04C_rvmQM7uZ z)mvF$$|38wLZ)xSDUaNS$l;0sDXVvWQtEpf+G8z`x0=`#nxl(!G#Z3{wN}^y|3P_F z24aZ|MEmG~?BAAaSxknZH7M@TRAa|mjIXdfmlXwMJoY^KA9q5^m(JmjMkAYr%4^G$ z4!n_<@DMD1vKHck4@BLshopF^e-%! z!NyyC0ky25q&3D~x&>*&3 zcmFkNykz`WJ>NXh3d0T{Q~B$Szq!0D*ON;gz#FjU?lxD7Tfc+#gPmB1^(7OnQiSm~ zl$?90XD2XjrL7zJzuZ`MSCD%vQ0glvn^>t(P4arx4uc4DeVnUP+-v7SBvx?GaSIFe zHJ-_r{t%ct0^DbT`5w08=in^{tMd)gpP}9`YF9k{g{Hq`sn#0GNqXWuB}9?>0`_eA z9be@CdH$cE%=5HCIS-f?Yt?2gE!jYeJf6Awnwer;>02cwPdeZzu7?hWwTafwGH4!dmv;hmu3DcI5`fidq}$p zXoRH^=kv{+k6#FfRXKg8|Jx>LXz6PLOKaJoC`c6}AV}q#;Wct!mb9j-5Q;1*v zLH^d1s9olL;0ePSsp1H-ddvcLebXg`@g5AsiqNH~l=}XOzOvDsy0k7JUtPstlWkY~ zhjjlZCF<)jZGfbsx$6gQj{XU8nsvU@fw_pC#h$OQ6-$n-c+CKW?-W> z&ws-Lqc688UY@?PCf_T~2RRMjmn}OdPjecJS$WdR=h*YTmm16g6?jsRdJFNJ!&sj6 z4VXgke%YNB|5$TD9!ki|m8{U2nO(GdJq>f1Vm8R9>NBTeAjQGfrZtWD%vAB1f3$a3 zyU^raEDN11Kw5M6e9_IDIY#R#if^bjc*QmQ#{4Q@82J>vWhVT{yeO~OxXgaCh304e zQ#@wbg%q`4k@_Ez<}S0R{%gI)3n&zyIGM3WDy3pG$s zNQR{_2Y6EK)be0M_IG)D=zDeY$WBAMWxvi24EoIpdm!;h6#9})9Haty!3ft{TPjFuSwl-3J7_6# ztAs+lmDM!`b?7U4vVchvM99nAOu(S;mU(0w$vKKykjh}*6DKaK z!CE#Xk^Iypd!49NDL~JAF}1c0)lgtjh01p78RnSF}1$ zbP5;DDaM*-Z=-zq^xeEF{8pwG$xAs+DT)uI_<;I?h$7huTRzy8wLd{|`hF*mH#tji zrRZz=W{4t1%Koe`(bZry`Z0>yGaB3A?I7Hg4fjv79|Joc2egGg!{RZ(iyX4Jz`Rtij?ao#2*=1t!kfm6{YMNZ&zG#Kr0y zbxExiAz9JY8uflvU{zEi?OKq8M>%<`iFE5i7o!)eNm#Y5hU#i!T}XiZLLbA==f7q1MV|FIEo4 z)z`PT)c$B}KoRhzE5A zfs{L#^wReAAMXcn};mO_0gmi1wr58?m~&OCn;9e z-{5B%HZR3qT0rhu)Hj`$jwX#Fk#*u%U;I#S=*vFi$R#~@Ja`n8irb;@Da&Fs8f?lg z(w98?wntCXcIDST7}+kzP>QUTia{h>uD%UBn%{xcK9*ACqy0C(ve``m7y7z{Y%3GV zsps=ezy1Tnib<)JgsrCMw?awVu*d0pLGlHaJu2Ary!88VoLUQ*!E@=o3$eV(C-iSl z{g;JJ`ns%rvZZaOExXW(H*l{Q;=7@;16;|%sHiYnkKBO8Z56rXpD8cR5Dvv85mr1c z8A<)JGRU4KDwHi-F(XBv%b;CFl3dOIwcIV>`HcVYul-8F*oUWZDwdf%SoL-G@%$f! z9c+}b-UNd|`Mb&@(G8ktOApthpQ~eysD&M0bm&k2i>`ZtbM@*tU{Fj?eXG~AH%ayy zeUU_7?XtVb3e(PmSGpB70f@={guV{Q2L8wAw+`iIj>cAX?V{{-lykRMGl7 z@vA!ZZJVwe2)TigAFw*3fXk4|wNzkny)y>6LG$^b`5^cR;F; z6-Tl9q`P$B+Ml~-$Q&LyM;4nJXgBqcKonu2CGQk(p(lEfbUZ~tA4cjylrxrf-6`BD z5~(y&eQ`4ctyf>)oNufxis-!@?Nt^DSv!`a!)`)@T}`^>#{RmbASvStJA2kx4I}(li4A0K+Uwe#C8;xGS$9Z>=r&l>6@RQWn^R!PolAX)f z(dr-L?e-8~x0mc~mM`BY{MfXo*yA^s0MENje1$JNK6IJ6*8gmHC~6;YD*WBjum?Im zzIyREY&3cQt&<% zUV|CAPf4xG*@K^$tMvFnCczU@F(K0MHY*ThFrJdRcqr_VsV)CmT|8EeC8jqO$72~b3`GuKjg?^y8oTTl z`u;&M-WBq~orJA#1Umg7&QVwcM{qR?om;j=kH+7VXZ?B8ADSD4WuXtJzB16=X!UKl zmQ6wWc@R`BU0z?J_ygK)#jiiq-W>XEV)S8oJBwBef#Tps?so^f# z5%8S1FG!`hfZyOL+9jb~P|}<{J0P^1BN02Jq{S;5xEGSRyxKnew;SmfWouu>ck(Ic zH?C!)RvG#(f@epS(tkBxchcd$us=jH^)IyaAC#y_!O|(zDlhal{&6RbU(&gDaVe6p zLrvO?`W+AW&uAGx%)SJ96=k3V>HXTJoCh50yFB{Jc`A5YW_l$b*YYHkwLuz=uCmdW zijkU$9*~!tc&7Z_^!@DAJoS7fU3@bqDHL@}W#~IH@}U z^rZmB>?(r3rEh?UhAO~S^ZXhR7G#o zf5iir-Y$(!I;M8%)~lk~(&gfuGAGqzP;Uuqk~{L-IN2N+GbCoR;Dv_AZoJyWw!7sh)x^Hw@?585QpC9P6; zyq7eu2^ab*sOBwsL@Bm_?03?p8}d{;Lz;nU?LN{@6;B^kpwE7z&nnZ$Rq0h}_|=$8 z^$lgkc9adFJUyq~8Wo@p`T3UNiM+x*bfT}ld*aX*f`&YC%QHef`toq*|L9H@sI<^+ zDs)@fInu%_1&S~jq*B{1w^(i`P<$%vV^o~efjbka!CxD zruLM{_u(SC>N(cn&maq(LmE1V3?x5B`6X!YQy6lPz9Xmq`cBh!q$0)Ql;`DYZ~V~}_i*Gem*Jvc}4tF_sF=vBRt ziF+ZJ%KJr;zja8imdA}&LVF-f2Otr5K)#li*$~OL5&!FOr*+td{AxW^E1}xAUV*bB zE4Eee!Kr4}j`i(B#Zi=ZR8eyl<9`WOmy7T}A7^&_hKh4$WlcUSciA|#12+p-nfc9t zw^J6LW#BFYSBk@!hMl)r*m}{@^JK{XLd^6D@zV%lk!oBDE z>wni@*%ADK<0J9WzVW5RpUxw`r}&cd4f}yVvCH@c>0`Kh$e#39>_d-br}9%$=zEO! zdH;~FD#ns4n&@<~=J|83-w@|5medbP{eb_k*$b_dkGOuu^#cw)xkK8!><+)iZt`gJ zKf<}mF6S%ko4(F|>PWM@{1W?`<&B!2XYT- z4s(Qa?&tj>a_l!(``GJ#kbUTT*-?F%bfMhsaW=E-eHYKRk!BMqHd4-ZenZ(Uzk%!3 zJYC1R(OJ*EcF?cm|1R#T3M}uh(?w^3)7hDVL?|J$b%)jC8CB68XevgF~KG45kpptJ~d$~G2u}TxW--!?E zMbv_yPCA|?fSOX8UErCBMWC-#W+2W$7N{>Td(ah=RFU#ZLaT~rSIEuHbx}j%1@S;D zh984`1Le!0wfeHg`=lBpq8y{765~YQ6s_`~#$1Ezx{M}8a!@=6*>RdN0yXwpF&aCV zaTtJ>*#>P4njLre9V!IZE4AhX+IRbS^Belv}DJTzn7kjK~EclZYRBd z2AZL~bjG0*Y7e{o6lU{OF$q1|rJ`pp=KW&$&EIgGf6&lmy^tpO4;tG#?&Za`!RTp< z5}~g-uEP^R`k(9+@_W;_-$K#%F)(PqWlD6;nZ4>c!e$~jZwSaJ?j*T_5Q?2b=qa9y@pC5$?pa7m^@UqQ$;&9 zWu4XAPy9+Yl;u?Z1g^Q)H)E3EiLL!2lD9m53Gxxw?u@U-i##c^nN~{Vq3{c-Q?^j8 zZ&*lQ+TWusfw zG!$jOAo5CnBZFw=uQ+D~4y`^^Mqa6eJR^Cv0@6zjPOULkEC}g_{NROkD0QEJj5y*&&)-(j2aRh6> zlh7LVO|`MC08hr!pfA8pN1L36b~y=+Q@h0$aA;*%tLR!!U&giKO3J@uzR^XOVt<&$ zs`>&nQja&rR{rncZZm7{>x?$Lhc*5%*7LQZzxO|Tgkt8Nz#4G^?e{F_31f%2fF^v2 zEB*J_9jW-uD0dc7M-i=J~ukGxwf*?iY-ciGE0dP9hIFiBzl;RX|)s7MA2fWE8Ff zY~@3PkSqBZmST);l(@l)B`pQL2hAAS3-IU@v83mwTJq%7LJy(CxIYLQ2I>xi;bG|m zoq!KC24p;=-ard)&LC`6g1#XTS_87vg`>0|=0VtsffgYW>4ES<48c}7{4nX=7zQ89 z803UuI}A&%SA+I}9Enx19*vY3_%kvm2EK}1tB#UYP@YVRWTmWyZBA9OUJ3b8_{Tt> zQ3q#^MoJjw72&_hoKUF@nGGXxRTVG=;VOA`6>(qWhxA6OJ0=avXpur@HeYB;=r0HAoRt>qFDYAME4#6}R|G!JRBpl{(gZ864VTyo4s zKkXTqred3Mhltvki0$$C63N6l1>Y_iNXKEE$he0S@fAeaTuYE zK{|N|#-ZF#kOw32AC5F?X~;mqe23nV{%rKGVF^Z%WHd7#VFH#M?J;KzBnj6T^<+Hq z7~^g^{f0D|x+(@kPN;s}%JiCe5a~$sECtNv^8B?$%uPX1B z{^_%@MUJ@{*yhvt-xzc$jzNtS^-ko#^ow)P=^H!xiLkg^4}Wv3(q_u?E!uude>0qp%J zmaKRCQUAzpOOzdBa2>`rCF4oxWXW54LP{qViHN()Se`~rJ}+&P*N|zap||}DYZu`C zenLW;-IA6EkwTuqQ<$H|{+IASElpQZ{xotpoyQ($AYU(|^mWV~-v@aLujAZgMZAm> zjCyqe`(BW~oAdaph>1IkQWt;#yNF!sk1t?(3|GN@&R`$n)=puI*M183Oc{R!IrJwc z+u}*w|7kplV_4sh``(A^rtkA!>FJOW_b{I7Njx`tHy^|^57PsQh_p=ds z+wo4;A#F9vu#H-Ut>xI4?|r%S{~7ZIc;C~J#&2&P>I<2E=b&CXbT6Y&x5(K@M8IgY zv<@8x@ss3Ype}=$B6^Tfcf|IaQFo|?YJs)-XuBDIssZ{14bWF;ggK+}un)l=MHTd2 zqS3>kj~3&OP)Ejz2vlk~9sM%)#b}G5u8?{=_A`kBBGV!Jp7dCxM`JentF)lgBbQM$ zG9`^fio|L28S9Ld{1WMJp?~-&*~{tWOyuqZ^s0%`z4G6jJ#<=KPNTe;&| zo|t|IT%N^ffDx|9sC@t_M=(~PX8@7=96gZ7n)rFzR>>c@9AgaHtGKlQ*2?)J; zlwMfU64?_eU9og{Fwr-Nqpc1Ye|5r$tO?edVcrZQwk8;{HN%*#DaLS(F>0fyRej9s zV#HTX@*b!ujr=NMI|?b0$cuoEf<923hk#FvK2hFSdSa{?jIm<{cy0w^H0gyY7^&{? z(c;+B1#@!csW5l7Q-7hf8$|_1ri>{ef`8rd<7kyS4Ca*}$%M5MY?~#026YyCu^5wH zESEGGMJSPl+&Ur+ZkYZOGZjM#@<;NiDx7K3(``hkvK~yU@H;xOw7MaxzvSx#`G22j&ZtU#GZzu zP+O9MBPHX=sTj5MDyVB=%%no3@hY>RgW(l&?9MTK21?}OF4Lqjej)ZMLP{obsHdTZ zhDZ2@t!%94NK+QJ$Sz3zKn{*T{Y@^m3-H8}rKd)oxFYOjL7owNns9X%lr|u}RGLbV zZjhe10ZS8d`F0G@ADN+HvPkbqK)M=lNoKbqO@{ZV!rN3rpFjppo2bNFR^ly_3(*a_ z1y8*5K)W591`RY09;gW}s1>fzF|eMnw)kS{34NCj=G2AhFxR10`QcB_x=NS_;~$Nw zBI+1*XMxZ|_@Gt>VG2YotO&0E5a=f&QMwXJhC?q=88tRi^2w_Te~VDm?kMb46&kr1 zq(-8?*TP=4aDy8P3=i$Ec4jF6qfw zUnE!*7#l5$1$r)*T!=*mvLU|<2+5V zPLGQA*t0FJw3T#ylnYI;#{HSXE2|G=Lv!r)|EVppM`KLX>yU%9Ey~c7r4_V1)bO;$ zk=si;(Uw@YLb-NGBQK}JkA?cv0g@~tLWYv559U3gU+sl|SIDp)kZs){zo-!+!eJoR z21Bm(g(heaG`Pc|nWYaC@i~rpETku+AkwdC93-bhe$sP@m;y@IxsbQ?be#)L(MoJ@ z!q!UYj8{sMc^#znFR=P=f_$bY8vS_a)3_JuhoA?d&(I2#?PG8V^k)10E?HYCL$*}MPMbOJo8*f4@=x7bg(GD`Uwh67IBW9)?`g?lY zo1l>)TGbU3?e!k8`8jkT)MU`MNIO&zTIC?L%d}}xTTvS=v%}v#2;+HrjT5)|16ud$ zXy+ZMM{+OGbG|J``yEk={8vQZIU+uE#yFm1dGaK6!8o3lP)059hjpT?*?Sm))Zths zzBv|sVp@_M{qS+ISlO==s zP9O>P;eQenQ3R*3P49E|--xR}ie4Sr^%1*|W`eS`B`mL>#ZF(__@)xArGa zJ!B~|xqOK1SEr-UuhxNnu8Pbm>n5)upCudOv<0>NPdoZgvS%_A7=*5&4@5kd9Ao^^ zvXini7>$mAMu}+Ptxh#jqN^+&&&36CrkmoNWEj%OPU9()Ic7P=haO-R3X+{bAB_Am z!!RE7!I(S&_Z$T4t^)7vGA!+`G6VXaebCzqfhOeuQe?6h7&p{~#npx*$Dtp5A3fV& z(Wi4m&#ecZ;eCuoZ=*~r)D|CTddA_2khNzVv^%t5N8;VimU_IuV+_;@wTaO{_%7FD z^fDYhLdJsjM1AXrw>S%LzCE;0-l(IMuxBj#q)e%eJ0ahP2kISJs%Zxez%>vBP7M}u zr$3;^`s4mI&@9saO3j*&ROfm1&NwFVqZQEKq}I?5%dZo3Ze%8*Mv%JOdr{NVGeZ(VMFP zS@{Te=89+i8+z)s@GW&hov4VEFR+=B8DKc%?p*A%5ps$=mNy_>7zyMqw8OO4eZ>9( z^rNNdALrStpr#FiO>77v9r{3m^l)kkNk17ei~c|a$3BR_nr-jq^bfB11ti@%9BngX z;sf-Mz3{vhh(_v$C-erIVGk^+Pri$C7a?i)L8j48ABlUS?Y{`pOablc8>DK%ZJLU* zDR_!<@Gv;x`iLE%)FS5yS+=R;)#9x%E+>5l82>35&y?(>v`muKn%ZX8_%^5uKcH4H zN+{3ijw3MoGV%8+q!7g(ikcLOx)X_7M0>vy$9F@WqTZOeV=d0W*bwABBvT@#q@z}n zWuCH;>^__mo9v4vk3;MR@!+)7w!^%>q$4G(2$@7^ zBy3~k5RY9IdvdJQ7;=^T>txoWR+YT5o$c{@P54#byE z`};6_%^ah3MJ_#X7>TwYN-$c%DEw*9ryhiw5_-w?#h#SNv|&3uNQiiufD()pO##vVW6Y@^T;kRfXT_GAmBO z^;5%4ZyR1CbxO4QQ@6uA;E`zeXY>(T{fFWx^kH)N>kvuL@pC^shrxKSL;yJ6MHiGK zI-n2UR(HIqPI!}D@qUS6?|~F*%lURYVb6{z)fI1$SdSl3hV`f=j?)%zw!8GE$$ZIZ zl^wBPC#e=S!BLvy*d0*1%YU_pC=f=x<9<9oYezd=17q2ALD{}IF7@wSG0|6u*EUef zqo+VuOvH*X)*X3N7*CWMd)`%N)FVczARi?6ruP>yD7`r`XYTGwuq%5#)|ciHJ7MDGQ6@L(jLIP@mQnoj+`{ast|oR zKuQ^k6e2c8q1HG`(3gt3K}T(7lnutHB9eo;MCt*@;0;k*NSz|TfT<`$PYLEyTSyHd z*ZFESLwO3Hj%}vFM2(5A#LbEL4;>E7~Muf z)-=O>GHOyKT-5@p)~&=I^&qVpLNb!$xEbDCecaO+q|?iUOh?|3pkxc{0D0RSXJj<& zhG>h~j|@b6q=MX}XA-@XB9LASrC7teK(^J#{w}Z*GRnFe^8JuPSxS2s{lS_^a`cE&;}FbMt<&c{2e_J_9z&4nEEcZ zU-Z}_O0O7J)DkR>$T8py?A;KbQ-<=6D<`D$o8FC@SPxI@ z6)e=WmlK_T5lsJUm$Vv2y3UG|psI@E6<7edWmXLGw-r-2;qr?P~i97a2 zd)gM?6Ripp@U8U5bw=aOIXJt>ok(vXwoH`DY(0so>xgS=g_4bM9gOwS4PR6bd?S?E zosm-mwVv^!D1C|er1o?gS`=~&k3boIf&90EgzSYj<0p(rev!r+n=sy3gt9Ev0_S5( z&S*{(P{P5gMQ>XAlCu5cvulQLxCPeOwvbcHp~?MS8Us=8P|8qV(1zjAc`PkZsYoQ10a_Pwt_2$6EEkpWZY2>sVBPX8eSG1oCq)`{2-Zbnx zAN!EimgA|7IQm+t-%cwk?Wr@6zX=Ci`Y>hq0!sd{$^rYpxCq}Y8-V|#bk$8-QLq4`vkZX;lxUH)l{k(MB9-cx-n`@Qyix$&e;uhig8c=$4< zhoYZJAJ`ut_vk-MpV_uJ0~w1O;kq53nUOd`O~^;~4O?MJ>>tYz`_~Ldsf}_~aSn%P zEg42Dp%kTbW#m;tUM)y&a#e?8U*b$_Ae9Wx)$k|IfU$$PJP+9+n@HCcC0%_q z(jp|J06iV)C0Y;pm2owc>Ezw4g0m1YPyalZ6IBx|Mv{ShLyAs)z~XD|H#wUF|(L^xt~v!AcSUi3~T(wLrq zYvjqcsLRNY=K0kpP?ga6PILTBsXV?B*lrZ;kb{Inmirk z?8tv=}TyK1#CIF<$3K z9F^!#ho>T$N|?sTSKRLduI2-l-*BccSdu-RTj|o(ah`(|GSTNrSI%e`g*Zc%m9Sb!Tb(Oy(0Je>Bot^g+u{6g@FO#P-m4 zlvo}wq-YayKk{LOOOti7q%2HErZ`bnq{9M_&K` z$_z!Bp*S|y32kg}p_u3Hm%#`|A zr&MZF_(Qj;9ue(qSk?J&pr^L@!=OqIYo8@V(F)7=-s2j<-r4 zSo%FU<4w_nL5a63!+WJCEzxD{<2ZUp^jpY5%^|lk8P)Q!mW>)m2IO?qw6CbWjBt{G zz5?~HWSQgoYt;Pb&~QA5ruGRm7_Xt>xQqWI_|M*l{`M-gH^eTVM~M^Yk1!TDSzgKJ zb_Tkd^UwxU6HGtG}9iIW1Ju%E(arZ1hnpqsNEMxOlpXFV&70$ zX>Q@zR`D$Co~^N04_ql}i$QJ99H zOdo7_#U2A;=b#U659~ptX-jGUmar}~$5wmH8^BUQ?nT-}+M`rsq&Jh!z_iwq)ub^j z9n5bG`$jt)k(k!nID2haG-#ix2aO!9M9pAvVidL7u+h|lUN=&*B!$8T6ODf*XzapZ zaSFs*FqR=mV@eR#Ly=0{w;!eo(7jcF^($IJf0NsiXxtDiJ@CYRVLc6$tY!+7R>6km z3~aayQk;>i!Md|ufs%|SW2+E`#^Kw0?zwyV5RQ^GyN1O z>N~({Uq|Y7Y+uH73u(82v;GTP7cjpfp|>w1xca7M=bIsmNsMWl0n23PwR(y#va{~PNMkoO4Nk8r(DaF@KFN4PgWhue5YM1$W0 z+WjRWjS(6D8EYT#9N$W~`S-9Tzk%JEsBk`e#`gMz?IffSd7p+U1zRO}o3t*|W=xBD zfn-JJ`!2_tNqYMx)CnipY*nZuYSbS$SZKXa$F!(X{{PiDAJoQB)IVBX>7_vbgeX{6 zBcX>TBA^oLdnDE>V7)2oeKkzA@EtV9S~Gkn_3?$Y!B%U@Hps|*ogo+6;7jg-FNh3x zJ@Gx$=GPTpI=|%Z*cyz~@sI+HEk>5Qk&ruM@r6%D$~efUQIIF(GNp&}Y^=|QWSkDE zM^-s{Upu(xC`rj0_$#CBUnU?R7YML5$cEjj{Y!oaGeyZq!)2qwNer>q^az#_1l;m5sB|GmyPh>Z{5BQUxuz z2l}TnXcJtZjZZ`wZ(IX4ke$#**Fk$LLkmk(whdN-Iv9Zwjmuc?98Wv&&$X~!2mJ=3 z@TOsm)eAjrN%REc$e&D1T~M+w)~HLSu8nb@7h)txG%kI7xY2prlgT?6=*rv!NMb>O0q5j8TMKl$;&FJ?_beIEo#mKaO z0ey8tLTO#W`c+uAi0it6iLqAB0oBDV#(%zrCH;VJ16z3?ILpWQKY}MPxxD!2)^kjc zfW4(}@B`p7=?Bc1v;QLP3-)KO-$3sUKivOOf*FBO4EI*{_&?L>l+{hW)?cY}}jMZ*f#&L+MS+sJe;R{|V0U z07ra{|3{Qy6qf{C6{E#`$31+*RldXhF*Y&R{>AzmHP)=kP?&!9q$@=|xx~FW|jg!15q$sN3D52J@9z{)#o?85g6bFf#B`)PUv4p&gjMyz~p4jTA=d zVO(AM2NF3sOR80~pbMhDg7yOH=~)kn@f?kGa%YV|jig4U7uNe=$(lY)O6!H1OZ~wR zd@n=rW%NcLnLJt4m(!Dv>+IW8zupdcZ82w`{YUhZseSE$zA$WR=uK0%&;ef$b@HvH z?}TMqVVxTOhM2P~?dY_wx5UIKRn4*07NZMl^~t%^3SV}6NmtMYDU5T_QYyn-$J7Q# zVSK0d=yP{PKfD{hMn;@)eA$e5)d|}UjR$ogv`2C5!4U{G^PO=o92XGr$MHrdJd>_? zLOr03B3C1A9|Q38Xw9L%Wi-;L%cf3!jPxvN$styk3~Ua}t^=1#eH1l54nIoTWr)e2 ziTPx_r`cG}#M_#W_cuq{o`D-v+(wpV|x{rb5SF>Mbt4_71!firLJb?yM_AXy)Gqh?ZrN!j7B{QreoO1VM%KWpt#pf?%A{5Wd% zQCR#bPss1b`hFJwzmRqjsYH#E%aDA1w;?kw0}=WVQiPI*%zR|><8%+X5Aprn#MW)_ z23$h!Eqs|bAa5Sx`+J4-d)U5@wO5c8|4LsZW2}EbdIGjTNR#9HeJ4p7e(@iWmj;dO zCw%FQ)}Dc!bfl#tB?T#okS^TH0HR!vZ$1;LnZW(0Vy_%5(=n%40ORuHU@I5pbAeze zf{y?>22#K;kPmcv2~tXdWG?~Z067Tq;2Tg5M7tIF3K^GXq!@sAcS5=nyaP@$#`JcQ zS&(MM9-@<4W|o!;(z%kuAvt5V|#a{Sdeo3pGKlL_}xihC7t zk20Lcf@fjDom%lPz?v0LKtP%aOO|9BpOGGOdQ$MoF@Bcg$z|YKGe&#@%6>!+pL`nD z^6-Wz;mJaoiZ@4ee+u3f-{Kd@@Q?rfKS55vg8qU}B{9x0o2O{gJdJ zza*!7sCA4le;2jtG1keF$$Iq<>K|+6Ig~h!>6FywokxAWf^DLd=?y^iKRHbq<%x`s zf8m>9`?(#d^j;yGB-?@AD8Wcdhw%mdj(Hr?=nb$9%dJ>)eJ3Wi#f+D>6D>7kvC}8! zSL8B26Q%n`e0f{ZZWC=z`A+1{3P|!b|7oF#Stm>6Je2quUow|7AqS@6tDl0rdFUG~ zgp_cg;FlqfadfCHo(7pfJ2Wk~oYSY9sCuRi$3#0ErOpJ%D>63FdwaAb!$xAgKmNod zQBTZ%3)d(a>5D`=XjjNW%EOM3eoUwJvm0b3EuCGEL#a&5YZH`U#5?ljQC>GhD?t2I zOXx3&4%@O@=Ki@Dt_Gj#(wm8M(>GOI8LQNHO#w)Gsn7Wg&EKjCjE)*-6l{C19Qk z-Cr{LnAHA#fo|^umh?9K1k4&S)Su8VWjt5L&S9*{Tj#@$*5vwpQ!qyt}@*N!Q%b+`%jUGN(xqrq)X09dBY|KY0S(L~UL>_SJJ{Z4X0@A1r zAvj2=>1Mpo{F)FBw_5ZBlZ`x5s^E~uVJ?T1m1gGFi(G$KQQ z-Wh?M0XQ~O$vMUN#$%Am@ez5PVsSV0nI-?yNaPH~Hm!6HK5p`u)3=?BE)aN0Gp7r#)UjpnT zV=It*ozI*R3s&HLk*R4tMrHrQzmo%fGbYAf-;6!U<+K%B;^dAxjrG0I>+Qoha5qx+V?4$qkQ?nF z)*M`8tl4L7x@uP4)*a& zC`Wb#jzSrO>@0E^ubE>}>K^|$CcTGsjz_QJJ7i3(i_lJ9m%hdaSl&d=9pwCjwTBYJ z`AvMekFoUt-|1td+{c`8JLrYX5$Y{4H{3(|L+rMI{CSBb<8{B6w6u&K{tBrM|7S+)rj3G@3tA>vw zy&Foj#}T_rV;bg@pSTH*!Pb^sI31AN5$Rlapr2@Ar=6Y_iZ)VNBBqEU<<-z?LH1Ij zT{`1gahz2L`So!G@*dL$OwQKYQaWQg@xJ-A$lgjOCqApX*oW31S~mE^X%nVplXgsp z_jV+#kMyPvLOMC0m`dKCV7%K1ylbMb12EBE=_{quF6D(*oiWq=BqWQ2>sE#`jD75g z;6|j295u=fb&t$V#H8AQ3}FoVQrO7IE=oTsCt!W@Q7egHVN8L1%(F1DzSCkzKDj)6 zEwmxhW|)O<;45+y@hyGFH<>Q=0BFhMm-Pu>TN1uYT2L6<=L^=T$Kp55@9~NBtvSjp$jY!>rp2k-E$tkk~ zy$SX=7AoYii7Hb988R3Pzj+@Y`twY z){x9JyZ7Pm37Dr=?&IlLeN1TFv>0L$^N=96ohdJAL#+o8e zBU2sz66DyJg%t8!PDL)u(f^FSCB|5pj$Q`)PUCQ9PCN@^YfZ%dKS?>Gu{Hvw>5IW~ z%x8?4$tXP%*FpXr#%m!bYkyqZaH+%;Tr;^Y9mjCkqZp;;C+syEOP-VW%7`p{3R6&; ztdZo6oQk8;o<+7-dd@MQ{Fl@P&%vKDljh@0bMa&x45*8c!?&>n?}Z+Dd{1O8T_Y_w z;4P7-b{*!MkV`ExIYJm^h08stFSO!qM(x^$+DMMmJ(%K9Q^_ys=r@qj$kE21M%`wM ze;Rd~?KPu}&^P1;+G6s9{DpRunqfv3`5WH~b;gW-%GQ$+Qpo}G55Bp7@oljM{el!) z>=}bR8Q&fwI3;0?tsi+Ijp{WVEN^UE*B9yhngR&K1^l+l3s7LDojT&`nv_}&$+z0w4E(c<)Mx19qjMVyL zL`P3-BA1B@;(QYHLo+e*8!3$g9gGe$F&d?~nxYMuQ@2R|{>{i? z?2pYDUpnFgQ#a)BSmql2lo=I*-pbVe?7{e$`XKry{|OC{W9-b4Gd+~)U4I$>3;18b z{~|`-)G+^p>2Lh$V|*K19Gf#LIG6O)=Sco4#`t&sOC`f3V_YzP1hql*^M46_5E&?` z$$f`E<9NTpL`^f7-1>xc>V4l~je6lXC`ElUql3Sew8UI~#IZPil{5>~BK?c)_t<)i zbsmoli_|?lM;e(WULc>#$2j669PK@>^(D4nW1ISJ9^nmgUn2b}?t$0LB^fKIE5Cz# z{Rg^-+qla|cm_AHb_36hdb8tLqkfHXOU~fA9mDz|XeX#erp}H0oz%qbgGO#2p8p@v zE->y2b#&X2^BX2|ZP24N7~!@)+d1R6*B z&{JbTuY5+)oC(bUwUe>X9};0V0(t_nl#_0WH zL_v@xpU4LXGd!_b?8%X>LTP9#~u!@-2E829E1$pgSK%mB;Frr33o%rG0H3%qnAVOEk)j1w4e^&;?vuQSD?mWoxWsvGs&4B^U#fM`?#WJ4!i>2uVvQaR?o-e@kq4LJnhG zw!_tM{|@+f#+k^M#jEI!H116_1AV#~3#%>ezB4ogop8S_MITCHBYH^xPIxX|@ubMN zH4J5kV>tv{#&N$>z3qhR;H71<%U5>?a3i<|9QDPd-!X5xME@QA{Ut<*3C*y3? zPq3vQjftG@>~oNJoqY^)_px`tKFKT`+u`rY-Ub=af5OCG0dX!a?e5XX)@NMy( zF^x%>NqQ{(ufImtdgin5#-1DZU}Rcu zvF}Di)Jp983y#Zv_bQ|<#{3s)Z8gfSLTSeSWKW%OG%1S~OA|Tb7D|2XB~m%&G7c@z zLEinP(ouOHvWZi#H4D;h338VqpT}K;bxJly68InA99iTY{Q8W0N2y2r)+$`xQmGF) z7kx(d`Pbsllyx}Huh4XoFW|8SL$4Pxr z#?d8D|8_|0T}U~Ma%3i_mW$d|j*mDtqR;XHlsg2yQXKS4r!eiunCc*;|9))khpa!1 ze4^KmB8~Vp>ZUGZ`>e!rVi5BeFD;ArkFS{RmN zv(%JK6B;uQBkDel(V?%0+s582c=_6XKSI{Ccvc)H8d8m{Ah88Uueco(L z^e<$uHV5O|LiB6t#bHNZ)`a$v7(g@dqC^6gV+?FV4kI@6M=c8@2g%W!rN4*@y<2*R zk~f*m$_~`07e?TW7Oq7L?IyL))WV0L#STKRmO7V6%o&S>5p6i0uY-xY{Ca5do1?`i zJ6bjL1)|aGt%Lq9W5PFZWjA z#@k~gaz^OtioHAFIGs?g2lniVE9{5sCD$4eq%2LmJUzge-xqx!db82uLCbL)q&oC1 z^jT|!i9X;CZd&S-sA-{QsTp)CwXhF;HzJTq-dZBRtKgYBv?gSiYk;l#c(#nM!iY8D zC=rZ_T-o%aB-cFAFVu^;V~ct;Kj{rR`cRCsL>5ANj`Hmrp%F2lPEaSpIdviQK`lg0 z%EAbVeE9V1O-EfTK;4VS*ow6-37QIO3~37?e+)H{AB*ux z)}lAH1d?K&Brg}B?V&`XhQKkhB_5P|0gmFQLhd?R=U7NNLWwCzXB#*i^Da1ZUz~M3 z&N2%{NKZnN(b=nu0sA%V}1?u$M`?RL=E*vyz%#_0m+!&p>|O7 z{2AkIN+<`?p7n^eC?2_q(5PiYKU|7P@+QgG+9-|1zhYhp`9-PZ3fbcd$)tolQsVE7 zZSsWcP|wLNPOol<|1e`c)t7X+! z)zJDm1?%*hC6_(9wP(P>O0S;f7$YshaxxGU3s7nqtfk8_QkoC@E4dXoj@pS58?dzm zsr2;Wm}&z?T}NQsrNx)=`{*;Y7UQz@m<|9ZK^8f3T%Ey~jm!z;OkfPmt452#X@@D;^xgQxxW@Q{ z@n!j)@{;oWa#Q&Mqu$uobl;S3$}#Cp?@jkiuI8TRf6W^#W=ov4k?j}TS=%$4yHHOU zAzT$|0%7|*@Oh!KEZG)$pyGyNm@-efK=oa;S zI`_#Q@g7q>-Mo5ujr2oO={1g072b2Y_4vGuv8gw`CRKN_s2%iOB zLp&RM9P~KuRn7ORzdi6#aKDh4;DG@%eBXMdxL?%P&~DJ~)xOmR=;E~X+@f9jYa-MJ zWwdghGG6(IvYxV&;;byo?jeq`jkMmi%(8T_$SrluCyiMKp|nQH^P)CI9gFrCZ7UW^ z9+oDTUMXEt`cKJ;;$}sQ^g4Z=!nlGeh0hAB>(lfR#f?i`OD~shEt_IUFjO!9yIgI& zTYji~Px*rK?d4v^Y-4@%Fv}%tJ;6s@2Mp{Jpn6}3onQrV0oz5q{FS1PvYT?avX8Q{ zvW~K!^1QN>YP-r`y;*Hk|DYMEsidi^S*6*aX`yL_t#+Cm^-Q&!`lc#abwc@E`LA-P za-Z_JvW@bVVvypv{0aChc4M5q%bqH(6eGn+!fo3a+Yh#6Yl8KZHN&d0`Pgn*pIWzB z+ggh)21{k@Tb1-JluwM`V!u5;J-ysKFX`^Pnw&p5uXjD6o9b29KPE(|7!vxi;;0Iv0tfom z^t$c7QddLQOxMQU=DyawvHMruWbFjEC$3*yI=a+xIp>_E>7a>MEmkHeKFQyKpK=p4 zaPeXTVWhQzIllZ>S$t_=>FCm#Wp@nMjbqIE zIeqWqM^kwwHG{T{XA09<=or z@`S0t96tp9`6aNu%`paChW^w`^o}Y*K74eFksXx%EN`pGQLIr$s{T;5RgYA6Qg2aT zQ&(4yRy|dgE8fZrfu31}p2t{dk`~A+%WvSxc`GK%<7D%lD%&>-N?Tv+pO!N7Zu2Jd zZSyws4D%lICG&gpV{?>cm*u%7(AwR4)>>?hwYdn{LYjEZ4xSX5m)s_MB`cKGkn7}0 z@-$_RTIuqSTeQb_uK=HozMDZ8njbJUaBlF0kXyme13LRx^b7MEqCM)m$?c1FokzKE zMsVHmRgqPrE=GM0zZ23RAl&z&=S^LE-Fa=Wb|~C`LtUITEmdQcI~9Qni~KO^PIq}< zS%lLg@qw+8wXfy5sfqCqLqX}K;}EOBa(xPN^*u@+lnpm#nFm<^uzs*kvbMI|GJQ6VHHJX0 z3^KQ|mfP-%1A!Ov0BeR8(z>zJH-N1NsWR0qoDaK9a6RI3-Pz6Aq~5ApqimLPW(i6*q=H3A+|v z9PwLJVbqR@;;^})krfsMc=+D%jq)EJ5FRXqZHc*5X?x{aRfWoTqi%!_sxT>ViqA=p z&brBN0h*r5iHdi4@_y*)zlY{63;qojk{^PciHWj#yI=?e>P=GV&e&1sj_Ipbhz zrglQ%%Js^g%Ab@LYwpXQ*Bf3ldp8zE>5?( zTT+Y_%XgI3FIAOz6i+VlDk|4+FREMIws>;!#Ny9I|L8Xst}1AsUo~%XZq;1h-0wNQ zxes$!HLyg zMX3dU<;(J~(NqM)n%ya zPnpiH5whSKf7IN?QpI}J`owlsXd}$Bt~FOMT{F0pZYjzyd{+>Xe!UX+`lC|YM zEknhx@`dVfm)ou<-7?*R-E?k$x?R=Ia*y&l=-WN;O-MngUu2b-SCvmz%dR=8j%&S@ zKg?@Xt4T<+Rn2ZS{@mbBy(%@AR{9>+F~lt>IIvrwIan3`B_cC&Y)nv0r>KeHgDb`a zXZwBg3~)QBIwjUO{#N{Jeq2_U^lz!xQs1VQq(rBVOP!N)B>BzvN#BKU{@AS}(sY+RTRyfh9la7v{gsosioy z_kGTs+`)Oj@V(#1>Yv@e6n$y>RUhvfKkjqSkFVaZ zcz5mXrMKPRH~jQ8zBFl5>Vxcd`j>{I)&wU%b(CwV+dJ)E-86vm-n!OzUE{jjEkvjG zxa1Y&w>I!r$nMY+5u2jpDkWBaT%~!{Syle1WQ%$p9#}CsaJDPVV-7;J4qBtck^m2}vW97pASql;`#=e23cp+#D`| zk3v3Ek*fGdv0pJ)p;EL~#43lX2e`b~F7sUKs|j2bd^_Y|h2s@}2%Q(I4yzF!9r-LO zIObiYtyTW2YOCsBO!sQso+o%-Srr43yg&u_4z?%En3W4tOh@cHD{ zLe*7v8on$8Wxn#kido86$}!3Y@@=xS_G{K}xF>yqCMzYm$Jcfrz204T74hQK^Ow)h zJm2*E)AOY-I=t%l=JdPpPwl>5OKO3Drip3O5$vKQH8W*UN~M(8lpj(XrgzG^n!88e z#IVfTSf1!S-@TE~jDR7yZbL|7h$T2K=yKrG0OtUc|KWftfo?$+LgFjT34I(Ugy%*a zj;s<@BeGJ&^{{Rg6NC2q>Ae1RtE}$flx?YL>{XhsUsUK-Xe&rAJfZg}dQucu^rA33 z|61<-td{9rlCLEG5r6zsg%6o;$G(Ys)8cKzcRfG&eA@k4h_8{j=UdH`Gie(#x99B3 z`zybF;rYVRkdIyRBleU6P6}_ zPQQ}XAh&11%|dJ8o5FJitqKhJ&GQH3uPgXg=v6$vY>lb2;GtOH(#+$Y&z8Us6`qIg z3X2Or9^O3cc*TT}{Xy>oy89RV>V4F{ZTtrKcMA9=@NrOdaGl^a!9ND41kUu&^zrkm zr9GvtAe&-+WvEuPEBAh8P{y0If7uLGV3JZ89?v`5_@xOQ{>;<`jz&%M6q z@7_~<7y3>0Q~7!N_VwB0?c;sY+v+pY@2dZ(!0Ev^LM$QuDy*n@E%b0`eCXux=@EA# z?2)@;mR9Lh{c4S+)fAPzqJFP%-)FJQN!dZ6v8Bvd%{0y$E3^3hYun_s-I;62@Qkgk`@HRU?{33FNL z^uqT!eKTLCsZ$T7G)i?&b54JnF)pht>vWcPcHQiNtn!Tc>6X+cDHFc$NIH{X_?rH; zC$|*s0rx8&O*}_?w)gb* zeB+VfF~##2FMpp8zQ6g82z(b*E96;7;|g;s&I)ZBb}Q`X@VJQDQMF(g*`OCS4$KTcIrmQY`Z41MTo)<4G%_&`2x}|h~ z>F$#C==YTroh|NBYBLDtIrf&Sk*@tb`uQ#j>=Y7P@mA=Bu)NR<6(@!G2AX_ldOz{# zr|YVHt=*}s<-X9PrPoj1>E4@sPW$}nE%S=;aMz}3_9@)#mCb!h4TUrF`k;T)E#p@D ziS%X}uQFcX|6}IgneJK5v$kfXWi`v%p3yKpJndFWU~>F7Ytr~6OXBgwElIxLTc%u0 zH)ZcD=vuO|Jk6}PCI~mgiK13`Ykh5LXMSl^7}u5WDj#p`W4>cuAfA*dRkJnCU0mH} zYTszjX=iJjXsc->wY9YVI#K8AA$kn=obPGzNO7N{Ypgxy)>_*_ch~*6SBzipzyl$V zL&rt@8I>8G8C@8CF8XLxab(*_&&d493NdAsTU8%jtFY$aYF8>fh+G-6-RmFMGwPv= z6*7g>T3KUdM@5*tiNas`6ywY`PG5v?W~VZt;BIDca?K=pV%>xhiM_w|Odg!#{QYc_ zAu;u9z}Nl>{gQel|D5(Bqkq=2?Eblp3#Jxc(8rZ7DEBqCG>$h0o03d-&6})$*n6pF zy2iQJ^S1ik4qOv-HKUj0>?&8zVcbeZ@|MmgL{Ga*_ z^qt||$FrJluFFT&FnO~0-m;;*w4_o|Y{9g=#<|Ty;bfEVP|DQRG!>kU&U0MuJ3rQR)il?rG!-=g z&YxW_x~?zO7bHoq2Uy);M2zcGS5FQ+#)5S8LX&tO_5wAiuBpDj%a7 zsJX5lra9!?$2nZBQ+yB)TP~G#EZmj*YgT!B?^M6!=ie0HUM0FFwE6n<>+J-Oqz&I2 zrv+wS%eLgrE(|S7DrsR@Xt-{W8|B72$}o>uIC!}3%cdH%{smAt@|sFex7UaALu#Feh zW%FoDTkAaAKY~FFbs8jFEuW<*RIFC+RIOBR*L-w-?0Q1G*!_*iEzhA|E4+?*j`2{s z57V}AlVN1@((S2kqNj_G-mhNJmy-wxp(V1aSgVuNl>6*K>bNN-%&ZU}bv`eCEmRpkRMAvlZp3be*kDVS0 zKDMpp$wl6Uq50a}h1nOfzGeNGxgq_2`pwjt$u*P5Cm%{qP3e?AGIM!$aPGa_@Pgm< z0Y!C-Y8ICl?=5*(@~pUX$??*!W!uUF%&E4TviFJr^+%WM+B)tz?*1NAJTALW(j{t_ zYHzq5a~lStn}2izJ&t*f^!m#y+}qcCkXM;UPxq_Z(=Kb&LzQ&_JDn@4gc;VEmfGeG z#uJ7yrE`jPMG1w!6vXD2n`A-V|M$7hl$&^x`GDX>;(y65nOWn(UEVGv-8?KcrOqa}_))TgB zVsCgtKSJx7C98z?bBW@%a*?`+v*7Z??T9YSRdU^HpRc=(; z7`-@beaHv@k6xQS+PS+yv$sTB&m**Jiye<&)>uAgqOHo)z^i?_>O=h@Cby1a31qdn)|$LpfckN#1CD}&Ak zYeG!H(?fm@QHHD!>0KeY!q1`m!e)h^j+hY{7qvY4Q}pZTM=_nEkAznV*M$xWE(%!X zm*o4SUy{!YpH+U-ed~Hhc-QoNpeuJ_mHV6KGd&*j%lmDb{$} zP_y(xQBQr9!tVuV3icK@D{4_ZspLuNlCrsmTIClFmkmaPx8X`zT-l}4jisMUzm%#C zpUQ`uCR?^!x7%(C-Nhc_FmOx+!&10fwnKhYkpMkX19h3YGIZ5*HDA?()%R3Wm2(wG z8W#?qQz@~XEA-J<)vwTx zD4JXxT~f8QcUgd83Z(6Q!%#zc*~7APWp~P=4Bq9FjjK$h=5*^UL22LbEIFTK}d?{nU$<$qOx4U(cT`N<75799 zI4kanqs2JUis%P#!Hx0=@A$jmC(O4A*7nw&mcEu6mIT!86tlZ!m}RTwyrs=E`qoQB9}E6=ODYxb#|YXUV2XyUw0fPot@?}Vv}%_s zQPp1ER8!HVm+MgUzgxIAajEEh3&%L5S*!7N9_eh;EY*~&d#V#u7gg(2_f+-O)zlNz zSJk)FI#_S|YF=raof9>OHH$Rw)Dh~%upC@hc2N2%%M{lX`xHwReH2X;8x(Ph*@^>- zi?F)vR&-Z1Q+$vQkzbLGhCl9auvw!<8Ln(qro215Z34r`$uty7%e^# z`U(MpV0&O2VCx2p&kO5b>k;cX>u_sLYftMyYYl4=EOlj;la^kV`j$vbE+o`$j5cb) z`q#+pY2IP_-L%To$<*95(X<_QzWb(1=27O^=Dp@GX1S%NCEB94q?_lNW6X2Sf0{R% z%gt>p+bnuZDqMd*Ux~gJTzo@pV{=}QQr+Tc)R(Y%ItLvyMtE;FhsheWGsyas9O5IsKN<9|u za3Cx`{nUNbwblN3b6Rz_Dj9ojQLR)>S9MeUpwg%wKw8{X-cVjqo>gv8u7Ct_ReCGk zm6?h}#baF0SVbE}vHXO5y!=PGU1pSJ%f8F*%GSz8z|%Sf2;hsb_il%uZbzUyyudN? z6?mhGKv@=m!R9J>bz)(KunL(%sh}4MgzrL%kRiMf{u2Hcb_<(?Gr}QZyRceVgeTrx zs4vtKnhTwTF<7r5cnRKu%~o!+L3UigdokLAg>J$=;ihmy*e^^H`Un+;hC&!*Ob4MC zj`6d%%)DOv0sfg`IpotZ1%M{5yBqWeF306Y<@e$YNznWT#{g!O_1!b_~q@pJ4I& zL0(hdQ~r~DjeNO$iF_5F*ckaJ`E+@Ad1ZM^c^i2<`7-%I`AxZ2Q4?=_xMCHwbnVf; zyC@za{e=8BECk&ZLlgrQtrXQ2YDJ#>iTslMko=bXnOvsuRaC})%@w_{k3aH`;5?J% zqvf4(^aiL?u{i5id4G8qc||zBdtvS+Z;ZbS-dcj}U)c#<>vq{j**w`KSsz&wS#3n_ zQ_9Mqoy>%Pdu3S^;_NktFa2w<7o0?0UIjb-d?0~_xS#0-RI# z5o!1g_?q^BZ|N-de2Qb-0k@UXo+P?}FDpq5w+kqh3yvgDAYOh4YUMD{AoKCPZUk$9s?}+Gl5H<5A@Vg@IH0NyE=wv zM(-nfaE%Agm_PWR(!nD}4l;5))dYjo13c3sV7-b4w^>`DO&j8UCyPGze7xZ>`zwj1 zYa!n037`bh@U1lf!&;_T6Y_o~-uoQL((#Z9dr%uH+dm-P$vzY11ia}#p}pvdD9Op< zFW}?idp`j#D?Rop0^8Mju@bnc_F~D1PSIeY%0$Y2yy?|AOKUM)T!tF)T8xEm`V#Oz zDY*UvVsraD^Z@?Aoq7O?G#|Z&k-+?3Lk;SW{oi2BF%I}!vUlzWLhJ(0Ku@SN^c;qw z)_USBAeiiG9i}2lC z5PwU4lHF4@WnSqUu^`w(q{J8;A7h%CJHVn`B?ZYSOjmeJKlAa=!PTQ!8H%WySgNn z2=ORWDyYR&p`BO*Wj5lB4MjWT{Y^;wJoFEHi9bnk!6%~+xI|16J_3g@R=6mfM;lWI z_3*x!hm^Ny(eDWj(9cjq{!c-D+$s8ruY_LWZ^A+0y6Aycs_m^Ji&)qG9XxTn@YZ{Zeeskgiz|g0!Uf?u z?kyMCr>l4dm+*GF12Y#bhKXq71b-n{SSeP+8{Ln7lL6-w(VJ#_dmQavQyl9@F$mv^ z0yW~Scp2w?Co~aT*$?75W(v0jf83ee{#I0@wOowea#g&o3Akq?-cb+HMJy2Z;=W4I z{~^m?Q+!FY?d`={!eXJ3a7+jge-l>&`Fsasr(MDYTL<8?TH^|**j>dPc*?=TNud*- z`+VT`_Myhb3a@RQQ4^enA;Lwx-KY5e7m9;~KWv9>wZ$>0;cM;H#c{$KT&Fii1BdaR z(jdzV@t)=Y3HGzCjqSNL+*)YeW77i>G11~*ILGm4)lTBE?xh+a8I| zPF?Ne#eao%HiK=jcu|n!9Uc|di5YgCI9ymJ`iotijIyD!_VQOwV<4#}I`sj9IYL%0 z+pKt?h;`Z^#tVPi=87MIZP;eND5DqFQu%y)8*l&~!Lz+Di^X++bh-(-aLv9<*dkcO zKY_AYfS%A!Cs7=N=iUO+<{$`p#-W{g4U|Mx;7WE|4>=8&wYLMQCyuf|7d5D_aTqgI zaY_(J;dmd#)o6WmVm(N_nZgzO8f7o}8F9M(ig40)#M)ALDBGjFswlCaL~ZzJOBRdZ zVd#s#LA>mee1&+?dSAS4UxNs`7W)?2GjX@At$iMz-+Nmlv7^&$S#$8@yp;v~A4gXK zR@Kr)&&)aJcG6e~*xiMl*sa)s9nVhe?oRCP&gZl9na^&;R^kG;?>+f1{*O;Z6u6w3 z*|YcBYpqTDDs#jcV7opS{c=1eAPok`kHcBGrz|2->^zJDI4leo`pZX{D0UhDgS5qI zYK?O?UGZU`jFoE9JG3!azTL5!t#Rx4NPb2NI+QYIeb!&iI=3)@(Z~w?L#bd5jB&)m1^Q6u{eP5dkcD!2NmlBRv826POKu?-W(@Qjvma;0szu$ z!C-I!h00Z|ET$kAdnm19rg3ArSal6pFejyvN)%I<4#3}a;sfyC#we4?Pdbo~W6Z%u z@=zvJt}6$bmvlD!n>14%p~Zr#O719i!5Kj=#&cXRHC$Q)#u@auYzy`Ta@W=BVzC_9 zntHI#+-gm)hsBkm5W$R1=1^G9AI^jBVyUaZKhkWaDa zG{qTs)!>xYVE?AYnKddecal>Wr@TqYM|*%znxXzt#*lgxiY-L^4WtyCqOyq1nPe`3 z?=qXgC$rP(US+#Tw#Bzh)d#Xss`znBB1J_N>Rnn+BLV-f#PQ68M3A= zOgzb?H{jZJgJh^>@c#D-s5(vb;TP(0X2xMU7-)ycyg3UAlsN4%09Ul^9sz`k|a-& zyOSi4!g7R^ppIiYvrB>fbz;|$9J#Y{nHjI_RhQ8jcsg~s&vX(=M$S`~JXOj`jbsz) z#15oQ>2a<#A4Vsro0Q9PAv%Q}iwORTImtiP+`;$UD32G%$=T`;;-tq|jw?c|Dhotb z31buZc(w?|%wx3(v9J%w4&{fOrF3CF(s@8xijw&<%Y<^BXmRDZ+>%sdCr}N$4s4IZ z>?6`v3I@N3jpXVyR+Gw~buJyuRmE2; z2V?y6%6woq`^0?mXJ$M5FMouKWEYT?%1`+RX5Y;~RXl|bAty-y^T)OVVg17n;%2e4 zsK^wDOYI?ihXj&059kLn+(BTOcR?-rj$Y9=(N3ZNDvQ*;q#3QQ&J_E~Ll~L%;p=k= zY#TZnUpYUX`V2L{8U@B_3-oVJa<{;VF2a_gjmQXQ95Ro5j2%@n=CLW!{%{MlbyvLK$ zDDm=AV0AOdD>{QO&W~lx>OvyoXDvjok|a_W_1_D&5!Ve>d^(#&>Z|`SbICx|6<($l ziDgf)TS+PK7gy4!(75ZkTAKbeOL<0yuq&AEV9jo)ujyQNAWt|00iz$4*Jq{+Ex}D> zXVBktC7nyOv^o2Vj8v;L({a|z(}$>EPOEU2Lw)iG-PRubDXuq(M@Cc;A3F=xe`k4u zx}Dw5kJqHIHK5?Gt9F+DDBak#TmpBGxG2 zZNnu-q|?9+H?dElB$>+$CKKrfCQ*G)_5t}a(BH^YeCkYU;}o(Lb%j?g&6U@#)NG)y z@EtZ0k2(z1=RMMo+r?gIUaObMMeNawz-XQ^-Pt&Hs& zXW<-IB#$x6xC{~KS9+G1Fby&RE(~rmhbA-E6djPR=gc6soMx}46T4ZtCXJ9!sFO%0 zQ=QBtkC}$*AJs_RvqCTiJHh$i2Ffb~?5C|}iUWCXbcFUj_J zZQGR{s2MQr8u6|yS;+K6cHD>gt{jzX%3GAd(6sDh`hj7;MF~YsU{lx8YV?@W8SC8` zI7Xt{m7(etWbD;QdFDOPgKfx*kI1{!PGEG!LcxOx(~?e}ug)eb$VEJ>fP5V{-8j;h zyjDue_vGiO6a>;3d09N}g|EsOX!AxW=j3aM3r8{$<3}h0O zuIg9h!Ofu*NC!GmSgFrw*oh<=`gY8sWHNy8UQ%Nbfg+HHhQs0WG<4?=fmRd+@2@Yl zkVtj1VpI!5wbKXCcVg2btfwAcy$TGlELH_@3>I zA&a1A`~!|_yjqdf0tC~{fSc&a zD?F!ta(AFjyA&SSR~~*f5D3{7bS8!?T~KFjP)`6gnv7h~rZ!ft$-hzk&P44z2#8X7 zg+)E$kY_46>Q?3$KF$FhuqoAmAEzkU@<3FNb(mLbN#(iJSe_3L{!^&lJ|c!>FbCCK z)aECY-$0b|=1iOM+P!K^Xc-47S5Yx1DS5sFZGb{1<19re)#UARqRQhr^uRpX?n)=1 z9WR0LG=O_X67GUhL`7#p01EJ4NrD0{gV{$i$X?ovE=AR~Ozp)iBNfOP@)UiBX(Uf? zdyBE6irK^VW+}UeCIKhDO}^7KAj<|s^0w$~-e4QDV`w4zo@TMR>~nS*yAk;HaNy75 zaksuk#xa_$fUdw4?gzV&ox&3K6wv3LG{|njebAb+K*_bFFg-yB(5=WEqYzPt&^P!Q ziZF|jHz(kx>`2DKNoyrMNcSNV>j!6g5kL8T&`l^60Swz+^NnfRf@_Jytm6e0Q|F1|}r6bZ?iI*EnyTrHRDCvasmlP0_rIJ7% z-a{kQOS~q|k+P)K(p9mzco2Fotu$HMDL0bCrKaLS@w!-EG>ZGhU1FBF5H-s@>8cbh z50w^)A?QK1lDYzI`2>t(j$B@@i1oHATjXKDA8e@7Qcxon!%wmkeUN|QgE9*D?_ISd zdO=G`2EE2E2J~?Ej3m7{`^b+D*q4PkWb)$@w+q!G}Sbf zG=K9u`3{=u+UMFmx>@>@`Xc)L`1MXVTDJ*ZX}xZQzNLPtuApv|u9RVfLDJRHoz}(Z zOX`2=cIhH@N!ni86X@*KMgO{vcCDtn=8xu*<|&4@>v)b2=N7Ur*`4S*iS%ER3zUD+ z|J`fKK>?huZUZKFOg74kq<&IUDI3pVxwH;_u~}jTsA?tz2L~4h=LZ)Br9hoP*+9j> zn855n{(#%xB2X{TIxr{@@W1tM@^|&O^KbAE^EdQI`!)W<{^$NeXa|3CiP zfraRr9{11m@9-B5yb81mP6>7lrU#A&HlcS{FklQM1nLKxL!&n-_+KzB7>StshI`p?XTvqgcbM;{px1^+5V4y8t4z5WNsi0n#ZJI9rQq726hG3LNjR(+JjdDqXMe~ zn&9|gJ3%iF5f7lkjFzZ$UYss27srdy=*piK&Oqrk4IiQBSJ0%Y`LtG0fT0#p<^N%)iY;&1+2-lg>EL5M>k% z7Q<2f9es{|kG`|+koIq_N3%#{(N58{gVwJSn~45r1G+|?t z`y`y2E57Ua{k88L^sl#k-+bx5IR7!f+g~E7&47+g-;y1j=^k>d+za>kD`aJb(BnF;tWoL#&07T&x*;OdOZ0s>bCdZMnpN6ox@-DR`c3*);9JL- zu9`B8g5jm{n5n<1yfMnK+)&uG+7xg48#7HBtBe-oZG5aAc-zr9E$___%{?pwEM0JJVoXoLN!?~FYy4s; zWoV&)q3ftyqP1ynXtrwBY4nk<)oA1GV!4}LJSpO2}6W7!lPhRa7SQwU_>A}&=_iDUErO667KDF{{Q@; z0ny(K%%}H(Hvvz;8z>lT9;_c68*CE{2{sCrMUGGl{O3c#w!#uTxiw-sZi~rMD%PU0 zTw3l14u~u_!DxW(@&!b)7xF5s$^dzw%z^=OS-uOdjUH>W0iCg%=q(*aWiT3?ko!_0 zXj=D3$vEk?&}nHWC5iWh3s{xL!U!P->;GPCBhDAL3JY*^@j`Q~Tpe+eXc8xjy|6mX zuqR5%1@QH%z~zBO@BJ&>II6+_{{fLnb-Dzci^)9}f_ znD#*XJF>)+@W>K1ChfmiiV(@RqwO8Bq*R=y{{pP$8l0RLt**N97HkJGP&kfFGL zI;(e-Qpj-!$yH=G&^M=aSlWSKeWWGWR{_MLao~f%&1f_Z|cf}Ft~Vu zM5IpQJh8d>LbxxC6hDcz#j|*wdxT_U<+sEi!F9n4LKW#VvW5G?6!DB$N9ryO7PH~S zphxdfLrl2}1hOVM4Gv3RQk6DgM}h-$0?O~^o%F7d}Syw0kB zqaUEp)cw+LF?`pb)-4AA(QI6+@2T6VYl;kdr|yt$ntrT4Mb8=bK?_vWSlhVO*w=X9 zFbKT3bI|y8)62Sdy2iTE+F`(z8)}yD(R?w!6teTyd|kdNUxa_pUEqFzFSQ8lPbY0h zOVDG$BFmwQUX1Fkpn3r7wG%l`KBWqFY!K(`Ga}76IROmB^HK*m8LSk?pawaFZdW&< zlh8wm!mpdc2k5;Ei}}Q!BAyUV-~_P;l=X`6PWT70?T)ZbsE^Nh3O?=>+&{g=%c2># z&jo3r)CRq#ZBjgZD;{A_^pPgvNw$*uN{NzQ3W}StntD`4f_Pk-jm)8?JX@+GZIXUT z0;-(z;tR1Jz5+#Fa~N^08&0Vr92d5UamWHXN;>JL*ax-KXPg_m>=lFJYU#XG4WD@v z-St-Jv3Es3H?Q;F8g=hcbQAx8t9cdJNg}Pr8tF%1HQ7K|3bGezK3a~g&0pi=__y3& z{5x===j)p2D(fCl&0w4XW^&pEDO?p zX$&gL?vhXBr18=ssV(mQ^3nl3+jPXvMD(XSO9@hjI2CKRQoJCp7MF+{#5JfUBcw{m zzpCS2*@7(hl=L#VQMf42RwhW#g>hnORHSp{UebK1pc_hwxPeU4OYv_h75hh!AL8?N zN_X+Ux5~$G(g#Rs_>PBhGj#(;v6|df$`tG4BL{G^eUlnVtgHk7V7D|-8jc+MFZqJh zRq8Db1P*l+ynst!-jh~KXfHBN8`fAcRjaoSt_S5BjOpb62Wfs=S1tB|H?shzB? zrtPkoq3NdGpgXE-qn)kUj)>P^tJRK2Cf^rK)6p7=j5~vygq7>fw*{Lto`1sk` zDX@Si1BnUB-{fV8RaJo)d_W~@!EO!56G@Yb$tIbXU*X1^A+1C-?Ep^^L5z_mAf|e7 zd$g8TfPcCUE+h5v?=d(VJ+Mj>@#g_?BdVCaVz^X7`T!Im31=}@Vx$#_!{xCfTwRs961Qg#z-USFWL{NYb=|OeSth=G5eGo$E9#Jxo~<65zWf> zW+1p^7ZsqEu=gRVv_|oWPedn9;0$-SK&UNCu@NVt_*M)n@O#@wxxnApM=w`yD9O;>aM3{FxFB|o|>H&A^PQQpXXJivfAZct1{V5voXl3}xPCxv zYXijNE@JWlAV;5&|NSEt#(DmQGuBorE`0-UeFP%FW_;#VNtCAGgk_1>q(!n1r%MN3 za2QaeDN>UB60dACn5udC|3}={dbtw#N^2AuH^3SNu9@g!YVqfPn8Ij5{o4uH{Tg&ccquCp%pPRC?*|qF#@QDwzzcD`M9Luqr=t)-MBKaxkMYLgG zaWQ-nSC>6Uzp{;hMa^QrV(+x#u5dZP`ep(zSqK*LJup;mgcsZ;c+dO;m$wS|=zHKw|Dh}K9Q(E^(+nNR&%n_4 ztMT9>Whm#tLhJ+1SB*SBHl-o*jRd7Gc60-vUuovBbJ7d7TGK#5y%zG#p|HYY; z=(kmaYf5S6CfSTG&^*lH8Ul^L0ooJjx|2DJzUKkz11fIC|Mb(TbTipQM&Ud~u;0+t zw4pO|g7#$h(+8w9ZI5%^5o<;O6idhU4FFrMm*Q4m@yXg8y_#S%WkD0{o&G%3n$v*o5!F_gx2u*=?|2a^yvr ze)aIfod&6s*1X zxQWXvJ=GPs5t~aZl@u^?#seQ&txN(#rL^=&xFB_7wyHg)SK=8t8m!rG(mSyWR_BV^ zA2Ifnss;1ry*Ngyg#J>Z`bJ(M>6AI>f`V^}3~32Y+;E0sox7{&NihOmh+Ld;QWIg7 zu1Xqn3JCWuoYC^k7E*~$W~^!-uv?O7Pv)Vri5X6(ftSQHrO6KRl-!5ITOD+Mzo0u+ z39R0ZbT9jW%0P(wlT)-m{H`{^G3_!P%%-3!YfjFSC)CKM!aHdkdCMFpi&>T%K`Vk| zz6^N$9kv3IRZ6eoy))>8YEkDOV148_`g_QS>2cHyZZH#u)5}2DUy)OYzsqqyjwTD~ zBw7p|qVvoLl1o-$4Jpp*8FCG-J>?h^JxN}IakhhLjFqm(>_8M=2&cFM;I&3E#mHne z1eNS(c!iHeH)N1{kXfSMlA9?t!A3fzi~^tF9l93xY5vz5Y3A9bN@L!azBIwbp0 zfdrHh%t%#`#>r{u3mpdTt|;qN0Xw0;G8oLJM))fKDpS-#=#P^L^ z_Jo7v4Di=)W9BM@Sb85mpZC!5NkiBB2R!uFgXf(NRqix!_%&dUaX4RLBqW_p@N zkvQ}Zr+~?SnmMFm&L3Dt2bs(0eePrj5=`?(RIUOHy(rniWMk!r(AQwczhkbU$FYli zM20z@*^cgG4fL6RF(t@otkf9NooLbfDgwTLI+F~>b}pF*)<=Fut9E1#P?b(#+9{ZZ zg_ByA>53SmpsOk4Gm3)IXF`^93$N)6?*BsIhp&ZS*F{yM{8kHMhbiE(-3QNTJJTDC zQgH3jn`jUIkPZNbDb}Ia{t_saMV$gy(T1uFM(hwU#J7Q+w?z3QO;qwc zPbVrpz`{I-v-~&ufJy2GaPeGVh`z$#wjej1rMv>Cx;(y50nDxbtd>x!$e)$1h;tOA?UPy#!uD+uVbgY6kLsG;Ne$R6R@(~)Cpj;Cn)o!H_8X}psUF``6n13 zmDR3tDR~6A6?2%==z-M+moJV?0PFRW+M2Edzi|ps$V4)dv<9>06TW^p{Mnm;|91e< zrzm+&TB%>97&V*RBKdGHJp?KzGBT?WMs5kVTO;-Z$x+5(CttuB9j<;-%0iFT87`0q zz{?+x?(bhfY+jR+q>Yjw^GpgYM#rc#ka-nAR+WwZVp+8=I&kg46boW6okgec0{D;> zz{2xd9%5~!0rcY3*4%^DhrHXRA$S2)G`!~`LY&YyA!LUWss>SsOLye zQU^M<#p+|+Bk6G3qv(iDU?zactye$E@6;J&H*TE2l<7eEwxU~E6n*%f==*JiyX#z3 zb%WHpbOd=R_m`5?dic5i#rlPVAwC}+dNUcLbdnb1C+N+*K=i!=jzfL)qZ>jWup21D zR%N8p0jiH?D%gl%Q-4(6qq`fUHj*F6{fLhl2<+F7C)SMl4?OfYaGe_j4(TlLSG%fH zaW5ZJ9XOi?1@6TRs|twn_t5p1XqggGQ9D|c}t=FJujD4i8dOT&$IL@f!nb`xZc3Yhm>!L2{2Zo)da z!0X?D{ILQ&IIYYo^f~XVT@ZOIFa$h};i>@)-^I9NO?cI%NjQ6eX@IrdNHlaCc3-M8 z6y98VwYstax7}UDnEa$46c3Hyoi_-5z0J@JRcHQ?N6Z6M7mrXQ4N*R*qewTb`)M^F zSp&4Q377*SX+^xq+eidYWCNbyc4R@AX^j5;Zl*1!cz!}Y;!x%&n6-&p zt~Ba_732w2rtN^ZEJEyl1>Q;*@`ZeWd#f94i`!7+{GMV4lAMe2!IfRi69Vy}?SVI0ov4Z7XpTy7| z%ypa>O!8({lh5FKB#`~e}FC6e%!MUIbiBX;MQsyMGhdN-Ry;JL0|`W?4vEN;v~>OH1CnTJk$0(pqj?qNKnCm2@gV76aGP78)LdLpmD zE9{BBd=i<7Dl!Ee%{Wp42v1?!ioj)`xr(npU%ii=l1_g>VN@2e^Av8Lp>X$ogbx0F z^$?Sbr~gj9gngT<_`n_YF~6k-ik?2C63%N4r4_i7dbO1-sZ;1ND33b88MPdu>c8X~ z`48Hlreqgeg&7V0=63P{ti0Vg^>u+`xUg&LfVaF^jeJ9Ac@A%msF;Ah*m_=)H(?|p8H|jB4sD+NhjlBiD_}`?Onom7K z{y~m7MJb}ZR+o@Q_&GiRw>Y7^L2SD&N1<2t4qT+x^cJ*y>!JTyMhK$SJ+La?(PDff zGEQEr21!F^r`n7ZVLr=Cq$kQ0MB(S^0I*$tQ3qF%OqFXgeb}E^Hx6E(8xVJ&l4h)* zuEETfQnVXw%ND>hwIDlOh3erR-o`@>Gnz@GX>=tRb-!gZQywR@88b)zuC%2IH1Ec^ zswR^d@O5M+R^5U7uo?Zqlvi6R9}uY*(+^A=M3b{jQ^cnGsvla#TS_%ViMNOz_0->Z zf?vTtECEFC0(NIIw2Eb+;G^)RoJ{tE!{|T<;iKA+egRAR3wB2z?5xdTKD`8c_%s;w zTanLwAggE@oC;BCOpjB{=tdm6h)Kdtuwp}%o9blhq%I&)d6SEb^f|ps+JcdHlPLf< zfI3k9_M)kHm0!th)=Y~s&(!8nc0N|GfUQ3mH_16LC0n7MPlYnz9=w&u;Qq>UB3Xx- zykGH?+i|nqCWF|o)Q#7lMK@tjM=L}vMw4mY?4)l%=Jt|*)o4_h{h8%>MPqQ&%wrP4 zjx2~+bCk>>=aCn*WMau2k=-}&Y0OB^c1p--H7%TQGGN+Y|T%)v+ZdFCR&$cr(hqSqx)$+vL2sz3+HkL z^4;#Jdxk&Eu+zkO)EEM|t50!%JqK%W0+WDx`#6x*`(!M+0N?8k@Hwf+3_#ZjrwbfJ5xZd{ zytDJBVJ~N*fe{3O9(=}(rGv~{+>_{@-SA`$*clvuMU*`*HBw@ z1H$nUSz!vZ8*GUpSkdn4PB0He)j|g2&VR1%REuKv`W(y;fYJvndn;bYW3>#P>O~;X zuhhrN9k41Z<9u}lPcs%zEsE)Ye!>Fmn~FeCINaTD)MWfU3+%VP_Y+jStZ=0|a@)(w6>!iO;LokObZytz%T0DFCe%vOi8LV+p zwc0h>SlwT`Bf87F3Gn492zQJmL!zOxVXR@TVX>jKae(oh(P&z1{D5BkesI907Kp1R{?H+2;iG zb2FhZeFxT4NpXu%8!Unz!Dz7GVuIfSTj8L$6rJ{7a2M%%?1Z77J8@5;6U_uBhgK~01j3>*d(RImgp9}5$~W!Fd-M< z58g_+SWuLN)##O{2|I-naKtbQcfiCb3Wf_8+#OgFXcZ`d@9Ocxm&xD6Z^4@#USB`p zqVM$ueDlC?8tF>}*Q}avr_bRNeX@`D$Ae{dz;6r;44erR29szs99$-%pAwX+qAOS# zQG6lEA=j~|+oR%;z`^gvI-wkD!MD?JaE5OT*3ckBTVp(YsdCNvtVM0b?MWenLNA1= z;n@)tA}2+Tk7T3%M0SYeB2R^v#Ty$oEu^|V(>B#^vAHcfEMv^IP3Mh8jnDKt-F9tB zblCsp{rm~O54@?Tf(L&eRsBf$tY{Q+papB?{p4=yUgRp^+TvW7I{*$mw%qMG-t4nE zwQ@`6j>~17Uvv98lUyBK(_97N8T8t{#Qm51w7Z+TjQfSVsAsOIihdm!>A7V+U5`DA6>9kOjzN1vGe05#+8b#TR5fgqL^6)9z~al zrctfJ7ld50EwQvStu+kPC+g;EF7mbcnrt^(85u}-2b)W*Sw!|zOz4S{JizE%&$+s!v3^N*JoVF=$O^VaWcDP?oDU1yQOEn zXPGzI7w%u{=fEWGExZ9ov@4L~H2IrS3l+fuHiLV|--PRWUvOlf>z5ch8#9d!OjF=r zUEes|An3>I6LfX8lQa+c#qgNgPE&#By@3*6KxUN=o_;_)FA$-1a6;fb{NFeFK6ukT zR*%DV!FeS2b55w-&lHO-sH;2!2_>cMTkcAqwUQ?%7#`Bn;o7Rkr6pAYH@Vw z{B;UEDPSnHHYTm`+1U57Y;4WK=VBHVs#l(ly{d-ZTVPj|<-Q(Yxp$}em0$COw9;^huxK|wT@lcsW}yLU&GmRt*fznviqgGzvr1pXwo&A+J^cahTHHm{b62hDQ4|s9d2D` zWo=h%AM7_mq|lw=%_65q4UfK*uULVT1@$pw3NMdc6=yCor0B$=9g3LZQ{#@rzAtnx z|H$Z}ky&Av?ei=HjMsIUnxkAK)u75K_{dHPvT~H?~lPA`~z3B8SaYCwb`w* z#%B&n-~8+H5Bd9@Z`QA8)B2_DO^f{Uo-1-Ar}{QZ1yy*97ceZ;lQnUmWkcS=r) zoMJgovbpRyM_N|*tRa~vGHPVx%ScW4{JHbz)SsPyKK?nBJ|$yS=IX4M4mtY?Ja5-H z%e&sWT<%+*9o{v*p8mOkI=H1O3y+03v7gu(cbXSWIlw}zjt*`__EyQ9y5UB=A1u{mYHE071Be0SNxUpGwH{$Z*ku& z@Y~(|{mJ*VpTXaA(m!W99eZ*e?gGBeffwRZr8Y9`uJi;ugj>N~;FB>arGvID_cX!t;=d|3fIqh<;W{=Je%~l+*9ZMY+$K9+7SxYm&XEe_k zn6WEkMaJ8VHkrDthgmBf`?BBVuujD}+I`Y9)7#ef%D2m(1iv!^$Fu}6>#m3n5vc%J z<5j?TD2&K8P2MXrKueoI6}1-qw5c?LU4UNJOpfsL_y+JT`>8#tTdJRJ=wa+^T5LXR z8ETET{cT%{xjB79iiOSy+Zg^ZVp~-2eBlN96g*X^5*(OU#{G`B7fmRdzeq%!Quv>k z?FH&bKa02?-aBlkU29!p>YyY12{wUb$}w;Nh!NevIRO>ggH&*RWBeWb?R?SRhwfzO zqwJemzcM1zzyCh|>)fyWzrXxk^<%=1?LUMc;lKX+{r1m+jC)xnbCR6<+{1nCgQcWG zsujrCGCoDKP@ADO=$7bA`u_S#hN?!5>5%D&$zq;nJ`1PcHY4 zl4h=LK4N-iENUP+kq@vV$rYu!)KEwb{PY#@UUc_$ea`)WyhFy_u{x`A)`d)4=J|~J z8ODru8J9EOWVFn@n^`F?Zpe43PkVrm3B1Pqgj>dyz{=@=Yu5}>)woA9<7$m>cl zfSNhbiQFwUds;K0A zU-OqQIJMB>n55X$I5Boj;WaU53k@&WCEvG*XW?tZr-#n94YO=EtvWi=DnCFlVpN!h^}{%zg~o+j>N z&PusC**6_mvtDQB&tx*^X2fT>)8jJ=W|Ya8mceFj%WRPKE~}Mes$-|)i(|24u0x+a zGrLnxz1(Ey4_7zOLAc}22-t;X@G1#KU#&Ed&bL6c+oILiOFa4ph7!{UumewW6&G>123V8=#z|w>qQs5w+~g>V^ry5=rr~|H%N0? zTUD>eG>$BD1?vo3wml^@Cv0y--zYKqNd6rKEittUPmIltn^dHJktT5@Hm>ldf;01d zj4U0I6uvXGatLRiZAml!*1y!|;|HUU{4a386JYtzlD-M4U>)8T5{2>b_MYv3>e=nQ zmGi^#IP+b`tqg6ZD`P~4DI+oCPKGISbXJCANzPH{SodJ>H21EJ=@%!Fs~;vH<-QBu`_E}*0?NP){V@^nTIpiK;qsj>wDHN zc-gPb5p&cWXU^4}hB#ZbbIRrxbAEJocH80NxYX|nqzXnb;&gD%oJR`MpL87?34gsc zd?C#s&1UGYOqxo(nfpZ}fG@tqxoL}@t_gbT3h;)D03LT1xFqmx;79|}M~Q^GHHaBB z6M*Ynz}mcG+`xFJ(r0Wd{)48EF2yj^R30wiHv9IFDPbw$-y+&a{R3C?fdx_u9*s#a zye96S_}C(|;_k#gjagrCYrgW43E>mMzJ&C!<1;Kp%ewO|K^rWK#=MCzEPakR?Z$TAM1F z>YE-Is~ImE-smeqm2p$MM8ooRSPl81ERtFY?E_nVQ@x8lYu&wEC7k7Q>*TD@-tH*o z$mi(lSm~(kAda4n9gd%le%W<%O5|S1{gIpCoaOA~Y~vj5eBwOos_riAS>rw7iwG zu_oG^gi2urA{s_cjUxG0<{w<(e8EbXb`TTSB>oNZvy+9F#+VBh&sQXJN_b*e-H?a2 zZPt(G9Y&A7sji{sICqTQ3y$JtCPlps)}|%k}4U z&AkB6URX%Ovv*4FHD?t}{7Uzg3M>fj6 ztYSH^#GU*1av@=*>zu;GtH%;>iR^}8ikhz#ySsLCA|0z4r-@1wJ(-WwJ+mmp58(tU9 zxv$(k+&_`JMS8_>+%(CeSU1=wh3*Tx6t0hqifS1>6?4O?6}*S386{)i#lA0mC+1X4 zY@ylti$|A@l)^JZ_u1>%EY{NIamKQS6x|KYUwj>I6qG~%0gL<$pRfMlwwJ^@9RvgL zzOX1T-ape<%v-=S*d6bF;`;5Z;*4?%x!aw4U60&~XP9rUe|{hrbv5);z(`&QMOkZoX?-?4y{~GoYNu%{XkTbHX#9L3_!RsB6Y>xZ zf|D>3s_YcCr{b2Hi0^~51GW5peJS3Ko~FpnYC0?C&dc#-@5x?@?5sfcNk=EgI>%N= zjzb5x=2+(^=LT1zdyRX$yO_u2Y3DtU+Oe@;6PO!VAG{*m5zk7Sz@q66M(if09l3yx z-)U-QliB=mY&G+>_)0iErMc&WW>x)xl@}bG{C~JKh*?56>|7LsuVHjBBoIqx*s9 zH}II=@aDZ8d<^}71GrNVo=S1(=5&OTxjp-foyzTm>ZTi*;1%Gb`WW-S&O_^`f@QOw z4x;twOE?uogVpu{u30OLpJ%kUzR&32;_n{_ z4IT>)5n72E;&3nzm&&)HeHo)fgAp+r=x%Q^3&`b2Fh$b90ay$E!7fBHPF)Vvb1Kft zGvxx9hPu302L?qounPOZJ8dtz%9nsB^+fM)3)Qm&;VZKhZXn^B>zXavB%M+JQO_IS z8`qmInIEF2YiCRlQxIn%(a0pY&kX`9fK~102b?Rpk0mN1e&5AQp(DS(iP}FkHB-+6EFq# zV5KH|`+Hk@Gri}0z3{XD@}CLJ3<|;d;JtT+W^Sn5O^HM_=nn6LRV2?t>Kxrj`_Xvx zOIniZ=+8`m1Ke*#MIDi%4ul(HDfCvy!NuY?biEe&U#Y4zU8LevcmW>_)(yT#{WZ}) z+Beuc+_TdC+11#U<~-?4aHi$1%-s&R?Vq{bote%duJx`RE{7}5UC%wi&3dYPI(u$; zlD)6JRiXO(ryy0@;S8av66_4)Y4n(sh=vi&RyzuJa zsnvk@z%d}x8}a)BFw-u8^Kuj{sUP5Sd;%`G6#RrlWY5tBFJX|meaEm#W z+s}tVcide2NLxquP*+R8PhY|iFdQ=uGSxO$v8a|;)+4sn_Ff^MLk@=oLSjP)!modo z?U2=IwOF2;=9{{kZX0D_)(!Qab^l-<Y-dkw{xVKSqesDVHLxmCp+kkAElDQ`ok_ZWWB zUcm=Uyr$SmOaLan7o7eFs8&1R^&J84zJlBw)#^GRQwsRPv!KxYp@d+sU4Y-n33TRf zzz-{rKi?D@h`Ve8G(S=N0c46>G~KnT_8fHh#S8}x)u7oMZ+dR3Yt~vWTgq84VwTDQ z+ZtOZTPd5*+SFRxQW7=DK67z%7&LojpxP^MYHQkOO2r(O&89HZHmI0uK&Ld-_}VxM zYNVOQBF4vtQ--F7!}{j>E;>$IS`&#ItTQ)?vvS3`s@xIwEc>2q2A|-n=q0@d+wdMD zMQvoLrO;j6jILT+IJh(gD`z-XsEmA4N*8O2E@2)NV8eyFf?0534n|V2C8p@^hK9dT z;3oX)ll`@UEm6M@n&nnL;#=jd#;{=mzeV?pp3n?xpUn?qlvN?w@YSUEVVd>simc6{kz_7WJ8Y&Co%Z>D%M` z>bvWUgd4ymf9b$EsM34GIX!RgfIk=x1bhPYuTO*u*gajZ{-QP z@(saOmC&Wm>vYYcb>URKnB)1$(Cg)g=kMihuf#xcG*JgjqSDUiT2udGyZI0pJtzFPqzQG z?YD*723iYR%Ua{DgD_z!5tDK@SlUBtUf!Hw>SAnb(CU9{1x+K(Rp?;8!eKjv--*t8dwFeOp`0|khRoi`5ak^!ZOf}MFss3;5zz6mse!g4sY`{n%!m?cs8 z|GMS3-rioP2X!TS5S85qAUK_XvqoGmX>pS7Q@B0jWv>)efy#JlQ9(?Yz0^Q;M@F*x@G6XV0sQzCIRm5>{<{S-` zO*7~m%Hy8OyP;y$X7GDIsS5BL%wUc}C17HAV{*cAE*k2rG(H9{3->izZENjOZ87Nc zTI&lLRKp+Rc2fiMXLC2pf0)&4wH>oHu?zN*A!kBPhCB^97cwqHvfsDg!LNn(74`{s z&VI@E-1f?L#5Tm%&=#~_wwAGewj8vSvAi%73u9Sk9%VL|H=8~h6O2s_L-ldGFPb=1 zc&)jw>?*b~bP_%33-nqSGnLT0{(*S}1?B3}dNB;DN<|2RYP2{U5B5L=c@-j?nU(9ZbL2o)Ng_(#4IR3HNo1!1#rIa z4fSh2aVGSYFGYjY5NfAfshXTB`{X1j`YVEsod)jWM0ERqgBLN7T!mxXC}iPQsCWKm z3vs)+aDD<-tBdBLW{kFoE)6HDp5d~gj&Y%Jy)gi6=&C8foQfGz8!gG!1Y2c$P25ov zL-&Tx3e|?T32A2^ZQE-dVBKwfVLgfU>}gwS+hY4=duZ!u+mE%{X!*-L$<)Yn&Dha+ z%h=X5({#?1ZMtXbVw!1eW1#wx+9iBU3qHtuAnYg!w zx(mhK-QD5h?(XjHP~6?!^&%H{_fpzYw`6=`Z}Gl-g)iu>56B$lcpV?e$MIA7=ScUQB^a@Hd6Bby9W}=viE_tciaQIhrU@J@qrgT! z2?i)Q-Kf=9pwI9eii|c;_Eb^)P#jZ^CFmaMkvfff) z{$c)XUWk;Y6=sj=jp?W<+*I4t-c)SdV4PyyVNe@p>+kALX%n>#H3O)TWFPeuIAy02 zF6B-T?UUgsm1M$t^&6+ILGWkAf-SooZpp9GWjMFf#D7Fd zScBPj4NRa3;Wg4z{zbK4hzhbJ+lTGSwqR?q71$tF&6Z+Yvh&!<>@58ED`qNFo6#Uy zD3>mvk6|iWhAwnnavgR}a+4|U?TSZF) zi`QIWzKDK*E8IaIgmn@_VJ_;wm8v43z1dz>-`D*OXhpoyX_>INrfe_}YQ% zNMjc(V2)ysS%%s!A7@t|?vqhW1apgSOkZ+!aAi0*p(C`~dBa)8b-`7R9!bwaR@5AN z9zBCTO5a08@t&6GWM(^4jeX3H<^0%}SWd>@0`=w@Dt8Vm;R@W}M(7YO2npgWINZO3 zGvO2?kjJuGa!Os?XWR+!@g>1iP#N6H2hda30V{nsv{PL%k#8gaC~t+eueC3Ogr>mQL`%qjO%g;ZZn9wuaCbs_rS$fGqF?;5w5reL3GR;KQkY^li+zkrKO?^dupgt4|k~O;Ox_8=HUv+hX)1FJNbt`@4X3UI(7$inFvIaCV(eSv{ha)iy_3clr_X(btxXO>b?owd~P>)fy)4CC4}$K zFXeyprGx>v>-Ru8au%eOw}L`!1I}+9=?QA~ukO~K7oHB@yP(eug|p)?vu_PL8zZ0r z8x75L3KG?~DkF&1goo&^I;RR!uTs0zv&cenJoSU>rMa$2)SiN_BvAiFf6Or0s5R{{ z{bMeNRq&qmgsrN5fqj!bz&_uWV%=uBXpX{(YN4r*>6@{v@qpni_$Ye)J>7Y*HwNf3 zwF}Yx?4r?V{8S_;D9g|>4c4@!&f$(Ss#}Ax(Uw>MKH4SdX+@~4CO|uQ5K7H>bTT?a zi+d6I1UtQJ(Rb<(nmg%h3D;CVU%W3ATA&-KZAmx{uYwuU5KiB{p4r&Dj-l?DBYi>A z&{52MD}%KaC)5;*`19a8+4*lc1&!t^av#}=m;-y5A50NWO<}B_eZx#)f{?P7NdI=N zbS1c4&il?o&fDl41-Pano%y5dJJRAFxt_XGT`IZ(-IqR(G`2B}9D83~?gbZ&#HyA2 zW~_RTc`xq}x(h3WC#WXsiYLWlv92@+^~4j@d___uh$jQwCozc)2A%sEdKojpBw7HU zXK7HjQ~vLMd?9NF&2Mjb4%=bITnP-41l4I(G}tj&>IiZK`4YbW4b*GwRW)$Z83FQ7 zy!Ny9GbZU*4J(a}O(mu$=9S2B46?nkmA3b{SGF&<;ecjIH=E5C(->oaqr*7JpfS|b z=WE+(_i9X<6lyEA32SO!WE!m??PLJColK^VP?z!Vc;P>lO z>c8Qj6R$<0&1Wc0J3yCM9mK-tq6B{3+Nb9%!syOXa2gVilwky%mn)-D@Dc|nIGn2W_SlG4f7uJXq z)N@o5{cw#MfuVB8>{hwh zuMVIJtP4L*Td*6FKzs~=qVj<(QN9l9tZ51rJol1v9wAfBQI)7>f`vAPOeY6W&rqWu z(MVXUwrJmAE_hkjTAv8gR*;EA{oUDOwLY{?u#LC3aNKv4N7iN)2fnxES#r&fO{Gmr z(=y{3gWm96cUOxUwPrlkiuy#(ASaVAk#5yREk{DpZFL~n0V?Vzu3BxfmpVn&S+$&S zD*uIMQmK5c_yV%*d_|HXOa4&a4O&$N+&3~Lf{lX@qyzS_%FxG~Fdxfu!nVmHtQ^%PnTtSG4q*+ zjDhXV-eZ%wL#sBJqs)pO}eVDo82~EnXqIW2{Jk zEi?!_);q}oZsBpL9Oi*yv=uJB*`NtE1=ThX&cz7Sz)9$CP6Y?|Gv?Ffa8FGk8FDCU z^ctur1WhZD&eA}p*{!pK9dr#<;3lkpGRqcAeJgD}WgBU)?)dCzAJ9I4c9e7Ya1I`A zX=E;swB#j*41J;Qj<&j{9Mzhf4{BYuYO2bjnnWmwUCL_6`Kp4nx$`&`>`~9ewM$p6 z2G!#i`V;>`-+C82)n{}f-^=$vk-iVQ@DkZ+_~Yuxz+%RXW;$j7HE^Y>fL6)Lt0|%s z*YH|KLdE+P`d&LcKxO^Ej2Q-iTWDfa}AbMzHd;Ig<%`~|Ffeb5tOpe5TXz7*;I6WMY>O8biM-KZvF!O^J#g3fqpEO<6Y z(Ajzh&6x?5$P~B2vk_@kb3o==fL>A~yw=ZHIc8(O>IgOOP;{GDV-UY2m|KD3jO#TXedj9jD^MGc#7cJ+&wd7UwN>F7o{H-I z^qGowa89hkdRZRxw`K5LNZ>90&3(#)gOEqg$$4-$+vCLSkoJkS#VC;$z6iU8F2XN< z1Som^P$M2j<=zwLWhNSxa5Fe3H;tG01wyJ23&PPF@u`?Cs-zIfi~e7sm?H{e6t2)zX*Mdqf51Dk zNP>tqf+WMbI8!=`bNx)v;#y)Iw1ES*%KO@zguZPUChvn}7I;FJ!EsdvRmEjhN6Z|1 zkvGXOYCT107HAyWty-;aJyZtmkb+p=aKupG_|~|=RK{Freqgz7y=qIdzjvGt2n|dK zTo zD&i!jYO`>So*?bOj{8l63C<(sOgzRYzhK7-Q`|(3R{-vpwdnaF!vPGNDd6<}#9sSG zk)}uok#!u%lEFAHR{`aflmC&=#!SHi?|MyCk8UK*%=X^I8EiAijf=6~>;ikRfheLD zt1TSD*>n}>K>x2h+klN@%RA3yuW_1M#uaf}_#j;UGU)DQiA`}vzbpNgf^q7b30 zr1Ri_wiT<1J;AJ6j*k92F&&z((KsJgb?ucW4FzO zs=f>8HPz(@umXmI*V_S_^*(R|^ioVgYFrrpR5xf)wcshd_H~BmJ_&af<>}}CiPP3x zv9!1X^Odc9YyLlO3D=QJK>z6?yN+#!6_8~`wdW z#q>X@DaN3xc#Zkt5~cusq%G)T4d$}Bwb0Kl7s8-MKQBf@{doyAs%7p>RCl#NHsYZq zZ|F{yhC*llOgsUq+hK6U&WQU^bDRW2@~C)Od?SW}WOYya$8AP0@DcJH`oLX0285IP z*ahMgC%_jjQ9i_6ic_6Xw;{90iCF!5X-;UsQH9$6i?$_Bt!>f&;|xEH4^3CiGc3cc zt!#38n*Fw;Pr#Fa4*?MY)tz~e4_qh~SoYmT+-JZ3T=n?T)+XL6kjN0NodJxOd$(Vzca48;} zG5a{g{6SSu;2bs=RmgWX5@f`iTs`c2Z3U+=7WYOE^vD}QfA`B>*)tq>M=IDf`5qJ4 zMs7GMUg1|NdfvGQyZ`$?iFE_6>^S6K?iS7HuPfc_-AUl%^Z*HE2&#(Fc(u>qOYMnF z4-z}n0aYV)zIq(G=;J7fnyASIbM;?s0Q5Qcbg}w{Q0i|n#2LRCcbR&cdGmG4Kh|)Y z)0Sud?bsRM4PXN*29S>ZwilK#^B7|z!({z+9ibz%J185OqN+{!6$NmeltBu~bL4&9 zLw;37Unh8Wb|8DASixYIDTGsHFGz3Oo7VvN%Ronw9){UUGAB0#@QYAH=_zSlIzOV>*r-c5F?oiQRGTT7#4A zYc`u@&@rrw)7WfGH4Z@=sfA|$7+(`7+GZjn&XPRR2=@)Q&z%VF=Sa^Y&psraia0IR z^H%gWfPaSeTmyNk5NutKwDbS7849&v7Z8ti=m36j$9m>?GCc!uN}PhEm^GM8eS!07 z7}5&NL=JHW$qwP5NcN>TaB;_LE9yS$2J3(7%NYJKq(J>97}^>i8rzueB6IznxsOG# ze6dck)wJi@8#u~1PS{V|Mp(_34LD0RH8j!h1f}qXW*ucFo2pnSI9|e`+X7mJ&&U$n zhJErLd=7K`UE!qu31#6BsGx2m&GsJp`|afSqV_0mDn6sz@)cFlIm~?*z_po;T`CA}jYi;|$NI;CduaAH^Q;1K zH(A;!RuZ#>E$Ce2;(Qv1zRx_aH20o8h3m2yS7r&jjGc|2_p=|MsF&k2Dv<)6xE>v(NmPA}ReM`ISLe_#N1wa0VW!~&+!ecw8q*S!(Y)F0uZf&` zt&aVty^MpgFR?GO)w6QwPP8^QG0f0E#-uP@`;r<)9#r)vf|W0!;dlx+doDO(55N{n z05fN&?~T6=CYaZ7_PGiDfE{Pr1AY^no8dSc?FZlIjQolG7-&C3;3;&Vw#iS|ee(BHSYEI{#I2Xwkl#k6e;v=#NPc4oKgZb-z)CM;EY{F zM>P_k@NSsZCZlT*jcYU!nX$7`y@et#cQ|&ahVpXIiRM5T^$yg_y11t@F_XT5G@UNU zGiikTNQM5b6_xyMWr4CXF%tdv9B4l@%KnPSs2%Fanjqs~2UyAVK;w*sCoo*vF8YPZ z!bd)VKZ1VxEp`L0N-NAjnzD`Aia0m%%tz)Ela3yGdE7%Q*@xJN8grZA#p{i=@d01R zXY(?_0KecO;j7RD6&eM1;AzPV67GEW1Gj+tAO>v0y5LrJ_l$z>;~hH4VbH{xz1g0n zI2Rnn{3!!I!8st2Z;{SQum9gaY6ll#p1T(G9=|Kse$!Ud zy4UP8E;0<)*VoO}-qGBm7L%VrmV4)d zoOl^@MLnK(UvDH>V--;IJ@9*x-8%y3qm#1hvUF&pBB6)B2a4ozg#}ajhsvAE8|X=% zQ_cY;Zw^R$&y_l&4pEVCD>ow@qbu?-a^QuX2A(T2M!|kRfJs##ru0t*t1ynw=E`!L zaV%e&#;iql)LI0mc2MWI+YChmng*$OUmWAsWYMfPy=uGFjNPJ;Xt|sN3tGt5jj$(Y(#frJ+X~g3SxB+Vg@=&ZHTr+ zOQHf%mKY7?q8?}BKFWU;dPP6^25?85V9!lP0>oEPYpX(g79x%oKA}2j&n;lzGA)@m z^bz_5IzcDt`#7imhi>=3=&1gnRnWFB$7!_&yAM6Od+>NY=F17kFnGFbWU8E5RIE0 zt6oa%SDZvD<1BA|k4O40>cuwbzMSSiKx^|s7$x2mA7YYOM!XJkcVDaw1(Fu8j>nEv z`&j&DX>?1E1IcPi1#QsgF2A*v3EM%-kS$8$c~7bVb@ut7sB>mMExN z6qr|h#8krTYJe`P1}9iG6b`@Gmf-P+@XOFW?TD^iG1QZd@UGqvZJ?9qVxo5i_kCTE zZyVs0S{Ex?9h_C8Ky!Ts=KKnDs_Kgg@i(CIyjs=BoFLA;Bz>tXQ#{+3thG03HI z*ne(I^^pG3(ZhJ|d2fJ8`ASwG4}%hU0Zyt9$>-Eh&28;!T$%EQOv5%~Gn3nN$lL*y zKws-`YbV?I^M}vGz32Gd|W6x)YkE)I_o`l-VX_Q+a=X53j;= zSL!Pc7uxcdxFqf}`ybmD83gOVQr`*pcdU@ke+2h_vM@$G3>V2J&l24Ky^(IMgGVF? z`^5&ZjN0O46b&t3(qAedsNqYX@(Ut9BPVb!cAch3cNv17d3~H!Pbn&5EhE5dng=Re zYg8e*e`^r;12Dsr#U4nnc*9w^UhE-sP0P|LE(3h6DbDGb#IAF`b*4KDoCa4P*8}swus_95!BT_{y=xJ}n`}qc|SudPoXw1crU?&g3^=OXkk>kGX zo&gPBMRyqP(_rKP)O1%z=7C#!i#~UINh#gN$+SH*oDIaTs3EVSi`O1BY6Y&`DV$UP z^BnXZ^u0zF(s*Ph2B=!8r;{hCbkMz3IEkhj_89*$MPTROYZ+{fxA|<_?1|`>Y6Hdu z{0OKMSSqkzKo`e4+a}8>(_+JG-BfLaW)L-$l&QVSEAmMHF3&0{L)gx@;l{HrWN z?wC#6(Q8}__vbI9MYO`_b~85$DtJz;@4kiJMh_^_+(_LnkKX4ce-&sl8pwJ1DG*W% zlw;s0F`?6{MJ~b;qCe4-C`TBOqBsQpw{64*f+X50*C3;52@;W>BFVb5Hy^x&y>XStS~iLaW8bjL zxsH4)KMW}%)6lD~EUm*`9pml|nsI&X){X2X-H?-BlbrX|4q0h+!G#w?f(t>JR>@}!_e^wg|@Oiu4{-l98AEKvaj-* z%F{$e^(K;|;|;}bJzbnlAqcp+I{vtj(~u%0hs~g0>1@z47wBaH0XR_ zNBOnDdyEckQGT z(6buHUdCLy3%ih;&v$_@p(Q>YPko0#mwky|Q*C)ybY`xhgPV#e)>^`itOdQg1Drjl z)os;RK{HMV+kZMSlURm%Opyf0BYD?(34VDfzcQ`I`SJ2dc1v}Ad92iFbLo+9C-I7muz z-vitJC?;;$a`B$;@XTqz)L!Qz|By-48Re~S}uf>g-4237T+&f;3^Bx zgq8{57Vs)@s=KCdob0&VqP&MhrG=`gxK9U@O~~Qspbx_=_5(V836z%VO6jQvWK;Df z)gL06IEp;63YZ#o!^tvGaU4ClEttZN_YLz-^2ppb#Qy|^5Xz6@mb3SmV0bhhxFTJ< zz-Ta)bT7V8v;{tiqeZ2PmlW42`BHKa%KP=My6A7#Wll3op*>&6RpcvRn$uiLcFUkA zNJJ%d8$0)J%+s3smLXqYBJ>}DG6P%?F8GEra8h}S?o0-lC1I#54uj;r36CDQ5_h3_ zED?8Nt?C7RT?gnOP5)OLr%65BZ`{41n2E!RuvS(H-lZq1tK=Qc0bN~#%=E}y#kvCh z<_Q7Dpx&ikl8DR=Lz*$&uFn2?C)vRe56g=S^Trg6C|cmma2<0sXZrIi1-F>& zJt5n#m`IFOw*q~AKlN5~R;z@k?uO=@MycsQCF18=nxE7%%@B=_N+w6CYpIGrb|%4_ zdkKB|1`vOrWBNq{!Tnf~84f4s_p#c`NUN zO0^d^9%`U{d<@p9!O|;tZLi$V$Sx`d5>e_GP?PE82C;X?AMhJxOy? zt~KyCG^WR)mWp7;V>fHZ9$?ehq0md6 zRlzD!mD{0ys-YULGO2nIub}8_rg(t!(^)Wf>-fj|-r}@c;ugS%NXPT}j_#-&&!`<2 z0gdohafllXAD>UwO4(1>Ij#_~uOPPXChBOVk8J-uhD@q$PAa+7r zi}+Rv%Mz<3J&!LN(>roQ==Y#zwljv!nsVeH#Y%ru54wG{Q1msgdiI5kZE3xJ2|qjk z?2x+X*YmVd8H=))@`0aRk`p#`TdVU*{bF*>r3{UkaS*K za)ZUwPn;(`2#fg{+;O;EPSNG)GcL-N?;L`8X(i?%`;z}7+B|!G zjpUn^S5*>OTiZiF$hhBp*?Pu)I6w&cUAkL{BJ6H>&B*FeQPE*Brr4LUnQ^uROXBfF zSA1AZNMx(fu0bW%Y{MAsee#X+w7-|+T?;~EJgM4(3e za=Az0P&3p!Z4LbbWDxlcFELGvGNkI7B8^bcBx&ktHfju-5b83SK%T+OtTU1b6^ij# z^~cFY%>IYMLz3ao0}VMJKC1}OZ}O!#Vs$u04nu1+0QJyhbU*g}e@@~?@115&VP4gN ziGzyl6%)d0(WUB-sn$}=vo1ojex502YT=YUfE$C|X(T!WnVP zQ5rq~4qd(4n){gA`ZejASsEAho9wO5B^E3F@*}d5;0UX{x7;bxe0YbN@^iUktR3}0 zPjp%)LHqWdP31-iEu`|EUcNuFLCRFsa;l~7jv>ap$jaGg25u?!HaI#=3_lpzJo;e_ z9czkDNSK+}FG*JBbD4xPwc@WwJq=q^>Wa-}NT+6~Dk>)XVm+mWm##xa|K_dFip(&j z$y2BQeD*_|8kSb=ck7G+*{$=VivpbGnJ?TyVUbkTi)3F=WOu9XlA|@lp}0A$|E3Q& z@W_7ZgxME@XEjr)+EAt4BzKb~aEE?YS5XgE1rj>tIyk$_VIG!?{BRzuyh7-QxA?5s zd&_yeQjYi)nV8d{L|zJ|{|NpP`g?cy@B9_?$)7`48NnT3B}QbbV=~nS{q&XS-=?9r z(FmvP%kYQXfRFbpQ^MBZw+jyGmb;Djxi1EjzFmr!N~Gc-g-lO9rtWI)YHy)qw^(0B z|5az#WoeQqi~5)Hw_Gc8d!sx{r5<7<;UD;9XR?c!o6x6ya!qk9#HnyOy$=rA9>R7h z(98K5#cNex>bKTnaF{AvhTEDsz62Z(`ciss$nY>eyj$d+s2|Z+V{~x`;>_{7gffYn z6V(aZqu+-g4?Y->W!|6@)F+e+WZ~XKX&F1cq;bL19452f@4Km`Qi-4PpYu}xO?&pc zZ|2CHtzdM7yEd_N1gn(h-sig_lOvb=rg}KegjW4t!y02lBWc_S*HvGAXWd)vR!uER zB4epDWJPioxsn{NPF7VV>L^3O*qj6Zg9e1Kk)QwuLnF2gDZul*V=>dXBFz(1gmO^u zJ%is&;KMQ5=#SprcBmu{^Gg0K7mqVzyZ@g?I$)ArdW==`c0}WPyihxUF1G$0=QI7wL7)jw6WSw@Yo+xA3zVsEFbD$ z?2Y%-aj%x%qUMYemkI~5`|n1IVMjKRorKZz1Vlc9~&8w^Fz+iDCA_2qPZv|o^rx{~6^nv@PHsMV=@)M2tvEvQPX z?8JJUxK4u-*%9u^XGnW#?@ROU_5K7?=ehJ$+$B5%S?)Y06w9F^Zv+LM4o<&gNZcHa z^S~lLfS=7JL!O;9M{biSyU}AGwV!7 z!?cW_Cx4Fn>HBF&i%*}L8J5$tU|q>Vn&5^C8>G$1mahuVOFvme53GV;>PxCbVgZ=l6_EWf$S1+CSqP8a zAgCdO+{x$&UFG+oy4lJ0gCimuYSn0XR={{bFW>=sQVwAbu8ESH3a#-U<{BQynPbcf zrakJM(##|DT6fZq;FF1Bn{!|I6mh@%kavu~7MQ%vh<>Wg>b+!F>IPLtlcDLPy{WCN zyP$iko2$E_U9DM0>Qvh>Z{Fg)0WVAne-WMYOr|0J-V5*mjG?=P|9?|Ji$ua$~&ZhCqZLN?+v*dwjd%ZDm3O)EF0%Y$VpHn zO->3=OpWOsF*#Ttpfg>d{wTZ3o_f}b3%Jp)h(a=V#UI!2$3KUB@A19L_t@{jKTD@| zOK<;&%KKC#ca>+l@NV&!bj3Z`dknMq0+sCa#7cLC? zQjTuRTtUs=1ijB0IM<9264BM}3GUZ2_8{Jqmtg+|LLY~dH_f@C(Mip639d*wp58#u zWb)WKd_`2k54`97qvfNO*#xc9AWOQCTt~Je%aDTlG2C1Q>R0M*>h5p}4g{Ez{`&$?r1Uu%l@`G&^6Jr)(ytgwE}n^xL!CS3~w)K1+EB zs@#5fuUn{3samV95&7tl&q4P*P5BR@R+&|giAKb04gT-rm6;Kgp+0CJ3_>zblk&UA> zV*17T+FQv2@bn6tHr@yG+cF@P1isF6wZE~7rRmjk!-A&C* zeUuvZtHneMDUxkBL>dH{DosOm1|m4O6!8{Q_`9f$OlQRtK0mOWMUM^9~s`W7__ z3cLDRlkU4V6dsA?`f-L}W1+D-$X6>3mGzwF3E556R* zddO38G74e-VS=%fEoY}>2kNJu0a@<_pehkFbJmVV7VHJ1Ca& z-1241!APP8=_(n2nVZ-;If4SYpq$c^LgK^H!e2!Wi@qPzA?|1V(8MlD31v2vd7sF{ zlnEbIYN~aX_MIZz6C$Q?PiWB@S6l|4_7Pc=Gp40oPQ9MGF!kE61HWr!{>d7X+pO@A z^9@suyN*-r81b0UQP?j$#KSMHbtfVBY>o|e7UBc*P;YaG^E)3(Zl_>_Vo^5n?g9N;}68Y*yLsI2r{_Dm^ho z2(^cQ56_BVBmGgaafk(eJp+D@38fl z2J|g>q2g%;JOhKEA?t29&fjuzKe@Pn7g19$d7)Fu5?P za9d#@kjD#3O1skOLN-#6ONZU%y$^g3z^iHlnv0}ZuWSUGaVu~LBGoI@8Bl+9CmUhi zI|?$=LuETf6?t`}A6-T|V5~nGe2^IbR9_wM9e21CD$M0rW*>U$om?ATub_x;3l)3} zoRf*@BfViJvwyfOp_hBHucum2feUNhA}7M9&QE6jqE!e_&xn#7n(6xzib4rQwUiTL%7L!4`?uK!u zZx)X=-CD!8+7@A(XsvAJELKa3DZ`MitF8H>o}DZ+x#5<93ElFBF=;& zTrQo%JZUMInOV34nlfYP`>yZKMCa?0W+hvTdlbhO-!GCC{VJ?jbgQU&aru&Q&PT3M zOk=LH@D((sF5a`g`AFzfLWR>Cdbh!f7x*k+0Uhr?(Ngss6zTrz`RWG9@I4E^{~*kJ zjuNkk%TU)Zhi;*A7n_lhOSTe3CJ^y{~p@$9 zeYTyni?*9KwXKqMmwAq{yxy&uL;fLTijzo;sO8Cn!j!_D93jn@)KGU^|Ko=AjSPO51!8)vXT}hhr6g`x!#*<>6G{bYs zd)zl2T(z;X)nMRUfPW$e$)8{3n~-<5m&j7JLu&RY&1-mEJep&g_0(@NJoLuA|Pz&Tb{gio69!^0((L%b5srVU3)Axvz4^=3UBPS@@%9ZONIE*(I69 zRf+}{c=BrJ?#n5iTLG-emBrs(%edy!7$00$WIz1@Q!ncr`#Q%>N48_HgLFjMd)hWw z3(S{{4fSE#7i2$`3u##6{PDg7?>f%~%rlN-I$Rl?1vyC4)1ZcU3%=qX{w6zw_LNwP z*A|NT@ALTFCAsZ$tL1jey_Or57nVP#KwfmBxT~`V-I{H~^Egukdd48j_bHeFNAWlS zg~msu81z)MR=y&F)K$pkluxr#H%A`>wbgRtP2(nGjL~dpqpPc#LRM6bSIqO5@;;On z2$Q&j%s$uEl7EXkg8Zr~?pwU7WIw1?c4iFR^$S6(dcfa=$EqGW8oS})cp-Lick~9! zIw^muYif@f_L#fdOaXDFz6KYE#zriODvpVdZN^bHnm7^Pc9`%Uh6NwIHRSaem7@ zPtJ=h;?LQPvFXh-W@c)$yXAK()-cn>c;8HAPt6^}a!XIUIiPZ2>A>WGL`RzKp>?~Z zuX(Mhx^bi4fxgIEY8tszy@DZJ zq5%c{^1kL=&u*N(IlEd;R?hp}S9x#pQwy|3%Zsy1YPwd@9J7gg$EONv$%Wa)cw~*X zf@ZFV_lx(YZ?pdtbO^hY7gcS@+8RMyULOiQRX^i<<9X05pF`t&8_D86O%-ardNxr* zk?*hLyY0!8R-j*3N3im}xlim7b^ui7t5MCQur_Wzr-nXx7!(Fop{-m5;>rl=fqSmE zJCY=35HHD!x~s+s*0YY4LEVFOVQnJnMpcRF8K+3Nm3XGi;{ggw;LGE4dSi{SRk$xsKIF>pJRs>PqRlXg^Xb)GL*JWTm|+VkGy;Wh&WN zct8JQo;UY*Zr8lZ`IW&$Y+cm0*i(GA3(K?-q3B{iQNuHaev=6y%-QVazDHf$JMs$ft`!i;@dB7PKtLF9<4972htdj9suF zGlDJT<_Igqn^L-`3X-%ADyyiUQCoF0jGZi3Y+oJU0?(9c7u+YLY^W>rQP`;PNfBEj z_oIt^F@}vzjk^?gG-h5zaB!w0#N0>|q%8LJ@{9#%sVo#PUByLEdy>Lq?kA(7S2%se zb3kJDZx2-||+{eG9N9f~Zcv~ZvV;XqqZ&V-2&YBeM4BbzV>Ph`~-3DC% zx*rv^pEOh9)iOZkVWFO&KANu{pwbd?NEiBvgr_KPHZskP?p4wQI3gYjG_KPQ?isqL z&za%OD)`K5a*1F|^$<1g7Fb{V`;PeUf-c=!=_fj?BdFQhU-~{Kt@Q*ZDhIJk><;-D zS`t<Q4p$lm=CO^=y)->S^<|v!Th&(3hb9y;HwP zUs+#HUq!D4?I2H=tWVTuqu-ON>#Ccg{iv~MHj^gxXX3E3isBcve20DGyr-ZRigCAv za{Cs}fRh9-xI=Sz1K$9w0+G7|`gDIjhNsX4zX}h`V$9+fp?A_vn25(4;fUBxDi6if zXK!OTOxMXS%Pag}S&*r?tBA#1mB6{AIUFJNlnbH2y@pfCL&Ys956i=^a0Ghkw(=Ob z>Y5{Es4)n172$B~grqzZvid$jkJ;Gk^+Z7hI1yyDPu{w43~u*v$Z)O)Lho(lgE1f- zy@O`1tz53?tQZBykWE=x8H(v?C*>+6DZfSKxQaLqqRuGQN!1^fOm3!&vcioQV*$Zb`XCS^SS9v_$oTaagxr<$kkP1c}B zz>(cjH&tK5(BCk@aMN%C#G|8dCvMaa(ZA3yHoP#@FmifKV#P^0;JQV!0u9$?VeD9H&(#)IU3G_@u zmn2qNEM^G9aNVoIS^5xe-S*I1hDhI`2z-J>bN4Ed=?-v%UABwe)&*zB=%8pY|Pq0;L;xF<(IK9u4jPA7txQlXt?Iqm#TnX6}9D zT`-rfg2ctz;B~UduswzuT_`k`m*9J^iFe%ZWAI8pz;%-5djV$V0gwn6!slMqSK6oc zefRzge&2QPCFr%i-ViYRhJyU`9eHBG;N~J_6)eAZ{u{`d2!=nnlg>4$>jIkL0A;!gSi?d(}-n};L0chcYg=eq`PV`uQgq+SSsTR{XJO zc@bT7xAasdJ+><{XuQ4 zN>$O+0lB3;T&4?k!$9v7bsP1S4F5vGTMkq27KVZP;kpUhhnT#6q-s%X$=B*fs*}V< zDFw~*EF$N0W@dO~BfOK1cV>__-zUNPI~(yngK<6s4kDK07=i_TX&SK|M51=;iz zMhUNm1{9U6P!m)EF+c{j>s8NUUjWWYi=frbR*odXu)>^IcP6`2+t8=`tX-))tUIWC zp$pPC)pym$V9rPDhUlVnY1)ojotDsMYtCz0VKQ%|9Au)p16WQA;3A1vbde8%Pwy=f zxEpytc&5Qo*wvi{#jGHX6yM{WvBIbD3F_&qd9$S^>l`^`<~Z^J7WdZFmXu7dj_}E zbeT(56F$)pWT<~v7~!xv2E8*$G=vU%FmVjelw`arw6ANSBH?=C3MuOfxZ;p^es2XbXu+|TQg zdG-pb5)ItLk$8mQ9kIf-oD3yVW6;aDAaUsL*AzHp`(V-^Ec1X-a0dI=9K3Js&@n25 z`&RDPVGerFcgQynDeeuhl0^BwfK>AzvNm3Ne?mDIfxEiBZ&DJ?{A<%WMDnt46O zqatw+_hb?}G8(cWb%N>!7gvhTqW=!U_gUjqQv>r9^K4TpeE37OrKt{>##|)M;rg9X z=aO=&G*pSHWE|%5R`nO+fHDKAvNE5~eMG8(&PKYp6>7~O>898b3f=Rd$=P`|_>B9w z2Hbp1!j5BJ<6zIQHMs5Ed+suReIE!|5qz>xPh25Acc*&J!e`6)Hei*%jNFwRc>)wi zlR>lF3zGJ2AqAsK^;qy8h?kc@h~`nRgQY7>#BY@z(B*aT;5PemP& zwU@|$LMuQZnLW_^4@he1QgvyS_!^l}^U-HK3&-OOC}a}2*WfuOVH$mhdB_M%1$Gbn z8+yF1@QvSuX08kF+6XA=zF~4P2X|@{>8mu;-3D6K`*0|{0vl#7I1nBrN$!@Z!H0N( z4&-z&=gKM%f|@@EIV(XRv@}yyQ^{2i!2$p4)`-B4{zrKpbj9Y_EmGm^L@yc&hmqLZ z7a$vZHF!uzKtIy?j$=pnVfQq^zthgGbRU<-pzce@^skE;Efxq@gp<&mtbpfp9)8soXgv=Bf4<1 z?tSoFac;^}#?!^K+_MMncok0gGjQ)e!)N_7w2)pf{wm>4_z#KiW8q$MKn1k|j!c)V zDxA(k@!pS?cZS!j6H?SVV`ttA`oV9wZS&yROoz{Q1DvyMl#P))~5@ROCKZL_X$tBoS^$QsGYcP|wQ$g_HOjw4Z?r3s%YIil&NI;BhyB z9;_L>BqY|&w_wzrg)VUzK0#f<^$CYcDHAN^W5|{s1jbe(5-h*Nt$!Cx*tuA_M}k@2 z1g^(0FtT~@r71s$DqZa-{XV3jhxsEwYpx8XM-TkIiD3OMz>0YRw5oK!0-2LFQN=WY z?s6`)IG0dsU4x?hDVX5D@%(97DVz@);8_gBvl)++(xJF2gYkVY)Mz#3wd7Xt*`o0L z0`V-(at0YJ*~qSZjO^~e*J2PphaK^JLvc0qSa)6iKcIepMBeRD5OK!inT+*MMqc|w z)OEA{gR#nu0kx*h?V;n9%^VPL}X?L;Sr5w(MCvi z=>vw#n*XyRQ}HSKBSTvP`Hm{N9?z^4eyk|3ho39r%u@^1<^X)gIv{Iu0A5Wkc`H<# zRq_8we4a|dp=iTYp2j=y9(lTta3%kKt;Fs)3y%uebDJSwQ;wY^49dyRAU-_CnsEvD z%MnzyYv9F8@&C;|+yw4VS6rddctzcjl-C1%`L<9xO~ikV#qaBjAGhMQ&BgCu1TN_| z|2lY)j-V5H0#)TL{~NqpKfxT&1#|p2yi+v3D@1}Tj>gCtbt2nFCL@t$tdkj$u@~e!ThrtcVRDa_stl_R1za%W?Pwjm2|X_W$cJ z)jt9Jwh{iG{wnyn74Dyg{uci7*pX`B`}qGqV*C|QNrzz05{X9=zH0bgzVYa!(!N%{ zp1wWU>tcN~z#H6&6)6dO*b$K5M*8+6NAnFdpz*$E==JG+7H|-rf=d{QQ-vKrszKAC z;BKn{r9f}2IrVVIZpJ=3#CI4J_>I_|1^f#!eigXrtMINJMyih%8SjH|&sW9g?IG&; zU0C;Y@Y5F~$9gS3v1d_V4_6eTr#l+w$OSlcEhG-3hdB&<@PCLe$jRG`1czjG66Q*B z^0C^d{!bl@{DM~Msj7EGoa!)W;#1(6cuqV+o`6wRMRl1-!6~i^F;y9&+zik0D0DY2 zf*G(2*Ss%?TWP+F*u_Ke+Ln0RV|QQW8H>z?Z{U#a0{JBonv-Y9nyCslj|39d264YQ z4Ufm7C?@=$uJjue=yvXj?j2x}jB;;qFTu*X5lr^W?rb;60G>RM%C~ycLC~uN2lY(f z2AqDjV0GIBUGq_#F7|=M?(}6L1EdPBQ9P*m5&j^o1aZhT6?`Pt2Dk4M-cJ{5orAtr zsHAEj-Bg1!><_GKUy)+v^k$w#p>17T2v?H-@uEy#)8+F(( zuL8STEzteCgH=7&w-(Q6AgY`fcot4yq+g3q*FxL{pK;du?5`!OEo&+pg=*ju)|Z-c z3e1;PSW{l3QoJiKk*9)QktxrY7vOsaoudea9T_l<6b->_s)wrb2T~ZGVii4w`!@i0 z>?7QhSCIOUh&nG3$<~Gb&G`3bVa0ok+HgBQLv`?3Q}~|1{d3kk6=e4g-s;F~&GzJ= zwtNUGYhTYw%&e$-UYFZebyk+Sw3T-^w8CEMW7c0`T#3|~#*ps0lm zkHdJc3BQPUwIfcX3z34k3e3E}>w!kr7HjG_)SJ7oYBTIjD)JfR3K?Kbp=0yot1J z+apQZv`t;o7K*zScXwx5+#QO`;=8yzi_79(U~zYMFNXe*ckUl1wt0 z%sg^Gw_X>1fgrehT*!v&uwgYqddz{zx*dGR_vn%P)Z9C_e8jzE$8BI_Kj-~E4Xl87NBD)75yL-XjFhZS%ls^rB#zxl9 zcl<*hdiXck#5=-uqE*25V@wPch6{hgmc0{Q;2}0g0AqK#IGsll+T?w3)AV=`lSPH` zn?}BaEJaHt@R&4~hNEdV1{G=!-oUjW(5wYT)9#|odrZ)NyyodwkKA=`M$M)?C>QsBIf_&6?hRCU4 z-&_H!qa_~BT;QnuU?xpxj*Z3U9Ky<8oR$0&5xtEtWOtw^;?O`;+FX0Ar>#cO9xZ6o zVyq{!`il{Ef)#5tIHi@b^a^7CsniZefRCPpWYlcIoygrSutfZcmA%z72S$$No+(I% zW}a?fr9^_4P}pPigrHRx23Me*rxKOzwRqIwm^#$5cS4eMK?k13?+&4Z-ba3>;2F!u zYFPoV(Eud@X?}?7c#E$p2)nc+EAcFJihHcvGV6O$=6-i!Ao}?#m}xG+-SP`=u1I7? zHPGqWGI~cKGZusD^so4U=&=>1?IN_$D0*h9v;>r)ne@qKaP97c=OoH8Ag$G-dbXw9 zfg1CPRMJibAK;#R1q>HEcE5?-mROxChwF?w85TE}?8kOp-YM^c+ieo40<-C*A@X4Q zr=c82P03eyK8}D`IT-ZbVr1F-qR+nKO0J;4FJP8+W*&u#nas|+$eh*qLzZ$sT^NC- zkWK<}<}>=$FfcoW6Q6UV9SkW z62LcKsd;}&FJ1GT!<%>j&%-flN)tW%Kq%P3-W5>vo_Jn?tn!_aA}JzkVLp7fd08>@ zp}n-gBiR}4Weh%rb;>e4ga1-Pdr?V3Q~C@7)la;D0j$rZsJdy)SZoI}^H97J)4B3F z>Jpw;b8m;Z>j$*KBm9LgkP;5s#1FkMl=jlYP#?w|&V{_mk0u<)yIe?{SiX(IAEJ$T zC%8faJ z4ZT|_#&cC3wS~&8RaJ#*tUFbit4;Z&70+#XZO6OjUJ3gf&o#lT)xK*e)Z*A$LLGit zgMX{eKNUyX7N#BY(*hA}0kl8}SFA@n*6!j55-*vk#Y-gaL)MKORD7IA@?N5ak1?wD z;TPUbi~qwoTJ4n)E0L4hwu0w%R7x&kF0SGIBKDSgkN5|c`0d`$_jpI%Hnewbgl^)~ zWxVb}+V5dJ9%B2K(Yv3sZR5SB=WhqsbO$B;F!r)|&cSAJ!R+{*`_S&^8}s`+kI#&(6z|^uMrFGC zi(dN0Z*u6dEPiF>*}_?Dj6fTEsr*v={0E;|dGFy{AO1fOSrdDYfwA2yj06Tn^`t~{1|h^93)Ov)JVYovEB#d^n5QO2`2nu~bHbrGQu zpO#`g7x#|nc-l$Z%FtGo_`JNgy-LwyWxabv`Lqb{{Hk!uZq%AG9Xey)U z8)IE-+n30VUyP?@wl~c1H%O8fj4f@ZKSSSm=$#Gs(d+K9f0sG&A2wqWM?FFAXx}G! zk9@}KXLPL>oc%qonxxjY@4WxY_LKilXS2}Xsa|=b$sKJ(IOrDx*YAg=YT{im_ZCH~ z!=g-!=cjM9F`SQrqa+Vaj#Q;>N^wlF|NBg9$BKMjf>p6N4^7hK z<5klHquA4WQ|r|@`YxJ%O)BK3ck>}JgZM6hzBcgR5nM?CS0ponw9%?^&Dwaiv1Rbk z#+)VrvuH&tcWGfHrtsZ&=H@p(|Hf-F$Ng{9#;4W_T07Xef8f}>ee{dhRC>z7<9}n? z#(oax%;q(lv%5I2c61u&%iy;^d2eC+>1|m}a%b>+Z3gA=t=9Wl>Yigsh4DD8qwvNYFRgF2{0I6FasnCSyrbvPYysLo} zsDfN5$C!?%{Y%rjJsruu>Nz{ZJb! z@l-LUyBB%-pto@h3E(2@hHdXZY&*D%Eku85BN2|OVAwxHTRlpY^A7q* zch;P4SXZ;KES-3@T8qQQDd;Udus0QKq-|Ij)oJ4t=G7C%O1#kZ{}7i>;Gdg;v^5Kw z)H0((zZDvi{qPo@IaR!k{?h_a)H}4EJ;FYop9^0F6(4PFFpH<*tC)vHvK$?03>sW7 zv8h;?SoJ6L@9XG!F9by>#An0!b{3Y&6!fU(VlM2$C+LCeu<{b|f$I2&`TXNyw%Pbh z%VM?s6y9U~=)et^@za&Ulh+@t{sCB*L0rp8e)%u8=#p57ZwBHGYk{q!@~^vSixqr( z6`U`V*J_%G9Xf`r(AwCjIrv)-30v9r@cXO0p5bv+*v*}7!*1BZV+Y=tH~0{J!NN|* z20p^^Q;4?q^=^OQNn61A4{`_Z`CbtB2h=xL-lCaA>jPo!m``%wOQtfGidw#Vo{}l zYbFRZNw!_inaX`BTwhc_NUklMc8_tE#;ab!-B=kUe3!0Dqa~L#L;PD9#SG}H4gn3V zEGTyUJVzCmT3+lSt-~kr2#a0e-p^DX(B!KjB!ztS<{A3BWV4T ztO7~s4M*`-mBYW0Aa#+pi%W$mpf98=dC^6d;jR3Lzw{XT;C=MOQtA+*TjA(WvET1heGTqN-~L+S-E80A*t_KEl>E(ziIe@4JS)gDWA8oF{Ah?@qbFwuN1cc>J)$v^lm zCNp-wGb7^#DtlOqQiRH6gm;u%vkVrIhJhJ)Ks+anm+R|F=qKuPh)2%Bj`RbexPtDf z{)Sf^0G zpp;XOiD6(IH<1&#PHOXES}+Q0RHq`TTd?h>3-zUHx=^sozsS4A>gplItv(aylB;nA zJGT{Qa!5bqhh)!`(N6}wyp^0s`jc6>o{^Oz`V*ygNhjqY(j}tugCrFT_Y=63L)EfU zZk=p6Vi5F!@f}z z(|wZPNIL{O_RvDlB;_7{pL+ODFNyi2K13#}Ff$H{;lgmGC7!S@WNPlEnt2y9#SCIa zS^N-F@nmcgPLQ{;A0J6gG|vXer~SmxM5!{PM;6{NihrUxuV8(hOP+aA=@%@6_2t4+ zOJO^Rp2e`N&Zt|(Lvm;0%O+h9>6^D!NTxD7MfoWhKx1z&m6Y0vjbJQI!;d*pElPCp z4)QZcNXHJmN4)W?aDX|Mj-R5Tm=F1J055cBWuAIcD1Z!W1_I~}uB|Gwb~w^}q8h>K zP?b42hPsZ+Qas4xr$P2AE=7u0;mi5Tn(>Uv!#zSZym$Svo)?1H+aGVlKhj`v0^0Od zwLW%S59EF^_;eERdR&lJiKm5oc+kt^kx5iLFp@PS@^0MuEqX&+Yby%J)x}B!&kT1g z@nt=zAf?6EjPvWtI8QX#eleZ}_+95H0_ScCt9k~Jv8Aj;FNEjnF?{CNJet*NU^I0U zn-JOSC@+^!fs;N^kX5774R1rNvP{+CM=d37;g>@}ntcrJ=>TMNePIs0wG7+jx>^hw z+Y^rU#&}ijSOk}qeRxbw_}wbt>w{OBwPgf-JV7-pDIkvhbWL!7@|eL)J1jN;m-wVm zO&x{rq6_ivb6B#2eoruHGH=`l0^Z?zFC$5z(4R>Cqy(SEm|=MQDR z&_QYeQ?MxB2D$SCGUzB?!8zR7PqijmOAxE=JK8W9591lGcolK`hD0bz;d|>P^hBTN zh=u)JIj;EO2^frzYd${jO30futo8ADY>L9q*A~6DB3JYR&p@!c2|r{B?DRv*VZ62v z_?7m%X?Sv1qXTb*dv!EXkF{9z%RySj$F6oHt`H8Ia(=9vWBj{8U8#8BwmFWEpf;SX zFVrJKUp$7rhz5*RXDGuws=E&|`l}K`JS-WOjvI`(3&aOPkhpD#8-_79#$x;3pyx-h z8g^&4#0uTjnT+*$$Yf2=sy6^b(O!Qw5Jb{a`U>D-i#lDIM=WSM80AYm+mzcNCm7Hl_QDgcQxjO>1IQZLt$b7p z5DB~kJI!x+#YSMqe8$^Q6#3PNQB#hq*~B=$gDzbjUwj+Rn&e5qn`ogP0>&lU`53;j zON{^A>KQzW7l_q3K&9=#ioOq=rcHR4pU|5f$QSyP9K-9LmYx_;l-JRp$B5VbhF5(H zbL=Bl;wrAJnbHY_*v9VOR7EcEEZ~k_ss6%sMoA?9ZNneho4l8vWaTLEEw>TT8Q)%4>mp~mqdSjf?grz{ z&d0y3##ge078!!)dKpr)6-a=Wz0rz(*zJpnJ>DSVG?Sdd0M1_kFGdjAtLN- zAeGbT$g`L!iOj(YNUt$iAs^KcR>X%~c|GEWu`n#`M#Fi?H*@hb9YI^HNgmKFw5FBh zXcb{*nDJxIP>Q1c{Y18xM~0PQ4tE37>l)Is8S#Ktoar9D_MuC z`H2f=aYQuo;20yO8gj7`ej_+8xVl$(M^lt$c;;eQS$}7KH}=~7b+9~RnCY4~ZWrUT z4nFX7w8La#Vy%g5?P81!X9jHbdex?*J2z(4oQ}Vu0zG>P`C3JI#aw^F$}&^Aq9-G4SoWh<7iB^Wu;$~&{VK#I@E9%D_8O!LagvUmuA6}usC8J6CGNNkG zXE7YFd65cWb!#58I6iNT=U4L>`SOkCjZVQ&^$ov>=Ksr!ZqtOL0`LXAVl>PlUiK&V zGl%P2Nvl?%{X&s#8RYHkBu?FfmhZ=`YR#+}fj!li*kvW=RXQ=_3)q~=@MNz-dwIgq z^WkT@09NrZG7?v!r-ZOy4x6PR*$q{(((;hGaFB1udHyCwK9B5%V0tb9EkXogZy)kz z5xk%?J-f(IzDvf|D^G?;WtP@d+Is&thV|CdAT|I-ql zr4{t&MPm73XjXB|q3-lvXKx5jJuo>9=CPc- zi5t3G@K67QGx9PPVo!1@nuAqbNS8+!1EO>x;zlENt>H1F06}*|UN6s)$6ymS#@o_L zt|D)f{viq_Nt?)sYKwnhA3lTKMC!Ci&=Gn!5gqw9+JM13ug)W_ntJcuhK+g!W`gSQ zw0|U4e~*~`cIx%l!dAZ!2G54%!xbef%?-lj99LsdSBtxXTu+@h$&=p?8u3mrqkF>P z*$7nJFJw5T!fy4NS0mg_mtc5#NPgWyM>p_x7l0SsiER}b8k-##dAtJ~A;6iRO!q#{ z1t7j2gk|m}d?X-ax%^xqa92D59j==*!Ijtb+Ihq^2ZWI!?oDL8l}B@VrEFv@wq|X~ zW~Gf~RhouOIL<1vlQm`@y8Q&Kq@_}Q`308iA>Hr#wXlsn)$0ug!xQ}+{Vx4Y{Z;lS z!m2PE9+`E%8;tSre2?*;A5hVhV7g^G9JoI4i|LmsC~$|VhAAK0KGRT;Y2?6F@W#|J zeK55)9SCR=5Fb!0pa8rV4g8n-=YpZ6w{e$mN#AN$FtQbwx>VP2_kZsC#CSU(cONTG zY=s20tu18l)b~sV%l{AMy81-W?C$)!()vgA!e1~1_VHQaQ^5D9?^$C(zoCAmKz^I! zpX`4oppxmd>3m>B@Pv@#p?kxsM7)js5Y^aRAX<#hiux_8apd*zLSYX>4uy0I2@AFb z-3*pOngw4Anhd?;qTroD&w~yG4+uJEdT8oyTH=4yIMKI;PrTkBHxdgAaf;~v&vnhU z1Qhh=G8anuxTfJLqa)h%7X(Aook$S4>OUfV=Qd|l^t*LU|`&v2y`apx7} z0bItoD`&5^4T?YSdB+kK$I9(v)K-mOKJxP%sa3yi81FO8 zcd~Jh-){e}0e=N%2h9lX5^^o{Tv*=lh=`(*MWRZWUzpEE`^M(PHp^wpH9B{*T!z@> zn5oeVqeexZjo2UYEFv^Q;>f{~0TH*uo#C&-I))7keH%O=cu>$zQ^SDgenw+apU3(j zxum#Bn5n!6dH5oCTg}yvJ`%vv8fzazwLp98JJ<|XWi8HJY3ZMlLgm|tv{h-Yv>((0 zhGgW<2+43#2UrFK>d4Gkcz;u~`eqjcU8^8<^b^4TO$8MkU^Tm&D*Xq}S1y-ZX7##{ z<=+{E|BJA46_bq8WtbSYgDt-vZ*v~$2|3HZfs}nzcR`l{vsGUFd;W~Uq@!-*S|rGs&L zTjtixbD7^VEwD>Bf?Y5X#^Q+V798_;_WSJOuvjPO46&B6y#eLK>L}^z;(p>O0+Y^a z@tX8W&e0v!A26Kt`Q3MiZ#B?a|L}Y0x558ePrqA#<|5oL*d73qppB4$Ti z4_gwtCUk1>71OwYG{3yQHT4JNKgDJEDNbR9CAt$_vtW@D!6YsT#^^!&AuulA=bX*f zWnHm^TY99QO^#k*s8<5f`@ea9kSS>=AE66;o+aGN zT<4rG9Yw(h>}R`RO#z{55}3^`vNFKBc$R4ZaXvn4AROU?;Xv!3RTng84LvCk)T9vl zaW6P|)8H$P2d!K(p{GbM`T-bzvvM$3z$DAb9KwzJMMg(F^?tB*1QGs{u6gP_r=`y*cH)(%;(K+ za~|{Es35@i21K7VA2L@r=R|goEEMr1Y-{M%;DSL5Ou_!2d?S4t=zB^&;$dop>VZf% z5*`veIQIRiNBChcOZ7oT>-p@U?6sM5EQc~IX&uvMriP@7sn>orNu8hiEpVR?vTfdFmjD+OPH3s9v0?3(_4&n^__~#|!-$ z?KKsfWiFanXR-rosNbmGh*k!})Nl*r_qs%^o{`ws$+j0flzuq}vKwc+vKArdCh~lo z*Tz{DvYepUmddP_8IbuA6nd+rCYa}AGP5%0WW`d;@FFL}8fR}tUB)KY9``fP6Xh;f zT2!ne8K~^MtxMLwHGJ^7?t9O8(C@N;uxV7_1rP~uh87G@jPOLdqJEhF1qr)rY;dlr zxu)bk6_*@0CXZiU3(z6+bC1b2H0Gyyl)1NguQ}K(n%75vi2iKeZGLC|FX}+#_lPqv zaJ~sS8q_(^9#GP6wogaH3EfCMb`6DF*nAH3vLCQTm3MuFh4Qg|FKbI`PHyUaFIfs& z%4H;^^+|1zdiK}JUn_o{{k0)ABlS^gL3(Ou+Jv;VX&ZRGmUfmp^>gWy(i77gWGu`W zo)K)RlG!<{4Je}-);@M$*b&z|%fg{}5-j$)ZpmYUTj(F`;1_WEepDvlcgPeTgDbmR zu7z%zU;k0}54viWJX{v>Ep{V&w6u5`4}c6a(OB%h#n|Apuq7v=W&Wjnr!u39d$DVe zGY1BXso>2%u=cWU&q>RE1^SqV+f+VV%zl{lBWox8sy{Oqfk(f`(%;h35(*~zaLW+O zElWi>!HY4zK4d57th46W3c((o=G^O=?H)2t)_ zWt{K7FQ9B-W>BAyGohd_go8y8DMTHJ>TT{5-99F7?C#hA2I0Sly$x9#Yz#7(+WB8MzV?wpehkoU zCTDL6NKn=An6@GJGTE)MMwy&r?W=4bt!s1KS$VT+WVW^p%@~*78l531eSexGZ48Jp zyQmcOq~)Y-P5V3TzqHh}w`oPw=coUhz9~H;y?(}qjLeL?mZzDCS!=VmGJ$ zFE88%J6WGkz%o|cbsk28d2U}ccQX;7dTKDyfdyhqtc{jbB+rsZ$t9@teMw|(G!Y6x zI!yesvY1X}@u~N5f+*opFJ|RoydZ1IA1^_y@FUDcn@Mk)3E#&g=GH=6b@b5V)`j2? zm9hF+4>HeQ!Z4US`&HHwxI1fS{mvZgOb;H<3WtNb82E18tzouo+hhA=M>(em7upK2 zD9YhWxySX3eF5k3oREO7O^AZ&&UE% z+oJw7mx-PeZH#fmERC(0YeB9dv0Y;q#a4{gKRUS(UdzJc>HC9_;+)l3B>__fq>ezN4tG-f7P(lZQT4DAHB|53)ej8z$1 zGtOmXWwf#QXTHtcpEVD3<`LG{XjO&1yjDr}Ke2n(IXsTt&cks0_IG8u3SwUkL@(A* zo6iyXdq%vcGn}1;B%SmT-1LTG9KPjsLT6$Kl~b(U_)>T6VXBYFs|myePI6` zW_at<*0>pFfHkJufun+I2mgW{APK&?3oryc2rC$#96lJP27BbjsHNul(M@7D$K;I( zk9i&a+I%$Xapcg*hLM*emPe#Sbc-w)c^__|ni0Mcb;A#ZRST;aS~H}2aN(f)rVarG z{l^=9eKV*c7@{8pAK8C$M|lSkyH4Ud5R@a8p`J5ftL}B)aFloK07JR0^>vORr!zQI zyRvGqTJOsIJ@cDot7VgAgQc(KFUww}|7puC%QZMKnp(kkbhkhl7$;M+v<1mCNlC;5=%-ro`)OIO zoA6@{#MjXZ&qZ;(q^-zJ+`zm%Pmc8_>}-XYMsv?KcQ<#i`yL!}lIs{YuLt|M9moT# zK`qN`ciV1I_d48G%=Q)*$kwohB!R2e3%bi4ziM>)Dn4rsNAz9vd+}m; z$O-v|FQhyygU5v(MB_GLuP3_w-5p%A)9g5Bdj<356_7I$LA4r_orcyMoqZjtGCQk% zR$nma?`7uB%AGYRvtDK^=Hz;M@;^(4r9SK>J+hKPTufr-eFew%jc=1 zx?wvEI?!`ld5}jl>{A^5VPfhJyW9vWb~nKNx7WRcb#EpbdKY&kxIA0C%g{f)-0P`T zOL9jMFRDo1abEaM4!Zln36u%HNna%7OXogkcbL}KJGwgxIb6)SZB%&g$Ik9YePMpP zXg@{oOt$s1m9?c~mG8DLwl1Ywd?7poxou-@8MgV1taQf=r4?2 z26*1Nzkn!pmg>nFV20$8+Edx-(1-c#0|ECA%nN0VMU8EYn~aOW9C%>t0ESH^cm}@* zq?;}VE(zKYydflS=;hFSVGF~4zAL9_ zg28uG{Dt@Mt@4aj4<0xWNh0gTUtK z?XWu5!n{2mwv!Clzyh$|Pr%Xi8)#vw^SjgK%z^KxE_^6St}^avRGownuWv#`rLD&f zXX;RQARIX*;Hw^wx5I%i;|A<$JMmJv;IJx!rqvF%$D+;$Fx=JxCBBE<&wc_P$(Bgc zV&L}+ag%`tHg{{bqL_17VM>>5iw`TC3B%=`O{L+^Z}n zKWaG`8DptUe*x;oCH;NEjI>3{`w0ec$=6gYRLUf5U*g0Ub<_ zO(g=`1eOV8`kNkr)l?imR09xmhM3y3x6riA)Ya4+&&EpARUU`%a@b8vO;-Y%ftNDO zf4yI}affk@G1Az>w=9`tR-aBj9yoaq8G0HX>c^AYv03*X58_G*z7?_#F5tV)#H(GE z3X<96+Xs4f(8k|f+u+##;haEya6BwaCP$nj+Of%A(jH=mkHa?0Ru#E5nikUAI)H;S z1=QEmHW$231sM;;;1Kk;>+LDF2R0Jg>`q&ly&QRNi|xB$1}a3Q)^f*lM=@$}Yl7R^ z0H*b3c%Y*2ELEnDy3!+m!AfU^mo5+%o8Mr7YRGeCm(i7szi>5vry(#(Hv%i8iL*L9 zT*Y953U|63uV75O3Agk-Sf#o;x*%z5Fk1^dd>nSLKAwU_d=}5gE;#h=GJ``M6&$1C zIE>`~5*bBBTvK5B{0x(8OLtpl|44TS_wQKpW!#PE<0Eb(+3VrNC+4c*M5Bhm{ajVr z07A@7YACi*kzHRZN~V4|zMBo;63mca$>Y$V_u@lYLuGj*pSeCee3EgxBoZXk5r_fGQVeWe5%)l2I>hgu>Hr7=dmXABIbRBU{q<^Y-`TXLYb)3DO z^FTM*=6nIavD+EU9B%IF;pzaxXeH)oCO+oPU_|tBCUD$Y{96VXzsADGwg9HX`PeOk z;VGQuT0-6Sc;tEsR~p{q+x+Uj)1UFt(bd(}f@@p`uEkqdCVZ6{t|CbHo$x)DV>ND$ z%04P}U0+0>s(Yt+m^a2yUmDp$ucdm%rNzsNu3RM|$o$$zj@o|4b8Ev6>rjnr4lM^(=| z@jcanH_2IYigl!kUOa$6a%=L5Ddbna#^w|7seI)A&Io^^eO9GTY9T(#NbH0zR1eP} zsuiTZQv~9x6^Y!xCi*v?C`uu`m)~GETu3F(8P8)pD(k6fTtVcfFTSt=WKE6YT`n*h zdQh=*83r+b&k5GrM7M#+aUpobYT+H8M~rB#r#3Z$Y19p-AityV%M5mRB^n{nKiQtP zWSL}=B{GiHIiDw*+`7f&lbpfdP>mR^3TDJY`d6zoOz@<8c48ZyBQCs@h*eh=%x$zY z9aSo=iC|mcDwK%z)uFm^ITb>MiTflFJrIa8KO}mxj!K}NM8ggf?;1dC`Z6(~V|bpn zk(KHsroV%T$}yoGIi7vU4gFhuj#ZHX+unxzBcw#~Y|Z2m`hk!zM`|YpQazh0zLuKGlcWdYBl0!Rz^L5-w1;96OtI1o zX^fZ~tGzzepoihg`jc_6gFF~bGK>-vg{I;cazGnlfkaW$_d+yFE=ISTtdj|h37asL zd#wo~uUp)W+%U0CXN26PHe99Vae_Ch{Wlq#-H|KxsOO6#3p|a=fkf)x-!QtWl6|4o zG(4x}?h&Up5Cab)L*Y7c#J`9rejMf!OO*@&}TU@CubYoA5#PBu0CHNZKhaZ<4d^ z;q^J^{YrkNjDJfa^WBU;M9Yb9NMu#3nb9hWV#ug<5YtWPj^l{IHX%2>5IOXDIW`2@ zTbl19kiv0nW-PoovX2VkSE!Et*PaMo11eA|QL|H*-__)kCRFLvAr4%R$gGx2Uh)4T zxm7qOfzO%~&8>^|sMVv?<4Ux=yuxIe=jEDW$)e56Z>sRWT8&T#;+mZ}Qp0g6%9XUI zuB9H81pY9Y`OrI+ypdro?_bM$59YN1G3PkmYkBMCc{bBNS{}YuTM@w>1rwF_p*F!o zPC*j)tm8^5afN>5d3`4~mr6gsBBrWUTKu9_p3{=gXy-4~YkndJKZ_cD4UOR{Et|o+ z`{WCeBwG^S-{xHBko#l~^8P>m`97Hg$2op4zkKFB z;sKvsBOBv7pFH81|J8*&;Tpbhj;~y|R-ygBiUg0h!a%ENc*{3gY8WkLVno>3%b*+`5&7k%Tydp~-yAZ@PIn8xyHG@~Sr(UphSQuJy8-bZle z2>P}h$Cmbv!F+g9wHh0(jw6QO#c}4M^hXr`8cM~8nRmH)4yBGm=C@jnPbg;*=t(^p z0byJjE+oFwvIhKlXk%S_*ckN=P>!@}36*_2_a@-A{a;Oy&AXS$$oR#b|D+DX!rf}s z8FpsRSMTD-C@;uXguU`Sm*G*7*4E}!Mdq4T&rzAZhRmA()qFG{!=|Bk=5-+JtQqsS zEuVDcxjXNBv2|lld)JdkUtR~W^(QkZf!E&Nn&JLrzYpaxm>A(8Ui(r-K7ze5yw@t> zhfpJ}eLvd!egs+UBl*Qhemj=evEFY+;oqB1rp|Jz0@e!)k*ZVp?H}aPcc&ty6<6K} z8B>p~3H!~tzV`gSH;=AdM|++-a39T)ICZ$k3LI05=eq3I<fu_DJJibio0_ysO=f;Qepj1^RuR{b7OTmALq4yc=>GLF`_Za_DsYEd9btJkt){IwM^)yiO1ze&CCbo>+WlAMvkJ6G zRh}!-BU%+vZN9C+L+hjRjIEkHs`9xe^R;!L0qs|VmeguLYq77jY+c@0!*f`jy;{7h z#%Gl{Q#rn=#;Z1JEAgn}-OBP?Z7i4Jx-+RX1wwrpcgawg~t5AN~qzYX|4Y|Y{eq<%mkbfSrmfd5Gd(696$lkZSdqsxd zBea}*ynn~Lx2$KcIqo$w_BAs46Q61IzHeAq}P>q%D4 z!>qCAS&gsodXhEt1{sPcS#1xo{%Uo++O`j^djqTdI$pQ2C8FK!VwK;D*0z(pKrM#2 zfGp1G>fd}bha9`5XmAUNH_azYYl=7LZxT6r!`QTZ%LJbLpd}7M7aZ!<@Oq*db|=5D zCt74rbjz-II@|GVlkd{>|NX+!R)G)L#-eGzZoX+6HF#2G5_TF#3{REc{k!|y6`_NshZ zgRL4_vvt{8c=hZWd|RDk>+xv9uPX5Gb$G4K5%u_on(Wu+$p8JH)|xfgv~L^H$~C!) z3glYWCDXAUTWvD9n)BM6K4^iq-ip`OJh%J5_uBq%^wn=XG^8i(eFvWVb966`8OYuM z`nD^ZhR4y9e(#PRpFoU%D6bViyx+*Sh3AcE zj5~PV<{jBv7|W{}+iSd|UK{HhIL{uwTgP!5INMqt+xUJZ&wuk?tC?TTzpmp-mUDfJ z+2(LX^BD6}yw~4@JMTb0)T323v@%8cL|)|yWj8o#!--0?QGQpJ5E)2thk-IzR`FAR zDj$(1x0!*f$wP~$1y-TC|AtTdzzA_D6JOU|^&bQsLp?1;=+O?X| z|9&z)AJU>OEdA%?neFG!D}u$8hy2cP&pQ{mruY;`x(bt1zfK(ivhoFxg8m~<_BHp_ zPMFNqOa|xdq@b``+#=t1D|x4@@MjDoXY>m7LYv6ooyEQ8qqgoN7-?N;<%5j)_gvj9 zq-9&djCCAFjn)NnLa&j{-%}_n`hvVsk4mXuN`7i%oW#`U5F`4fj9`ScK~85;v)7#L z_dTpJd$_av+;ussH9itI@_>wF1v%;}UX%)C$u>g*p72^BKD10xH3`o}4w<$_<+x|4 zySQtdqn0h1tR@lkNV~PG{SF*bn_acZ#+%`q<}%=kUg&<}J_PDsSMa9lQhhlFPfArX z^2;*Md(o=tN{X6`NYO&6n|xbNgoD1IT#X9SX=Lw52nWT+ROlJRhr+*%XH|G2uBAe6 zo9?#mobH*lR2<0avX?&8^sgGiX7wpmHC;ezwR`N!b6WQz8P7hwToC+7_B6b^LCn?tszPfcKje}caKq3GaCQRC1UF@)nKK#@JzZR zC*gtFfOn^bco9!oSw{Os@e=J7K(_7~UAX?894_5adnu`Eg1kmAgVQVOVgO>Dpsp6q zlA+`)+3=inB!}rfzL|wo8-@y-m1x$@QDDm)rb>Af^Z6wD_iJ)d77M49`mlO+^mI{2 z3N2uL^bx;!Mt~I==Gm+y3oY^g><2?BMD0fO;cq32PpFiW{t^$WQ{woqJMqtq2b zq#|-fDq6uOME5dLUHlCjy0-9zKCpn_6NMZ*i8WMN>LvG(Ye;kG;|A0Ty%wfPr0rE#m;7z_OoI@Js6K+x2DM~f)stL%E z<4CG3?gg!0stqPkk~CJxLruds)gyGKEq3TCfZ^6hP8RcncixYD_v%y_pVIZvm)5nA z-YZKzm#C|#D*O`ZOR*wnpDHbb_o<@PU;W^*D9?o2_znHV;p!sgp1M{VAlH`Xh&z;t zpb587|Ax&bMVBOPSHnFGJsZ`7lD}@Q>=a+ByYb+zQ|=0RrEwrxwPr0Tj(yotya5J& zHtTC5(z&MaJD5qcsrIiY;>{vIr9QRg--L9yWfDE>)he>lu*7Gd{)HH;{=`$fN@-5} zeiP=2N6Gt4rau3eu!1>eMOSPqs=* zLIdPCf^{X223@|XKxS4EFmxF>yQT5~*Mcsf;UsJnR|xUUl2KG!qzJvJl}KWq9-#V@egx-?eQFQFYVxT929-xfO43O$vn>L=l;sP*d> zDi=>tPgG1*gaD~JmFOwpT@GPw$}P+UDBwon@q zKZzv*d5ajDK^TCg^plKt{F)#<%mH2SJpTXR#R?!dRN$&w;myh?mXZ3Avn4QBL$Hkx z^81NA0z^OB%Eh{)BMWZ^_xu_?QAfaeZ3uI5z7#^dcQr&}pg59>0TC#M>6l ziP21_PCA3tEg6sWCF<||y^ zeG2*1qxc7{mirC0)IL}n2GA@kGxnx3^Mb{H>6 zHlr;s!A3gIFW%w%ZG`Vy^Y3YxMw+Kl^UY4+UZ)b@)M_*}nOlQvnZVuNMe9ri0Y$TE z+H<9=!Cg7TSb9rKt)tJ5^R4DhT|)2I?<{s?}R&i{uh4G$+YSnj( zwJV#=FV``)x6wl185?KNU50ZP?eH5P7D_R@6F_3vPQMJN$L4{W(vLV_bd23h zHFu7h)KHKjgH#`6Q81P6cEya}pa~ZHJSx^}uo9$DC$*F6uA5ZbhGO}RMN%(CQ*D6k zs>>XnL0f6Qr}g|p6V6l|JOc?I!~ksLe%R5M`0oRZ6({oQ0g~+`+DSWp8K|D+|DP*1 zMZ~|+n>L?=d(VKR&DdjLFGfzmtY+c?fTq=B>KKIlkh-I?6fI&}m{=DW0IW z#E_S$AsU6zmIc5F`YJqPHa%oUCkp}C3gg9t%!G1Cv9YXZvtS#T$u?M8B#mQ#6v#E* zyr|`Eq{?7RZl#uQ9ax{?vcb#Nqak(Xrg~u<&)L)wO7c~5MyrDmn#|g{UOWY=&t=d# z3UHqCd|n>(&gbG9PzvJjuOG!Uq(Gm0X^7`l-Al@_h@7bO=?Eg0hj!vr=$) zU&2#xk!-hRmFa=^RZ=%*O+c>?6(R=mb(7q;TO*r&@rG(z+VBc0-OOK1AI*_OxH}? zO#hn71WpVz2W}$+>tn!U|5U$FzXan|-#9qa@A_==SqY2xN%&Q>^`-R#b-%-DyGmNa zT&pUqB2%HWrxUmn9mrYxyfttBWg~?6@ItD5SvIG#aLg z8jd24H}KbNB%?3h{+dj@5#)Kjv`(^?BIovS&bpkwIiqs6=A5VEEeCe*n%0Ka(biSg zY1XmuSZsp9;t-7A@wV}{*S5ZJE*y3Ig*RpbC}uDSsc*2FbX3avgXq(goWd`x_Dje& z-X~jhlME?7rO7ou@4q!*uc=R9o}fcPLxNuh4+?o5GBC6yT&||@b>aRIyCOm(2U2^s zJ*utwb5w^Y|0p%$N%+su3nA@8ng&k~${o}uC=(W)nxA%tv)25}#a1`dG-_6LdoX>m&bs4%Ujca2HuwHSn)&Lk6eXZvz~-I71EV`jh1C7EY3W2k95oAq0Ea`w=i0PAz> z6!P^h+Q&P>sPQdM{#1h7?LGjCQAwh#Ix$3w^j2^FVMy@l?c3W}$nb>Lzh+jmRL%IAzB&CA^-Iy|ZPP>24^s0oEaPiNJ#vNbSR8PE7tTCH7Wg8|tBij# ze5g9xPA$;nti#>tX^6F(L>8JH)sBr`6;t0J&{hW<{bE+H)pTP zK9s#UyB9Sx!(kVknSC@nFsB{0z>TbTtxas-Z1e1m94{R6oUNGMH$Z&rh|l(<+6f6* zi)=rCR+>Xnjx-WGwxaHoZjj!O>!0IO&o`Ixf^o24Je-F?0pkPC1qi0Prdg&Jro4e; z0&@kO4ayz-Be+dSTF98t(V^Eu<*=`zNugqxDXe1H_^@SR&%=&|Z4W&dQY*Ml;64Ab z#?3z2`gENG7M%l(1#)ASE73>wOeo1yTU=SG%M&>aG8CurMj{j|6m=ygij_Oe7}46mNrTyL1ca_ zm(mr$id_Tl^-R1^^~KXf;P!yEuo#|!5YHd*3)FQzbS@_!w16`g>stw@ks9f0&M;W3 zQXI#Sj7j#9_Mgb?kJfS4vQ`CVTFF|-I>dSre$?K!YgCtZfq&+gy{x0BW3FRA74NUe zGOooLSFtL}uGX$Kt`9KZZFH9-Pp%Sd3`6mU&lReQ$EoCbER7(4uM{nLL3dsEO=qS0 zB2+`zgJ0pZzK~(L!Dbli^8^053%+s2f#iZeHv0M%^egOF-fw{4WWT@tcKW6IMfx}O zpXh(m-|k-p#;$1r&jOSHdq8zlQ`2Z_*~^(826PJ;?LXNs)VR-QtiexzNPdq`GMGHU z!eoqn;wlQj@Gyz$iv+MA$4N0#KXEchR~wWAo~`aKuCvY$j+^$Hc7uJlZH#RX_dMEu z%pO5z^*cu#mC0*Z!Jj+tIWMxtFDIingO(^wW_53SJ$nsr7O;!jlXz#kbE|6<99mA# zG7x)iU`v)p`<{!2Sp-YUfo@sfs}JtLGyf0hEaO@051ojOc9Ea zJ-5Vb_tcV>Nq4|$uLPF&RCzw$$+_qr)7aL_JLCf(g1?Y0pnu1ax!H&e&VITvummp9 zjn_@nZ6wDt5k8HN^t(>)s}InJ>y6}yI&^Qy5`9Sy`BV7KR9!iJXU4-ea($oct@^L} zkNQ*krQq|a;2rd%GC2vhvAN*u)s@TQ3oR_?_VeDx}-MXKW1*6x$ zp55fmjv`C57aqc{prs80p}i{_+5i~GMv`0m7vp?Am>nBEvpjRzOCS!Bz;``h#B7VN zz8d~_6Ksgzsfjp3X6Qd;>uz+PBmej{wQgm|)g453=VEe4kMUn8$u!-{`Tq7y_e=(r z><{q07J2he_u;j$!hsOQwKW4tWI8dGiy$+mgQDe6Tq%w%iia=$?KCX@4~(QF#?TAA zD^{$|QbaVnU{m}_>~0fna1_n|GPcAeEXt$w{v9&E{=*XR;BzZLEWR~%nwA%O5v)uR zKFlh3vwCBLZNr=SoQzBr-MkpAM7?12{2SlY33#H?VJWN$638Oh;NQyzvW4rD#oLFx z;9+p7jG(f77;}F%ES*Of3EKP*C7(N4_ieyTHvftpxM zXC?FZ1S2hho;J!>={WI#zT|*Lk|}zXJk>F19tE-YZ(xfP55pTJ3a7E8HJ+NHcrreL zB(@){l<~w4>x1d8gRANq?fo~{{9VbLF75Gw#q_!R4pEd%^v^t^OVi!6iRb(QC*B`$ zv-EPeasS5LtKhCkJSU1uA0L?LBzLw;0ZGK=lHC%FU7;}46{c>Yj<<4RF4^UOQqgxB=1O|-Mh`jTIr8zjYAY)yEy z#7gTSbz=1$z=|{mzJTfE+Ahbxyo#!T9nx-T4_?Y+R1BP?zU~ZJ!1sB7m+dNhhxz6N zNA6+7tdSOAF;3vu8t*`VsVBL<5g$-B$kDi?xr zpXQ8$c+maBLESNcC}{y_=Nb4A*F5K$hljyU-2`sgGUoMc&seOn31rfbgVkmfd=A5S z)_QCVQeh$^`6cA<~X=BCy8-Brs6mq_7ZNbTjV}iUI=7sC>6FEbcz}Sh`)z!6U{!P}cA-;JYmZCRa ztcNIOS~(;6sM`u>Za*sIG!FQmtO&bVDY{^j1j}EkCD}?HNkb|_ zZOD*4tSa5`iiY4tyMeE8j@LsI>dmp;O-8~X{F$Zby%eO_0p#*<@I)#Q!}h?H_!RWV z9rVqgNZH2JITU6#Nz_AqL#p27q4Bb9cCX{HfE9C=dmP(D*21xP0YtSdc{oT;tFXvV(YK#HnXpn7cjQD(tdJ)A&kZ-ZVD`j6JQ{7ld`I(kIQM;XwH%0ei>i$eI9q2u@88k#O%G# z=a0o3-p|j9Phd;BOXm9>KE2{S@)rBIIQ}uec)|9P_wRU@#B&n)1Fwkny<>X;!_rIU z-h0?m-h(8Y0xM$%S@hrFamnUe8%L&#|8eXmj!WV!*Z7w!L{3kED!mIXqAgtYJQzks zkYlg0z;(itS08V289a0O@TE#{!TsRQ?+cId+Z_=OAg@-k+6=}2*A0K3miwn6kekU~ z6Ty7@MfSxDG^k5>EttLyumsU-i$;BLh+D?;!!Kg zY^{k$t__imq5SJ4Zx&4=Ucoc0o?7-;3jG|49M%x$H9iIHZkFJm+=(Q<#VVRYPX~am zS{#nK`rvsFp^uj%fwkHtZB|=ES&G2VRgUV0-;uN9U`Sa()M6hp^D4I57bzKzGAG_L zA3SFT;lrr}&rNffZ#v3@U@jXEGs{e{lvkmRts`o+8w7!U@?QA@n%Ob=q9_IgA?nft;-s5z|U&QqlZ}EIXy2(pz-C3seJbMW>oXY`+~` zTSd7}e<~@G7;i`L%PkiNP%%=OhY=6mcko-UqHnEWqz@#QwlQrIK~Fkqr^oc*F0x}L zknhwTpMO<)D-JCzi?;v3Ji5eO-wb-$Of;M!JbIJw(}}s(nD|>2&(S6mtK2k;B#iw zM;>3 zV>>&N82U{VQBLFW zoW}1tK{uR4{_bN={LA$&{_fzC!1aDE3X*=55j~F9-2oz{lZ@*ru5aNP-zPKon)kfG zI6niIPiBTmc>0dQmQA`F=`OjiPL*^j#c|k31Eed?A)*19SB_nX`-R z&(E=4b`;|))Bma#2 z*F#4BE_tGp>_`dZXb!M;cVf4e5CP6-znV^#WFmgnFnsI5*_;TWCM!EO#PKdo zY?~X_ZZ5Jz+2AKcbAALed#Pl(B+i#oJ@Ezq<|D^Tj^t3q$vrX{56EO*A%lN`e8fc} z5~WM#X)OI2vKI%r_W=K&;Bu6El)oo=^b~)U`W9t{-sj#syvIW>N>?03WipBPe?tXD zGCVY;*22R#V3Xzh=$N|-^Exa0vBJJD4BNOeJB`93Z^*9GitAR)_omb?#1W|vVlnVLp04NBr3>9HMn@T!6Z}mie|envY9H2IVK;Asd8za=QnHkb ziLd9eKTjb;GaY+623sE=+N+0<2kXJ^*@KnVBgAk0gFQfD%GSgBHpE}A%XNJs)%vh; zYmry}Gh~@7k&7=twk;oaq%zj}e@tV=mShWA6h~-}cEFJ{vF|&$kgniRa|s01T!KLr zc_KFjGhTLg7e^Y08~dZ|_h}&yAq@{84Lk7_d-MSh{TY@+u}z9+kj#JYu_uqYR=RFK zIfxH7~?bgc(whc~PE2VTzYy z_Qc=^Dl@1qbEpoi>iS$-bF{<8cjme++0>5s9|QSs2tMRU=I1~z6NswkFmGpby?{eu zDKF>v|14*vOV?iJ{x0m#AuQ1e?9*{<&}Ay`u48{5aQiW~>;ZA;!;tUrn!KITVd;N| zQUj(`aQ(m@$y}5USY9loABKmbN^9pZVV?}4YLHwoLyE#PQF=-#7p2FM(r2p#7PkaG zX4z2HL>2z4PUS@v_8_HNqBj3E9^g2~|(`0y7&9cJ^N&C*D|PcGwRY zN;kJSyndz9BNoe?%=zxeDCT9KzQlh197UQB!BULc6wa@X=y)==ZxNrip6|U5>$rh$ z*@#b-Ilmu>6>gBD|6jeHf@n6R4yi8hn}u)nicwQgp^C6x7Vxc7QRrQY7W*qYEcV8i zXha;5m@&+A&il;Q-;yev z|7)~QQBxu3JTovZ7|ov74FokC(fcGkva4z<9p78f`_is)6Oq+}Sy>x~<{12qS=`mWFqoya^5) zI|}DZ>EqXt-FGqHl->rgL_XoQq~bAM;W^Lfrn3bPr6c>fQU%t6HKFX-v+-Qkv-cij zW*%dg-N0Twomo?hGp=-?QJD5R7&IEzn$iLB7yf~YwdjYj+7!-tW>yG=A2i752h%%_u_MV;xP|lB`6h%quHaUa(gh>JIRe+XML#E zx$)Eraqgm zYEbeZD7kXW0h*dbCQU)hBQ+0Du&+u)umul7iP{uR?VQ|Jx-vH6myE&A8%3oph??WB6pT7k7>O0h_=F{JL7P=Vod>ed;^tM)$+vo;5dyJgm z9se`Xnj^ehXVz#|Kv)fktPzBE5L(Z#L0FrBpD$x=exlPt4&ul;)KMs%HB<2kj98c! z)XXFh!IX#dax295y2+gXTl0d#;O<1oue3gGL0vbp7sqt3b*1$a^oR5&!(WCwh8o7n zMxSwl@Kz{=8co2|jm*>ov4Z&@^C`2+QqvM=Ibcb%M8KjLZ5?GDX)S1dZJBQQU@mSx zCr%M_h^eM(;zseD7(^3zkvX4vn^;Z!%d|jPV!UklsLzgWBnr>kb@XMutqw37yD<{; z+2x`$ZU+Yjw~;s4!7gwN_s1Gm9j`d=T&E% zvpG4|GAK}$BiGo(+0fbBdBSya>@(_LOvEEyrGwza{?POOcS9Vud zcrt17bJ>NyvR&ROZIFsd)0~BzG0yYp(dpsonbTjUtxkKH7LVTXrS!?pvgk2iAzOZ2 zz9(mQwRbIX&2cSr9f8SGo%b4yQr>Sk0DC>Nyc2wj{Zm*0%Kj$dgPtaS-X2^9!ZMT9 z@SfbzXmG3Du-P@(mJ)R9y~HsMG&u>ZzPRQtD8nu+)fkY4HSA-XP!7I^o!>z(d25FSAX zkXQwk?FkmUh+O3*e+*;ynXIWhSP>T63gWl}MB<0Bx@PQBF3`wXnx5LZx()gchM?h# zaU>k43#e>sVOQ8p?Mz2aoy8nxkNLMH0v$NDJ<5L2w$9q#QVA8X$0!g*h`Y^hOJ3U! z`wfRFte0bm{a@P{>m&17(I^f#eHCg7Cyaj?|24ePcSE(NkAAp*f&LW?gSPsAbZfL# zHAi6 z)$-j&*R&29DJ74%g?v@epPB9|ivkgxb)}YqgTZ;ATt zSohiBPp80@^mOF@4w`!tza&mw1m^M`EPQFWkH7Je*5Cn8#beH&@s{;n0OivSAnmmn z!K)jP6bWkA>O`uiZJFaS)v8lK=C;>t4Yt1a*? zT&iAVoGPFx^jzDP^%2k~8uA(s8O_3StZBGunkk3)FFF1KmJgQo*6g;6*80{dj9e|T z28v%l1(R6b{LK<(f8|&jHZ$ytqo?DbZKP#``G&Y29jQ&i6I8((8{Zgq8gd#|>T?q zjh4OA3hA`dm3|1->f`8F&rZ+j{G5Ir4$D;M5a&9y>f@!EC{bR7GZsUonuwC<4CiHM zZE3ocEKNc&`L3&~JJH?DbH?NJbcHwY97Q)Ae{Xg;P6d6`4z+-~waav$ zS%RN)6Kp&IuVxr~Z(VSuF8DL|=r9w%_u9&Sy@4!P zB>Vc-pnx~rfOud9%+=iNmA&!7kJE>sFzn|)G%d+56(Hj?N$=Dbg%g+^?$Ac#8e?W* zw6I#JX?kUvEsiqxwN$r0vKFyzv_7?%EUm=crmd)fof7t%c9@f_b?s9eBg49esU25r zm#unBJMo37x9OMAO~@w12_ucS4eJf3VI$2k6f=%94l;f>2!`Lfh1$L9imFsN5u3=< z)W^T*5Xcr-?XQg1^=23uYteH%?H+<^;Q?%+3%$@JG$w~g^`)}Xcxkd!hU?B!X-S9v zGp-@CkeR#`vbN z`xJ%mGQ@Y#_se&jx*9EPmuXn2zCpa^fuy@MwdijV zK_~P?yp60dQ_g`6FQQgsIIPY|SlLBn2gZ>dD~a#FhndiVt~+%>s(;|Yv`3Y5CHUtV zqUhE{SW5NtOJbzI(f_`Izq5!8<)X`;wGpaGh8^eS#!d4-|w9ZsgT*pdlVzHylHy4GzZkA~CG1ECApI{Nr3r)p^ zmYueJ4paE}aDUiyM@4%F>w0rVahT~TT5eq!y9y}Owlx+t_Cv{Sqp^svg!|4ImmA8W zvA#@A_h)iw%L8$!UhRbOFaY0hpnspwhEJ2j``FXdWAkie6?S#4hBrDU0vT@Bi+y37Usk_ z&lb;r^zw*Auk8wyeB&WItR zc&4gGJa!88*y5VH$w9nonflsU&Bnp z8$)@nxOI~HGmcrge>P*dPr6MvfOY6G=aAkw_u-$OavEXa z#<41U`S&uuzU9QOXn&Y0})M`XTP8QQ{re>rPfjUJ!Igr~__(^(WTL zLu_jp%2AK;MJmCvxd)HnDA|ZTTz?7X;2tY0FoKBtL~sRORf}LrMlmOq%-WzokeA)F z4eKz4&tC{CJp$XVRtHrb@u`+j*;JV9o);ctTV1Md75>a?!vORT^9tjHH^Lu`Uc49y zm$8VYx@C{~sW`>dL&$IJ08`T;bTjG9X4?RVI;<`QP!$~wZ70n!;zHBk!gym#<7eYO zV|`9QNue{Vg*h#b~ju^F0PNd2^C?rn13~cy|F2+$(h{`R1DPh&!r>I z3{(wYqVzl-eaTktv#w^YMf3paiLcT^8YwN8tT3hX$_3dWk4T54CDKIc9X#n+)@miJ z<{~Vt*ZBxV=DLzrJ`JaFk~_Pn0UF$sJblos^t&s1+IR+fcEG4Bg}q*i&SFL1C;ZgW zs1)7~4kZIOl8l^}b)odi>I#xknV2(+>a;2r`#6`3=?`+z4?s&^l1=MNu0+xI+r^5T zLKopmAQPSNR5z1lb(5Xji2YPFSCr1@GIsDf@81ku>Kz{NSVkv?4B?|-5s<|x=s$hI zW0?XflQp9U`9%@rYCqhNA)uPoz_q)<$S6Z*`x_Cul4HLV;?2B;i9b)11(m{kx}o~6 zhDOGVjNxeEl~C2BHT6XAa=f@hY(ejrP3Es+8L^EizwjO1%z?&H!dcTrb5mO$$4SR3 zM>|I?dogR0c);Wl`WZhP48)mjjdu*a(b8OO90Q|&FpU0n#!1GD_%lbf7LA|Irvvb~ zUkB{Wxuf8)Q-gVfXVE$J`F@}>8tHB4xr@&1IQLzb6_)z}`M0!7N|XMQqZqBL@;7-j zyInX8{fSav=>pMU7CD>TUoJ0SCBmp6Rgwy_TNaX^$xB@A-5=b=Q5CI3U!Sw?#dxM0 z(HVV251!YahTffM|NizZLR)hzodOr2yg4>FHy9hd2R~*V$l?7!8BkxPN9ITRh)U{c z&081)n?OOnf&o2ao!zI#$WIQevbsJPNk{g=U5xbzs(wnVpEI&8z&0}&!Qa%rtOp|; zNq#_yIL;H%dxAzX52+wHN><`9=H4h06RfiS!Y^ZU z;}l~qQyy~(YZ1HK(Hb4;dya|rs~c_{F)gl^zmpKs7>8%IDLG^dec3(JS&OxSGw1_ z!dx5WlB8<#;iv49ugG?nPfnJf(uW|goQ(YxVHRhW&GLKc6?F{jq=j5Rlw#yZ@=RB8 z_er;r>W3yC2O8bi-H-8izqsq5DqG4s7?sgR^!au8|Mgd;PeUBBVG36CJB+2_!Omz8 z%p-Hq8O(77^$c5JemFoU%2I#&n~d~*JeGLIsS|ncYE(Upz=yd=oE8a7AcYuX3%PI^ zjCm{=SphPpZOKwap_zDzov}PvKw2;=RF`;<8uCama3$hvgwMJcu2vKHj`OhPAA{Z4 z16P69)FJ=AnJ8ywh_`k|ol{d<6HvF%q)=0SM>~NP7;A7E=Ccd_Ev)CPw=@kiUBaVz zDQ+?AEjumI*3FhtL=+=UCxp+&>O!RHw7AuB)+ReXgxSJZhV^qiupPIQHkU#p7PP&5Og>5!zl``VFVW&sY8`rp>Q6@F%~Z$R zX~vGYMtUq|ms^sg>+QNm4MRD)YPEIObvGs2970E|E$+8eH9YVX^zQYx_FebIz%z`; zZoNc9)E0a}x40|#_FF?*qiayxxP~2jtZD?a=m-pgL-?Kf)X(r+r;_vh8?@#uIICji z_Q2BGO3XL{?!Xt2oHihV>0}3#Dzx)p*0XUP^W$cHq7{}gwoE%eEGAC2;Oozdm z*$Z={V$jKVIS(#al^yvdzRYwynHX}J578JY#%f;-YW!4fhw<`^8tJ|CX3DCcq2Fv+ zXMAAHBy1Eqnc__?&`(N8;D&^3$c@pNcO!lwY1E)r91kEH;lN6 z>i%52-I~oj(^Nok8DHUl3b4sPn!Ki_V!A2L)XbtWu_ACrms3U=L-}({(*nEl=C+mM&eEwXhpDiS;%p8C1>ozo2iMuhK2b3 zCA;5B?mNRey9qx2ft@M>eq9yDt`=ysL!Ay5{tsA*!s18e48a-}Mr4O(yW;FzQRv z)Q!+^Esbi%Q{8R-1w*{C0MTHI&-lk=yHe!Uit@)eTYiVH}Y`H8Zo4T8t3h~BN z;}?_K^4zL+6b~O2kro~k_R+4k*)6|KH(0@s1*hO4W=s_Gi1Qf9jd-@}g#*Gbp_x#^ z_=lm3eup-*rX^Ln-%uKw9qL`Vos6_O!xDV$|KS_#yMz*V7F4+N(8)>dS>ld!ts^RI zAeEr%(!^D?)u#bsk z*N0&b(f?t`yTjg*BjYu3LT~oF`!E!%!cJU*1uqJ_>M6E-F!<_KX3>7|@KbafOeFhi zAe*5m*2R!RdkGKkm8vElQz`Xy^$3)ojarp1mp)bB-;m2_7b*)ygbu94pTb??FH-?g zOP8lZmYvp_*5j7$<`h%1@YLABm|K`&`eB}I8|Ii5K0m@3F)!?_{fYIe`J`zBtF1pi zs8_gTvWoS@cri-c$4J%#wV5P*FupTv)mPI+YT^8nr@PDQyUEPV%sz3Oec)Z-k^eYd zL9%*xQo+~KQw1;NH`Z^C>n&*a7O5`L!&fPjTti+WuLM8c0)E;;ddaBSr0h~ENKKIO z+(g&07f#7pNBS-OE4Ol8aD8@}z+YR?)8GI$__(Y==Y6relgH{k=WU2a{3UV@c|o?i z`iIhMeG16(1M&(v$oPE<)F5x(6P=KKDh+wCX853I89}9YxY8TB1eWxIDjR!ZJpO5a zDs?CRaw<172!I5=xXVX<{3lDJIXQ_b3YOpG`f z&iXRy4(ia=rUIDTL)4eH1#|LQwXwonkv|y=k5(9yCoAni|U*1Hw%6G3#6Vw(y~m z_Q-YN<-_vXZ&;>?R?}snwa|;W^1dmTSc!gyO~sq0!KQ9hT(vRTgsDUuGxdLIpMp8A zK>Ml~Ir4HSAS%j>^D_zupHtcQnO;J(=$^UEGlVgW1p(jfIwF5!hx^X@$_b*~g&NDg z_@8z0KW)+?=R9X0XDjDW(6(pJZS?7x=)B{+@6-{443mwnnJyQ-UcS=H;0YZm>d?<; zn>^T+jXo!zJs-S%sH+|4d*ypVm0B{je)Ij4$UbF-bDxi%VOIls&h^{ij*N<|JwK?H znGu|cmubN>c}5*XYtXn~csn-OR(5inH&`QQ7{giYkwX~8qjd2qLzemmp5+O8=(-s# z2fLvq<1kp;c;3A@JjBNM<(8m;lK39DnQv+t%{zcfz#k?d%RCQLp|}HM)W-$LHvEv zecSa>4wLIjw~5(PsIxpIrQn^$;G>QP-#$jo<`8EmGEb!$$3LiT+?D<_{V(Tm*5-a` zIcPWa!SulXGU;!VV+@We3L9&Q~jJ`w5)3C5|$;rO#< z#9?BBI8rPuCYjWxJ;FL6uTal8$WUKjNw-{67PY1Ccry9n(BDAY(t|J7C|EEMpliWF zuoHvlBaz!iP|eZq?XJD@DYAq^ol(w6>M(QTd;UdN$;0#>8sLm}exNH>XF3oaOOH>V z8|q(^DZN5^V)}pSd7P=vBxyVyVD8F4S$#R>UDPwS1s_P2*2v9Rh4(OofH9{P)H#>3H(%edxW9OzH%HV&VpG&%XrWF0y)dQjsV0~7|Iee91 zMt*P@C4NuAyDWmgB7=jPgTDjmbi}>_GruYwk}CzPu=mxZO5izbY%!~CD9UVcRAT*! z-OPl3nki5Ye61IK;XVXPQAsrvORI2#f1qkN9n0MlPC*yA?@Fg#Nz)pJbT0ZDwAO13 zZNS0S5ml681?@5ABhBdbl#VX7id8V{C&woMi7!hPFOe!nbS`s|h!sH7duuFA>*?)wQu8d##4?VSqf)?~9#(Ts&>EH&N z;ej0??ySh#RD(;M!|%M$}}uJAsR*ac6J z%Rfl3WRLa~8f#DWdkoF!E^?RZ((b}|*3dDNPW&wnwluV!qa$*UJC8&3={3x_-EJx>cU~o{yfzbQo&uO9h>oPhIFLMy-JVr0*gX@cH3k z6hyTv3Qwds?9&tAEQMhgU8b7iCM-xD`&$9BuO4=&YRtvEWQr%_qmETwg~eHsl{%Qb zgO&{8I}oTzSkyIeu0OM$27npV0|mX#PJ5X8nGWoIe^U>sry??rx|-&waRvgNz)RN$ zOQSWUXszxfraXrwm=j_kEm!}g$xk&ycJhRW^a=VrhU&(?#`5GT)(T&R+rkJ_8FLv+ zRd^P~Y-_FC%t`bG%4`Y;;d~-YA;M7GJz@PLn?+s=j{_fFV{IlznVt(?<47gu7c-kv z#CY={i_Q`++Qn|-Zqt9p?S>hK1pP2}z%H5#>ij4RJyz9I--Blstxiyt%ji$-`x##j zZ#hqX(C7E`$U5rI@P%*5}Na7arr^(<&L~10b6V95F4J%Nu{bq{v<-F3b;b2n z(IrVVsEPH;31LDXVX|q7*cettymgq(WzA)|EB+yV7P1(Z7~UDPh+oaIHlL$oMDxh_ zh#KK99BXXhmLXzwywtA3V&chWq5~z9b> z68cQ4C@n;xAgl9sdJ;YLnmCs_2RJtqM|8!P$?YtOKYG{Mn@;8(-tw1I?1R?o8w;de9NQknaT@jyu3~m`snhO8)QUog(S2wGs`}`mDe=bSHd6 zRg|J>R73R&?=mx$6459K4Zt6*1a7$-)JqMcKMT>sGCa`hJV%?c5R5#Eag1T4?*;3? zd{xwwn}VvX1(j)x1>K3avV_QQ9QphBz;0ILaoAd!+5P@z2Ha==EyAaCM(5}RHncRF zP!@G->b+J{zxgNo-%mPx-O|r7*jYRO7>z`Mc9Se5nqG>h%q=XV=^|^gSF*LX^f6x( zdzd~LOA9Vj7jwAPX|EU2GcsS~{fL$ko5POT`dg-%>F!44e#Nvxye_skn=P9yIV}au z$APgWka7C$-03vZ54f*$AhG;$vWTsvw`8Hzl7z>4 z)j3Xz2JLQ5hr(#k)eA5%>VeP=cI|b2Bcgxr)|0Dv;;BMMsK2Q59R;^Y%~@=TFX04b zJ4U^*+dqq#{%LT3#zO2wCs>bzsD%0jUZzGVZ#zEd2H3z^*%_1B3D$#pKj26p?q3aS z@e&dGZY<{{YLlDe%MN4|hJmXc1HY(Dj$$kRNhO%7`>0Jl0RnTLTMyX(%yeOFOl{Oy zR;t1*4+aZ?x3$8Kzog!9v#J6aX76lNLq59KxSON0V0U+SJ%Yh@0_)cTOl^}~Or9WB21{EHcdfs3 zgmX1nB@t1Id3zfC0UD?>C(DMswe-FyP;CCi@1ISo$W02+KY1a1}%+amMjM2 zzT_S2tKol(I%FlXl|{*1K7^0dmGwA>n(Xa?coc2Nsftn?=?2|aX~wHx!R>s)2zDky z^P@YuO;wpnE;V>_dw3XX%@xqMIPkQ=U}e|HG@>EO2u6cI{f~iB4IgePbqL$=W!_P( zI*!X)_Q6B21#$;AK{>9qCGTodzqkn&;(IEu2jOA-3QP-*BR4?x z6Lrx?sW;yYYc{K9A}TGr;Xafj_M3`j^Q)SZvFHW2Esjwvrdg$aK~{YeO51&i)N6v9 z^$kYCe>zT8eQDzH&oBYRj06}ln~5Qc2QmlV(A}~TyPrQ$j7o>{D@y&y_wJKgqFxij zTGUHbB%jnnJ_ic=2U(?4=%wWKm^?b7l%MWsIx9B!e8Yln^45bLRt#p?gJ3)I>Dj;- zMiPndBZ|%n`?E6jHfI$5PLx+`YtLwt>HKyBj&eiIE0}B_(Z1~lPyI4BcD%ZPW`*Xs zrWpL?iD)$b3tsVtUGEwzst~bXU#dzR)Fb``!DvRrzkqSMj&jCaqWPcrF#D(xH{g-3 z50s`V1*K^)?z(uOJ*iH-7>ZdMf~*Z!4P)0ifZ}m1HS;<2L-cmTZNm@vWS5QaV4C@a z!kqmX=2n(M*5$TdcCF)vV^P>CT6pBJIxR`&!=fl&GUu?#wtJ3G5mlqL(M6(0MYWC` z5pHn|vo*FAu~a06uVog@zr`pOjie7CGpC)^ zooi7Uy5+1)zJ3_oi&!ZGWvmy@L(Z+vzA)B?$T4&)tpf)x3*K!T`b(~$2Xh}3*M3tA zQHO4nkG-ROjpzWH7wq^hx~ED_<1{jPGpMz`7o0)mZgFZa63EPTMUh~H_M-MNwyTpi z5gzGzD%w+38;N8);Fo=dqj(;D%{DM;WU~1E)u&aT@O7319l_n8Zvtfpc|m$3$`z$*<0tI1D~;s2Z)i0!9= zpiY!Z!+F}^+UG6_I%h+buqPcvQ&2H1?EOV2&l&#j^^Ae~+Vr5KO>FgUiseELER-vs9DQgj)H`6KBp`;xfgk-sGo zXcLf}1|V<)K;*LWY=slP4=#ufM6`o?0$R?`$?fJMFL+WP)b}&2HvBTA7#9mWg*v9C z;#G4&%M?qz?TY=Xy@g|SSXy|;@Yt~4j$ddQ?zL31l(nRo?^r(BTZNa3=p0!i`p-=5 zqZ1$Jl8x5*3C?ixy;6Ux}4fe>Z+>U z8N0}lHK6+GBTUIUa9^YS3w-zJy_-b$2mt|^ljX zW}{c|ZQ_R!&a=)^(kW>X?9l^6{Y9iF_%<`~XD-2xN|ffylU!}#I%&Lf=*~7fd37}(;JoP7gQ)3L5#!|~ULVfbBxu@U zO^vz$`b>vm9zKDqQcyD-O{6nCqc)uCwp6jSC8nQGCDAL;wZ<8bK+&GU$L|m@U^DB4 z{3U_zl=i@6e|DI&pFm_s`08=F3%33a?aDR&U!mTXHNe}(Q{y#*DC0jo)t;a_vlP~i zN>ZPK`6v=i{Hs?PI^oNd0BgBsY$e2+9+}FDlGx2M$=cG^%s$MqnU2qg!m~sy30H^J zvk$hNAY14$KeF7n)`5XPB79h6{-`!l^P|Q`b&2d9ZVU_9JK8Q-=9(9SXa9hqSQ!@S zG9g{)LC*0H(>GzEFw^)@A3)1HQJq_L1~k4*U>yAIvG6&su|gWa59|hOd#@|b^#-lS z0$9#*?0I(Qk#r%wb6U={I%yGU=Tq;dzD{+czUNOpn>Gu=IcdLeApJ#=*b?0bq@rpdp==kOiz{_PF(4kvbUkkQtGL!G1V z`V{8YKX8)Uz{LL#wW3q(l*j!?{paZt-;A8s64qlODg|n30-AH$^}1PlkA9xP3S0iN zu@2U>h^aM-ZAIZZHL;YnKCmseS9UZG(}X_^Tkq&?ziWGEU1CWv*RYgBsja5XXI~qZ zExcrSU>Obn==pX7gp%f+P6WC3D zs4uCyWvqi~_Y6exwC^2ic5^(V-Q!#*S$E^Giv{sIi!maPoF$|?nID)x znzLGZz`K}cDFp|rgn2cn*$>khQv%rtpSaIlkw;Q2uPqxaddo(0PIGN>ndu`Py5}3u z8=?)X^eX+Ix-HtCblP}Eoyl72*=lKiv6o2fJBPu0d(y?DbYMFbUZ+voo$OuViSb-> zS9Kqv*xb*2O{gM!>TiIm&==|&GO<%- z5B`Al_XLfwDS=j4zrq1Oaef5Z*FT9iKEROw6Yu07qVINioQ~irdfn#^`0z&t(oeez zTEiB<1MWyQl=%9C5$vZb^n?E~Dnp0a{U+kOboMvpx&1(b;?XMFL(N1ojNkHsA$UJ; z0=dXTZ3RjF9<+i273Pwg_^>*4ckRhD?Z7+zK$IVgFExq0@+;!AJj9}P=+j#oMnNGE z6AL>*cJTIE_*RSI_*_J{M+G0tsws(fR7?7Jk3hL=7ph&SHJ_+>%h2S}wqYJDLN#WO z_B^V7BhmTmsV#uUURG@>G+_#&4bxiNgj=<=f1wmppPIvtTuP!C6GIiEQ*#}quPLa) zW#v<3qE|(^`!5&_X?(*(e0QZsVt%v_zk)CC;&;>mgM5ugG?};yrUp^mWL8sd@@@Ax zV>8fanm~Nsk_f$}zqUUQM^67QvVdF2AdR4ddrx0icE=KEHe0E}Ip;k|jQ<~%GRLV9 zdxOn=g(~Ge6qd2@-cS7Zp8AW|jQb@zYTxGZWGZ~~^ZFLP?&w1Q&1JH08S09&e5GN60efd9*ytkc(o)!9E4aO$V=cGWqtCU9$7i5B zHk8|aP~>lis!e065~9&5Nd?n6L9S&fwnXU(S{iG1aCSHcX$ws~PJw!!Y`J03i<}VJ?C_XE7Nov2I z+E5dFg#&Lb5803Ms6;pSH}v=B7{)xC%V;Vis_b}o$@1Q2t-gWT?(v%g;f!aMKutz6 zj$<4aVHuI*0dmiG$TG^*j%z@3ic(MB5}aoQ8IX1G0j`4ty=DCd(4H`Z3{-}#+5xuC zNJeZs%#YjPy-FQ%38K0-oWqG^8Fn*bH^@22aJLlYm-4LX#&B}}M$ZSPsQMUey<{|j zGSDi{iz<2pO?NPkX_{fEt1m_?YLn)WW)D>*2T+AO!rI=*J#$!*Q?a(=SnreZ{3dH= zXy$8X^LHSx>xCVtN_}xwI(d9XbLcdDzj^AB)Ok0cvM3kz-Dzlby(Ej32&TA*Z#j_4 z;Hr$aQcwS#U!nA}o=Dsf%Xk|zQb9znkqg)YIx~Y~Fn+w!$EQA7^+Lq?%3S|Ww04jB zl-+^tQhtY` zn-mbKS>EKAWIAKHncs5`WbYAJ0dn71isD3UsK;MxMs*0B z$F*Ruhsb|iA?u#R@tgHvqQX2IoK}U~Q4aocV;D_Jr(C7`?KrB$mA(?oSVK!0{l(P! zY(^nwCp?qm>U=;(PM#+hfNYvG*wYGUdq@mc;7hBUh&MnbpCAb>i`Sgh&4#&7>#b1dqt+ zDLSJEV5un@QENdt7h(P9gR;%%)+FZ79M;7!eAxaVX#<%}-O+XHi;e5Wr3Jj&M(leX zxo(SIOj|ssR$RB{x(jQn7YJNeo~`spZ_MqMyrwhn&>1VFbj9q$b9(alU|0qtQKp&5 zCn~)V6@{9){GAp0E=mu?6&$ParPshYQ2HG1LkH|IGhETVxe7D%0b{Fl68!;srSxn5 z$#4BmY~ukd%%C?+II2hCoC~F!brDv!qRCkfj9=+dTO0hQ4xFM+oWq{1qHcKiqsS1? zKyP3vD{Cd^d}&B0NICxpLdRilodol_!u2IGL$|nfozc2Um4niM{xbQZ$6Q`9H{OJf z*F5J1_dntBE6knCJoA?7KCy-J+DnY_NwT~LQ6yR&x@=&yS8!XIhhzCB6GO9d0w|5r zrMW$s`p#IVmK=>Z>hXI@piYtJP8A?aVWvr2P+*0~Vu7l0JAT$!Q;7*T)w=Gac!{9_!erw3Ynq+OJ9w?%m7A7%GDjNQA)xw*#oSAO*#UUQI7OT<1UaNjxZ zRV>vNUbTS|`FP1QUhvB=a?c08^)1fi zE&jfMZI{HOxA^@Mw;!wARNDT)jvCazIbT^||NVgBsZ{i7)h<DJi^6b?Riy{=s_JHB4jNRk_=hRMPV@vF=Rf7U>feE<+#uK# zYrivNH?tw0c%`96qT0U`UQB)XSP#*aDX!^(&odA2rJbf3amE+*dCfzOpbendp+j{x zw{EJgHHr%wT}P0vKJ+fw%6H6195R-X8N*pu)T|ruOHXk|U#dk-I(eF%;3-+?b=Hf0 zdOk{I<!T96cleC!iPA;f4w+&ZI~7HL%y^_?Ph+SXAa+I2D+KGdbNi+YE?_H zzKfv;SWf+gU0Y%`=i^n~m{IkJslIZb2i2Ttp3?+f$4YRJ+cCqs@mt%f8}fe}9w~+< z;4RkW9ai&a_WUH&T@(guB0J80Jc(<}cxA;YijPxSn_+4%enTzwQ=a#hJzxtq=oahw zFe`Z{zw8F9K9SeBR5>_bI=%%R3RpMF439-)GM`$}xI2JNIuEx!FLPoYdrDpQj%O)uS~!-cdZ=a7IJIr#gkNRDjuDn3b=nDEc_tCHOriG(La8?iAD&Ss#N~ z6?Q5tvh%xJay~>l%9`l4MyW3kG{k9!~TE=faVGc4^7-aU!m zBC0=fJ{Ggz9b}#!BGUz?^reGhs#hFtZ1!(;3H?s?7Ya)BrZa-Uj&2 z9{PSL7N|Sw!q zkJUVtHCBahRghIYfzL?9vXZexll3*$B!QlfIzP`jlTYx_6a~qhTp#EDJ9q*Ktic3U zh2pgh;mo%r{#JUymBbq_f!|RZkKB#lk_|6KQMYWxh*b{hl$OH}X-`#pMZA~XcyJ^u zxfW6CjKYKS!E&&ZYm6dh$&VLX2z%F*@oq?rT9>+$a@>=N8n9~Q(v`eHV;*gcf72@z zy>4M0?!_Kl;9IAw{LHmS{P&svJ}@Vh3Z;+CPB*u!@ax-9zy3E$5=%MXM;MdcoI6E7 zTG_F#VlR{En45{nv;c^AGa}W#=w6JW>*r_`u@<68wwL1w8U~laFCU?2l}5!%fJ%5t zqoM1kmdtQjx(yWot?EzbfX*nRFVgM+-_SoKZR%C9@gyzYmBXjWLs$;1f>L=IEv z)mALS4-axT%HTDqs0yI3F`qbe5>*?eh*P^z3G|y>NjQq-&-nWS%wq+Z;5^X4Hsq#f z`8$DmGzItS?Qey$RX;GNmf&E+Ktz^;6Q1LGHHiFKkf(j*LU+O++Yb_z2zGIi`jlH- z%mE4PB8(hICgS5FM8j3Vqnh#A-H3)4qs+4kEb1ZAuP=}v+@d-0?q+ldva;vRz+2bh z%?@EDmjEF;iEdqI%^6LQ4BQ&}1r;VAc#598HS~-1_w{8AbE#R+XY6jgM+RU#wO(n$ za?@9OjcpRMm=nwvI<#1=eXX0V7p$XE<2h}+XN$70v(LA$u-~xHvS+ccx81W|u{1XK z6UUh>!UsdLA&=o-oeS+PL03@Q84RPB<`7EC4)q@D^L|lfI6ko4zs%RhSHPR-9_*g) zN}%rNC|G?WT)mYj?G$!iaLyvP)>5wKig8bK8$5nb4sUh(vo-W?rdQi&YL(a1nXLr9 zi$25XiXlrGAJn20RT%Fl0&TTvWUdXmr8>R-4Bdsk()p*quD;Ho8>c-@~sZcQiO67*9 zRu)~7Nn~qN_q4@|1nC0G4|;wzUlQGC|W6Rq=p!44qwW>3bP0#!bdw#&1G<(?e4%nW1OqOqPe1 z%+?fZRa;$KW!nmC3G%ZQETXxFs59*|>Wv%pkKk`!Vs%GSTf70U=^j?ys=kAjiJ~j? z@!+w*W3=PGdB=J7k+EAN&y`*{MQ5G#l(c1OWytUZ(~6~UP49)eSswX_yv^0ut)YH8 z#q)=ExOa?qk~gPU^cJET@Qo+UGt_(CTLbm5lhh&nO@(nytkV(oa=HYy)^5&0*O*)?b^_90)f3T21$tfILfN>flU*$cunE6_d=Mc2Vu;QyV-EB5v# zdun=?x!b#oQD?TwwZZkqRfW2>cz1+nl;@JCBD1iCZwEZ=S!51OU|&j&=S0xOB$&0! zsI^RHjk?I89-_9WD^YY6yqLOp_j$31H#Og}ciFZ1==~5teL$tXM~97z)b1scUwOtn zPD5)Z0xiJp+C$WWmC!ZO{iz$QTSe!-SX~}6`XjJ=7VRVS=A$()KznE61y2q6_lsEh zyU7ma$Nx@dWIMt9DvK9>oNU25-rb`qqOA@3f01=s1y!{HdP(2TP|$e8m_^tvbR~CW zqvzRSaXvLFH>mO}E3`4R*PYgMBl}thRCraW&gKMY+FLlQ4yxX3p~&!)+?SfJ%xB45 zw(_Nt)w}1u@A@DwlwzFg(#xh_PFqVSv553_>2c1X(hB)s*BL6ko_Tw+j^oG@M3d27 zN_PA?tK3ijupgdq`dc0Lb^!sNjy)7PiwW@99eDl~(Np}O$;-LRrM*ipjh*UmMDs!F zCmn&l{(s4}AEKXQUB1I`?{4}(cJuA^c{r+55BQP%UmyAk&hgsePgP+ac5oMVXLet5 zCAoYs*h<4_G<$Y={vyBd*muz13dT%wa2PvsFLn(DWm^Zj;sNWZO?BqKtmF}B-e1Q4 z{zFCPCOB+!QJEP*{la67gPxfW;Ipm5@)U$e<7Y)D;E5ytBj7U&5Yvp5Hky zxQq@cXV_`%c!dqI9_QiuAJAl@8^H_hpG0s8x=FfrbhYfN%dR`9t)pF~xuia;8l2HS zm_P8;x8J*nbMAIkfUVX?Zb~J6OsJYZje3YHu+`?1k8c6P@G12Y2VlpnfcbmdDPs|) zqv`bnw&5iz&n~+b!gRXp+3uar?5ROE=1XuX-)K-Ml2{BTGYlSfRyq)Wg;#H-wk#Gl z*+{fZr*NFa$Gu5xbcX2d1AdK3?Z$tTK@RTWM^`{o^b82(Co;kZ55eM^j2ite?LGAnRagdm4RS4SQQMr3s$D7aVpm~&eP9hd@Fb$T^V{9m zoyk4ZCCh5LrL@MW#pX^=E1c$_6Jdcgf7;pf{Z5Dc7wn^o-oCyy{x<;~C^*?&B8_6| z=fs3*oVV^&PqqaqE`;Y)lI!|JC0VgKD^aDg5!LS`zVD)m)g0zmuV;cKx^wB3NNmwdy)I8 zyR4_1=Z>eF_qTTgY?YVreFsrFw1m1P3qDRMIvQQZnm*J#z``vgL;4y zGDy|@(rWd!3?GdZO+&@`RE}wEqiv6EbLL2i8|~%d+aaYUgP3t0mCw0m1%(7=M6Hd^b@>O5@j_ zr@C*rI}yL)57$D@R(CnOTn%Lx7s@XOWuJV&RUck$6R+2M(AR~GS1a-$sezF}Kk5uq z@o54X6WIw{688kzYkGjUEX4CZLUw5d`KDf)qMG~YwCLc@ZYGK^O*e>>_#ppLgRmg* z+5e}1gzrCZCzQNmJty1~+%4Qjy6KH~HF0_1*>|VDtO%Y^vh)$YMLD@0Z2e34YO7rj zspl==9!pitOKNyapg)wvuD#vcfE{uimDgR^4dciyts^&jfxPHh)Jcvr8qe{Blw8|Q z?pa4wLr?NJ7OIlo;p5&Rt9+Y1G#cNmA=M5_Uy0*XMr;cXgKJTieB>*t2d~55_(tZx z9*m6Juq0}OJKqKM&5kGBh-i2$NWuTQL|FN6|0ypnyXlDSh8BDSif0w+G6QY zIosCC77b(Vq2;SNiHhvG!an0m!#91IP|wMd+Vz^unqEZY$BFYMl7qTLKD8fS{#SOl zW@I!^kqKW<_uXc&31{PH++_U6qA%LRb=DP4hr(i>$DUT?!I%0X$Yp=^4@VKC9agUv znG!{dTfoXSA=@*6sID&9OBx=f0lZs_J*&s1Fqlb2*kxbIfSd=bubc6O3L|ZBdO-FM zrX$)^R9Z*jU;T%ITT#z1_XYPPcNa8e4ek_FFAVNTG)vY|7iy!1e=(fkSh^|RM=znc zuO&J7ICS-P`L?rTie&qT!tP8#-?9~4P#1~?N;d8oBlkY2hdWsTWUd_ep_&T99rTcm zL8<8k*}Va9)iPjOyr(~H2DRR8@uHpt3d8AEY7txDy$_7!@zWq0JHuW(fx3v6%D;R< zbzuTZ1{;JMRLBR=eNvcb=g}ICgI}5?+RcT`+2Q$ihId~bZhmfIjZ7goEBK97l*o!X$eP#| zyvTni`0pVrYa6ehh%eTdRdgOUU!K4%>aD|2@)s+p#F2YGvWJENVsmDuDR zmLwP5vD?7M7>{?9h?dZH-w`yNw~?t=^7W6%+<)QF(?73)e>4%R!kNg;?EQntw0S7! zTZpksh7q?EMc5`VeDmQ4N zx8u1z8SiQjSkW!^q;lB5f9S)Mgnv>SR?IHqg52~a9IZP@C(|UIPnSnuh%U!}>4(xu zZ5}+e6ZA{_1zS9iA(pPpqYT3giws-QSiA>!Jl&9L_-zo3ZbORUg(1ms)Ue#p-B7~t zL%)wsY?1mWx`n##bh>oW!R-{RKF6qbPI>p3uAD z3_P68tg*@1#W?I>3|gTEx?uheJSAU$7L@)heAkzp=P)wmS?RWv7v7T_hUdR%({v%* zo&yz57t!f6G<*-kia8eQzW&&sgl2RYiiUrHJxpLdE+qrMhri3<#qW;lL7fC+oL^jH3%> zFh#rP9hV?#f5k%F){$VD@p#<(K+%-E&t0(LubidN%z$1HT(C-IF26AAnU8dVw%Umt#90KI*qCXF~>0);$I#QYpY zEd_K`rs67!-nU^I5go2q{QsFby$W@%Q6Xge4Osj{lu8P~T4K|T~ zUlLp7V;+bdsYroc8TtAu)m2~l$p!uzV4K~b&c6mVcLS)(n}SBL5|zc%qeQ?KB*N}q z!UU+uG?2Y`G`qo6uFHRu6HaGVhYT)OA6%?C*sLgC7OJ5??g&M|a-ZW7yd!Sgk1rd^ zPp$)j8IPyi8T@oCSog2wC(eKvje}9q3mdwCuUCNezW^021-@M#)}9JqqA`d~H%{|5 zdZw8b8?Zw);pH1(T$Lni6`7eA=_)$}NnQ-+sss0%#eFt`tE<3l`rw5=p*wc~Z4g0H zOMy@~5FPm5exB(K&o~YInBIk;$Dgr&L;ug)Db2))bh!;NOT-sngEj0Tln}jw4lG_L zK0#BiLo&)?8@q6twsM7bAjU=}Ca;p+@R;)Qz3TW2kBDd%@TS$lurKm-FF13{$hTDF zca`AKCazywScgY3o}RVi>8o0d_5T1{pc+v@MSf=&_NomWjxAuRexB(%KifktZUOoE zH`p;9m~%^Fh6q=^3eP)-sA)SrT1tUf|3`IRn9q%XTk#({paZrJJBgq60#vU7tNV)6 z-;U27V9olm`mNb-50f!|jdQTbu+DDuo}LEnsD9(bSjc?5B&RZ-$gZ$>Ngl?$ju>PoU2aAN=nN3E zRs4Je>!}xa<83q``W;QaAqos;58SL;obNy2f$tE{!sAO3nxLQKSh23K>WaYm%O>)8 z2I60p%-3BquRY0Rln_@4Y2u%7HTsduu!0c1=Kb=qP7jgtBdoz_;_QWF&o0C7xGYRV zQgVb>@0QwdcRUd3K{0TF65e2%7qQ%d7nkq&oD z@sySkmHs8(Brg4qecMh=*`Mg-n@sb0L3P9iR>LYihcDAXUd%pyB0F3HFNoEM*UK5|h4#HtxNoT7@m{KjyuvIxdHomM%svtg)tsC@BB zi;<~8#OEF1mVA^1VzxBoZ5a|!0D15s$v=?Y(TYRCO}`4&sD!Mh$iz0hB_7{R_R}OZ zmN$`0drOz2hs6A&h#A&$LZ1kSM2%uAC;1>5p)eWFiJZiR!cB3Spd{PTT7XrjcuJR9 zpRf&l^(H%23%zlTD7h_px?8XYoS;K1;8yjO$FpC*V69s5|4qnp?Gl@y^Z!K-{{oTi z!mf7{bLrI=Cm!Q8Od$u95BYB-{7YV`Kk^V{w{Ho(#OY*rrZJ7MyD*BUsmtGs)LRXc ze_+in@&-%jFS0|b8_CG?7H{ceeOXAL?rbVictp3wijhjaLBd(yatX4~ShxvarVnv@ zQ?h`skmvzmC5wbx^n}xj+o%yJuego|u!yJ0`hQPlLM`|}CD2{<#4e(lj+blkLGH23 zB{>^oIDLEJ50-~hcNiX4QE=wxbVSM{ju*-ijTRUF;qHC0Onu41M{-lQVU=k(yN}6Q zrwRFpSJ#T(ZPSVbclt0!c&TA>xP>G&BXB*0C40ybWT+POh!)Styv;U_BCYRmiR z_|i>iCr_gTOdE7fNBOf<2+cp7Oip`r+)1kP>-_&T-9t0nmUo~R z>!~q5CU-{ zkbAyj^~>|bv%%_1Vo;#1>U}dp`{3%W_TWPPjk5=U%x6_G^dGMgE2tdPr)* znf#kR6<2xALc$8^0PWBIiWaAzMq5zNYzYMuBw<1tCs>UHvmbA7cQYq6UDb&5+l_ zu3cbnqN7r}Z&CbznBA(}PVVUP*J37jpVZ46j^B;r?;z%Jf zKkyiyV--1_+)aJfZxB6t-Vlf7hey$!)msj>(gly_3;Me!o@gQRz9M;_pTza->Q!RV zL}>^wz)+lZW1SPWebR93*lFJ8~%ck zRk6UOs7$u|&HqnH5p2yybeJAH^?@_EjZ^;tyPS{Q|6H{B1>r5WJtXE8{*oi#-^b@y zu!h&rgnf~>3qmic1uJ?}s?K_*%720;{vbAFFVa|-_o7#R1)F8B@EBcOi)yDC(o%UK zPif~YJb~Nzmem-;=Og~#fB1BfDW5xpV_XA`O&_{b_&et!`d7Hh`p2VDs-rWnp^0QE zQJ6^Aod_qr7v5nRp#!!pGS{go+Up8k@Cyqoxa&ADrqN<6p%DI*h82wAWQ>(Xk^XeZ z`A_2CXn=O;rWDu!U#WTj8=YH&Q?{EqYhR=#JYfvo?@vn`v1G;Q2Xh}hF~T^R%xWJN zKFM?W{j*%BvAA2lhLubfPI68UpdCc?$z1m1Pq~5clbFS+87g+-EayePHi%}skrzfQg{|V@GIVhuFv8GBzh^U6y}WknalSBT5B$G zPE$_GG`WD7!m3*wEjZQf(VcsUQHOD=#tF-@8uQtYefTJU;(`|e;Zlt|k#_Vt>uSbWINTG-7u#6uN@TH--|w;6jE zk0j<}AGdL;79){E!3$2;N|V%Ol`&bn}kzHie8+9miqs! zTXaE=WE37q_wimT;DMbJW^k$uoLm8vD9#7O9fZo|@GC zoWNm7&u%QLOP;_}ZW69bMes_uNSEaa)UWQPM@kkL?G})QPf}OTm7T9>B{| zMeHc&w3a+dDvm6E3@;#0y%@hBS=uCzm-?U|-_oacDOf~FIhFd`x6~$VVzyRic5xBs zV-@SvLiX`rCAzb!v`1b?Rm)J}7wJ&=3U<4kJS*( zD$_x}DD}k#oR@xJn#|8!t*l_i5*6b)FA=eQ!X7M>R?97!y>&v)8;)h)#)7alK@Q$Z zACMg*k?1I?lPodQYb$SFTbf7);E!++UuakOhE!V)hX=sujdX|J3%1x_DkLP!-N9uV z%SLHxcsAFths|;)-X#RvXr5G3SPx431-#}eKf6!wk7+~!_4(8u(CY^LTRNO1PLUpl zqlBYmcCLdvu9x1@nXb8%4;fl6F$GA-i*I{gI)bzW@J%f8Ht81`92_mRnjPpRpJK0{ z5LJE-mmuyghex*ukG%%_dmc2YI$7?w;6VoR3f;&HwWc3dHgB9Q4Hdj}Q2rCGaRaIA zi_g4Y*hEfg7IO409G23V=5~iw&XtM@_gT-@_zC5S@PnM9jzWlQbqAxmE6iHu^JqlS$}QCta8RiTL1Tvx}gABJ-Dj#0%IY zZxfG#2lNzc;>Wy3*Uvz!EoZOhVT+!E`yLVplc&BX)R(77N%9F|#z|a%HD2gUeEnWR zOMLel*webw13KTX(^dc+%R(>E913&vm7$v;|lj%%6&<9C0 zan?@?Q{aAg#QRwQ7WOSXS>A#?*5S9xA+2+TMN(Vwl+Ydze4MbFb*d(=lxKrl^};78 zif6kB-+dF7_6hHqkJ^&8L|9MA&OVgh3ctvQq)cHR>%IUl`aT*>BTN?KvF2LN!$z=% z6g)HJQrkSqO1l&1v8 zf5c4Oi#$_TB&RNV@K@ry(%5D<@pCo4FUn`78dS@ilT^fIAofH7x4GZ1=-@{Dyf4J&-#TUp1+ypfp$Gpb+eAh*f5 z$AzhFJSr@c_l3^T15qs%;az^@J?7GpXlVF%^lLI0-X-aGxwyO}+z36;IowOiWo;+Y zKedtEkoOpir{4=t#7+Oxt!QATDM%Ob`RGT?zC8&ibN6>d<5OV%?U33bpR1+y;XkCO zXqo(2z~QXvN@D1|!hh1%@K+c&kI8<$4v&R@G*+T}13phB;gi%tKEYkj$r4)cB@%a> zT^Zj*RgJ{p(o1AVg@=D8+<@;Eldi#Y{aw0( zT<)R+f&pwA7G$tfdScDBC-=Rr2_cLjqu_?;Np8siaf;>;Qh_S*>u<5;=n(4%t<+s9ko?$f1 z`^dDbOf+S2;%kFE8dR_)($!o113j*#UPvInIv4Hx164@Z@UF&iO&@kM6Nz{xgz+aU ziCw8+C_+TBh3M=a@z70>?t=0fVIAoEJ>s__M2#1Sg?hl_A3zLoPd=)+1=Fa8VkbWf z;*IXbu2cepGzcH4w<#keiB%K}*uQ_oXtAg`8h_s{&cM4a$~CVkej{!y1zKELJSQ8- z2aezzEhY+Y23nhq{&w&TPO#2Md>@$?HWW|u3^w%xQSE!drZ^1RyN-;@Pm22DB>6wl zA*SJDuM-~-Ewl#Hufy+cWX~7Da33p{6PzHyBS0;D3N3NAMsY_pfJux|2wZu-*ivzU zHP1^Xav%AQ@44<*WM>*E)(>Q<&j_!nRj5G*;uVa&qCydBou=}9{S}kMlf)M{~G2e4xEzgxlctp;~BMefi z#STcqXR_oHKPx~KPK_P44ZniZD8Z0Vh!x?({mYs+BlGl_cz2lW5}I?hDZEP@Sjb^M zrJ(vMl9}nRXp7Wkvc?)#qcK^T527r8Cr;*#J!G1?gG;qzZ;JA)r-|GeitCYzO`=6u z#drYuB7N)DzC!+U| z{Hr2@Ztex`EFg>(Z;~mw2=5>d2=xXkTI@WnioKzeGx1(s>J{tqMDJMfDxAm*;O-Cb z_kz5|U^G|>(Ss&7ito_SLqT~Z;0TMvC0Kl~SP*$} z@LoOOv2P^0t`FkVM_7X$=_)h?37G+c(Mg<5O*@oHPHkQ^)*KkI?YZg*{Op@VD)FF@ zv(R9Z<(2rRDM;FCK@Q)Lf1~Cx3tMp*eeo8p`y)?L9ZzZsdiNmv-VJ`>dl0dOJl{w% zjE{))1HY{z18~NN;%7 zZVozZ6_#lTnU$*e4fl!J$7A~}LY%_J#MXR@iR@z}PdgX8vz9LVt=QpvWLz!Ol-4B| zxB!;O4&>}7o@^%3;|q3tEY~{;mJ*rvv{?8~&W{(?PrQZq{y`kW2{|P{Ay2&@Oo=Qh zmb5INYbEZ*V&B42K8IO30JQfmnrv32dQvPYUnLKBSNaD|;VzDCZ3hIk{eF~MLx+n>d#$fi&c5nBE<4$w8%;#gh{YAUJ4BLk>|qI zcrKmbB=^keE``Zcdb&3;WRB6ND|)M%EPDky)OuxIxZgqUe|NbGl8)$Y4D6 zh4gIof=m=w{E3~N2A60XCwZpAMdj3JaVz_1CSUi1xDXU`ChI<6;7nqvK~+IRZK<3Em)KcxwP<3u@>~^wc;{bWj5M& zHF?I>Ncsn~dL=p#_eAqw<_yVXF5i){TP!wU7e8QSe`Cr)1+44=ajpEE?>{6Tc@UKH zXBeLc6(!KCPhkIc6#tXw ziyfJzS5ncLCmEo4Ne1d~Wp7a{KNWY8?YbkL025nB+$6zX7$@h$CM}h_ zJ-a}|`V;l_mA{IE$QChk011fL?{l2;NM-9L>4bcNj-m0wENqiP{6$y`DmRi6;RN+Qsqu(5}uAI9Ok?S#n?l3)E_r+pyfQU@6sisf0)iTfn)lgeQk zjM7fz&fh(oLc;thm9AUJyC)||PG?0%t zgUr^401RQ4g)Fn2)Ag;g*zs>sB#_rr=@9u>M!ANpToyCxJBAf^cS|0}N5In0R z^d$=rSD5inyGQh@bWd&%>tR(mKi>5c;XWN^P6|H~V>HI|Z%!=oMm{STX2@JX@=QM_9S%w`DMxFl#%cTPxAtehZb zQyJeJP5hSl^DEd)DKO|R*WK6K~ zjpS-r)F)t=pxesJ;j_{qx?3*=GL_S9#paY0M8!B^np76uGKD@KTVb@XAnSPq zTQ-5Sc}i-9_3wq}aU5y-PyUKObO8UYED=wduugGO$daytO)V2^DW9VY+A5~T!-*92pz|tXo;>*1uX*!yFzVxq$~^B zX|gQiaqp0(;MwlOb6L!~oR(C0)hqDcr@%*PA=blMT_BF`ByI;u*-B<>I+@j6Ji9Db z=B#`Szqvlh%_}m0_8dE3EsNO%bXIEs^71nrnYq&4*aIg;=3P{**^&B*+1;$4>!7yiSE z=>^K2h22_7y}Wf zGBp%FQ#RH=iB8^cVFo@7mx-(jolo7M@M{DGI9fH>ebif9fgH;%$wFqiFM0kv zVm-wyxnAgdVyQKt3MWB1Dv0x?0@6cz`|btJD&96kXoEFL6w4jRJ(9<~xM`07QrI$lD^=r>F^Pf!J4MZ!?^cXaBG36AQ>K5 z70zZ*_!6EX?I7MvK%-1Wb8qHz-9W9>#LSiDM6`kuoOPD;qrlhUr}&9lEMygY%f)i4 z_#TczRVgnR-Yo9YjqFP$n859*#;YJ#m+ay3V9zDt-@FYE2eY|}hWLy3A3^=;T+rBG z!ROwR^AP32;NhU~;a|~FBS4#1g2L^@N395=83ajDv35PlkG?0<)EbuEGO}OAz>5}O z3p7;V-iAARTi!`tdO7(yUFd-{M0^1Tvm?A!Dy`@t)d=;#p1hRWhK=A7)9GQkQy2)7 zxuiT?ip<~|K?c~4-mlB=JjR~nl7;CE`Z62t=u)t{ijoGLcpbdpU1XA~3O|5H9R*v| zqfwM-$&FH8ENK%au@?hULcgi$)Q9{r*z*=*z}p}| zMY&Twa@9?+(a%8QyGhT%=dOUkc9Awvb<`VQ`&M`bypiolcRNn+IGBCU@yE0I*T?$j zLx&v0o1P_~ki4uTom!>(_{|R3xfPiL(jS}L8Y}n}6mg+El4!1v)DMeZ2ECZV)%USd z^NE0x$=aNQg?oS%4ZypNupr7xGT!z8u%_xfYc=A+4t&>59_=$WvN@i+lWffZ>Ogjb z=7z%kq!cj7XHsDzsdk{;i@|$0VKpDH`_;%u&*x-*#ydE}6ZHpcEiV^@q5B!8$2GE! z5yq^R^>~0^H4a9uiLOqgd5`6I8GAvxy}}$Uiyq&3G`c&2v7ILWM|B-}zAoUa#X#x? zaE^W^M^r^Vf>nx4^s5H9vl4%PCf+zgjyC|Vb4|XBT=W4Syhb)Sk|*s9leq@0ArCx> zl31Bn=#;}?o?BQSo3tIADTi+LiJZU7V4}(RO|8L7FH`B2PJa0xx}Wc+Cu1>eP(5Y< z7UtRRhv!S((3%B_Gz&>1q;a6VYjWl1&Vqm!a0VQGwfq!3rY~&Kh3I`Fe%lD-BA)!n za%QA;hI4(0Y-J1PB~&I;HjNb($agFyQ#J+s`6AxXG&w(d`ZDWXl9eweoMoQLdo1)V z>Hq{#;0egC9~7-Q&(fE7+=Pv{fwJBblEgLW_gt)06=L4qWwYTyp zH1tt)%3{uw3eT{Fl!yFCJ3P+{U;y1%)4gOno}iD?xJn}HU4}b9C-?OepPfpECX*gt zz3~Bhq50bJ9t!dmXmt?Yt>obv$xT7BJIen_GMKUz3Cx1+uoFEW1+x|O2VUAf{O2Jc z8S$KhH1<1==iA6CPr!5C%Fg!3Hb=oyXvaMVA!`NkEA9$Ki3@j#wK$Cv_4xmkPpI zHIw()M`UmgbovUONNXbe5fSDHtK!C1Uc5nEvuA~o^e)5^v7j7I zLJwp_9efj5%!aVp2eB9;@>nv%dJQ6 zxxtE8u-=E+6*cJ4Ue4Sb@_ePy6@O#VVnD-7v!d~MNUK<>6nxRYBe5_0>tb&nl2wf` z3oXdm6TYVb!MlLuj6}w6az0z&Czj{AMk9~=xQmr1YllAj$n)=!*0atXk=kY4{VrN7 zn{$5*4K|9C@DfbpA^Wp{6Xj*iyR+{H&^4{`Q|fRc?@DE{a2<(vU|`}a7XU%JPVMI! zEJij@wUmmhKak!T_+^uki)^HN7E$>!^7gCHR`rnT`_v@tVtr!d!g$3S$Ul!pYSYNW z_2$!YMDv@#4kCS7-m=&2kk|~?`z}_lA`GJfpk?3qY5>|VA8$AwZ+96ob&~Zggtvbi z>mB0Xt>hFJkjH+^YF%WN)*-q3>E&>o_w-7yrE*xdqS(_2OYszPeT_5miSs<0%9pXZINWozzaCJX5$O1(_&#|xgP4Bk=;@}GYq39n&S^~K{q$Z0DIGpHs$d@Y`Q zI1yU{zQnI&J!|mk0&ri9NWnPvYAun}SWa;*G)6o2dIaBTja6FBUff3ps=?5}Y z;RF5wUipmZ<{W#x5*4hH@ zm;}mUL*^s%+hSO=;zSwMVT$!dLq>X2t>f)Nsy=pvpZS9w z?8DzhTnSu+t4I2D&V*?fnJhMtQ#G1M@Ox&HDM6xR(0G}g><3t@*LY29>H4~xIQt?M zH;?)An%Ltd*5m=&^#+;c$M`Ej7}m+`Pi>^vk3}%BQ<>N_6JO=SKWxM4YRaFAXp&Ow zZXJBkNHz5&-f0m}c$nu}gKX|ZYwSdp4kF1%=tUc;`uak&nnXNMf_2WrpDO5#`mE+4 zqQlW>$Vl(8WB9QiG6yB7`szuP`xkw0|AVa`srYIDdNoJ!54Bg38A6W~?-Vbn{FCW_ zqoEUa31wCO)TdL>Fy$2GAS%2z&@Fqf@-W>(mMB*$*D6;l7cg&YHnm(cl+&o^>O#F& zKKgUKrZ-2VX0JDuTR}1^m&7$-Ty>}+zD%UnlO4%Ms%K%z3gVG{#E$(==YV9`G<&JQ z>=*7z1zimFkv8UupQK-L&rqjOB|0Mup$}A9o}l9Lclu#ZVb*zPW*YR&?UdV#{~yP9 zrc#f&oqpEGnbly;4df<=EOaPu6za+>$Z?^`{GCBZp%qkQo}zL#JtWbI6lve)zIo%=79fJW-L+Tv55hpklzNr-5*B(-&4Pn`J3NGPl$fXM1U3aj&EdGPN#};u@mSM zq&7C7N5wN^z}VZg(X`*R&eX-^HO@3Xk1ZVAf++&Mq9-#6ei_p`%WB@xmnT{=6TD?| z*hlC1uGyu7c>_uQF24OVfl8;osP3=%LHkC#TK7v-L8h9- z#S}686x-Q2!qhdcQG82tfdqfTO-s+jl1b@FYm=L#1X8A^UQE4~IxF>Kikuuw`X{lj z4b^#~QFUx6QL<*^b(mI%+$gJ14vHy7#!l?mt5)-ME{_zo<816zBKopW(Qk(ZuhL1in|LscX79d$cS zMGx&4ZEf8_U7@HyqP|2m)o&oR)g$^&bf1`yF)a+24P!Y41B~yColPfAh2vJom54tb zpWnRM+&tl*gqI2P6H*eYnhVBTm~C3k*eQ0B;dYD=b3xxWYOZ#yx~Z~+*c^OP5n7zn zFS}7t9eC{vdcB_Fp1JPlu2!yf&J0IyM=3|Nqp9PgNXn2_9^Ss*D) zxgN-r&TYDHI(<|XCXD|PH6^M<)J0v2u9dcrW}Lb^J)TA>mcm%SfhTh-{EXfOOLE#~ zmk3${Y5xAcW8S9TfafZGoc4N7cpiFWPkrVeIlQfzOkI_J5!C|0z{B94?CCjea#NTf zG=lCeqhJe3QcvuH4Mct=-R5fGd7l!!VgmimE78ZT6@9P%rpre&<#EnNsyd*4t`T%) zqekeLF90xP`_KF^;JDno`QrLeX&NoY-Iu|8VbZ_bAu*PKW)9?W{F3t9Dk+ z%rY6je{-j=Oh1vH|J%uLr!($khO-iE_ieT8o9qwlZhJw;dWXh&(z%z}GVeUezOqcR zNDb}|(nBn}23;He$$cHFApI>@6(1>vsg#=Ev>CeA`m@oYn5wajjSWpnanIwX$5%98 zFt<&3kxlp?8&|_LKK4M2NB=`q5A6oEN10DiPG~Om4z73wLY`$ zK2%cwrEI49qMEPPXntcZ>?Um~-BDeWsH;)=_09Eb^bhqxeZA5^I=lkPH)J zZ^TwKo}n{}KJNFpYVptFo0yFWHxp7Ur7Xu1>}I9;X54<$8DmDQCAKA2qHFZgsC(KO z>idcYf>|mNYM-+ym>SscYv+CM9_Q-ptl_9_Ut&wQDy${5W@jd6YBK9(Ix?SSWm@0c zy!N2uk#nLekNcT>H52Qn`1bhU1=6yY=e(kyQ6Vt+2x{;r$axm?6152lEFdQtLW}glO75ksMRk+G~oo`PPI}(J7;1X34B6)s_HvHx z4zDBGIobKtIgow$;?^)L@@F~>b>~bx%x=aEkIKw|*cti~t|Pw}7Ak71PN>!NAsigF zMt>?gC+7RucE+Zrj&VuxPveW5TbbLLe>X2P_cy#RBJBqKaouf=TUC@S zBQxEZ3aDgy>y%~sN^P`hOSMfF=r_y{$Bh{bNPhN3CAqc6R#&ePwbG`+7dGV8^0xPx@ooX zdTey;U_-;0#rl@IZR*X6L-P0Gk2!L1Mxc!UoA;QfpIdUCb4<05vdy;M&Z?8ODD!j1 z{)~eeKV;s_JeTFR7P41$^mjIL`CL2QMLagTJ#F60RJ2owFpncyO~n*>?^LHr2June7$#ga+nVT@E| zt$|_oiQMgD*dcp~sy4xk(~G6)3(^zb(K`CNz9JjGPSJ=dQAWwN4|uv$74+MF{o*u_8AJJo&9IomPNzRUL7+R}O~ ztA3U(^Iqot%)6P%SqHL4Sj*bHwk!5O9X*__U7g$`J=4AGegF6`2lg_>{Bw4boF#PN z>d2{0rejcgs1E%TSJS~}9x+B&PRcYoeOwFY1BZPk{Y;!!hR8RL4zV?`+MCc!ebmJ@ z(b}ilJGy_Oo-!jm*Fr{Vc_0$n* z_tKW9jZFQVJSM3@;@gC7=1|-P(@NtQCjZ^lD|AWfYKj${hQ~So1)urje62laT_v51 z?SjpjrN~Ojtd!C8Tgmj&U*~^W^5w>tykGZyU6lUYw^13zGIKKzW_@qnYyH(W*S^59 z&ndWfdaC=r`j-du@s`~~t;5BoPtsj7gq;;%m1EVS_E+6#rtapCz85`--cQ#JQ<=Lw z-1r(j)yq`ZRKTP${cEgfTorrNa5QE@bS3=h3ujF*im2+3oPiG@@P-?;=KMOkR!_&(ls%S#w{~83UJ1676NTsbf z3_T^oO#GJvAD6FA%Ao~-4yj2g+jOz9-7-e zdr07tPw=L=%Q6-L~Upsys_VwSd)6*+_lfP**Gcp%tRkq%= z&bJk?%k~6k4cA-s5 zn&29rM-x z^>ljEZ?!X0GhLaBv%a(LwB|({XdI24TU?TRk+-Vezd3TQ4)0`(v;*ezE3uz4 ztXij0(KU1|G9)q)evBb8_DJma#x=$)V@uOE_M%E$hq%&l&rM2`E%u?|5T4zKs71Oi z+BD65)nuhltS+>aCWY4ITn(P^U-uUD9C7tS?sQ~supNW_)x3$^P&sIcw(j-^3#{Fnem?Sd(%kMEt42mKCVGroj60> zYEzc+L+l=Wigf*AI*3Y|L+bXb809wcGuh};p?W#>gU$Stymt3wS8Hb}$6R~5t%_}o z^+8tUtP`0DneQ@QWq2}LWCk;zXRWX|hMmSG#wo)#2&7I_2_ly5jWj0NyJdcd}w0;2l_KF1DlkVlH?%Chw7 zYM}l{-B#n(oYW504UB4~uN&PVW`<#M>>T4{Q_r}@@gL)Rnk@-h%P~u{#2bl2l2pmN zl2cQbr{qtanYu5ve5y0KOwv2ctAzdLbKYir4kKcM^q_H2^{#>yjgKNlU6at67Lk@8asJaW@Tz)#H z)WM_b$|SkxVsm<`4^ldnT~$w29q5iY2p{*1b|&&RNZ%~FPE12XXQJ7rrhIX6@e1=- z^W}skmKKQ_iNllfCqGV}m!eEvm0BU~ep=BydGh26p ztwly4<5K4Jtoc^i`iHHK{i%I}taN6xtWwEKP^aRb6bX=&RhJ zDxq1ReWZIIby43t`Uc$`&&RYkSPcVXufR9YquL=v~Y82i}b;5t-p3T;CqRqa0o(=AOuGh{&&M}S`_7wX-+a>E@Yh!CY zVvs}D{I*|ghiu*L`5jLko1L>=>)g{kzj=%KKKuUge;05B+`<2{r!l3wcc>Iyq&?xH za0$kO5IWhF4&)_*plY+|U@fXY4gmkmY+%tYbTszab*qbr+^^>(1RPz(wb)FR8 zB7aJ7YxdKexKQ2j7@6rC~t}$0*DjB94 zHX6nmCK<*UrWpDf1{o?DKE~9GsSq8C+NWEk-LBcE{-kQJTB)1|f9o|hl;=W)a!+Iz z3p$tztMVzmT|N8U*Ij3wZbwbW4tqm;qTOYCXM1l`+JCZ-v+uKO9J?KDoN2DFu9NP; zo&@h6Fe{tyfdBWv{NP%m^M>>?okD-g(oDN~Ntd(PFhe?$dGV1AA4U#$A{nSiW&ZEP zg#p-bBbn%)NQFDRv0|QsZq7Ez7wX4ET%qj(aj)DbC||r|o;&OzZP3Yo;OdWQH19F3iMTrJ#BJav5M{As~ovrput=SI`LEtx#*AHr5~m14c}iRzm= zz^Nasv+5Q_J&ZCD|K*9ENhY8O(`BQXAp2cR!A({!UD_CfUQD4lkrYMsI)QnxQ%47JOBmu(J~@K5xn zd7Q3CPPMbIW0ifI?WMH}+I*DtjkTcdsI9sEgZ&rB3r8y828Ud{N*#@TDZ zw4R49!c`dotLz#qJD`hNOS`fE{b>1$e1H$=Njb6fpQm0vYpxm$4qmhqqROsQ=6Q0{j*yMiMEP5mW( zn_PrCEfCj5b1 zI!&yjyUZv>hN3=sx(qtvuT~kDC|^u-L$ik{CNAnwRA;>^dIcGVD8oU6IriV!Va6!D zo*&}w#kGll96!YT$Xq00S;BMT%m~%;f=YU`BZ$l_-}D_HC~g)=c?x#=v?JEVSjDAYHeuUW36Z# zX1i^hZWkRj92*_YogbakU47kqJjH#Z{hP43a<)ijDiC@d-YzwT75bIhtT~F2${wl| z^>%d)%@oZVx{3ZrzGt#l)^66x_xkPbL+yl3wn^YwHAa`C)YWDrWHYO`<@vijTb!*%qXL08U$1!`3ZMN+<+jQGR zTMt`JTPNF2o5p_IzT2_dS>KiF)_CqR5nbhby3MhIerto<}40?;3SGzl(P23BD-0(kC+mwD^%~ zn!2baQ?pImlDC@?rP41zzpsz374u(AOM}O-EH;VhA|*`sP3_~Z#YM+2i`SS(n4g=A zCtOSDWGR>UGO<9?$HW(w2MIM39++#IU&rr^Pm5mxJ{E|rV`#{JHPu{G%~sMa4)*5w z@Y~!XISqn+{ja?PJwx4_TvweB9ko& z&@_~EXYI1oSj8BZ^O-|FgxSH_~<5!w1CAbr&T4EAk;d`Gl zPdCpsPc%0$2jb_&mx*5z_noO|Y=M{`_4l+fnk?0JWrE@&jQV=v$+@2Fn!#oM=DsxV zTlW*!OXqRN3i}Uenh*9L9ltm>JDxfG4wJL9)9Cu*3c3q;d->M-uYweg$o@V@pilAD z(DHC|qP?a>7i;KVbW@zll)2VS5*k83-UZ65${$s`RVCCr)i3D5AEgmA%``JK8#O^r zUr+5Me5Xy?RoW5S%G%8uNj*{>R8>}uQ0`aQm>k7yGYADcLe+CWXYUVA2~_bryx*8k z|K4SBH3x%DbR4wT0M+@OGiI@svGue0Y>({+LHkM~VV~X0JfpqceHHwv0XYy3`m@71 zcXQ{2D$+GIAG+`cdFo_2KirxM)MVv>rSS<4@ZT`bCQ(fe4;Dsj7U!`T+{f*pJaft6 zH=`=XK}LH$9Ei1wD$EP1ta_yysD7<3qnV|-q$#akp#7lDt81X!tP6wooR7-KWPxq^ zO!}BFjCMsgjyWAu*02sVZfk6F<0WGUGBgk4uEifVFHR_Isce~&Fx*_8of~K_YOZKb zGS7@Z7k9$si9HlER^LdsL^EAISmjo95$RhIev?}+XApZQc^`T_?uPExuFB42#{_!| z`!xG@`$fCT(SuBx%`wF(JO6N*-9}G&Z)aZ-a--JOy3N#WM0T(XZ)PChc}Hx; z41>wa7}XM$T~$l{i+Ubi%eTS<_+GO?6Vf!&t_45M(E79{c5|}s8hG}nsP6i7{jliR zn6WX=m|@_kuUevvS59ZL z#4$RoJPnTtmB^K{FEJ(IwEwN|l~?epJ!W@P*Wb?4oWTyT7+%@0*e{X)91niFz**Y$ z%(d8E!gGZzd7AH@?{9xUW;Hzs_F>A*{hZFZbl+!E*KhRV-xQh&r)E5Rww_t$LqqlG zjsKpx61v-A-AWO+Y$!tT?q5ze)aF^*pyGaTuTD$ZVL&o3|;6e!TURh^3eNzR_OciZ{Y__#JK?C)SY}wF>;YM zu>kWWUMjjUPw0@U74c^+=9vUEowU2O_p~9cReP2hV;;>U^z2-3MrYgaE0?Xo&MyN znIWGRs!5mf3w*yn6DDNz%qEcY+Vm(bBb=xH&?U}OBq|pweagm6T*_1xR(Db#Q$JT1 z(X`gg*6gA0`!3CX{v6fp)tu6NLRwYYAG95{yR`|rA-V^;HgH0^=x^vpN9$wuGsmZ3 ztkP&SCCBOFt?@6-n-cn1Y9v|{(-Ye!-m)CAJhK=Qb%{4EBP=fytmYN*aq(A7+L&Zb zE-1x&?ru_>RfQF^gnz=u97~|BZ@Jg!{n`7Srym}`6z_G{;u-tdg^qQQ>oue6nANN#wPMJ^jy}F~exc)snt4aD>+Ro}*bb4>E`mBDdJsM?*?jKz+ z`o8|Tz8JIdhQur~WW=^J{T5d&eo4FzFLgA$jp6KM;l$L$K8c4Ck0#Dc++%raei1h| z?w9y*e82c!CUfk(=yy7%?$HZ*z2ZE5jXdG9p~pGPv)cz>`seu0G68S9$L}_~&${Nj zCb z>sD&-Y8Goes@IBUAh0ErPFRmg!lrOXxH@;3wz5C`A#^gh(DU4V#CNnWT0xI6gR)+<#MSX8znbytKj0KP|bUJro?j-n+ZDBuF$<0DWuQ2KFDDy>B%n|qnn_3`zl2hFUL_a-TL7pt^6qn%v z)K_LHwkeX7XPDYDOVygW4V^WIHKmx?(oS1Q>(>0GDX6&uQW;QNH1V1!%@sSur(Yo<)}>BiT8kS=ecZ(e~Ut(sYt;gZT|#$ZO=+ zj7om#dSIe|j_6CPvBl#D5FlaD4RCP%?(-0NQDDdauv&5P~b z;|r(v%O`({z~(@>IGO%;O{H&A2YCt8?jDd*%YE9j!MoNw-`mey#H+G*cJ(TFKh*-80| zdH#oi22wBC%J?) zZE8Mkmd!uSxy=PldyLPFD@^t2@A3klx12dYJcLKa_NEP{jPR45Sx#He+ZMv1Tn}S) zr}L^ai>tNkjw>-NW5lw^646^@_Qb};MaRvJJs5K$+7x{v>U5MYIxboty*KJj>@%(J%o|Oki3M5Qh z-YmX$zUPeVTl(R34Q$0Gdjl2d=yU~!%x|fdJXxMa550WyA*ra8Bo?QKOd7d?+%C0) zOfmU5{ri@P#pyV=i#)YM$|zS?0!li#v+mR*2fW<5R8vKRlCIOQ;aV~nCmF{WSHnAg z&yJ@UE1J&373fTFw9@9H=CbCB<`QPrw9{16lw@oVOV?s7!v1s(uit9Q&lR@=2l{4e z$=F9*Hd|^Dg-ypIWwy%$rLk?t8C z5A$qARHmq5k&7d0hwpQJawOaGSkIf+nKl^{4O@f``b)vA+Au|wj){i@N&bBP&b|%a z-JVzOf82T8Io!G3mGMaGxb5y&DHrHpIhs8E9Zz5HFK=OAH9BO~_pKt0KI&g}6gdC6U}uoYrp$OB*vVgEsiCa#u+eVnXgX|4 zGU?3*^Ci;?BGN{tIMZk2I`-%HhOUO&u)=atx7&dFtPg5Ab%WxUd&`HV2&td=nsNT% z@8b_+C$8Zu=u^EX!L%Ft*7{EQKKqLJi}r8YmC-+nY z41H5X6S7a=qmD;M$7G8+8C{$e+%~FVRNAN~k%J=x5osa<@NLgJ0=AOYi(qJ1jQ3y} zH`H$kdelyegeSTsaLE6eJ2k+&*8^wLeK2KF%IuUKDO<7I%~JBE#L%g9XG%@?AF`ZQ z?_gHVGj9O9J>M5bU&4N@l?-ATaW&n~_Oe>a$gAb+%6O-L~9H?}aoqlZud!*}7jFoz0|g4C<}VbO9k?4vEB50&&x@Dnr1+YioA~rE%*YzCrXr4;m_TFh&@dzR$CAAmdBRWwI5&z}hRF*cv zkSZ#Vz-#HFd{$af(>#LOn185i%R}bjBnoW3sa5GlC#bPhT2wWT#Jk>4e~gXBQO17s zjw)nKB6e&*&B+z=M(@zcS&2Ga36xk@s2+Msf1xHMPM#yVS%>dg*-!lw@tf25b#!+0 z`>y(~`R@9DlH)D!ALHNeKjeSKs!kiI9he$;70AN~&K1{)Bg6?}SI&5pIG=rIkC@0~ z4!_kC+om1^d47?23a_J})KfYu#mPO1eKQjko2aP>r{=CIwa6pU2HT7F_*tPjhm$gbj6g-+zqVaq4|s1OAP;v9r_q6whuNu*8HCCTJu@OIZOuARpSXf(euXJ#{Z1p zi~(ak?3#@j@t1ixUD*~{3)ybjy4Yp=HOF*kYta19VKc)^gDq~2tQ~cX9NGP-1yPlw z!lUj)&W$V+X^q?(ks+d9c%iTZPQ7EYEj>H+9afM=U)_@^JJ9P+&8Ga4&QrG$9+>UF z)Xmst@%p$Tln|+v*3&66t{~- z7-LCl08jKG@kw>Y`HW%#>xtEt!zCy&<;; z8U~{2j`_xa-+vL$;fDVXd$iXdOULQ{fw_SVfzyFssXO&S@w=#raZ(1Uh7>R5mI_G~ zq?U~4P*!hu?E3$tMp8|_#!JoUtvy^iK;=wss*YC5f8+`v9}?`oooX}{Vfo=oH>HO_ z2mLSR?H%mYK0_wv?IUAx(^S(#lha&>oq9AF_%gbsb*F1uX0vR%X4--c?`A5(KJ^2VLZRT*W)(X~p*3q_D`z`x8@`w+dOI-0`V%Yld zDiIf1-?t+RM-`6x8M!*LRAh3*F)E76MLY?w5xzNWgKHrCmCm*#%PsS1(`n-&!%cd{ zi~{F-qApkJ$O%$s`YB%V3;wabWbaUKwD*JOjAyN9f~S!uljjAa9q#Gsx$ddwJ?G8m zo925!99qQRihh=EEcr+BRrkeG^bj`5)#Mp4AHT~{bU3@N!fYt^u!}aQu#Pe z6X8)x;7aM0J9JabAn%}i@iEq8l=voaJ8&xSFVzGm0+;9xdMdCja5iwBnx|NLls6H3 z(INX1vAszuD;1OS@+i*{FI8ilyD{1$=z`pV(JsR~R+D;5bGV*|%;9WuJ)*8h%;6=9 zsB{Ih%%e>u$I}}{*zWk7TlA$+K6243?E?JxfySeFTWw9-OrK4W=Hhr{h0IQKMta}f zG3_$VGPO5lHMtqtX~qO&C1VET55o;CS$9K4`lU6b!fKTv#ZZ_|jBkvE>9hCQG!y%< z+)~;4)jGwN+y2Tv)sf$M%Q*wS((SNT;org=L>!`n>gR}85z`}zP(OArd{lVp@Z(|C z!^XR2IES!*&$h-}YMLA1=g%|D7P_ML=L()yCn*1udrB+mVA(RT4ga>Q@4k1UH?!C6 zdG0ywndKSmY3-@UF~#!+1Zkf)nP|G7Z@KTH?~kvfejKE(ak?nLCFD{M4w^gy| z{mI=GQiY@WIhO-xpWUn5{OlTrD6Wj2&Ng|v-o*6056bFmb z#dYF!uBRlUza6BY1iQ#9xty{>k(JT-X|3Tg&|xMx0>;mL;>Rh%Z#XhH$gxgjyo#Gf zGsXry@dR@ha~r(1>co#u^BZE!Wq97TO_BJbTaCkv@#G`_8ZOflZz0u{i|Mv_iOgR* zx1W|w8XrHb{nb%rei?D49O?^yUq*9w;_Y#pd)i}23jUBhdJo5GKT z^$H6MJLT#K^=_GSuVV)FQDv=JEq;8qJM`5SQ3lI}uHtTWp|VLnB0b}tB?Q*{WBfyW z2H##hvnt*gufgl3F6w~ipl7$|A;?*K?_F;WUr!K}D17-Ac)q{=CFyhfD^N?^BpR@W z2kD#~CD)PH$WN$G>PIirtT3Cuq1`^8Y(#15X)lneZV7KtuSai-exo(%=srvs$XE{n z3GK@FcJz0&3ZKvwszC3I8Ppp8M}{;GmE(MYKg(sYrJz$!2liG$}^zp1jt!R36ks3)=WCmE;73pc-fPPm+=wOzY zRT@oPdES_XmA>Efh*8hWN^iuNH!zn0OEH*#!Npr=>SL;7iXi`d7;E0iSj(8*Xk#~f zg-@e`#8fa2V2}G^Y+zbxa#JsN%3KIOWgF`s_|uAQw!Ng|rlYm)wW~Cp&Bugw z3ab=WAndVgp)0`^=epzU=ltvN*=5@a>o#(5Q%rq~6Aj0N_j*71=7-u*Z2Jm%pY(=1 zGbWJiZ$KSpd7s;R9He5bw}H2^HxGNe4bD#%Zx8P!Z!X^g-xHt9U(ny04%C;(QgjMj z4PTm39Rpp1gU9Kz( z@-cSfY|>lt2&=u1IF!c-Mr}FcyPwfa6wiu>@W_2)H26tz`gnJf21^sA0UV8(qX}5^ zf>M4Sm9hHu_${=LkB~;wmG?Azc~$y=-=dDCKWI^T_Nu>XA2dN)vR5_0R@K&Dhg})! zNWL9TQXBj**;vE0h$=vjsQ}~L5{ziPxhGlC>gKfOPq1NjQ4ie7RF>WR4N>|eP~nos zaHG|jVt7q9^(?;nZ=(2WAP@VDN#s02of82<{_pY#)XFC_`+Ul!}FlZ zR9zjX9FspwrKSCh|KUJF;G2K7znVYF@AlmQNlx_L@?G~m^8KPud^!IFI#Y-9nkj)R z0lk=yRkm0BAr_FPVh6LzZSi={Q&*i8*3d4pccobA_th+D+pPvYO%v=$R1-k4Z60c> zFLhbrL2jTXG=~1Z9fXdsQTnG^_rj=~77=n{<6pCqy5Ps%g9TO)rI}FQ%@_3FHqavRIbWgD9VvRPS0Vb2eVFALC*|wets__7iN#{C=cQ2C^wU9 z;e}M;)w#f4O7K%tIxi=Hx^!S=kHaIqB5QI1WwvsG>gU$rv{9&fB%)=$gv!0)WZWj9 zN8AmjQ#E?{IgCB1XU$62+Y=@ud-)J@HrLG`K-0V&AI!JR8_aX@B!}gKbLi{_!Kbg63)9)hY8Gt{0 zH{d5aYc9^9=FGtgnNODJhm?T|`(><;wDhbULRM=vUDOq&CKW9AiEqkMjk=D^^ILN9 z&G6`-qX5(u)z(93Ovq^Fl!J9SkYf;CHhZD0R+mnC>GV$hOX`sZQz`x#HH{K@3K;~YG+D-G zbk*&Fzd4+2)i|ZQGKLK047`OY$^vCN#~O0Wdq4>vD=xJ>RXD5Z_8Z0AJ;pxOjI4PS z$_bxzz0g1{C2S!Q76_>~iyEbEKt)WufJc#ZN8t7ORmFzF@0pKS!P0 zW=D#nfwRAJuXBfUowJEEuQQV~jq|HxfFss1-yUUeX4`IEYgtCM*lnZEnB7o8XsYjt zw%}Dd$Er$UWw3lo@_{_O4)h5G{U`mK{k{E_S>xsXnfxwRdU<~n|6>0Kf4RU2GS1m~ zHodknKu_c0c1@HqW7H_=b4#w_Xo28LHoIPxs?FsAyb(3Hur~jyOYlx+&L$ zjWd{8xeiZpKlgkMNYDU76GI7lF-yWZVLQCc3WA?%^=@?7y+fTq8hTxHr(XZC){&mX zm&m}!z_1vFH}yl_P27}T{>I&2!z^eowPJ6Klfpr$U$Oe1^LQ`b2G#h%zWPJ_CB75w zl7rYdjg(U=B9&ratwUGzP;_)zO2G!l$c5-_KNh?C0{pHJnEZO>EFN<<6g?I(B7caN z5~vN`M(#L>_E0mFe>TIdx(zxK>RcJ>$e4i7&>ueB7Wnk<(c6wdGq@W*LL#i~Qp~4M zuud1Uf95rIPPN99sZ;C<22#y(6voapcyb+V|FNdF+w(dWJL0IXF}vougs@4V+EY>3 z7!vV2VnF0yINeIrnCN#ft`cIz=$X-lqOV2uCUY?}G9Ojd=^`4!J#9r+({1f-36L3T zXDCBe0sWh_WTmLQLM##ZipP8jyz@7z3dfRL!bF{yG%TrAQgPUWm6B@17PyksJo!)Z z$duOZOrDRP`Q9?V$G(QF!9&!O7a%`%j9gAe^`SZeHs>Hze`dl_??Ft|21KtDaqxWN z$39diXCQ{y#6D9AHj7|wYfZEUtl_r&wv4u4)LRd=TCK+{BblXV@Hs#5AKO3)J;Htc zzbN~l5TxZ(+bf6UN0L=4DE1G`@?Z4j@lEo|p7!L|{&kNAjsM3z%DvkCnCL#zQ-Fx? zoX6}Pg&M~MU(h#|oW@c-?D61r?W9AJjlE!v{1m@<8SAgHx)MHs6V;FjChbmO3%Pvo5+XoSQ6M1z9}WuVc3T>APCR!=jWj$VuCw+9_2|RwHq^8 zjSuvBh1tS3;S%*`Z4G@jR8u+cpJW27f+Z#@KT)P=&#twHzVvm`{@kT~*Q&$TzJU^?7q>;NWm_xCX-YIzw)|#8D#Q5IYs^eGc68ep`cP5 zByk3Q=XvFwV#gQhs%~aK$;>^U36tjsvpgrNO@mNox{j7q29&0np_Mlk^|Gy~$en~6 z`ivaTXL^qQrk7$`&afEgSsrw{EL{Owu>%YS?VLjo>UG@ZjeI}EmEGdHu5;a&P)UCS z7Wx>z*9+7Np7Gb0e1D@C1hbG0&$))sTIj<_9cFy}LS{oNavoRUgw-`JWj8Bgnt;{F zWFBTdNiF(B)QDKfq6 z>YC&9p;UCip2ptQ_R1PV<>4&zW>W`a7Q;=toT4lWpW(XvuXJCG6kUNxe;E{UmU(Wv z%el{|6i#`O{D=(wrsVa>#+0%t-BK>46mpMopK{j&OBm|?N)>%Ce-yauQn9x5L>j<8 zu}z7key1`zTA9!jYs~%2qF>Iq7Xur(!`0O@EFxOUZ*0h(djn?5bEBRtrq$#$r6Ei5 z!gK+c*@tDu*+JV;AE8#KOsq3h1 z-C@T*#3=3*770UznL=~c-B0F3Z)WtZRAuZf?2-X(mX=*R$?SarBdZ9pTt#rN(GSAVjbS61Lo39viK&nJZIpUC!;#p1(l&6;0cw`h+9k4@Fca;uDZTG*urA& ze8`77K@RvZxwNC$heNEGM7|&9w;gy7OR?)6=+BZDdy|$RUBQ)q|#GreA$k)YgW9#_wkjOro;1APUX> z@y52J!T1^um<~<34Con_NY&BnLMCo7^``T=#z`Eb8TCQL0zFVVY)d~@`u%VZL&|fd zQFe=^=Z^su#*jkbD{TLFRQn!qJfdFZ3EcVPs4$-7>wTVojQxDXr|H;Neo&1Q@&a?{ zN--XFIEHXc;#0S=JO7JKy-$ykPiS|B`YBoH5nc$pS_p)=AdkFgS7*?N(=8&3wPOMM z_=;N1Rcy&V#&~Gzc@{@6G&9#_4SgWJSO1`~_=|IT&XL5v=3|D%fgI{v z#X4dha@}@O3)satvLB5=xYvV%W{|2#Gs$Y?lKT-`CCLSemG^+wS0H*ifqqd=R7^Kv zkzc|<&W|2xNI`TaGh{tVo>!@kxQ1TnHELR}V6oS-X2x^0hpSwTc^QjZ;yV=N4x-09 zmP(j-Ehl=(f0===VR_%;>s$3ZOkh3zRU5$5zoXgt&x0s5<`(K0~mPGF2?@@8d1yY39C~QUcYXUP-zB zzWSZ`>)6k7RQx%ACj6ZD)AO_IubAI|enzQdpDM0W0-vxcjOtElUkGlz2v2u=Yz`u)l=wglC+4 zbjsZ1`$+|pcAyC}`;YH;cWQil{L1+&&94T(9{f6j{@jnh6_Y2WTyZ$N*l~}N-zZ?bOU(HW{Pw0k)K{r8{?>6+SX-fHDxecQfH-q0cE7Lk3dC(c z(c)S$t9SrJ@uh!+-{#-w%jY}kZA$#H!!y}4+%wN}g$jY?-b}vpzW)BWz{9{Iu?-oQ zXmE)9T65|sFX-)t(x^>!GqxvZ>HBxgm6=sM!e%QE-@7(pI_Fp)biU>+&a*@(t4Je{KlHgX14un-OOzHW=RKs zw5EBG=>(aC+Xkm$2e{}c{bf4;OhVbZ4l0Uu)nDxV6<{;0=TjRKwdVur%qZ888>KQp zN3)l1hLi7rtvvyLR93pyPC;$vB)PX&a4Pbl4?dB-;xC?SE!0KVVt-!-!?5q&!Rc;M z4O5%A;Ssu6A%(|3LJ32E(1oYiq)7Rvj>2Brlx^VmXq$m5SwR2K!_!zRrD^ zb}npE75b64MDwYG=Fldq#ndY>%Cq2MpN0L_3)HnfXly+m|HwnB0y%}ZT|gO)CmzOF zoTdJ>8_L}peHTZu3n!zE-j&YQ_sFXjrk=h(c3}<~!=>cgma*e6!sf2lPt`BsuhX!> zv+x9FU>nC16O7`~AH1=X4T%)=kq8dJb|;W4w9BxHLA%l zXwE6ul>N#k7zyjhzOKW!-Vav$UJ=y%_*Ikf1(HBKTk@W#@ob7S7Y?EioSxWhhVB$y zaa`=8Wk9J~f!sI4k7+`@QjEU^QH|O|-nJf5AiWRR3F;FAy{3b}IJB;dXc5{Q&S(uj zUk`SpibPqN&{R#w9gX9sg0PW`^0gvoT3f9`4z~%BSa)7KmS}ty=e1OwhZ4+ElzFz3 zN4kLb_=_6&Yb)veEvpS{H>6&|&o@lb6)(f9&I*j?fjQs<0+rQCVj;8)BAG-ThQ1fj@ zuDb(wcQ~IslDfiGc*r|hb;mj9oA?}0P$Pbf=lC2C=^fwSbA-I4JNQQXdHp;*^v>Ml z(!|sjV%Y1<(s^XpMuU(i;KAi(4%pzRzC=OlI5in3`Fa$e?dJ)Y+Jwk;FxuClGom*XKG+%d^pJ=43T@DtpBP+-g!9+ zrTTAWKxWHg5kh&V&FB$3QHQ_B@rK!CL#tXx*7OF~*$z)XGdiaN7>ACCz`zL8cOIMZ zoh#YH-0!T_z>6sk&Rkxr$=I|aYN?HXU5RVSsl_o;#rb_0S9zaVl9>_h$$XfD55EVD z;wR@^9e;Wc=tY2@XwmHWS?R2kOCO1SKzxQcMaA#@pu3J1_ENm=_UL6Bb$6LXGw|5k zGuKL{>XR$zOuq+zJB!8G&0U>Kw~qzT_ zX6|LWi`?e7+sy2j@ChH{>wjf#E8u-PVgM^0s05Ve11KFhv1%C^_iXIB5&Uf7P=X#} zm`|+KJG{pk?)X8D{d`JDb9@$`JBgz=S23OI2>HLEPB+sz7W4eV)HT0>JoqUzyN|G{ zZ*%pbd74bD5nAs)=9ffW>jYs&HHk6W)*ZNCT_;cZOkL|irSn@1!i6W{wptMQjYTq zb@eOBdlll{s`Isc>db7-e|KPy7{t5vVfP)2j?!eJ%$4lL)2S<6rCWre!ff`#>AYep zG2n1^mEJtsv#T^_Za3oHOEBvz@I5D28j35c@R$Fw2USb`l+YQK<`GiM>dt2@0QsE6 zd(Psqfb2%-(^jGOx0RK@C-tWly7lxaT1Xd)MMOdK`1IvqrPJx6zKTv2tNCd*KhH!j zWj5y&I@hT@mT|s|`E4y$Y#z_9;J4-c?@ZlBo}0~eP318Pn>dKiA462zjy!w2)GKL2 zeA|LtOV8Bn>x8#8h;!}AYsaD?F@czQD*HV>>Upf>tmks}%lS*_ubVlRbLJc9A+Uwd z3iaJs!8tDEd+7C(`MQ#`7{zO6^Nyi+ZNsP5;l6j|Q(7~Mov_tSxJUIFu`*bRYN_+5 zDB~3B9+02!6_{fd=AI7R;~BenXf0o44ZURsy<^_~Va|SF2EAg|IzXWc@NVNduU#;7 z9)s_skbRER>+v6p;L9ZFd*UUopv&W7{IV_R;7>!Zx&{_Ek3Izt_y>O5WqN-dK#is` zqZ+RJ2vRYJ$Yv^C0v5CH&1WU=Vb5F$x*h7su$W!_ICka}s)0qhw*FlCV(!UhxDPjY zH<^q=dXUJj_@Wc=`}ToGo#BxPI&^^VH#z=*bH>1iD1#qe0kzsjV1TXh!p94fV8k6G zOZgT>2!kOb8NO_WBH#xl$+tEpE728PV69;*xrw{vC4Nx@oej2VC0L{Nso|agN97XL zAwkgKL8h^$eWuf}N57jK;QVS{w{KNDlWuvZs^iksP7_&Rm?#2&AD`%|^VE*x)^U z<`SBWN7@`7_(7#HIhU1kOZhh$tVU91=_ZV@9$=;EM48;y%fR=*vp{k{4wQxIeS~ah zU1=-KlKx}{;;Abtj^9y6+YYyPC%p7kAiksU=Ub5>%4--xHpK;FZ?f?wce{#dxap8d zXU>m4!fx`C>BwkJwH&ng(0iV2y=yIQ8-oT=8T%%?+x`z(f2Xsj^MbR2YZZ#mgTj7= z^$C9&-k2@`hR9)&Pa>nEx}e#y7v0iTsIYwte;KyZb=i5-@d>;jpY6RRXi8@kgsZ{s zYCn07_z7{LQVfD4PQ`b?@85fxxmU3iK%TOg>2b4l{)N?LFfo+LPb@dI~8MX!9 z*%4Q`tC4e&!|qsSkG7w*b+Ns*##{GWVl6Gr`%FetOXE%|Fp3D>=)QN5j!(6<)ocJw zlzDO@8Rkl2Srm*i`bv0nd1|;9p#;${Ib(8M^39|*n~)sO1x+loOe6zrv2j9z|r<51HM^F7OD>tZx^57=L#aOt!AsY<+P z-)SFjkGFelYiwb*3Dz@~KW2|f0#{3KXa~o=xc(j1DKDIaHsGlBw8umr9bg((z~`TX zhWCAN)b#k;9keyXrO!17`K>g>i}85mRq#bx<2z+f^$DM9N5No6-~$>^ci)cZItFIR z0W!M*wF0{N3y6{wts0rpC-hdmhf@2k)VY&D9YA^G!c>;oM9;dp{sT6@N~pgje$oZbF|Q_}_<0oU?h;t<9U_7Q zVCbQU<*(`j@93y4(oPWryw(K#zAErACegiSZK^%>VO zYD;3kAzsnMX=JHfGU3W@{Nf>NxF6P)oH_XI z5M4YwWfZ-DZsCLM!Z#Y28Y7j*qpO8KHzU=O{-MGx2RlI*_T8}{zQ>8yJ>-HK>U;3{ z=Royu5licZ>!7VK^>?x2cgP7|!Ja=SS9TrCo(MkBgS(SJW--7j&92XcKV1sOQ9*G3 zVtD@wu|Y<#Mw_9Sj=kikz;)u0G^nKVh zR&XThKY-TX*GI!|sS3K^8nm|*R(m!me@|haa2IQJ7Q3woXQ)*ACOn1Pyb&9=9E;W& zo=7FZCgh?S?_XRDFFoy^|T1s~LmPng8mY{$PF7wSaHc%_HMVZjgnOZ|B) z-HQy2d=_S;g=Y%!*K~Nu74f7C5@Y1Xj}LVyDvl*AM<<(5&zYjsfdAEuyedFMCNmFy z5*z&DxDImlgNWuS_)sX;bi+qT2j4mxB%&;etYsOa{}Cn7!HzhfKG}!CL<0@rm2@X= zs7Lfyi8)n(b4s7;t;g_rPFB4Mu8$c{K7!9I2Qt)~eRDJM<_lD2GBVRTGlwtG1H72N z0eH@1c+{=Qq2Cbnh6LFE=TH-fC*$|j7;WlLEu-073ueG~b8$EWmn>;ef%w-NWov0$ zY4cNsycW%|co-k29ZBfy3~+96-f$XSg$DG{oEuj>mArdHpzD@1R9nsBB|Dn=Fn9+=Crm%s0qu@;3EMcV~7Vp_c7* zav(VknDLU7>f|-MdfcAbRDkaE#rq9#COeVWtVli64dt!+kjk|NItR1zwZ0y?zq3M; zFpLcR7uNj}<64-&^SHt|*7hTmS#pA^-sBz#%!@5VtZwpL~+?`vGF(FZ8Qhp)JJ{RR-^G&C!kM^P+YW%NflKNY8v6 zNwhbKm3{-S=?t;JFFd(l#2Jr47Y-09wI>p+fn_x^b423%RYbCUp}i|zTN^iyUgA-mmO%N+D5IU768-A&fc3rHIa!; zLg_D2*`drO+tW&^2G5}|2yI3sjbc(Hw6P^vcJE+9yy2@GW_KB-0Z8HtrGz?=-K8Kq z;dgBeQU6q3A=c|?Atzmbrof3kMdzbm=KdCkb(qy}?PL3Et6|@2e`ha3c4M*QnnQL- z4v}@)47`4@bHDR03TApTBIR5)T?1XaU3Xokux3;M7`iuLutdpB%Q>mC3c*dDSt=9&3AN>1sY}tYNsTzYoVrLlHp~HwXUpPw+MN4)yGI zPfD4U{4%L-(k>KV8~;7}_boY`p2 zUdi*7t}k~y_dGv6nZ4D$OUa2W^X2fr_HV_iq=OTQ4io5pJF>fV(Ww~9+L?}Z83w;T z43HO_U8NF?rB7-t5F{UdZ!cEw80K{sy1`ap%~xhcyTHLd;`=P7=W2E; z@b=R^D?hy5dw9%8s0eVAhp43vRTqQceWQM&G)ni+Kp=+`y|pDiDoZp}kWL(4p*kF- zCk7D}m1)MesK<;meJ~X^Pk~DtZOLqDVVR4**d;P3MbJ{|hZh}dt72P(l2f?71YH%z z*<0HO+o#!=(O+S$eYd?CI*ondM=!9&z?wa2S!BKjS43z0OoTN_KUmi*xK5j+mQ&tJ zkHj;KM6xfNuRU5KGvU5@+>>F$HFCFd*K+qkeWfP~BGWy2V2Skf4fkiGqHs8@*{xD} zEc{+F01MQntce<6F(t@We4<-+bGnSw2Qhc*RoyrEvPZ~X&msD)1h%mg3@x6W^&a=E z9sSPk;AysCg{B9)KgUXIuS8MJwiqQSKm3j+a51-2QJk0Btkr>bFtqJ~gZ{4m9R7#C z$-e(#*Gv1d`zrg|`UWyjj!>Ph_g5gtw$JbJx1{1NIq(k*xSrBQ6eOQgIbRRftee`) zeBh|tz*Z&I4n`S8Mj=X5@fuH&O=v|XLBc}JfV0sHMs*$KAI_=+JnCVvr&poIl^YJ) zI#}Q1;6;SG#)d|+g+4{!oBD{x#G+M=XNU9jJFyB@dqw*U z_|DJlpX`MlJsrczG$xX7+~v6FcdbR}8_$7@5?aOJ4!~ODK#gz#>eDs{atD_AGx!Ji_IL|&f7$>Iw6lb-#3fa&L6cai4R0 z-H+VSo__E!e|rXcD^Y9L&YzC@#kN$Nx1tUsJ$b+~FrZDu8W~~V?*jczU~P86qaRIF z@(a&y7A)%se1;<+=WSK1x&dqc5!Ou^>Lo6a)vQN8GZvme6gH`<)K*$W-t#K{z#n4s z3@|45Qt6l-w&DfFs}v;5G@ne#eRiDRtYvy7gKIZs7C*wHEP{_ZpHIGoJ&GXtdZMF9|9!;|M~m-s(Gt<I&*sz(i<-%?MTZnb`MdQ`PwtPwhO_AcGCH z4A(#;S_|Ka9=>zUPF)Lp>|DW36_$BiYPT~Td_wunlW0g) z2SvQ3a}qUe2Z5R5P7Kp`w}jra5!1kYlUksN)U#d(E9=Q2Tn0Cs55boRz-LwZU~BKi%s}c149ngb88xut(Il?Z;>M zG$`(-Oh}1O$(2$l zr6bB&Ro!W+W$i~lr7-GMPs3$y0K=dp*XU$#i=tBHuzrCM3rE&Rue4FdvBtT^@kHiB z$bOeJMxgpL9Ix*%?7S(&D)&J9?hwtZ+7dE88MMoITg}y2^|P{B$xpRPBK1XosT=JE z&UBJ4hAm;=oC(YbRHLrCr2ma?9MzWhy^FoWyzRU#ydAycPz##P$RGCpp+jRWtdhw; zgzB6wRI`o)iyB5nZAW=0O3kD3-c+R*EAb0)eGx4O+0m?I0$<_tH94**{8C z1HT&G8av808>opc3sX}HyrpJS3K-!5HH0hu1&?BBYGwXzbSb*S=9W+vpN0<^fxobm zj8PGyy{Y8>E`iZMBRBQ~o?8r7E+qeKJg*y+Z>HWpzU_k_v#0iO2=(6Efgp;Wua zQz!A$RF0aq(`K`!EGXDHi)bm${(aQ?*_y-lKajA^w%4fIhS_u5tFUKxxA(UH3w|Tp z;~Znz6ZSh!!07RPmV`26(bPtY1$nkQq{8s^030ztJwdOg-?Lz&Y+hRdE*#sBzLsDIeWLqw&ygC<*w^ zg{WR^PSl+S6!|;ILnl=HcMpck3((qIUFJglbp`X`BnC9M8Ha(w~v z>!YcjiBKQ0E(Z~(J*HBrgB(kwwi@0@71%HedLiqmUMxnH@;hqOdj}c^(gr^I@A@x* zldeY7?LNm3_HfZ}4j2L%0tKkPoEz9jeQS0!Cniw4E>MNh2yV(|6u%C#jt@x>V59^= zEHlFjsYoqc4k{*9__e3;?-og8V6cva(K4NC$D{o9IV^Kk(qOd~mD|I`yb43K9s7wM zrpY$)$Cb57ta%f@<5Y4(x$$4V>l$$%tU?!9XxD^iRHR5k4AD?gj=F}XC?|DKtzI8M zuh?d=$#c;M)|q;gg}>W_dX@K7&=sT(Zaq~1Us0lNjhA-=^~$N{yXJTh(k#{qaC>Um zCfUy8*CjC0CsChjfrs$iQPf$>*}&P^InUY9na`QUnL?+$CXV_J*}lQv#$L{T#1?P6 zW$kTUV~LH`tqc4JZFVS~f4Ua(USmrN$&H}U01@}gzvE`E&NtO=DuCEz6FP;*fW zRxOn=bAmX^p>-t+zC<3fJvqHu;QKbPs|H|&%R#bUv$uKBqL#=m{M5a}jQdTICzu{!uV^!sUx7Wb{RE zAScx|KN;Os@ONiWn_i!ay)v-(BJlvzh(Sib2)b}BsCDn3szx$ioK5}f4wO{xiBG7` zFrhpYkA}fNM3mE{QPM1qMbdg{lXQaE;lA{f5szVJ#KRzJ3YTSqybJ#A2Wqyf5~1#d zC6WbCVFsR|j~OtI=v~z6a5W!;Wy$~i(v^lq`yX8VO6-cgU^I_{BRrO$22sg90=8pk z(81i)FTJ70djbe~Bk;2xL`6%fs8ID)iBIOjfPMrX7E2CeHZ=%w>>4+W`AuU`*2`_~ zg|@{{a~4YjIP2#@%*(=#T94MJ!&cf>&o;<5jK^ReV{H@pJ{kM5!nPdzXNGMV&vm!e zwUx4k+rC&&S!Y^XSaVnn*5gDDeJzzO7RyPL+{&P5Gu>2?irntR`(DFw!z`3;6AUK9 zZ{Z~h1f$u_+flEU0$+#r+RXNXR6-OJ6397JMi1Ag{|M%Hnd*e~T+=kJu^rV5h4eQ4 zT{y|3K(sP4{>Oq_=uF*|Jar)$5O?v-=7S>V)6y{uK7k2e1DPEIJ8K}-tzDRPt*Hyi zr$)lxdCUsj53^_>NO=jx30wLlHICcB1Lnih?28>~%zBNd0=7QYa{U>XNpv1VOzXdAf;pFmzegSwpMUW{T-%uWTy zW3pbm$o$QLVf7D;+q&S%Mahm9gC7iHmdQmQ*E0Qb&aAh=-N|z zJAz{t+A7mfG(JZC!*laDbbxiJi!11ko<^s!6}pB`P3ui1Od__ZH%hs;3=639$!Rcy z>wJQjza2HE)p$0E!hI0j45^IWVQ66whPOM$(1owP4BhzN%+L}}Zwc~pnGKOtTs;8a znqKbw(D6m+l29W5e;?m+yh8M!Ni~$MVLJsR7 z*~h)9+0@bOvgKeaYT%`_@x6*-PcDLmO@`;)9A-~RW>h##ODM5$W`3y(7ukI-bEo}+TlfJ>I3IgW7wTWyFcw`>_n^Hnsz0NwTZsz6F{mo6 zBp2G#*be=mj8ttoj4uo?VOw9Ki}?$Dc`sO@0ZwaqV>4qP6gftd$NR@vkzA??j^khO zt%YP=jfSVxVveS=qc$1Hmh6Flg-A5F8uE$tu^K z*lJjg0?=;0E@0j?B5r8RYc}vGgtE;i_~9Ldw1S(epPke)9Oc+YcK8s-c5)6&;V&Jd zo9R)WJp{-69=VTCs419)7kDM7`P&5j2(lCNV4{x#2^vODyDnC#Fe}mp-|-$flne0J zH!u@#!wEP>Zu}yuV^?%{xx+FVJkPk>H&9;~pUOU%3{nvfhodqGZw`2g4?*lFfc=ak zwjEFA<{DhV#$;!fvj5#?-|GaIq8uYFGv4K3VHDA2VXl9K+3}kH>_g6ACO?U&J^#|x zA|terD|y6-&gQ&(l7BhR9M}w^TpF%Oc`{n<$)c}fhMWP}{mcKzR1ubkU;3JUW4Wnr z-Azr~P%`F0>Lo%pxS(Frh47YQ>^7tI7xY!Yz>4s?&E(SEpkl50FF}YB!q}hou&#SC ziXVyF(vd^XNYzC^pAprO&g2|2|m$#CY0jSGQ;NJ z-=x6sm-yRub^5g5a2{;C^6hUkTGp6aDC#ty`eW2oSj znZr96^QO$B4p^Jf=$^diJVN&L5PViIR(2}>rL4P*x_N$KICe6Yil&~R5exXt3tZJl zy^Y<}K|C^s?@?T7B>3M7-ftp0XeME~K3+IZZmWs@5-Y7VSX5?a#AbdjLhkDkH4*!$ z%|F0>E(H&6FskC$g&8Qa?C1U_Qj_vOuBtNCOXsOo3TZZU=UipFC*3C>aDr>Bi)XCY zU)7Z$_qLIl`465!ag;WLRH?=3CvxSjbf>}RyAmh;32wk!iU<2zin3ui*~PEAaIzaN z;XS)Z&7#gnz9H%CT#0vd2=F=MHVKmp$ zftlA=Hv-nyN-`4l@ZW0c9J(*TDDKrb_OOoZZW7sALAXMG$|}4f7HB6F78X$favgv8 z9e3n}t^-`-7Ob!B+>`roY4R`|ztbCHn0~yjBvp$CQP--VYfeqqNBT;(&T;h(pg|J6Azzy4}v+D1#4*%s`^6F;r{M-=341{PC z{{0U!Fn@!q$c5f!X1s(wU6Pe_lY2Ckc~}A)abI78`LbVk1ZJp17Y8SB47T11(vwAZ zh0mIb{V?kv2J>>oh4lfr8)@lsZsrOLa0h3Dho!@gbfY%xJsF(#!E@vtkHcF_rjjJT zt}%JnRl2m?n_T2hdck3vgun0tcGyPhoSI@+YU_3fJ8P?g+jQN73*at3=D(_PO#}3U z;K_B?6=l{G)lDS_837-sKA%;e^T|S;;v~G5jJnoZHcbtV(G5nayr!-h=h;*LTvs?a z4JKcDtjq(h(W3tl%79aG_Z`M&U9$Aa;2o#Qtd@sac#-UX+h8v;qf>ODZivgV%J+0P zxj(Qph5E4eJ_O&ZV}grygJ3Cd*KP+}Fh+%mpcv9J>k^jm@O?xZ! z>fT~gf2aqw*W6ElEiDf#^SZNCmQK>uVPBn!Z`CjuhA;jH#qsN8 z+jeUIG6q}7UYhcGX~dcnRFZQ+lMn=-?a?E(;&~S;KeN`LS4y~4GPYK54=<> zr1fP6P39hC*Bziba;#P__(NMrE>6bZf6dGa(5dSqxwcK(3ZkIy#I`Mi#Te@`VAsF- z^yg}QU3Gm=xD9(?XND@Ma&zwEKpba*X~$?wc&EW?-ryGIEfq?-0s5yfbCOv9$MFTT z!W+*4;~+vmBv@PRs`b~^2<}ina+lj_mpS_c{Y#iduY9gy81!r-`^6CB%N0tf3(I3^14;B;5#oTO3$CE9LWCKPkE9XBXI3Erct zPqI<#v~isEn&1$1oZ6nT{larW6ZFpdY0LQ8tF8>z;NHS4RaYqvs&d!D z-l@%e4%KtsqiU|IwiP9VUs}&#DbC=sRxvmVcKsiG^(NXJ7#EKj;dkh3?xVJ@Oz^n+ zPR@wmJ{WXos8#^HvL5vxM`4Sv*Ame!ysHJ(Rq$`Ka^BNX=-#as*8U+Xtw;8{kGe`7 z%~=*eP4yjq{#fv>pJaVnaTiaknYH?${_nKSL>&>RjD01_I7iHwmCB9JjD8WuB|_&R zuk;vAfV=#^k9W<3A~snDovtBUrVvl@rGh=ARyr+dH(7@=%b zPcx$rlV8(QIdGME|Cz|UwAxLpLe<&{brbjR9kx9oI0;)apSv6rsyKnMG6mfGi26xs zt#yInkdG>ty6n{hwC;4P`Ji>uWeIL)u54ppGk_NThsMJK?(%ajPjDaeCJpoP4>gZZ zw5hri!HY_L?Tmhn)>kf}j^nj`Gz0Z6TeMtc3P!`u4>Eq&iK>KPPIl&(+NayXElRkDNEEV^f5ZlF1rXN%4~QUJ$3O~CFXJrxYUwh zJPc@X=av>QdTr)>-+~PTC!OvT@juPeFlRRQYum$sIn@GAsMl zy?Cxg`J@AUwh(N|KG2&L+K9PXoYy9+0X(X6sHT0_@^Nl!f)~_Mj35kg&8I9^-vle^ z=LdgdJ#K3?$u3?3`A^0VdamKuXe$+;ngdU(i~3bb6MRoJwo|J|Ch$F2?KEbim3!q1 z9?~v=2o#`(usPVkT2>o-k(Pj^-KE8$2=oOTc8K}9je1S6Z+5X$_#*4LCmZm;r?7_) z<80eg;aW?#nkvE*ni3p~ja)%a=mHtXm+aU*u+o)b`&`yar6K_Pxt<|#gTAnup5SMg zb!YH^RT7nY5LwImkgICA~b;2XK!MM&Mr>_Ku zP$^dkPX27&bFHz~AQ*|~pCfn{&#g4P^siuJDcUS7|1!E4-=&+&JJwDKZ2~?_S!UG` zc$qtcgQ$dfPYqT9DmVUski69jr>89(`u!Fn?hc)Q>vl0@6BN&^_!EHo|JsE|^#K(o0OAi?xo&KuUg7;`dz22}? z&o8ZR7`soDUZ=~2?N1E;V6MRgB%b(>HLwE&TMc<5!QaFrJ771LAVBQp3D z?5{aM5Uzj$zat`OrW=nZ{YMwU{rM4!Y_tc#3&dP-?pYnw#_0d*ow^Npi^qdTVzz@| z4(*6Vvg*GCGie$1|I_kYF*;|Hk@%|Jg-WI z4!Wjze>}Ig!;H=H&vSl-j|MSm|Zp2Jx()8^q`qj^zDg0 z14Jc0?C?`v0lh}lLA?xJbBCZooD06#ZQT$&YMuTw>#MN-q`rPglYmI%ZLph82lG|X z&C~+HySkg&4_%nhoO+U$U{+@s=UzmyMtvN99`yntZY-F{8~q=mt#pQ*#N4s;`^^sK zT@I$>DZRjZcIEk?P)#>hO{;5WIIRm}_rr*tIZV~j0L&Uw!(kKj$4JoK1|H<&Z}Jz2s$>WNBlTQ{*v+XaW4Z{J#TBDU=|eeFIvyCv|^Lc%k{>Gc*uC5KGGlPsKHcrorE#-NHzE zJ|U`{UP;`YeTG;v_+P>5LQ1abSLm`>M-W2o4Abdxlps>&Fa(5N)Hq|Q_GWT=eGD$) z8s->olVcVVM;R)F4jE4Kj5St8bFsS+1u8Z|XvvC-H`EqC8>~!M8VLEt=0u#O^yydg z`bOa&T#pK6J-m?&ur~tqUn&?}!cRjwVFL9-Bswc|n8?JT!2XS@ccHM=@Rq!Pf|y#k zLT+(HC?YqIbI&md9$=`UW~%b3y{Tch%`{t*0uhj1yO3p6ojztaOn^ zTq-Uy+zLfeiOvqWsGVE0KT3pFpa}7UT4h2DiOR7!-?fob z%M*M?XRs(I4tb9-#V|6sNJwo=2rUcVGyEssHDu*HI8AmiJ?IL4F|-kmQ4iM*`Ke_0 zh90s{Bvx@W{fZat^s$CT^ygxUA6o^RI9NDH)!mud>h{n9@|Kx~xk7JPUP@??;UV3Y z1+eEDfa8khZpk1%HZmO@3eVmSzTUw-`3YC&6#SSYyr$m7lc7S7&?2s3hme~*xG0l^ zlPK!O(7#j+FGDK~i^bN49HFs>hGL8mPuH-e(ID0#->j-Pg!JyuuLH z%399J^ui8sCzt3CtzfU;HB=WLf?ZzE6Ah8?O{9A|l8E`;FkF~UA7d!YpkIa&^n#AD zMjx|kw-YB^h7qCUVEE3#$ec)rrXa7a3rcJ|gfF2RhUMb_gukI_h7H1Do`Q{f_9d(J zx1p-A5a!-7^nibJGISLOfub)#FD5Tj$2`PJE74FE0%VJ8>AMZ#@9hoOgJ}$9xI-&J z9V`O_7b)5~$y*v`@!ZmI;&c|yfhmfmsk%-hB53WBiTYd96g1%P1xz`a!Ie@zVik5hIDzbC(N0+qY> ztf)w#2AH@I9V8{xoj%)R`nkvH`Me@ZrxVW!VWwOQdSCHOUc1rX3ecr`&UrbJEa?bq zbR~)~z1XK&dGarrP#;4_YZ>QTY2wqZkZO2AKQ)%0))$nai9ACT%LyGr$A#?1rUo0k ztS9}|4MHE*YC5s3SO`{r0^C}==n*c3j?e?!5t@yb!vTJK1o8bSXR4WX(a_-K&ipZq z6|0Hs>1A%CQ#;sjfjppT=pbFjUvveUvqP2;gT@lorgDcRu{^!_m0V?4Vr?HflIyuk zFX^p)C9>QMHfPt(2-W4j)-|X?8L^8YyRe3nxSIG9o||19&Qv6Y=nq93w$qO=2pav< zT893t?yn#U3!$Dio4cQ0{KYwu3il!(k@Oi+^9B9G=bXA_gi)c3h8|1|mI>Q9L8s8U z%mUxxnIX0KN?3`Pyg_&jF8eN=>Jf&MOr-`1dFhg!r%U&U{=ptYB>i>N6NK*KC&MoG zV}D@-ov#|)`4n^wPtb*1%{?;Ex$SA_$NaM_6NZH_{EpCVyiFYcO)Q?v`u$}n!s-wW z@pJ&|fE=01o_I*du^+iq9E_SjfgizCLO(-MYKT}N4eaLJoV#0D-4(cd9q5P6=1HQFQ+1L@z8`}RDj za&8!4kCz~-y$ZF&L8XsyQ|uu$XZMNhx(f8-7Zb^!FujzCZw+}(Yp4pQ8QOusIuN`V zYQgj-2KH)@H8&B>-$mjw;T30ZJb8sgmN6kz&yYwTC6+4wd+>ayIrrVmsXl>xr4~=P zInVZ~VZ6bKUe^mlYoVTqLMq+($#nhR!|^;2y2Rgdk}o81pPPbj-ok|F6Vt@{{Ll1c zJV$sk$;d+M)BkV6nn=d8dv+V8(vxeLNaomrXm{R_o>AudfdCrS(bE@~dQC*TLIu<*lQznMmR#PdUguJ(2G_!}V6 z$N0c0y=7Xmp8bCXu2p4b$TsG2dtsrzqDLRrK`TXUSBVJ~=0~1k;3g z_CgxlwWp*JmAg zVrh6@>6jQk;mjLGF1je3Z=j7(fmb+>e}BiF8I4wNb#W@5*J+r3Oy{mNFjV6S&f)q~ z@+#w5Gj+)zYVg1BQk5r%@w}2gx{KYM1zz7In5?^a&1+zJRU|uJ9ePI$vI{EaDHro!S{(NaaSgdA}+g^ znB^Jyw>Qj$&EzK?SjD?oZEN}XjR{VZ&}BMpQ_(3Z0Y|7Pvo4*!#9ivi^K?*)a@K!j zhEkUJdoxs$ub-yAe~*Gdn7KWTdzXgQ8wchftP|)#RpS)(?G7@$0$g`tVpB`H`eit0 zp3?Q}$KLo($G#X7v;P`JpgnM|4 zSxVSnKCD)HCF%c7AzqXtS6#}p-_0wxvpyFS)g{;nviZf_$$7li4t&>ce)1*Gjwm8PI9uCC%)oOloi2jlF#b0 zwhPr$H!etLxiluHIzi1B0qET@#HR(c^2=Iv^fN(pu2dTtGR>AcqO~Y z1>S`e)^JNAZh5Y;J@+UJcd9MVCX#A0BY$S%R2|7`UPin-&%S%ce0exIcVm7}Wmdxk zGPxVPimP0Ixa!Qny1Gpde>C^GFVCqRJ)9YIL=UhF_V7F7c)A@~Q?pn>1G&CUyrSpa zy_CG>9Q@Xw+|Oft^*n4o13RDAW{ScoOCRg`U zVH`n~VGnC-J0I_{;uf&d7jV{H0lB=2e_OyaSj}~x;i~g-*ZOmf^T=Knk{MJcqxj5P zzQlK5U^j?xZ=<-2*@&NE)rse5puAz1ns`=axsK-KDouFWHF*k6SRq~c*okW!$Zn}X z{!yPbT#<~l0xKvp`!=jI^PYQjpS!SviS`< z5w4>Qt1yyywu|T1mEBo`r`w#j&P0h0#FF~F)g*egV0K8yoa@-ly}ZD4y1-Q}U@b<| zp)3RPEDtA1R^m>W``(S+x)>bLKCbvW_w6L#eSmv=oNIW`8j!i8+1OEO=m&k|c^v2W z@8i|pN`}z?*rZc%3&U-Asr7yoLhTUC}-&z4a zWmuc21fOST9$t*kaWbgisiWwV%SlsCAM>Fs+=0UU<`PT`vNDOc z(ScQ%Co4>r&2%T-oCtQ_1v1J2y>Xp?3Bs~Y!%z8-sZLosFpYR?%6pjOniuYNdU&oP z|Hm6NGA$9&n{|P#Fwr+QgRE2e|G$C)OxU#K{dW5KsrYD1iq&R*kB#rk$xklI&#TT< zw>h}N7Cg_YJg35(&*}L28PQM5#&?wG-)bg3@k(4xW$tA`zFLy&sLlTgV^!3eQ(Y)nLs>t$3%Ws_&dXKWqH!)Vdc=F-1{)EungBzjw@@*HMQbvJ;6(~ zpidS4TKM^AB^IHw=%(B53|w3?@sQ4pIqfW z_00vmEKV@HxW=EC_)7Ti$C%zO1)DO1$>j__i)9YCJ9v`G$WF3=wM^E=GQo&JF}E!o z?DF6rn35dcIwW1YPfcu$~x4c}FfhQYe=JJ$z z-#dQy1LlBFldj@4xyaMtXB^0bK_~w&Bd@j&Ppm!nZ8CRjG5cpX_xd_hgR4ZV2gK|L z>~)1`L06C*p&n;f44K9tV(5ITumi;XV`SS8P!3B4)+s+YxkhBt<JIuTJIFM*fIeJ{274HRc!zxODHV-b48ZUEj&Iis^8VXo;cxl)hKyO~ zJ1&7r+yvUGCrT`-m>j(3d|%B;6V?PSz^*$>eCtjuv4;HM7q&5V-T-fGAT#Zj!OF}x zk};>h$|PUHTssZm+>;n+-m?ruIJ+9dqC|5(PUe zD_n_uN&Z0PV9iv*9Vj{pE4@_Pt-Zll=0Cj|KW{y(+ux{^S3oOp4U<4KT5pqh+D`V( zE_(c3sRHN$(8;I@JJ2gOMA`SNF~6w`Jfd@^=cW{(joYB0x)eX&E#~#+9p>HUjp)Q2 zH9s)_Fk3CfECWC*Ct9lG(|Z`?dod|m+6R-ml)O&1;sCV8meD@bzR&*8Uc=GQQPR=M zF#ujnwB2m4YAY^xl=@nuEIN)t0kk+zkWp0%e%2ppi~V_h0j0gt#~bhYpC^mwLgKl^ zFNwK4X+8bmm?igi#`$!);!;)hi|>$su(m*N45xf)a4M_pf7Cf0s0)r#7dYrBrW8wo z?n?vm(Iq|r13wQ{jc1&qMagm&uv&k>|6fLbxCEGs_wdvU!G>ywv&&Qv>j~(mmh->% zP4ZRo3BEaMG2Dl?DphejPBSXZs^?P4H-qc^*g5-+Ty0S0yK+0KI;J|#prX?e9i2_6{nSKdf02Ezt(B~y{(9M*fb#1lag0#e@I2@W^wo0u z@4#p4W6gVudQwymnc$U|c-}oapQDfC7 zstbn7Ak^nG!YG~Z-;UCO$6pEt!gZ~c{zdN$Ypp)+Eaa?85xT(lWe8q`VO@o1c@$TZ|X)1QeWFf*?(Y*X9pnW79lb z!$;$a-of(MGK@VqO1dZIl?TfQ!{S1t>a*qCv#f}S(R}RII&)LM; z+qn+bVl`K5S02|DXL{#X`wm-qc|Ts8^~}voos8|oV#0+`_uxVOrN5FdpZWv#Usq2y zPt(L~iG340Bz8(PdX9R&c=~u-DtDFUYCT^&e`yWf1t#vBL(l1cCKsz2Tbn$l9p)95 z7dTLzM8B(`Un;(PiQYw6X;lY88RGteGJ>SvTy-Qa7= z>ASRAxTTr2E%5)V`h&P07V$Z7MVNv!SP9jjp2HQa2%b@r5~<{eO@2@Lu9Q-@t8dk0 zD2G?`$NP(*7_tat!m>aea67}PVGq$&i=_+jo9XvSJNdA|o+ux=_->EuCKx4L*-%x2tSOlj&0k0d|rk-g?*ICd?tT(%T| z2c%oONe`tmL{yJl&^F$7-B#G%5iaUKdrf?|HaQY`mNlF!o$s7ET-{t-UGH6XcNX_B z*9PY?M_qfI{1IP8w|N3g05_f5CZT-6k$M;ZEVZ68$J00Q^S`72I{usX@72FqaJKh( zx_WElwUym>$M@6!NxP-X!L^}u%qoi+x0~AH13Jsn);h;}fNNSJxuosZ2G$Kkff~5D zO@+DUF?KYb=l+zEzmHH__(ZMNk@}`NdCjQcTX-iv zPTu`++P=YNyU6di`L?Tr)VgW`JfyR#B~gYuq(-n`3*sL+9@mE(dOsW~&IP;CTRFf~ z=pPfruVQ^z6PmH5X|d@NPWCFQYM*gL51M}BZIaqt(%cYDtbJzHT+^}$(yhtL^LM8P?{>SWmC=Mw9q9&rEPiNvO!VV*tSH_BXCqYeE_wGMa)We!Hu z*;+0f5c8wnHOpMsVzqX+mchxWp432kfL=&p>v+ou^F7ma(+AvvG8z@JfjEUJMt8#w z&=$4X1r@(;LO~MCd(7;DA~#`e6)`9d`FR~sQx`D{bb|}-Ro-s zAMhOR)%oFu8q_G_?jo4s{qU>JgA-Rum|!2Y&SXtl!0I)p7FiA&TqfFG2668NvwjM+ z`6}@Gad4D&(}P_Vo>$X(yi4vFMsAb<8C02!tSyQ$%h7-Mg|>JUek0Ry75IeXYfIGJ zU#QzqGML5N9W@hQONZ-kD^BJGdN%ZDij&=rmN}+^Mw|EyynN>1F8!N7!goR0 z>>cD;owzg6>WTCW^_=tWPztGyeW|rV`UU+@U~}lGAx?NJt~Kp3x3&Cd9VD%ida*Mr z!aF=7U6%^Ulcnk?4pg&v@m2i-H|#K7^8?i9+o;d3g5vs#c1AZck1D8n3`HR!2pVZD zwZ$_0Rz?L|p|9~8UhQpSUnlaE=Xw|2sxQa?DIQj^8{g_)zVbdF4CR%0Ro}&fv4ZcY zFFX44PHmEw9QPJC=b(|8=b)ZSNk6jzIaoS+;+xQ==!FKEQ#{443^Nid5H*~1|JN|n zNkgBsL+Cdcx=Ns(SHd_f5%{4Wh2LKtUG}qNQw7k1xyRGnj92P#*rMa{jCnycX~i1M zqHhLwI607$ti3kfhm)`ubm0X4^{-w&2E5PiGa?dS@YK#{AW>zu2cd!IXH#Da)N5gE|^ z*&W#-a&g2WcM+G{p~~N_y1BM#Kc{GUFvxcUY4z9s#lH7SEcyt|y~91TJwLqh%3tM| zdeuJ*4~rsTUpB$oxFC)+T{XKb2Q2NR^zvKjvNT+tERT>XpdhnZim_g`B)|mBX{v$l zRz@)y(~{ZrZbnjnWd|8BEI6Fky`0mt7um`WYWd!DMt$@dUX!oHP>%+JHL{Fvlktpq5^>k~b^k0p#82p@!OeCGeqdS` z3LRjk6fgWIZeWJ+9Co!4Ud%z>R*Bu=gcK1!!gi|3lwt!ttMhc}Zc~rmLgDE>Oui`2 z(QT;eM`@4Ih8RtxsKE(P8h5^yT+2Ze9`d7QpMg5bgceUe>QoywSsf-DT|juO!69sv z@h!S7!*D8niPxnKe`?XJp&q&sfATeCM!ii_`M8+RgQhBYSeq;hiM*?<1*Ef5D_OL~ z+A_e(lq2uBu9(oe7~@}~wI^%K8EOzl6o|>->&-Q{w%kHl zW(qzVsii1sj+8};wa(;xZ)nY6nS;yn8e?u)L5-Qx-lZ$GmY&LH@Pci@zC44&SP&GP zg9?5dQ7Rd`=?0zEX?DgLtX!9BPx-XZEg@O{96F%q})#b~q? zf)T7~?O<&8C#no%YTuL|oEsFuV`gawg%bGA{1MyZ#CZ`{ndGK?rpzX*$%i}VGGk9z zij9m_IF+UvPv9|A9e?8D=C!C#F2Vb9HurI?%6KH*}A5Z**sOf52P5n7z4N)EY8PFg6q)gAVuzCv}aUQp@Wv;ybN8@m};g zloHA&<&`S2K2K_m1MkS(;zQMi`S5F78b_J#n6u%Z+|zo}TGx7%4EGxR-h)Jz)8^m! zdR^xHdl2x8HmN8z zzhWcRN}NBv|1c^rDSg*CHHV_+)K8sCy#J!6K+)kAabYbwEE}~Ps7jULoEQX-v?5*O zvUKy`Fg@DM$z2?F-d`~-9KxA!NvGi>)Pv7+8UxgPY0#)^iZ7^$tMD;XOQLZzyi9vq z?^>%!OF6&BqdHr}cF>j*7vT-|%#OB>;f`x~W9D@>cWy-4wYqDbE1NsLJI2)mW!D7T zW$BfrvU$AGi*`bDxKxFM3HlwBj9TFsABVzVHl-vkZeI0(Z#p@VAgl5+o>n7_?YqV7)-fpLA8ESrkZ>ntkDg35KK9*kHT4Gr-F!Rp?4|wHa zj(k|%PGpysBfg%Yms%7~&ImG-SKyJW2S37iX@R2AF}ge!Jq|saCHS*^^R1-z4573# z8V#V7stFb1qSW;d)Z9eghrTlYQ&d%Zv|RcoJufRF1?c9#AebNFJG342U=AvkkyJ4g zne!2jMqMoW)+-%NBBFVID}&b&GfoKk+K^ZPhSD~PS(Y#v47`7fFX2IB{G zhMvGq{lZr?YMHihk3Gg__}%q1YgB#(t#{B*SuGh+(~6gG$?0qzZF6mBh`k-{6Hvsk zJIXk^I%YXOqn*{+dBs`Zwbr%I)zcN{9PY?u?zqXQC1xGC`e(xaz1#gv{ac!*L*K_YE_>qbjGOJ&PXvbWsC z+s3Bd#&1Nq9zq?u2@N=#x`W$JL&mX-=lPLZ&B$ITN)A$(9XW#tyA7X$vvAql@qMT0 z7L6wtPJo;KKm9P>xLr83Pxlq1S9F?vl3x9(JjJ_s1Wxqhc;BMCXLzwe$fL`JGS^W<~p8DQ5;#kQS`R7Z3nLn2cz2pv!ZOqgio<0;Bbx zIO&Y^zef%Fgo-brGD-2#mFnfKfxcRO?8NJKQFM&IjTS}5I0bgmcaY>i zLgQdjmjp-ofC>5!p6M65s3V96MZs3w0V5EeaBTti;RJ)V7)9$XVsWEtJYyPbE>E;Q zW9=garJ3?yIoh@!b);(c82fbeZfp*-BbTFwqq(D!Ba5R8$|r*z2OSxl!|{9{?zn5O zXKN#Mw#1q;8B2p`J5N24ERagir=cO@+oaT1>MC2j!@YHtHfl*urxRKe*g$`R@gS*_ zp_2LDc+I4k@|sf+AsSf{@I5|jK8^xID$^TywNGKX=L8M6k_lU@&;ZypKgj9pk>M;3 z#L{6q1jph9i-g)ixC=U^R>#LvCi8$k)@zo|wY;2~<2uSDAYs$c1>h{_G`NN-7R zMQ=;Gj4i#hyzjkD(Aa5@4$?_q2Y*5IPnP0(_9QTgtf&^1uL9z$1lXpSr0Lrbuo~Og zwM(gH{(zncD`2#PV?UiL*aYMGg4o-boqod$(o`$LgqXTpF zs@f;t7qy_;jx+RuH{eZ)Ce#;Lak*P)nAY);xIaWvxAv~r>Re_^8D6vcP|iW^K+V3f_>#jJ9Mdo-zT=_X)~) zrH8UWiC0c5snt2Eh39__J@pynE&Vi~)>{w4%8GGtp?E;aux^Dnlyl$nD_mHQ(Clg)K1BsGH_Ef+*%;FuyE`6okP*3`n z_*ZBv^ww}Oj|QuT*=bCjt1*uZkhv!?gBry-(vt~U1>Wj|4uE4r2PqyN@mcV@4xIQd znl_ooSQ?=)ycLJ`dUAcaikuuru^-YgDIVAMne=^kpfU4J$}Im+o+(>w{cP84G4=tD z3h0;V&d<*Mj-2*bd5ATwIntO1ZgHF72Qm-M?^7o!d%V{@ZxSaZ_VcLph;}Ird~^K! zwY&QEpf_~YVB~4uH#RY4pr@yi|75kiL8T;3Y8fIUSF#pn% zuV$j3@Dl!}Cvb+33hc$&fv;%1T8O9ds0@{$kGmUXppz(nMWO#?;t5XlEvFOHf~QlA zNc&oSiaORHwHCdid&)tcW3*CG`RTpoUFjX+9f;oKac??hB6(70RPtN#L~GD*8jo(& zeLWUV;uyNQVT8bPcIq>x?%kO9uyvVe&twXD zi-~f^LIZpw-!o9Qh$|7L;0+8VUPYO0q} z-L!h|C6+-Kbc1KFr@Ys!#``Myw`i$@Gnv3Q7P689Z8N4Yl`wZRKQbB31L>(}{uc*(<*_eym=MYNJk&4HA*xS}y)?2}A z@IGd0@Ri+Cfv7uP$)O(L1TBQ$U2C*KkLwN4j&Dj$l#jV;Kkxu6=)})v(m#(5LNYqH zBVq1sBgS54@^+O|dX7IHJrmS;U$cI!PJv=uvgUwRiP8P#j3 zD~|L#aM105u1q;p1z*8)uFUjwIhp)B)T7IQY+6n)H-e|RgR`l&A<;~B{^-zROWvyndWX)oIk2-HE%VKnboaP@U8JCMo=2G;Hj#$SrH_L9zZFf?Q zesQL92i$uiA|g{nzH&#nT#iZd7)wQCO+)=)SG|b;KK;;K%4v_9cq-B4`I9)_(@EK_ z{`39T-Uc3_Akj;_W4vWb%iOMqEw+>^L3B9 z!PgU2!~D3wttUSzz+5FG2(*(R?ACx%$_7U*8Qj#=!8lybZt8vXbj(vupb_QL-ZJCK zh8pe`-&8cmQm{)kI(zY4+cdQWCwFh+_;FRhPjotc#+LrWD3A`*RBayWLc{5FZGh2s z0L=7tu$>8HIfHopZk#dBq5e4UH9$pCCM_xSVZ`a zL6*YQc7{LN7p=ZjYLWevQ+6mv)P25`S|)v1pjl`uxZH1IVbdWdvHi{S={MHIg}$D- zCw0h9;|Q@9*wnBp(Pva0hNCZ33*Da6RL3ujrA#l3i%{yRZoGo3N;B&9UWN^@1RX(5 z-=nSY-y;7{>3gT1QRg#99HDkrd#L+R-OcO&>|dzmWO6*0`<#!O{yHdxZ+%uH8S!w%m{XZ%*U&kQOjth#8R(|#Q+KxwcJ z@8~czVFq2E7;zlOi4ACVe#Yys4(Ihiw1dZ^Jb8dAXOQPMgeZ|iwG$KiQ>Pq7MQ*t7 zov)t%i@yi^ev4j$daEzpt+6~o2kYe=8CFRulmkp}{)b}DPq7VMf>@?wJIoy|KP}6w zW@#tN`(xyfa%o$ATOZph+h#tFvJJOYwt3{&==V)RpLvkHRz8o5$Uoa|dolWabzKJc zTelRsGtv@ijF8>Aoqy#=mO>`{-h&13H$CsGtbX)1CF{=P9m`zW=JWf;YEuH9phU&w z5i^*!nP!+PSsJnr`{VrWG4^K<<`qi_HNmj!+==we~5S`vn zNC7CtN!7@8D#(ZBuC@{OGme+eH0}-&9V06ytC~DAdB@1M?j_D|wi_17xZO}Mcugzr z|L(h^URG8q@0EAlk7mB*)Z9<>IM}L%U@Z)#KT(Ei)?gWI-j1(mz<5M7p|bat-sUE1 zvt1x&N|UEeg4Yva+=3TP7t0o$bJ9q8S##witM#Axqp5>&1%1QLp`5`CfyR1gEtNk% zS%phINCkV7iTE&IMRaW~T6N8;pW{AH<_;&&nP02xAU;~+>o8eepzQLxy-hvu65l6Q z_8joEAiMdXjALT=&Uey3Nt>WI38W>imnA=qrMISlW}61CDjk(`N%R}y$)5+3Enh`L z+YZaS1$FI3&^>`*7^7rD0U{w#Iuk zAK}$nFMUKH8kE^4cKSNQY0#NRz%Jk7^9i5;C(zeBi01AQVU!qctZHgvZft2x@6|6o zmDkux;L(!}JjigAvr9W1j(FUK-`Kj^MB4!Q3n-E$V4ljzf&I^+!lIj zP=!Ip-=;R^yq4RR)t1JV0Z;N83M{35CR=My&zQrCtnp1gFJvQh>7Va-LrOW44S^E7Mco)t}BxFwq~U)nZbz3>AYe!MpT+o0FS20%zBP z`lcuNm`Ci**3^#+ghF_EeP;Y_uprK7y@Z!>Y1<+6Zrjqq8EbEC&tv~ZG;M23Y5OOi zmD|hBs4glLKK!I7*_zl>IIcT3J14k0x_$1O5i=vNMn)xDn`~9GVv$kq8BW!< z-1^7ZNk|o%rOW8wE<)w@t$&f$TF(<$5xfR&w3-k@UiC*bp~1NaJ-Rt~T%3j55hWa8 zt)cD%iZdVVw7N`~(xY^~(saJxWI1N4X>2EaCC}eV zt`f$Lv_p5dzTO$__+|PNerMQw{#)QL)2oWiD%PSwz81CVv;Hjp5u8qC)NM+4rH+zP zx$gbp%}F2Ns?t_{qb?w_dHnO45cgodv7R2<8~uUq)&Gz`Mxsxdh3YgjJ-}x4AC8iL zRHc`v1P75pe}XA6lbE*>PC-hrNu5I1!S0>H31hXMihh?(i}hDenxkH#pR%iXYj@tE7+bqt++O|e~C3lj-aXnlL)}d)T#vX4k0D7dXbEmVa z>xOHr`;_~?h->(lSC9NJvS!34*I~yt+dpfp>7=lQD0f#cPbJrknO$G~j=r0jkrAwK zv=EId`B$b2chKW4Bff@XGJ{$|V_NkK>|D)2a)NwdHk?Wfq_y3si`pw1iy1;ZPqVx za-N`Sd`HfDL@BPk@{T5!{`B1U{PxuHUhuXj`cEUaO=Tx~@#{*!Q=%+ryyW^d^39{@ z%74&YdIxHbYj{QEMuWv4sKFGqH??{U`9~`9{{yH5rVw>po>sw}*cxVk;AtCTHvZh7 zh;Q3wH81D314O}THK8>b{swpnxS@E3QX5% zewQ)y7X_@ILT|CBu_!Lxi_Cp3k=6-TL8>9eN^7NE(jjRbk#mEzM%pdClk%fDJzTy_ zzAD?w*^1*a-rIJ-=CqfyPXymJ$&ni`lYS`Kw|0$it#{pV<#o4r4{=Ak8@e_+KG|}~ z6D_li8DW(!0?%|Ta01bYEg&7}gEyfjwx_Kf>+%mHtcrN!HShm~cbiPY&7%_n9g@*#vqNe|@vak}LRr zs%yZh*w8%gPcL9Oo=QiwvP6Lxlv(Pbv*sY9>dNW7iQbVPX3#)bC2hESZK*s|;}g?9 z^CHVQc4i^!i)OaHOg`G#$Jv+KPx4;HF54fW9WB|;$ZzqF`biYtV;f*UZm;X8<8-)U zTwd1m<;hQCW$N0K=6{yY)_c-X zP@l!5duUkyHE%V2HwZgO@Y@i1RPZs=1U1gz2AY8+{3>5B<&Ru zwUn%%Ib>%Zy%39tOWYX!p2_Cz5n0LSTi8r4z2NcjZr7T_o$5hc*l`iQLuvSle z|LD5Tp{5(5S-{zO!RcLPsx~Y5l6*P=22>9w0jWY?rRf4soW2A zCSqCmxvjyjcLEzelT2$qY@Y_?L5q;K7`}tqVi>YE3>hOjp#$Cv*oZ?1EY7}`Oa0xea$^MVjOQXB8Em>aF=w(+7#)k zrL6IMsA13t(zuj<5RJ8_q4#L)F}PH&cSS!n`xM5GIJ8U$ekv33mi_i~^#T3^{g$+yBOdjW|4c;?;f z**#^bN+!dlFv4ZH1M)pfa5~-m@`1moKwree=dnItuY@<$ej;j0ZN9$&USl1}(dtl7 z=1>E4>?^9bLD3}m`VwC+YIQ;6c0>EC7`4n8un}*Wuh*yJdLQoZF)(_w(OkJFRyF>_ zVdLCxEr}^xa)!DD(c?vI>*d%fo-L9!jjjtT(IKAu@6*!6k2nBe)D4Ci&)cm z8)ejQ)UtJq&BXtN)UZRkqr-R?ulcq3CiDfJ+8uSV*8wLu7b|+Gg_yo|F*O9Eeb)S& z?*0PiT}8}KP3cX3vfYDd4Md?X&>x(16`>axs;zXa8i;jKo2@JCXR4nbEv52-;rbl% zRWp6Vg1&xgHr1j=($UYu*;Rsid^bndW~(7FQCayH6j9Cf)xy5vqbL8q5rB3_5)4QH3rCx-yF1L`wJy8&P=k1RH>) zRXJ;h5~I$tSDz7s9)oYc=FjiH#{|3}z6j4$pPJrhCi`B3H)|Pu3PiNTUurUw>xOhN z29njS(L;EQG$wZav{4+<;9F2>AsR`!?&;V8St8i@zkCFwUAQF8j` zC3ud0DTiE|EH;;16}@&vu3_6@t8f2m@9((fCNGpUWMsw@j^!qPPs+r-eN;4-J_~U`zAiDXtEz7?3Y zKjXK2;=MLg%?$jG)^KZOG$WLNBT!2pqx}X~lie@*Tl8Q77|2Bf1^99w*EaV0)==#*9=d%bz^i6-9f0(}n z9sn*7Wuzr|P1Gg(-;J-&ee$3a;Jj{tGU!F5-{)V7E0hTj!Fc-X+x0qhAiC1Q-wu!e zC)y4_gkho#R>W(ZV{)0tf{LzgiKCNV!aCJ@!J0*C0xoZpB+3otHsERRQrlIb_cP6Q z3E$OB_GX|)uCkkRJ03U=;n`5x8R;tIdf@EfZ0oG%sBX(9$(CQn*5W>N0cJDBstQsq zKhu}uq9*vnipC8|T!TEq8k9K)qD?jgjq>7wdHQMG$^L0gscI&mU*CexV?MH?6evqf z6Y7c|#YARt6F|-;H@4+xtrcpc()kShPeE8`8Q|tEH+(Z>gtrh42C^4in0z3UQP2Rb zk~z?UGxT=Y57F1wcUf(wW}%DGSNRW?LPaGD9*|dw#F@QU=)0atT$dP|SUS;~SUj<0 zVkV{lD-)mKC->1a!>fCHfR=6xitjThfP+5OHxf%ZMxw|!Fo8uub~X0b^7r)* z0PEGAkJFOotbKXbBmMpO`cA5uf~fuf);e;Qx8MY*1&X2+F+U~BDWAa(UKCQJ>wHwq zgkQ^3V{W_>Kbwl6wtvr@(oz%OyZvBr1f1@A@VwVsKhZ53%E?<4p9Pz(y=|8*Xe(>4 z!}aO-0ekR zxfX*0-^aQQ;lTDCCQdoHdlO)0PbRL{0QnvfI!@QXMaOLkRm@v*Os^J8{nyF=0QZ~B zU<-PyebmCLk2&Q=Wr)&5$;GtzllP1Fsdt9AueTC{Jb?GD;~$hYOD9PSM}yq z`q3A0s8!UZct=;HYyR0+l6`dwAHDqSspFc&6ucb~XfBcSJT=ElcC!ITus58n56NL~ z^Y)d$U19I7C8o~-o3})t$HcA!F}IXn9EQ_>pdl*i)!5&y^qy3tOF5A*P%qtP%ASz) zQ}0uyz2hg_16j$xhEQuiqvoqbwBJMKmV&v>YGx{S&?|#L=)WcBtU`U!kvP~F{q=?L zzV4wUQvm$vC04?8kYX)O51Axyq(4`i?#?Gm9&0P+WY?@8t%~&zALFh4t%a;#EF17x zIB4-(a)5arXgy_pW|gFF(hRV8Q>ETgE$NGOq_vzi2v1=y43wOfS9IOmnsed2F%+$R z)wm147@4ShUOXjkMIB-i2v)oJjE+MLY_*fbgHIrSBAHfnLNzcYGsGLby6GTmd($z^ z4bJ=={e`A_q<){NQjAuE?s{_Vng1HmZmPd6Y?bp&!=il8!T;1&Q}c{pD8G37ghOjG zs-4`trvO*7U0F;&HHGrad&hf>GxsuoUZx-amudSq?`>AXRqu7)FEGgxhz1>$*0ueJ9fKAH-)h)dqO;Rf9(~3V*=cWMEOgu5^-5)7`V?Izro&0#7?l1?*VJ3liO=WG& zDgDx#Rq6UBI`gj+akdMaqI{0&+aKNSsj{^o}W0DS!EjPq6xkWzV|#m$)DMu1};%!_$i~Qb-yyjtieqB zh4zcHx~@K29}k-BJiVd-CsYjHIcHHU4r^AQ2BjDbrsk|p4vRP&xpo<QO^z3U(;0FpioaeE>H;1?h1j$PPZa9GseI#L^pJ!*b)f zHXIL$gZNlnF+OKf^&5AkA~0XJn7$Lq7lNa&g^Tzdi;<^27F3GgT87Tb>tR2@hP~ZZ-PPLp%RX^jf8X9&z9bHl{GL?j*`7NGhGJx(1yL84}QZk zJUMMJ93;y^b2{^7(?C-LQwr02ye`J`#B-p{e_4!WHggP>udTR993d8!hPkZ5`ntlj za57nIS|a};dH~MA5qeu@{UH9h({Zhtp{)e1yaWtU2Y8tu@lorBPvJeH>^QKtgK-FSoA| zKj|<}w-D#*47@xyf`4Dj)15$86bt(3nLj5i(Kzi2ez-+gmE(!M;h6i7j^+Rj;A^tWbh3Te_qjPOlB;ApGh7VK^vc^psjHD|TOR=}3smX)^@wsh>@eo1}Fjd=5( zwe15r_{R2wS~>yr!*cSsRQT(?mbWs2jl)kXk=emO`h)YVC9Pj^)DbN6apg%gEi;YB zO{I|Oxp5v*+%K*atB6*#Yl;fbVFP_-j^7g$gN+>Z4hX->ApCBC4a^{!1|Mc(F zr6Y;iP1xn-@URw%+bej+x%>wIHQ!uzc|&m4$-$&Zd~~woBS7l@RnIezp2>u@ky=X4 z%rr9%aXpJ#j;Py79iQ~pS#8Z8Zlp%D4kk0rd!m}@9t|OWT=jW$@J66F8KSXU6 z)=0hzE5HJuz9&74OXy~~!E{t6&+Lk7!(PsY=c$-ZQD+>- zIb$C+;}QP$hDxId9#g~ch`)?Oc!V)KJ^7Znjf^l(2GhIKcpD5vPWIR;>a5f}|Ftm4 zl37~eX?34?-jpouu{D*{ff?B~DG@}WOAbn}q&w0ZDUDo8ZYp<`d*hNdOFke!;_VH1 z!!Pn4_I)1tFK&GC_+_<|O3<@-W8DK*CYANPr8gdnqs=MsQLAmTn0_+JA8w2?3dWnn zpkDZ+oaFVmP!4pUPWV5%Q`cb3hjA8bqVjQm>V5ckAPY5t9gju-FzFtR0o*Dmxc?`kj_O2T*MOSMhXgD8O{`w$arqUhRg%|n#U}7*W zN(DI1qRMgRI$!acb9%;TnW>%F` z{1A?cQP$zsE%@t}k_Ph(-_r3a!do%k3(M*8NNvVGUJAzI8c+N=K8tpCw`5Z|%ddb% zEF-(+m(m4zPBX~dib${dS)G}aZ?)7RTN`F}nNM?ump3_0UtkH0!2?~zhi0f)5Qh0g zaM1_Q#_@r`YC!)eIocI}f`{=rtH=!gO<*BYnMfQMXOY;WyNwLVd~JJnq|0t#ky&(*f4PGI$o#SP@lyxtI`rA(CHFZ>ZOCzx}AX;hq(y zqu36{Z(kxsZ@#Y^nZas$Q2%^c@Ub1r9N-)dxlS!V{r@V&nCiT@qxxDwZDAzCXr@om zH_)yA!;{VnPOdh6k^Y?A8v<8w4af+Cy;pDqXVg`mH5f~($0~GDT7qty4m#na|s4V!y5qJ3^Wq)$@Kw4Lp^p3qCi2ODaaksySI; z3}<*Np8q)CWZx#=W$G^n6>=xm)DAp7Ur>jMpfw5+aT;hnnNKXCCcDoRsv;DjiSVyc=}1kNy} zdjSW;CZqvrQI4~HAPQ)!(2%~0M#f*%>>|l{Q{fd9!avCm)&29h@Y2dE6b_m|3I(yA{$sPA48?!DH*;l-D* z_l7ywOsPBPMm{N}^woL}Tx4~tV%daZz!`XaH_)#rO~3OGS>`I@e_8xA?@_IlXD$~9 z%WxY^m$&qk$J1FZ&s4$;^XD4#^oC3aE>NpQG7;`bC*v1RgD>zF-U<667B9kvC~z3* zYs?`o3;r|YbN%qN?9H4f{IMsMTV3{e2GDVL!5B|gyQ|Ha^ED&qU#adT61-5YKAE#3 zC%%|jSt}ViHS(ZLFp$-A#;4%qT$8mh2R7qo6abFX`MO01EbMd~Nla)+hItx20u$@9 z92z3SSP?6k_nxBP{gyswb|xzQ154>GyaPW_5YM|_A#zjZr2WYzwb)H+V>|sQ_%t z>0|ai@c)VDl0HwS?ni+A1$P;sNEE*rueQ^lJg z1Bx@dSSFM}uX6@>;UYVw7TIr*Zp>3w!B{HTl)*Dhhf;vc?+x4ErXM2)Ea4PbNDVa% z9fBybfs5eE8~W3766^$B*p1n4IZ(+}*ynB8-)@eUj$#OhdM?bMwLBZO;CT5 z&!fohUxXRu7~D_KaOU6ezoh?p9n@QpNq=P~UE5fZ zW_m5n$oYoDshZS>r<@2 zUd}B?P|@Eb8|c7Vh?IQR2iD(od@d3dPFg!z^I7l0&~sQOgS^{9M0#%APOmkV714vp z(2*+tCTCj%@eLjNwkSFL<;OkPBuP$el>{E4&iJ)YnZ&h^>;J~$}gvG?DJDQiX!l7q|eNY#USr`rST(^NB>qc&KJCF)I(um+@>ib}@IH;o0Ow}KR zf_RIS$GvbeXxodN4m$au1;(z1y7()i!dI9=5^^M^4#&X#?s%3jf_ z?}k@ug1I1-{|O||3R34U>9rH0arnVU!@F%!;F z6!~Ln`sjOz4EwY+^jp`n9y3wpcVV552uy@EJr|V9vB34fe%y>cGbQxHYAZypu$(H_ zN(LNFzj7Klv$ZIr%_24o0z+RE?J*J0f#bZMrM#+mUgsO|?qO}PaySi@M02fTs1VwOsKU<5eFf5FFGc?Fb1 z8`0VFQW2#?W3f9pBm>`95>`VK`tp|04ph-HP$fjd$?44Q7%Q|BKhUv!%5`?Yb9Ec6 z)o6TIou(Tl4-IKnP{jY`)L*e#E! z=kqgTjr8rI8+%!40|H_W?7b4+2Htw0Aj*3Ccn^4!@!aR4RWz3_&~n(+HHaz}&VqT` zJy>8?JsGuNe!YU8g`Hm!KinaDEIL#F^qNc&hvC|6V8U9LY3q2_Xmg^N4>zvu)DN%e z@(*NMdWUOTMx{G|NK#zOOZHh0pXO;Go}Lg-8uLB3K)zIBMV+Ox%or?!U(^WR#;`W7 z!Qsx&8M1@QVmZB|anzR0m{Qhbl2x22@gFBm6s{b( zgskw7?WiB$0eN#AE!06wrz;YHHSjp!$Y8&sMO~llRYPgzx3G>0-&SgkjiB>)o6A|A zTAEsKF})op`OrNYDDMR8l#Cjpm2IqTJ6`P9Z6l%#%G=p|ZFOReNaKjEggMYhawRDp6TmF!&*%{ER!W1e7^S{b!|43w^acfi$PFhch1DipL_W+&vjVaDXZ8|4a6nUt^x$}Tt<4v@v(v$W4)v7YL_^gSn zE;|nN*>Q!xN1vp$ewPVhCFmp58ww2~s27yiQEZ61-rK^PB;RTedOaoh%IPR39A$;}L{lz37|vtd*P8TJx`0Xj!PlQBW!z=ap6?B#_Bx1Y zKRzrIm?^9yyY43#Q2>{O{%FoVgWES0wBUMr%Ozl53}mvK8?Htd-+gr;y`zE5Y(}Gk z6z1IwqUS$^e(^i7W;dxff6+BOhDy&_gd#K8cKdm|)&436hRmE2u z?EP&bP*Cf@bL>L*zabN<7dT#$?aR9_9aH zzUPD1R6gfFvVS07w(&_!a87pi^I3Z`&&kY_gEJ$2HQaFnbCFbsqsf(RRss_12Pna< z{BLdX+*NR=*N7C%QMIirZczgFnkkZ*nmUkoZ7$yUPE@V(`P4_*-A7nfD`j!cki-9S zYEK-phWh9gQrD?;{3OeLl6I+0VtooL+!|*8?feH=s2`E`8OgUq+IK)j41}V)An%O4 z@;IyKFn*8!iQNS?OozfH0(&oxWtg=AS(c4mkCE9BZX&Tr4>cy@lsxcyMA%PZ)> z`;GjCY^ArvMD7&}xf^|qOpHl6g0jQ31{wZ252_zfw)= zIx6X|f)*R(tT4MHFWLPtlzTMWaVTJ%@tUyVQ&VQB>~ROaaai?$@5$rl zW;1;~zrM`XXW_1o!D7@*oR%v&2IjS5-Y(JIzI;&!_@&meO+S!JIYYMGZa;v$qS(Lh(Ua=UD$o+xGF^C%hg1HC}>s`V9J*m^q)nA}=#Xu6)z1HdznLluzTex*jK)oqa;? z*n`j}cS6k%E>yG7e)RH{2t|ub)xk3AXS|VqhDCgH+Am@&>!R^}BTae}Jt|b+t%+u@ zLAg83qB{omp#M!yZ@k<;MQ@%t79Dv@lPI0=jEWGX+xS1eF8IxvYv?;iX!KW!eeF8afWH4MopYZorAjoAvZC2(bdWW& zd7o7yy%zp{90y+st9?kN|K_R`ZFWRGVq<4bP2Y773MQ+N(A?U*+-=xWGX5LYB+BYK%)fuA0vR zUYlGq(_PAsUx&x`DSo-iA%{C{83awJ`EyuhkNLP|MKd8RQCXl3o;$|qYEAo?oBs+6 z_=fzWY8d}4p4fohlFfpB8_&aYle@4H&OD(^OWRDWb?g5q5MQr0L>x29jy!Yyun@@(&Pph-qvuB_vqW(S+Tug z$_7Nz;nnj4IY~R7B}RKY z9iuwWcN25=EP%H8AuSW0Ee7jwQ>+oh;qch#*txNjX-#7#;ZonkS?=WFYn|3c<+g!n zr9CZptM<6r%n89F8^(j!A1-96xZ+pn~;HQe{bwNjI1lRZ?vevhYWFI%CV?4O?UOb63Qr{;etvh^FB zdJz_9?Se|OOzRfZvaRk@JNLe(paWTYK5t7pEacyCiOW69V$?ehZyAn$ZqC06`nL@) z%#~>Da%i)D{QEERkj}w9j7-|}%NObc z32`}`%d3T^%jIb&->yla@0?LFdtKHgSXO4+iV!u?$_$hs$7}!)8rltJxW#PU* zM-R1B^9eKbbz*Nl?f=uBC^PLJX#OXlIr>BCR6!GKY&*run>3{#>bO#cu3>OHdu89P z<<+g7Rt+!tif?Zu&)6xvatcma1|Ry%yeFaVdn4Y`?K}gIi>qFzuMYA*P3KR)+UU(z zovt@MARR}UB-f&8B3mB+KY7#XpwEkQ_rP0*;~a12EteOyNk((KM32M-x?@rPjqdPs zL7tq8b6k!?JO^Dj+4%lcZS!NQo2R(f4n4ab%4G@4eBbwreE&L0^BhZbFy3-2?X?p< zy|XK{&99SRLS965y6rj8Tt(opYkA79H2I#YVvb-BO=qthVf!~hZ+)OBv|pVR2}L+zerzPj3~8sC72bipwm6k8u|yuKkC87i56 zhHh4(n9|1EK(+REsJ&24{jBHcr8{E9MNJ+~|3C%O>KQ{aK2!<2R{VY##{KcK5Uw9( zuF5=^Svl(&5@ZpaQ_1Wa+1*7{XNjq<$X+IP_7G_^N>u%ytkmpzSrs?}deNyL)++pUuDybhsFON5$)CFc1DNkGN z%dYEnW-kOvk|YlCsh0|FRS&Nk&+9>Y-i-W#c*$Ckt=^*W&BQs*7lqx%dKi=VU!ps_ zcs(=|V&t9K7;h#Lh9e*z5Efr zs<~L#h>UmC8OevN>Kq>m1N(hEH{K0`Zhq!Q6@gmIi2sq_G&idO-D9xoKVOKo&xWyB zEj#deRoN!eQmcrmy+)_~Qtg4Fna_%*uH#Lqh?fk2rP=DvRUo9l7xlf{)3&CKmP^Z~ zVO;KspHI6Fn)5qo_5{a2k8OFo_ zKD5--U*Ri|0wt_l-e?vlVqh~CqZOr8n`bZtk zSrB)N*g-q_Ukl6ktxl$OQaPr(-#uXl_79+QHlVU2{BV_^j=IrbU*s!41_|ECoJCKo zyRcX;bGEs2uHmzus9k;#hdF89v=E&&yEIGjPE!2^{CpK0ZBp&RGUD?sRQ2k{4(MdO z_DJiOHZ1Lh2($5H+VASJR@Nuo@$(O2i^b+Iraz2JUz>h@#$7O%yEBT#Z;X$KuaGHG z0c!3JHqJEMWdn59?#vQu802RD%*XnD=F`wSg;mkJG9HVsPyu-bq;AXf9dIC*K}Ekr z&nO`Ex_ZV-Q{Ojl;3WRw2W8+5fIJw&?pkC{!8K;ytS`#kl_Z)>0xh&mrCWVTeg+@N zMEYqVdF&NZe}YK6gC)0A4*Y}SE~jC2rmIU_*BH(y*sC_eV!FZu#^uc_QXGYhSSK3# ztUSI!$ttYKc-tGZSIQ(4{N%a3YI9V= zd_$JUP!`Jop0Hl5qgx?)+Mvj;{9*O@(ds1{;a`{d-z?GCH&=@%cjP5`h_B>vcY4V) zFA&Q+o+v4+riVH@U!l`t=)J4x{&e1@Y*hf-X@@D$9!Kc110fmSrK23Au~gz6y~Q$| zzx-`p*7@?1KZoUdBG6SIZ-c`qcx@BQ}aePetamcP7>ME9s=c}K2H*Qp(ba@1h zyL9^4SZ_$7aKYffnWN=;d*{6UxJIa6(9wKDJK$Qk@)cZO zP`MzFMtC!gVGnEcY1Tr8WIjK`S9H*ho;9()H{V4-D&YAown#k|8JKYhQBUAr)`oIhj&{P1JA*0nsR)y#8n0^)wVIX!l&$@3c& zz;SiUQWK@oO$UCpccR~yWBE+Le@f$FBV=4>(~9p$7pukktKp!7*uihIh!3kb){Zyv z9WA*{RAm|cwkr+xJzkMf{3I>twTI5s7qfbcKdmfDJ>L9-vrx<+QI3asth+!VwZC0%h!d@ad=1$-Bu%UnejLe=lIxITxve`{rbPOOyb*d=WkNah6;QqZKuqeL9uz_ zwO!b`Yt*f4FAr-T4WuO`#~bl)A$$+0YIZDsFuoi5Zx){M916HohE6q_MUi-B{7}ZO zjD;B!V8S2F=nVsKS;hq!MKV&l(J zsA?*nSTiL<&Fzu6$5|p`uhI37kkz-d3^qZ7cEIn?;V1i9YmcXeK7mvBGEUp+qc+BB zXVugDSccL)f)DdU6&pSg=^d|!{-?+K=+7{!tE%B#I+2sehvwPH!S8 zrpbO=00X!z?|a{EG1iacDrNkl4T|W)3pQLf^9T}R3<>cL-FGI+oW_efpOjmcSee*> zBOI_KM5t;*gb(HsnUVYkJ)EPWPj|Y@7rg9M;K9f7gjG;~bUaQ_9WVVz`{kxoOs#_! z+FBaWGK#7LyP52m%vah64`|1q8fG!Ah3~ep^pA4Ex1ftVMSA~Kxc>} zWMpR?7q8iyzC|tfgK`M=({i)bcQ2Z8p(Tb!GSpMr3@Pvd>bskrR4#psJ3kAFT!ww} z30>rtw5!Z!zlm<~n%O`aku{}ZTK1`@n@5`pmRV`h=hoi01rBo!E53!;OC$4x)baim z;61jfuJ%sk`8XpAc@;Fk8i=Y|1qaCC8=!#xO3vhWYMCrzK9jMiq8p8~j2EwNSpP4JMO%?qBDEX=k!Uxk#TvX(cZm?_5e5Wi`T$Crm9$|40~ zkRx&P83oE|j6ZZh1&=v%PGY~Fu9|EpH{n@v-Or8uoyn8Q96D!RzK+{11NhIz;%hVW zzs~=Kg`A_daVyqV$lP2F{c#ab&UoI6)UKiJ&0|RtGCUecS+j$1QkjYv;b}bagx3S%^l02Ei)M(#~3NGW} zc+p5-1m*L)`j!XioX4Ws7rt@iEA3zZzDe~0TMCuvYKevz)N#6FX#Chne4k~NI zyLP>7yxzFNYwVg(n`Sp$Ne+4{4If&|Qi-No#d2Zt5^D|FyCE;Lqdd)Cw1$8joF;GJ zk7NlbhwJl4^OyW%29&|%#R2mc+yb|=#4McwDOz0(-UjrqI_AeMFHW6d{?day;w$M2 zi+G`DoBwLE2<=iwj*7k16z_b6oXMfxjo^1mrZ#1bEP@5BL1%bJjzC;C<*n(Dr;o?w zmmBB1X%D~K`^tEJS+3>nd{>oWxH46C-l0zL0vPidvb5&O9Qzt|ZAM=Ov7+gX(Acdw zz=ZV0e69be^_NU9AqSvvMry{M^z|wyzAVS9ZF;5jLh0*W;~6$k6CSR^ykhUm2f2>l zR)P$ z{=m-sfNk54Z>kweN|*inBOWsv72Rv3Uuy1^0@8k!8BiAC`}6SqA89DR;w*f7KNc>WZs9808ea zDLylok7JEzPVw~ZAl#maxW-ZO*=FSALn_$Jgv(ei9>0lx`MY%uPx^MA(p4hz8_Za` z(hL=AY_~!191*FgBs$wewBk(>mo<2BSu?KR%@X*W9ealV{0cs>QRzeUW2jVp+1c{*-yPhBtd|+APSiDR|Gkv>$mWck!$iF}FZdR!3hp zOvp}H7~7~?LOi`ldad-v(5Ede4a_Z3K0S@zxykoqP)0YusT515?Lnz;ruAh}myIan zOSb7?arusx%Se%?DiqcuAF7cORcRl=hNU@pm$GK>n5H5=%lpOT+_@r~+c z)csp)g;{O;iDq9;|13qK9EOXT2W9sHd#)>AQZ4@MQtHl2Lbgp4H$IlXFaKBQ&4qAC zV^}i7#X|>Ldg1VQ!frGdS*y&8af(17$JEztFNYxx~88(fzdAoje{}liTg@ z6A$sOeSDi5Bs#u_q!rlvexf@cYZ**#@W}EQAis!a=VP}7oed&Q^9N# zta%K5w19San%%J&O7I_+(|Tgt&FC1{(cF7OOO54eUkizv3A5b}*M2H>w3$1p@!Y&%em-{&FxO?gq zkp|HoeOyhCXogb+{i2g7N+$)&uu;am^!Xf*R`&7&Ffr0T>BoC(jVg8eFJq*h7bJ# zoo|IgKdsheK|vW6DKCc3xK6FX0sMWh$P=HBpX|ar3e-G52mY%ZN~z9vYlCmJVWl>; z-;xE}&{~f~YVO$O^4PlYlyx)4Zi?1!(VVh_R1SWUwG(DPpHGkb1CBX2(rzjn!EN={ z02)BR;e3mZcA}%>Y}>QbvedyTY(1A}Gl#DtOVl)c&LhQ+!;Bo#=i97%`0aQ5zt6HY znm-{=Z0C$~GFT>gG~#{E*=iozHMrXfv&}B&;hMo`H-*0Z9$fSrG^m$p8vz+P(*NOn zVS`~kLj1l51oc(8%_XShZ2HGGy7*7f3v;0tLhYVgd6Qdefs0tR#Zyk=u7A+u)}xyB zIPV78USZuMhV{3$4AIT<&Z|zAH?&;M4!qg68^8RuWczj6v$<=XZ#?G1!ft{-_>MIH zkk&k3r5JvONLa6c;x>9}Sk%lQnU)>rm-X*7d9W2%ob!_saZF zs3Doh|BwN#l4ExH61Ig&%wooTF;A8WaeImfXA>;U%7_LQM7qR}<^|p59gbK|xkgHa z#xH3`^J*xel_$TR?7fSJu)iqz5D4~1?T@6%_T@>w2bT9%?|7YdYAp5;cuq5Wfo8gR z^S=BB&+&b{gGQ$E7JS9hUCLJ6!va3V*P6lGnw5GkOk#={S5hwYDc123F`Zyd{t3an zN4`Wrtj=L2e~QLtiNa4Zk|$fgbj3xy5FferyY?qJW47~Fkb8Um@^DIl>!y{Oy^HqlamLGgAKJlw__lVM`pDBW~)rRwF zi?Z&9@#$^tr`Fm4^->-sN#Er6oPpA2qqPN|WhtF?4lXhq>Sc!YV@QxsAi+Mij#Dl5 zEx#EFgBt3sjH1Il=PFN$ScVzEUxcU#TqtNY--%W;e|Y!( z){_vId9sCzxO;i`zla4?6FR#Uo?G92>bg@C&vF?a-O_lw#(H&1YtL8H65c42wQ$T8 z_qpCdboHAty%_a;8NX=HVbzRqvH@%z+ZIUl#~Mb^QH7oMeR{H z@~yYUWvo$}rzCi;1r%ub@`q^qQ9^O$SIwPSJHK+~~kFUyg0^zPa2m(*Jc6mwXM z>_J6qZC9YBUyZq)Eb^Ts=)Q=;@=)9!J-gX7~?aoQ?=m?9A+|Y;w`@# z<+`Dr-;VC{)_?a4Jv>!gzh^|w^&~&&rw!g?6T02y$^VL|F*RzxI2wy<-O}2kthOlb zf0jOv>w}~*6<5onAo|T=BPficgAs>KGg6D;3;$}CXZvk2S1IB;#kGB)*9Pb&=$6fm ztA<8W6LekQu`sR!o!6s5)MCGsccj99BQo0+a*WcFu2xYmR(I|t(Fm-|UlO#`>Xr~| z3q2g{n+s4(v1mkxQ5|Tbh$l#KtWdOxsO*wQL_&s>KTq?8zsR#UnmiiC?w&wKjU}QwC7V9|AA$mp{>D`eocDq_C zH#l~iqkZk)XWL&!&rsJ6SAW_vD!S4;G?4dQb&CIQd7d$zVyyjFJ=Jqj7Ul>~GK|E% zOPsQc{%EQfYH7XmBYu>BrjV8?thG}0Y$4i9O2q97lQ@C@pX0nT##jmMf0k{KfhV=# zamxwxA2{znC@TejEb6x@BuJ3PVK3b^vm=Vi_RAoZ%ez)tOOVnzz6t!ee8g$OH`RPo z5>2Go4^rkRK9JOdr}f_d|2t0N?{UwAIP6j5^Em1}?ekI3a>}REo-6!M*yfVDaoix+ z8wNT!V?BYoQ&cYqclcLx2>diipm4{8>z?vH$8p6Yu6fuy?jlPz8b_OH*2{4D&y2~L z#@hSF<5;73JdXQ@b&`=h)u%~#-Yoy;7_+k@nfE!KIo~*5g!2a3u@LWF42TR~^^p4>za*fN=XHuP)D(5a$^fA0| zp!t8>A&mRKoDs49frGk+ZcyBSIJ8?ervFowpn&Wd^Bh*|MpOxX9&M29MMmj=gcrd3wiEL z+d?QMNZD-forZEUtp9TA^E_v9bQri!Df{8`d47}Q$+J9NHjWjK#{OCQu()5Aa;#KD zTP2O}3M5+Dh)XrdCC%}(D`{%2@T(xvs~KIDT;(F`#rRkw$LjmOzB7Z)+00qZ91GTXu*zG} zaau;7y4rT3k9Nh2I=MqvpE}?Jt(|e1rIkCkHhu$d4IZH8?iBdx#qJsI7IKkz zTC#+Dl`~Q+p^oC#V%oZ-9?C=^CA9x}dgWhjIT6ue;1@vy4s>vk)DJpr(2s-evETpS zeHS#hKT-2hBRP!TBpN#&$?zk78|>*Yy2I!^?g|I|ViQUWxl6$}8Z^;Wk;gRXCjq;@ zlJxxE2%K-6EwTP!)UGrNR~vsnSk@bRo9Mqk`u{zRh)eCHB8|^h;|P zwckXeb&2iIk@m9A|5fh&Giz?O-~8yVd+@^Fyi=h0ph5iMQ_vj_dCNaM@xJIi!@C}| z2L1OpZ?eJOMiM&Mgq!TG)-Jy|y3G|gJ7bG0g{$my#-EPvbJhX>cUZQwM1FU#ExzCD z9zh@6?)%@PD{jKKesSjauCv73FY-no*iNHcOs6w^M4On3M@@{@X>^Kdbc;zoPl)y= z`t}WHji*seaePv=H=YJE5vO}E;(V{sLEfg<1YK|x9{8^%GTOPLo%1#xIo8#tSf;w> zL|6ONx)A^TUT=J@T^H$*pY+qemhd`HxuRGn`PCXMl(4Le`faWE4!)J`zT4}Z zFneJ5ywzUN+=5p8`+xhJoVC-pziEN3j_!2+?x>A6`|T$E63##D*nVyMCyx1t-U@#9 z?b>*k{@S9wLkn+0nOpSRUU%B-+i=%Iu6NwofnJZ<9z&x+FAP%SUm7mJZ9^VSkT^ju zX5vhNSLNVO!QKg$NYI)}S&JG|K_kn;&w`{4T58~U6{2r~HBbWY3TKtFf1%$tunp1V zfI_O|3K#fX#;1}vLa;AtTY_F(-CDKW-(_xpbr-Kuw_Vf~LzG}QXA?PKB@5A!t!y=j=7qM@{-ptTJ3+yBv)?xpqgvG<5` z9-}ioY!yzcYs z&V1eP##%?w(8BTO{o+;2OOCv0{~h~N=~QFsSyQd!Abuv&w5HO$g8yQc?TpAzHQn}O z>r^qV>9Q3kLD|f5?k7IYfCicD+fR5qK6CsF$LB=9njQTrTw$&=0?KTrU(IsvhtYMX zy57I{de8sKo@9b&dBrly(>`H&L`yuRU57+%8}K|g>AxFEksERIR{FD1q={G8+tn_hzrBfIEt zG=IS7!^X#M%XTB`7ymaoZ>td#G=krZ>|b!qz;l-w_sb$ZXc>;T#MoYnuYPYEj(_g| z*Ld$joOdp65V%8#kuIl0g>ROzVVC(P91Hw?rQ?Ac|K!|tzF*>ZOB@THwXa=m-hXG$ z^=YoF&bKU}o6U`)1M?!!)|a@`e7tEI9{H{#WAUu9JXvG$wFxZg(UEuSY1q;iSk}+* zfDN-9V*gQlck$Tv5zp?$Z`Ygmt~<|54>`CG@Zt^Ro$t&4d!Kml9oE}bMYu)Hh#R3t zIy6pJ@%L*~Z@G_ClPZqwK`+xv~GX_>t?Lxs*?@rnQclv75`# z3g=yBY2}ygt!*7?;d47{3-@W`Xgj%nt?hMitfgOtt2g#t3r}}7@8AuV;B~yid7VAg z<vru*<;yRu zCkqWq)i2k+Ti?`i1VIpaj6A}N0iqxYafG(5`nKxJZ!Wqm+@fyK`{_OQ%6fa;$?hdL znU{greZ96`LocV7!%OOo_woo6g+{_D@0(Xn*e@&;@(N>whr%zRsSs0m==piS!|Uv2 z@tS(!UK_WPo6x=NWOg3d8|HC>$z3RI%%#j3z|obpGG6| zgqhfiZPhYw7zNA`<}P!MxyaPb@|JDJGUphZ%#iib%4pRyRV&>7W7V{ln?WnW&g!gj zzS^es-l}cOPI)H-&%4eBce`sl;ZNaJEL{Rd}F4tURkN_IQCd8p;gU#ZN<0G zTRp6})+{T#oyT5p)#6nJd$fJmDqz*ND%un5xb||Zo%O|vVb`{s*!}ICEd9$WZ>=_; zn!l_^DXjEA@o>j*u5>qSw|#p~9h7p(CLzdNuu5Xi#WO z=wN74C}pT&C_>MyKMHjVO$k*9KKA?Pk*4l z(X$!D495`6gGP1Zi}A}$Wkr~WjP~YCtCw}%oMIj{!>y{8VCA-&S$VD2)=Mjc{m^=B z<}vr0*DT5YXf`%8nxo9_W^XgEnaHef9x!j4N6atAbbX$_%-CmkHdh%3jh%*UEYuh4 zm-Nv_A#;rBa$Lhht3$Q*ZN@jFhp|m>q-WIQ=;8Vxy@%dd&#hPB*uwOLp}o<|Lf`aH zdWBGt=)KWvLSI5rEFj5|g~bFSIT z447w(aYk95-;AneJ9Cb?z#L#UGGm(;jT~k(^Pbtj`f8Ro&l;YgnaRwW<~dWbjJLHY?vU*9pWI`_SqflQs z^c0Io;bKeSg0N4lCZ&=-h{ss^j3A30#e?D?v9~x@6vg;LX)h?e7fy;3#212}$5bIC z^b!|}YsGIuPxfQIa7Fkom_ib9Dtp$Dk!QKEjJ<3w%w~_02&UIa$SP(KdkZ(bD_&i` zdm!8pUa{PM;i7O#ki-PyJ~64ZP^uzr6laP9#eno(nk$!-`$`YR1bqHliY@PuYD=@F z7;-N8vGhS|BoCHL$%*9Vas}l-dA}4>Iw%d7E6Ts6L~^7YQ@$%*mMm$ER8lH0DRNgi zh8#oMAQqB-NPDI1QdX%a-$nA>VzI2)P24LMlafj6#UkQO@twF*9LhcvlO{_uq;2AP zM$?|+erccdOLT<7VlL^DG*hZ1#t=V=J0w4A9xdgP8uG0yNzw@EtaMU(APtsu@tXKU zyeV~+2g)<~#Skeud%RR?DXo**$QhJKrHmXY4V6a74V7}r0l9))MJcToRof{e`8 zRnShWDb*FqY$czvT#2WyR{Lr5v@zNOHHP{|DW#g~Y|YXxYJId!T9o=hJ*S>hAFDUi z-r6W_j5-C)V}I=)@P|zwRdWLb%JtEd8SU$ zDru*bGRk4)zWPXwRAqICI#(T}?3CLn1J%LmZDo?uN9n3GQ0gj~l|%9tIfpV)c`rAV ztI7qHd&(zesytOHCY_hsD`%7@N=f;SR8?**PvQGa@_f0eGDBG-Z;(z&59P#aZgrpX zPEIM;lB4A}%35W<{6XpdNE^5=*GLDX8&WN{mtHO` zJrhNK*+bqZ_mRI!0r`N`OS&j^k-N#&9|x>ZX$P=-$@_H^?Z$=AN5UWV%q$E;d@v)FfY|Jlcs0F|-fz}()jR8L@rrp*-ELkJuP8{fv^T-a?tOA|xObda&I~t_z3_Vl z-JZ@6a9)_(%w6Q>aYM}G_wDJ9&hJ&5$dHJy~sYkQ2n)h_M0pxEB_LojB4yPbW+uHtlZnmPOIUuv4vzz7TC`BfZr+{KO7bnexR&}eDRm>V?dd31%uoj!TQOj6wtTS_2k(OojF%}!e*}FO*#CGOI zbCY!yEWW^4VI;P~?3DI6tCM*iELabuQp{S*TEBs}_8AqxpsF>}T4z2rvY6#Sldp`@ z#vG%k8P_^&?lMXkLqNdgj3P!p^1DbpuYRD4D^a1)Nu<+;5N`0{L-KcH!)@SKyjNzce5unY=#&L70 zIol|&C)Jnh{fyS&@3zKGy@%1uOk@7mYv`g8ZdNx1Go?|6)I>_+)%AzUfW$cI;IX}RRdE9JcMjIhxgBfWxrqxcE z6V2IXBCD`9$Qog_feA@gKH4_Ix?`R-J6pOn&YBP2t!74+0>7PB9izlM2|HtJ=LG%^?$jq_%1 z`d%aRol%iJbIhdly*%bd+N*$B*IaGJvHoM<6PUxz{MK*UVxRHSsAFz`Q4E7oTr%R( zBV^d=Xk!sPZKS!}+-5X0dKs~d5T#w*J(#Hsp$u$ z_p#=h|Cw=Z%RXpFuwM^Mf$^k+-4Xo%({ik;aEkhjA@%G{b|NReGsmt4o^NMYvOimy z8L>Lqb-2zg>y}l?dTJVGc37ulelr_0ejG8c&_Bj9;+(Rko2g9CtU^B-Y%Q_2TI;R8 zT)`);vhdkiFxq!kO*@aX%ozj=zRupXa$IM-)6qHPq;iwFH=IgNYv-er%^l#*bz`}6 z90fjHzWe*e>h5v-{a!>_v zhs?1D!d0?er$@|Vpr-C!jIp=KRZ#Ac*+n$*riMzy! za%8upd&bT0wet?U-B2cOxhcK9ZXNfuJI|ZyrS}HA>D`v@RX4Vm%e(K6c3Zn$-FV(a zuZ_3XjpX;!+)r*7W}WSBaaZzYdhO98N_ca;)801E^)d-*g?K_zVUaLL=qqG`Jv3q_ zy(oSVZ-@oNkl+d%#L`k#si!zmSR-r_8-pr7h+SYnKZO}$7^p6zv|B7KW)eGzqnV2% z#YN1N1*D2nHpvl}ix0#!QndI*O!;rF?JX^lE;Cz?7W+wgMPxn z{)177k|O1nhQmKZut_JF$Rp_*OsS+)in;nUb8=09o5m@NMqG(&+9AZYv=poEAAiQpXv|zi}{-P zp75@(zrVkXKc7E||F`doPxp=T+x|p>BmM^dqW%K@>;4jfD!i-gZ|a}vkMhUm-FN>M ze=mQAz~{i)z%&0*|A4@$K%GGHK*_+kK!rfpK!#w?;C)^f4h|1)55@@o2($^N2ul>U zE;uaMD|j?0g{fgLf@)Z~uw7w&!#)Ss2E#a(Bw-JNBZHAaFW?Jy3kqRtg7X43IG#?y ziot7vnSsUuovkkjya==ltP1=NJPmYVPagQQ1$qaZz|mmqV57h#e@p*Hf49KE;GW>= zz;*u*|KULTV7)-n!1Tb$(>>9(Wzd8%z)^6Q~tP82lcHU=Q~NQU)V}O#*lQclmOThHBR#)bENe<&RQAt)}KzL&{L~o?4YrBwSsk_E1x(Ghy;Kl>>}1 zeU!9HVI_^ymxqiJksF?+Ft#*j585-LOp_+SJUn>vc=oY_d|w)fZgD_rA&-Q4)kbg0 z3S;>pWt0!g;Yvte%Bb^J?ys~_e!^y^$#Imuu-1DpwrlY7V#+b}fGtu#SYv0Y6(jR; zI9Uf+TR@x)n<^%B5ca`c{Ng2)>#5#N@2N0>@qds|Lx>?hKwTOoBtVOr$LPLH+%5JH zuZ#JmO{hI_RSU5Duvhb#KUT4Fuar4r&kp%AR`C69SR5#hC$?w>N2NU*$T9N}i~ zlcx%<_sZMrt@IXqZ@hqzRruoFhh?qvZg{hpDSLSry>RS-Xm7Z17Zz7pu)V(uU^%#J zCb7A=6eie8)P`DXx1;;qx#ui(pMxHX zI<4SlgTXl=d#_y_J{RUpwuiy)rrPnH?#@KKgUv9fnogwi%+3uWnhP#T>2&2A)h*z@ zab7x!+)-{CypUL4q#GM#GRQ0M9drk}3*F1^1h=s}*md0R?i9BB(kboyv>Q6Vop5&% z%Ig99HaIUcDDJI&*FJ7Fx1LyaL3k#d@RhaHdWPe1()woA0L#y_LY86Ow}#*mq_!IJ z$YqJvepu2WbDUYyTxQHQ3Y)oMhQ*EfXzk14N}Y`Ue7eZ!X|yo%8gKMC#&qM7@dGEJ zm(kSd08cw^jDbxj^`Z&EhEHi_HtT zF5PehMp>QNuaj_#r&bWhW1Mx?`eQY)ZR?Fy*&bwPvG?*>SF5+R*E(vQu!dVc>!(@C zYyxBLg->wTI1lcAXLK=k>Gfe-Z}swUjU##%eR*h0C_O&G9lfP~BNRv9&*M$#SSYBE z(f8>`bW8uLZ`P~e6ZniqFwQ>4YU3<>8QXZzT86X#*Nj=lOMSX78*Pn-;QQi6U84aU zGm@i@GS1O5ijmt`NL$R~NW;yn@b2x#BzSc_bA~yPX9+|g&#j)`$PZ2f?~2Oe5YU&H?X2Lmjte+!)s zRnn(%4Bho&`efMTG5F2u(5uk#(8o|dIL@-r(deboBSNwCMEcs$+R%TY<)I0or_oYq zdMHvqK|5^=O${9l-3&br&11>Ou%mSPg;2jxo$I zFwHLTp$agZL*@{3g89enh+8rM{`b-hvjWy_^Sk*7kEFSI*X%<(ioE)6ik5Ax&A9zNPSxP4+W83O(n5J=ET5Cw1~Tah!&B5qqp1lWYAaT1q3kuKmk? z&bM#)T|#FUoMjSR?1-JpN$1>x&E$60ItiVX_A7gtQyk8*j^UY5(sUDocIRunW18e6UBa_AsiE~2~BVhzT%yv zVKzG@J{5CHHq7OU7)#oZPWlOEaz(5veS;&^!w+a7)sc3JHN~Fd9aO>|QeI}lD&jiU z)L$GRW)pLu^Gy)bi~SgnH=@3I!eb$^_*Ez_78MJ!u4%$)p_J%|C)l&qLIL5Ha7XBe zOH@xtDYj(KzIq$H@7_E{^43C4VGEwiVjhuSkv*#G)u@X|gDs55e`{5YeRuZeV zRYTdR3{n;-6_lb%P!W~RN;jpxvKy^;m-JPt2{VZWL&?DUW60a#D61t89J5gti&xc<^Ep{KBeWG?p%tJWi?Hn^(gZONE>JU^qg}!m zT(nm#V+zqiMYfYnd?>6FdgF~%5G#qX#R|-(wSnl6mje$j7fSGQ&+=N*mqOr;+(I)Uu5i^eyo&Vc%EC9V ztgu-aP48(}KqXtebEwB%aPO|8G0)_BDM7D0i@z63 zJSDV%zZVu%Ar?pa!%Hrt06SPrf~fg6l7r<>liJ3@HTnZ(W#3G8#w;9 z-d_(i7wetvb@J-a`-_9JS_#kjKM=J$3D@ykAscwCx|kb;))_~#1PJn|_#G6v7#ul{ z>+KS1!#NOTM;pmBi%Y)_-G8eK8$JQ-v|3I{xG< zAsMfRfR48?LMh^Jp)^RZGHBEi($klrYB<6-aRQ$H?Xj%!jPQ-~5KkPyR$|b)kA;k4 zm^d1odJAW`G|qEd9NQpn#$_P`PHk$|n?j69JKknHsl-BH$quw+JQSMTVjpyt$zp4^ z*h1XQ6&X|7LECK-i{fr1lGciA_)Hfoat+@VcZyNCsUuNZMoBTa;$rZN-QroU_myb#MdC*VQ4$aP+a0jXTjXP7#G?w!rWrC=qB~Xukz4>#=}o8 z@kt+ClkI%gkL}cy8cAEFhtd?r+J)@r5w^IIo|PFF=^0M+CVG<26)~P3c8h+V7KeJ5 z&{@bV9N;QT#eac3O-ZhYi}d9L!Zw_fK49e*^mZH;u8&qiY}}&^!YW4HO)Py{7)P%S z2qPGOcXPHL;NjOM>7#LN4&uAnJaPy>S^Fn)J5^ZsQ%^zxJLE-s4$pV&#d5C*oW~{^ zFv6?s#q$oiTiur4PE@rx-gvhVF2V_V&k=W%JI?*#rllXIbH6wR>3=WiZ$;fWuIqdS z_g-+0IYIj1QujQ+bew07?(`&CRLy(o?qr-f=RQW+ONhhs)Sc~CaNmTq?-X(4x{aOr&H<+_ziLYksXzILvh39`cOrB4cGk5BrBioI=ccpLY2;L8zjvaC z7I2P{*U0JQa*8-5@JOE8ufYVv?arY1cJ>FWKj?g#wcOfAPUfqb)>=ry=Lxx`_aO8# z=2$Zp>7VsvIF1?jjE_bUbGAWuj zVjjl2R+{P1Zp&FQtv%*Pe4%z$AuEMd&pJ=8q`374eft6$^9XWCkMN4_m@mw!<`Od+ zZ95OkDAss1@E#!W#%2;T8+o0sq({QdTVq0nnvab8Mmck>S(!9VX%OUJ zx8M~yn?dFtGAlpL?dE+mw)M`eX=S$df+^2%#QCjEu$rP)C)DeXWTvL!XtaYr^uQOB z(6x`UT$GvBN^gbC)8=^dD)=*%_1WxU`RoDwuB)}d%4>hO=2&w{U`5)oQMOm2w|}+{ z!$@-2D@f;*AsLh3zG8pHwQCQ{i$q}`Vn4ELJHPFU;K_v`yj%7Kdk2}cXV!Hq9(kF% zc1C-bwF|xe11zSCwcXlGo~wsd#~Nd`w?2`M+DO)C5&Uf~Jm(Z@{15A~_1zkRx?Y~7 zR1y0yZr?1l`nL8$kmEKS#MO3BXTQ_NxdX;*ZI!%H z2m6w}AN0Bvh5svF<4B&}!M(j$;u>2YZx_eiE6ewZoQh62C)~MdpP&tHqwv3X^13_7 z#r1M-Iq|rH3c#bgp|~gWvUz9Sz3v>Y*huF8BwTAtxPA|irmODVa2w*b)^%_4{-FCB zF5HzX@Pu0hG~Con1Ij2sVsHSnaylV7b95AJ{T*{?xbVw6;`u<2otc|IdAgUK?9l|! zKsv^>pC}SxjBpJEg)yop3e7lX^wOvrt@!;2)cTs>fQn${^WJN;0F}`(D0p5;#>7R; zX8(DAN4ycjO4g8=v9&lTyQ&buV=gG79hf0LGk15!#>HTYF`$asjE~(w-=l?ak~Ru+ z)^o7KY2loZ2+Z7p98__%`v%Ntb(p(#bpE)~5%D1yEryf^WEPH&AC5*pnsmZw(mB0I z^DLDXk=&Vv!Zw{`z<TLtsN#G!lf84uLsbLWUqfk`!2Gx!mtmf`j+uET z2xC85&2;uTT*{1IWQwZvhgmTWISqwlxWyxh6v158jrpqukMi(^ro3B(6EO~K8bf*~ z+TtGRjkE)1@l=YG3(LvC>^_vZoG4(c!PI}#Q@!M~BruktyZM#m%5^^X;^fO0JjPiI6{qCMzNtvq@ zA!Sog8K@LjzMwy@XRQx$Dg2;*NqLTrR+G%nWO;{t039=@{7QN$U6)=-70C+KN7GzI zE@cpk=4kniTowkAo~=$%8Yz>NpX8i!sHu6BQZuS))R^i^<++jvy>hbJMJ>eVf0IM` z)XHjE^`}xx71TS*L6$zQ>{r(D7^2LBbF@;1b1c76MZd@&QAO`_yj$h#atvh#+N4D` zXcBu^gFMJ%bi>!^man8O(gIMzAUvUWcs1+Dk=XyvLVpy>#M0kfhmWk#e0azbdeCUt z%TajBZz+x(ig;k zc2!5KyVdq;d9|DRSuLbB)^cf<8lkn(2JtL{t5Z=+tC?iK+Gw%0KdN6#uPNGnQe7KZ zZiyJ+IbL6RFP>LkXyv)p%+$wV7H2|EZc<4sE*_d9he( zxSC$=r*4Gt&Bw+1t<+R&vTSFy6Z>?8&nKvT)MaePQ6sevcv}b6_3BbJla?KysS|rw zMEj*S(+sj|owS!P*jSDX+(9CA8_OCOx(FvLtF~0VuijR_sWr8NtUI8^(LSjk)i!AM<+Nt#_8ss9 z1ns42s9)7f>T@-z_MXJtB6YTUh4whF{!-IwFWAq6xN_~()9MIbMd2{a=NH}8HnegX zH7R{kQPa^XH`hw;@QZS_S-PCH#7 zD|eg6ORllS_1^dPYx}4qCF6IBD`L9RfxoZm;Zu}dENRl-Yx&iD zr8l45r}xiRnk$9qMHQ40Tti*Crd#vbch<2IrF$fr{xXteiImD*C+m5(hnG)eo!w#T zJ(LnkLM0`9Jwl1Y)iH)^`7EPE6n*zUSbtI_3Dbuw!DX1j7<^3(!R@v%65r%~6I77` zuo{W9>we~+;qc%x%yd!SbhvnRSaox+CwZo_-V=8Pv+8a)oD|amcRlDi97O&MM1I;$ zijtHKExZxzvys=r>xL>m2M%|~`{3;(*}K&10cs!bRfMOH_f|7+=k-!E7Z>n?p6SN% zE;3K|c59P|s^*>{3v~`$@`KdW8E2ofigdt4=dAM`%IzB8s_&em>sK?EWORpAb}0*v*Q5?~()f1gHIh{yq&f-pJ|6mYS38 zk8nPt=$}L1y9l<~3eMRK)_QB-Bvnz_E``E30^HZxZe@1^wWkLyz9xmA)Q*Mjo)N_O z!>R&KD`hVyJ@o>7bk<&gTDX<`*J3dKSn>^<$!piQXWARVbgjUa$?d8j#~Q3XhV5Cu zt$XAh&cXri^Z%wb-kNH)0H3!2Db^yL)51yw%Kb*BJ_6i60B;Ss3-1Bi5`cf^iQNO1DB>hy*$cfjJjkZc)1aMNr|&m)vQC$Ji=LgZvKFG z4yG^9B(b*(w)xdk?Fd`6!>Qbfu;bZxtRvLGq@miSAny{|pDf!dOkKk*E6Vyrox^9o zUqxRVXiemr7;Oz@Ekj|*3vh+@!>6Xgp7)apdWoO3!dk~xZdvcFICjViw?9(HlYyjA zd3ej;8j3BXr4HEBVKujH)BcD8{|5%AkROWglynL@tw;=2hsS6z_e4%vCjnlYYloxp z$A!U*cmpYL45~0X4Q9+p?bM?Vq8)z+Gaj9DwmMy$sW7ocpm-l4dE90P6?2Mt5!ujpn&Kd9tpfFCt=3o>TqX3S~=?^%P6F^mzl1^U`cRE;EF zT<;aHm$>iHR`3@v9OdRhx%3EJ6Jl;w+PY1jBK zh_6r~MKLG7t$|vXoWifW_}Yb0KKr2gXFyR-f`gD&EXjPAp1Cb4TK*&S)d|dYHJPja zYTPrJ{WhVUc4iK2#2ncLe$tYqKA`@^7NZ15(1p6pjG0)zEzBhgn(AJ(^NY-+F3!X& ze2U^IVk4wO(r3va-4sVoF83o%untfE9Y{GXYQIcQsG8COywHo8zL`=0R9#Q$kH$Y5 zEV2v?a084}5JY;G3WED!)@Vfsy*vlEKINN_${Uc&E%3n>q%d3_Q6v+F zli$w-!Z|MAmG{6G4wJq=MaI4&8L7hL7aFr~iX&q<2yxaWw~#e?}0Cd|ASBV+rA|Jx4vJ# z%>F;VMC6&ylYd&}o8jx@Ye9A;hA+AAwdQDsc2nC;R_m_zQS&tDp)aK`-1kGfp{4Ov z_Vr>LCw)_V!+cYH(^+#oe+hqV)^(q>)E?guUqSXUv+t933EVwSE375Z5^JKSs~6P{ zR3j8peQJPQ$=^)dV_N7m*!4LzAm2DkSviOGc)nMPsjbyo@VGEo!zeh*CUqRVp|v`k zj9WW(mbwPcFbJHz8P?E_UyO#CjN!ZNYGE~j8UfBu!+I>`0{rM8TRFpdhywqPP-=or z1Mtr#N_P5bRwcEPNAZC?i%>}s!Na2uXHas$vC_eO-^&U3{S%Thh8*DhuYraB<;6qj z13lrPt-+~DsgQ`|`AssVtYmWD;M1*>?$B%rNy8*0OE*F;MrBAjmN^3Jod=tHC7ml{|Zn|;z(~L znL3G!B!AM9+WW{hbCIa2z~2SbOmrZ>@=$&Wf8W4=iTunEaQYWHo^o4W0;?R&mIsg_ z8bUQqb@n{J98DS~B*kX?CFJbnhwgAJ$65a|=?Z^$Vi0@##)9^tqtmi0atOPhU0_O#^@6K?aGatZcUD63ZLclig;B(>1|Ne+w;C0H4J0C{e0Jk^_*aR_q<)W zONUUd_Ji6N^KQ1c3JgCDpQ$DZ)`HaF)}eYe+{@@iyEci}>+TMe^>yxB_XEz)bLN}h zWLzJk(*1B#fbt)bi%snfg&m~x6e=moP${thr>-=6nb+&cJhBj9dMwPLKQoDp*ASPg zT|fU*;=9EXlHp2x^?rHz`OF~|wGZ!W7S2;=j;tzwtKtr%B5ku1pYS2vjhWAT;j^!V zC#;~7_Y|CDF&rWhmBatRLqbkWmQR3If5*9wcKXIS=*)tJEF-a;f}~DaGCYG|C`-^V zi^CJT!8yt^JV52Wh&K8ZpZ^%BKFa=VpMoolwC7XD*^lQ4J{<^ysAHG5(@~?5 zgrxN!blx$jzjLTC?rv4Iil92zr82e*3G8WBAJDx^!B45w@GXJs9CbURX*v11qlLYXkA7Id{|Bi72N52N7-O3D_KXD*V zz!yIJ%QTgvGGaN|o7#p%CMPk8pU?Us5rB6eK?vAL)d#9j+8IUXiLgKn`q_F&^%)oM&Sr9f_nzw8l2QkG`HY zE#_}4wpdReM+Hh@Jr6BYkJQl*Dyer;Q@tp(o3zpojpDwB~KMmDM%l^)~BoIQtGoHPt0ExyuiV+nO1 zdFaQAd6Y_$gw$U=<1D^0^1*=f;4R&x!X>7e$&6_}WG(Igt>LgSlXVR+hEiiwipp%;wq{i_>(~PKtY&m`aHTcsi4vrf`Zk zxGd@2zq(3W@`5kiNA6ehgnmZSGK|PG$qeoFe&7gy#{c;3#Utr36!&8|nThQn`@X^q zVJ9K>qGC#1fi9?1g~0C~mC)Y5Ji}^|M2lborSMBylU~h;W3rRn(F|cLUdL#1DC2m) zo%=L=7Bb;MTjVId3vrq265>4wBp&kPuT!~=-Zco-t3SAZ6_0V~Vn@Xbq&g3xl^x^% zJUoMIs9Af&^L*PL<*Y6IpaZUd4YHvv$t`TeXW2+*j% zQ;NFse$pyZz3Z6otE1~JWPaaGWyCT`k*_fO|7965c|=fGyNySFDo{t@Q7j=xyc9IP z674)3gr0)DQ7IV5Ebx01ko-Ip{evivSwZFTNbr6jN1TFRl;K;ARFW>o1^<=in;f_n z3DD6S@h$^6a2CkaC7qN-t&7rq6h-$bSaX*;2L0paOmmev zaD`(qrD1R!6J~PupC+3DzVeONtEg}upp--}okhxOC7JDvtgSBiwm;iiiBh`^AEOcL z>BBmP!)^|uDKF#oDE7OF3(Ki{ruT3qcUD*I8+L>R0hHkBty`J0S7l;SA-IZ*2VuB9zJY6gsIp7vTx z>Z|CB=_}$Z3U^9L9zTVz2W+aa?~QguJ4Fuv7{9s<&$+EF(%P}qIQ0JRQ~@+b`ERU^ z)~aensS6mP{q-1nXKiI>#_zpH@yoZ6t9@Z%0}&f+{`DKGHc?!qnx%N^xi;QOKY zd0W8xm*4~=cr_2LzAPN$qnuv(DyQKZC_qB*H%s-w5135fseohfmn-d6F4E%)!kpu= z&B^rmzbxz~&PHu5t5#MkgBMd+YeIW;)f#Apw3XTxm~0(ds2oQ=p2uKXrnGX;9T@0{eM@UDZi$c ze=Ppr-#arjlG9Lqod=%w1n2LuluynpV`n0`tbdF&cH^-o#$v8QCxYexu!Pr>0Z={lgdu6Gh4aF zm@ zXEb?>yHl3YG8ruS8rAuIaA$MLH>r5*4{Mu7a;mx%5AUW5uKGLiE;V!s`J{sMg;{7V zbJ1FIUfHOQxQUBXkZ*EO+y0bXorrguLJ}k&E>ANmBkC|4JVR|d!PA0^PQ!;kFWrNS zjwFG)2<~?PZn_JG*;6VYokFE@@Q7~WpX*fZnc`wR|E^>*hvU`6;_;b`)kI+u>DcYe zXlcm!Q6oYEFa&RV1&@pJ9+5|QN%Hp>oazKAU>_W+E=(&(P5(-`-c2gq+X$)1Ddr*J zP>6)$a1^@*8{c52K7d+xLP#gR6MhRngs)^r1L7*Sw}X2FoF-3MoGo65xlM#G z4gopj^fKYkaf#yj!3z)VJt2(BjWhm+(xF zXC$T0;4}YA;4I`MMb)3$gK|bVNWHeP8RsFFQIuDYjD6tShExlrrwZW&I(!}8?<5(Y z9sPbH-@d{1sKOr2$Em2!XZ=9>6^)cs8^p&6h{ok`_5S)$y@Os;Zw6n8(CboD(1Ldj z;5%pF2y;1-=Tv^U`V02#5cTHq!OwY&%T&4_WlJ5%cBuuJ%#a3Uqm}y(wD(vuES%d>m}LWdU|K7;S17|17Snw;2sxXN%8cj z@UiGnn4XL}ko~Z&BzhsZk{&7n7a30L7vw%zvR;Z>`?PvC?gmv>AFFp_U7cYkE7;OR z{iU7^$0xComW0~|;|yNedg@+slX`1_ds815W&xhrF>144;EiR*zqw1@O-?+!)FkSv z;Ek1oQ-6g=7}RX1AfZ+e|DqVL(!jH;Q)M3)R=m|Exnt*p{k7Witxe{8m><%h$* zfXP4Q{!OR3E7o@0zI`y>C*~y_wg)_xQz?HP_bkl1g-`Sl59v2dH!ixs1eo#|R00>~ zp;7~W5ifF%{hm8F#H3=OE>}hyRE>=|7A9A?ID#5uxv&2D4F}y_?iRNLpAT~z;9Pto;T7Ax?<^#j_JiwhFrG*w?t&pYpGZA6 zN;@^`6$h+vc~L4`vH#>_ktrJ1^-laO)+f7_PAVdmp+a#!uiH~4P?2YCW|~4g6LO~~?jQp{iAmz1 z4DbF>6L6M<_XpN>4nO1`3F0vBY@;xDJ%&kLVlLYS-ztXJ(u&HCtoSi~;5nJDSeho_XC&mGR= zGvPJo@dM}j4po1*NtG|=Jl-Q2{*kTiV&A`0WvQ}n)mg4O`;d&pT}_TTA#Kuzwy7x& z<`@>C(4H1wP(?TgF8GKnTThO2mY7^hDZQks;s!_|F9_g;m# zFwP?QN)2!t8iB-9gTeoBx2d$sA8_Lx?jRMHjNCRk5ed0RIBd)D3bGKPd;-1^+F%GE#)-YrmHxS^m}adg&164 zchMS>imSP;|;xPt9YNvr|;rVu_x;bfG4xa-=#0Su8kXUlicE2 ze1+KD!>JVNNh9j0esD#HxHe94UCjNbW1JMr;zaDkDakL#BeR!>`yDI-pB{nd6oWI@ zfq7Q|-M=Gg7f-1K<4>>rP2lYY!S|v)vyzKUi=%Ub{NWFLPL0=zGf6VSAU&X>mN33g|bqbk+1}9Qv~&1C9L$idxyz+vUot{mVWghP75{=U@*Bz61f*3B zYB}0-<-TX+*hYT#0_gNURX4wddW-_AXy0w(TRh;Q<(A6Dm_8>xyIEYO6M705>p$qpY_e71>Q5N z>|`8iLV~ys{){3#;;tSQX@!I&iobh%$U=p z$v77Wi_alfwHS}20QV)S%i{y}!7tqn_#x}u8}4o9$WOR7f81}>3V(2ax!IYG26E@c z!&L9gArTPQH5}XN>xScUT<3l(8JMAUoRF_LAxrTzx`LbEIA1`}znlhc4QBGGZadfF z4o@!}i7JT4AnR;yTKAdr)Vao;ol>)&_s%;~T)(J+-cH_XA~St^XC!ESCu=#xy7!RB znu#X)%!%cup}rysNiZ8kaM9Vq8lJN3Ip+g;@Rc|PT$z;85ILGl~R-u(X zw99}k-jV94Pg0`=d9!M8gWM>mU#Ym*!J6j&y`K!(k4NO$H=wXqwqIKFL1sn4V<|{C zb)g30y(xp?N`m1Yn}w-0L|=jcp0C$&!%iI!BhFG6TiR@x{r3;mwM>RxHko@8z9L8=4UkL zvv?Mp`F{}pu>%C?P$!TJ?`J&K(8us#V&SaR;a**Rx%2H+c-0=VWrwH`m_wG#q5}Mb zm5NkaeEXJ_-cDf`uoH2Yo_p-+WNR6Fc@j5iBaX^r?t1$kC3^~Knh_}0!>k6>C%mO@ z;gR)$nup}JW1XQo`zKkN>-;)M+gny_ayoVGH>}|~_X|tK-FR+W7pR%oL8}d;ZF=Ef z6|}-Y!L3PsWux6h|$N*NS(xC%AW-#ZoIQ?$8Glu?G8FV?^N$%v}u4IMyn+N2%&WYrSq{`|RC1`u}#2@+Ins(%@+hqs}M~>BM@tn=V*k}EO5 z6`YQm;FBaBcak>hK;6b??wOQ@V@kl$wcw~$I-TLdyIJ23XEY4BBUj~juG7qt9G$X|a?t$dF8nv5y`0XKgpBqJWMpKSBjD%85_bb=0 z12ZVWKIS6ZS&LlKQ^uj8Ec=*BsiZj2y&2y&aPNfq-fhOf>fB$cGhaLP`$J~vK zT<56gIOfj6VLlIcm`}QCfZLBXO~tpE$zG*%>%%7oQA1Uc`yLEuOgjP-IpSuewsZu} z^;O2)t=tW;8=mt4stdQ{Z4?Hxw?*6Ei}wGLS>!7J<5PL83kA5FU`a4zO)B|1aTkkz zV5M5(7trW4=Bi%IV0o!|%SLVBJm#S;%xbdmkxc6fsur(#kvP`rnZF7!3;ew+?MTM> zTEcg7up0A#D7?f|NkARyPxeW{-~K{1uEC+Fa}Sg-G8EPMUkZe} zf#iNTsAB|5?|t$vyQ!kshq`}8@snCfpypC_(%uoM^99r)D8KpD^QiP+(EfIz2_8gS zFN5|y9*tB+l|P23kOiIZH+k!$BuQRUUvV5|bsrlKP5GXvCYyoSfr+GE+!^tOa{kV2$xuRznEkJC^Hjq)&xWH@)2Ed&OX z$?#|-bdK<=yJWPUDhe8H6bUXrenKy50)~^u=|z=AkbA{Aq`D4MV{sBy^DYj=E0o<2 z9MvL@ART%5@@U!3Q6eX>%pTP3{iwT#P#<@x*VH#?&EH53rAOV3jf+r=`_nE!ukZZ# z-cacA9to+>+H&m>sj0c>y*qIe4)S*%oxfDssWm!R9Do3R5pZ2 z?0aOe?vmy|M4dz>lxIoZiu#=eMf@D?Gmk{h4N&$*a&232nVQp%=g6^4L3=z5t~8~D zu#H#$?&sQptW95PRLa9A?vu|c!~I>e!b^tHw%_GXRJ2T|Uv-q*p~aUaH`9;g%^Ip) zhVf|u?z7d0RySoFs3k+-w>;gsXR(4{0-@knOp|CMK zGdnwT?z!ijTNQS+8M^r5R82*|$IE!E9o5c0j_%1EuhXcMPJ&=7@cFM{Yi=0x@QHRn zrxwAn*u}IDh1)dhk#tldf%K$m(KkBG@wSrB=Qz&_02zni?y1VNBhkZEp|UvV=b7m* z>@%h(FX#&Ww_6-noB2LeLZMWJZ<7VY9zfd9S5#1^*&{9#L(w>>s=x)m$7{6&r0%c$ zDpCs!0ySndB-3l`jfydlIx|q;1yy}L{_O>p58~cDR1~i`gCv1x6X_-!L}Wj}N8 z8>)sk)SlPrIQD`~7y&CWhWaBv+-n^D$SI`T-}3e2Y_=Pv&Rm#;O}V`z*jBS4Ur($M+dcPeac6EjhPay|+B6Tm`?4OEru(`c?5-MBWYEG-tNL4TYC zl{_AI5L%#6v_Dh9s~%4eu2j*a!oKnr=UO!jMrRdQr~STI*z|8)OFr>Q$ZW%Lu3?+l z%02id=77atP{(}6&)kUQpsHw=@};@_hESKB4)r)pj&F3-AnEq1`X)dy)L;bzzf2AI#kJ-{;__dha6Hljp2 z<32*V;&9S*Hp6Z=057(6cXbEhILHl-u7_jc3*6%~oT@iLgK?;eQcxzG1C{-R8UF~@ zO+`z67p+2W)H>OD#e8%-BjM2(x#yx!(2*axpZ9shJJ$7#0zpNv290rR6au4N^kjk= z%n71-LhT^pym-K;`3f}Q_J}xEHL8Kv;Gn7Cvmie8>uK1`#BCh9;|p29G{WLLdm5M<+-DohR}q+@1mB9Wy|~s_zu56M4#jU z)4oV^-}Xg~9)#K{7rLwAsD*mCy2Hfg#@Ub;9(634{v%=iTj9GdfwtZXr~ML6dKd{v zx9~ZyKp~R{{`#+@Ac~WGOaX{NE3+Ps`iK3G{UeO|BNAzT!caf7XNRv}3Io51-hr3Q zR1xJ$Z5+}vOENfbp|ml>iRXeDUyfEQ9ya*|ZswdYzv)~BP=WOWCwC#QDFjp<3Vx3Q zTZf=-8Uw%E5C(Y_+V`#ao#*nK-$6c@dSpD$2#0%~O;ypB|A(NG?~9_V0o<~mUw#K~ zoC!+1zr)dxmA#P>ZA=q< z7kac*&A9dC=4bT{q=aT9>Bxh}WGU{AXeyTnXwPQC{!c`0)_@GCaojh^IfQONc$!mX zG^w!Z$vwS+=Ej4GI^V%{%eb}wu-+>B<{>_bTGU@7(5F46 z5-RR~;ku7!<~&)TUTT*$pz&097W~To#AS#3uG>m|P?sw61y$s5Y8Ma3_z7-L*~9*t zF9SbrYP{;yC(BtfYBkwpj{sRBe*Mx7$MrzEt^tq1UGdMsca|w0e7AnvCtlutv&QZ5j1 zgqTEj#2UEMbg<=*U|1ia;0eZw))m&}HYoNZEa`53yUOis+SM}?-O|P|*N5RT6G5Mu z!La*5C)3c=u0mZK0utzrKe;51-~w>im0+<~(?wP2R5t*LM8PeNML%;0m+LBSS763| z!%@C9PNai81bo`j)SWB34gK#aux(Cq)*Z$M;J7WOGoZ}_B=UbY^um{W3l?n=iGV#& zS4W~l7z&EWL`Qfe2=N;o@2&V8)0w}3D88CXn*V`$15n{ROoPF9U(j}3GCx3BV>4$! zBQXnn_rZ})i)P6$+(5-~lC@cC84K#1mZo+XZ&`$@qbE9u`ltwofi{<+2H9#^ z%#tE33(+2gpfGr7{)<+@f`hh#C7mT73WG#*ZhGk|-S$W3ttdc>l0P{KeZp^xm0Z-* z!WTg%x$**9{~AJoP*5-nHn#gZYLmUFOKw<-q5cmO`U(4lgFI6aQUt&DzJmAyeg9kb z!As$fkWti=vYA=@jkfa@2c0yA;3*Cg7!W!W$$(P$n-aICp5fbnqtQ98VV<;k267<$!5V?#``5dbMS?1m#0JT?cINsWLrGwynKfu0ULno1*-zvbZmjDT$F#1(s<TKYMKvtj|=g+&*d0e!0Y>=N- z*PHKNX||>vc)lz>hGA$(4xu1oG z^W{;YYVu?H1<5|zkh>Z*t|rAS-Qcq`7B(_!nE;8nO|_>PM|55A2c zV==rPeh0^gG$+G)&Zo`sBlN`EFpaKGPwK5&WGW=WjxVFD7e<$>x;_UfNgn!W=jg#b zA}#GBicYisDHX~OT@bavHoABZ=^#4ja40yclF+B*qfbYCB`mA@2x4qtdopvD?Z7w87gkt6gWI?;1zg0sW|524Z3dV@b=72PH`&JY{_2JtzD zP;my)LwRc`&Cf5E^pY*SW=Q6`!9*3@6<0WqKZUul@t2$a%0*t=hHAGMm1>-b^JOs{pl50l2BLzPkJjC1L147<|9#wmdx1WtgjVT*o7Mv=jWl31&Zm z9F(8*&sX5@8$`NjF*vz-R4Gwp64iox{N)J%J^P(|3hw38Fx{QWDa-_(NrC%54yKrk zk9ax^a0YiW?#Ior%Bw+(!FYCafMp*zCD#{cZqVa05MX`sJ|Bbl>Vwf<;0Nu3s-!cz zlESDeexSoXVqb;=q^iB3y#V_5%Jzo#3~9ghLZMP0m3apH9j&$2LQALpQ)jCkRiE-! zxuslGUMlAmMKP&e)uZYKHIo*gW!0K%qtUgmVuo0xR#L0QeHpcQb+6h(ouQ6b=cvck z+p1g5qUp6R+5qje=0Ll?9!dR z{ngrPDYcU7QB>uFlC0>|`^p>TiE>vNp)6LqDdiQPd{v$=my&@s^%uQGz{JUQa+D)ykasF*8HA@W2{wYEr(WI`>HCcUVFmNUUi;2KrN@X zSL^dLM%}1hQ9r5zN1jvtr2b%gteQYN=_z$3$6ORgUVCmeRl6dpMb&!TyIMWMvA9q@ zt=?77tA%)7D0A%A;YWDN7U$u(n1XxZ3JIpq&^48%uA4x8k$kQ3^ji${C)QE} z2U9Cd0lA+>HPZv8J&-D*t*0_Pe=4(DE|SW$k&5&M6~b54Y>TN{pYY5ep4F4?*e&{O z9#3t)*9BldJapiu;4gkkJ#d1IfmAqrBNfGGUi*_CS7uU#f1s#)@AlFCNOb?<{#d$M zN4f6}-}aeQE}N*_r&96Gq$=6Ly5*$LbCq7s<}`1Cjc!Z|uA3m{r1(A4&q)3C*X%=> zEi)MBO+B{(&rT(jZX>8*x>DbS;F{QtPRN4)&S#(qhXLA1x77px661ZzS=|L2^Vz$bZpj?_Aop;=y(I6@#^bN? zqn*KzJ)d61XzxOvaSA?ZJ^64$>8qB8m&s3`C=_O<2IuXb+`7SdMS2f%OCUWl1kPz4 z{<~%HV;8cKr*o^U-(G^7Vb zq{DyNm`?vucy_-7AqGEXPMo1F!J4(uY(%5?-OZyjV2UHqpPYarO=oHYRy_^FngIGL z$IXv+?509s9KQoO2~GVzFy0vS_4B|pB~kM>VtGYPPOkA$EMXH$gAlqAJ<)E@m5>kKbQSX>NeI|p923oLU-n95QHuRbp*sT9dl%V3sI zqiER+7Wt30T4#J^Y=lO64!5;<0HR@y576Cs1ulHRR+q$e{U1npJL>V9_$3yamw@e0 zfb~X!{UTA7MDTxZ@N7-6Vs|j|Th#wQVCVZ#h5fK(r&{PG%%-LoC=9~U++OHQT~uGF z#HlPXd2eBa!C^ z{^Txf%?c`%$=o}PdZ9gtcQMbtBC7Rf^fQX5 zIR|dhueeUFIi5a6ZmNe=*5U)t{mr}Hr80ibIyubsa3X|LSx7b>6g`X@H@4TSibll%Q5? zK{`M_Y78?Ps8e9tq3{fw>4->12Yf{|N8(g^9aT_+2ODZ|Zfj2;qzUnR{gzkjm6ad3Zd5*Omr(H>bDPoo>!->WffP ziwe;9$ZU*G>w9b_@2dnHO?&z`Wx>z$SWgej=*BZUQjtu=n|&RB`8)b3`{l+hxwnSE6sF)47yL8)4$LYNuYNei$*9f>sO!N zz!KC?* zv$2IM(rr9_iClZqk^C`$y5cO|g=h3(-qE*M0_XRhE7>-#YS~F|a!`+)^FAlvX%z_{ ziKHzT^jWBDs`9Sgc)U3&POE*3a9(fs?V=~qjy^;QUoPKas*YJGhDMY6dye$q&%CQY zfAJ<=i733Nr1EJEl4*tppelWbbZCBGq4TMLW?(H>%j2x=3$`R1DUJO=1zDJj ze2XRd=x02p%YTyOslja1NS@u1tKkm20BiSLDA-%-kA4qG*0{MYG}&b&%j6O}2k;yr!km)mP`={0<+ieKV?w)%Zjs zNx|9actVbl4$b;EH0hskh`vqZ{{?b~E~8k#4wt`=$pEY2`Ik_Y%tIN!9^HMIqa@n= zYNY7w2VWkhgOI>;x8NFh!_-Pa&;oAqlT_@G?(f+Sqz-r^wO&L$cB+IPF7tN#`@IYcsl@ zI%seF?(ll9e6H8d3(jw_@NLM1>d6ukNh7+7f_)fyLsMD8duJBr@TKrRZ%9YY;z~lf z-jzL>fIs*Q)z*1>9gpaCM3DN^7pHDXN1&srBa7pq{RFtahdsajrIw94vMN4;Dcm!b zdy=UgzTkb1XLj@!>Z^RzBPFN`b7(rvt&T~n8ph*s?xPjaicv3a(xz)6T05-*D*pG{ z3+53_Bz?(E-SOORvj;ocl2P>3?jzy9AD>-z*1%(Lf#V??&WU&S?v6fuX6*w0k9T7&nNSz0O2^rkP@(Q;E<-Z@jq}XjOC>34F?G#n9+^#z<6o9aUE79A z^9g;RyLczQ;dPF}v)#tth^lL!)(W5ZbUfcH_!*B6+`+8vzErrAcy%j#G5c5Tx7Lnj z7PecN_b`R(X*w0{C+c+{iB!K>u0R%3cE=U^O_i8Fv58}*5$R2BID-Gti7L!dl#Zh- z6i0~0UWjva;B(%=bc!?1>>Ls0=yp}WA^(b0t>YXAair@ea|B#=n(!bLK&7<+&wXz) z01mqPk!&!TIw3RNj>*(5Kd5yA=<=0BQ@4r!-zGXMCrLcmK~HW8)s0GBbCFJ-KP^E( z-PazqR7(<-D$}Qo#=ATTeN+P4!wBYRM$swUi-xN`J-zH`tBz4`3`L6-?+NraA+0Ew z^Ue#pgpD{aY~?l;mEnD~P98iAlRT_l#%RwaKiD=G^oSE!0M8nhz+T66nIFqjy`M9FBMr(I=py z8c8ki9JXc>2})DQv26)6lT3$tCCaEklu^5RubsUAN^dUIn{zn_PD2BFjx+UD5hoikgFDDFs@D6Je2fjE< zF3}${ihlEV5qM!UXe$)7J{zsi4D?0KOownRUW6g-OgE<$^-C~1l|Z8i8e57^Wjp$n zEwHM0=?yuJO{iA7ko@=zZ*n1AfW1KXBg`ovzH>MU!_8+=(~N+}wwVRXHN1n7^lT;O8K>z2dO1O`@G|cg3Fe*yZyrH%=r(g-`YK5v;hWqu9rth;X!wjd z0fg^08!c~O<{z1VfmL_I#%BlNCi1!!XpO$HuCM9Ggt5kxLBSvCwrr#il}P_-E{J#p z?`R|^wWZ|}HOwuZ{}UX&mgVi_U4NRpvECs(XD(f%*Sw}On7b*@{sX(c6*PX4TENQd zn^Sk>;E@`1oECwxbKtTJ<9({5ahixDIuBFyI#93F6P}X8WMsD38|sui^pDDd%!^oB zlN5EGzLcK1W0`r5hYr_t_D2F8vvqipW5DquSxuMOJL4>F=JJgdiZg%gH?>2Ie>=;Y%;&V*l3qyS6Ut33@QD4j zgMArDr>i`l-U+g&stC`7i&SOB=m0GeJ~8_*MCik3wTHQYbMQWwV+-%IhFhrWn$qLb z3E$~=h4J2QOC=s3N5vqpw{KA6yrOP6fWN*0byWhNQXh_|)pS(enhW!OMVS(~l&q(C z>Zt=%TW!qWshoGv{fNXZZpXoUo$lH*kZK+jegHCg};_N-{#f~j;gs@VeO7*huN7!uDZOy<-OGQ3{m zobOF#Qvttxd2(;#QAqV*P2$jzsknE~(yiD-x@lRK<1&?^g0z?~(Al_UN>3)41;yMf z>gHjn>&D>SKZUlcjmgGYr767;6{qk8y#BB0(j1}V^_31-KGRyhhr96?+YM9Doz*p| z{JR)lU|d?KW-+-=vGDcF=>d4)_t%l46v-_LFYXlbpa;Y4SPU<5Rpfxz=|pwk6TR3F z!+m@RGJS|k=p-kj@E-%aGm0KYL+YiK{C0<$=^j3SE=*@Qj~^xp{O@P?H7bFz__yBR z7?=y{|Ap799cuooX=xKvamJG(VZ(+?_%^e{DZNz6#XKbDaW%WQN~FLg%Nr6)YrN!Xi4dY{fh zKO&HLfbdNnrE^^dq)2V_-MetH&tDsxUcW0Zf$!GxZnmGCiFwdP)bUaA>&LQOQ$-S7h@@K|R06c@P4rh@WccujObvXM&F z0F8o;1Shl4O-&JnuJ^M?hZ;M*wh?*rHR6Ly8 zp&5vHll>M<`Ef9&K*o&=UoaDQ^SEN|5=o^O z=%1wGDAv;rxo6LVFL}RxGoIFV_6H;i(e z4@NhMnM-@|8qTF(SstzW2YRy?=*iyDmBxwK9u_nh7nY36DJ;!hnFL>Y6u;6yrtFT1Exbj06V1Ppv+f^eFgm2wm^S zFjc+4iA~{bv+!62{B#|_bxB~#W&HaXCMt%$vV-pV1@P%g)-MbG$?RNDj>DIYCuOi4 zNOToz6A#zbo3z8xXa*{wIn3K5<-$A+V1QWa> zxik^Q$xYK+bScH)eT$*qZ-si{j5(LZOCCu^OA0LOU~pk0@WBq)-+AWSEMEZC^km!W zqK}wCrcifY5k!j0FR*GBxZ%oV%y)nlc60wW@XK^`^x0rp2ZA>Hv26uFBelU0zfEIV z(nlEP;XGq67$=e2e$qQGvk%^|<&AkHulWqf#?BFBrsnu(DnY{hdhpFgRQfkbHr-5) zh~7MdPi!4}?>eUMC}I|{ZC6le#Iwgfu)mG)+7scstMTry=}=rY zZ}}9jnj3(egUDfd0Bih#&+-5s({PkGZ@}oY*qa5Y3j8@Q;VAgmg3TQ~t2*4hWVsD$ zm(ArYM^M^yAR(tO+qs<2e<}Krav-YPsLf*7raSEaC3IR=<3;$yCl|$5KcX+=HD)qB zGEU(eHwiV&DZHPVInr~|agk7uIPec=>=6eEn{8ok?@?FXp!%|yKBJtuWBkW& ziBwO$;dA$)sLMIeZm&jbt)(FQvmK~A4|qO?YU>;OFgs3&4wiXj=oF;(O2W-CpS8(mdCs?JC0>f> zD4900{L7&JUMTA#g~66ddN$>bmwCP=!MvUCQaC-50p^SB$&%pdb{v=OIU1|7 z|C{o??PT6fT{{pL$xgaib@38}p|$#EJYcG6et|wIm_*=_VD^1r?$hAy!n~#>3ZYD> zncTc)6UU#E&(CWdPkou!+`^PnMn8|=lG--Fc$jm! zmGj+Z{_j9mjmFtuLD_Q#Z*dc}FQp87>8n-avAoP2=me)9hROGF@N-JftgQ$XEu-^ z)6BOX)VRPq3zxt~?_Ko#;iQ>fLDy0ahUF0K@d@WCkuy=0x4<#_(Y4)`%U#vI(Y1#p&EhEKr=cSE zPuA6#bh^a(AL-@);9^DBG9G`y+|!>Zjz_!R!@$lZC({QT7sgU;Wa1wtiE|ory4u6e zCBx?~cLu|2FXuUWJe46Js^%zp;z*}y>RRc1>!=QAdy-d7B@uo$n(8ppLB6B5o&dkQ z#@P$@HkO3>`EcAJXnx9|*ltKR;%7%b=CJm3#=&VfL-()-6xx$Cm|Ttw%<+}&V_>{H zpj@xctbj>ysRu!S2OK5QdcTA7%?&p`+tCI+d~fh{7}|9kIPn&o^>7kvyr?OTlP`Hk zTdMiA4)(ifd#-66Nxm7!+}Lm07EtU9%>-`E=x9gsN<;WmpM4=YEdJMRVb*LmdpgH% z`!!hKGxj=U%Vqw7kppv!eE+-T?#}?pPXdRw0;w+ql@0;6{+zuU_x9zz?!nw@ z=vw;PpKB$_x*S0^jM<)>^|LzS!P%Wb%`2D{U;~%eAup!|uZ(A#g6soH(8*~pgN7&= zoW00C9%WG@v__ZVi{HZT{{n%BqfxqmhNhMMFBvxdSVok6fZe9`U_bu^@ptFF>pA|y z{!ZY1FN5Nr^DbYRW!wTDdL*3mC+#{cd6;$2Fy(DP< z0>@uvEt;ZeIZNN6A$dhXr0v{t451>bfRbJ%O{NIW+&>&=vCc1K%w{5w<}v+{Aoj07 z)wR-j-*MbA47WmSoC2SnV@R!ay863j<6ek%M&XnQ!z1yISC@6BIO>u-Qxgr^J|;yB zb9p#&C%P)*EGdQacnw_g59bKi4BXRoaa-I=%hAk0+D%)0%GGfX528|-Ne)zkD<@ht zhx>r1il>`9J-!l|Ue7Uk=oPXb7NW7Nt?$d^#2LCA@C8qCgKedn*o>KsUqs&Rct}# zW`eQ5DX;M-iE9qjpM4Cneyw3K)yF7fvOj6tSQ2IQHLzfq=^*MCr?IW+1=rA3s8`0C z>a(niX(|_ub!g8ue>F_=Gh=BK{~hS{=Y!G82SZ+s>YxN@Wv?lU8mIzEga5(L6)>;F z*Lnn=YcuGq0Nn2r^HEaxSA$KfgJD;JZCs|7AluRAvvB9b%%PUI=4+rfk7+I#vjaS? z9qiu>6!B+&6$^E}v6$r287Zv%2~ z%5t{iF)a=%4l&2VKz@T!&Oq+{3YbruC95zCzPGHUjZjet1!*pVlkH(y5B@cw-!5U< zg&M+cngPoH1&TSunx9~5)?fI+PcY6^!1V8_R0f+vV0aIMj|;=#UW1jq37#^7yhbQ&FUq#r`BOEDh!P`Ng^EYH89)rcSQc)e^ z_a7WTfoPx>;ZW^ndO}ZQFuje9D4)8}k?*koa?rOC*Yy@- zBlP~^sHTdsq|Wqkx*PLz{6RIr!gZO4*NCPbM!#NUfnhw~rg9+4HTbzt@r|<> zeEN99W@D^j44kKt8VFJz?1X$H4hB9<{GJ_n$K#f5pN(J*B-iN|0lFXqb{c}TKkm(-S z(KUvWWZsTN@o^nZwU_)tx4s5^!)n7^{R7T(oehy*KXx&tJj$Zic2cVu<&SP6~xLV*-3WFnKIDbatx~$40LrD4Z z&uX4e8p>;aYpIJvJDtP#i`mls&}tW80+pM-S_S;Gt;n(#6JabHk$^}-3F+e}UA^#0 z_G2Ds4^rJS;xSz8s!1kEA_*Yzq{C%`>8!;oO5qoa<~rF1jQ@f_=F%hXNDRU8C26IM-zZEQ-Hwxw$>DRirxPUj=CL6V(zN2K!> z(?(G?>hjN^&3(=w@=#iXBi}fVF{O1A`iWpT$-<5>Ce6Ng zqz5f!cj!nh^fRzS97%M{=b&qNOWJm3M`e3PdjM*PaK|Dv<|n{|m3gh#-o=q(Pj5e@ zO+blUhdD(T?Dw>snn~NPz0>+@<+b#rtVd`L^&yJp1=@VnMaz=4b*U@ud+yOqNS)7wWqR1 zDX1FM`N|~qDEfw0s$PAioPtPD}cpkrRGW<%>N zpn1rzR#zq|H`Ge@6x7Q3&@|sxC!v=Apcc`Vs5#VqYM_>&erKI?s!!Fb+C&uSUDVF% z2gRVYRjRAO+9kECk|KvHZPZc95qXGGmwh%(eJ_8v-IF8Lw`|8{rGomBrN$_=6th|k z9ry(8xLQjcps4a=`I>T7&7$(J;zUOtq+CNyol`BMZdL6l-tQ^@DK(YH$_TZBmZ&oD!j$qPEk@fZZ2spVfS5 zJEm*ta2V{@F7bb7@-}PPk7-@0bN<>7kWAkbWky(<0;w3Ry4?}Tw=EawdIq)KAm?BD z9zjg}s>2zggzFqgJ2x)gRjxLq@LY6y9J8n;3%U2BfXeH<;#fe{+0V5Nr>&0fcoY@s zd6G;!lD*^ROtlWB`6_p)YlTyB&Om{F!sR4YKa;ztXOz1qx}jCD@^8_A&vG4bKlkj! zS6|BW%^m5P4SU|pyB_^|jHf8`uJ_YtIz(^jA(>Lq-U-Yc2}DOWg;bPuWFOd>#@?x@IJ0efISxKg^#)Z{xWG$fwOo@>oOsJo!;m=HV*UguJn{ zx^*Pql^}Pa1Ukzwx^PXvh_S{)hHpIYG|r#}+$xcBqT(6KM9#%@c;?A4%y&>u6h#vp zj-s*;zgL0bj>DrmpB#$XaM(}KHdNtCex0<7eQ;zoVTOF@8Z_f#__!gkFtV|xxtqDK zX)@R3?j%$PF+nmIx7Y@Fc?Lj=lEJcNDpsxMq4RpKfSr&oYhLUYw z8(!{=c@SwN8*zr-r1u{R5?zn(;XU2#bGTAluuk(WcZ5YuU<#v4{#M8-nWaTyQL(=G z2!~}`F;yrac9BGDaVZQp^x_((H8mFlxj)mq!ZF$X}UN}>>#!~@tRJMA(hR8(L7uZ%vy)PD>tbs?>t$Xw#M;~XLaHFumflMB zq$OfuX{+>EIwfi10Wnm%Ar+HOie_nrv_*O-rWcciC~=jTf>(63P(wV*_I?slh03^E zN2BT<$o8%hzX;JdN@ogPgg{Y}wu^$;Pna*{5m^^09Kye^#Ixd5 zT)R)j6JkLrRg4e=#a-fOsgiY>^oL{3EUjWc6crD1%;b;;i*J|>c13I@K1N6XO=u^^ zpqY0IUwMZJa(0#r3&rO+gue?V#TKGP93yNKe8PU_g{_7?t}8U>d*?7$6zT}AE!~*p z)rorGICa@8%TGEo2dHBzQjOK7Lz00?Du&F5YDs)Mm8)?4DU zE-D=3`*+CP+Y%>S6OPa!$-&$*x5e+MydA8$bK@1+9~ z%J+OUN5dV|{lDoV?55i50MmY$Zc_$2D*@)|^ar+@G*dJ>`?plC+i_x)F+F4g^-vV; zS53K$F=U7TGrT8D{{%elCOULq!1a6iSEkz+MBeBX>gq7V9HZV8XH>|JU2J$skHAQ8 z?FZ8v0(?1eDJL6%!NfLY{Df^S@iIp_&Lh)rY&Y)Lz zk_62rBnZucuRdzXX)J84Yq$aPeAkfII2zr?6y77!kbz3zjlMmu-Pb6o|HG}aA8q_= zrgk`WW5}Ip1Gn6r9D*LO%9(X3Fuk3b>{OYQl~yQ|L&$>J?pfgN$Sm4wD1p!Mzt3I5 z)6*N~YvVmf9>QLDoW7p(?yVk=*Fg=?!Fw5A_^Wp_j7vt+C}T-1zfB6Gou2F*y0Hh) zlc)Dh^ltOj(m&DF@ip{z@I9jvDNL5{5z;3kneTpqe1x0i@_a|5t=A3nWkQK9!pV%( zwe@}QT=O*X#*%9G8Xd(McME)715qaa#xd0=E#rI&xhFGlP|Ze{uoZsS&lneU#i7tO zyWYaTZXoG7mE69eXmjJ7$M}7&D?jOZKk$|nb+&OW2K{Go_hq7GerCV+0@MFs4$O4d zAopPOl+VcUu)F5r^6EfZL`K%&t!pT(*gL#g@8N$#TsP3}ws(FZVJaspa$FI>zrWR7+7~eAT18dM;h+oEo~l>15~y&i zX*$)R+U@gUAs>S%S8IVVq#lyGx59#Y?EBC#*Ky>9w>3MvJEyZpmOC$_B7TMTf$2hM zO*cDNIGUmy4uq?$&7vMUwxe-A;W&bRVuQo&j3%3F-$QCa5!vRVo{hQNdOB8B8E$>s6n zw(Nj+&+Q!MDC0ba->!*7?E}x9kIAb|NJuHb zY}t#BJ7CLLdr@a+GGPWe`;!yj%vm=r-_hzax;DDD(DV3ACQLr}Gnmc*&k}cSDoxdO z52Uxy{m!+}y$-!aCQo-Vc0BH26#kNTIQfY^JPn!nmd~4m320@|1NHE}_Jn&>cWn|4 zkD!zQmGy9s6d-_&)km;cNVNr*XQ&o;szy!P?i*V1K z!L6|gZZ;?Bu~+c8U&S}Rn=?mw*at<|1#U$qMS8F=gS;sOqd zZo1~2CxUgWP&ZXY-P8!()EHk!JcZNnT(rTll0rg?2A6Br7uR1UO?)WYj<2w*cUf*U z=e_s1G-7ql^e0G)4dYB$n#cO15couW%*&Q)BRRAl=a)^sWaOhtuhA zZLUfNL_uccY~VWQHpZJa;NY4JYOVvnc-!0^@7oO6!vU6#LJ6U~rJ?!0X`y*H?y?@1 zVyGQGWqk z#z^*!3Wqxz)ufN!ZVo!T2AJGS++27d_nQ3*(AG>$DhI<_2DL+RRG^dLYG>m13t|3f z9u&V<@c7N6bKO-uFZLH-3Il{G!eQaAFhckwED(>7cQO%-5+_^`J*1ZCNGK^F-4MTu z2f?u+;#hFX0%@q!OYF~V(~DvusXr4+CxF3rpr&mhya!1~i;KudsQ@o5iY9Rq2&0xb z7mTPwv#J+C_Vi4H55Aj1eXLV zQw#5!i|@z-lu#|vNfbc|lFJm1vLP3lo@Uc-aAtE9T<6d?#NjX;My>sm9LG||WAuY& z@!daZya+Nhq9M6sOpjvasd2DrI6ay~V?X%yMn-|e^7W{GCg8RFPW3&Bvr-{cL7ma5 zY=;#uh>~nKdagB8_SsA+#v|l;_vZ{1WqgjixGFm0!%X2BPhaf}=k<8bP4RF8$921z zF>r=N*4@4`x|XPLC;Dc29bOw}ZU<-HI-tyRctHmFI^i0O^bPUGqaC#4q?_$q<}FBi z-U1wVc9M8Ilc(3so1I+R6{u_*lFTuePTyZo3Mg!~cRe2QCY~AYyj&M+lWHB0L(Sn1 z@l^7bhJDV=m2rosk9R!oyxhK>Ea@Q*z7yyhV$tM!NxwTzqHS|DiL*%dd&yPtr{|5g zsV|rJEjeX*Q8@e}J1;jpbb8NzW{s~$@mRrgmQ3XHBnoVCFCcGk3?BZnqs&x55k z!7TegB&A4syNf67pCby)QIlji2X3Cm@IoQZs&KuF=+SO+9z)Gpj@czkz#u8$v3lr8 z_uxTmkNUI~%+nf_oGFfHs8D0jriMFWU|(Y#%fKnG!IIbQ2T89B26>Ek>_cz5%rPCc z!Cl8hI{0t!r(Hw2df#pbt(^wLlyIzt)z#rxnnw418Qt$`_V3zcdpWwuUut7k~Ln5UjpLt#Ycs?lm5Em9rC&8ZF`t)#Wu7!IyEUaQGkjM_(? z1zXx*`>l?K{TqqD%C2ot|0;vjM_MO)Q*9KlAIIz%qkWf_3olwDGAydGrjc5GvMPpZ zqSjH>l>KTfGc6@;hFV=!luxj-BUKHqxsJL@siu@tYQR9+m3nN+I^~IMgb8h@Tv28z zW8{-^Z}`<8awR#Z{9c}<^n<%UAV0D-l&dNpc{-d$q-TkP zd92)24wq}lmF0?ZcG+jUMjA;+`GFiQm$SvDcD6N@f3mCwvd#9;_SIIJ^}KEK*k-~f z$H@)lYH~B!T%VFf{VjWK^W}S_r8HMA$cyD-tlciwYmSmp*(~pn&ne+#cU(|@Dp6D< zUz9%THZ?b&^lJG0yzF^9$Hpqy^P1WVov;g2k;*1NP%gNHpK$>YT{_k!*5klJb$z|K*{o!{E<=G8;=#e_FYU=3Zb{riA=;rsKyf=P9}=Zp&nR@wq~y*H{Z4w zI4aMfyjg{}a*Z>Q?^?3sr*pPT=c?`OKz`wLvUuX~S!SjC^2524+|5zU>@7@^dN%i8 z=8NudodFwnaDS(cT5_bZsL?lh&-=PlcW>gaLd+{@!1=dqnu9t{ zS4Tewr}r;iYkg*r;xPLCHry0_(J_CeM;Qgy?MIGB9VXDEH>9Ey4#od*o8P0-l*HfA zF_%R3)|*b{S9*@;4M&aZQ8AW*+x-gv;6N?6h2H0BrmDC>ooD#{gRwGQ{aIW~M&Jx*+~D|n+J=%6)h)(?2%#kjtnpyn+LD>VhT)kSkvuwrT90rMu0;I4{5i}?=ES0SM+ zb1HB0oR5~)pi+N!;Y~14N$}1O%T^HDWPDZUm@C;2T>2R=S6$(QU=bUMX43wSlYU_b zkCY;rFPh}PH=wpN!VQxDE{KoCW^ko$VGyXTq4-+(31-U*GTs0}+bRA9^<@+@f)yu; zrJ|B8o&|HvyWE>0HX;gY9{ z_rzoHg0FolG` zK_h7MA!xagc!2!FR>C`>E~$fM#2Fx}{UGh1f>-!XZO~1a1rulmHJZWxx5)Y^#=0d6 zyM!d+i0Bm0!0`sb->wx$kyf|??z9)%;S~G9oQHyki?d#P*>@Yn`Y^iLrQ%|B(C-`e zLlb!O$3heK%UrRiuox|gAg+cNKTQQtmQ=~I{QDn!wj-G&yU99CFH|FI#K@j|f={r7 z&{8N0pIeg}W)Rg%Bi2r{bYd=OZ}9GR{ENL<`vl(MJh_gW$nKCWLF7DE6gFB0TZV#m z?~zqL8oy*mlp=dzHg{WMIgX~HB|n5FJdnKfv8aK6pyt?ME{C)2I*2wDl|}@=k2ilK z0Wlf%#TQaJK2e=rCfjoa-;Z@@@q1ILH8RCgqcub2KNt7sM`KYK##H#rzi4>6Qit|K z-`~P$LH%5t@9Sc6K~MATu8-E}F*@isB+YLJm)1o?@!Vi!5@R&JfFUHpw8S5FiG2N3 zlu}>uP6U%b@ts5>8`A(5;L3W2&dN((wuAiU_qu*C0dNSU7EK}(uO|Ja3DoDibYEZw zT{y~SlLoOFjq4$t8fWlv4Mdgj1YV#IlL7M5#|mKPSQC0%c6=fV`A5;DI{RmVO~Jp_ z7xzdic@VRGopJs4BXMCO=hF53AMAUKHldR5AlY#De51gpJHBdaC1oNdR9dcLWULSdwa=qCdS)YHkLmarDO9-Hh4jg;C$-aectGbHMozcB-&z z6B)&&NeAdguJLMEx@Jraw4pSNgCYBb6KE1T$ckjed2!-@2ItMxlC)}eneMt%TdB1n zH%!O%b-vb(uIF_@_Q@TTvdT}njxtD@$ZfDvL>Z2D zA&TWiC^wZ(pu^(e!rUM!LEXTsFDkj%YZcWKN;Eie0xE)%>SARtH%;lF=22sn9_&8{ zOmCn%hrMP|B>MaPwYPNEd+;tr$n0yWK2`RUaW@rCxQhBrS+A5+2lM=2An;&yKKHp5 z16qW#=p)v`sZLXCfVU5;??Bg$LFJeDjAOv%)zl=lG$^_uJn|HEoVr3i%VTpvdkN|^ z-t&VxRL!ln<&_sWHe9@4V>;@EsSu8X;G6IcvFcb5{xuTb3aIJThU!IiI;x9TN->tP zipO%Hlkh6F)r`DX4U`c{N|3rqy~q1(B|q*Oza3X+sy5Y!exig{nyiWH+ALHWmC=wC zg8??$KXMFjA}6jQsTI3WD@)Wd1Hp(sra;D_5N-$OT8J5%qnHNigG+bWL#dpekmJ#Y z*_98;#rWqaiVLv^KgkXSC2eWyux<4BdoVTf6k3=#M}CmvU23f3D6#93-!TNNxYfA_ z7W|U)m(z+8$mR?|x%1AcXG-K96g`8`=O|>e8R*u(AZ@xUS#XC*M^D6E7w>B9zJ}(# zGfMZtIP6A{2sen%K~!3*+ceyEZ+WJF2J2P&7@g^1j3pa83pooL+=1j(cXxjy9s4=? z4GX9%E20fB;L)rBGwgI*m^2^H?4p*OO&+>ic>cLJkiee7Jr1|#3sm*>NXYI^65V3@ zA(?S>?%=*2oP~CyE4YeUHxf0}Pq)OJoFsQvSl#QaX^MM;rv~RQv-c=CHlFO51>`d1 z@_zJ;^4{URxXe3-c~iNm-+Pmfznd(Y0Fo4r@q0^VE|$evP>Jr%19IDrkfq;3mq*u_ zGx!vI3bQ#kr*NiE#Dlk7Bt$w$dTy*=+}jekZpe5DlNjbj7$6?xPN<@+DWS z*Pu`h??+J_Uc+D_Eu^un!_#Dhg*l9q>5VCj>*PtCM73dsTEl#vqx07ZFZevJl|^79 zb#S6naA{S9GrDfN2wT~dYw080U4Czj-(TgXOZOFqGZp=71)N1C&BvIcoEb)`FU;>Z zSSKev#93U2Cz}hScWi(fbw1qYXLOA(@Q*!%b9#$XVI5cI<|rWxlXRA3ZjXOyHHm0- z=uUpX167R#ulBfg`rwOqxViG^T32f%osdQe}A?((#H(6_O3$|vl=C%HXPt9aakoHO|>C+#O7EAM`Nzzhjr?f#D z#lM@Sky11EfnEG5z7|u(Kf2g-=cXagRxzy6AqP&-6QjigRe z2wnVD(nHB&EnziV?@K==owbg2pEZL0AWLnn>#WnQXRRw?Ylm4cTkY0T0ks1DS-)6+ zT3@qAep>YbO9M6p+zptUwiTNKZU&qVI35re@H?O%TRJr0UO+Oxr3-j(&CZsV3J49z z7I2O|`^H+4J(kmYLb@h3vNpALvbMDb@d@3LVx&CQ{?-cCiq>YP6nt3xB<7JyOZ&jkVLXyi+D|3$2-dteN|GF?0g6!(JVb|)h(04w z+$}_b+k1$OMVUuNi)vbCUmq}gps0CqA)&*vf&RI=VSOj zjG@1fj-MH5Yv!3#nMeMV?~0pl{YB7i9z1b3slhJMi|9-re?OVme@#1ZvgIdR&x$tL zfuGU|XDg8_F^TuPhL&I>h`Kq+8M3)HJ&tR9&z@3wg_6FZQQOThS4TM!L3%|Z>y(eW z>!qa&&cdstfedG!&`wKU)CU<+n1`8{!77(xe$iOePv!d!XRQ;Z(*r|0 z5>ry}oApC6)t0m00(A9ua+f!v_wQ*~M6Z4*Jt)6ExTqnKOwMrJVHW1V`~){|M2lOD z?)Z47vDVSoU@l@;aB@Mi*o@3evHJ?(H^@zPa#s+te~Q+5dhh=+=_{1XoZ}?jT!1l{ zjfZ+Wef|o*C-|?!(Tvq1Urb~oOjoi7)}zV|!r6V#qvGnG4dR_j%1#gXh#q8*3BIRz zF_J*G3&`bp#Ur~&J=leIAejFzlg_*h6cx}kK)NsZh@1DXziH*QIpPQZYG1z0Xx_7SUj)kQoh1vqNk79)surBqlo*YHq+o{ zG3UGqOiM-n?Zz~jGM>y#lekN-^EQr?a-OW72h4o)JJ>_Wh5f`e@-WxfWOp`ZO{639 zC==a$Cs)3zWTxIqbD2joqvi`!YUVSyE(D$?1b0dX@OliMwR@<8yW>3DNJn>yYYzEc z?OZA>>qq8+>`R-V(HphzAY5e&n5#X3|5tz+!bty0?~I{?oR0|`W1XXz%aIY*=`vYo zJ($mtg`ZJijP)pl3xhYB@Y*3HplxB!U<_Q9!&w>~Z*iC863`vzr8ixac_9VS8((&Q z$I;dbU2+6J)8MH-ayyO3=LwpJpZrw9H#5m?bw6``a8+iujNm@(5=eANVtPv~d!j#C z4uilKm0+UYlAyZ;F8h~zENs*`*s1z(-9wobIfH3-J<(!U^3)?4;y$@%_rbK+6RNy#T5vrb~0`L z%St%21Ni60GS@H^9YHJVo-t@Ar;>}d5C_sa_VjwHqXDE%G{b$QL*IO!H^sh^~j=F=h)}??JzPYCE$OKt^z8` zw2e-8GD!@bqGF(8cX$0JcK6!d-Cfw-Eq05!7NXJ&J>8xEbq`0DMHpx1i}!hNdZO|8 zAEENd$FUmYF?b@N;beJDo~GWG=yB-^jpr0}yo7lz@Nu=pt6G3F=M+>Vy`UD~h~^s# zGQoXbD&DE-Ko7T~i{ua*} z{R82udIdjbCHRuIU_~~8|38jY!Hq~Bxd&a&1Ehew#dnUPqwgbd!O^%nlfiA=hF8;% zJcb8Qi%$LT9B+j4JONjp0?kBaNCta=Y_Jla&-WFRX$Z6e<#23Pq0dkZO~6%5iEbay z@AbvvrxTF59DysdK6bcgan@E0~*{@l;|wn2_|S7?l8M?ub7WJ$p@sD7?EGXf#+)y{?tSE!*J}| zbFq8pLK~D1=c@@RlqPh(7NOtj4AQ)gq1$jicJ-IwfpucXes*w z^;SzXqzFh6=v3K?4z*O0h1dXi>U?}R8QKeDl2C`p6sR2#GVJT83iYiA3lE(Z4B1$aZs{WAZ?cfogE1%Ryx+q5UlNq z^kztzilTF{j$g)4yaa=5!t~mT-VrRjpO#8bMxqxLebZgR?{0yM=nH=0EYQtySYv0w zxnxC}Ksge%IOH z#swg3G$I8ppR8ISGIEqt7A)y2Coi zBxr`T0w($?f1n5GG*Gz>I48EkjWix=!K28JJ%%%;0=ud+&?|1mxjYd+wFOMU0bqG6 zkPr0~XLcr1ou|Q-R7I4)N%YTOO(8Tn9ni3Cha>$3lr|DL+g}06%Lgi#gEXae!0o1@ zZ;yqUB^2tP=9pXN!)3G-I-f9P%xaLyY6*5lzHBz!L`uvmQ*jmF#FVles-FeOY`u)X zNhjRE_dW#UpyAmE&0=fdfmzV-7@!VH!ZltCb-M`n1S#P(QdIjw4|Ey54a>ptt^m6` z6`82bktXvUYy5ZcEvw-odVy4%vq12=1W17|{#Y==WTdoO0}b%|e_KqKAQOBe+U5=; zAFv7tA_Gkk75H2lG~oGN!rdk`s0~aDZbk!iiy#p^GXeUejNn(`hkSf4M_TzW+>cja z<~iVxf&+d!R?cqF435Tq^dIcdM_`2>f)!H%#bG7Z>5a&XS9yc*;{Jh;$plp4g;xlV zZY3JuenM+y_O^y@Oyey?Z(1A>20wmEi`72bw++8@6jRA<%mVw64_XKp0^d6uI=GIW ztyt>_9vRxiK6!q5{sr%~1CHxsQ27l7yEXw1@>TA0NKoB^9Hwx5{N;z+!v_)Vn&fN_ z7InIFq%++)-}xMwpVbbE^AGsEFF>o-0pt1L_zc%dtg|hYd*2!hf>?uh5NWHW<%+hNHs%6z-MB_8a!8K=b2Nq zh3u6#&`tgerBV^xvl?*SVknt9fxAio_q`hYRS{OZcJ{O(`p=^=``T1&+uqgc-l*$ zHGK#7)ghplHOLU%i(Jwo+7qyv&ybAT8pz8&=nhu{&E!Dk*&4dS=|EoQ!nv~y%(9Vg zhW4@&3PQLypjRX^)Nr}D!DpWaziWY7uqia0SAmjp!KO}y2JA>XkGa{z7bxW*7QiUO!TB@Kx?=R z$kbF!m;Zp5mC$EFujq$==ngcXkLVus*{SF=7*BwUd7=B9%=iP%=>@oL*26*76(iUe zpldSb0_Iz|u9gBVdkPG86|k@^`065~ALi4CaQ`%B)MZ@7WA~+Z23y<`o}O3e@mmRw z`6b?`uXwH;x(6zO#z;_o1}r!LFU(x1Dibj|i|O6*dtqq7_#a-!LG=yK=mib!gA{1|>mbyxMc%);}RrYd&&X z|Mv0SLDJTA^x6%_dv_9u+;8AyNAaAR(`(Un_m!56iKnmqhjz%+d^k{JLM4*Q^aBO8@_!IF+ znK&-3;riK!^W_29@J)DD$B}&10Z9hEX(>R+8{oOkLLTiHBsj2;KX3tAy5G=}Q4E!G zZ+ysU$B?*o6AZZpXXBgy&dM|3%8%iiE5v!3jrqJQtqm;|DGUv9wp38XP#`nm)H7ph ze+jh)6&-(b@zp~3`J7ZK?JH6oUPJ#ppZc8I3)jPOFa-NC-Ty%HRWVeROYk>MsWj{< z+9Oe6EWVO}S%{?Cu*X{tmVF9-f=OKpeM&y~1t#?&T#9#aCBKL2Wj3zd4PYwrvGbaZ z>$)rQ@{XY~GlKdOsOBuRX_nx+?Sg&cQ?z6>fWD;{l?pBETuL5g6cTKW(5@CDK)WShf@f4BNxKD1uo%Rs1mPkgffB<<1 z1!%t+4V8W~q+7K|O2}{Ad-LJ`oP&?eq-*F5eTG({bg(sLq@iGDBH?k`jdYWG*q2Di zwP>vCg0|7W4RKMpr*^<+1Ma*x(E*oICD{D4NLAg6hU>9dRh9%l2UlU;aRnrp-fO^2Wnn)5g0z7ubiXYR>XC}I z60Hh*fgdCQw=6@K_bBwS_J=1n7JOB5FgobOiggHRs2xaVz`qb!#8Axi7ohv>2@JCl z+_VuuK8@hSl;}Um05(?b?+YbBhCdgK+%sT{P0)tT_P>PYs}tC~=0FwyVvT))Q)WO3 zFc@vieypjH`2J0Q`+&v2z&{eofCtFbG9o8yKe7#$VSP&z z{FYXmz1fI({ z;ITD8{<4wNvkqF=sew|Uq>0FBUk$xkE2MXp2WH@%+>5lH(U<|YBKM&q)Mu06Lg|lG z+rNqIe&}f#fk}A%rM|QNrAP?fgLlpa^mPU>qm9UlKMMTmCc2>u@QkiOM_c9X2`)4d zoa!N9g@53YjzU7hQM6!RKrYfDV5H@qZr(I62NTvOv@*Yiy0!^4x5K=7Nb2eeq^&J* z+FEZrxUhx3RhYqg!lk80&-4O(y%T6%J7l1$J%`Ysy~Df6bIIKTkD>z0n}qI(*62-M zg1+Q@_dH;HQsgilLS|Qg&kdx33E&Kyip+@i$P@EmFDAyE^%|(=B>2C-VP6;KJB42E zD@fcg2gW%E@9b@4^568@yyGy1O+tHDclgRefF>UYel`Si*dAn^g@fbe`ip%X(0x1@ zOl=l^XC`v`Gq6A00DSN$5_x0s=LOWq4gLAPQ*aIq0&>kjj<6a#*&;NMk43hAeBd?S zOEug+A=vFs0-AdVXIVKA@R8`_I0)}v0$e`V|6`obfDQfwSKA%r5vO6!y9e}+52eaZ z|2ZIU0|Qra2KwQyi$k9A>OciB^!L6_@Fhk6msEV+{}4!GWB)~*K^CMl-3YwG*?Ah> z-+OSDWMStWADDyP?nY#FNRjt{pMQ`FH5cdG z9Xz+?f$sR%HsZ|72cO}^>mnn2WDa_oyCHGJ9^eLxkWbex_&eAMX?UlCThRNigp=qB zX4Mb4OU%Z-;U|!U>(Kk04kkcP)R>Ts=H@Uo-iWaG7Z51$4P_7&#MXq5$cpL=PtH}` zvyLFmDH|So4M7Q>W-^kZZXiV^kMJCfw-gM`48mBXQ;Ug*fPg$CBqC|$J=hx#)J`c# zx4w=0>=~q6SAzeZ47M8x4=DmUHg8B0BnfULU4z5uJM@=lNe9s#6i=FkuA&$uKEdUQ z^p`B;*3QB7dW$q28LQ3U#&1iE#c$0;F4O|J=@q0!#3jgeX z;ST=*%CMK@KA0;_Ku(v!um1~d`yIHN7Q!vygI4q?vSq8uiNLc`{!88&jG40>xjZXL z-N^(>F4(Y%n6EDb`$Q%Hn$$)ix2N@giMLbno28glTOcc{Ik^euXCoN%qHc;}OqbHOxkr^is+QV30q-_??5VA|9<($w(E7hR1&h?J@8=D%?a!U&TuO z4<@`Bv_4pY|3SO7oHhkh%Sx=xb{gKNg;>@9rSfSM%n?5@wf4iBKb;ze z2|xiRF^w8eagn)b>YYLjz*SL9-hs5*MOaP8K#8ypPOJOCR<)R|44Nv`mNZ*4GcqZ{FbiTK6%#=tStB)k2Wmr3ZVl@>KKLdYDgXgY263c+1 z6FMRXCpYV6EJY9oJsM2Y7b7BbQ7Amw0JL0E*e_GIEz!V6p_?U64r1L)o%Ab&Z)=spEiK{a7L&PN3y z8h?`qbT9>(@2Nlxb%ZyBK4^^VPMQGq-wa|pu=GOg$BvNxN8Ui1gEROF^tUGXOZ=qv zly=C}O+l;g`2Um|{it6sld&-Mb*G=Ol2!Fz;EcU_Iht(rxT`KO)Kj@DeFLtGchm#eKP<-1A{{DQE&V4Le+gq3J%mAr79@j3 zU^?g*8B`v^ptlm&&XxT*0eyB6nvoEn}oJE`$oN1i#>@4;l&J6A?&Rh0j_Cn4` z&P?`a)@t@X&UnrzwwryF^OJLtbA$a8N{P4ZbnaTtV3wLOgIUAMXXmmRY#w_%+-^Qb zd*%q%1lASieQ0;5W8ZKOsR-+#{EefZr_MnpEQN9qZPiX_%Uh9C;e!835dCc-Cfoy9 z@(!5nYp|RSq@!H-|G?B#0Y*}eyr@RV{_F=`>lHClxFxVAbsI1AAKvm1Vs z`tX}f1v=HuITo!+&5`W>4#*S}uH;apJN`g}?MP%%9tVz;3(of{90dSXhGwPbp^g52Ur=3!+F09Phrp#mwK9OoDntHy6g-AQC~45k$>3?&hW?RL8~2dUyLa`&B-Yw+z0Uo4Av$b+g?p_}m7$qMetKTGbahsMB41 zX9+Z`&!Jk~0X^|N#|8UI^fDK~-xLQYQkiwRm1&z{yJKx^8Ef%cr`t-bD=Z7m_sn$5 zNXt5l&b-%5wPac(mc!=O<_qS}mI}*pi_db)I?1}e+T#^DO^b;;p&X zYCQHrYb)y;YbLPpc~+(6rd4hqVee~OZ>_MNf*u z81Eo>ff(@k4}^#Rss9&LoF?B`|4?{z+Q8ehKQKAC3w&8OFia%`HBk;tkOU3wi^&}* zkD+($NUj5JH-mhX)SjG$?XGpAwUc!QKDt;fc<<|(r<&`VpP0O+ zspdUqmU*J-x$(L2tufwo!ZgzKk5QwqXBcR#FfK7JG%PiAG+sA$GTt`4H@r7)H7QMg z(-@P*c+L2)DbaGy!m|7_Da?<}4K0%`TP!*Y46N20mKhd}WvbPVqx9NJfdaf4{!F)N zZ2v%6ooic*md7-f#%flO5q6=So6? zZ!V7ILT8j~BfOU_+@qoQ$U+CnM5KHDRc=_I5w7w4i{y{#$ZCl6B}3Ve=)VAU(G9=L ze+{`>7vTsP1gF+m>?3wylA<8*HJNw{9H@&p51kJzbS<G~hSY>S0&M&^awz8KH#nYyiE9Y4#37i)TM>5w zAD1KD;4nJsi-0(EMxw42O5bzX*=AtJzSH~M-3a<-cVAhA}O1{psX7n>TIb{kh4_ZrU_KN{Vp=jKGS!)P;pFqN4I#(Da= z`n`sR#y5sGhD!#T>79vXzG}K*oN06zjiyvH&D7d>8$Wx%+!g1+JTn;`>NhQA<_vQy z^JjCWCD(csY*@JUtaSl=oODNt?VD}4gYMemoa1<4@9Aja9O+7P*CI{up0g=3I|!a; zxQDa62IRGbxCgp+x|`tM_yRuJ63=Grx0BFTG7!5?Gc-#E+%wO4vwUr!YtbW>{I8yD zb%2j9%`bt+XrWsbco}R%sKoBAl<)z1k3q=bT#mgQ+d(OPz)muLnqC?SrIlxZnZgYo-t; z;@(Ll5QF!DAaS9CTj5{o<9LtYJnW6>FT>N`o#yK8`s$j6?n))vX2RjzErBjR-ErE! z*=~h1GZPtsW55sAwcl_MoZszkTVo^$${bVd+rc3^!07F^O@`MZ)&8%o#OAaYI>tIC z*w@(!&JoDGJBSp;hHz5eglEL=Ohdx?GJFr9u{w?h z%H>G7+XC#PIaG1u{gS{tAXYE^t)cHLf!>y%_qAlU7#Zgg$qB8b`TBCUFhS0@jqcS zX1;)amxe^G)yz9=Gv_4dIQumF5T}fr%bU;NELbM!%^%2%05L(C*VSE03ZY6grKVGniujaksCkn3#|0gUGNQ74eC-`x^I^6G^ zSZ+UV7`Hq34EHv7F=quP(m~7~jCkZaOriAzQ&52%-(BE8(x}ia!G(OFNA(ulFznYp4 z84FGK6KJw0v!dCP@$PNmR&#gqoIE8@&%48)DrALt1$w@k&k>r1-9qY%mWyvn_DTOp zhf4QK<7F;s2Wd~q8F7@DEzS}bN_NY5q1mB}!;XZ@Bie@#3zf;;Qne&TGFogDm59nk zvEm!z9g+!>_u?Jmj^cWv1;U`K0 zgO|Yk4h~`&T#(ynDs+qAqg;o&<`{aD+Yp67cl5{;e}@dKLqMMR*e@A@PEA9Tv3WYC0qzG^WQIw1Lzjrvg$Dkma z3pLSn%4>=bUzfwF+K!eBzU@79(Ql~X^wx}#bPbhD)6#9sHLOyo*|)MXINvxUIBVD^ z*&jG{dFyyLaJ5)?a=|6xND)VzEy@wmCFdorC3zx`@S=bzh!Z{*_6jkI_|l&;kNkJ& zh0x9NO){PIhoq@^Sjb@ES3#9NJ`$yuo|G0b3(aKHY!EdlmEdHct@O zOPo6kU0FYobFdV>0WXnTdk=lAm*GfHK)UQTxc*zfg~fB;g0JY3V-S4jH?a#ya^6KA z>|VRiKG(6+(d4f}1^EY)(Edt*@-r6xpr`0mNXDFA;#urrK&h(s{D7vQCs5;fV4ZpX zAntTmk%06SDyFDl)4)XNhYp}0t|xYC>F{R9K||CHDVmkwF6P6JcmOC#Q*=`|C7gf{ z@f@0|Z-K421@7S?cBV$8PLQE0D!@!}6FkLzs330#=R>pDAM5@?WFi&98@Ua;`;;Opph=qd7VYd)n>5mk)Tac#N1x z{7}_&LRRh#X zFEspXNHgXdkC1aZJlg>>oK_SdLUWB zV%IpDJ878M6VS^>f(vpYvLX1`#rvIdoEHn-yFEH&w+1}#JdKgc(c60%?Q+eb7;foN zxtU-9{&g>KH-*cqg(nYbXgA@&EOgt^<1p1N0MfF^-NrQ?=g>%a&Stwhxzd3Y%s|uH zEYA$2d(1(H!#;1QZw8u2)Sg1`FWdvH&}y9rbjL$GK{$5kKZzf}jV&f!Bp2e!+)vE_ zpWcdAN{eGS8LOCEShqQ8ydZZcR-Rex8?1?7jmzM*P&0)bJNE`(D(o-R3;GCW2}cN$ zcmhrn_7kLOX2QXp&L{(ud;qzRx$r7*JyP`d=jn!V&Ox4^|>r}a_H>zS) znwq0dz{dvl1+`hVSaqv5P}8U8T1{&0-P)4c7}ZAALiH+5wz@_YqHd`$5xW?~>2xC*@QDZApXY(Qp!K$;)vr+B6kw~nDS96TLjeWX( zfc?1re{ddJkR8$xIw20ao8LkebRLSd7sz{#bKJGpM|Q?F+a%jfYpS&mQZ9DFBdW6= zwtlzfS;@9MYkzB$HOKPYVlZDatIc&SYb>eOa%+|?3+k#^yWW1+@ftqay{?bw^;nCH zm4i_G7(9E>j@JxLIB&rij3Nvr5uwbT3h(qY+I9HiRSYqU!e(*$ai8%-f(rtNAfKPh zd(MmGeP{EL!!(pbZN2R^P~gqL4mRF; z&N9mU(|E+lGj7t$b+y|0x+^-rHdWhI8>3Zd4{8GHm+E5mVU1UFU%ObBtvjt-p{t`? zuDz_;ttryfXx3=k=-%o|b=&n!!xO_t<4@xpW2$MS*=7!!7g`dnKdlwEx5#on1@G!m zhu3bx%zoJsaEx;9aea0l@HFtgL?cfw*tXVK^F|>bZUCn9a%9Pkg{#nnmXMQh9N3{Q zQi9Fu3Up%?u7)JDb@i`72)0fcPt%hFCZ7;y< zw%Kj??KA&eWEBu|vLkZ~p5M4d%dBB^AjbcFm{=)v$UipPa6E;-`7&}@GHr{jpDjh^!RBA4LQ|;e zo$-h9wPC8kqi=5*Z6uk#n@Y__OJ|!NJGP@p+I|gP;Z|Ey`+KCBFLUsn4re+%s}IqN zS>d_lT>%=Y!hagFrZaK}pq?z|4(l2^W`{`+W@`W{RMK4c~?Lpqd&Cd+cbJ>BtanzKP*2h zO_!b!PY8J`bO_H0X7FS90p3*pV*V|ji^u1?I3rlcnROWtDN~8z!S6uSE_sh)lAyc& zP`zFQ?r{eRNBtbfLk8A2WF6)a8zU+99N4S>Av5eJJ(0PZ^_=~i zP3F)!*WvG5!d9>ku6Vh5! zzmw0AzJde10uNXgGQJ{*B1cRS~JcipuE%6qT!)9Z3YDU{ZM}!>vAZHive*%qgg6Og&Q}#u^ zE38ArlTZ{-iHmuY4(4EtYwM^|lniE)!a?}GgcQu2wEp(94rl$ikx@J3l$O0&|9k+I{5Y2_gY-4}p z9K$~SZe5A?gtkz7R9i<&*G|>6)YjJx)8*+z`c-;9R?Gp0Lx$$YZKmz!y_PWBO1s5z z)m80Y=w0bM@Bb8F6X?Vqq{);-c49|zyYhPq&xgzt-m?PmPdAHjmcC zw2KSIyAqknKxrP=3sPf)U~Mm z$X1H9@JI4oNoR4i@B;S`JD*WT4JUUesQh8xGFMyY1$#P@tqQDvfb-0;{4lRI6&N<^ zb=o*hhFYKsuhCSkuiR1|rwmh$E<0V?x-_=rS#h0`r6uo6>nazQzpf~+%Bmfu-mU4S z{ieOHv+0`|P8y~fw-}q7#+V0K##zQ%#h3vEu08Hso+-Y#KzqVCQjk2H`jnQvt)8H~SDsu`%qraXa1`m^ zsLv^F$jHnLmIf026TCCssjeJnGsijG7Hg=bzj?GtYuJuC$fy0KZJ=AH-KA-u?xA{J ztF0-m{!-PpN>Vkxl2-YpvQ5?6s@UpoH7{yq>cJW{O0kUkW5$N&(bh@!7S3+&O79`f-{~>MyFUs&LiST3&5@ZC;I|id$7t zIlp2;`Tp`8We4TsvfCwRi{pz`g~Gz_1?T>3{=M<{&fi79`T6?`x)%*9U8Ou$@vFL> z>Zf{!_MpDRAT?Rd{j9~dJIGl30tD}wJ07}bI@pd6{?Wm?$bo%84x_fGcVSwYvFufx z)7&Y%hrE-#e|YuyWBF-(8}ATL#qGiE$SGm1VjiS3Xl`WC%z|QU0Vxx?FL&V^nTgcE z>Ha3ZPF}XB6j#qYTR%$!^A6)_eYrMUQ>vO$6J0&NvY`B&vS-<&(pRO=OK+4OF5OuA zp_EcqzbvL~YFQs;nzEJhxbledRQbM&t(B{)npRJ!*;e~jm8u!2Ez_+w95flMNIY>r z@ap_+2|1*1l#R4GjB4gOww%{cFi^Nqv{5ot79aLJypf`P6c_iryYUMWcO?f?w5dz$ zKTkW=uv7Y&CXbumZ922b$n@Gqm(!B!PD=g~KOmM9Jy_8-d|cQypcrDQP+}9k3P~6C z7X)~%c*&ec%(aZY)IFq*g!aA%uIu*B<|q1#YG?KJiqmDh()UH7g;@nN^1uJt_DAyj z*{?6ZhW&o{`|I!feU4RzG!XX@xm>I$%W$!cNSbJa29+hEG#}-N-AGm zd7}Ee>X3G@zLV*$WuZOORpPni&kM$qYRRpsW039nkiHuF&nEDUYmqS86W34$y8)tC z25}#7$vh&jfG6ii@h|Y-@m>5%{x*Ile>zXWz07{j9LhLF%b+Mop@eGzukX5t;C|*b z**Uh8m`9%(!wfO{5Zx^8Q;kNwL%l$C4Oh&`>IGG{iVo$qWqnJVmt+>(3lA2qFU&5y zU-+i5XHl!-yyBFS7NxVwT9hBH7*ge`UQp{$S85CO6~?jVUDi1JGe@zj6}Yoeej33{ zl2Cur6|6AMKHfutGUTOrlC-0|G_)|heWX9?M$FZ?(1f*#A<0csPS&YgcUQf$^>3zC zH@M!YY5MghnN6dcUCsz=zBXf7)713d4Ug6PopL$hOH5hhlkjSJ16jG`sHkPgJ%OG# zj~iqUWOZcRLEqI~vLCvM+y3s}3|BvUzNMQ{qbaCuQ#Dr^R!S^BUC<=|-kYFa%jZtyPU4Q>-sOyBcVO9&rW&NqC5wnj!8g8P9+~Tyont+0 zK4+Y*&(Sv3WU8`jmQ~BEI#jN$*jt{b+@_qRY@i&VT&e7?yi*oY_Mvn`>5kHCCFe>C zN}86gDb?ZV3{Ym4AFBwf>R2tPJ*28uzt;ZN&omvhWZQZ<%iZ;TO#-(F`6L?ELMvqO z*_XL?{;-gB;*Qe#@?W8g!$&A~M%In0j-tnGiER-dnGlkgl(aILQD;`_wYvT4&8oj4 zZFd7^Lsp~4jbAn1n%`z$ez})t}cle=T@u<{Un|vXv{-80waU= z6isyx3Bv=`;09iMcDPTYLw%QHt?hz^V*Y42q-&&kUo)+0S$Vh8DR? z&ol2gf4|@d!gW$DhirtX#%9S{LdD$^~+N(t0@fzY%^0-viSlsS>e6W?Z;KZf_b6Uf{Ia-5Nt@F0(vf8p<#Xk( z^1T(?D`!;=s3z2`tZ82Bsy(bSs6S|;bwc1Ma^q#wO^d>2wu@b%o`t@(fqjG$(rBul zZeb4M+~GL|BSo2#BeFlCA>lnE1}c6ix<_`8oE>>BG9hYn)Z3`H(aU4PW94xn@r;B9 zi6Kd?l4&XSl!mGKsqgEKucxo4uD>fylU9{BB~4RrS?b1QRs56a>)~yrONCKfK64tC zMcN;n=>Op@asTB~huibvnYwQsV~wzOu`tZ5O#6*n4TE%7H5Y3c)gLR4mklaOD6-~r z{@8xK$&1KqlxO_O%Twb>{rQtp$S!GHrmi?pGhe+%H`e&ZyvWuT*^*NDf0Cg5{N}yr zn}?nNUEp&tgLsCdLmFTem4Vh10c$_|0k@D(3^^vENaRwE?6u4-Bg)HU=VWm*sdTp3 z5F!=!;A=TYSTe>*>O^uZkrPbzOMO&tF&a!MuJO+6U|tmVm)572*5;tGx$(2Ufv!fg zR{f!NNX?U~l8W^5UdnN0lS+S-)D~wH^NU{;-6)z{L@F9lw6nOVL{|1vnOd>F@_W_F znr5nD>aLntZ5>@beH;B%{b9pu<7U$ZbFC%K*2mu7F~|ACwZ~J}r}cY+cZdb#@6*YB3Lx&F1f(RBtTwvLr4Cdw{^9N?+GGsVJfp z4k`GPAD&j=hw%?s^KT@H6+?>yfvXrBqliMR^> z+Zgx?XXO_(dR`|iAO?x2Ne1#+>Qeex=0^5c?jn94;ogwPqCLPVZ;MmKM@7#Q66CM$@*`o_bS|it#ykfVi0Kp=_p| zWb}Y0Dx7dPACsyG!Dwh0^V^a@hgdT-i!lS6L@nnrwxvLbhApG<0s*+VBk# zF_EXDtkG35uQ1V$2!x_RjN+FV@ zgkSy`-(*iqH^tT1xd^#m`{BDwg3t0dxWGsDA@-9vVhJXzeyR4V>R7eC;#qk`+1S$b zQcp=pNxzb@B_B$ZrKw6|`MD}*?N*K0P-xD#)i@`6hWXM1_n-v)NSKP=)LLXXbtg7O zbI5Wykc-jZBq5Kd^rx<(Enz%k&EU-ERSG_Z3>43le3Pt~Qe+FINz&hvjpAFPeIdsL zvw2H60cJJr4y8S5QE(EpPPy*quJ2IaTyc(ou4)=|IV~Ly+Y_ij?pvChON~Cm5B(tB z3C#iZN>zi}lht>tiYwPwPOVI-EU4^Q^|)$gby`h&Em74~{YKqOGf~@LcSe5$`@3GI z7iOO28#DtE@I@BDQ#lq%XyL$rt6lXxjl4=CL-I+XwV}Pj=wVaB#)LUTKZO1-^mgdCP*>=Ou!rG0BQ_~sM&66& z$A-iS;+rOnNqm^JKe5f5py01tU4%ihaq#Z6|n(a+8v2j zG)8*K&nPPD4*G58Z8njo67&{TON4S#*qHG15nRR92!D7~cxh<0Y^H=FdMyz0cw#ynZ+74`VH>G2rIJdATGPbhaq4r2^R&D@;BdzYJc4OL zx9~tnYtd2hRmnZ+A=w)Fw9xRdgJDa<_k|n7`$lYt7@-I%8b)PA3u1C(@?yF1iSdE> z;|T=`brNqRHcqNc`kMSGWo8|3otio=>O>}YNl?eMi!782MNIxNwvRppx#(+1cZhr_ zFU|xDpge1dY#Ft0pZ5k@Bzt-Wx-K}Q?Z>U*=Cy`lx=HG+8c$`QysefRxte<7NRX9sJ66|99W{*iwm)b;}dZJ-Il zp&@_5oUxA4g5~B!2quJd5zm&6mXky8%8$wK$&#f>5{sxWN*@Zj57`vvaGHypLkt9^ z=;I*!x}e!4(pv@Rx5|^}9qfIIoSC2QBd*B~yyMnircs7;T@y{9_I7o2)uM`i%=8*+5S{*%o})jc%hkDo zsH`*gGB>xf?Q5Oq-PgTm{ojH-5{(i^-G;uj^TR64x#MYeH<&iR2Y^*s14J{7E|#h_NM#mGWp&D*r0G0i%F=k=%u}fuIV^1sm1W zcfzyRGvB+z^8`tLbv^IVa{ko0)3MtYW*KD^>c?oZYIar4t~jammdz-pqOh7#`!JRDz;HnR&oKds!qk?4NcL-alNx5T|t zOsXLFrzx0O?C!iFf*ZmZ5lvhoN*3jWa6?uL3U~v#BiId@9Qtd@Y|<*ixWE}-9j^u% zGr8!ayoZjS&2UDafya3X?v0<&8ChmMV%}(cudmg_sgi1(m;~1=mzTXQ?NHjbG_Fij z_E4Eq-ltMqWvcF6`$N@IeN_EbvsHUp`$((TuGIDhY9Y~3HAMA6)m+s>l~?siJxIG& zH`kDA-ef&zU+#S9ws=STa{_4u3RL9bqFJiJ2R)U z2XNa$GhqqoD`rT_B#opKqywefq@$$6q;I4=*%7H%I!4w^_F1-GzA1E3*w*l1#P~>8 z)V7!|amfj-lTIhEPSGR}NX(84kM0<5kW3crX6Mpbl-tCz;11|tefOlr###|2jy8j6zL~*rjWP33Bf5u4S6v&g)y8x zo;#ZVSU6g|MA|@>D{U^DCk=?LA(a9;{|zUP^^|cCT<0Is4}u`5gRc5H?t&yJI{Wz! z;9TIrQ#}cdv7gX|q_WSos!eC~k2Moj{c2uU$}3#T-evtu(@Lk6e!~vxpmJ4(penTb zKut@PQ{`7*(X7*M(Z*|uTA^lz`n&3GZEcOTrb&&WhF{aW=0eT2+WP8B&3B#Fu-`1S zZFU@Xo%XEvmHJBq2a${M4(j|>1R2x|?U00VB*=rlPfa9}Gb!U}I~Yyb!?`j68;t!a zX`Fn&e7$^^JSclAqs#s$t&g?2mGpyTlk}Kut9(Z2vamhj%7{UcVbKd>h;cdb(-IFQ zYf~!gXi|A~GLi)G=E$R=`5|STYxMi%1H>aivwu16Rtf%Qfql^9u7@{l6|_DfK}n#* zNAS7aYNyrS#+qi*={l>QRS&HAT~=Dsyu@DIy2Ma=L%E_Nx5{0kSC{IBnF_2g9Nj$8 z{-i(*++!cmAw=}0dB3@B$Yatw2f8-9FL+pfQ}8ZHO{Fjk*i>GUuvTUqz2#j6xn=5BO}^j4AZ>G!x|t zuD%h0Ro;fK4fc%|FRrjt+M${;>YJ)KFrC}gA)1fc7Wy3qvT2_Arlqx2j2-|v5?>{@ z7^}z3Gw(5;Fg(`3(OuB>(off0^m&HfrqP!DHkOm<4tUCZ!-74C!%0O@;O?PHXfb#m z9;D?Y!ox71)t9m&NJ5(6ONy0#hqG09O;F80D4P`ekGwFfb7bG>$S7_!EoNEdQ$_E{ zYYI)+=+Mn!;&6B9nD8!&!HV;dG0_WS^5S@j#AI*Ei#p2G7MSKQrqwi1HawT6PMwwf zHfBxOJV_TpKh`?B0V;|`l;h;ptfpVg9?$QVOkLA#9{p>eduv|`#uv@yCFKY_8oLQc;B>TeQ@$O+u@EOqX)oisNz zlJ#db{ndr4gQ_|zZEX@5>4ln;+8SMm;fyh8$~L#Q2(06*<(L3tERReRP4|sG3|;kA zx?-JQ=hjP&6{d-neCthnu2b)N>>20F@&^LF39aFS*Z?n5404a=qy2uBZVoVn_`cox2(TBPd+NNOXx~DQ@%x}0>*bk;+M>nhR7bu z&dNW8HVIBb!Hws#zh#F`OPk4`F1$^l~>EZcu z{K_DU{E+IRw`CP@G`!0KLC6fzAhAe%R}>zJKg%&RL3CJ7OB{^c~xOmYbsw2aO#h<$JU zvx51ABzWOYA_L(O{6B4I0(h-Iz<=F}_KT{dLfb?g34hit>PXrI`e25b`GD1&)114P z=i_G!ABH>;Efqf&-xjYGXJS9nQ{TFA=GZr$?< z893)xJFeUBKpS6z9n3=OI3PI99YfGzxWLl@&gpCDJQ_y0L`a3Fr6oLh%?VF}FW^Jk z@0$b8>4I~aBgX#8YOs8?s4WGSBjCH*!MhNNobiuvAzXsW{SJH(O)Z6{lg1r}WW8Sd zNb^bEMmt$kcOSk+g3UA%U=^f~w?uagdF31)023tdq z`wC9H2E^&eQym4b?pi`G==ChvVc)=O%f?P13o42$?n`iQR3iQ7E7U*5u12o+$hz7N z-#`jJ#K^Onwc&iKyo+px-T4}7M>pfga6-HdW$u3>~B&5*CZu0N<>qj%}&7{E84No<{VH_!d4kJQ!vP+Z~n~_9gUU z=-5zh=w3NRzEQ@P^_KRQ{3GrqS{<@XSS}bOsLNl(({oqC9eADHo}I~>$s~be`bZrN zy=ya4C@~Y7$rWh*8WP9`5|Iqu^jps!_c7NlXM`iv-o^IVsN@I&==t$Le1KOD{1THnuS(ncth&SuR?ZTCP~uTKZcA zmd|F5xyszZa^14a+7UYZ(dcXqBDd-@`l#H{lzN;#XE$Vv3em+i%rh98dY(UsboJUm zL+F8bzzgsKX?INMJhO@Okt%eWQcEqM71O^n4l!FH@i~gUkj>%j=0tH7JUdSyXe#U) z@(OprY0@^bL?8k)Lj$2*!j^^QhDpNfg-gR{gulSVm8m!bt&BIaN7SUK?5MP8X>>tU zYSjD4tjMrP6;R^diWEhxLZ*mTs3PJc=7k>#+aKBuf&CSdhvLbi_z;q?lOTsr4D|gZ@={>d(%8IZl>$LVenpPUJMy%|FR8**w%V<=t81UuUawWCma5LG#Oj6WZ|VxQUj149TYXNQrCz1pr@oshNIqK=xZ-=1Nk(5i!c&1_B)9VBW)4ewVqM!O8!k^&L@PfjU#QlqM`+4xd+Q2xgh3k=8QeM~8?)=bL;D!V z8D|)K85PE+#u3K1#!{xfrcLNZv=1u?8xY<++>5Bv9yTv5*EGYVG@UoDHBK^)HBK>B zHQo!|9=b8q5)ugJgMJuR>r=G1HI+3B)TLB=l}(ku6_?>{u2IYcP3Mkupu`0imZjuk zVupA`$mUza>!E}jih1_Ca73FE$n$^lokPvF2A-ci?kv|n*G^X(R~1(s+|{F92G?Gw zyH-QZRbXpvGh36bpNgDCMr(KLB&%rcYdd7SVry#KYO8EJWz8;XTvVn=Ta;-@C_00u z=Z3YK?X9hWy)5QC^{BmXvAwmfw~n*+vd*`rTKC#6+IKjrIK9q_nBUF76Eof=as9#* zwb*HQR&~8`9dkD;PV)Tl^o1KARbl{FZaCN?VcyT4H>kF+fj+aQ?}hJ`e^Ve1e!-EL z0-T9St6^Xz?1M|jYd%>>7Do^Z$$iu!_+Nbi`LRe+9;}=ba7vo0)=M;cT?PlJX8pAQ}z5)-;B^h;<;Xd~k$<9%bM(P5lvnq%5(dS*%tdlXg_mJr?@ z@qVT7is6p1cVXwkwuaRUdt%ySQkgj8d*e`Cp+=$qgvde?gZ+jK{axKs?KsU!wSeiZ zU&=|!GhoGAz!;h+TO{2l84lj77kb^fM1SxPXF@5m4Q|c#*w=yKa3Hzndj{2UC2t!~ zZ1D{D2iG)LD;Mo5aPD(1bRKffa*lFJobMeBbX*d|1xe^|Jh1M@r_9#Tw%oSMHXQf= z99tKg(>l*u7IQ2uir!j2TQV$yWo^+wD`lH$`v=$XvF)~PluczT#BA<5>o03*TNB$+ zTOWIC$8$$F^fr_wG5!$M{Q~H=7YtYZ&gCQeB&7r44LygOfJB>?>wT<75 zi%rAA3d5F$JHn?#{D^QPg8v&aFCsqT9iF1pu&!Yf!t`OsQIGm$x@3A`DsLKT9Emw5 zA?UGTt3Fcq9s5lm)hgvC#VkdMT!h!xIXKSWV7k&u>L-yXvit$=I$Q)p1Mi^>uLK=y zxNi;oDMxu*c%1GxP@x)~>2{ZG1ZFoMV0Rd6O~Tx|zo=@_FH1+vO+PEN7d$EGgsRS# z{MY$>z9m1QU}QmjK_>ooE`LUTVP5$>G1riHCwE@%@!aIRxBrj24;C~l6bmjEcE#Lv zBlFopPhpaIjrpZH!#vi)TfP=8v#M=%ZL@4MZ0BtcY{zV?Y$t5b5Mgw;d+fU%9~~-a z@75!Zh;lb^zjS-u`EFIQqWExeN%3+-ub04Xd0n#2*WT{{AuU+KUIi^ z@*tfk!Svr9dLa`DhpeH}j!@S#m`taX@0E2_cGY)vytb^)r3*IH3o>AG)*q5!Y;GdL zo`XuA}cjpt&CC?-{ zYkEK*+Jq=w?>^-sT;rViP(R+XAGhCw{&5a8tGV_<`$hX|duPn1kFlMwwzhsLYEVQY zLbzH~$GX!RZ~J2FXFp}nw!gRA?CH=1{ z_F!=roUWP^>4X!xLnb^WcEV+(g`~AK41D4Vii+T9WT@_`yJ=o&dT590#_FdS1_q4^ z)`TuF{%blHri)03jER~PH9hKOR8Vwe^xCKvsH#7S>Ja@RdPs~I6BTzP?oV85+>^MO zadqPUjjb9xG3IylztM}MheeN$ejLq4-;3@Y?TK0zH6+p<-Xkp6I3e^}@WP-iSIad78ExO*~oT*$|S9{leS2_18O!0f%nqmt& zLp9tlT&=NJq&bgaWy*IhaFusIb{mT4Lj9}uBzj&J--a^yI3^3~L#4dSoq~IRD%8R2 zK($;C-QxD#a56`PN{mS+h~e4rF6G- zR=wRYDd<`7^N=&4obj~jTiCVmcM->6w!1$nC8|wyh3Jz}ccZ#Rk3iS{Np$6yV=)%Q zY6Y=(V=u(siR~ZTAa+j72}Ed?s8>-7qk2ZkqwYoSj(i)@2yxo4unVRn;{(hAH4LH+ zpL9R9leI3*H%+wWk$NSj9uuM1$pORKh6>3()Q;YQtV5u-(3P}7z5h{^gVnGZ6fzI= zFSY#3eBaTF<-J;Ppnkv;R|Zdn6W&a4LH}Y^U+F&NYT|O>ypMM_#?CX!nGBV$#j(-B zLfQSvR>Stu+SYo^dfs}`+SsPIue0;^gQ%P?fi^tXan^Cd(cYnTG;%C)WIHJ5d}n8< z@~gYoLX+=y|8=|E0e3r)xgKH4{k>-vBCus}h_(4z_>bX=SmDqrhu3NeRN0}RB)kS& z*9otLUi?n@P5tNlmMmTnrxQGJoNPhWqlYlV!R7cPEs))kH-etGvhuJpRcTaxQyo87xzOvOOHDh%I+#LD|CsihmKv{zObQ-l z=&x<2?x*Y|FC|qn6~OgaOe}%xeIc9@p2NQ@7188V=(ew7hQ0y_4GRNZ{c_)14^v#z zm22x#)ZF~MU~7KU{H6Ij3MLnRF)u6PtRHM=?UNj<9fKTJ`&F!2%dt|e#8>4VcOADJ z9h@GV=b;WeqHDt53US$E>prW>_Q$4i>~~giKP+D8k$JD-nH%JN4AR=x;)voc?h5Xm zuDUK0qVx97vyNVl_V#YJ8mKXt%})xO7Zw&QM9(OrkhVk?)wBjf1wPuo0s3!+eYS0o z?Vj~j(bl4(qAJK^_t{$6&pB2@$&=~ME7p4zc&;i3KCK+BmDQUx8Ceqki^o|B?`$1?Gi5^9%zQ098s(d81$Z!K55?3~1v@tb3a z$it?aA@-o=hC2Frx*Tl}ZB0!D^*W_TzExIMTAk@gJwg?yvA}`1)r%by80;_Wlb3Yx z{Nw6ouTZq3(2-Y|otvr5Y@GQZb5GW_>}t7Ln89(P8@A0HZ&_iPY*80Yw=S`f_UaC^ zbDH~H@k39V_ahj1{oouB=dF(lKxZ)S{3R*=QfxYB5yHqS;GQ<5lj+g46UxmCJ z@WR#M9+bapIk<2KF8e;B%Xr6C#<|))*>=f#r)XtSZc*6(`vtvi18t0LxAl9`gQEKu z!mOgeA?q3xDfwsaW)J=(pl_b5So#BGNTM?<6sD7b&rd^;b zXNV561Pu?K9w(+IuLYN~wHR5sP>Zq9LCDCuAd&azo`8RfR+>H1mr8*@nPuQM7 zCVY)ghz*JCV!UA}qiHIi#GD{=;dVYsxCj5=YT|0)7H@<9$JM|f|6Vv~$9oN4mG=zd z`08+EP`Sf^0^&ok0&EOY7caZb6*J-t#vBuR4Y_&;cz&w=o{r<8ED6r;W~m1zE|8qoFl7XuB?wV zRklJ=OVvZ&TvMpIplfR;<44Eg%c`fK_p`qla(-5T9}U6F3J{ugrgG{c&p zx4{)d#&<-pkQ#X;>khZ6C#gA~gd@NYRU8IiMMt&xqfRUjRx`SNv8a{(YI7}Wv zuQC>eSB>_az}3`LJug$QpC zrk=YSt_RHw-V$6Dc}#ahJ^ci&Pem&?$t_?cjR$k7EWEI8NMD1?^c+s9wIywszI1bH zGFge(Axz*GvNwIryp_6HTN#GyV6`zAzXBCX_qE^C-OGovhX-T;3 zjC_W2rn;?mwXRTaGb{_<6B3JVsMRzyd~HPgsJby*gNXWo3nOk!%aC$p zu6OGQUA*?Iy0gj!*Q-e~mGmt00tAOK_fuaYkmtq}$S8u<4>eB%lvD+N8teTrdR;mv%CA1Q)WL?#8^VjrwZfEzb%8Tkxwa@Vz9Dx~R-MernORu_bLQoBFC19J+tQr%J+pj!*jZv#My@EWnW|TZyfmH<+a10k zyj%Dv?DNyZ-k3z=+0aQL4}yN^*J|&p<|>k89%djlikKwM6+G~rJj$Pedd$uF*ye$8 zzQNvz;!n=e_WIVAnBHBO|0;J-&gZOeN(UYEjyljJa8XoJ;wm%wufZ zo&OZyED2%T3ZdjWI!gLO_OD_Q^j2>*YqSAvH{EibQ#VT=Z5V2JYmfzLgLFZw4VU$m z^yPJJv{f~|)pGR!)kkGbWuo%&e=|1ncCs*Ou%w9oi5Q$Cb`iyJ-B?SEAUDDNVXkDo zY`x;V>b9ngJ~pU#$aLfCu!fPtqZ`JBCuEf_Ro+mkMwRQ;`qcbdJF>1=hp&}ZJv8Y^ z+2Yu*rdYjPahq;I%n?rVmC*A|Wgqz)lq47LcaE`lw@tK#!M|s3@zRnmf!^F$zAa{% z-g1@Me|=NE-`#bbqV1?PP!wX7+M3!ejytZB;zK240vh2vIZ_&>I-+|LyvO8>utoKc z35A+vXk13zl=#>2Pvf6Ml0j9qye(oIWU~Ouv-CVKIl<&=Xlhr4)SH_z3mg#lU=cI2)=hLN`J+oeA|IOV~ z@XMmLZ+B@*#K1`*nW`a;QW#ai8eZE)PZ&NKdISv*njCZ_Xm`-Tp!z`v3}5uCbi=i+ zkn8kS)>PQwLcK#Wmsw1|gtzfmax&K6f8Y-q5dOf+wKi80SmNIaZFrQAD`}4XV{f1i z=j0P`@9$&E$_6V&sy1s5=tF}yhaL!PA9XzTcBxfmMpc+uSy{D3jTyD))j3xCL(NUq zbd{15dq;H$nXmrE)D$~#T>`CqpS+^yU-x3itD<&=6Y?Hr=V#r@ewf>?V6#PMpXprh zb`02)>78J$dT_@j#;``4x2j^)-6cPcjRW~l+S6CeK<>(wI%aO#(<2Xbalqk zjB%NRvfJja%~mu*#^sbDLAx7^h-^%8w?N2B+KcM0uiFlcHi@|n(L zWuXi^-}l?|(0#(m*`2m4);~q%ipZj3%M8l}b6@jF^Hy_d^Y_B9g*D7F^DA@)>lB_T zc$I%BUsbT7pn747d3n(xTcP8roAZA2EBG};J*J0@P&UwP(bvEoSv9Oh#LmbAn7b>C z8XGk)GCyK<_%)L=^xxo(`Zwwcif+<@^j_i^&i@W>D*7;W0^j`$p>~#F|JmVN0#3&g z-$vhMUmr}hgc7IG~I!q^`Ym(Mnu($^Cs*nJE-FK zq{3?VYlhXCQEz*LsD|Gfq|`f6yMDEAch?W+XXjJrSoA`_ zyT5soOC0`}oFFcsyGlb9I`uQ{CQN_v#+-=ZF|Fg%6NZ*PRAy1OaUoksOqT8a9}fA$~pZg!2f^|Vwh+?AJ<^D3))R@=<7n9hyK_>=x6eQ?IIOh@MG ztlin~p~z^RtIzG7Ta+_B=TlDO+`6dpbt~9w{%YB7-DnRuUG7(&ZaI=N5j@PrKFwbmwTp5 z<80;#w=cF;wf)AF`Wo9g+ZOaY4q|Tejk6xQ%Fp0%*U~fB`>N!CKN*qKdm);Pqhq8^ z6_jSM{%-Ia)1b&#F@NGamu_F~d<8@0epOafYgJ=FO?%CLHD^@2T)B5SKCWW8Q$Ii< zkO$b8-jyz`t)h8;-oUJFsZD=>``+%W;!DFX2fu#)e)3mLN_Kj3R$?xjKiT}ZsIRTJ z6&xm;luxr`~1f;Fqk(;U|i4OScHgwKeo z5=+MW;!l-`!0XVae{f7_)TNC{4Bl0GD3ROYCxaoL}<%jZ1L`ItK|uPCo{!RNvrmb9Yj zHkBh8s*C!bJ|$!Qn^>jLhS)~EU^wY2#c|a|%~IVc!_uIb;LE|cf)j&_41@I_wOQ)k z$|U(p2}y4z;)TO(D_?@AzH6$zto4uiZo%XH^?BO7*ST+Ui*p^h+w;`hif1d;PU4e#CP^z-t+=e*i-dop%a{b6TRw$+7U=I8;P__Qn13?+a{8UW)Gzb5 zF<&-*@_sz`Dd9`z*DgP9{~nXtK7-40FGt zdcQVH*WFM*XlT%xpfbUppkYCK3>N)E-AZkaI$qUFp_2W}bfHd*Ioz5+Udc{R4|iv0 z9s5Mc;-xG{EKQ2Wpbz!R#@k0i2mYxz6#$X(?0P;^45Qri zTFDg|rCg;>((N`(3#njIMHEM!jrGTGPuyIl1yqGwD!i*0U+Gh&YL(TMiz|{9%9nke za3bbJn8$ETRZ+5ASmgWSYHyR8Z|A5KJq#+R6Hvwy_@u8>kX zy?54t+`|RmET3(oT(vyEOD_9Ivn%<}!XdFIp&{E+-QlghUs6pbSL|1ISJ%S(-){&G z-XC(p*e)zJJSy^hRP~sU*m<#8vA1H~F)O1}P@~;$dKEIwuu~&d){$1AdI+!m;oe;? zrTwNwQg}44Z_b{qa+%2)N7BR6yQQ<~6Eb>bnzN?l^v_#eu**EIXrZmZ{@pnMb)t&i zMBj1$sX%`&g|9A-LhOH?YQy}Ne3X?}d{gd3e%MBT(=a)BTS(2&ouS7=e}obQLw_R7S;0sev(*-7o{i1hQ~d+PJ4XKimJYsjM$7kIdD~cM9Xo>&&s3 zS~+IDZIi=CkaR~EpYTjB>F3|V*5mt#L&*U&#~hQ|WFd+$m>E=!3^`SsV4G7>TRr0gDf-ojVb zna)<8v%a!i6H!S$XUfa+?G86RFvW#w!?v5A8rz2M3f^ewr7KXo6(Ail{mFwu80#x3?b+f=v9Ge; zwhT2_FKkorG5;EN>rMsZ3pbfBTQZB5*;+YFuDD{EcYVn?|1fqocZH|LuEYk=i(1q5 z7(259l<-%W=okVj4k2FyiZ!jQpgN@9thuTEi#qfxLugP^P~)IlLBkEx^f%DapP*@} z4p+5TmRHDRdnI=I8F@wQ!*^nv`MZ})@!WT}a78)0IpmIB@aw#TPLtBU*uKcI-MP|r z-@ORcvGu;jfmlSf$zm*76?3ldqnF$2$}Pf7P@+{-S`TWjuZi*#M~IDC4pym*L= zWA;dQ%HvS;t*ExDcfzkFMYBOWNjqCRK>JWTO1DwBU)KjtJVp9ehGhnI(B_~K!L35n zp^ng2##|#|s%UH;x+i#S&>Q`0?R0f@^2vqaMd&7z~u5OMmwsO{hrFqc+OuHVkezCQ6Tz9r}Z^wLP zbMLQ`CVp*TAghJj$=9RnHy&*B z&TvjBQCgMzlt;i4->Oh3D#*KGS}RP_kSV0LkjvrxwVRi4{y;QbJ1fF*izwOcZH);f zC8F?&m@NBPvdeeUUlJJ0&Eh%XED;AuP$bSdYnj;NPvX5x-HtN&)0 z7BoIMD&%X()KEK=nr)0*j2Df?Myv6G@r-ed@kr?B5K~Bp;ATNnkriaB6P01IR?J@_ zm|y3=?)~kyINsa-7M(CJEohl<$jiuilYJw*EtE(%b93{~7fdt{Dq3xeamtHVdTaRS zu^ai*;x#yYgfausSz9af$wq?Mv<@_;+2BoWhRb!TY#b;{dg(;T2WAi|9p~uJ_*_Hx zr)$ugDKA`+zYrqa#SiiIxG?sjzpC%D_d@Y)*BD2Z)oE^B@FDkocE8Nk>5kNQDZ^6A zr9`Ea{N0~2BK2unQieJ6S9W+_P~obgIrd(z51!|~@$4`DA0ms213|o=?2-I|Vv@3- zs--$vb5A>4S6ZK=ZyEF{czejF(3hq@;X@)GMP@|5i)|hMs??Lj&t>kFbCo|<@p9#F zNz0N>B%Q8&qnthNrRl0BoUZ3PYP(dhDSJ=app;I3&i^>}+5h&`i(`+?_q*TwcK`IF zs?TS>JM{J2?}ix-^S@YExng}61qJg|W>QswgVJ)tI?P+v3)L8R8ygv6AQ^l`Z`2*u zdbQEIeY!_Fxo(%{l)9Ii(A?GDHLMC{!vBesMz4%kMemFp5YZs~zG+oxli=F=4(e4h z7xk9!=L>S1t)mNWWIaz=@bk?V&Bre9_Pnvaw!Pl_X2sjl@9w`(`*`pR@uTsd^6844 zwuQ@W2Di@}%o1WawUOB^yRGP^YK`jeMfE~BvNl!iP_l}p3W=h!d?{*xc2Kg)!LdGF zwpt#lSfD6YoK;?jqv1ErIvj5qT{CU6`iY{lOv?-*W^q;gFFkqACbsKlHg92eos1Q! z34i6k*Z%DKWAFFN--rJYf9n3cOzE6)H+xsUXld`bUHsMelQR?NnPB-jWw55P?t%V= zVRO*zU|C4{kiWs>;dbcLFW0Tr^i_>iJd^n(3z#N!C3Jv7$XP^bI07Go|LX_h2)-T< z2C`O&<=b+d*hYbkK6S}?th|GrL+o9x-LY$@=Do~`%zm2rGh=5)>x^Z{oyTvOF1l4uV=L9il@J4b@7qn@ZxdM zI}@%I&JNBWj)RUij;D4jI^cQsYVhQ1=2{Mwd}puL_tTGplMq8Jq`osbQoCHCQfqdh zmg3R>F}w+?5}XvgJm{8Tt$w@qvigGZv%HfugI+{kBvJ)0x0j6%9P+I!IpWRttObE` zuxB+owR-PjuhY8}oj|4koqsTx>jrRsHlQPGLe@zINdFYfFO4sWhdX~)eBLP0_}2Rf ze;>FvGOP}+V{_q>-iCaMIC7mN1M~Zbz+UU1n5O8g$dfMxi%cauCK<^D(bLI^;z_;* z_am^)ztQ*Co9g*dY=^&fPnXqs*m=qM*lBUza7}dQxEB_8^h9|*-jyXL^zb=&0xs}B z^)~@ivPGaArgr8AW`Z&P74xL^L0f$QR(lneg1dGHOv>I8Hj9&qNno#x#+n-=$(78L z5@1X$mYtFPlx+aNqPuiA)|e!wBi(`OLOMjX-~;jTHfC_^`NzSVlJ-?XU*)p50^AK3 zmfS2E?_2G!3Z~>txXt(Hi~07b)gBb*pc85$gUOZTb`W0fV0}6O@8jj@`)bHk_(}Hy zV{7bxcOO|61m7m)PBNIP0TxRERfX~WF6Qq_r4o9RrpxzD@ zPQep(A6Q52;aqzzFgQ>HK>vn2n1%WR|I_o}YwrP5aShcCl(BKpp>3k_Kr8+MJ?mX? z#j>e?srFO^ILJpqHF*1fU(KPSR-7jkV|HaGzlCoGc6}ncAOG+l_&m&3)&(6X4CL@` z;!AM=_}?BPo}5hvQBA>jTnxX|I$$;4q2rlQOdO+U%yec3f{dtTvgl3lP#u7--VACa zSdv=I&|QNo)=Y8}{PlNZ7k>x^bXTxUu9IJIUK_z9sy^n6j}YUCg+wUv9i&SUo}eeh zP2x#RK?`Ce$UmoW#ph7(&T(JGvT8(JWOL6zlu=cupB<8J$YS(G6)Yl}MMvI~f4Nc@2=V890$X z2CL{gXzbO%%2rb!!Mzwrx`{U6f0QL-K$^)wjdl~fnxA1Z{4@B`8K74GAR6OJj>Vk# z9}sFPz>nh>@tX+3J(dSzJPB4!E{I8T5dFn2s{k*4J-v4_6EC?OF}R255G`w2^qp$!7CI9RiSJ>41P%s?jhJ{ z`#^tA24%K)pk|;{fQ5g*24vbx@J&A&7|VWU-+}*D8$VYGe5)=v_T@O*`4mU>1?zqx zuFXNX1%G3_lHt;&n39Z>)dIirr+k$nTe(gBOykf->E9WS1zSRP8ykixBA!L;36COe@A}(f{%qWEm=^7t%bhjoMVpjr|WugTexr30b$q% zU$_Qr4mV2JBu+pl#Y_w$)9~b0ru#CflJU}9X+v2Vd1-}F`CBPdZ&YWfPpVt18TA!a z7gZ6WWv3z%+BUaTD|Iu+>7LXBLM1liE5gt79Co@(o@T|>-IKwzxaer>kT}{o`e2H~ z>}ZbJYr;!W?EYm@}YQP6S)!mh_19 z5}2ot5cPBhEwn9IJ$1m~l*)2JL7fe9s1LKR1K}4qTXGEgwoCLiYB#wAy!1Un9;Qy! zTp2bwFu;GzX9s`#hxdl}gLj?x7UqCH<7=;XT1f-nS6_Eb;+zf0Sq~fFbb?yEf=+l6 z8I8657&6fB%r*%FVx}DAEtjMO`n8Xem*8AolH8Oe!z*0`)p8RkT}fD{-hn%l077ja ztW1lr_D%)A%LZO<5QtAZsXkO5xrBTQ@2^i_v(14g@^yGCCkr(MDO}~t2;~HY&_dWC zd=tun*v5do_Ly+MCpiS9vnF^Vx02OBxA(#e^CkU>zC@?e6PP^EFIlFXBp7tvnUY(e z&}g6_X(OE~4VS(L=WD%Wv?K`Z&DmJ}cVZ80NSCGi(Y0w6{Qx^sWokdECa1#>uorPg z>?59qo?;ECd-H^2P`*|N4^rSy@-NX1yv=7pZQ;Y69SQ3E8Mrw|gJb^+Ue8D19&i#~ z_-(L9yums$0Q>S0@&`1cyRjGi#VlDrtZlETqf`ubp95IeYM`gulr~|CA)TV=x!C)@ zfMW13JP8+~({u$peQjd8*a>GRLCA&w`%di43gHA7!Z+t$b4tMso$yVb0X;7Y#3@2h z@JUcPz2&pPj@ZF3<*T4eYva2K?eR{Q;Ak@i2Rh}Sge~ZmPZB4J&BT#l-A@*;VGnx+ zTJr=beD{efp;B-{Z`>FxiV&!(8WQyh9uEI+#M_{8b`}LN1C5{=><51`3r?_Iu|{2C zmx0e{WHZ3M?iJV&SPrjcx4%|E7N7$c{ipmtLGfPh@9FP^&#V5s{ylJ#qXW7C2V&C1 zfF;nCEe}r9N!HCqfN^z>JHg%IPJ@vs;zN!-zaco49}#731?{pYyw|62pAN z;wF665))Pi97#Nv0@~7P_6>MF^D)<(0G9+c`wMQ+9(Y2#!GPQw*dFKs_s*sP6I`Fa z;mE!r!kdAaz&ZZz{FJ6LFlCSSNVlzXE9iU*K@y5ft)c5qpgd+(DE%EifOhds%_wf$@RUf%@>C z4Q0!)+du@jgL~NvbMZGZWyWI)tgY_U6KH}$jzJzyyI9UqXf*<@>Oh?+le>jBC ze|Z)z?Nx-1U{Owhx5W;)-v0s(A_cza?LlmvAwB~$MM~(A0gWQg6B6vsLC|h(AkX2c zm`{EJ)jJ6soH~dcPvPmfi@iUUBCuP<(9P&)(2;#W?0fNi~&8Hs3!k#vx>mL!5@ z&<2dXni3tzd1JBeEC+>ptfT;3>T?pgG!pw;H*l?7ituT~Fc+oEq{)c3Bp?Ro zN#05-Nb@E8pbwlPSt*$+`Gd%;1S>~HFnh~l?bwRn))YkR8JK|?AsLEKqvSK=XRKgw zk7h=Iue1sdhDs&|v&p@fFhr9rK^a*L=HD461WRZf;q};1~vEq*uU?<51x-T_ZU8BF-s8R+L&j|Jy3%mVMPp<)RMTFc9Mp8 zpMCItcH({CmVA>GN?xN9G+44$5{7K#34V?R9c%_u0c&?DxL_AzMSV!WN1QgD-iYJr zfMc|QVsjtSvI!KgwphW-AsGw>&+9C%=3B(ck3jidK)wbalcMf}Q#Fy?jH}&^Y)V$f zwSNbH`_5QNR0N3$I#GNgUc`E{LbQu9gaLDX%dx&>V+!S&m?l;M1(+gYFjZV1vkfmq z3sxs981jvX2E-m>8F3TTO&=kG#KRzh`vTqN0b(`cyieG_8SsVYf)#IsAG(JuK*Ux= zt|Gr;70yOfUJb|66cM1CC;;a(3qP|6x%xz84%3Mt$k_`K8LlFxLA@GJCgOe#gA>vo zyoSbbLA=`(L*=gip1IU`#pK6m4WHa(7c#55f zb$FFkL=&P8_=~8s6BLmmz6M=rmAC|J@IGkXhKf~1Q7~cJ>w$1c_#j*mRzP$40#8&H z9Ob)XeV#40!`a%4*zO_r7{6G8neh$866}aI$Q{T~eqnaJHrS&5!B*>zD`P-Z+6ehw zdul1-)hxtWL-34cP=BbG)F33UxGc3?lDBJYE2x&Yd!i9}0KDBIz~FV=z{^9^RNvc(|cF=pC#LhZH@ zM;?oO(keuNnL^=dx2>-2|LVsQ3*P8f@l`Zh;^ex4JdFqpesy;!|^L|5$=a~ z;QG+S7tmURpeVM81n85Ch;3j-4<`0whs`B^BH!AM>mP==|2Wo|>%=3>(Nnl`enL;q zg3s|9atr3u`y)sAkDFGS`~W`V5KM742d8pB_%v6@8OZArKya0jEkRw7<2x6UZ4vR$ zArE7^eJY%ReWU^LeF5&DZ^*Ro+*$wK!9|;A1m=u z&~!J!G3tzPM_@sKYbFj5_h9!uguF8oRL&mQl|3MQRwHsn4VZxE@idLbS?+~Wi3*SY;``yArN~3sF`;2nUa%8@Nh-p@ygu z8)26pAx?*4@j6t0WpMROAXnVR-T6qk4NtdT!c0UI4TV0KMw}sx!T!-74BuM9bfFQZ zWA=ii9fx=&8#~?}ek{CGM6MHmmOsJw;Aev|d>ygL7VIWvgm@tuI}ZmU@e;&-_3*pK zBKnH~ck6^OUl;=N%>cwg?k?2t&$t8JAo#;fLgev2}T^V zftv$PbR~WwXy9iMDM*Chi0DQMNgyIO#Jl{)#|!W9?+2k$SjtbsyNkhj?EqGIByZ+c zfQfvCf5#8QS;@!jc|KoH(1G%N1T-@SQ*7fwrK=AgvL=XG!l4oz&-cZds?CRDj=2?l z-Zygz+(;0!oz}*yR^z4MYIcN^&mv4uKjivyEO-wa@VA<{TKQ}V&is7# zKKqJ23P#9#Q2ZS1H?{%DV_wjg`g67Tb$kIL(bBvezbPNro9EKGrr=oo8)_UZAXdxfi$}^oWTKQ2CAGf5Yu2N-%=4eh-mYUEH1HaYm-#&i~HG;<_y71^zwW zn^rI(8gGd#=a!(x8To+~zM4p)dN2Y})EoRuA|^p+;;LE%nfMRrrhV`?lQ;)Y+$%wX z%;~J~1%B{<@Z^ekHfMl;JrK;OiozS=37(HwAp@^x!#ORB-?0J_-gK-e_l47NYiK0i zfwtlRR)SIj2}k*nf(1{_GQ_M5zknOYzv0fXUa*c@a7wVvUAW#2@S(<2T!2V>0yh^= z+6nF*w+-*MBhFDNWJtOEYW)0X&V%a(-2%62R}_Hj}?LEFAF`zGpOg?5msSZ zw-E1rK32Rm)I2WW3VLuCY!p`GJ{XSU?1?Me6!*pm+(~l0kGF_KNnsP;9`{-|oY}A3 zTg2NbTr#3@I~NCn-yYoem+(w~$5|POT*o7P#aYQ0lCajjfOis!7$g;3rVdyEs}Wtr zcrgJ{zVay1-PK(B1T+H4WJ*>6=(v#r$3!U8|VmnH!Yz@ zfbRMLnd?>RA}XA_>C=>)x=wbX)=_h*$@qFPbrm0@k!N2*L^%&xXLI@tl>&E$YpA(3 zr3a!eX9PR9Gu4PrVjfXF!Rt=L|F1wLR!57}aJ)e;m??;%XEObnQ_MK{LNvhnTaG9-4b{fcpzCe^ z&pSv`6?MzzlEHKoT$6h74c_Dz864pe_%BZ57lij!G2Le5S6X zB6_8T(g(;I&Pgr!?sLcxOwtH^*BFTsmC^0es**gWnRElFTSYkb{*q3(nzbc+BnO#% zP#-5jWf_M)suJAg7a(4lp@a=Zj-r&jKwabSQIA-W;zzE7t!Pb6Mwx`R;-zm8i2eMy3l{qhT;tEr`MwzvIji(YIHEDala{n ziey}%z^5UG8bxiP&mse8Nqwaz(JrbnY9-r=zhrynD*YeL_XE`!b^chK;iZT)rC_*w zs2FN0qR&Uj3q-`HHxWy3CR!o-hy*!2flL5T{5W{V??k^4Nem%li9x7HtUz=kbC@&qC}f>Skm->08L0g4Lk~NT zK1+Te7onQnh&oNKM>V%sy=;Z~3>cM84Xh^EwB zRfT%k(Q}0z1kZ2hLWt6oTNuw~a#CWAScIdWjO#mD*ocbQ1-^^Wi{B3gRWbXRoiFr* zbJI(YwXaJAr6 zFq^y2*X1k$hU~lnPt1)*U#~F|}WTC=$oIl3jK{up5_XUw1wwD4CDm6c&*;PDj1?-R?4-vhr({`D7d z46e{p75Q0<6jeSvH4sZzI9-SzYOA?K){4bT_UC6=389(jBv%m&#W+L~udxrm68(T%sJRQUqO?J^`8~Cnf97u{M8GZSwwM8bSQVASD9HkDKlhk#Oq?fM2nRXj z?Br);8v{}04e-0Tfx;eeTT_Hin9N*HtRqW{C(xCsMK+~I3zOI?9M3-^j+0x&I-Hrk z%+C=+sA<%{!tOvh@vF2fa|D%o2l&>D$Veg>EVZR%l&C_)G@ASkhp2hTtv$p_Dvjta z3?t_S=IC2bTIUk^sWoHQLIMIbEBYVsoB;Sz5*bny5Q|Sa|F!dLe zl7`G6*){sLSeZJ@xY2v3iRE-hd5WYz`C1r6PLceOh*VSZ2sMwsBAFsxK>ZMRkeiv+ z5+Acv>Xl?u-^s@yY$ee~@-ET(KP{20IhBYgQB7aK-gpn)?qGT^T}byP7m3Mu^_%cm zy+*$wo(t2+R_N1CA~x}V#etF|vi@XMey})?Tt&w*LntZq%w4GVLO;GU(T`rw{3ND| zYf(`&qK=G)MJn{$qh&;rf5Ran%mrQ)Y1ZfC0oiU4RI1f2Y@{9U!m3u+>$rJQZ zY8k3gg1Ai>Nq(UG(9s>D#(^`rn4AX|Dktpbyh0?knVf-4VkDV{J8dqi+6ToSh&lV9 z5@R4*5S_@2sDZSE8**i$I@ynSBAgOik|xB31wto&JaL2!hzp@X+|IYezC1>lEWYHM zvgf%!#B5>-mlc@KFTqaqoS!dN7P@m4`I+QYaYz7O#fa}SkuOH_{qb~IgtgpyJ_Whr z8`QeYoD9AwiEMzi@x@{cUsQ4+zz_|D{Q)(%4|V40$Vc{atw2w{&wfFK|AcGHzTu8w zU5&%)+>8j}=dh2&^K@;305xs~IgYMDJtw5xWA_~Q6n7g}6X#lMn$2DO%TvSO4y$P@ z_Y0F6-=TA|imepMsu$>`?U9X;-IL9dZy&(rtouL{l2dBD8sB^tQM`tKeiKvVo)^zGHX%QX~^%2QUBFm5x zWCUO6wM+=TkkT^a>8E&}2TFQMhLRPC$Eb_W6~_?~bRMNATHaRD&A{UBGo}ON59u&<)*y2y`G3B{T#*__J^x)0MBpMyS_zAj;$X zzDEUS7Gk@4h!PHoTad{|Q#AP*dHQL7G>EUa5iLcc_B0a{R3C{K$m%os9$4vW(>JKj z#9!fpcoJmb3wY-WqB1H~Bg7c7F}h1GVJm+0<@{4&AKSzq#+DO;L=#slP@WU`SMWhz&qoPM z`Ch1j^hRDS6?IH>68^tCzb)d|J;V;wH2#6-<`49Y--rjqW#l;QJ~EuA zdZdErCN>kkf#f`$UnXLtJW!hp5o?Rpk<&~jIt$f_@8nWZi{6%k*eoVENPFBtx;})X#bs1NsG>7_~(2VQkm*AQm@vOJ!TV7je z+sPtrVby|*<}$W5j$`hv-Ww$}bc3}68$q|%;7%EZyY?;-&n%G6W%{GCd{4Z~_Vc?- z?s*n?mU%Kg`+QX~6Bo;7b6-R+?L-cGQ*uLET^Xu6C9enn#7XjclC$J8#7k3A(=)I) zd?$R*{O!QEt?A$EufP@Z^-zV25l10DP?A=n233bFMKnj3;wC?t&4(LgEW5~;?1?Gf zT71mAujH;*S@HxV)>^)4Y&%{iJQ4q(rk+Z&n7HYPiS(gleeo_T(m)9oITEd73O12H~TIGesLA}a{-xuN}wm| zOB2{-fr-3<_?M3g++;fuWr&uj{FWCF2$#5;?9RX~_6FaXpTUj`912WjlkilOy6DX{-#JjmOO@B%0hs_|Ynlq=yv1h=m{eDxC4C$9Cd7fh*sYYcBi|M?#NB zV790XUzO`6}2F>(hyw;!T+ zo6A2&-B$p)d?fF}^K%jTR8{n2Dx%Ug2bDIGxB}(vC{E42748uQ;sQPgIZZ88VE3UK zSdHH*92bA^-va6EV^lM3=)v2#yL?xng_w%$E^kPqt#l#tMHJ!mMgiAKmej|$P^9qtdDG9I!s*v?Remh!jq z?em{y3t6jwk$*3n8945{?N8vc*{Ol!><9iAH`{+dP|TMS1N?)C8FdLD5))E)n$7XymEl5wtBm|qk7K&I64O?xw5Z|7hk=y+SOJ%p4hhSOw5Tr z(Zsenv2Al=+nsc$jdDG|!ngn5>XixCbkgtLd(S;*@88yK)5Yt*7>8Kim@`cGOa@Du zb&1s!HavWAMAwLw5yK<*M3T{CZ5M0@ZS!MK#l4TuPN{7A+ecdh2iM&lgjXov})2#Aon5KrKiJY(n~AE~n>avE!H+ zCXqhtZH$!5yUtWsJ(t{7&6$K0jDwyTzG(U=yGvN_j}1MSIuyBNCEX5vWqmnA6%b5r zo2;f^=00$hB$?Fa1Ex&lcw+_AXLxb?8n>D#v)_CZS#H?|w{D;Al0FBih}G3=p{(pf zTv9BRB}m=`zxiFld#JQ*%oATl?@O1`S<}(haT!#gmEiGg1B2&=Yp<)T`v(X%M?uw= zQf=vLY$bT2AF(#1XEwkzW<0t9{X(^*ZNQi|%l-moG*?1V8dWK_+nfz!dC05&t`}WlAVNyCdIjmzve|(6uuxH*GTKniqwAjT#eE5FKT^ z7JomnX5!z8m698$^h{Zk(l{kEWq0bVw5h2bl8uS8X@cHK_49c>%{@;*MSAY4;Hv0a=3MU>sJVQ}C@aEP>*GLwwZa=L~3pN59uEp#*OGOe|IwrI^3Bcp#{_z4|- z2hBe99L+8bLn_rlwVPCd%#jZr$zQ|>!l%qd*Zzf~Kqg`~Jvr1YSRpXhzd;zntzeo^ zr@hhM@1E=K{m{)#ruv|IsmfLrlT$z)k5V5eyQ?GA9&!VjLWYs8RTGFT zu3306~&H)TNrS`Voe|4Mm0`;|I%z@PzO(;X9&E z+0KFjbRaG{;c4RGq~}RTk}{BSbUL+q`l|F5X%|zPC)JL78rju4)v$*gB6}W~?;kD3 za95E|KFPPv3j~2P#<{^2=DC2RpD*4q;Bd5b|LvwcyO8QM4esQtNV#6=9ONi*=D|ZC zhd0Z{8|_8o-(k|o>yg<}b7B_Q=Gdy(I@xx|e2O|2`8Z-%SUYnz zSU)p#S(H00p&IXp{_HAQXJqh(g>-@S;u)ccW9fOw++OL*upcfR zR>~KhDcG3*6d8sM3;hMR3ul(xMhctKHP>5Aw`K?NJN?ʂOpLDiV1EF@w`UgcNK zBUh6yaFUaujNOO*`xZVbYd0gE*`oWb^=LcluIe`E#%VXH_mc{w%SI3@l#LV_GHB(3 zsrU*#<0r5d_6}2luH~EMT?q$nCu#x`)uNgIea?mA690ifwP4-Q_s|$=dvxA&<&@l} zys2zV7*(stb84&RvU)T!FT3iC4G#_9jLXcMEV{7T$l20HrQ5E8=vOvjU{cSNX35UP zmPvG?KfX-d53v6-VOyCYUE$QY=qQk)~-zw=Sgqi|ndOS6-0KkfeoL27ma&#czBA zzL1~D{|k0?Z+;_x7^&`ATpO+nzCJyfLG%dZR4P3`U486diwld67Ck7Ol^>mN&)=K> zJuj4BR5%J$%?2f}?A2U)PgO4u_x628!R}+ba-;Ym{3d=hKL%OepV^aaGOrfa@>c#A zpD6YgHR3YynEwdl(A+5%l0&?~A~Zd%HPNh2;q zP~ih3i=$`7RE%L_GHo1kFBU{ZSx@S_s_IF{iKFPPt~#aG;?%;t+}_#uf0}+q{jB@5 z+t1!VEx(3k$LB21eS&P^Yq=Tt{#V|D{6TruavS6(=N09S$X%aHelZMJ^0@u2CwnXy)n&_p!-pHka+Q6JLuR3$2U$_`6D23PwJ z3s<;-Oam(2*T8qld)`ym^Orm1?B>)v2RM@KPfPliY$#q=n4RA*PnR|wc?1&JjF#~0T(Ph09Lvl9O8Rp*>1paJ0!4Vf zeL>0AqMHR*@^|Gg%umi6m-FrS*qmm$F?ojk0|l*%P8N?Y*=;}O+U;rRP4exgAG2S$ ze*A2F{vUy1n1~OSXk=HQr){SAgV?VMS3{trk2fwctu!CC?h0=lStj~KOn+Oft%(#Sv|+w+->?3-SfQ8r*)*$I>%}-SeVnk$k~&-0^UMvQG;R&a!B?&g7$OvNsi7S?6!COXCDGj=n2;=cL(nyDxH<_ zi_jI`EWzG1d9Vc^rhFR7 zYApW;AIavf$ucZwslT-Fxc}s3!#o_ucTFM($j? zuc3E_=ahSZ+v{mZwPi-JcD`z$isXcRi)y`gzOkCMLgciV;c=A`4H{h9VP zMVr(u{-e!}-L#*1f+3_0kZ%;vq{k$eLkj|&aFCzGbNqQ^ybJ6Fwj0}=jbuR>rHiR5 zzVAp9SK^lcsaRFGuRvb7vY=+ZIqz0pr-InRW<}qMm)J91+dw2=P5sSv#5BY#QOIs9 zx)U-uF;0`!$>plca2l*5kE@=5$JmR-*~BzabtYJzm3X?8mgKk0IhD2J z=ZGK5@3p_D{ivUr@{7w}lDj^CWTClagPj9ge~NpdS4+vLEZ=XiU~70QdB=Gj$k{BW zvY2)JQomFZlDU<+26;~|zUHZgv+x4esnC~WilO4bph-~now55ELB8<2XpJ`cALyD?Y z@|voK>L)Q1HQ{W0tRW;sHK-Z}sCKAE5SIuQIYZ^cJYq3&hWxSv(kRK6V3$CQf1dD@ zyT=X(i+#QCn)ev!s3zZ9&{FDB9L3Q^jD%alhlv~fHIWzCMLJA=UQwP{tGY-YQ}@+e z)Hc%ZFbpzHxAYBDM$nNt(PyxVN)vV@S52LeR<}&Mvg^vdD!->fk4jZ5H>pfkzF(S`zJO@7m)Y;u(vz)Xg(PH-;+1KI1Y>8ne(Vh8h5e@1AZ?3c2IdZj)L zkzoCz&&PgFIGX$*H9PG~+T^sAsbq4mc(g1d>xDfxp3v>n^dOtyjyqd&3Uj){|Lb-@ zeb4YJ;SYGA$_q>QL)->-6w`(NE|4AB%khTUXiwOZ3eJ4<&$Z!;dl zo>xNb3BBRhn(Hx1FPB`+yo%g;)01>5`cg?aohINS;FJcmAL-*mPyd+;wlvr}fj%u|`2Gylqb{cFzePdOL! zq6_~hSpw?FY0m`u2>Sv_PXuQAouLNEfMT!%^bjgk3=N?z=p#7|ee)=3Yxzd`9JwH$ z0TQAPiujGPv(k!^uh32$264^C^`OW4x_F0p7P%@q%9I9*BZ{vVW)*xc*jKQ&Af|9c z(d6PzC6h~c*n2tWx^H_L;mJwpZFC-eo?+Nmxasc`YGRhz0XnnN;6X_Tc>=+ys)7X5 z(b&rT#j+{-cdATN2WeE+scjDM{Xu{5VOPG%>Mj!r9on(XkO-%`)vy z86gbyy|d>QaM|jgyT1?on(&$Ur_Q zY)m+eWRJu+X^bLrwe^*$rT&h(12IK@SGp!tHxMb_gePP@pTz}{csNU_&9?%LzZ3r_ zTb7PgX6!Nwzj@T|wkXl3S1GUAGoRYmu7vS$O-Cg8*@E|Fh}w55RH0`Im^cqhyfzxeNAws;2}$!pR{pm{D>eDwJnHK zB}kGgrmRnWnHrO}H=W3M1)AivvZ}Jt88vwNUxh;xr4+pe$= zEd5nF)luK|-96tI&P4NN{8vJ=k=DJOT%-Tb(l4TOv?gwL;;Q76DLvByX`|DYr4CQ2 zoaBq$8nr%rq_vL8qF<@u$wx#k+#tQ>jbv(ReaSk>es~7*B;6%bB?<7yO9N$uqih2B zwPD`r&MT$9;)0?wg?|?OoxeZtc;3sr%K7K>#}qU#+EUuoxxjtL`;xBA74wh8y@3Ov z{+OAKkY1E-lPyBp{3(eUGp!QjVD*+Qmq#e~5r@cZ^%?Cj{W!yD;}_!vW0GmLX_YZs zpQGKcuBrN4@l;wpqz@bcnP55Npw{{}BkSfXNYPQA%HF;poLa$2wy@vXom?kjtk@h{ z*}?GW*GIqh47xx20#AZXFeko^?&%cbFWh2D-Am(bYhi>Zx-d3Bp<{C6)HP{8%Iqy$ zx!kq#^(wZnc(CH^N}DQI{v)ySv5M;}ysO};c&lPug@W=;%dILCpG?N~jkss5L_U{9 zh_jhq-YTwmhpqHm(b~e4!qVSfGa)@`aPpj#u#~Xmib=Wg z{cZn6RgN4J{?XFR^j<$+`;`1c)dVif1;iKS8e+04Qq>h2fJw?8ie|FPP^yjcJNe`6 zH2R$HoO`C@PKmE*U?Gt|J9lAj)w}_Dz4BV+#}*AN*;rbLmHpHEoBBmJWtZ@ja0vZV zKGavjLBUlE+WMN9zf6Mzs-}Fs;;qu7x}@%@ov(jlJZPQ+BIRi7i?9V@YU?5MDcrIf z>SMKe4wT>~od`G4HZkA_}9<#T>DHh`kiwA@OXI zEIA`(K59K3JPO z=+(Mj*;|%m79!2NU~GZ2AhU2sQD*VH(gXJKj;+qx?iENFY3(WRS?KELd~ZKc`bUYO z_53wDp&~!zX;+XO{v70C*uBgtc&LRWUA`eQ2gpLI2ic7gH+T{Ds zecbumUR3h6s7%qhqE0WzxXKm1rT6<4Z)`Dt9$A)^~oT&JT@;ik2PKfiKvr)zb1^lr)(r2 zAUTXX(*)rmH<--^al=8MqOViOs0m0bm`tZ5o1hoFi}^(xnEv1lccjlzN2vPL-@Y}- z;M?Qt?HlSlOgY&f=t%4g?3Vl?pQ@ZjuF-BZvtjaPR@y4Y&W#-% zR}%MMyg#9BQm>@aq;bguQ?{h;Oq-J)3)Xsq{`;4Q1 z>8av9MOzEsVGm3xZd)uV`BZwtvD;a}bKS0nxkuVr0rT&r8B zSwx;C>Od(gB7xnLsTVkEUBhd$53G=ujO5>U8-0t{U^1QG@ z!QwnyPUP?W-@)J6*;9VM$la13D0o{WEisgSw5!0aYURpyy?5<)JKV2bU!95Qam2VP zyByBzE{*r4@3qfK9}u)bIc7Wx#WrFFGOK%Q^xDhF897A8sctI&Q6$Ner1t_6^sNqX zk*q*Pfa__YzW91jI_4_V1U;+8>{G51AHhH761aT!5;BX_Ol2m8_9ADmzwbO%ma_Y5 z(D#@dT&b`NY`a#nG0G?8a?LdKYNX~J*0Et@!qP$7TM;!iQV~VOyteg=T@|}NZgfJ& z#N?zW$)8ekQ=6qfE0dhDH)D0Vgz}p5Kgu)Z-ex>ct&$jwo@_m@ZL4_bU&(azWq9^E zX4t)@%SwsTR6AuK?YQa~=lahR51JR}S?#Os?dI0Fq|Vo+gNv^g+{jyyb0LS#U7SCv zaD4H&((BGeo-Ci1ogmZ+Lj6FLSFg~efgV=QqO@;vMc=KoLw^b}XMS z&J9ctO_bi2k5H;rzsM4`Oj}O7TN{ULujcwUI-{YvalLVhG1k=05+0@s?-)@tYEMj? z*uim6;}uDFlAEVIPEJYrmO3_laB6J)fUVkdn^0PdP{U6A}|}QiSKwv@1quc0+nOU zP&w%p*%C!t;FLtpCF_c~!r?`q@uUL9 z*(GC24}xVm$~hJ%EbUtCp6&kVayw5r2RolS`Z?Cx#nP{(4@=c{zrCDmvuA#bSM!04L5yQzP~(x_n6ljtrn zk7I7cSZ&{J0i@_1v%Rt@Z4aYbgwHlj(&Wjn1-7t_z1v+j=R^A=`*cSY=U(>|?;-DZ zUoZMTJ%Jg+L^C%SmY(MO>bdBt>+R#-?Fh5qM>pUa-Bi9-Jg!lo@1Sb%1<9 z)>d}{H|c{)uUb$1gl=OJ)b9mQi|}xA4u|LNN1&lULR5&O#W0}-^6cyLtN5z?7cQM| z2WNkIUd}&YUoxAS1GI-)OmAiev%A=A-1|lgW`F&_rr=S@zp_qny*xnj!!dO`ZBt#e z{+>Z@nqZo0o@dPr&xyDhxhbk_%rM*X*t2n$;@$Ck67D9}PuiSRExAp~U#V0|>tsig zGvcLAD-firH zSw|D=8B0&|1!G&oKz%=5XH8%7A5|7o2)fJv0Kw#}?4|6Je2=26;xqg|kC8Z=jC|gk z{$2vb4yS3)GUqmXzfvAGebb_Fe0WgFh84{yx>Q6J9RpP`qGVmkrxIyt$I?@!$4i;g zV*66Z7Uv{a6ZaN(Eaqz;y^E>ObPd+P7YLTX*HBB@B6)S?OqEEURD0B^niJ|_xq9q|D0&&i`Zhu$#iB1umjP>oR9R% z1|axf;*v2{S&TW#6Je-$Lu}{o2{P57K=+VF8idbyDBMJ^6nB(^RclF)+N@oo`>bzk zj4}7NjtHkB8b`g1u3?*Nqixo>^KoC}pCqUfzQ(UeAQO8g`V$u*gKBCLL9``qZp1mJ5 z>}7@T&^6WPzOx04gXzFd!3n$;48|VfuHXe}hGH-Ag3QL6KdoP5{M+))dM)gGcv3_l zoC}j#Uzo-l&m&W0hOVxT)i%;D*K`0q^MqCkrNMOl7JW^98U111U~N7rBWB5aqX}8h zZxL>>J?X_%N9qG=v6D#s9EO?R8LBSj_tl_6zAPj}{D<7CpWgqx!+Z;T<9(lfAxC@-lJ)f2RHbQ$^~hTTTmGy%_Vxb=-C++5eV z5Ge?^(Y2H5MrsdfBajO-Qx~OI>CfnH=rxAkhQ0a(J&WH{l9iQz%DYNig^U4}c%N&} zMldtz-gGzmAU%bCMEyziKn7J?-$l+OPEOvHWSolq4|NrfrOX>K8x!tG$Ivpd=8tcE?y z=va|G&E4ca@_z9gblwulEolq+1>6riD$_9Enyhwe${`Jo)sI3#-xK3V(*@Hxv)NiU ztZaC4M7c;Y>Tb*)+wj=qam(UsCrnRhl5jGiG`>Ro(D=sj|Hfs;rN_^WpPxV^j7pdj zUm7iod~N;1@J3Z$9w%86j1JZetPIWtnZO1gdtOKpS}3tY)s^Qz?OzHl;uoeR6JTEW zy83qds{0Jc=Ln;Fu>1KW@o;dKvNEsw1RP0C#bqRF z{sh%j)d5v)q`5FiDjGmmAS20evIcocb(FXTKcGuW1-k{;_({GMI}mrF0^e2d2p{W9 z@U6xA+U|YpQG4#WdO#7gvvhUI=Hg?;MMX7=;);w#*+nVE!;1HoaHZAkv+PqH1s6^&2TNO-~>OxadiTdyYl)5w&xlf=;T_n9K8!R8A7^C=4 z(!-%o!R-N~|E#!1Byrvi5cSBlixyfzua_>2ZRPb9?G+Mb1ERX>lBzK&N5)?p?I>LZ z!!6?nQ!7gt$in-=k49XItQmDb@^)nPsQAb?5vL-WMzn~S6j?v&TNH~l%*^P%QE_1% z%wdL0 zmkyH81RHeyV5P0(onc z`>re3x!LitbW+L8;s!-c3-1-YF6dKGzu;s+I&zw}7uG0BC=M0BF4+dj*|I_^3h zE}wh1C*0lqhuNUvc-jpIkd`E8ClW&&;G(Qv_~$t$gvmU%t~6jSS6P zdNg~Po5)|{Zwi~l%{YCg!GGKqUc)KUm$F>>8^v_xFhZ*Op?XHL>bDvh_M&+ps0}x} zEE~e4NYTC&*)Hl!)Whhl(I=woAvgPV%=Q?)?JN=jPse?ZdlMg%cqQRi{3u&POoFu* zN{6aTNy0grtVmLxAr2t@d^oXRSxvcG*-bf5@d5n1-jYrM0bK-&W$3X~U3wq*-8VTc zG*}(bt91oqByXh`WS3;)sT{C5n`jmArDDsxsLd`ouBN*zwu~ zU5<8*cBiHiZdDba1T$;asN0eU{+B=``zie@=>_^qP53Q`gT^p1u+!g7{DjlEK6enj z-yC1iGtr#_J=y@r9(ymxUPq}T7X0WQNMLT_lDcX4C-**l8|=yQ^!F-!***{Wp=G(| zd;-+#ez9X=mYKyTIMa#aDC-Xo%rNO=2)nj+h8mR~N+zoLxUNP0jY|Js+t=OG$E8@Q+(aV_FAR*gU6!mAs6w52aUhPozdi4eJ zFY>bbwfZ}laZ5EO%@1{mtV8Znaf)OpE&0G4;RzDLt?V^)ek!9XZOZlIdJ0XDW%|K? zB={w82|adA@Gee_Jy5PT44Og2s2{2Usz)2iG!RcJ%0NO?Yy(Boq|_>_Dk;SvaDY04 z30W+kCeMN6Yrdp;sA=#cbQr&&Wj+{~5HJUp`<4DJ!f@V=hmR;}X16lC;b0Go5vvly;dVn2c#*=yn4e1bBf%H#!i5g40NgKckCdXakIL?-F zp=Tgl?1S#Q8r-e7;Ceh5_y=sW${Z_!h6 zdJlW!Fik)29fltMHBW7?-rEp~(g%=Fo9g|`+t_DB<$53aK@*w3*nD;}vT2X=r-jdA zZz$?5;7A}vJlG0;i5hToJ%!inDSR5O!E-t%9SnldJy5CEgS+<=^w#%yvX3PbL1Wqv z&WuJ)O?AQ!3s(%jQL(t34S`j>`~fd@0Zrn!w}j%5v!I;w#jen0H* z@ct1mBYs3)k1B}16LU1?vTbT?f$dFnr>IL2Ppl>8B;%j@$NDq)xM0jNJ_Wr!PilS$#}4Y_Q2mgI~0JH`lWvn zC~`l9`{D@y+`z=(BXl`VNp=9ussrz;HoOW`q#q@*lHPD0cY_;w2`GTi@F%L`-M=c) z<4h_5>!KzUG(P_qFhe?nE+-W{>;rBWr{wmtD)s|1oz!%14IVzWac?kLSHdk3JTHKai$g=qpdZN0mE)iD&n-Gq@ex zHm(m}1*u7!1)o?4jF;YlCa6UdKA{44#*sLscOLo*gRXK~=^ z1wi7f4!=-Ee^u~5%>D^tL(zwf?>E9}VTlj}ZrvWS796E_{mbFdJ`KJ|XFTWC!3}UG z#DZ6KJ@h4{l8lkWgP+w`)=fTCQ6Emt_e4+C7L|;gMjF)nNEsEiT$ ztlWu))`q`~y-l-B(~WcV*L5|tr*XRtD9?jnHV|6LwnP$03~{Ow;yF0BmzBMhI(bjo zUg?iuXMY`GD*v44*`+MSwE|JPfY%AVuu6OJelCVLa3gTPd_`x_x2P)ADct8SgPOkw zdAJwZd)x$mKfeqq&3(D<+@IV$wlO=74Kh}?2^$3hbv#$f%?H77E#Cr~_lw*Xb`blU zsluQxp&H{((FQu?Oz&rJvXAvX^FH^az-0bz|ftxY~WTUfzqCg3VNAvOY_6AP`)8OUp3Elrou&k40 zV`M+ziP$CYrdWlurY^W1HHdyhP2?f_$#gZX9;->!{;MUR+Yjgxb+vTI^>>Y|v8OR$ z%rTuYCtD6$=ZEzQGlfTn#aOkLJ;p@CA$`7Xo^G)&SzpUo#~4Dk<{U#M!xp_({~N0R zZQ38|kEBm^j~I#cfdurRKgnjuI>|RH*2|-zZstI_nW_ga><;B?TC!5R@@CPhd&Li>S%r-?}wW;1?-`J{fDsruEH(!5(IM@ zcuXOj^n-&}u!eepNHZP`(s=aTt4KP5VB8RFu}aeYP>6Sv9F`ow&e>4<2=imPtUpK` z9B5>(@Ri7w4nP)c?f*|oKZnzCfc&FuihMfwBFp5AVh@;xa}?o98?l7wNXSwBtW`Bu z3B*;Rlj>i10<`KMYKg|Cxvpk32K^OXgzk=Rm|>d1rF*Fxs*g2(z*G8YXk89JCY8wVOz8=h+`>$LhKnqHbs z>RqaTl}i)?h>uY?N5T~YU!hUEpy~AB{KuPu#G(iVfh*tIWsqFSx4g zcq)sMVK!Qec}`V8KKwtlNS~lC_&!rN7z1wvEA3aIf@J+q+blTh!d-_UtKqN*VWSHZGi z%LnO0%r_)#K4WjPH@UW4G#k#nL#j^d)%0YEd1p9jUcwkL!{R z-}Lhhf>CGAG%YlhHQhuWE2FdUF^`yL7waYh5HqmO4=WsKYR-gf+G}NV4N)* z0*8YYQDwx3et`=$4BV26fhc%RYx&1P8}?ZA`KzIVZv&!N1N=GFjs=>p&hBtjNZYsTT z0uBIe#)p2~d9ZepIh9pOd>) z%T!lXe~=qh^P$N4uJnlajs8ms2Kc8mU~zPFy!-O?B88|X)&k6FcV&geHy zF}uwRE$gjS!aiHA)-%?bVPh@vruD`%h84yd#zlt829t4yF#<`(gG^bb2By7+Pln@$ zB7Hyoe{eW_BY&vA5bu-=i9gAAAi{4|RV62?_7mNRwZul{57}JFQuIg;1jj)Sv?JIF z-nw3aav?i91nEc-P{Y$(A#@v4wsrMz1Rjhg0-5eG_`bF z_3yNY)lJli>b0sy(0ngZu22NPqedbE-nj(DQ6xfC0y}H8v`y$u;2Fp%$NY(6n(&D4 zCTPSM;T-ppZ-p*aD%{UI1Sg(P1mA+K#S}4>SS2%y{>cntquFxIC+0c(n3ZxX*&=2% z`;Ps}Y@t6=Dmo4Q0xR_XHz^*y^E7G-{ejM>Hu~OUW^SRT`r^H3G0m&((|Og%&wP&E z*oNB*;a%;i;+=*cb@C**zqp@z7I?b5)$WIGljpIU@#K12c^7(kZzak_*{N!ny-%j9 zqPx47noH#&e>0cv#|%Z{NfWv|Q=8quPDeHTnOn{MU>~q;xXWBT|A1SL)m>L8;o}5@ zI21R{RI!HG2mktw;%Q{sN8#SQ+}{?N8~p;4ki9-H&O_JIQXWBm1EyaEiUiZk4$UcK*x>0&ac3n16YQpCpE9)*@B~gX!fxrEN&>mH= zlsEFEKnsIKxiCuTh}tkuTr1c_xi~}E&F|sraA(;*%qDsXbAmm?65Mjm!<|5#(~Y~z zy4X&ff!ob`m{MBJ+@l}Sm6=QM%lxJnQA?<$OnbIHvy*N_S4VAKk?P^g^RD!|y&Zhl zy^WzjAL0G#zUw~i>EWH|)p_4zX4)OqvB?|oj`f}Kw)95$ZudOzMzT{h*7FN#BWgHnvpBiqtz01rh1_^Rhy)( zr~9JYuidEGrIi@&8&(+`8Cw_!nyw>dc#8GCwM$sB^}M;*yvMx5m~WV3IBFbXtZevh zm~T2@YGd4El$$)pM}|H6wR%C9pr5GQh}+R;RU=i1SWk>awOy5X0bf8T)gh%@-Vk{v z<7IWFHA6-)CTj);`JbZiW$||aJBh@pISlpWNL0nBmqbcf4CU8I!6cLitzZPdPSJ}R2m56X~J3L-6W!utl<*b z2-e2NvLD#l>@&IzeU0e|PtP1?6Vs3tnZwLO?8A1dKD`$o`{~WJ4tJH?bX}$zo?i;* zL*_tr#*bXNZK#79V& zTn9S$C2++1z`xm8=}@jj!peT71w=mssN$oETB>QPyQ)Lz>2hRUbso7*?ZKX#31#q2 z-G1GAU6`KMrx|!d05g{iQ;unprN8yC^{RCuk}A7d9+>->1Ez4(5W{wZ-dKziIN3PD z)X+TKOqz0y9#ehOWy2-@gTEO(*N>u_PgMK&gFo<4?0^h|#NaqkciQ`(i92zl ze=P)Yvzg6%Q1>k6XYrT$rTk2A@Jsku)Y>PwMz|kmaS@!4{f(M77HPnK{8>4?{pY}; z7eU44x6y2QWwc zk`*$w>>ue^*>!nCc_mP_55vd04nOJw$4eW9MlML7$}S++<`wAX7Zt4)`3kSH5>c0s z67j@DxR|nt=Bim_1G1OuDKS_z80Tj?=1L3EU1+3^QY$p9+OFQJP1M!a?b1C%UZPyT zS@&MoQvVuRdTphvWmYi9m|U#XRdh6+PFJ9#=oWN8x+T4mwjm$H z#+EPwQ-K}9{>zSL7qIu(2<{VmnI-uD-ML2M^UVaKVV~F-tFkOrauZCBKKE_3+vE~crlNJS%7HeDYnRl2{O*4(jhKo9n_7^%9pLBio zF2g(H0^>r{M$>=B2xAXJIsF3d2F*9}6EanLVgl0|Z)0bq=u`t)zat2w4HY?xe-&|Z z1)P=?R^lXom^eykDV|1;;5hDQ9?&&Lqi4Ss{K_qGIM~r`v7y2kiu>wcNCRpnR2ALg zHQa0Gp%*s>nLo!s1G)vu-$-yp28pwg{Llz9y|Us~;gWDroGxh5L1B#+PQkifIuB2L_E_y^o(FIf^%Hw-S9YgZl zH+nepAJc`Y!OVqv;yRlM7EOpZ87`YU&&LZ#h3} zP%YLIXMl2j4!5mq{xF;~st^N;A_<~o5m;c0{=ctZ8>+hnU=O>30x0N#(2LM`Nk>#k zou!qf#nS&k&m4)&pF-(ca83g<5=kh}&04Jr=F#msO_L@ zh_1v6onGHvZ_vl;+G*;mZMv9}lbAWo4yGAfmivP%4;@i?%vF1Eb=k|zAf_kXmD)~aQ`e|}=#I>C zd}Uos1hgqg;iVHTl>xRvfoi_{Ki zPD8%3KB?E|x#iu1KK?Mz5=_wEdQW<9dKP+Wcn(2@H`E=0kE!lD?zzwg<-5zf*SPv4 zQK+S7r{{-focE_U#n;_8!?(eA%eNeAhaHrJHqsBF;jhmurn}Rt=~;}AS&z@{WR|lP zp@{mMD`7eIEZ32@@h5TLsVcM+MhKO0?w5yde7`tKoQ7LmYou2B#gSOy3&9ANfN;Gj zuoqmUN+86~lhgn=t~>f@6OorN3G<&7a5v0Fk4Gz;CM_=;B{#vhcwSkB*nsq+%c==T zR2r<_rk;ePzVqr%nmd{^P~vpbDfQ>|M+{9(UOdzHmYQM8@Z|6&*2U&erWT+xtk*6C zS*0qvj4FMU;jrO{VW&}TT5Q~97^$DEJ*nPEt|B%mHE=&~M50?7$aNznBy=b{Wp?Q@ zSuwH?Y_jK)ej!;PN4N;JT0=aQF`P+g4^4(m%oA_=ub{_}46gVxFs>}vi{r#OsGGau zmQWFye`QM}Z+66Y2Puzjd>L#SloWg0l z4fM(8Vl8p3P|Ba;JMs^?9JVL2497vi^njU#+|>8%c-%=p!RK2UJG7N+g6A=Y4P)0J z{cjN}tuE{|ZYD26<1-0tmp=Yp{{Qvmci^VB7Ip5<0F4v2Ch}aWfv=Ytyoj8PhL|z& z(n07u3`MHl8|iLjRs^K^Qb9UEmWp|OA6a{3?R}7MKswW0u%64qt2ho!;0K^84^n~@ zsjL8E@HXWqOxJc3)l>mhBQmJ^rkX)ov3|aTi}47nva9BeW&<=iR%mh3^)*oQRx{2r zzJkA`q3Mxvp2=qZVEWr!(=yPq%z7tmT{szeAZl~;)96nz^KER5BASd`5OFK4g=L=U zoZ*~dDR?Qj&8+2>wR`xC@Ht`q!nRq*TU^G!^<(vvrWloPMdA_qzW-v6=%@S(^yg1d zP&kP>$auP~yenTLJsWI*ts1lkwP1YrmUfjnQA3_WuHqpuEc3xds)#I$1P~3fP%V`S#snJ$PWk=* zvVMb~1RK2=Pqs076xaAZc$XeQOGmO9Og1&1O2SOQg?rH*UmsrtbsU_QuhcE777Z2z zZmXjxGycRBY7bRHJ*VF@w{XUOW!G`fxX0XnjzyLJgWHS$PUaJ!t4!rvVn){-d+=`I zfbbd>`wyJ3yF`oMC5{n`@c#Z1gW_eeDsBOse=a7cJwR3Lgp{H#!8}yT%RrxB9H5XooAh3eQd2`u57%fzk~VO zO;wEQPdFn(WT{4~cj;Q_1)R&*^bxv0H6^NZ%6LV4**Qta(1Ac0n7~ouA7JtC!&`mZ z9}8~OBIFZn1XVSGyT&x9xA_L+9(mC_)NAzh_0^-!(NWlY7oy+Zl#9hW?!mo9AN~l_ z1`6+DItIGH>GU7W8TKD;HaDHK@M8t9Aj9d^0=keVNO`Ks@8{~Ui|=Z02LvoQV&YZ&$Q|Tw$(NPOro5XzZNhT;VL^y6sx!Uh6sF35N=)Bek9~(4S~2vxGj4U3m+X#J{P> z)KOGs2Amo}q<-(qlS8Pxv-lvNB2*G`kdw6u1l^B(SyY4Lgoa|6zer4j(jfry z-AR8sIu~>Obx}9;0~`NeFmewE&IQXO(W?>iK19i4X;8Wo8GFw_g|Cm~!kw}f@^%Ux zbS0CO{c-ELs!AYxscUJZnyZ>Wv`e6QkJM%9VhwK$k;WP(g+;K8vaS!K!}^5ZiinOV zv-F?}`M*3D55CrNc(wO4J>JvCG^HpY4U&~Q>8qr0I_B+C-R6)U74Q4P>|Cp-D? zio4+%lR?9I2|Uk<=$|#TWb>++A&` zgW8Feg;K6AuSM5lC0`lqq$6L8r!nFCCOq;V1=Z{>x-zqZ0%o{d1C2rYohBX@n({J! zEcbV4*^<}taiI)6BBIXXE^j$V#&jyg^l zyj&ICJ>0k5>pUa8gOQE9+@thHd2e|}dpvH@y%7$E9QX%Via-@zz{Ie9kxompxvU>k zxj5XB>p@$zlG)AF1I2SYrvkHNDqll5D4YR1Yp~c1HTr9G{3%Q;o{K8XVV|N4^HrGq z|7x)U|5>OAB7)nH^KckxEl;7mvLhFwQm6uQaU3B9)@vJ3$u&rg6J$H&%fMS04mSQc zOy;f-RnV-`TVOqGK5Mw3+pb-s?yCNwS*@dWtqo_57lJVa!;zANIIH zz8T(yf&OneGp}K%{(+yB!JMNyly>dJYnU;Q7bk*?cs?LNStwv8zg7~Ew2&rALcyKT z#vFta_^~h@dc191DVqle+)d2d6sXOzx#^hCF2(Ih&n;mN)0L@vUWIo8I*a|_UwGzP z;GXN*>^b3CgeUhFbzO6u!R^=?teeTh`maNkrL*W=%y?*{cXJzf874DV#h-!EsBV`c z^XvZs zpgX3()81Uot1qJqTt#+{hlL>6x9oz4i-ceV>)|xjMPZ_Qmt{Z-snpq3O>V(&eNQo>7-)3zAPlCbl zS+k3DA`Kx^8i(#?Hq=#-!9&4l$vtU;Y_pV*S5Tgo_d ztXNHL+jh6MZKd9tscod#l&P_`ZM)s34#zq(b8z3@C(qvQ&8BTKbIyOh@1-hqEbPP+ zvJ_fL7pYRnWJ--Z*%OolJ!kXC6;v^L7$uTb!JZa_ws9i0fzr?u(Bak@%Di{zqd3b% zvJ=rMx0yZ1tYRoOmDz+O&eBk;eL@%R5A29L=zGv@{{?+;Kg>;+xMXChc=_c>vAV!M z<_zi#?h)IC-v!Tfh+C=lXnJF7y`Wo;%zzihilzqU|IAG-wJme4)gntpCPcijhOOz5 zny5#SFQWBvMdJF#)sMfD@GLPiQCA?dK+OW70*4A%6RO9&h+Ja5Yu;y^VQg$FXzpuC zvzjA&M0Sdr8?`*Le8fcaAR}Qofv4_hW)}KG<{h%Um;&=f5HleH-uz(}7ZhEgWR0S@Coq`jG$j`Tt58S<_z z^l>tUI*4B22xceCa!K&frl6hl8M^`LGK<+F%y=}B)Fbpt5Y0>zfkBRgn${XDiw?s) zY#jx{yTl1dupcY

+0U#zF9JK0mwo$nm$y5QdKIpk^QY44T%)dRf)u|iiNDEt-N z4c|$9p*-{%)q{-CCKwNe&Yup(OzA)QE1C@YA%A=^w6}%fQQoi0QXZ(zp=Tl&+2JRU ze_WI3hCZ>$gd0uGB-+oaQ=`y?aR!+}S!nv|O+LUp$P<-S(a1||uezj+S7iWQ-#`|n ziqdI#=PxnGF^y{2M^J;$V6vFT=o5TKzo7#3M)n#zm#d?Gha|ibKoHmKwZ>D1SVJG< zK=7fv04L1DCaCwegyU+oZ>{&1=P%zboPuYBlA*)lKGH7vZ)E`- zTt8JJP`cJ|9Pp~J++ExpIuu$9E$k&IVwXeB{84ES&ORH>pcyI|op!WbTr`BA2zz`6 zu&XJaOlLh;uFL1z>Pm4(dWU+Kc)EE@W6~PrO>hr!es&zUS@Ny+k)c>`AE%;lH|FnODZzA;OUhfg$%~kyWK)d!bFb!vYvtZFscj&nr z%HyPaNFch6KHzp@4!XDcheig!WAgh~5JOHN+$F+C(FssoJPq$|FXc0MyMoe4@!_AZ z!=&IBW(TW5wNW%Q21%kLF;6GUUzF$A9bTgAp$wdaZ_#x(OMWWVQcB|KJ58CQ8bjSe z8{}-72maoplUg9$Thc!h>s{3*(q8TH#7cb ze945V@qJ^|k?EEzhBcZFyZ}V0EZvHk#u+u2v=g-x!RDUR_teE|)*$C^F8h%@C~iPz z3Kf{+l|9crqupEG^E_pIb+I?31%IMtVms6Vzroh+46y$Go)_-FJ&!!cJWlsH&s+cL zzzA&qGlCbTb8=hgnER1AR8jN_B~#bXroD?8s)|$6*pB40)KamR6&;v!uiTu(eLeNFvG@w8v(i}q!DAGtFe zE9|o!=WTOycjSJyjkPPeUvsB8UbwzF8@T8A%A?`l<~L!}`3?MdR`3B(LkBM8;S;3;9B7P7Gf%zQx+A5@a6EvG?=^_7z_pNs!awxvZ+|t-T6gXjpeldjxra@xTwW z^z)4YbEZYK6p2WU>=rdR>Wj6IdA#YKwhx;@{b@y9g&pe-6rbaeXcEP!7(ZTDHT6qn zQZmKdU`VJOSmzn*R5@zeU*+$BtM_`Y#^!_9a*|`MJI9^nPVpvsYq|%+-Mkiwi#;4p zo7MTu)ymnou_101wdl(Djq`ki(iBr#9(uDnp)Z^V%AY0N_V4!t zyANjvUkmxcfnYo-u-(0Y_gxH}^@34#ly;oT59MCUX`6j6>vfh7i3>|}>*PPm%gk?Y z``h+UethoM>}5G6az|zr|F!d1fvguhjy;1&j z?>6s|z$xLL5JpyMyYRoE63FU#2xPK1QqCWVd(f%ZMfNJ|RE1OpknJ~(8ckJ)?1tFL;BR(pQkkIaB#8O_6<}mj1cE{=OgXf{v#4%g%IHFK3eD zw&Rwwh36k%bze<5Xx2g#)Y|vNYX}SxMg`*h9|L(o54_X`0=>{WTtEChJRSSea3w)i zh{&T_QzKNX$r@~F_8~QmUd6^R&s00*;h4`7lw6#DDe`Jbhm-j?G!~_i4bqFKEFVUD z+b9v-pKqkk?kns)3?wnmy~lOi@xgh})54eF?eFOet;8d*+uP8)&%FxI+?TGd&hyT> zF4FzORS@2`Q{ZNM`kP_T`74+#MoC)CVXx%Ts>@_HIT5Y*gMcR;A;(}AK16PUD{?A9 zpEY39>oWi`m7bL(x`?&wxgvn3_@lR{JzAZB4CC(^ad{ zH#cM$jv0Cz+hK3{W*%u-YrPqHI4UPH9Jx2j8x;}rI<{L}v)Ctb@8YW_o=cjabRJ1R zHIln0KTbRy`z|^^k~5dpU(zOPJMnQy{W5D0X%-{<@ttOXwxhPU`Y$el{fc?7BpD~U zkOKTQ80FpSe(de0F4)T0J~*x-XLG6j zwDY8!aFww0j%BV{&OY{#y`{5}^NVA+1sJ#C6$0g4Ht*Y96AfXA`Abk zk_s-pCD5>iP*UE6df9+pg$+m$KP(-P_R5K>YM6*75f@bkw8-@)PNQLA2+|BI;QM_9 zq%0ra{Cc4|K*?(3uH6@kLo-EcsBBn|KEhtoMIgyfpn@!mS7xr1jwJuC=#?6&EF+eo z)9wgS4%&%ctN`X`HoqB8jo#Xuy7GpBhOUO2#`C6*7Ki1x>2K2}iw_)ZjAgsU5K$!R za8$u4M|AJ_Yw`8t%EaX-bV;lbTQ`!i&N6+}z2Qz#FUT=u9cVP()8DBv^e46s6G4UO z*$j3tvM12u?(+WNjlh*aS${h?y!SzYt#MU!O?J(6Z}tejV}XIfb0Ib44=;xj@sRW$ zZp@;{1PNhZ?;^DU#_?F>kiPO#DWz{EeO3Q#c_YVerdEPYdIiy}4bQE$L98tD0 z_LZ(R&aw9W&I#^ru6a(KYmFzu_uNwpc|C1?>%r~p1=dj{kSMGVBnU~NOlX6v;rh|a zU6caQJ(Qt#lP@SEn+OIxnVZJFW>2zbq0e3lyu z*5P#ystNTvb%gc`QrQ1dXY&=*t&yM8mpMxIRXtUP%Y=9-jf9z%u7I1Kh}_Eh^vTOByz8rlBKdzXI&c}EeB_YM+@sZ`J3-iyBe z{sYLb7%DsoZVtUizH|!a^MAy(VkP*V+DcQzJZS}-Kuy6rU&2(cK^xOZVlvp8u|!!U zSbsn-Qvz`l9eg9;PKXvA;227UJA_6<38Vv#@>$3hS_QXcq2%{5MilM1-sz}K_8qEc7&ARt#DbiudYM7^-R@1VlX+KjH90+8?hAElRL_P zP_I{8G)ApM(?&Bz8`Le+)z*b{gmHthI`-DT%u_6pmTwk5B4BM0kr}Z(qD4f{h-m9% z(@}kC__D7s>10n;Bzm%|E6bE-$in{sK7G2_4%kzw#o8AiMZSKna`>N(HZh3mpPnib0cY8R?Mp174QaQUa2OW5lsRl@KeOh03&sZ;WR) zn7bs`1cwez$He?;dE;_l<{ZjtpED_2lT$vYR*sOpJiC9+&YZD1d$RXt6FFSYyzI)^ z)v`0QdSy4v9+*`ri_I>Q(=TUH&WfCVc_(ds?TziP?RQ;g;4jT{doVSf3e*fd@OKSt z5GFxyR~lS?GO+J%0lR;i-v|fsia@DANC*a_L(hYcvA1u9D&RG;pS}Pydlsx0`VA-8 z-M}g#7p!D^|07?Xe{5itzoG9MwyWBHzfTjG6POvu5A=Zsp;hQWa8h``v=|I`9dQOy z;hKOmX$-9Ox0EK6K$!bNh2Is5DI?TT-`I3a%2m}T)Md17bo+G&bu;uGfj4$C4>F%K z9WuAFwy_?vJh0Y?>=juu@@Hgb)V!$s5vQylEd9;*j6Qv;ZiIG%W+rce-suk4m+L@} zqV~bD_nNXJdqx1?J_iiVdsQK1pRR@mO>wM1ls!6=)Rr{y=n+uur1+QDg=LBEuuIOyo<=O?@Y>2 z60L!LcLMhRi})E={%QM$tDr18$`~}~-iAe;R9q@eiq_!*ZeEQ1PcpS{i?tN zaCoEqm%y>E^0)UD!}l{A%BYHg;cx_`2TDSb*dxdR@hBI*1HF2Ya20Wr7zK4Z+TG-r zQmm{+hc~C{iq7~j+T!P6GucL5S1rePmCa&DJWyc&nZ){1Ysi-9?=(>D$Y?b0*w75ths=Xdv>D|E_F;p=q6u;= zCc`ffO^ri_L`5oq)B6lm84t-WvXa_xn5t#!>JDmpQ%_($P==m~g zo4PcVFad0^@mjUEx~`9YyZ$g3pIZjnGy%$ruEqkUXC{ZKqWO$@f_bRPY$|I`F>f^W zGpWoq%w^5p&7^s?sgya%9v;7x)|+q zjZLG{l+wIc598-yx|F$gd`a#oFqsd3I$|lFDnczI+ri&e49)YO(0p72ebFAV2XYfe z0H3ZJt`TxT&9o|T7FhidWCV2f&+*^z&h|tgnYOLlRNya_>Jp^bFK52 z^O>_0u#2)zy=yxhu(ROQ8R7V7*TNI9(NWZK(NPRDRefg>XFYtBfQKi>Wphq*MIq^C z58PS@-Bmn3q(5;TLMQ>{<}I{~R##;z zOXNrBr7n(6Xh%2>DypBMCzw0SVe>yC%t2mrH8^x6|2ltbza8wLh)$(EZyS8Lyo0cx zCHac@TKOVM(TFzCh0%wm+1TIErx(0#i%zLnlq1VT?TjiCy)|lk)Yj0g7EcN{$>J;j=c6C>^oGddbqtQp8f-{L0wE*ay0+*ZL;g(#8$ndR-{)h6+M=BNF)lEoA$hc|-e{<0p8GEQ2K#>bO8C$E{orZqK;P3Oupw|9tkFC0d%fWL za>G613S1K&!^z1CkAOt)MB+dL>^$Q`v%>AgB+Lj6ab1>0eqaeClL^k_M-qF9Ubt4QWO5g=%U%ox&+!6gCfo#z%sXe59g?(&;{HbB-^=;JQOr@(aU1*Z3wuRJBS$y; zdfP!;efuzbz_!zN!}i+N)=t|O+t%0y*(*7WNXW~yErWWht>dh{6wdEb&T8-&7@Tp= zZO$*wj?Qz=mac-XUCt8lB=m8YbVs^JyDNAWdQy=TQ3F|J8Qwm=e|+{+RO(G zdmr+Ux54*+6&to&s)h#O&QeF5;P)ix3*nu9h&GG+U{_0G;<`Z0CBoR^u(!fpyNvt- z#YSCfJe7qec^>#vN#+`Tj2_Cgf>!b!I|bX;*#D~|8&9BU#%tOtGOcryj<-7SFRFu`NW30QZT5Hg9 z(b_&@W<*NFJ!_IxW!-McHs3JcGP{V2F`ajJPSvB_kHy-x$1oXcx(F__*!_|dzN`7!?gv?fzQFu;-Yc(jalMmzgATFsnC zu0v7g6!@fGP{00-`;=m)Gbfm(%vk0NvkV&JN?d2~rdF;B*MU{DBiIvM8~z#R;<~6i zYTl}EsOM{zXcg^J?KDhcP4#O10Np-ax_*YC5$36ihT3rGwnu-=0Kn4 zH}y6TGY>XhG0rj$HP$f<)<1>T`kr=>W|w-E+Ne(DZ*W~W7D>8BaN1F9Pv$>*2GVve zgI~Rg1l*hG9qWQS|2VL+Ol;j#;1X*fN#O-h9j3ssa0`09`^c_19$*6we=(@zCPI-Z z!`XEJ4zC$Frxzn{W-w6l3!c-+Ci>=ifV}cAo_$1I@a3XK)yU-6l-Bj?2Il=YtQZERd0AjKfT*7teZJt7b%}CG0`cjg- z3{IdJBpNuB6X=zTK}tYL_rrjQM*8?PL-&6NgZ$iqfMq|;8 zP>)joQtwu8$J6{gcC2&ifABP~0yoe}O_rvewzIC6E>*WdS6TmF|3QBSeuV~xgNDNJ zb6z*{CYy;c+f8%uj%~15Ek`U{aVLi?%Yd9Uv1VDk<{jqs=K1C(mN}MrP`J0Tq*!9C zN37kgg{`U9A`yahlC?Hc@a{wZ-qG9;&W6V3e@#gy&P1AK7*h>F{So~h{S%!==g?l( z8i8*O!jAu5-4uC(8ucvx3tC0YTn1}mlbBw#liEe8sISCk;w7}Af@%(0V?!#BQXYys zE!ql?Ao1gq*c2O4Iy{~kp_^c!O`#g0)u92QYQbZ|Yk0Y?2g+d{4&k}{1JCGN=*i@f z`nSq^66&hLP(Ro8-bR|w9YvOI#`QeOr8z?~;EEcHB#WK|&2Qi**1` zR3zvI3)mEzkM3~HRDt54H{7?^!i~h`(4YxO;_p(gMj^SJj*NdlCx8Q__xO9!C~ zS&mM^z0xP3eh=U=&%{;IS)L+W;f!SAdsu)JmydAOFIUc^6MPU{qgG(%PMmEFPPqL_ zE*dmjDoxPy_ZXczx0G*aaMY+4;VVxlHQ+FN2&MdYRX60TBoWu3pTCNR>sClza*}qk zH5H&5(gTnxb@%^j`QeO~4Y5sex7}wOB74it-QxQ2=aGil6LaA;XuWdPUZ`(7!+lm< zJp<`9T6Bt?<(KeAD01IIG4u#CH>3TjISvQfCe1>m11XM4?2$;%|;8=@VbP@1e)#?&~?}$$>v?k!!Q-f~$k8i))K36~DG~r8!$V zyCO-7b-qICcPz5ouGy#CIr|4&7kg3rpX{2N_A>UMZKAC%*s3^t1fKTWZ1?Q79YOmW zdv#!E9q=c#ckXdkab0wcaZPm1aTRkf1S>VwRR;RPa-N1jS$25NdbHktm=wQyZQlJz zZC(e@={h`N3i*5culgGV#=+^>1J1Y>!c3$~wZPT$OUMkSgtSn5_eIVd8@`O(!tUW^ z;ak`Pia`561*l?z_yM}6|9}})gNE-@_^wz~3P4r83JTtD;@?n~mX!ucFQlTlYHG?R zIT{U_3vkYykqNkdE+{9J4(Lee1m8q+)iPBPq7ZQkE%N~=qvj*=y#yS_XOTsDkd(-4 zR2#Y$)V(e`lX=dx!!x5fI~a=k=kSs_&?>T#8^i5Ge!~~&Id}7^{6O_#wN?F=FRgx! zDzuzbzmjdKtrK#LMj+Murh|8$!n?oNSq5l+ zL)Ru(9$M?>d8(t);=X4uocqOnvv4ZJ_y_xE_%r-Np(^VLw_9_;7pMZB`n%8S6V{s$;$BIFt-4VG(Z{d7$!3~myOscxz zn>S!X?JNzFFM@?^E1!~SWj{P^*Wrw62+wdIr8TnmzCx8$1rF+>K-MM^4%HnXJ`8x3 zXmSS5$_C^NGg5%{%-aoxDiTrB^XSK)qNpst}w z(u~2~eng$8ZlzhG*{CU}-HEwiI+%#*m`DE&FzBkdloQFl-~MB81v6)eRW zXhK(Lnrd3BP3lJK{rpvK2)yVexp=lP?)ONhD07G2f^4$>)DU!seu1;+zUnv76S~or7nnCVU@mrXENvDv#8|n#j+-64)A;9vJ8E1FWv4KgwUiH{5#y=kp8i zYIu0_Juc5KPo8JDXQe0Xeu^C13izi4FqHl-+11KD57$a-AvJX?=I$k z<=XH5?JnYBJ@M#(I_CY3C;m!r0uoc!_zwHd`SkEYO@;3vE#L`M1a=ySXZ@vMk z=qCQ8rP-Cbs>4tltwc)KS=C(`t+L$Pc9J9;tfoCC2WeAw2D%jIH zBEh2uSsHWDR49)gq4#7b9Eyp|Vekqk7>@mfq^}gV4|KXiIV%U7AM!eub4U26>Qwa^ zbZ-2o9-yhMX$4R46eO_R*SQ7`4--#%)2tIa_{~~3K1&UaP znog~SKJy|qi8@2Igyv@=?ZKXPoL&mGVhH(lZ*cz=q0iFCn4!!N+>LdaEleUag0|7k zSem^>cSCB?W1O7b(DV2jSm_1m2kUS*;IphD#;`zNSlAqf2jpS0 z9l4X31uk$YcKA)uoxA})Zj|px3Noe6htGuD04dK6y+opKX7GBjBK$C$(IeL@*gJR= zI;~DZ5p>vg4Se$7fhP9_lpdepxGLaJ_nDB`#QT;Y)8Umj2X}LtHwvf3J~%2u-cmld zw>gm8QNU{^;0b*WYWijV>HfFyHQL~)u^}JgJ&1#HzyNcEd2qE_gC;neR|iW5W8h~k z6)G5-8jMBf^l&)b<^Z*81Q$YNI1TAPld*lw5B~@|!WGd@{aI`c&2JbV#i59mWfz-@HL8h9~nN$UMW0UCW^k<}| z4W{n_gO}*ZNT0pRT!8L-BioPd$?j)sLUUOd|K5vhhIi{eJC?i4r6PazI=2w6*k?%n z`OU52ecVxe_Axh>o5a?`U0e#;6DD>va{!&!>2OLb-~@_87khvjLslg;z=>7RFINB? z(Fa|%9aQU;O>%E}BIcB0a*mWJrHSQ`ELH^ChBru>Itx|jN+8t1;Dulid-OWtWMGd! zG2rt*hV$$+wzg)zwZ3lta{dmOt5*6Rz$w!bu8WS|_Ff*C>~&8G^v>+{{P3iEE_gmd z^EtqC+!N`k=^5skzVtJ`va09XwP)G<<7Xho?YJRUISS0G;cTG zRPR7fWp7tsUue==`B>n!2k?w<4UX%Be+{xgo?(jnA{a3F4}jbGO0X^5w%hR}&w)47 z9c~Uy**@_yeCLOy8aO>Jq7CV+GExc1EmQ-cjXs6cs2*q$U#+^SBJd7h#dV*m+5~NE z7os|8BdP(3TTJ{Q9aJA=SQ^2*R3k2tg>k<`Km$9U>`pyq%Cm{|Y@h)L=rndK(*)Q+ zF1-Yej6IlAKyV7u2kG_9Y~)>)VfwNL)=%;DXz0N&f~nund}oHUU63fz5xIGNxNocl zJ0!*T<9&Fak8{JgRBVqy-igm{<@4a&DgzH?KEH*(#CJtg(@yn0-okC>M)801n>Z~u z4|{?P73(Kv3i})R6h+u&(6gG@Icy_#K2wfSJe}oc%-V8@JP`M1nIdC7preX(fgp``I^aIXc(t)z}Pi(b05(kHS1|Ffm0N#nvg z(I{V%f8aA+u$i}&9*U)Lg6H77x{dSOgq(~v_jd)dp}C4Lf@&Rk)-qZLWb^+Wb;Pi{E-g6+zG<%+YP+0p!M zWXZH;7jfVCD834^6+3d5x##%m!dxU*o!f)d8j?H3{>zd;64rARx-JXzM}a+}eFT%t zc$^?#+0|g9d^o9kA|K@i6V3KV_V5XyeV^f(7{hL1ui*qak94pZ$O3P|48*H(p4kBw zf@i+bu{e9iAj7si!{Rw~i7v$yWVKV3#dNH~^w&Saw&|x|qJ%Kv< z04A^_K-@OKJ#!e>^I|fHgxpEkf~O<-z6zO0zQ);mhP;ToBZKTl)d#w~5%_rp@-H%t z+y#ESAfCk&$l`d%ijhNzlcbKSK{mp(dNx^%d`{FM&f|%_nQ&reA537fM_%|g@*?p~ zwVj~g6j?!(#QdI)d;2_*2~W>;@G#@xq^t)%qax=0SzzmVDu%j5b_bI_pGu>)B40U7 zPN6p7-n&c`1qM13t#DP~3G^2+A zH)ip54XG6H^?y?eb_qLK5_;`JV12&hdnt^cx)h#18Bj2^B0J$c+D5Qs6`T#{RsD%Y z#BXG_UQ&HkwN+{1+8zeg_ZDTGl8rR4Uh);>@s^f;APbU_qj3M^;Z9kNndc*>o@G)O zNrk&-FP{0|pxk>Z9h0J@6HCvE|B7vvx<;!rpWJ63b?Do~OraTlJq<&YJ25tCQ(aHVi>Fk|w z;f)(E4h}B`m+~VN18k*3_(JGb=IhlxuXIyF@+WvH z*8{Yw`^DEBk3&<-uDsRTq{T{OPla&gJ1@7wlV6Iy$ z)A4710blh@&Xr5yXI&=GL(;P#J(4D1YHW`rrcCge6_i~{cVx$Hl9wV;uZ-M5jz_N6 zJp4ITfPD1;|4l18>}zxJBz4FtWMf~I3o0J4<>RD+*u)jQ;*X>YaN)I-H{x1tj%&4< zk}qe=Z}Eh4~lPaJ)g$sNbw4=EB`lC^_bU1!*Qap{c7Rl@X^Rhcp@Kz4w6^{z+|Jh_9+H#Y2B; zlr%sNFN$g5PT~;xi4rfTifQo5{(OvxsNk^kHYOyhW5Wv7(y$fmii+{A=CQgs$x>qRl4*zoH{%sYRsvXiRW zs?kWLUZljpe_LLzB$IM?X&KT93c}O9963(0a46RY=iNgr3s;RF$fpJV*V4#Ut%U80 zfm-n^d_{rq1SwP85LyR^Q3+{*_yVb(wov!*6QD+i;cy>{T*p@U_!=ya<_vw1MZ@Wg zpf$8LxGh)#nJQgEQ=xT>2e0}zxIK6*_$o9wJSezUSd2}xM{qurZd2d_JOMu&fv<`R zUxw$z7`g|3{v^7x^1$r159z|6L+jzNzZ^Oeo+r+OZ@Dedx}G@mc=<5y01GCV@3_*x zp($gAJXX1i+2JtqvLoOQ8jif&xk$992nG$D>G0CsQ5?{d9aFq$(de!Ef@}1gOyOR6 z4cv&q`!X7*XKlH%^aaT8F>#VuS`5Sc3QuF`0+J8MhMtFtBVFJKIIth#(S_U{1VsM2mIZ3w1fA7<~s!`QorP?;9EaQ|3ZnaRVqS5 zx(|J78?pUV!~|R%c-JMR9a3EusT{z3Ppg_Bf%XA$47|i1q7L%tnu5Xoi8P;T^l3b! zKhr5pVRj&r{nObN9D{DDhpf!q1;&xis{hX)G=ic(hItIW^FKWGmO}BD3B|%G>M^x~ zY6X|!Vmvno0NZYkUCBn)Cclz5DGA8T97Yr0dvGSil9=-{ZL&T#C*^WbNYHX!xDibJ;F@? zlsHUSp}tV#$v>IwM&2cJk(r)@xuY*qtzz(O=J1qv0ME_F&v2evg_ie9_)hyX@tC4s zV6Q*LrUUJ(jIC@NSC(H4ZO`A}@zeQ|Q1b|UCO<&k9E}bCAtADF$lR=pYe=#tDd zCW*bkc%WC*BQNnf5VRcT7adR6$MsVT&ggb@H7cGA5aX~npCsmCW61!YewQjqF9WAu z0-e!TYApQ3vyq%%k$wdxeG0Ckope{a43HBZbJ}9+81)XU$Ve&!d>|a6~-xbQ$u1K?wgo5rSr^D+v9P@J`w-k*x|02`X$z25dV&!Nc zx(o3-&SDeT515uEW-7Mpr?ek9aAE2%Nj z$mGH}QO+aPqcqNQS}K7|lBeN!xVKX=`7FRhR2>?}Or%X06MuvbhHi&1iUq}9&@QaP zu00HiRR4(O@jJVq-DH5&6iH+fvJ(3!jj)+EfWl}g@e_#kaB>;$W(qy_7tsLFl$?WW z+5iVdI@$m#;FZ0LWQp>)p08qpZiVT09JvIY4@tyse9zU9eVd2fDF!oV260c7uBuCH zL$38Yps&ZEbK9(Nm|U{t%3yti&^xujl#n480_*tz&!BRcO}c^!*5QeM2pu1*p)lF3zvb8%9qX`|c6;sy@%wSfygA1tsY`zQO_w9p8YrYavvN5kU$NAn| zbql)TjbLkE;QK(LBwqW!`F> zReewtXt_RN&eV8{Wq|_+Iy-v9lhYNmcQbDxg%s zRCQf0hG}sPUh#K0--{|=(dknkD0qevjlX#frX= z#7As5@uV5AV>6ucKcLO=sBR~f$?=pxcI89g z7jIK225cFYfUfkXKN4LI z?}d7B3jE2>Y6j0uGMa-+!8@@F%>w;{%Y_(Wt?II4h_6AzP5s)l&6VipcaET_n#h-(CmsbwD747++`-~{q>x#<28yvg)NSNaRHIt~3&^8B zQ1SFssx2m>*FZKZAU)(NS|00>HGyk%1y0bInu-&79ngXkK$s>X+cXJj-Oah~{4Q|8 zBluN(OLY@<8lL~1)fw<;3|0R?3VaFm31GubfDD)7F9Ox+1vaWWx+F80E{uYSY%ZIan9+T43%vK zFXw&XdFdJF`Q^R=C-W0`V>ju(fR>D*o@_VgiS*v`=6O?~%t=HucC4Sk?^g$GNH%PN zJEfX z`8V>P<)6sc=axbM>`fevoR^%}oef>Q`v>mK&+cxX?r_+V!!BKnW+S(WX->PyO2h~3_dVsW*rjsd zz#8wTyf<7$&cA-!HD17xuKOqliU9)_Qu)9vBtN?R^~^RzpV4D0qdZM3h=envZk7U8lM>~ zh8?=*=wIlL6#v2+oBFwWsk*4TD$hb)(V0o4V}Sxrqi#Y+SPgvKf5;(NLf58F5d&0O zWtLP^B*N;@Y&45lkWUy6b`EXaEzzA<1R0bnX?nlk=0FgtdJY20*_D`&iDMyDZFQ9kP^zy3<`NL}eak!@lXo*`wBRNbnN7F@PKsMoZZE57?Tl9qu zRgE=GP2nxIoBLRlGP+Z~r4Unrq- z{DinIv3W5oqPs@ku}m~gG~Cd&))v<^$9uGj|ID3W{{@ntLu!c4_{q!2=YR`M1rk*f z8m$FD)fOQWwUcx*+A8Fef5+f8J_kN#?q!8rc$Q+#wo`C@R_H2#!$uZ z0t)+heEh9_qY0|@nxDu&t<5vs4<;L2!+bJRMXB0AK|M%LlFo|z!)1Xc6vn-&hZ5&# zU^ZL~dA=sd18s?uauamxn}riWCA1g5V@H^h#>waL{AR(2WC8KMj6BIjy^*Rlw9gb$+G~HbNb$vg>U}F#TLG>|3n+IDe zTjyF!MNEsR9oaF;7bQiHj5!+fF6MI#8{0SbpIBS$jkq@Pd*Y78E{gdQy*6q~#C1zM zv(+@*XoX5u)>qM=)-6Zp?pW;1yI2j=7P?7-E({g&X5?U&qj$sowg_oePWdtBiWA|c z$T^M+E6@Wc0o&UUzKra)BVZk$g+7M%BSXKcI1sb=LS;3QllI}1e@yRU^z2b46YT7H zHl9o82Jx}#f7Qd#uG1L(^)oc5)pgVdu))Xhv$$k*q2?gVAr3pSgV_&1HAQKtJly4x zxUTA;d9ks05RQS%Xt<$+wS@`7el)&K@N4m2t?^so@!p7r>jVBNa37RG^3QNgXD86c zeKuGcp2w{rA@m~L2M&WT@Py3+>l%<2;N5(Q&0{p?^NPUuM0jag)f=EVH16qY*oV%8 z#b^jtWFY1XiZUX9L4~_)M%tPYUGVnuh z_(Y_;sx|*;K5Mgez4Tx8iw!f4PE&h}%DUJ(Cn6)VO4QG&d(m7>i;K^4Kmy|nleOW{zd@5$a^=O6p z0Keo@Acj6D^fLuJlK0wS3UR^rV-l{Qe>n;rpmJ~+R3@(A2&68*37-_7NnYg%ai6-3 zle-^y@|Ef_&{2Pb58aRK{eShvj9pA^O-)QX^Ez{Fb7iQUH=Bl$9iNNi~?h@9~~n3a1wk( zo;nFEGzwFzQEsA?Q_WJD+Jd4%))nzqfad&ry zr#+K*^V$3wK7oJ9*F%28DYxAx^Z zcO_;crz27RB2|#~iKD?HwGH(Q^+6X?MPVYmUbWD4vJbscC2-fZ4}1#DKyOo{;K9&3 zWV0-Wx?m)5^S_Y~SO?0^4CN`3RhJT_$nE45v`TD)e{V1P*f&EEB46n+ng)QTiACf^k7XRtd=C1-dzX4kyx2vKsEe!oWf!=!R&* zyUUb<7t+c#M3Qo2^=0)yjY=o!=IC<_n@!Km25U;h*vQ1FnNe9$DbeF%9m))uarZQV|!^d z+-gIieJ=x+YYkkBDtN9J;&sXQDc+90tMH}84r?Rl-Rsg{}(Lw+4HVjf0M{5w5?1>QhMf{-~}5U21LZZ_Q-QUiidxniM29 z%!C4YCYOM0ips2u2{VtF-N>c*3O8gD`BU`)84g9kO_mY=211aAf9@frxo;>VC?FZ` zKkT^!(9c#Z@G$Tl`PKz6qh|&$hM2Gkd&CwfJ|@6%w@V%j9ICf!0hC*H$VKFAvNci* z_v6n$fOg9)Rc)}jw}@oq7A_^PV=j76b)jq1W0CPw4mfo;dJmp@ZJ60mWuadk*mVJ* z)(yc=x1=s&gHusjpqEGSp7+4rT#W0-kH*jWQDf7-)h*SpHXJpcHcd7EV`&pXN0pC$ z7TqqkE2fZralhg!#gC6qkDnDkCw@r$?>JRlp_nRB9_tU&L49Wp%Qs+u)7z-_q*HYc zeW1(4HX&O0?mOVU=FxhVqBCbR9K`u>tiJH2_|79!yeN=@!S121=gxty!|n+{6K25k z?+Xy{yQo5mV5$P56(|u8eNq{^G4!UXe4GZ09dfRWhDUIRuF|j257Q6Rd9|Ol`?aT` z5M;E^)kD;Jby@xn+lbN82As}!lqp9|Hj~q^m ztA(o!8lh{t?zlY259^AgvQO}8UBuoo+~@EyflWdTl-2vf7o~1+JbqJsA_`JAY7$a5 zNIaqv#}*qIXDUm zv@ARo_9Hv)A({;i`wPLTw+<~P9%xFN1daxFATuEfnSw@W)~*19D2rShqy&;7I-T`% zt<-NdcXb^x)!#Ptg9mn2M+0gyRYJgtm#s#MFc*@lE2cMW;p7 zH*e54S3BrrqK4c+EE8H9=~Z>RRq|eCx6OL`>-^7;KTW^PzrX(Oko`0#J(tT{ zk+&>=fUT)*I$9tH*^?Y|oTbr|^w}HsuSLSf+8_~L4Q~A|GRyiC-^j7R#beoPTy404 z3Tuz)o}q<$o}rfEmA-<0p*B)eOU>{b+4f9D`UuoLQL0VY%pQb)1=9oRKD%e6`=qms zW1{^cnkl36`TT16uk#$a^>RPwOvt&G9hIGxRXqD^R{88p**$U!=RV542fh8jwjy@5 z!{khM&2q(Je>ZsJeKq}W0xTNY7K)psKMlQo;e1Oa-eH=cfUTA%ZUFrqPgEdk0ij)^ zw2&){^}M!O>8S)X;TXH)6lR3dHRUg$%(*38; zH*hAw^w~Vha?ZLla$?lGsI}2KF_mJ)m=!UfVy4F!V}?X;jAEl+Mr^b8G50pi(9B_{ zl20Wf^uxc-yUSh9S>E0&Uzb}U%l5P9kHg<~e%oGvl?48$EUw$vXv`dr?WHPLi+jTjhv#B3rlYW?wf3ww zLN`=*SDT^v#Lwm!_9rxg+f?P11JXDk`Sn621ut~0E0CqQ5etc6nV9P|4Tzq@ZhilXn>Kzcl~Bz&YVyFfH^4i~9u>IT1gaU))>p5{1A=tb(fWIaskNU{5*&)h|c1Mbn*L-Yb?v zj{RkH9~6eJ^C4QS4`UuZZJsOLB< zJPwxXKCj6SETYlj zExiZ3dwIC#BqU%rGMqK!7!1bs##qxz(;Cx4Q^B;xxc{lma_&TBiDGEIFK%f%C(8w!53hLhy&sGJu|!z#6z%^!OeMhMI=Hpr0ZZ z8|Yt1AE_@MML*9|Y=`7Zd1LXSW}^QDw2P!?yUWxGh#m6Z0>?4!7h>S zqn5|Kim4X+HTF`xk`PL0l{hGANb;KG2L+xL*q7WTxp0AZNhgzjCw))69iJcbD6*pE zp}r13h{{!_i1UQW{zPB$|2R4aFuBe)44<)Qjo3CC+qTWdwr$(C-Pm^07#lQPj%Us} zqyNr-?bUX>X|tQ%GvD{(dG5zm#lARQN_~~G6E=?RSO4FZlqso6X$spe$0c_^-$hA> zD|%yih++vjPHm}~yqY)KZ>U+zQSlrz7J)y%n7rA}X07GB`G?7Gbdi)M8LJvEkZJ$e zm}D4Dt-M>8qEhz0lxSG&8BwEf%d_e}RDdQadZ+3XwbD^GX-i-epG z)IN7e8m%oJ!bv(AmhCfG?V|#B10#d^*%K9kqSlo8-FNzz>cPxVnZn#t-I;lhlIHl@ zc!s%dIpXbYY{g;CTQZt)2WT@!WVFgSnX%ROw|$y@mfdE5=+L8ReoekzPG3IpoVWlI zNH=y3qvf~%uWZJ`Buhl+SSvSmJ`;g5t_&~QL(W4 z-^6T=HO8HZRmN_Jc@}*o>TqOCWW~thmNvpP!~Uqtp-Spx%fLWGIXzG?qj#!0$W}vR|5SyML7a(e1nH_lT6?X{8-A+y#7%10#aQz%VBJ1DK<= zWpCF>(6fzASAJ1D(7%;X-(p92RDD8~pv=ikyEEJKpU@00gkEa7OV{9?F$==wy*jC^b>}6|Z``gxoH2YKb#r8D2*16ePm#xne_ZLq` zyg}Ez8%Sz>Ojc_*|CzwEpgJ@;crh@S?`ncSx7-> zwp_Mjwq-W0?E|NB342q>0SS&ej#~V6-Jx)Lr6esLSmMz3Csm-b8P zQc#}FtoTGQ9C{rts>q@m%uLZjmep#*7UL48iE-qvF10+i3?nPeYWBD9&j1{&{5yIu914a4F7&l zv5WZ9_u9ALXTlql-)CX3bqG2@9#0l`Z}_QlMuCiT>4nour$i?w{}z(x{_graB6(nP zt>kRUKaxA9eoS4IS~s<2+Gd!wQOuY>ItsXEQsaE|oMI;a*tZ?V)JyRk8ltE2$3R>Nb)Y}ZZjD1WO%n8! zxPIRJ{WYa_`XIa0IoOk393y`T%tl3*7j4!ch*ig#KV^l=S61^9Psm=<0G_C8skh92cNUJ_9G&C+$T;^-DZM7Dkl!wx=5DoIO3;Js;V9c63#B zEq8v!XJ>_JmhKqtT<84k+yY0z=Prn=qPgb-GzG~$&pnH?@iJ~}(f!?RAY*wDNmN}u zI`Va=d4_sYJd2?NPxk#1ol7!HUX z{ZWU&K{=N++t<+B2iH%2_itBjR|~R`PCFJmXR=31aNco#AamXCyvIJ~yZeTxr#IRA z%;yjdIL%u5W7zr?z`>`4?=?fw44Tzhl}YnnTVJ1tOt@C2C+0lXuhtb%UXMmziBiU_ zjL8+NjH?p&HEv{lj)dRwQ{zv>pNtnq#>AoV)ncW{kolorrS2r; z2=<9_VuZ*!;TO6!u6nDR8GVe0GDGwE+^W=9{V*3(F=>^Fw`3Uig0)Y~E}n_#y>#o+(|uHT?id;d)0r+caUDP zk~N$kb)`M{gk5HN^o2j=qVg6f744*p;(8LPHb6#L=Q-xCfRA#CyEf?y(KsvTcy^P= zxWhBfv(2-Q{q+lX9)2>d$->y}Y3)4@87Z@`7969pbU~|qS9sQUK}Osqj+bi6C)gl3 zf~O#2?-5oDB^5Q5KbdU|f)JLWs-u3W?n3TeKlE7<+_GE3t8vS0mcNLQZ9P-n4_y;o zKS|KYPO8;Q-ikX*JDWMXumLRZuIMq7faUZp#;dUwHDuP;~eJSzJe$H?Kgk z9f<-whqerrPgpM-IzlgcXW9-gGQuib-$%@kY!lTKD#?nN?U3@X#fD;=#SVr}Q6zSM zOfWh!`d3u7=ylOQqa$M~MBj<1YPoIvsXeF|7A)@XBqgzj-%fVGOh;DR>h!v48&V9( zkAAOA-kdT$tpNP${fR_D0959Ejr*@(G6VCinDAuMx{Aj3puT;a>E~Ho{oCwcFP1ihh5p8Eqw3<~>EPwrf zE!L21&85D}-t69Io}4661-_j4+iTba1Fo~UACdfcLNWroxw=lIGs#a;9XePph`Jt_@ zTSg{r64k#-+ftohIa}x#E*yG;9%CKuxXZpE$*xD)n8rExI!-xuaFfk)mUsPd?QwVZ zOo4l~Q7Y?SgvKxrne}Pmx%f{0gDSd(4be@N7lrx}wTbOm9&H_zVk_BdG%;*5v^L&0 zy)dU*=31vmyosC_l{-2qdS;9e8yVX$c5m!~*nY8_VpqpL<*h|*z1SwPU1G6%oMwlbI29&E zk~foYjBlZ@I6sdRpTl>LIn)WS(mM-cQ9t)o_jUJse9re_N^SQt`6G)WX zh^Xj_ss)f zwRON*nh-ID9Pxu}u!lzdj=B=HBkEApFS_oP(dD9NMrV$(#WW(t?WgsoX@lONX{N{; z8syi>H=teT@%C_!b(-xJGg8xbr>3QTN~@7^+1AOi%r(R_+4n|z8JHc$PoX-j>A~Gv zLvJzYp_rXDzA@c4kA*8$)^ye=b6PIZU(;394pSF{9z6`#PHyysawL_dJQZFhxP)Fm=c;Is$z<>;X9=DmI}1-&yj~o3b7X*qz)S0 zI#(6fWjgyAE{XGUmphv`kGM%Hg;MvA`g$_Snf+k*C8@5fmogu{tG%Ebf>Lz9J~N!B z5|F%38XSfcvfEDTa~VGwx0q&_r&?3ca0BM z4IK}(^;eM&`^?_X?!VlzXpoY;MMaNPBXBfWTrjG-YCdXBhNY%da}&Djvk~_qpGOwq zb03Jx71ceeFd4ZSIFPei1MPtj#M0(yUh6fYKIR~PAR?|tXF zOrq*j?=Ih7@eu0t_nglq;RiKARaswoN#!9krl|J4b_f~3<8*8FKlI=6ah5TxHOw?5 z)8j`ORv0G1WtnQMYRX}*3pY2n<+kOxwPM8dh&qv@qn<=9jCvh4H2Qc<``BR2qS)qf zZQ^dn4vE#p4USXBnd5ZvN2w(3Pm-=_gbs~$#0BUj#~Z~WUke8+t0lY&oFYO^_+C0*;mJk z6U3s@eH`2t`7o-%Y*c+}I2)6o=nW@Zcc1WB@rOaZUsY9IhuM8=l3Pnt8&Ao zZS=+E-wLIu97t|Zn)-|82a5Mgx;iMKN8p^>ioV*U*XqaV z7o)~*%8eLj9BjO8bU+60LhZK(M%o`%!xxg^6QZ(1R;&?YjJ*|mJoZ>@x41I#<>KGR zA%F@37XgwktZ$7jYD-m)ytF}6iLF3a4L1!5IK_B!hKJB z6j`yPzBU(U$~jQ~-U=5%r#Cz)O5W|k|KisT`8wYG%`@Y6Ec(%5p< z{Df!ykl`B}$LA#1N9%g(O0h*t#KWEbQhtw|fWv;Y+!^0TjQ=T`&Vncu%d>B5%=@q9N_ev}pf*I4 zYn`9zm;}i*80x8TTr# zR{VQr({B?;bINAVbTPh3%(sZO=41MhdLP}}#PBJ|p_>8&Bj1kLVCNE3$E9UvA_=(YK;%z}MA936aYpj+1WO z&NKv#%olxs)cm{jU-b3$d-T~2o%E%2r|@WK@uMEbfBFF)Y&&kKftuIqXRtR1t8Odb zvUlsljVj>sITOeoxXm{0GH$%T&_y)xSMYa%s9>QZNs(UR|@?^R$MWbPTEOOQ9_ z7V|QaC(-HRPGhry^jrbT*D^e4+J&Nx*?7Wod zrEt?)#f*=!$J~p_67w*sd_+TY5kqNhtTH`VE%41hOV)||p$^NQ7Mo2aUK4qqY!RB!Z?VRugOX9z|TS`%_U!ehw)7uB{uODpvSJ@ z?(Qn*I^|4qbapgz?}m+0PF$kX{#cg5`xKs_E2n2zV}GyD1_c+&R4 zZ)=RgHw#m&p=8ExqVu$qH~0#dQwlVuUpU?#p@WYj$L}P%w1>)SDvNqFzLNf$GIW2% z@pf#{o!13)Q}k*2>eN`*jo*zOQECk_=QHir_tmsj%Ax)Kb7BGSHupEzR_8r?S=;9H z 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

7%makMCAuG29n9ysoa*rym;ra3! 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 literal 0 HcmV?d00001 From c5fdfbe188ad42117af4b9567f4f1dd1a6faed40 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 14 Sep 2022 11:50:30 +0530 Subject: [PATCH 057/375] shape/value checks --- enhancer/models/demucs.py | 3 +++ enhancer/models/model.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 2ac067b..d4e35a9 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -100,6 +100,9 @@ class Demucs(Model): if mixed_signal.dim() == 2: mixed_signal = mixed_signal.unsqueeze(1) + if mixed_signal.size(1)!=1: + raise TypeError(f"Demucs can only process mono channel audio, input has {mixed_signal.size(1)} channels") + length = mixed_signal.shape[-1] x = F.pad(mixed_signal, (0,self.get_padding_length(length) - length)) if self.hparams.resample>1: diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 2adfef8..275027b 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -3,7 +3,7 @@ from torch.optim import Adam import pytorch_lightning as pl from enhancer.data.dataset import Dataset -from enhancer.utils.loss import Avergeloss +from enhancer.utils.loss import LOSS_MAP, Avergeloss class Model(pl.LightningModule): From 042ce2b525db8095a5595d2c7605228c20fdfa2d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 14 Sep 2022 11:51:03 +0530 Subject: [PATCH 058/375] add valuerror --- enhancer/utils/loss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/utils/loss.py b/enhancer/utils/loss.py index 7e555b5..3c1cc06 100644 --- a/enhancer/utils/loss.py +++ b/enhancer/utils/loss.py @@ -11,7 +11,7 @@ class mean_squared_error(nn.Module): self.loss_fun = nn.MSELoss(reduction=reduction) def forward(self,prediction:torch.Tensor, target: torch.Tensor): - + if prediction.size() != target.size() or target.ndim < 3: raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) got {prediction.size()} and {target.size()} instead""") @@ -46,7 +46,7 @@ class Avergeloss(nn.Module): def validate_loss(self,loss:str): if loss not in LOSS_MAP.keys(): - raise ValueError() + raise ValueError(f"Invalid loss function {loss}, available loss functions are {LOSS_MAP.keys()}") else: return LOSS_MAP[loss] From 6159186ecde95d1f6f3fe18f6070612c22522a9a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 15 Sep 2022 16:39:28 +0530 Subject: [PATCH 059/375] test utils --- tests/utils_test.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/utils_test.py diff --git a/tests/utils_test.py b/tests/utils_test.py new file mode 100644 index 0000000..169284e --- /dev/null +++ b/tests/utils_test.py @@ -0,0 +1,46 @@ +from logging import root +import pytest +import torch +import numpy as np + +from enhancer.utils.io import Audio +from enhancer.utils.config import Files +from enhancer.utils.fileprocessor import Fileprocessor + +def test_io_channel(): + + input_audio = np.random.rand(2,32000) + audio = Audio(mono=True,return_tensor=False) + output_audio = audio(input_audio) + assert output_audio.shape[0] == 1 + +def test_io_resampling(): + + input_audio = np.random.rand(1,32000) + resampled_audio = Audio.resample_audio(input_audio,16000,8000) + + input_audio = torch.rand(1,32000) + resampled_audio_pt = Audio.pt_resample_audio(input_audio,16000,8000) + + assert resampled_audio.shape[1] == resampled_audio_pt.size(1) == 16000 + +def test_fileprocessor_vctk(): + + fp = Fileprocessor.from_name("vctk","tests/data/vctk/clean_testset_wav", + "tests/data/vctk/noisy_testset_wav",48000) + matching_dict = fp.prepare_matching_dict() + assert len(matching_dict)==2 + +@pytest.mark.parametrize("dataset_name",["vctk","dns-2020"]) +def test_fileprocessor_names(dataset_name): + fp = Fileprocessor.from_name(dataset_name,"clean_dir","noisy_dir",16000) + assert hasattr(fp.matching_function, '__call__') + +def test_fileprocessor_invaliname(): + with pytest.raises(ValueError): + fp = Fileprocessor.from_name("undefined","clean_dir","noisy_dir",16000).prepare_matching_dict() + + + + + From de817e45b81b2b1946861134836772dc20422878 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 15 Sep 2022 16:39:54 +0530 Subject: [PATCH 060/375] fix resample bug --- enhancer/utils/io.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 023a57c..2fb2779 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -43,11 +43,12 @@ class Audio: if self.mono: audio = self.convert_mono(audio) - resampled_audio = self.__class__.resample_audio(audio,self.sampling_rate,sampling_rate) + if sampling_rate: + audio = self.__class__.resample_audio(audio,self.sampling_rate,sampling_rate) if self.return_tensor: - return torch.tensor(resampled_audio) + return torch.tensor(audio) else: - return resampled_audio + return audio def convert_mono( self, From 381067e4efe4bb5203e508da0a097796a5324015 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 15 Sep 2022 19:05:31 +0530 Subject: [PATCH 061/375] add version --- enhancer/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/__init__.py b/enhancer/__init__.py index e69de29..b3c06d4 100644 --- a/enhancer/__init__.py +++ b/enhancer/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" \ No newline at end of file From c341b23ff8172488cf7f2390ab7ac560c7f6e77a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 15 Sep 2022 19:08:08 +0530 Subject: [PATCH 062/375] save checkpoint attributes --- enhancer/models/model.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 275027b..d697fcd 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,9 +1,11 @@ from typing import Optional, Union, List from torch.optim import Adam import pytorch_lightning as pl +import torch +from enhancer import __version__ from enhancer.data.dataset import Dataset -from enhancer.utils.loss import LOSS_MAP, Avergeloss +from enhancer.utils.loss import Avergeloss class Model(pl.LightningModule): @@ -74,6 +76,20 @@ class Model(pl.LightningModule): return {"loss":loss} + def on_save_checkpoint(self, checkpoint): + + checkpoint["enhancer"] = { + "version": { + "enhancer":__version__, + "pytorch":torch.__version__ + }, + "architecture":{ + "module":self.__class__.__module__, + "class":self.__class__.__name__ + } + + } + @classmethod def from_pretrained(cls,): pass \ No newline at end of file From f7c09d80451d232531aa686df4bb0f2834dfc7f8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 15 Sep 2022 21:09:49 +0530 Subject: [PATCH 063/375] load from pretrained --- enhancer/models/model.py | 78 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index d697fcd..07281c9 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,12 +1,21 @@ -from typing import Optional, Union, List +from importlib import import_module +from huggingface_hub import cached_download, hf_hub_url +import os +from typing import Optional, Union, List, Path, Text from torch.optim import Adam -import pytorch_lightning as pl import torch +import pytorch_lightning as pl +from pytorch_lightning.utilities.cloud_io import load as pl_load +from urllib.parse import urlparse + from enhancer import __version__ from enhancer.data.dataset import Dataset from enhancer.utils.loss import Avergeloss +CACHE_DIR = "" +HF_TORCH_WEIGHTS = "" +DEFAULT_DEVICE = "cpu" class Model(pl.LightningModule): @@ -91,5 +100,66 @@ class Model(pl.LightningModule): } @classmethod - def from_pretrained(cls,): - pass \ No newline at end of file + def from_pretrained( + cls, + checkpoint: Union[Path, Text], + map_location = None, + hparams_file: Union[Path, Text] = None, + strict: bool = True, + use_auth_token: Union[Text, None] = None, + cached_dir: Union[Path, Text]=CACHE_DIR, + **kwargs + ): + + checkpoint = str(checkpoint) + if hparams_file is not None: + hparams_file = str(hparams_file) + + if os.path.isfile(checkpoint): + model_path_pl = checkpoint + elif urlparse(checkpoint).scheme in ("http","https"): + model_path_pl = checkpoint + else: + + if "@" in checkpoint: + model_id = checkpoint.split("@")[0] + revision_id = checkpoint.split("@")[1] + else: + model_id = checkpoint + revision_id = None + + url = hf_hub_url( + model_id,filename=HF_TORCH_WEIGHTS,revision=revision_id + ) + model_path_pl = cached_download( + url=url,library_name="enhancer",library_version=__version__, + cache_dir=cached_dir,use_auth_token=use_auth_token + ) + + if map_location is None: + map_location = torch.device(DEFAULT_DEVICE) + + loaded_checkpoint = pl_load(model_path_pl,map_location) + module_name = loaded_checkpoint["architecture"]["module"] + class_name = loaded_checkpoint["architecture"]["class"] + module = import_module(module_name) + Klass = getattr(module, class_name) + + try: + model = Klass.load_from_checkpoint( + checkpoint_path = model_path_pl, + map_location = map_location, + hparams_file = hparams_file, + strict = strict, + **kwargs + ) + except Exception as e: + print(e) + + + + + + + + \ No newline at end of file From dbf25767a2210cf129e7b7871dd7eb0e9267bceb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 20:15:52 +0530 Subject: [PATCH 064/375] inference class --- enhancer/inference.py | 84 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 enhancer/inference.py diff --git a/enhancer/inference.py b/enhancer/inference.py new file mode 100644 index 0000000..fc60624 --- /dev/null +++ b/enhancer/inference.py @@ -0,0 +1,84 @@ +from json import load +import wave +import numpy as np +from scipy.signal import get_window +from typing import List, Optional, Union +import torch +import torch.nn.functional as F +from pathlib import Path +from librosa import load as load_audio + +from enhancer.utils import Audio +from enhancer.utils.config import DEFAULT_DEVICE + +class Inference: + + @staticmethod + def read_input(audio, sr, model_sr): + + if isinstance(audio,(np.ndarray,torch.Tensor)): + assert sr is not None, "Invalid sampling rate!" + + if isinstance(audio,str): + audio = Path(audio) + if not audio.is_file(): + raise ValueError(f"Input file {audio} does not exist") + else: + audio,sr = load_audio(audio,sr=sr,) + else: + assert audio.shape[0] == 1, "Enhance inference only supports single waveform" + + waveform = Audio.resample_audio(audio,sr=sr,target_sr=model_sr) + waveform = Audio.convert_mono(waveform) + if isinstance(waveform,np.ndarray): + waveform = torch.from_numpy(waveform) + + return waveform + + @staticmethod + def batchify(waveform: torch.Tensor, window_size:int, step_size:Optional[int]=None): + """ + break input waveform into samples with duration specified. + Wrap into tensors of specified batch size + """ + assert waveform.ndim == 2, f"Expcted input waveform with 2 dimensions (channels,samples), got {waveform.ndim}" + _,num_samples = waveform.shape + waveform = waveform.unsqueeze(0) + step_size = window_size//2 if step_size is None else step_size + + if num_samples >= window_size: + waveform_batch = F.unfold(waveform[None,...], kernel_size=(window_size,1), + stride=(step_size,1), padding=(window_size,0)) + waveform_batch = waveform_batch.permute(2,0,1) + + + return waveform_batch + + + def aggreagate(self,data:torch.Tensor,window_size:int, step_size:Optional[int]=None): + """ + takes input as tensor outputs aggregated waveform + """ + batch_size,n_channels,num_frames = data.shape + window = get_window(window=window,Nx=data.shape[-1]) + window = torch.from_numpy(window).to(data.device) + data *= window + + data = data.permute(1,2,0) + data = F.fold(data, + (num_frames,1), + kernel_size=(window_size,1), + stride=(step_size,1), + padding=(window_size,0)) + + return data + + + + + + + + + + \ No newline at end of file From fea9f950c515060e40450f26692127c2d4e5be6b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 20:16:26 +0530 Subject: [PATCH 065/375] rename resample --- enhancer/models/demucs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index d4e35a9..7e51c87 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -106,7 +106,7 @@ class Demucs(Model): length = mixed_signal.shape[-1] x = F.pad(mixed_signal, (0,self.get_padding_length(length) - length)) if self.hparams.resample>1: - x = audio.pt_resample_audio(audio=x, sr=self.hparams.sampling_rate, + x = audio.resample_audio(audio=x, sr=self.hparams.sampling_rate, target_sr=int(self.hparams.sampling_rate * self.hparams.resample)) encoder_outputs = [] @@ -123,7 +123,7 @@ class Demucs(Model): x = decoder(x) if self.hparams.resample > 1: - x = audio.pt_resample_audio(x,int(self.hparams.sampling_rate * self.hparams.resample), + x = audio.resample_audio(x,int(self.hparams.sampling_rate * self.hparams.resample), self.hparams.sampling_rate) return x From 43b1dd190adcfdc71aa2f30a0243aa5b9e83ed8b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 22:33:43 +0530 Subject: [PATCH 066/375] write output to file --- enhancer/inference.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/enhancer/inference.py b/enhancer/inference.py index fc60624..414f6ae 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -2,6 +2,7 @@ from json import load import wave import numpy as np from scipy.signal import get_window +from scipy.io import wavfile from typing import List, Optional, Union import torch import torch.nn.functional as F @@ -73,6 +74,19 @@ class Inference: return data + @staticmethod + def write_output(waveform:torch.Tensor,filename:Union[str,Path],sr:int): + + if isinstance(filename,str): + filename = Path(filename) + if filename.is_file(): + raise FileExistsError(f"file {filename} already exists") + else: + wavfile.write(filename,rate=sr,data=waveform.detach().cpu()) + + + + From 2a293a1d40af7b4482d38fbc3e43e408a064ba9e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 22:34:24 +0530 Subject: [PATCH 067/375] refactor IO functions --- enhancer/utils/io.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 2fb2779..ba3b4d2 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -50,13 +50,18 @@ class Audio: else: return audio + @staticmethod def convert_mono( - self, audio ): + if len(audio.shape)>2: + assert audio.shape[0] == 1, "convert mono only accepts single waveform" + audio = audio.reshape(audio.shape[1],audio.shape[2]) + + assert audio.shape[0] > audio.shape[1], "expected input format (num_channels,num_samples)" num_channels,num_samples = audio.shape - if num_channels>1 and self.mono: + if num_channels>1: return audio.mean(axis=0).reshape(1,num_samples) return audio @@ -68,17 +73,11 @@ class Audio: target_sr:int ): if sr!=target_sr: - audio = librosa.resample(audio,orig_sr=sr,target_sr=target_sr) + if isinstance(audio,np.ndarray): + audio = librosa.resample(audio,orig_sr=sr,target_sr=target_sr) + elif isinstance(audio,torch.Tensor): + audio = torchaudio.functional.resample(audio,orig_freq=sr,new_freq=target_sr) + else: + raise ValueError("Input should be either numpy array or torch tensor") return audio - - @staticmethod - def pt_resample_audio( - audio, - sr:int, - target_sr:int - ): - if sr!=target_sr: - audio = torchaudio.functional.resample(audio,orig_freq=sr,new_freq=target_sr) - - return audio \ No newline at end of file From 872303642f6d499e32f272994eb0268392970e06 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 22:34:49 +0530 Subject: [PATCH 068/375] rename io function --- tests/utils_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils_test.py b/tests/utils_test.py index 169284e..81de1a4 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -20,7 +20,7 @@ def test_io_resampling(): resampled_audio = Audio.resample_audio(input_audio,16000,8000) input_audio = torch.rand(1,32000) - resampled_audio_pt = Audio.pt_resample_audio(input_audio,16000,8000) + resampled_audio_pt = Audio.resample_audio(input_audio,16000,8000) assert resampled_audio.shape[1] == resampled_audio_pt.size(1) == 16000 From 9ef6665b84463f596c0336ba690d77b8e617bd6b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 22:35:21 +0530 Subject: [PATCH 069/375] add enhance function --- enhancer/models/model.py | 66 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 07281c9..f2339a2 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,9 +1,15 @@ +from asyncore import write from importlib import import_module +from lib2to3.pgen2.token import OP +import wave +from xmlrpc.client import boolean from huggingface_hub import cached_download, hf_hub_url +import numpy as np import os -from typing import Optional, Union, List, Path, Text +from typing import Optional, Union, List, Path, Text, Dict, Any from torch.optim import Adam import torch +from torch.nn.functional import pad import pytorch_lightning as pl from pytorch_lightning.utilities.cloud_io import load as pl_load from urllib.parse import urlparse @@ -11,7 +17,9 @@ from urllib.parse import urlparse from enhancer import __version__ from enhancer.data.dataset import Dataset +from enhancer.utils.io import Audio from enhancer.utils.loss import Avergeloss +from enhancer.inference import Inference CACHE_DIR = "" HF_TORCH_WEIGHTS = "" @@ -30,8 +38,8 @@ class Model(pl.LightningModule): ): super().__init__() assert num_channels ==1 , "Enhancer only support for mono channel models" - self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric") self.dataset = dataset + self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric") @property @@ -40,6 +48,8 @@ class Model(pl.LightningModule): @dataset.setter def dataset(self,dataset): + if dataset is not None: + self.save_hyperparameters("duration",self.dataset.duration) self._dataset = dataset def setup(self,stage:Optional[str]=None): @@ -99,6 +109,10 @@ class Model(pl.LightningModule): } + def on_load_checkpoint(self, checkpoint: Dict[str, Any]): + pass + + @classmethod def from_pretrained( cls, @@ -157,7 +171,55 @@ class Model(pl.LightningModule): print(e) + return model + + def infer_batch(self,batch,batch_size): + assert batch.ndim == 3, f"Expected batch with 3 dimensions (batch,channels,samples) got only {batch.ndim}" + batch_predictions = [] + self.eval().to(self.device) + + for batch_id in range(batch.shape[0],batch_size): + batch_data = batch[batch_id:batch_id+batch_size,:,:].to(self.device) + prediction = self(batch_data) + batch_predictions.append(prediction) + + return torch.vstack(batch_predictions) + + def enhance( + self, + audio:Union[Path,np.ndarray,torch.Tensor], + sampling_rate:Optional[int]=None, + batch_size:int=32, + save_output:boolean=False, + duration:Optional[int]=None, + step_size:Optional[int]=None,): + + model_sampling_rate = self.model.hprams("sampling_rate") + if duration is None: + duration = self.model.hparams("duration") + waveform = Inference.read_input(audio,sampling_rate,model_sampling_rate) + waveform.to(self.device) + window_size = round(duration * model_sampling_rate) + batched_waveform = Inference.batchify(waveform,window_size,step_size=step_size) + batch_prediction = self.infer_batch(batched_waveform,batch_size=batch_size) + waveform = Inference.aggreagate(batch_prediction,window_size,step_size) + + if save_output and isinstance(audio,(str,Path)): + Inference.write_output(waveform,audio,model_sampling_rate) + + else: + return waveform + + + + + + + + + + From e1125272110bcdf4c93253461e281805897680ef Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 22:35:46 +0530 Subject: [PATCH 070/375] relative imports --- enhancer/utils/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py index 1971db9..39ae32a 100644 --- a/enhancer/utils/__init__.py +++ b/enhancer/utils/__init__.py @@ -1,2 +1,3 @@ from enhancer.utils.fileprocessor import Fileprocessor -from enhancer.utils.utils import check_files \ No newline at end of file +from enhancer.utils.utils import check_files +from enhancer.utils.io import Audio \ No newline at end of file From 8a90899663cdc7b8c2c16774b950f9b4e4ddd774 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 19 Sep 2022 22:38:22 +0530 Subject: [PATCH 071/375] rmv unused imports --- enhancer/models/model.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index f2339a2..87fab4f 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,8 +1,4 @@ -from asyncore import write from importlib import import_module -from lib2to3.pgen2.token import OP -import wave -from xmlrpc.client import boolean from huggingface_hub import cached_download, hf_hub_url import numpy as np import os @@ -191,7 +187,7 @@ class Model(pl.LightningModule): audio:Union[Path,np.ndarray,torch.Tensor], sampling_rate:Optional[int]=None, batch_size:int=32, - save_output:boolean=False, + save_output:bool=False, duration:Optional[int]=None, step_size:Optional[int]=None,): From 3f40b54fc693d2311d0b1b531a12e7f22088bda6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 21 Sep 2022 10:36:56 +0530 Subject: [PATCH 072/375] refactor encoder-decoder --- enhancer/models/demucs.py | 96 +++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 7e51c87..571a915 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,3 +1,5 @@ +from base64 import encode +from turtle import forward from typing import Optional, Union, List from torch import nn import torch.nn.functional as F @@ -8,7 +10,7 @@ from enhancer.data.dataset import EnhancerDataset from enhancer.utils.io import Audio as audio from enhancer.utils.utils import merge_dict -class DeLSTM(nn.Module): +class DemucsLSTM(nn.Module): def __init__( self, input_size:int, @@ -29,6 +31,59 @@ class DeLSTM(nn.Module): return output,(h,c) + +class DemucsEncoder(nn.Module): + + def __init__( + self, + num_channels:int, + hidden_size:int, + kernel_size:int, + stride:int=1, + glu:bool=False, + ): + super().__init__() + activation = nn.GLU(1) if glu else nn.ReLU() + multi_factor = 2 if glu else 1 + self.encoder = nn.Sequential( + nn.Conv1d(num_channels,hidden_size,kernel_size,stride), + nn.ReLU(), + nn.Conv1d(hidden_size, hidden_size*multi_factor,kernel_size,1), + activation + ) + + def forward(self,waveform): + + return self.encoder(waveform) + +class DemucsDecoder(nn.Module): + + def __init__( + self, + num_channels:int, + hidden_size:int, + kernel_size:int, + stride:int=1, + glu:bool=False, + layer:int=0 + ): + super().__init__() + activation = nn.GLU(1) if glu else nn.ReLU() + multi_factor = 2 if glu else 1 + self.decoder = nn.Sequential( + nn.Conv1d(hidden_size,hidden_size*multi_factor,kernel_size,1), + activation, + nn.ConvTranspose1d(hidden_size,num_channels,kernel_size,stride) + ) + if layer>0: + self.decoder.add_module("4", nn.ReLU()) + + def forward(self,waveform,): + + out = self.decoder(waveform) + return out + + class Demucs(Model): ED_DEFAULTS = { @@ -56,44 +111,45 @@ class Demucs(Model): loss:Union[str, List] = "mse" ): + duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None super().__init__(num_channels=num_channels, sampling_rate=sampling_rate,lr=lr, - dataset=dataset,loss=loss) + dataset=dataset,duration=duration,loss=loss) encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) lstm = merge_dict(self.LSTM_DEFAULTS,lstm) self.save_hyperparameters("encoder_decoder","lstm","resample") - hidden = encoder_decoder["initial_output_channels"] - activation = nn.GLU(1) if encoder_decoder["glu"] else nn.ReLU() - multi_factor = 2 if encoder_decoder["glu"] else 1 - self.encoder = nn.ModuleList() self.decoder = nn.ModuleList() for layer in range(encoder_decoder["depth"]): - encoder_layer = [nn.Conv1d(num_channels,hidden,encoder_decoder["kernel_size"],encoder_decoder["stride"]), - nn.ReLU(), - nn.Conv1d(hidden, hidden*multi_factor,encoder_decoder["kernel_size"],1), - activation] - encoder_layer = nn.Sequential(*encoder_layer) + encoder_layer = DemucsEncoder(num_channels=num_channels, + hidden_size=hidden, + kernel_size=encoder_decoder["kernel_size"], + stride=encoder_decoder["stride"], + glu=encoder_decoder["glu"], + ) self.encoder.append(encoder_layer) - decoder_layer = [nn.Conv1d(hidden,hidden*multi_factor,encoder_decoder["kernel_size"],1), - activation, - nn.ConvTranspose1d(hidden,num_channels,encoder_decoder["kernel_size"],encoder_decoder["stride"]) - ] - if layer>0: - decoder_layer.append(nn.ReLU()) - decoder_layer = nn.Sequential(*decoder_layer) + decoder_layer = DemucsDecoder(num_channels=num_channels, + hidden_size=hidden, + kernel_size=encoder_decoder["kernel_size"], + stride=1, + glu=encoder_decoder["glu"], + layer=layer + ) self.decoder.insert(0,decoder_layer) num_channels = hidden hidden = self.ED_DEFAULTS["growth_factor"] * hidden - - self.de_lstm = DeLSTM(input_size=num_channels,hidden_size=num_channels,num_layers=lstm["num_layers"],bidirectional=lstm["bidirectional"]) + self.de_lstm = DemucsLSTM(input_size=num_channels, + hidden_size=num_channels, + num_layers=lstm["num_layers"], + bidirectional=lstm["bidirectional"] + ) def forward(self,mixed_signal): From 0fa16054d905dd827373bbe9feeda1ce356d437c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 23 Sep 2022 12:28:52 +0530 Subject: [PATCH 073/375] wave-u-net init --- enhancer/models/waveunet.py | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 enhancer/models/waveunet.py diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py new file mode 100644 index 0000000..2a9d083 --- /dev/null +++ b/enhancer/models/waveunet.py @@ -0,0 +1,64 @@ +from turtle import forward +import torch.nn as nn + + +class WavenetDecoder(nn.Module): + + def __init__( + self, + in_channels:int, + out_channels:int, + kernel_size:int, + padding:int, + stride:int, + dilation:int=1, + ): + super(WavenetDecoder,self).__init__() + self.decoder = nn.Sequential( + nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding), + nn.BatchNorm1d(out_channels), + nn.LeakyReLU(negative_slope=0.1) + ) + + def forward(self,waveform): + + return self.decoder(waveform) + +class WavenetEncoder(nn.Module): + + def __init__( + self, + in_channels:int, + out_channels:int, + kernel_size:int, + padding:int, + stride:int, + dilation:int=1, + ): + self.encoder = nn.Sequential( + nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding), + nn.BatchNorm1d(out_channels), + nn.LeakyReLU(negative_slope=0.1) + ) + + + def forward( + self, + waveform + ): + return self.encoder(waveform) + + + + +class WaveUnet(nn.Module): + + def __init__( + self + ): + pass + + def forward( + self,waveform + ): + pass \ No newline at end of file From 24c7a6f1f0ee6bd6ea964e5e2e3a4c8491341810 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 23 Sep 2022 18:21:15 +0530 Subject: [PATCH 074/375] wave u net encoder decoder --- enhancer/models/waveunet.py | 63 ++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index 2a9d083..bb20c3f 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -1,4 +1,3 @@ -from turtle import forward import torch.nn as nn @@ -8,14 +7,14 @@ class WavenetDecoder(nn.Module): self, in_channels:int, out_channels:int, - kernel_size:int, - padding:int, - stride:int, + kernel_size:int=5, + padding:int=2, + stride:int=1, dilation:int=1, ): super(WavenetDecoder,self).__init__() self.decoder = nn.Sequential( - nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding), + nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding,dilation=dilation), nn.BatchNorm1d(out_channels), nn.LeakyReLU(negative_slope=0.1) ) @@ -30,13 +29,14 @@ class WavenetEncoder(nn.Module): self, in_channels:int, out_channels:int, - kernel_size:int, - padding:int, - stride:int, + kernel_size:int=15, + padding:int=7, + stride:int=1, dilation:int=1, ): + super(WavenetEncoder,self).__init__() self.encoder = nn.Sequential( - nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding), + nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding,dilation=dilation), nn.BatchNorm1d(out_channels), nn.LeakyReLU(negative_slope=0.1) ) @@ -54,11 +54,50 @@ class WavenetEncoder(nn.Module): class WaveUnet(nn.Module): def __init__( - self + self, + inp_channels:int=1, + num_layers:int=12, + initial_output_channels:int=24 ): - pass + super(WaveUnet,self).__init__() + + self.encoders = nn.ModuleList() + self.decoders = nn.ModuleList() + out_channels = initial_output_channels + for layer in range(num_layers): + + encoder = WavenetEncoder(inp_channels,out_channels) + self.encoders.append(encoder) + + inp_channels = out_channels + out_channels += initial_output_channels + if layer == num_layers -1 : + decoder = WavenetDecoder(num_layers * initial_output_channels + inp_channels,inp_channels) + else: + decoder = WavenetDecoder(inp_channels+out_channels,inp_channels) + + self.decoders.insert(0,decoder) + + bottleneck_dim = num_layers * initial_output_channels + self.bottleneck = nn.Sequential( + nn.Conv1d(bottleneck_dim,bottleneck_dim, 15, stride=1, + padding=7), + nn.BatchNorm1d(bottleneck_dim), + nn.LeakyReLU(negative_slope=0.1, inplace=True) + ) + + def forward( self,waveform ): - pass \ No newline at end of file + + for encoder in self.encoders: + out = encoder(waveform) + + out = self.bottleneck(out) + + for decoder in self.decoders: + out = decoder(out) + + return decoder \ No newline at end of file From 7641e5107c1c93a2266242d5d353c7a21374bf5b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 23 Sep 2022 18:21:54 +0530 Subject: [PATCH 075/375] fix imports --- enhancer/models/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 87fab4f..640d090 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -2,13 +2,14 @@ from importlib import import_module from huggingface_hub import cached_download, hf_hub_url import numpy as np import os -from typing import Optional, Union, List, Path, Text, Dict, Any +from typing import Optional, Union, List, Text, Dict, Any from torch.optim import Adam import torch from torch.nn.functional import pad import pytorch_lightning as pl from pytorch_lightning.utilities.cloud_io import load as pl_load from urllib.parse import urlparse +from pathlib import Path from enhancer import __version__ @@ -29,13 +30,14 @@ class Model(pl.LightningModule): sampling_rate:int=16000, lr:float=1e-3, dataset:Optional[Dataset]=None, + duration:Optional[float]=None, loss: Union[str, List] = "mse", metric:Union[str,List] = "mse" ): super().__init__() assert num_channels ==1 , "Enhancer only support for mono channel models" self.dataset = dataset - self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric") + self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric","duration") @property @@ -44,8 +46,6 @@ class Model(pl.LightningModule): @dataset.setter def dataset(self,dataset): - if dataset is not None: - self.save_hyperparameters("duration",self.dataset.duration) self._dataset = dataset def setup(self,stage:Optional[str]=None): From 10eaada080e4326f6ee0d17c09fd707802597a8e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 23 Sep 2022 18:22:09 +0530 Subject: [PATCH 076/375] fix comparison --- enhancer/utils/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index ba3b4d2..80bd1a4 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -59,7 +59,7 @@ class Audio: assert audio.shape[0] == 1, "convert mono only accepts single waveform" audio = audio.reshape(audio.shape[1],audio.shape[2]) - assert audio.shape[0] > audio.shape[1], "expected input format (num_channels,num_samples)" + assert audio.shape[1] >> audio.shape[0], f"expected input format (num_channels,num_samples) got {audio.shape}" num_channels,num_samples = audio.shape if num_channels>1: return audio.mean(axis=0).reshape(1,num_samples) From 06afc9570144e890367fe97d22d675f692adcaf9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 23 Sep 2022 18:22:25 +0530 Subject: [PATCH 077/375] fix typo --- enhancer/utils/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py index 512bd06..3b1acac 100644 --- a/enhancer/utils/random.py +++ b/enhancer/utils/random.py @@ -18,7 +18,7 @@ def create_unique_rng(epoch:int): worker_info = torch.utils.data.get_worker_info() if worker_info is not None: num_workers = worker_info.num_workers - worker_id = worker_info.worker_id + worker_id = worker_info.id else: num_workers = 1 worker_id = 0 From 48d5f9c21ed80c7ebfd9356a301814745b2ddea0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 24 Sep 2022 12:46:06 +0530 Subject: [PATCH 078/375] wave-u-net: --- enhancer/models/waveunet.py | 83 +++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index bb20c3f..4a0e20f 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -1,5 +1,12 @@ +from tkinter import wantobjects +import wave +import torch import torch.nn as nn +import torch.nn.functional as F +from typing import Optional, Union, List +from enhancer.models.model import Model +from enhancer.data.dataset import EnhancerDataset class WavenetDecoder(nn.Module): @@ -49,32 +56,39 @@ class WavenetEncoder(nn.Module): return self.encoder(waveform) - - -class WaveUnet(nn.Module): +class WaveUnet(Model): def __init__( self, - inp_channels:int=1, + num_channels:int=1, num_layers:int=12, - initial_output_channels:int=24 + initial_output_channels:int=24, + sampling_rate:int=16000, + lr:float=1e-3, + dataset:Optional[EnhancerDataset]=None, + duration:Optional[float]=None, + loss: Union[str, List] = "mse", + metric:Union[str,List] = "mse" ): - super(WaveUnet,self).__init__() - + super().__init__(num_channels=num_channels, + sampling_rate=sampling_rate,lr=lr, + dataset=dataset,duration=duration,loss=loss, metric=metric + ) + self.save_hyperparameters("num_layers") self.encoders = nn.ModuleList() self.decoders = nn.ModuleList() out_channels = initial_output_channels for layer in range(num_layers): - encoder = WavenetEncoder(inp_channels,out_channels) + encoder = WavenetEncoder(num_channels,out_channels) self.encoders.append(encoder) - inp_channels = out_channels + num_channels = out_channels out_channels += initial_output_channels if layer == num_layers -1 : - decoder = WavenetDecoder(num_layers * initial_output_channels + inp_channels,inp_channels) + decoder = WavenetDecoder(num_layers * initial_output_channels + num_channels,inp_channels) else: - decoder = WavenetDecoder(inp_channels+out_channels,inp_channels) + decoder = WavenetDecoder(num_channels+out_channels,num_channels) self.decoders.insert(0,decoder) @@ -85,19 +99,54 @@ class WaveUnet(nn.Module): nn.BatchNorm1d(bottleneck_dim), nn.LeakyReLU(negative_slope=0.1, inplace=True) ) - + self.final = nn.Sequential( + nn.Conv1d(1 + initial_output_channels, 1, kernel_size=1, stride=1), + nn.Tanh() + ) def forward( self,waveform ): - - for encoder in self.encoders: - out = encoder(waveform) + if waveform.dim() == 2: + waveform = waveform.unsqueeze(1) + if waveform.size(1)!=1: + raise TypeError(f"Wave-U-Net can only process mono channel audio, input has {waveform.size(1)} channels") + + encoder_outputs = [] + out = waveform + for encoder in self.encoders: + out = encoder(out) + encoder_outputs.insert(0,out) + out = out[:,:,::2] + out = self.bottleneck(out) - for decoder in self.decoders: + for layer,decoder in enumerate(self.decoders): + out = F.interpolate(out, scale_factor=2, mode="linear") + print(out.shape,encoder_outputs[layer].shape) + out = self.fix_last_dim(out,encoder_outputs[layer]) + out = torch.cat([out,encoder_outputs[layer]],dim=1) out = decoder(out) - return decoder \ No newline at end of file + out = torch.cat([out, waveform],dim=1) + out = self.final(out) + return out + + def fix_last_dim(self,x,target): + """ + trying to do centre crop along last dimension + """ + + assert x.shape[-1] >= target.shape[-1], "input dimension cannot be larger than target dimension" + if x.shape[-1] == target.shape[-1]: + return x + + diff = x.shape[-1] - target.shape[-1] + if diff%2!=0: + x = F.pad(x,(0,1)) + diff += 1 + + crop = diff//2 + return x[:,:,crop:-crop] From 71b98ba67c9b6a6f168eae78ffc018364b0decab Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 24 Sep 2022 12:46:40 +0530 Subject: [PATCH 079/375] dataset type fix --- enhancer/models/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 640d090..5d1cd3e 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -13,7 +13,7 @@ from pathlib import Path from enhancer import __version__ -from enhancer.data.dataset import Dataset +from enhancer.data.dataset import EnhancerDataset from enhancer.utils.io import Audio from enhancer.utils.loss import Avergeloss from enhancer.inference import Inference @@ -29,7 +29,7 @@ class Model(pl.LightningModule): num_channels:int=1, sampling_rate:int=16000, lr:float=1e-3, - dataset:Optional[Dataset]=None, + dataset:Optional[EnhancerDataset]=None, duration:Optional[float]=None, loss: Union[str, List] = "mse", metric:Union[str,List] = "mse" From 43261dec16eb73b785e59e22f3dea281ba4db65b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 24 Sep 2022 12:47:12 +0530 Subject: [PATCH 080/375] pass metric to Model --- enhancer/models/demucs.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 571a915..115f63e 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -108,13 +108,15 @@ class Demucs(Model): sampling_rate = 16000, lr:float=1e-3, dataset:Optional[EnhancerDataset]=None, - loss:Union[str, List] = "mse" + loss:Union[str, List] = "mse", + metric:Union[str, List] = "mse" + ): duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None super().__init__(num_channels=num_channels, sampling_rate=sampling_rate,lr=lr, - dataset=dataset,duration=duration,loss=loss) + dataset=dataset,duration=duration,loss=loss, metric=metric) encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) lstm = merge_dict(self.LSTM_DEFAULTS,lstm) @@ -151,16 +153,16 @@ class Demucs(Model): bidirectional=lstm["bidirectional"] ) - def forward(self,mixed_signal): + def forward(self,waveform): - if mixed_signal.dim() == 2: - mixed_signal = mixed_signal.unsqueeze(1) + if waveform.dim() == 2: + waveform = waveform.unsqueeze(1) - if mixed_signal.size(1)!=1: - raise TypeError(f"Demucs can only process mono channel audio, input has {mixed_signal.size(1)} channels") + if waveform.size(1)!=1: + raise TypeError(f"Demucs can only process mono channel audio, input has {waveform.size(1)} channels") - length = mixed_signal.shape[-1] - x = F.pad(mixed_signal, (0,self.get_padding_length(length) - length)) + length = waveform.shape[-1] + x = F.pad(waveform, (0,self.get_padding_length(length) - length)) if self.hparams.resample>1: x = audio.resample_audio(audio=x, sr=self.hparams.sampling_rate, target_sr=int(self.hparams.sampling_rate * self.hparams.resample)) From f65e13f9c280ac2b7a1221af77ec44927050825d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 25 Sep 2022 11:51:30 +0530 Subject: [PATCH 081/375] refactor fileprocessor --- enhancer/data/fileprocessor.py | 98 ++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 enhancer/data/fileprocessor.py diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py new file mode 100644 index 0000000..4df3e23 --- /dev/null +++ b/enhancer/data/fileprocessor.py @@ -0,0 +1,98 @@ +import glob +import os +import numpy as np +from scipy.io import wavfile + +class ProcessorFunctions: + + @staticmethod + def match_vtck(clean_path,noisy_path,sr): + + matching_wavfiles = list() + clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] + noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(noisy_path,"*.wav"))] + common_filenames = np.intersect1d(noisy_filenames,clean_filenames) + + for file_name in common_filenames: + + sr_clean, clean_file = wavfile.read(os.path.join(clean_path,file_name)) + sr_noisy, noisy_file = wavfile.read(os.path.join(noisy_path,file_name)) + if ((clean_file.shape[-1]==noisy_file.shape[-1]) and + (sr_clean==sr) and + (sr_noisy==sr)): + matching_wavfiles.append( + {"clean":os.path.join(clean_path,file_name),"noisy":os.path.join(noisy_path,file_name), + "duration":clean_file.shape[-1]/sr} + ) + return matching_wavfiles + + @staticmethod + def match_dns2020(clean_path,noisy_path,sr): + + matching_wavfiles = dict() + clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] + for clean_file in clean_filenames: + noisy_filenames = glob.glob(os.path.join(noisy_path,f"*_{clean_file}.wav")) + for noisy_file in noisy_filenames: + + sr_clean, clean_file = wavfile.read(os.path.join(clean_path,clean_file)) + sr_noisy, noisy_file = wavfile.read(noisy_file) + if ((clean_file.shape[-1]==noisy_file.shape[-1]) and + (sr_clean==sr) and + (sr_noisy==sr)): + matching_wavfiles.update( + {"clean":os.path.join(clean_path,clean_file),"noisy":noisy_file, + "duration":clean_file.shape[-1]/sr} + ) + + return matching_wavfiles + + +class Fileprocessor: + + def __init__( + self, + clean_dir, + noisy_dir, + sr = 16000, + matching_function = None + ): + self.clean_dir = clean_dir + self.noisy_dir = noisy_dir + self.sr = sr + self.matching_function = matching_function + + @classmethod + def from_name(cls, + name:str, + clean_dir, + noisy_dir, + sr, + matching_function=None + ): + + if name.lower() == "vctk": + return cls(clean_dir,noisy_dir,sr, ProcessorFunctions.match_vtck) + elif name.lower() == "dns-2020": + return cls(clean_dir,noisy_dir,sr, ProcessorFunctions.match_dns2020) + else: + return cls(clean_dir,noisy_dir,sr, matching_function) + + def prepare_matching_dict(self): + + if self.matching_function is None: + raise ValueError("Not a valid matching function") + + return self.matching_function(self.clean_dir,self.noisy_dir,self.sr) + + + + + + + + + + + + From 4cc96c7b09cb130c7420d22842ef5b9654bcd799 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 25 Sep 2022 11:51:54 +0530 Subject: [PATCH 082/375] rmv fileprocessor --- enhancer/utils/fileprocessor.py | 94 --------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 enhancer/utils/fileprocessor.py diff --git a/enhancer/utils/fileprocessor.py b/enhancer/utils/fileprocessor.py deleted file mode 100644 index 6934750..0000000 --- a/enhancer/utils/fileprocessor.py +++ /dev/null @@ -1,94 +0,0 @@ -import glob -import os -import numpy as np -from scipy.io import wavfile - -class Fileprocessor: - - def __init__( - self, - clean_dir, - noisy_dir, - sr = 16000, - matching_function = None - ): - self.clean_dir = clean_dir - self.noisy_dir = noisy_dir - self.sr = sr - self.matching_function = matching_function - - @classmethod - def from_name(cls, - name:str, - clean_dir, - noisy_dir, - sr, - matching_function=None - ): - - if name.lower() == "vctk": - return cls(clean_dir,noisy_dir,sr, Fileprocessor.match_vtck) - elif name.lower() == "dns-2020": - return cls(clean_dir,noisy_dir,sr, Fileprocessor.match_dns2020) - else: - return cls(clean_dir,noisy_dir,sr, matching_function) - - def prepare_matching_dict(self): - - if self.matching_function is None: - raise ValueError("Not a valid matching function") - - return self.matching_function(self.clean_dir,self.noisy_dir,self.sr) - - @staticmethod - def match_vtck(clean_path,noisy_path,sr): - - matching_wavfiles = list() - clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] - noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(noisy_path,"*.wav"))] - common_filenames = np.intersect1d(noisy_filenames,clean_filenames) - - for file_name in common_filenames: - - sr_clean, clean_file = wavfile.read(os.path.join(clean_path,file_name)) - sr_noisy, noisy_file = wavfile.read(os.path.join(noisy_path,file_name)) - if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==sr) and - (sr_noisy==sr)): - matching_wavfiles.append( - {"clean":os.path.join(clean_path,file_name),"noisy":os.path.join(noisy_path,file_name), - "duration":clean_file.shape[-1]/sr} - ) - return matching_wavfiles - - @staticmethod - def match_dns2020(clean_path,noisy_path,sr): - - matching_wavfiles = dict() - clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] - for clean_file in clean_filenames: - noisy_filenames = glob.glob(os.path.join(noisy_path,f"*_{clean_file}.wav")) - for noisy_file in noisy_filenames: - - sr_clean, clean_file = wavfile.read(os.path.join(clean_path,clean_file)) - sr_noisy, noisy_file = wavfile.read(noisy_file) - if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==sr) and - (sr_noisy==sr)): - matching_wavfiles.update( - {"clean":os.path.join(clean_path,clean_file),"noisy":noisy_file, - "duration":clean_file.shape[-1]/sr} - ) - - return matching_wavfiles - - - - - - - - - - - From 2b6b848f0a91a466bf218993d0b5bd2db5089366 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 25 Sep 2022 11:52:16 +0530 Subject: [PATCH 083/375] import fileprocessor --- enhancer/data/dataset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 797d691..f4e7e4a 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -8,9 +8,10 @@ from torch.utils.data import IterableDataset, DataLoader, Dataset import torch.nn.functional as F from typing import Optional +from enhancer.data.fileprocessor import Fileprocessor from enhancer.utils.random import create_unique_rng from enhancer.utils.io import Audio -from enhancer.utils import Fileprocessor, check_files +from enhancer.utils import check_files from enhancer.utils.config import Files class TrainDataset(IterableDataset): From 18721e203b8059ddfc6e5b12370de645ac3dc466 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 25 Sep 2022 11:58:28 +0530 Subject: [PATCH 084/375] mv loss from utils --- enhancer/loss.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 enhancer/loss.py diff --git a/enhancer/loss.py b/enhancer/loss.py new file mode 100644 index 0000000..af6a4ba --- /dev/null +++ b/enhancer/loss.py @@ -0,0 +1,64 @@ +import torch +import torch.nn as nn + + +class mean_squared_error(nn.Module): + + def __init__(self,reduction="mean"): + super().__init__() + + self.loss_fun = nn.MSELoss(reduction=reduction) + + def forward(self,prediction:torch.Tensor, target: torch.Tensor): + + if prediction.size() != target.size() or target.ndim < 3: + raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""") + + return self.loss_fun(prediction, target) + +class mean_absolute_error(nn.Module): + + def __init__(self,reduction="mean"): + super().__init__() + + self.loss_fun = nn.L1Loss(reduction=reduction) + + def forward(self, prediction:torch.Tensor, target: torch.Tensor): + + if prediction.size() != target.size() or target.ndim < 3: + raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""") + + return self.loss_fun(prediction, target) + +class Avergeloss(nn.Module): + + def __init__(self,losses): + super().__init__() + + self.valid_losses = nn.ModuleList() + for loss in losses: + loss = self.validate_loss(loss) + self.valid_losses.append(loss()) + + + def validate_loss(self,loss:str): + if loss not in LOSS_MAP.keys(): + raise ValueError(f"Invalid loss function {loss}, available loss functions are {LOSS_MAP.keys()}") + else: + return LOSS_MAP[loss] + + def forward(self,prediction:torch.Tensor, target:torch.Tensor): + loss = 0.0 + for loss_fun in self.valid_losses: + loss += loss_fun(prediction, target) + + return loss + + + + +LOSS_MAP = {"mea":mean_absolute_error, "mse": mean_squared_error} + + From cfff2bed115c6d054b4827392e3c5ebe987c1cc9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 25 Sep 2022 11:58:43 +0530 Subject: [PATCH 085/375] rmv loss --- enhancer/utils/loss.py | 65 ------------------------------------------ 1 file changed, 65 deletions(-) delete mode 100644 enhancer/utils/loss.py diff --git a/enhancer/utils/loss.py b/enhancer/utils/loss.py deleted file mode 100644 index 3c1cc06..0000000 --- a/enhancer/utils/loss.py +++ /dev/null @@ -1,65 +0,0 @@ -from turtle import forward -import torch -import torch.nn as nn - - -class mean_squared_error(nn.Module): - - def __init__(self,reduction="mean"): - super().__init__() - - self.loss_fun = nn.MSELoss(reduction=reduction) - - def forward(self,prediction:torch.Tensor, target: torch.Tensor): - - if prediction.size() != target.size() or target.ndim < 3: - raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) - got {prediction.size()} and {target.size()} instead""") - - return self.loss_fun(prediction, target) - -class mean_absolute_error(nn.Module): - - def __init__(self,reduction="mean"): - super().__init__() - - self.loss_fun = nn.L1Loss(reduction=reduction) - - def forward(self, prediction:torch.Tensor, target: torch.Tensor): - - if prediction.size() != target.size() or target.ndim < 3: - raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) - got {prediction.size()} and {target.size()} instead""") - - return self.loss_fun(prediction, target) - -class Avergeloss(nn.Module): - - def __init__(self,losses): - super().__init__() - - self.valid_losses = nn.ModuleList() - for loss in losses: - loss = self.validate_loss(loss) - self.valid_losses.append(loss()) - - - def validate_loss(self,loss:str): - if loss not in LOSS_MAP.keys(): - raise ValueError(f"Invalid loss function {loss}, available loss functions are {LOSS_MAP.keys()}") - else: - return LOSS_MAP[loss] - - def forward(self,prediction:torch.Tensor, target:torch.Tensor): - loss = 0.0 - for loss_fun in self.valid_losses: - loss += loss_fun(prediction, target) - - return loss - - - - -LOSS_MAP = {"mea":mean_absolute_error, "mse": mean_squared_error} - - From f453242f4571642b72963b81179a2722086a3ed2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 12:24:35 +0530 Subject: [PATCH 086/375] fix import paths --- enhancer/models/model.py | 2 +- enhancer/utils/__init__.py | 1 - tests/utils_test.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 5d1cd3e..999dc6c 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -15,7 +15,7 @@ from pathlib import Path from enhancer import __version__ from enhancer.data.dataset import EnhancerDataset from enhancer.utils.io import Audio -from enhancer.utils.loss import Avergeloss +from enhancer.loss import Avergeloss from enhancer.inference import Inference CACHE_DIR = "" diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py index 39ae32a..a8ae539 100644 --- a/enhancer/utils/__init__.py +++ b/enhancer/utils/__init__.py @@ -1,3 +1,2 @@ -from enhancer.utils.fileprocessor import Fileprocessor from enhancer.utils.utils import check_files from enhancer.utils.io import Audio \ No newline at end of file diff --git a/tests/utils_test.py b/tests/utils_test.py index 81de1a4..413bfac 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -5,7 +5,7 @@ import numpy as np from enhancer.utils.io import Audio from enhancer.utils.config import Files -from enhancer.utils.fileprocessor import Fileprocessor +from enhancer.data.fileprocessor import Fileprocessor def test_io_channel(): From 23051041ce0304eda1acaa0e4498dd011c7f80e8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 12:24:53 +0530 Subject: [PATCH 087/375] fix typo --- enhancer/models/waveunet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index 4a0e20f..62384fc 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -86,7 +86,7 @@ class WaveUnet(Model): num_channels = out_channels out_channels += initial_output_channels if layer == num_layers -1 : - decoder = WavenetDecoder(num_layers * initial_output_channels + num_channels,inp_channels) + decoder = WavenetDecoder(num_layers * initial_output_channels + num_channels,num_channels) else: decoder = WavenetDecoder(num_channels+out_channels,num_channels) From 21cee225c2938b6b52966f0ae887c60e45dc4de5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 12:25:31 +0530 Subject: [PATCH 088/375] fix loss imports --- tests/loss_function_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/loss_function_test.py b/tests/loss_function_test.py index 637d6f3..fbc982c 100644 --- a/tests/loss_function_test.py +++ b/tests/loss_function_test.py @@ -2,7 +2,7 @@ from asyncio import base_tasks import torch import pytest -from enhancer.utils.loss import mean_absolute_error, mean_squared_error +from enhancer.loss import mean_absolute_error, mean_squared_error loss_functions = [mean_absolute_error(), mean_squared_error()] From 26a1f862f6bb86a4dbe1362c71b3ee968c6f22dd Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 12:26:20 +0530 Subject: [PATCH 089/375] tests waveunet --- tests/models/test_waveunet.py | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/models/test_waveunet.py diff --git a/tests/models/test_waveunet.py b/tests/models/test_waveunet.py new file mode 100644 index 0000000..43fd14d --- /dev/null +++ b/tests/models/test_waveunet.py @@ -0,0 +1,46 @@ +import pytest +import torch +from enhancer import data + +from enhancer.utils.config import Files +from enhancer.models import WaveUnet +from enhancer.data.dataset import EnhancerDataset + + +@pytest.fixture +def vctk_dataset(): + root_dir = "tests/data/vctk" + files = Files(train_clean="clean_testset_wav",train_noisy="noisy_testset_wav", + test_clean="clean_testset_wav", test_noisy="noisy_testset_wav") + dataset = EnhancerDataset(name="vctk",root_dir=root_dir,files=files) + return dataset + + + +@pytest.mark.parametrize("batch_size,samples",[(1,1000)]) +def test_forward(batch_size,samples): + model = WaveUnet() + model.eval() + + data = torch.rand(batch_size,1,samples,requires_grad=False) + with torch.no_grad(): + _ = model(data) + + data = torch.rand(batch_size,2,samples,requires_grad=False) + with torch.no_grad(): + with pytest.raises(TypeError): + _ = model(data) + + +@pytest.mark.parametrize("dataset,channels,loss", + [(pytest.lazy_fixture("vctk_dataset"),1,["mae","mse"])]) +def test_demucs_init(dataset,channels,loss): + with torch.no_grad(): + model = WaveUnet(num_channels=channels,dataset=dataset,loss=loss) + + + + + + + From 04656487ab936ebf8942dd934485aed821206d38 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 12:26:41 +0530 Subject: [PATCH 090/375] relative imports --- enhancer/models/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/models/__init__.py b/enhancer/models/__init__.py index dd12bc3..0d21337 100644 --- a/enhancer/models/__init__.py +++ b/enhancer/models/__init__.py @@ -1 +1,2 @@ -from enhancer.models.demucs import Demucs \ No newline at end of file +from enhancer.models.demucs import Demucs +from enhancer.models.waveunet import WaveUnet \ No newline at end of file From 868fde7e691ef70241dd0afec38192effc7578e0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 12:38:54 +0530 Subject: [PATCH 091/375] rename as infer --- enhancer/models/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 999dc6c..83355fd 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -169,7 +169,7 @@ class Model(pl.LightningModule): return model - def infer_batch(self,batch,batch_size): + def infer(self,batch:torch.Tensor,batch_size:int=32): assert batch.ndim == 3, f"Expected batch with 3 dimensions (batch,channels,samples) got only {batch.ndim}" batch_predictions = [] @@ -198,7 +198,7 @@ class Model(pl.LightningModule): waveform.to(self.device) window_size = round(duration * model_sampling_rate) batched_waveform = Inference.batchify(waveform,window_size,step_size=step_size) - batch_prediction = self.infer_batch(batched_waveform,batch_size=batch_size) + batch_prediction = self.infer(batched_waveform,batch_size=batch_size) waveform = Inference.aggreagate(batch_prediction,window_size,step_size) if save_output and isinstance(audio,(str,Path)): From 989024982434b362e5f8b7546578d448c7d58607 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 17:08:41 +0530 Subject: [PATCH 092/375] test inference --- tests/test_inference.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/test_inference.py diff --git a/tests/test_inference.py b/tests/test_inference.py new file mode 100644 index 0000000..303aa67 --- /dev/null +++ b/tests/test_inference.py @@ -0,0 +1,26 @@ +import pytest +import torch +import numpy as np + +from enhancer.inference import Inference + + +@pytest.mark.parametrize("audio",["tests/data/vctk/clean_testset_wav/p257_166.wav",torch.rand(1,2,48000)]) +def test_read_input(audio): + + read_audio = Inference.read_input(audio,48000,16000) + assert isinstance(read_audio,torch.Tensor) + assert read_audio.shape[0] == 1 + +def test_batchify(): + rand = torch.rand(1,1000) + batched_rand = Inference.batchify(rand, window_size = 100, step_size=100) + assert batched_rand.shape[0] == 12 + +def test_aggregate(): + rand = torch.rand(12,1,100) + agg_rand = Inference.aggreagate(data=rand,window_size=100,total_frames=1000,step_size=100) + assert agg_rand.shape[-1] == 1000 + + + \ No newline at end of file From e9ea0d16956daed7ef792d4bfcb293a4db70fe30 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 17:09:11 +0530 Subject: [PATCH 093/375] pass total frames --- enhancer/models/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 83355fd..65946a2 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -199,7 +199,7 @@ class Model(pl.LightningModule): window_size = round(duration * model_sampling_rate) batched_waveform = Inference.batchify(waveform,window_size,step_size=step_size) batch_prediction = self.infer(batched_waveform,batch_size=batch_size) - waveform = Inference.aggreagate(batch_prediction,window_size,step_size) + waveform = Inference.aggreagate(batch_prediction,window_size,waveform.shape[-1],step_size,) if save_output and isinstance(audio,(str,Path)): Inference.write_output(waveform,audio,model_sampling_rate) From 34755f33aa8fa79cd6c3b6242232a58b12f3d2e7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 26 Sep 2022 17:09:29 +0530 Subject: [PATCH 094/375] minor bug fixes --- enhancer/inference.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 414f6ae..404fe95 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -26,6 +26,8 @@ class Inference: raise ValueError(f"Input file {audio} does not exist") else: audio,sr = load_audio(audio,sr=sr,) + if len(audio.shape) == 1: + audio = audio.reshape(1,-1) else: assert audio.shape[0] == 1, "Enhance inference only supports single waveform" @@ -40,11 +42,10 @@ class Inference: def batchify(waveform: torch.Tensor, window_size:int, step_size:Optional[int]=None): """ break input waveform into samples with duration specified. - Wrap into tensors of specified batch size """ assert waveform.ndim == 2, f"Expcted input waveform with 2 dimensions (channels,samples), got {waveform.ndim}" _,num_samples = waveform.shape - waveform = waveform.unsqueeze(0) + waveform = waveform.unsqueeze(-1) step_size = window_size//2 if step_size is None else step_size if num_samples >= window_size: @@ -55,24 +56,25 @@ class Inference: return waveform_batch - - def aggreagate(self,data:torch.Tensor,window_size:int, step_size:Optional[int]=None): + @staticmethod + def aggreagate(data:torch.Tensor,window_size:int,total_frames:int,step_size:Optional[int]=None, + window="hanning",): """ takes input as tensor outputs aggregated waveform """ - batch_size,n_channels,num_frames = data.shape + num_chunks,n_channels,num_frames = data.shape window = get_window(window=window,Nx=data.shape[-1]) window = torch.from_numpy(window).to(data.device) data *= window data = data.permute(1,2,0) data = F.fold(data, - (num_frames,1), + (total_frames,1), kernel_size=(window_size,1), stride=(step_size,1), - padding=(window_size,0)) + padding=(window_size,0)).squeeze(-1) - return data + return data.reshape(1,n_channels,-1) @staticmethod def write_output(waveform:torch.Tensor,filename:Union[str,Path],sr:int): From b55e12d15c7e8e054d383b168682c15bb08cee3b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 27 Sep 2022 11:31:40 +0530 Subject: [PATCH 095/375] add logging --- enhancer/models/model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 65946a2..c4be077 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -38,6 +38,8 @@ class Model(pl.LightningModule): assert num_channels ==1 , "Enhancer only support for mono channel models" self.dataset = dataset self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric","duration") + if self.logger: + self.logger.experiment.log_dict(dict(self.hparams),"hyperparameters.json") @property @@ -79,6 +81,9 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) + if self.logger: + self.logger.experiment.log_metrics({"train_loss":loss.item()}, step=self.global_step) + return {"loss":loss} def validation_step(self,batch,batch_idx:int): @@ -88,6 +93,8 @@ class Model(pl.LightningModule): prediction = self(mixed_waveform) loss = self.metric(prediction, target) + if self.logger: + self.logger.experiment.log_metrics({"val_loss":loss.item()}, step=self.global_step) return {"loss":loss} From b742756311c09b33711629bddfd88c920bfdc653 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 27 Sep 2022 12:54:50 +0530 Subject: [PATCH 096/375] rename num_layers to depth --- enhancer/models/waveunet.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index 62384fc..89b4bb7 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -61,7 +61,7 @@ class WaveUnet(Model): def __init__( self, num_channels:int=1, - num_layers:int=12, + depth:int=12, initial_output_channels:int=24, sampling_rate:int=16000, lr:float=1e-3, @@ -74,25 +74,25 @@ class WaveUnet(Model): sampling_rate=sampling_rate,lr=lr, dataset=dataset,duration=duration,loss=loss, metric=metric ) - self.save_hyperparameters("num_layers") + self.save_hyperparameters("depth") self.encoders = nn.ModuleList() self.decoders = nn.ModuleList() out_channels = initial_output_channels - for layer in range(num_layers): + for layer in range(depth): encoder = WavenetEncoder(num_channels,out_channels) self.encoders.append(encoder) num_channels = out_channels out_channels += initial_output_channels - if layer == num_layers -1 : - decoder = WavenetDecoder(num_layers * initial_output_channels + num_channels,num_channels) + if layer == depth -1 : + decoder = WavenetDecoder(depth * initial_output_channels + num_channels,num_channels) else: decoder = WavenetDecoder(num_channels+out_channels,num_channels) self.decoders.insert(0,decoder) - bottleneck_dim = num_layers * initial_output_channels + bottleneck_dim = depth * initial_output_channels self.bottleneck = nn.Sequential( nn.Conv1d(bottleneck_dim,bottleneck_dim, 15, stride=1, padding=7), From e4db841ebb0a173e352c028bdf4fe820340f4d23 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 27 Sep 2022 15:52:28 +0530 Subject: [PATCH 097/375] configure cli/default arguments --- cli/train.py | 35 ++++++++++++++ cli/train_config/config.yaml | 7 +++ cli/train_config/dataset/DNS-2020.yaml | 13 +++++ cli/train_config/dataset/Vctk.yaml | 15 ++++++ cli/train_config/hyperparameters/default.yaml | 4 ++ cli/train_config/mlflow/experiment.yaml | 2 + cli/train_config/model/Demucs.yaml | 18 +++++++ cli/train_config/model/WaveUnet.yaml | 5 ++ cli/train_config/optimizer/Adam.yaml | 6 +++ cli/train_config/trainer/default.yml | 47 +++++++++++++++++++ cli/train_config/trainer/fastrun_dev.yaml | 3 ++ 11 files changed, 155 insertions(+) create mode 100644 cli/train.py create mode 100644 cli/train_config/config.yaml create mode 100644 cli/train_config/dataset/DNS-2020.yaml create mode 100644 cli/train_config/dataset/Vctk.yaml create mode 100644 cli/train_config/hyperparameters/default.yaml create mode 100644 cli/train_config/mlflow/experiment.yaml create mode 100644 cli/train_config/model/Demucs.yaml create mode 100644 cli/train_config/model/WaveUnet.yaml create mode 100644 cli/train_config/optimizer/Adam.yaml create mode 100644 cli/train_config/trainer/default.yml create mode 100644 cli/train_config/trainer/fastrun_dev.yaml diff --git a/cli/train.py b/cli/train.py new file mode 100644 index 0000000..e6644e8 --- /dev/null +++ b/cli/train.py @@ -0,0 +1,35 @@ +import hydra +from hydra.core.config_store import ConfigStore +from hydra.utils import instantiate + +from omegaconf import DictConfig,OmegaConf +from pytorch_lightning import Trainer +from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping +from pytorch_lightning.loggers import MLFlowLogger +import torch + + +from enhancer.models.demucs import Demucs +from enhancer.data.dataset import EnhancerDataset + + +@hydra.main(config_path="train_config",config_name="config") +def main(config: DictConfig): + + logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, + run_name=config.mlflow.run_name) + + + parameters = config.hyperparameters + + dataset = instantiate(config.dataset) + model = instantiate(config.model,dataset=dataset,lr=parameters.get("lr"), + loss=parameters.get("loss"), metric = parameters.get("metric")) + + trainer = instantiate(config.trainer,logger=logger) + trainer.fit(model) + + + +if __name__=="__main__": + main() \ No newline at end of file diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml new file mode 100644 index 0000000..7845b01 --- /dev/null +++ b/cli/train_config/config.yaml @@ -0,0 +1,7 @@ +defaults: + - model : Demucs + - dataset : Vctk + - optimizer : Adam + - hyperparameters : default + - trainer : fastrun_dev + - mlflow : experiment \ No newline at end of file diff --git a/cli/train_config/dataset/DNS-2020.yaml b/cli/train_config/dataset/DNS-2020.yaml new file mode 100644 index 0000000..f59cb2b --- /dev/null +++ b/cli/train_config/dataset/DNS-2020.yaml @@ -0,0 +1,13 @@ +_target_: enhancer.data.dataset.EnhancerDataset +root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk_test +name : dns-2020 +duration : 1.0 +sampling_rate: 16000 +batch_size: 32 +files: + root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk_test + train_clean : clean_test_wav + test_clean : clean_test_wav + train_noisy : clean_test_wav + test_noisy : clean_test_wav + diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml new file mode 100644 index 0000000..1788177 --- /dev/null +++ b/cli/train_config/dataset/Vctk.yaml @@ -0,0 +1,15 @@ +_target_: enhancer.data.dataset.EnhancerDataset +root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk_test + +files: + train_clean : clean_test_wav + test_clean : clean_test_wav + train_noisy : clean_test_wav + test_noisy : clean_test_wav + +name : vctk +duration : 1.0 +sampling_rate: 48000 +batch_size: 32 + + diff --git a/cli/train_config/hyperparameters/default.yaml b/cli/train_config/hyperparameters/default.yaml new file mode 100644 index 0000000..5cbdcb0 --- /dev/null +++ b/cli/train_config/hyperparameters/default.yaml @@ -0,0 +1,4 @@ +loss : mse +metric : mae +lr : 0.001 +num_epochs : 10 diff --git a/cli/train_config/mlflow/experiment.yaml b/cli/train_config/mlflow/experiment.yaml new file mode 100644 index 0000000..b64b125 --- /dev/null +++ b/cli/train_config/mlflow/experiment.yaml @@ -0,0 +1,2 @@ +experiment_name : "myexp" +run_name : "myrun" \ No newline at end of file diff --git a/cli/train_config/model/Demucs.yaml b/cli/train_config/model/Demucs.yaml new file mode 100644 index 0000000..27603dc --- /dev/null +++ b/cli/train_config/model/Demucs.yaml @@ -0,0 +1,18 @@ +_target_: enhancer.models.demucs.Demucs +num_channels: 1 +resample: 4 +sampling_rate : 16000 + +encoder_decoder: + depth: 5 + initial_output_channels: 48 + kernel_size: 8 + stride: 1 + growth_factor: 2 + glu: True + +lstm: + bidirectional: False + num_layers: 2 + + diff --git a/cli/train_config/model/WaveUnet.yaml b/cli/train_config/model/WaveUnet.yaml new file mode 100644 index 0000000..d641bcd --- /dev/null +++ b/cli/train_config/model/WaveUnet.yaml @@ -0,0 +1,5 @@ +_target_: enhancer.models.waveunet.WaveUnet +num_channels : 1 +depth : 12 +initial_output_channels: 24 +sampling_rate : 16000 diff --git a/cli/train_config/optimizer/Adam.yaml b/cli/train_config/optimizer/Adam.yaml new file mode 100644 index 0000000..7952b81 --- /dev/null +++ b/cli/train_config/optimizer/Adam.yaml @@ -0,0 +1,6 @@ +_target_: torch.optim.Adam +lr: 1e-3 +betas: [0.9, 0.999] +eps: 1e-08 +weight_decay: 0 +amsgrad: False diff --git a/cli/train_config/trainer/default.yml b/cli/train_config/trainer/default.yml new file mode 100644 index 0000000..eeb5b85 --- /dev/null +++ b/cli/train_config/trainer/default.yml @@ -0,0 +1,47 @@ +# @package _group_ +_target_: pytorch_lightning.Trainer +accelerator: auto +accumulate_grad_batches: 1 +amp_backend: native +auto_lr_find: False +auto_scale_batch_size: False +auto_select_gpus: True +benchmark: False +check_val_every_n_epoch: 1 +detect_anomaly: False +deterministic: False +devices: auto +enable_checkpointing: True +enable_model_summary: True +enable_progress_bar: True +fast_dev_run: False +gpus: null +gradient_clip_val: 0 +gradient_clip_algorithm: norm +ipus: null +limit_predict_batches: 1.0 +limit_test_batches: 1.0 +limit_train_batches: 1.0 +limit_val_batches: 1.0 +log_every_n_steps: 50 +max_epochs: 1000 +max_steps: null +max_time: null +min_epochs: 1 +min_steps: null +move_metrics_to_cpu: False +multiple_trainloader_mode: max_size_cycle +num_nodes: 1 +num_processes: 1 +num_sanity_val_steps: 2 +overfit_batches: 0.0 +precision: 32 +profiler: null +reload_dataloaders_every_n_epochs: 0 +replace_sampler_ddp: True +strategy: null +sync_batchnorm: False +tpu_cores: null +track_grad_norm: -1 +val_check_interval: 1.0 +weights_save_path: null diff --git a/cli/train_config/trainer/fastrun_dev.yaml b/cli/train_config/trainer/fastrun_dev.yaml new file mode 100644 index 0000000..5d0895f --- /dev/null +++ b/cli/train_config/trainer/fastrun_dev.yaml @@ -0,0 +1,3 @@ +# @package _group_ +_target_: pytorch_lightning.Trainer +fast_dev_run: True From 634f146ca713ef825abaec62b810aa08645654ff Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 27 Sep 2022 15:52:51 +0530 Subject: [PATCH 098/375] fix typo --- enhancer/loss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index af6a4ba..693e610 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -45,7 +45,7 @@ class Avergeloss(nn.Module): def validate_loss(self,loss:str): if loss not in LOSS_MAP.keys(): - raise ValueError(f"Invalid loss function {loss}, available loss functions are {LOSS_MAP.keys()}") + raise ValueError(f"Invalid loss function {loss}, available loss functions are {tuple([loss for loss in LOSS_MAP.keys()])}") else: return LOSS_MAP[loss] @@ -59,6 +59,6 @@ class Avergeloss(nn.Module): -LOSS_MAP = {"mea":mean_absolute_error, "mse": mean_squared_error} +LOSS_MAP = {"mae":mean_absolute_error, "mse": mean_squared_error} From 9ab996e5c73a89598aa3e695d8d386e65bafdb44 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 27 Sep 2022 22:26:47 +0530 Subject: [PATCH 099/375] add callbacks --- cli/train.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/cli/train.py b/cli/train.py index e6644e8..bc5e287 100644 --- a/cli/train.py +++ b/cli/train.py @@ -1,21 +1,14 @@ import hydra -from hydra.core.config_store import ConfigStore from hydra.utils import instantiate - -from omegaconf import DictConfig,OmegaConf -from pytorch_lightning import Trainer +from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger -import torch - - -from enhancer.models.demucs import Demucs -from enhancer.data.dataset import EnhancerDataset @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): + callbacks = [] logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, run_name=config.mlflow.run_name) @@ -26,7 +19,22 @@ def main(config: DictConfig): model = instantiate(config.model,dataset=dataset,lr=parameters.get("lr"), loss=parameters.get("loss"), metric = parameters.get("metric")) - trainer = instantiate(config.trainer,logger=logger) + checkpoint = ModelCheckpoint( + dirpath="",filename="model",monitor=parameters.get("loss"),verbose=False, + mode="min",every_n_epochs=1 + ) + callbacks.append(checkpoint) + early_stopping = EarlyStopping( + monitor=parameters.get("loss"), + mode="min", + min_delta=0.0, + patience=100, + strict=True, + verbose=False, + ) + callbacks.append(early_stopping) + + trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) trainer.fit(model) From d70bcd214d14a47fff2822a5c20b0ef44540571b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 10:06:35 +0530 Subject: [PATCH 100/375] rmv main --- main.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 main.py diff --git a/main.py b/main.py deleted file mode 100644 index a626393..0000000 --- a/main.py +++ /dev/null @@ -1,19 +0,0 @@ -import hydra -import torch -from hydra.core.config_store import ConfigStore - -from enhancer.utils.config import EnhancerConfig - -cs = ConfigStore.instance() -cs.store(name="enhancer_config", node=EnhancerConfig) - - -@hydra.main(config_path=".",config_name="conf") -def main(cfg: EnhancerConfig): - - print(cfg.paths.data) - - - -if __name__=="__main__": - main() \ No newline at end of file From 157f3a45ce4cbd6fd752c8c0704efad2a6f68a5f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 10:12:41 +0530 Subject: [PATCH 101/375] set hawk path --- cli/train_config/dataset/Vctk.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml index 1788177..d40f27f 100644 --- a/cli/train_config/dataset/Vctk.yaml +++ b/cli/train_config/dataset/Vctk.yaml @@ -1,15 +1,15 @@ _target_: enhancer.data.dataset.EnhancerDataset -root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk_test - -files: - train_clean : clean_test_wav - test_clean : clean_test_wav - train_noisy : clean_test_wav - test_noisy : clean_test_wav - name : vctk +root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 1.0 sampling_rate: 48000 batch_size: 32 +files: + train_clean : clean_trainset_56spk_wav + test_clean : clean_testset_wav + train_noisy : noisy_trainset_56spk_wav + test_noisy : noisy_testset_wav + + From 6b8492cd8acf1e6389d2df74060c1fc89382a303 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 10:15:43 +0530 Subject: [PATCH 102/375] rmv unused config --- conf.yaml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 conf.yaml diff --git a/conf.yaml b/conf.yaml deleted file mode 100644 index 969bde8..0000000 --- a/conf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -files: - train_clean: clean_testset_wav - train_noisy: noisy_testset_wav - test_clean: clean_testset_wav - test_noisy: noisy_testset_wav -paths: - data: ${hydra:runtime.cwd}/datasets/vctk - log: ./runs \ No newline at end of file From 78b8a0582da7d79c3f4d6c51510a2081f5e962c6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 10:16:06 +0530 Subject: [PATCH 103/375] add pl --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c279ff0..c74e46d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ mlflow==1.23.1 protobuf==3.19.3 boto3==1.23.9 huggingface-hub==0.4.0 +pytorch-lightning==1.5.10 From 9043e4c9f0752be1718b28dfe81e004295b65587 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 10:16:17 +0530 Subject: [PATCH 104/375] configure hawk --- hpc_entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh index 0b8be4b..aa6410e 100644 --- a/hpc_entrypoint.sh +++ b/hpc_entrypoint.sh @@ -19,7 +19,7 @@ echo "Load HPC modules" module load anaconda echo "Activate Environment" -source activate deep-transcriber +source activate enhancer export TRANSFORMERS_OFFLINE=True export PYTHONPATH=${PYTHONPATH}:$/scratch/$USER/enhancer @@ -32,4 +32,4 @@ mkdir temp #python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test echo "Start Training..." -python enhancer/main.py +python cli/train.py From d2c8a33f1371c907439670db7b106c051870bcd7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 11:18:04 +0530 Subject: [PATCH 105/375] change pythonpath --- hpc_entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh index aa6410e..f502ed5 100644 --- a/hpc_entrypoint.sh +++ b/hpc_entrypoint.sh @@ -21,12 +21,13 @@ module load anaconda echo "Activate Environment" source activate enhancer export TRANSFORMERS_OFFLINE=True -export PYTHONPATH=${PYTHONPATH}:$/scratch/$USER/enhancer +export PYTHONPATH=${PYTHONPATH}:/home/c.sistc3/enhancer source ~/mlflow_settings.sh echo "Making temp dir" mkdir temp +pwd #python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TRAIN --output ./data/train #python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test From 3570beb2d8ae6ed8f667b4babbfc5db4bbdcdd9b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 11:31:15 +0530 Subject: [PATCH 106/375] change pythonpath --- hpc_entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh index f502ed5..2a4e3c7 100644 --- a/hpc_entrypoint.sh +++ b/hpc_entrypoint.sh @@ -21,7 +21,8 @@ module load anaconda echo "Activate Environment" source activate enhancer export TRANSFORMERS_OFFLINE=True -export PYTHONPATH=${PYTHONPATH}:/home/c.sistc3/enhancer +export PYTHONPATH=${PYTHONPATH}:/scratch/c.sistc3/enhancer +echo $PYTHONPATH source ~/mlflow_settings.sh From 38e38dc7dfe440cf051600d490a75f28e3975358 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 11:51:58 +0530 Subject: [PATCH 107/375] check pythonpath --- cli/train.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/train.py b/cli/train.py index bc5e287..9ed1cd0 100644 --- a/cli/train.py +++ b/cli/train.py @@ -4,6 +4,7 @@ from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger +from enhancer.data.dataset import EnhancerDataset @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): From 0a80521c0295223f0d42fc7b84dbddc9bcd4584e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 11:55:17 +0530 Subject: [PATCH 108/375] rmv matplotlib --- enhancer/utils/io.py | 1 - 1 file changed, 1 deletion(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 80bd1a4..afc19e8 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -1,7 +1,6 @@ import os import librosa from typing import Optional -from matplotlib.pyplot import axis import numpy as np import torch import torchaudio From 6c595e844621c6233b99ea754a650dca82518a65 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 12:06:06 +0530 Subject: [PATCH 109/375] torchaudio --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c74e46d..e7fcd24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,5 +10,6 @@ tqdm==4.64.0 mlflow==1.23.1 protobuf==3.19.3 boto3==1.23.9 +torchaudio==0.10.2 huggingface-hub==0.4.0 pytorch-lightning==1.5.10 From 443c0a93f5cc8a1bb91374b5feb079dca3d652ed Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 12:49:41 +0530 Subject: [PATCH 110/375] set hydra to full error --- hpc_entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh index 2a4e3c7..7372eb9 100644 --- a/hpc_entrypoint.sh +++ b/hpc_entrypoint.sh @@ -22,6 +22,8 @@ echo "Activate Environment" source activate enhancer export TRANSFORMERS_OFFLINE=True export PYTHONPATH=${PYTHONPATH}:/scratch/c.sistc3/enhancer +export HYDRA_FULL_ERROR=1 + echo $PYTHONPATH source ~/mlflow_settings.sh From f4e625e41289396d8e1008c66e25bee0fbacef37 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 12:49:59 +0530 Subject: [PATCH 111/375] rmv import --- cli/train.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/train.py b/cli/train.py index 9ed1cd0..9aa497d 100644 --- a/cli/train.py +++ b/cli/train.py @@ -4,8 +4,6 @@ from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger -from enhancer.data.dataset import EnhancerDataset - @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): From fb31711653d20c7abf1f87b438b04da132e6d650 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 12:50:07 +0530 Subject: [PATCH 112/375] rmv import --- enhancer/models/demucs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 115f63e..0bb81d1 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,5 +1,3 @@ -from base64 import encode -from turtle import forward from typing import Optional, Union, List from torch import nn import torch.nn.functional as F From 52314a817c9da1677d8b72ca7c6f4382363ee5eb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 15:07:36 +0530 Subject: [PATCH 113/375] rmv unused imports --- enhancer/inference.py | 1 - 1 file changed, 1 deletion(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 404fe95..6e9cff7 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -10,7 +10,6 @@ from pathlib import Path from librosa import load as load_audio from enhancer.utils import Audio -from enhancer.utils.config import DEFAULT_DEVICE class Inference: From 52788c029baca5840497be5db04d06bb1617b169 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 15:07:55 +0530 Subject: [PATCH 114/375] rmv dataclasses unused --- enhancer/utils/config.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/enhancer/utils/config.py b/enhancer/utils/config.py index 0aaa2e3..e9af6a0 100644 --- a/enhancer/utils/config.py +++ b/enhancer/utils/config.py @@ -1,18 +1,11 @@ from dataclasses import dataclass -@dataclass -class Paths: - log : str - data : str - @dataclass class Files: + root_dir : str train_clean : str train_noisy : str test_clean : str test_noisy : str -@dataclass -class EnhancerConfig: - path : Paths - files: Files \ No newline at end of file + From ea3861eca92957be26417c83570bf3893e0d6fce Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 15:08:13 +0530 Subject: [PATCH 115/375] rmv np --- tests/test_inference.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_inference.py b/tests/test_inference.py index 303aa67..5eb7442 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -1,6 +1,5 @@ import pytest import torch -import numpy as np from enhancer.inference import Inference From 60b5d00bab9dc7df59c8655da3f379f2315c6924 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 21:51:00 +0530 Subject: [PATCH 116/375] set trainer to default --- cli/train_config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 7845b01..6b5d98e 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -3,5 +3,5 @@ defaults: - dataset : Vctk - optimizer : Adam - hyperparameters : default - - trainer : fastrun_dev + - trainer : default - mlflow : experiment \ No newline at end of file From 658e4d08a5df00774392ce9e54ec7e9f1b9d680d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 28 Sep 2022 22:05:10 +0530 Subject: [PATCH 117/375] add num_workers as arg --- enhancer/data/dataset.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index f4e7e4a..98abe8a 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,6 +1,4 @@ - -from dataclasses import dataclass -import glob +import multiprocessing import math import os import pytorch_lightning as pl @@ -46,7 +44,8 @@ class TaskDataset(pl.LightningDataModule): duration:float=1.0, sampling_rate:int=48000, matching_function = None, - batch_size=32): + batch_size=32, + num_workers:Optional[int]=None): super().__init__() self.name = name @@ -56,6 +55,9 @@ class TaskDataset(pl.LightningDataModule): self.batch_size = batch_size self.matching_function = matching_function self._validation = [] + if num_workers is None: + num_workers = multiprocessing.cpu_count()//2 + self.num_workers = num_workers def setup(self, stage: Optional[str] = None): @@ -85,10 +87,10 @@ class TaskDataset(pl.LightningDataModule): self._validation.append(({"clean":clean,"noisy":noisy}, start_time)) def train_dataloader(self): - return DataLoader(TrainDataset(self), batch_size = self.batch_size,num_workers=2) + return DataLoader(TrainDataset(self), batch_size = self.batch_size,num_workers=self.num_workers) def val_dataloader(self): - return DataLoader(ValidDataset(self), batch_size = self.batch_size,num_workers=2) + return DataLoader(ValidDataset(self), batch_size = self.batch_size,num_workers=self.num_workers) class EnhancerDataset(TaskDataset): """Dataset object for creating clean-noisy speech enhancement datasets""" @@ -101,7 +103,8 @@ class EnhancerDataset(TaskDataset): duration=1.0, sampling_rate=48000, matching_function=None, - batch_size=32): + batch_size=32, + num_workers:Optional[int]=None): super().__init__( name=name, @@ -110,7 +113,8 @@ class EnhancerDataset(TaskDataset): sampling_rate=sampling_rate, duration=duration, matching_function = matching_function, - batch_size=batch_size + batch_size=batch_size, + num_workers = num_workers, ) From 7be0a2d0bd98c06b12c1cb3182b27f35708ac6e5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 09:39:01 +0530 Subject: [PATCH 118/375] rename to yaml --- cli/train_config/trainer/{default.yml => default.yaml} | 1 - 1 file changed, 1 deletion(-) rename cli/train_config/trainer/{default.yml => default.yaml} (98%) diff --git a/cli/train_config/trainer/default.yml b/cli/train_config/trainer/default.yaml similarity index 98% rename from cli/train_config/trainer/default.yml rename to cli/train_config/trainer/default.yaml index eeb5b85..6c15867 100644 --- a/cli/train_config/trainer/default.yml +++ b/cli/train_config/trainer/default.yaml @@ -1,4 +1,3 @@ -# @package _group_ _target_: pytorch_lightning.Trainer accelerator: auto accumulate_grad_batches: 1 From 8271573e1c4c87545ad7a4e44c987d5c001c831a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 09:39:16 +0530 Subject: [PATCH 119/375] rmv depreciated --- cli/train_config/trainer/fastrun_dev.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/train_config/trainer/fastrun_dev.yaml b/cli/train_config/trainer/fastrun_dev.yaml index 5d0895f..682149e 100644 --- a/cli/train_config/trainer/fastrun_dev.yaml +++ b/cli/train_config/trainer/fastrun_dev.yaml @@ -1,3 +1,2 @@ -# @package _group_ _target_: pytorch_lightning.Trainer fast_dev_run: True From 25568bb84a57e360bf4f2f40cd5b1e1de656ab01 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:13:47 +0530 Subject: [PATCH 120/375] si-sdr --- enhancer/loss.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/enhancer/loss.py b/enhancer/loss.py index 693e610..d673ec3 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -1,3 +1,5 @@ +from modulefinder import Module +from turtle import forward import torch import torch.nn as nn @@ -32,6 +34,20 @@ class mean_absolute_error(nn.Module): return self.loss_fun(prediction, target) +class Si_SDR(nn.Module): + + def __init__( + self + ): + pass + + def forward(self,prediction:torch.Tensor, target:torch.Tensor): + + if prediction.size() != target.size() or target.ndim < 3: + raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""") + prediction,target = prediction.unsqueeze(1),target.unsqueeze(1) + class Avergeloss(nn.Module): def __init__(self,losses): From a05efc7866a7094228a97bdc54be80087435870a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:14:25 +0530 Subject: [PATCH 121/375] change exp name --- cli/train_config/mlflow/experiment.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/train_config/mlflow/experiment.yaml b/cli/train_config/mlflow/experiment.yaml index b64b125..2995c60 100644 --- a/cli/train_config/mlflow/experiment.yaml +++ b/cli/train_config/mlflow/experiment.yaml @@ -1,2 +1,2 @@ -experiment_name : "myexp" -run_name : "myrun" \ No newline at end of file +experiment_name : shahules/enhancer +run_name : baseline \ No newline at end of file From 31a3335ff026d3a806c5ceb8cd81df5ab5bb5047 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:16:59 +0530 Subject: [PATCH 122/375] do fastrun --- cli/train_config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 6b5d98e..7845b01 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -3,5 +3,5 @@ defaults: - dataset : Vctk - optimizer : Adam - hyperparameters : default - - trainer : default + - trainer : fastrun_dev - mlflow : experiment \ No newline at end of file From 206355270ea300156563304e0b25d8b76a6dfd68 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:36:47 +0530 Subject: [PATCH 123/375] update progess bar --- cli/train.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/train.py b/cli/train.py index 9aa497d..5a056a2 100644 --- a/cli/train.py +++ b/cli/train.py @@ -3,11 +3,14 @@ from hydra.utils import instantiate from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger +from pytorch_lightning.callbacks import TQDMProgressBar + @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): callbacks = [] + callbacks.append(TQDMProgressBar(refresh_rate=10)) logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, run_name=config.mlflow.run_name) From 6fd5f964ddf31eddad536c3c1021084888e8fad4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:37:00 +0530 Subject: [PATCH 124/375] increase num epochs --- cli/train_config/hyperparameters/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/hyperparameters/default.yaml b/cli/train_config/hyperparameters/default.yaml index 5cbdcb0..4931c7c 100644 --- a/cli/train_config/hyperparameters/default.yaml +++ b/cli/train_config/hyperparameters/default.yaml @@ -1,4 +1,4 @@ loss : mse metric : mae lr : 0.001 -num_epochs : 10 +num_epochs : 100 From 387fc0149394dc98b2afea16695f6fe3021c3b25 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:37:52 +0530 Subject: [PATCH 125/375] decrease num epochs --- cli/train_config/trainer/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/trainer/default.yaml b/cli/train_config/trainer/default.yaml index 6c15867..633c6ba 100644 --- a/cli/train_config/trainer/default.yaml +++ b/cli/train_config/trainer/default.yaml @@ -23,7 +23,7 @@ limit_test_batches: 1.0 limit_train_batches: 1.0 limit_val_batches: 1.0 log_every_n_steps: 50 -max_epochs: 1000 +max_epochs: 100 max_steps: null max_time: null min_epochs: 1 From 2cdd81af36091f249db3031bfb497ba3346dd5a3 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 10:38:18 +0530 Subject: [PATCH 126/375] trainer to default --- cli/train_config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 7845b01..6b5d98e 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -3,5 +3,5 @@ defaults: - dataset : Vctk - optimizer : Adam - hyperparameters : default - - trainer : fastrun_dev + - trainer : default - mlflow : experiment \ No newline at end of file From 838b7d2357c75c14e09bc7c4daef1518269fae63 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 11:00:41 +0530 Subject: [PATCH 127/375] add SI-SDR --- enhancer/loss.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index d673ec3..3bc6fa2 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -37,16 +37,38 @@ class mean_absolute_error(nn.Module): class Si_SDR(nn.Module): def __init__( - self + self, + reduction:str="mean" ): - pass + super().__init__() + if reduction in ["sum","mean",None]: + self.reduction = reduction + else: + raise TypeError("Invalid reduction, valid options are sum, mean, None") def forward(self,prediction:torch.Tensor, target:torch.Tensor): if prediction.size() != target.size() or target.ndim < 3: raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) got {prediction.size()} and {target.size()} instead""") - prediction,target = prediction.unsqueeze(1),target.unsqueeze(1) + + target_energy = torch.sum(target**2,keepdim=True,dim=-1) + scaling_factor = torch.sum(prediction*target,keepdim=True,dim=-1) / target_energy + target_projection = target * scaling_factor + noise = prediction - target_projection + ratio = torch.sum(target_projection**2,dim=-1) / torch.sum(noise**2,dim=-1) + si_sdr = 10*torch.log10(ratio).mean(dim=-1) + + if self.reduction == "sum": + si_sdr = si_sdr.sum() + elif self.reduction == "mean": + si_sdr = si_sdr.mean() + else: + pass + + return si_sdr + + class Avergeloss(nn.Module): @@ -75,6 +97,8 @@ class Avergeloss(nn.Module): -LOSS_MAP = {"mae":mean_absolute_error, "mse": mean_squared_error} +LOSS_MAP = {"mae":mean_absolute_error, + "mse": mean_squared_error, + "SI-SDR":Si_SDR} From cd052e77c70acf903be7f2b34054de1a88647123 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 11:15:53 +0530 Subject: [PATCH 128/375] BS to 16 --- cli/train_config/dataset/Vctk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml index d40f27f..70dda64 100644 --- a/cli/train_config/dataset/Vctk.yaml +++ b/cli/train_config/dataset/Vctk.yaml @@ -3,7 +3,7 @@ name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 1.0 sampling_rate: 48000 -batch_size: 32 +batch_size: 16 files: train_clean : clean_trainset_56spk_wav From 18759a3f843a29b6d34cf2fb5d7844c2eec1b54c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 12:15:21 +0530 Subject: [PATCH 129/375] halve BS --- cli/train_config/dataset/Vctk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml index 70dda64..07adc5d 100644 --- a/cli/train_config/dataset/Vctk.yaml +++ b/cli/train_config/dataset/Vctk.yaml @@ -3,7 +3,7 @@ name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 1.0 sampling_rate: 48000 -batch_size: 16 +batch_size: 8 files: train_clean : clean_trainset_56spk_wav From e22cecaf2080521eef200c1fb418f874a6ca8346 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 12:27:04 +0530 Subject: [PATCH 130/375] fix model sr to dataset sr --- enhancer/models/model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index c4be077..980c583 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,5 +1,6 @@ from importlib import import_module from huggingface_hub import cached_download, hf_hub_url +import logging import numpy as np import os from typing import Optional, Union, List, Text, Dict, Any @@ -37,6 +38,9 @@ class Model(pl.LightningModule): super().__init__() assert num_channels ==1 , "Enhancer only support for mono channel models" self.dataset = dataset + if self.dataset is not None: + sampling_rate = self.dataset.sampling_rate + logging.warn("Setting model sampling rate same as dataset sampling rate") self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric","duration") if self.logger: self.logger.experiment.log_dict(dict(self.hparams),"hyperparameters.json") From fd526254416669ce3caf65ffaa0a7919bf664466 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 14:30:08 +0530 Subject: [PATCH 131/375] add job id to logging --- cli/train.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/train.py b/cli/train.py index 5a056a2..16677b4 100644 --- a/cli/train.py +++ b/cli/train.py @@ -1,3 +1,4 @@ +import os import hydra from hydra.utils import instantiate from omegaconf import DictConfig @@ -12,7 +13,7 @@ def main(config: DictConfig): callbacks = [] callbacks.append(TQDMProgressBar(refresh_rate=10)) logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, - run_name=config.mlflow.run_name) + run_name=config.mlflow.run_name, tags={"JOB_ID":os.environ.get("SLURM_JOBID")}) parameters = config.hyperparameters From a0f70010f2b53452584530bab879f04a27590850 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 16:05:02 +0530 Subject: [PATCH 132/375] rmv print --- enhancer/models/waveunet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index 89b4bb7..a6c0d34 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -125,7 +125,6 @@ class WaveUnet(Model): for layer,decoder in enumerate(self.decoders): out = F.interpolate(out, scale_factor=2, mode="linear") - print(out.shape,encoder_outputs[layer].shape) out = self.fix_last_dim(out,encoder_outputs[layer]) out = torch.cat([out,encoder_outputs[layer]],dim=1) out = decoder(out) From c1b67c1e3a60ca052ba68e04dd5ce7182120a67c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 16:11:45 +0530 Subject: [PATCH 133/375] use waveunet --- cli/train_config/config.yaml | 2 +- enhancer/models/waveunet.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 6b5d98e..61551bd 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : Demucs + - model : WaveUnet - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index a6c0d34..b354f55 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -70,6 +70,8 @@ class WaveUnet(Model): loss: Union[str, List] = "mse", metric:Union[str,List] = "mse" ): + duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None + sampling_rate = sampling_rate if dataset is None else dataset.sampling_rate super().__init__(num_channels=num_channels, sampling_rate=sampling_rate,lr=lr, dataset=dataset,duration=duration,loss=loss, metric=metric From 4e033d2ab5cc3dd1ccdb331f942a765aa8959a5a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 17:08:49 +0530 Subject: [PATCH 134/375] log metric --- enhancer/models/model.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 980c583..64bf201 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -38,9 +38,6 @@ class Model(pl.LightningModule): super().__init__() assert num_channels ==1 , "Enhancer only support for mono channel models" self.dataset = dataset - if self.dataset is not None: - sampling_rate = self.dataset.sampling_rate - logging.warn("Setting model sampling rate same as dataset sampling rate") self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric","duration") if self.logger: self.logger.experiment.log_dict(dict(self.hparams),"hyperparameters.json") @@ -86,7 +83,7 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) if self.logger: - self.logger.experiment.log_metrics({"train_loss":loss.item()}, step=self.global_step) + self.logger.experiment.log_metric("train_loss",loss.item(), step=self.global_step) return {"loss":loss} @@ -98,7 +95,7 @@ class Model(pl.LightningModule): loss = self.metric(prediction, target) if self.logger: - self.logger.experiment.log_metrics({"val_loss":loss.item()}, step=self.global_step) + self.logger.experiment.log_metric("val_loss",loss.item(), step=self.global_step) return {"loss":loss} From 79525df76ef60bc83c68b5d8e4b1c2d1dc0f4058 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 17:20:34 +0530 Subject: [PATCH 135/375] set sr to dataset sr --- enhancer/models/demucs.py | 7 ++++++- enhancer/models/waveunet.py | 8 +++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 0bb81d1..7c9d8ff 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,7 +1,8 @@ +import logging from typing import Optional, Union, List from torch import nn import torch.nn.functional as F -import math +import math from enhancer.models.model import Model from enhancer.data.dataset import EnhancerDataset @@ -112,6 +113,10 @@ class Demucs(Model): ): duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None + if dataset is not None: + if sampling_rate!=dataset.sampling_rate: + logging.warn(f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}") + sampling_rate = dataset.sampling_rate super().__init__(num_channels=num_channels, sampling_rate=sampling_rate,lr=lr, dataset=dataset,duration=duration,loss=loss, metric=metric) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index b354f55..f799352 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -1,5 +1,4 @@ -from tkinter import wantobjects -import wave +import logging import torch import torch.nn as nn import torch.nn.functional as F @@ -71,7 +70,10 @@ class WaveUnet(Model): metric:Union[str,List] = "mse" ): duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None - sampling_rate = sampling_rate if dataset is None else dataset.sampling_rate + if dataset is not None: + if sampling_rate!=dataset.sampling_rate: + logging.warn(f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}") + sampling_rate = dataset.sampling_rate super().__init__(num_channels=num_channels, sampling_rate=sampling_rate,lr=lr, dataset=dataset,duration=duration,loss=loss, metric=metric From 0bd5d3f994b5e644cce848eabb9cb04cfb148d13 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 17:52:16 +0530 Subject: [PATCH 136/375] fix log metric --- enhancer/models/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 64bf201..b030b23 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -83,7 +83,7 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) if self.logger: - self.logger.experiment.log_metric("train_loss",loss.item(), step=self.global_step) + self.logger.experiment.log_metric(key="train_loss",value=loss.item(), step=self.global_step) return {"loss":loss} @@ -95,7 +95,7 @@ class Model(pl.LightningModule): loss = self.metric(prediction, target) if self.logger: - self.logger.experiment.log_metric("val_loss",loss.item(), step=self.global_step) + self.logger.experiment.log_metric(key="val_loss",value=loss.item(), step=self.global_step) return {"loss":loss} From 192b8ffa7bd04b3c30eb592fb1e949e06365db06 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 18:01:50 +0530 Subject: [PATCH 137/375] downsample vctk --- cli/train_config/dataset/Vctk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml index 07adc5d..d1c8646 100644 --- a/cli/train_config/dataset/Vctk.yaml +++ b/cli/train_config/dataset/Vctk.yaml @@ -2,7 +2,7 @@ _target_: enhancer.data.dataset.EnhancerDataset name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 1.0 -sampling_rate: 48000 +sampling_rate: 16000 batch_size: 8 files: From fccbd88ba290ad6ef97e54dc0d9c72bc8d1e0ce1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 22:38:26 +0530 Subject: [PATCH 138/375] rmv sr filtering --- enhancer/data/dataset.py | 6 ++---- enhancer/data/fileprocessor.py | 26 +++++++++++--------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 98abe8a..5749c36 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -66,15 +66,13 @@ class TaskDataset(pl.LightningDataModule): train_clean = os.path.join(self.root_dir,self.files.train_clean) train_noisy = os.path.join(self.root_dir,self.files.train_noisy) fp = Fileprocessor.from_name(self.name,train_clean, - train_noisy,self.sampling_rate, - self.matching_function) + train_noisy, self.matching_function) self.train_data = fp.prepare_matching_dict() val_clean = os.path.join(self.root_dir,self.files.test_clean) val_noisy = os.path.join(self.root_dir,self.files.test_noisy) fp = Fileprocessor.from_name(self.name,val_clean, - val_noisy,self.sampling_rate, - self.matching_function) + val_noisy, self.matching_function) val_data = fp.prepare_matching_dict() for item in val_data: diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index 4df3e23..f903375 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -1,12 +1,13 @@ import glob import os +from re import S import numpy as np from scipy.io import wavfile class ProcessorFunctions: @staticmethod - def match_vtck(clean_path,noisy_path,sr): + def match_vtck(clean_path,noisy_path): matching_wavfiles = list() clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] @@ -18,16 +19,15 @@ class ProcessorFunctions: sr_clean, clean_file = wavfile.read(os.path.join(clean_path,file_name)) sr_noisy, noisy_file = wavfile.read(os.path.join(noisy_path,file_name)) if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==sr) and - (sr_noisy==sr)): + (sr_clean==sr_noisy)): matching_wavfiles.append( {"clean":os.path.join(clean_path,file_name),"noisy":os.path.join(noisy_path,file_name), - "duration":clean_file.shape[-1]/sr} + "duration":clean_file.shape[-1]/sr_clean} ) return matching_wavfiles @staticmethod - def match_dns2020(clean_path,noisy_path,sr): + def match_dns2020(clean_path,noisy_path): matching_wavfiles = dict() clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] @@ -38,11 +38,10 @@ class ProcessorFunctions: sr_clean, clean_file = wavfile.read(os.path.join(clean_path,clean_file)) sr_noisy, noisy_file = wavfile.read(noisy_file) if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==sr) and - (sr_noisy==sr)): + (sr_clean==sr_noisy)): matching_wavfiles.update( {"clean":os.path.join(clean_path,clean_file),"noisy":noisy_file, - "duration":clean_file.shape[-1]/sr} + "duration":clean_file.shape[-1]/sr_clean} ) return matching_wavfiles @@ -54,12 +53,10 @@ class Fileprocessor: self, clean_dir, noisy_dir, - sr = 16000, matching_function = None ): self.clean_dir = clean_dir self.noisy_dir = noisy_dir - self.sr = sr self.matching_function = matching_function @classmethod @@ -67,23 +64,22 @@ class Fileprocessor: name:str, clean_dir, noisy_dir, - sr, matching_function=None ): if name.lower() == "vctk": - return cls(clean_dir,noisy_dir,sr, ProcessorFunctions.match_vtck) + return cls(clean_dir,noisy_dir, ProcessorFunctions.match_vtck) elif name.lower() == "dns-2020": - return cls(clean_dir,noisy_dir,sr, ProcessorFunctions.match_dns2020) + return cls(clean_dir,noisy_dir, ProcessorFunctions.match_dns2020) else: - return cls(clean_dir,noisy_dir,sr, matching_function) + return cls(clean_dir,noisy_dir, matching_function) def prepare_matching_dict(self): if self.matching_function is None: raise ValueError("Not a valid matching function") - return self.matching_function(self.clean_dir,self.noisy_dir,self.sr) + return self.matching_function(self.clean_dir,self.noisy_dir) From 5983b094701f2858c65f9d96ff6f0517b3c2fe14 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 22:38:59 +0530 Subject: [PATCH 139/375] fix logger --- enhancer/models/model.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index b030b23..20c8196 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -83,7 +83,9 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) if self.logger: - self.logger.experiment.log_metric(key="train_loss",value=loss.item(), step=self.global_step) + self.logger.experiment.log_metric(run_id=self.logger.run_id, + key="train_loss", value=loss.item(), + step=self.global_step) return {"loss":loss} @@ -95,7 +97,9 @@ class Model(pl.LightningModule): loss = self.metric(prediction, target) if self.logger: - self.logger.experiment.log_metric(key="val_loss",value=loss.item(), step=self.global_step) + self.logger.experiment.log_metric(run_id=self.logger.run_id, + key="val_loss",value=loss.item(), + step=self.global_step) return {"loss":loss} From bd0bfbeea79af0d0990344fa67f3c2ed62fdc38a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 29 Sep 2022 22:39:55 +0530 Subject: [PATCH 140/375] ignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ae420f9..6eb0fe3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -##local +#local +cli/train_config/dataset/Vctk_local.yaml .DS_Store outputs/ datasets/ From c717e7c38cf22209a823d325f721f8e9e170ad0a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 10:10:35 +0530 Subject: [PATCH 141/375] log val loss --- enhancer/models/model.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 20c8196..8e607ed 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -86,7 +86,7 @@ class Model(pl.LightningModule): self.logger.experiment.log_metric(run_id=self.logger.run_id, key="train_loss", value=loss.item(), step=self.global_step) - + self.log("train_loss",loss.item()) return {"loss":loss} def validation_step(self,batch,batch_idx:int): @@ -95,13 +95,20 @@ class Model(pl.LightningModule): target = batch["clean"] prediction = self(mixed_waveform) - loss = self.metric(prediction, target) + metric_val = self.metric(prediction, target) + loss_val = self.loss(prediction, target) + self.log("val_metric",metric_val.item()) + self.log("val_loss",loss_val.item()) + if self.logger: self.logger.experiment.log_metric(run_id=self.logger.run_id, - key="val_loss",value=loss.item(), + key="val_loss",value=loss_val.item(), + step=self.global_step) + self.logger.experiment.log_metric(run_id=self.logger.run_id, + key="val_metric",value=metric_val.item(), step=self.global_step) - return {"loss":loss} + return {"loss":loss_val} def on_save_checkpoint(self, checkpoint): From 610c23a0eb18035a221def9bc4bff8debb89480b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 10:10:51 +0530 Subject: [PATCH 142/375] change monitor --- cli/train.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/train.py b/cli/train.py index 16677b4..88e513a 100644 --- a/cli/train.py +++ b/cli/train.py @@ -4,14 +4,12 @@ from hydra.utils import instantiate from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger -from pytorch_lightning.callbacks import TQDMProgressBar @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): callbacks = [] - callbacks.append(TQDMProgressBar(refresh_rate=10)) logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, run_name=config.mlflow.run_name, tags={"JOB_ID":os.environ.get("SLURM_JOBID")}) @@ -23,12 +21,12 @@ def main(config: DictConfig): loss=parameters.get("loss"), metric = parameters.get("metric")) checkpoint = ModelCheckpoint( - dirpath="",filename="model",monitor=parameters.get("loss"),verbose=False, + dirpath="",filename="model",monitor="valid_loss",verbose=False, mode="min",every_n_epochs=1 ) callbacks.append(checkpoint) early_stopping = EarlyStopping( - monitor=parameters.get("loss"), + monitor="valid_loss", mode="min", min_delta=0.0, patience=100, From 7b0e7e2312be6faf603969bfe6c5441ab89c05e6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 10:15:28 +0530 Subject: [PATCH 143/375] change config --- cli/train_config/config.yaml | 2 +- cli/train_config/hyperparameters/default.yaml | 2 +- cli/train_config/trainer/default.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 61551bd..6b5d98e 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : WaveUnet + - model : Demucs - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/cli/train_config/hyperparameters/default.yaml b/cli/train_config/hyperparameters/default.yaml index 4931c7c..04b099b 100644 --- a/cli/train_config/hyperparameters/default.yaml +++ b/cli/train_config/hyperparameters/default.yaml @@ -1,4 +1,4 @@ loss : mse metric : mae -lr : 0.001 +lr : 0.0001 num_epochs : 100 diff --git a/cli/train_config/trainer/default.yaml b/cli/train_config/trainer/default.yaml index 633c6ba..560305b 100644 --- a/cli/train_config/trainer/default.yaml +++ b/cli/train_config/trainer/default.yaml @@ -9,7 +9,7 @@ benchmark: False check_val_every_n_epoch: 1 detect_anomaly: False deterministic: False -devices: auto +devices: -1 enable_checkpointing: True enable_model_summary: True enable_progress_bar: True @@ -22,7 +22,7 @@ limit_predict_batches: 1.0 limit_test_batches: 1.0 limit_train_batches: 1.0 limit_val_batches: 1.0 -log_every_n_steps: 50 +log_every_n_steps: 10 max_epochs: 100 max_steps: null max_time: null From 6aa502c3dcb2e53a2101fc17563b2a3580a9b5c9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 10:27:11 +0530 Subject: [PATCH 144/375] wavenet trainig --- cli/train_config/config.yaml | 2 +- cli/train_config/trainer/default.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 6b5d98e..61551bd 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : Demucs + - model : WaveUnet - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/cli/train_config/trainer/default.yaml b/cli/train_config/trainer/default.yaml index 560305b..ab4e273 100644 --- a/cli/train_config/trainer/default.yaml +++ b/cli/train_config/trainer/default.yaml @@ -2,7 +2,7 @@ _target_: pytorch_lightning.Trainer accelerator: auto accumulate_grad_batches: 1 amp_backend: native -auto_lr_find: False +auto_lr_find: True auto_scale_batch_size: False auto_select_gpus: True benchmark: False From 9170666592dbba76035e2cc3237fcc5a7c60ed50 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 10:55:39 +0530 Subject: [PATCH 145/375] reduce params --- cli/train_config/config.yaml | 2 +- cli/train_config/model/Demucs.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 61551bd..6b5d98e 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : WaveUnet + - model : Demucs - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/cli/train_config/model/Demucs.yaml b/cli/train_config/model/Demucs.yaml index 27603dc..1006e71 100644 --- a/cli/train_config/model/Demucs.yaml +++ b/cli/train_config/model/Demucs.yaml @@ -1,11 +1,11 @@ _target_: enhancer.models.demucs.Demucs num_channels: 1 -resample: 4 +resample: 2 sampling_rate : 16000 encoder_decoder: depth: 5 - initial_output_channels: 48 + initial_output_channels: 32 kernel_size: 8 stride: 1 growth_factor: 2 From fffdf02b932e9147c790aa73a0acfbf31ba12616 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 15:17:58 +0530 Subject: [PATCH 146/375] valid monitor fix --- cli/train.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cli/train.py b/cli/train.py index 88e513a..a5c83f0 100644 --- a/cli/train.py +++ b/cli/train.py @@ -4,7 +4,7 @@ from hydra.utils import instantiate from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger - +os.environ["HYDRA_FULL_ERROR"] = "1" @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): @@ -20,14 +20,15 @@ def main(config: DictConfig): model = instantiate(config.model,dataset=dataset,lr=parameters.get("lr"), loss=parameters.get("loss"), metric = parameters.get("metric")) + direction = model.valid_monitor checkpoint = ModelCheckpoint( - dirpath="",filename="model",monitor="valid_loss",verbose=False, - mode="min",every_n_epochs=1 + dirpath="",filename="model",monitor="val_loss",verbose=False, + mode=direction,every_n_epochs=1 ) callbacks.append(checkpoint) early_stopping = EarlyStopping( - monitor="valid_loss", - mode="min", + monitor="val_loss", + mode=direction, min_delta=0.0, patience=100, strict=True, From 1f4947103f12f2f9eed97a2b93468cc1e87bcea1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 15:19:06 +0530 Subject: [PATCH 147/375] ensure loss direction --- enhancer/loss.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index 3bc6fa2..ef33161 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -1,5 +1,3 @@ -from modulefinder import Module -from turtle import forward import torch import torch.nn as nn @@ -10,6 +8,7 @@ class mean_squared_error(nn.Module): super().__init__() self.loss_fun = nn.MSELoss(reduction=reduction) + self.higher_better = False def forward(self,prediction:torch.Tensor, target: torch.Tensor): @@ -25,6 +24,7 @@ class mean_absolute_error(nn.Module): super().__init__() self.loss_fun = nn.L1Loss(reduction=reduction) + self.higher_better = False def forward(self, prediction:torch.Tensor, target: torch.Tensor): @@ -45,6 +45,7 @@ class Si_SDR(nn.Module): self.reduction = reduction else: raise TypeError("Invalid reduction, valid options are sum, mean, None") + self.higher_better = False def forward(self,prediction:torch.Tensor, target:torch.Tensor): @@ -76,6 +77,12 @@ class Avergeloss(nn.Module): super().__init__() self.valid_losses = nn.ModuleList() + + direction = [getattr(LOSS_MAP[loss](),"higher_better") for loss in losses] + if len(set(direction)) > 1: + raise ValueError("all cost functions should be of same nature, maximize or minimize!") + + self.higher_better = direction[0] for loss in losses: loss = self.validate_loss(loss) self.valid_losses.append(loss()) From e2f570a8d1504cc8652ab61817b26753298afcb7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 15:19:43 +0530 Subject: [PATCH 148/375] set cost property --- enhancer/models/model.py | 51 ++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 8e607ed..b1bdd86 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,3 +1,7 @@ +try: + from functools import cached_property +except ImportError: + from backports.cached_property import cached_property from importlib import import_module from huggingface_hub import cached_download, hf_hub_url import logging @@ -42,7 +46,34 @@ class Model(pl.LightningModule): if self.logger: self.logger.experiment.log_dict(dict(self.hparams),"hyperparameters.json") - + self.loss = loss + self.metric = metric + + @property + def loss(self): + return self._loss + + @loss.setter + def loss(self,loss): + + if isinstance(loss,str): + losses = [loss] + + self._loss = Avergeloss(losses) + + @property + def metric(self): + return self._metric + + @metric.setter + def metric(self,metric): + + if isinstance(metric,str): + metric = [metric] + + self._metric = Avergeloss(metric) + + @property def dataset(self): return self._dataset @@ -55,16 +86,7 @@ class Model(pl.LightningModule): if stage == "fit": self.dataset.setup(stage) self.dataset.model = self - self.loss = self.setup_loss(self.hparams.loss) - self.metric = self.setup_loss(self.hparams.metric) - - def setup_loss(self,loss): - - if isinstance(loss,str): - losses = [loss] - - return Avergeloss(losses) - + def train_dataloader(self): return self.dataset.train_dataloader() @@ -224,7 +246,12 @@ class Model(pl.LightningModule): Inference.write_output(waveform,audio,model_sampling_rate) else: - return waveform + return waveform + + @property + def valid_monitor(self): + + return "max" if self.loss.higher_better else "min" From 3c180da4447e289ac6a17f01e9006c266fd433a4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 15:20:13 +0530 Subject: [PATCH 149/375] rmv root dir --- enhancer/utils/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/enhancer/utils/config.py b/enhancer/utils/config.py index e9af6a0..1bbc51d 100644 --- a/enhancer/utils/config.py +++ b/enhancer/utils/config.py @@ -2,7 +2,6 @@ from dataclasses import dataclass @dataclass class Files: - root_dir : str train_clean : str train_noisy : str test_clean : str From 04ba785eb3a56f5afa8ed0cf8bc128ee29397e8d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 15:37:04 +0530 Subject: [PATCH 150/375] add documentation --- enhancer/data/dataset.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 5749c36..fc871b8 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -91,7 +91,28 @@ class TaskDataset(pl.LightningDataModule): return DataLoader(ValidDataset(self), batch_size = self.batch_size,num_workers=self.num_workers) class EnhancerDataset(TaskDataset): - """Dataset object for creating clean-noisy speech enhancement datasets""" + """ + Dataset object for creating clean-noisy speech enhancement datasets + paramters: + name : str + name of the dataset + root_dir : str + root directory of the dataset containing clean/noisy folders + files : Files + dataclass containing train_clean, train_noisy, test_clean, test_noisy + folder names (refer cli/train_config/dataset) + duration : float + expected audio duration of single audio sample for training + sampling_rate : int + desired sampling rate + batch_size : int + batch size of each batch + num_workers : int + num workers to be used while training + matching_function : + custom function for dataset processing. + + """ def __init__( self, From 74669990787f7cbbaf70897205decf8f5b324259 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 18:07:11 +0530 Subject: [PATCH 151/375] change to generic names --- enhancer/data/fileprocessor.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index f903375..d38e04f 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -7,7 +7,10 @@ from scipy.io import wavfile class ProcessorFunctions: @staticmethod - def match_vtck(clean_path,noisy_path): + def one_to_one(clean_path,noisy_path): + """ + One clean audio can have only one noisy audio file + """ matching_wavfiles = list() clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] @@ -27,7 +30,10 @@ class ProcessorFunctions: return matching_wavfiles @staticmethod - def match_dns2020(clean_path,noisy_path): + def one_to_many(clean_path,noisy_path): + """ + One clean audio have multiple noisy audio files + """ matching_wavfiles = dict() clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] @@ -68,9 +74,9 @@ class Fileprocessor: ): if name.lower() == "vctk": - return cls(clean_dir,noisy_dir, ProcessorFunctions.match_vtck) + return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_one) elif name.lower() == "dns-2020": - return cls(clean_dir,noisy_dir, ProcessorFunctions.match_dns2020) + return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_many) else: return cls(clean_dir,noisy_dir, matching_function) From 35bd3951fff19d7994a1b4f743247f32ed368d7e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 30 Sep 2022 19:03:06 +0530 Subject: [PATCH 152/375] simplify matching function --- enhancer/data/dataset.py | 7 +++++-- enhancer/data/fileprocessor.py | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index fc871b8..4c485c8 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -109,8 +109,11 @@ class EnhancerDataset(TaskDataset): batch size of each batch num_workers : int num workers to be used while training - matching_function : - custom function for dataset processing. + matching_function : str + maching functions - (one_to_one,one_to_many). Default set to None. + use one_to_one mapping for datasets with one noisy file for each clean file + use one_to_many mapping for multiple noisy files for each clean file + """ diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index d38e04f..eab41a0 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -4,6 +4,8 @@ from re import S import numpy as np from scipy.io import wavfile +MATCHING_FNS = ("one_to_one","one_to_many") + class ProcessorFunctions: @staticmethod @@ -73,12 +75,18 @@ class Fileprocessor: matching_function=None ): - if name.lower() == "vctk": - return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_one) - elif name.lower() == "dns-2020": - return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_many) + if matching_function is None: + if name.lower() == "vctk": + return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_one) + elif name.lower() == "dns-2020": + return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_many) else: - return cls(clean_dir,noisy_dir, matching_function) + if matching_function not in MATCHING_FNS: + raise ValueError(F"Invalid matching function! Avaialble options are {MATCHING_FNS}") + else: + return cls(clean_dir,noisy_dir, getattr(ProcessorFunctions,matching_function)) + + def prepare_matching_dict(self): From 3c9a3ab3f3bf8e07b47713e1b46f97e5374e7d14 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 08:48:14 +0530 Subject: [PATCH 153/375] relative imports --- enhancer/utils/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py index a8ae539..3da7ede 100644 --- a/enhancer/utils/__init__.py +++ b/enhancer/utils/__init__.py @@ -1,2 +1,3 @@ from enhancer.utils.utils import check_files -from enhancer.utils.io import Audio \ No newline at end of file +from enhancer.utils.io import Audio +from enhancer.utils.config import Files \ No newline at end of file From d25be3a59dfdfd782faadaa85b57b2ee2170c895 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 09:02:46 +0530 Subject: [PATCH 154/375] log model artifacts --- cli/train.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/train.py b/cli/train.py index a5c83f0..391df13 100644 --- a/cli/train.py +++ b/cli/train.py @@ -5,13 +5,14 @@ from omegaconf import DictConfig from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger os.environ["HYDRA_FULL_ERROR"] = "1" +JOB_ID = os.environ.get("SLURM_JOBID") @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): callbacks = [] logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, - run_name=config.mlflow.run_name, tags={"JOB_ID":os.environ.get("SLURM_JOBID")}) + run_name=config.mlflow.run_name, tags={"JOB_ID":JOB_ID}) parameters = config.hyperparameters @@ -22,7 +23,7 @@ def main(config: DictConfig): direction = model.valid_monitor checkpoint = ModelCheckpoint( - dirpath="",filename="model",monitor="val_loss",verbose=False, + dirpath="./model",filename=f"model_{JOB_ID}",monitor="val_loss",verbose=False, mode=direction,every_n_epochs=1 ) callbacks.append(checkpoint) @@ -30,7 +31,7 @@ def main(config: DictConfig): monitor="val_loss", mode=direction, min_delta=0.0, - patience=100, + patience=10, strict=True, verbose=False, ) @@ -38,6 +39,7 @@ def main(config: DictConfig): trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) trainer.fit(model) + logger.experiment.log_artifact(logger.run_id,f"./model/model_{JOB_ID}") From 1dcaa7fbe05313487c34fc68c28640e4cc130036 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 09:05:02 +0530 Subject: [PATCH 155/375] change config --- cli/train_config/config.yaml | 2 +- cli/train_config/dataset/Vctk.yaml | 2 +- cli/train_config/hyperparameters/default.yaml | 1 - cli/train_config/trainer/default.yaml | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 6b5d98e..61551bd 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : Demucs + - model : WaveUnet - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml index d1c8646..aed10ac 100644 --- a/cli/train_config/dataset/Vctk.yaml +++ b/cli/train_config/dataset/Vctk.yaml @@ -3,7 +3,7 @@ name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 1.0 sampling_rate: 16000 -batch_size: 8 +batch_size: 64 files: train_clean : clean_trainset_56spk_wav diff --git a/cli/train_config/hyperparameters/default.yaml b/cli/train_config/hyperparameters/default.yaml index 04b099b..a0ac704 100644 --- a/cli/train_config/hyperparameters/default.yaml +++ b/cli/train_config/hyperparameters/default.yaml @@ -1,4 +1,3 @@ loss : mse metric : mae lr : 0.0001 -num_epochs : 100 diff --git a/cli/train_config/trainer/default.yaml b/cli/train_config/trainer/default.yaml index ab4e273..6c693d8 100644 --- a/cli/train_config/trainer/default.yaml +++ b/cli/train_config/trainer/default.yaml @@ -22,8 +22,8 @@ limit_predict_batches: 1.0 limit_test_batches: 1.0 limit_train_batches: 1.0 limit_val_batches: 1.0 -log_every_n_steps: 10 -max_epochs: 100 +log_every_n_steps: 50 +max_epochs: 500 max_steps: null max_time: null min_epochs: 1 From f1604b0f0e568b0d4495db8d01a4557f21a2668b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 10:01:44 +0530 Subject: [PATCH 156/375] demucs model --- cli/train_config/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train_config/config.yaml b/cli/train_config/config.yaml index 61551bd..6b5d98e 100644 --- a/cli/train_config/config.yaml +++ b/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : WaveUnet + - model : Demucs - dataset : Vctk - optimizer : Adam - hyperparameters : default From b1e15c7552ae1551af8d7e26e3bfe596c9a15e7b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 18:12:21 +0530 Subject: [PATCH 157/375] add lr scheduler --- cli/train.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cli/train.py b/cli/train.py index 391df13..d951f33 100644 --- a/cli/train.py +++ b/cli/train.py @@ -1,7 +1,9 @@ import os +from types import MethodType import hydra from hydra.utils import instantiate from omegaconf import DictConfig +from torch.optim.lr_scheduler import ReduceLROnPlateau from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger os.environ["HYDRA_FULL_ERROR"] = "1" @@ -36,6 +38,20 @@ def main(config: DictConfig): verbose=False, ) callbacks.append(early_stopping) + + def configure_optimizer(self): + optimizer = instantiate(config.optimizer,lr=parameters.get("lr"),parameters=self.parameters()) + scheduler = ReduceLROnPlateau( + optimizer=optimizer, + mode=direction, + factor=0.1, + verbose=True, + min_lr=parameters.get("min_lr",1e-6), + patience=3 + ) + return {"optimizer":optimizer, "lr_scheduler":scheduler} + + model.configure_parameters = MethodType(configure_optimizer,model) trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) trainer.fit(model) From c11bea6aa0a5b5df1aaecb0d48a4bbd402f2fa6a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 18:18:32 +0530 Subject: [PATCH 158/375] add to hyperparameters --- cli/train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/train.py b/cli/train.py index d951f33..7d49acc 100644 --- a/cli/train.py +++ b/cli/train.py @@ -33,7 +33,7 @@ def main(config: DictConfig): monitor="val_loss", mode=direction, min_delta=0.0, - patience=10, + patience=parameters.get("EarlyStopping_patience",10), strict=True, verbose=False, ) @@ -44,10 +44,10 @@ def main(config: DictConfig): scheduler = ReduceLROnPlateau( optimizer=optimizer, mode=direction, - factor=0.1, + factor=parameters.get("ReduceLr_factor",0.1), verbose=True, min_lr=parameters.get("min_lr",1e-6), - patience=3 + patience=parameters.get("ReduceLr_patience",3) ) return {"optimizer":optimizer, "lr_scheduler":scheduler} From 6faffdcb1731b9b0f799a68afaf3f87baba44794 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 2 Oct 2022 18:18:56 +0530 Subject: [PATCH 159/375] include more params --- cli/train_config/hyperparameters/default.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cli/train_config/hyperparameters/default.yaml b/cli/train_config/hyperparameters/default.yaml index 04b099b..49a7f80 100644 --- a/cli/train_config/hyperparameters/default.yaml +++ b/cli/train_config/hyperparameters/default.yaml @@ -2,3 +2,8 @@ loss : mse metric : mae lr : 0.0001 num_epochs : 100 +ReduceLr_patience : 5 +ReduceLr_factor : 0.1 +min_lr : 0.000001 +EarlyStopping_factor : 10 + From d31a6d2ebd25e1f7b31aa4aeaf47a849311d926b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 09:57:51 +0530 Subject: [PATCH 160/375] check file before logging --- cli/train.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/train.py b/cli/train.py index 7d49acc..8a4beaf 100644 --- a/cli/train.py +++ b/cli/train.py @@ -25,7 +25,7 @@ def main(config: DictConfig): direction = model.valid_monitor checkpoint = ModelCheckpoint( - dirpath="./model",filename=f"model_{JOB_ID}",monitor="val_loss",verbose=False, + dirpath="./model",filename=f"model_{JOB_ID}",monitor="val_loss",verbose=True, mode=direction,every_n_epochs=1 ) callbacks.append(checkpoint) @@ -55,7 +55,8 @@ def main(config: DictConfig): trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) trainer.fit(model) - logger.experiment.log_artifact(logger.run_id,f"./model/model_{JOB_ID}") + if os.path.exists("./model/"): + logger.experiment.log_artifact(logger.run_id,f"./model/.*") From c8815fa969dc54c6560f4d1b29764448b7cd0e2f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 12:24:15 +0530 Subject: [PATCH 161/375] vctk 28 spkrs --- cli/train_config/dataset/Vctk.yaml | 4 ++-- cli/train_config/hyperparameters/default.yaml | 1 - cli/train_config/trainer/default.yaml | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/train_config/dataset/Vctk.yaml b/cli/train_config/dataset/Vctk.yaml index aed10ac..129d9a8 100644 --- a/cli/train_config/dataset/Vctk.yaml +++ b/cli/train_config/dataset/Vctk.yaml @@ -6,9 +6,9 @@ sampling_rate: 16000 batch_size: 64 files: - train_clean : clean_trainset_56spk_wav + train_clean : clean_trainset_28spk_wav test_clean : clean_testset_wav - train_noisy : noisy_trainset_56spk_wav + train_noisy : noisy_trainset_28spk_wav test_noisy : noisy_testset_wav diff --git a/cli/train_config/hyperparameters/default.yaml b/cli/train_config/hyperparameters/default.yaml index 49a7f80..82ac3c2 100644 --- a/cli/train_config/hyperparameters/default.yaml +++ b/cli/train_config/hyperparameters/default.yaml @@ -1,7 +1,6 @@ loss : mse metric : mae lr : 0.0001 -num_epochs : 100 ReduceLr_patience : 5 ReduceLr_factor : 0.1 min_lr : 0.000001 diff --git a/cli/train_config/trainer/default.yaml b/cli/train_config/trainer/default.yaml index 2aa5083..55101de 100644 --- a/cli/train_config/trainer/default.yaml +++ b/cli/train_config/trainer/default.yaml @@ -22,8 +22,8 @@ limit_predict_batches: 1.0 limit_test_batches: 1.0 limit_train_batches: 1.0 limit_val_batches: 1.0 -log_every_n_steps: 10 -max_epochs: 30 +log_every_n_steps: 1 +max_epochs: 10 max_steps: null max_time: null min_epochs: 1 From 158591176708c817bdaec3a9180fbe4f06564c0a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 16:09:05 +0530 Subject: [PATCH 162/375] ckpt --- cli/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/train.py b/cli/train.py index 8a4beaf..cd19ffc 100644 --- a/cli/train.py +++ b/cli/train.py @@ -56,7 +56,7 @@ def main(config: DictConfig): trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) trainer.fit(model) if os.path.exists("./model/"): - logger.experiment.log_artifact(logger.run_id,f"./model/.*") + logger.experiment.log_artifact(logger.run_id,f"model_{JOB_ID}.ckpt") From 07c525ca150fc3bf81b3d7272689cee355243404 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 20:00:14 +0530 Subject: [PATCH 163/375] fix key error --- enhancer/models/model.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index b1bdd86..5827301 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -191,8 +191,8 @@ class Model(pl.LightningModule): map_location = torch.device(DEFAULT_DEVICE) loaded_checkpoint = pl_load(model_path_pl,map_location) - module_name = loaded_checkpoint["architecture"]["module"] - class_name = loaded_checkpoint["architecture"]["class"] + module_name = loaded_checkpoint["enhancer"]["architecture"]["module"] + class_name = loaded_checkpoint["enhancer"]["architecture"]["class"] module = import_module(module_name) Klass = getattr(module, class_name) @@ -216,11 +216,12 @@ class Model(pl.LightningModule): batch_predictions = [] self.eval().to(self.device) - for batch_id in range(batch.shape[0],batch_size): - batch_data = batch[batch_id:batch_id+batch_size,:,:].to(self.device) - prediction = self(batch_data) - batch_predictions.append(prediction) - + with torch.no_grad(): + for batch_id in range(0,batch.shape[0],batch_size): + batch_data = batch[batch_id:batch_id+batch_size,:,:].to(self.device) + prediction = self(batch_data) + batch_predictions.append(prediction) + return torch.vstack(batch_predictions) def enhance( @@ -232,9 +233,9 @@ class Model(pl.LightningModule): duration:Optional[int]=None, step_size:Optional[int]=None,): - model_sampling_rate = self.model.hprams("sampling_rate") + model_sampling_rate = self.hparams["sampling_rate"] if duration is None: - duration = self.model.hparams("duration") + duration = self.hparams["duration"] waveform = Inference.read_input(audio,sampling_rate,model_sampling_rate) waveform.to(self.device) window_size = round(duration * model_sampling_rate) @@ -246,8 +247,9 @@ class Model(pl.LightningModule): Inference.write_output(waveform,audio,model_sampling_rate) else: - return waveform - + waveform = Inference.prepare_output(waveform, model_sampling_rate, + audio, sampling_rate) + return waveform @property def valid_monitor(self): From 5e5fd9d9b02a3f5485a26502c71041ecbdd9add4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 20:00:35 +0530 Subject: [PATCH 164/375] prepare output type/sr --- enhancer/inference.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 6e9cff7..2c63d54 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -18,6 +18,8 @@ class Inference: if isinstance(audio,(np.ndarray,torch.Tensor)): assert sr is not None, "Invalid sampling rate!" + if len(audio.shape) == 1: + audio = audio.reshape(1,-1) if isinstance(audio,str): audio = Path(audio) @@ -65,6 +67,8 @@ class Inference: window = get_window(window=window,Nx=data.shape[-1]) window = torch.from_numpy(window).to(data.device) data *= window + step_size = window_size//2 if step_size is None else step_size + data = data.permute(1,2,0) data = F.fold(data, @@ -84,7 +88,18 @@ class Inference: raise FileExistsError(f"file {filename} already exists") else: wavfile.write(filename,rate=sr,data=waveform.detach().cpu()) - + + @staticmethod + def prepare_output(waveform:torch.Tensor, model_sampling_rate:int, + audio:Union[str,np.ndarray,torch.Tensor], sampling_rate:Optional[int] + ): + if isinstance(audio,np.ndarray): + waveform = waveform.detach().cpu().numpy() + + if sampling_rate!=None: + waveform = Audio.resample_audio(waveform, sr=model_sampling_rate, target_sr=sampling_rate) + + return waveform From ecd47905dd95256cffa52ffb40967a9f75ee4f1c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 20:00:49 +0530 Subject: [PATCH 165/375] relative imports --- enhancer/models/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/models/__init__.py b/enhancer/models/__init__.py index 0d21337..534a608 100644 --- a/enhancer/models/__init__.py +++ b/enhancer/models/__init__.py @@ -1,2 +1,3 @@ from enhancer.models.demucs import Demucs -from enhancer.models.waveunet import WaveUnet \ No newline at end of file +from enhancer.models.waveunet import WaveUnet +from enhancer.models.model import Model \ No newline at end of file From a880125322c9201c5c3059248f130c17001b7971 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 20:01:19 +0530 Subject: [PATCH 166/375] fix logging path --- cli/train.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/train.py b/cli/train.py index cd19ffc..dee3d2e 100644 --- a/cli/train.py +++ b/cli/train.py @@ -1,3 +1,4 @@ +from genericpath import isfile import os from types import MethodType import hydra @@ -7,7 +8,7 @@ from torch.optim.lr_scheduler import ReduceLROnPlateau from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger os.environ["HYDRA_FULL_ERROR"] = "1" -JOB_ID = os.environ.get("SLURM_JOBID") +JOB_ID = os.environ.get("SLURM_JOBID","0") @hydra.main(config_path="train_config",config_name="config") def main(config: DictConfig): @@ -55,8 +56,10 @@ def main(config: DictConfig): trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) trainer.fit(model) - if os.path.exists("./model/"): - logger.experiment.log_artifact(logger.run_id,f"model_{JOB_ID}.ckpt") + + saved_location = os.path.join(trainer.default_root_dir,"model",f"model_{JOB_ID}.ckpt") + if os.path.isfile(saved_location): + logger.experiment.log_artifact(logger.run_id,saved_location) From 687f67e40c2c5b94ab2da160fca40bad529854f4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 21:21:27 +0530 Subject: [PATCH 167/375] write output fix --- enhancer/inference.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 2c63d54..fd2f57a 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -84,10 +84,15 @@ class Inference: if isinstance(filename,str): filename = Path(filename) + + parent, name = filename.parent, "cleaned_"+filename.name + filename = parent/Path(name) if filename.is_file(): raise FileExistsError(f"file {filename} already exists") else: - wavfile.write(filename,rate=sr,data=waveform.detach().cpu()) + if isinstance(waveform,torch.Tensor): + waveform = waveform.detach().cpu().squeeze().numpy() + wavfile.write(filename,rate=sr,data=waveform) @staticmethod def prepare_output(waveform:torch.Tensor, model_sampling_rate:int, From c609b57309ca841107343b65921308cbcc0ded3a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 3 Oct 2022 21:22:15 +0530 Subject: [PATCH 168/375] fix typo --- enhancer/models/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 5827301..de2edab 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -249,7 +249,7 @@ class Model(pl.LightningModule): else: waveform = Inference.prepare_output(waveform, model_sampling_rate, audio, sampling_rate) - return waveform + return waveform @property def valid_monitor(self): From 7f00707733ffa9115c3556ec194c18fbea026fe6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 11:11:30 +0530 Subject: [PATCH 169/375] add doc/refactor black --- enhancer/inference.py | 150 +++++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 44 deletions(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 6e9cff7..27a9385 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -1,9 +1,7 @@ -from json import load -import wave import numpy as np from scipy.signal import get_window from scipy.io import wavfile -from typing import List, Optional, Union +from typing import Optional, Union import torch import torch.nn.functional as F from pathlib import Path @@ -11,89 +9,153 @@ from librosa import load as load_audio from enhancer.utils import Audio + class Inference: + """ + contains methods used for inference. + """ @staticmethod def read_input(audio, sr, model_sr): + """ + read and verify audio input regardless of the input format. + arguments: + audio : audio input + sr : sampling rate of input audio + model_sr : sampling rate used for model training. + """ - if isinstance(audio,(np.ndarray,torch.Tensor)): + if isinstance(audio, (np.ndarray, torch.Tensor)): assert sr is not None, "Invalid sampling rate!" - if isinstance(audio,str): + if isinstance(audio, str): audio = Path(audio) if not audio.is_file(): raise ValueError(f"Input file {audio} does not exist") else: - audio,sr = load_audio(audio,sr=sr,) + audio, sr = load_audio( + audio, + sr=sr, + ) if len(audio.shape) == 1: - audio = audio.reshape(1,-1) + audio = audio.reshape(1, -1) else: - assert audio.shape[0] == 1, "Enhance inference only supports single waveform" + assert ( + audio.shape[0] == 1 + ), "Enhance inference only supports single waveform" - waveform = Audio.resample_audio(audio,sr=sr,target_sr=model_sr) + waveform = Audio.resample_audio(audio, sr=sr, target_sr=model_sr) waveform = Audio.convert_mono(waveform) - if isinstance(waveform,np.ndarray): + if isinstance(waveform, np.ndarray): waveform = torch.from_numpy(waveform) return waveform @staticmethod - def batchify(waveform: torch.Tensor, window_size:int, step_size:Optional[int]=None): + def batchify( + waveform: torch.Tensor, + window_size: int, + step_size: Optional[int] = None, + ): """ - break input waveform into samples with duration specified. + break input waveform into samples with duration specified.(Overlap-add) + arguments: + waveform : audio waveform + window_size : window size used for splitting waveform into batches + step_size : step_size used for splitting waveform into batches """ - assert waveform.ndim == 2, f"Expcted input waveform with 2 dimensions (channels,samples), got {waveform.ndim}" - _,num_samples = waveform.shape + assert ( + waveform.ndim == 2 + ), f"Expcted input waveform with 2 dimensions (channels,samples), got {waveform.ndim}" + _, num_samples = waveform.shape waveform = waveform.unsqueeze(-1) - step_size = window_size//2 if step_size is None else step_size + step_size = window_size // 2 if step_size is None else step_size if num_samples >= window_size: - waveform_batch = F.unfold(waveform[None,...], kernel_size=(window_size,1), - stride=(step_size,1), padding=(window_size,0)) - waveform_batch = waveform_batch.permute(2,0,1) - - + waveform_batch = F.unfold( + waveform[None, ...], + kernel_size=(window_size, 1), + stride=(step_size, 1), + padding=(window_size, 0), + ) + waveform_batch = waveform_batch.permute(2, 0, 1) + return waveform_batch @staticmethod - def aggreagate(data:torch.Tensor,window_size:int,total_frames:int,step_size:Optional[int]=None, - window="hanning",): + def aggreagate( + data: torch.Tensor, + window_size: int, + total_frames: int, + step_size: Optional[int] = None, + window="hanning", + ): """ - takes input as tensor outputs aggregated waveform + stitch batched waveform into single waveform. (Overlap-add) + arguments: + data: batched waveform + window_size : window_size used to batch waveform + step_size : step_size used to batch waveform + total_frames : total number of frames present in original waveform + window : type of window used for overlap-add mechanism. """ - num_chunks,n_channels,num_frames = data.shape - window = get_window(window=window,Nx=data.shape[-1]) + num_chunks, n_channels, num_frames = data.shape + window = get_window(window=window, Nx=data.shape[-1]) window = torch.from_numpy(window).to(data.device) data *= window - data = data.permute(1,2,0) - data = F.fold(data, - (total_frames,1), - kernel_size=(window_size,1), - stride=(step_size,1), - padding=(window_size,0)).squeeze(-1) + data = data.permute(1, 2, 0) + data = F.fold( + data, + (total_frames, 1), + kernel_size=(window_size, 1), + stride=(step_size, 1), + padding=(window_size, 0), + ).squeeze(-1) - return data.reshape(1,n_channels,-1) + return data.reshape(1, n_channels, -1) @staticmethod - def write_output(waveform:torch.Tensor,filename:Union[str,Path],sr:int): + def write_output( + waveform: torch.Tensor, filename: Union[str, Path], sr: int + ): + """ + write audio output as wav file + arguments: + waveform : audio waveform + filename : name of the wave file. Output will be written as cleaned_filename.wav + sr : sampling rate + """ - if isinstance(filename,str): + if isinstance(filename, str): filename = Path(filename) if filename.is_file(): raise FileExistsError(f"file {filename} already exists") else: - wavfile.write(filename,rate=sr,data=waveform.detach().cpu()) - + wavfile.write(filename, rate=sr, data=waveform.detach().cpu()) + @staticmethod + def prepare_output( + waveform: torch.Tensor, + model_sampling_rate: int, + audio: Union[str, np.ndarray, torch.Tensor], + sampling_rate: Optional[int], + ): + """ + prepare output audio based on input format + arguments: + waveform : predicted audio waveform + model_sampling_rate : sampling rate used to train the model + audio : input audio + sampling_rate : input audio sampling rate + """ + if isinstance(audio, np.ndarray): + waveform = waveform.detach().cpu().numpy() + if sampling_rate is not None: + waveform = Audio.resample_audio( + waveform, sr=model_sampling_rate, target_sr=sampling_rate + ) - - - - - - - - \ No newline at end of file + return waveform From e2c8afdfb9c1048ae9a212198537d51e9d9a477e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 11:11:57 +0530 Subject: [PATCH 170/375] flake8 --- .flake8 | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..ed37421 --- /dev/null +++ b/.flake8 @@ -0,0 +1,8 @@ +[flake8] +ignore = E203, E266, E501, W503 +# line length is intentionally set to 80 here because black uses Bugbear +# See https://github.com/psf/black/blob/master/README.md#line-length for more details +max-line-length = 80 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 +exclude = tools/kaldi_decoder \ No newline at end of file From 1062eb3541bb311499a0fff1e0d61fe98c562cb6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 11:12:05 +0530 Subject: [PATCH 171/375] toml --- pyproject.toml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8f12f30 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[tool.black] +line-length = 80 +target-version = ['py38'] +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.mypy_cache + | \.tox + | \.venv + )/ +) +''' \ No newline at end of file From e8b5e343c7729c7ca52c091045e26101cb525ad4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 11:21:30 +0530 Subject: [PATCH 172/375] black reformat --- enhancer/inference.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 838ef5f..1abd8bb 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -28,7 +28,7 @@ class Inference: if isinstance(audio, (np.ndarray, torch.Tensor)): assert sr is not None, "Invalid sampling rate!" if len(audio.shape) == 1: - audio = audio.reshape(1,-1) + audio = audio.reshape(1, -1) if isinstance(audio, str): audio = Path(audio) @@ -105,8 +105,7 @@ class Inference: window = get_window(window=window, Nx=data.shape[-1]) window = torch.from_numpy(window).to(data.device) data *= window - step_size = window_size//2 if step_size is None else step_size - + step_size = window_size // 2 if step_size is None else step_size data = data.permute(1, 2, 0) data = F.fold( @@ -134,8 +133,8 @@ class Inference: if isinstance(filename, str): filename = Path(filename) - parent, name = filename.parent, "cleaned_"+filename.name - filename = parent/Path(name) + parent, name = filename.parent, "cleaned_" + filename.name + filename = parent / Path(name) if filename.is_file(): raise FileExistsError(f"file {filename} already exists") else: From 756465c2bfdf6eee06a55526765b10f95eba2e9f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 11:26:34 +0530 Subject: [PATCH 173/375] format loss.py --- enhancer/loss.py | 110 +++++++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index ef33161..1e156a0 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -3,62 +3,82 @@ import torch.nn as nn class mean_squared_error(nn.Module): + """ + Mean squared error / L1 loss + """ - def __init__(self,reduction="mean"): + def __init__(self, reduction="mean"): super().__init__() self.loss_fun = nn.MSELoss(reduction=reduction) self.higher_better = False - def forward(self,prediction:torch.Tensor, target: torch.Tensor): + def forward(self, prediction: torch.Tensor, target: torch.Tensor): if prediction.size() != target.size() or target.ndim < 3: - raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) - got {prediction.size()} and {target.size()} instead""") + raise TypeError( + f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""" + ) return self.loss_fun(prediction, target) -class mean_absolute_error(nn.Module): - def __init__(self,reduction="mean"): +class mean_absolute_error(nn.Module): + """ + Mean absolute error / L2 loss + """ + + def __init__(self, reduction="mean"): super().__init__() self.loss_fun = nn.L1Loss(reduction=reduction) self.higher_better = False - def forward(self, prediction:torch.Tensor, target: torch.Tensor): + def forward(self, prediction: torch.Tensor, target: torch.Tensor): if prediction.size() != target.size() or target.ndim < 3: - raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) - got {prediction.size()} and {target.size()} instead""") + raise TypeError( + f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""" + ) return self.loss_fun(prediction, target) -class Si_SDR(nn.Module): - def __init__( - self, - reduction:str="mean" - ): +class Si_SDR(nn.Module): + """ + SI-SDR metric based on SDR – HALF-BAKED OR WELL DONE?(https://arxiv.org/pdf/1811.02508.pdf) + """ + + def __init__(self, reduction: str = "mean"): super().__init__() - if reduction in ["sum","mean",None]: + if reduction in ["sum", "mean", None]: self.reduction = reduction else: - raise TypeError("Invalid reduction, valid options are sum, mean, None") + raise TypeError( + "Invalid reduction, valid options are sum, mean, None" + ) self.higher_better = False - def forward(self,prediction:torch.Tensor, target:torch.Tensor): + def forward(self, prediction: torch.Tensor, target: torch.Tensor): if prediction.size() != target.size() or target.ndim < 3: - raise TypeError(f"""Inputs must be of the same shape (batch_size,channels,samples) - got {prediction.size()} and {target.size()} instead""") - - target_energy = torch.sum(target**2,keepdim=True,dim=-1) - scaling_factor = torch.sum(prediction*target,keepdim=True,dim=-1) / target_energy + raise TypeError( + f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""" + ) + + target_energy = torch.sum(target**2, keepdim=True, dim=-1) + scaling_factor = ( + torch.sum(prediction * target, keepdim=True, dim=-1) / target_energy + ) target_projection = target * scaling_factor noise = prediction - target_projection - ratio = torch.sum(target_projection**2,dim=-1) / torch.sum(noise**2,dim=-1) - si_sdr = 10*torch.log10(ratio).mean(dim=-1) + ratio = torch.sum(target_projection**2, dim=-1) / torch.sum( + noise**2, dim=-1 + ) + si_sdr = 10 * torch.log10(ratio).mean(dim=-1) if self.reduction == "sum": si_sdr = si_sdr.sum() @@ -66,46 +86,52 @@ class Si_SDR(nn.Module): si_sdr = si_sdr.mean() else: pass - + return si_sdr - class Avergeloss(nn.Module): + """ + Combine multiple metics of same nature. + for example, ["mea","mae"] + """ - def __init__(self,losses): + def __init__(self, losses): super().__init__() self.valid_losses = nn.ModuleList() - - direction = [getattr(LOSS_MAP[loss](),"higher_better") for loss in losses] + + direction = [ + getattr(LOSS_MAP[loss](), "higher_better") for loss in losses + ] if len(set(direction)) > 1: - raise ValueError("all cost functions should be of same nature, maximize or minimize!") + raise ValueError( + "all cost functions should be of same nature, maximize or minimize!" + ) self.higher_better = direction[0] for loss in losses: loss = self.validate_loss(loss) self.valid_losses.append(loss()) - - def validate_loss(self,loss:str): + def validate_loss(self, loss: str): if loss not in LOSS_MAP.keys(): - raise ValueError(f"Invalid loss function {loss}, available loss functions are {tuple([loss for loss in LOSS_MAP.keys()])}") + raise ValueError( + f"Invalid loss function {loss}, available loss functions are {tuple([loss for loss in LOSS_MAP.keys()])}" + ) else: return LOSS_MAP[loss] - def forward(self,prediction:torch.Tensor, target:torch.Tensor): + def forward(self, prediction: torch.Tensor, target: torch.Tensor): loss = 0.0 for loss_fun in self.valid_losses: loss += loss_fun(prediction, target) - + return loss - - - -LOSS_MAP = {"mae":mean_absolute_error, - "mse": mean_squared_error, - "SI-SDR":Si_SDR} - +LOSS_MAP = { + "mae": mean_absolute_error, + "mse": mean_squared_error, + "SI-SDR": Si_SDR, +} From 96c6108ec6994998405b51e57dccca8c6cbb3bf5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 11:49:27 +0530 Subject: [PATCH 174/375] document average loss --- enhancer/loss.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enhancer/loss.py b/enhancer/loss.py index 1e156a0..f2f62d3 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -94,6 +94,8 @@ class Avergeloss(nn.Module): """ Combine multiple metics of same nature. for example, ["mea","mae"] + parameters: + losses : loss function names to be combined """ def __init__(self, losses): From 2cf9803ed1bcd840608264d2e8640045bbb691b6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 12:15:10 +0530 Subject: [PATCH 175/375] refactor model.py --- enhancer/models/model.py | 309 ++++++++++++++++++++++++++------------- 1 file changed, 204 insertions(+), 105 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index de2edab..071bbb6 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -27,66 +27,89 @@ CACHE_DIR = "" HF_TORCH_WEIGHTS = "" DEFAULT_DEVICE = "cpu" + class Model(pl.LightningModule): + """ + Base class for all models + parameters: + num_channels: int, default to 1 + number of channels in input audio + sampling_rate : int, default 16khz + audio sampling rate + lr: float, optional + learning rate for model training + dataset: EnhancerDataset, optional + Enhancer dataset used for training/validation + duration: float, optional + duration used for training/inference + loss : string or List of strings, default to "mse" + loss functions to be used. Available ("mse","mae","Si-SDR") + + """ def __init__( self, - num_channels:int=1, - sampling_rate:int=16000, - lr:float=1e-3, - dataset:Optional[EnhancerDataset]=None, - duration:Optional[float]=None, + num_channels: int = 1, + sampling_rate: int = 16000, + lr: float = 1e-3, + dataset: Optional[EnhancerDataset] = None, + duration: Optional[float] = None, loss: Union[str, List] = "mse", - metric:Union[str,List] = "mse" + metric: Union[str, List] = "mse", ): super().__init__() - assert num_channels ==1 , "Enhancer only support for mono channel models" + assert ( + num_channels == 1 + ), "Enhancer only support for mono channel models" self.dataset = dataset - self.save_hyperparameters("num_channels","sampling_rate","lr","loss","metric","duration") + self.save_hyperparameters( + "num_channels", "sampling_rate", "lr", "loss", "metric", "duration" + ) if self.logger: - self.logger.experiment.log_dict(dict(self.hparams),"hyperparameters.json") - + self.logger.experiment.log_dict( + dict(self.hparams), "hyperparameters.json" + ) + self.loss = loss self.metric = metric @property def loss(self): return self._loss - - @loss.setter - def loss(self,loss): - if isinstance(loss,str): - losses = [loss] - + @loss.setter + def loss(self, loss): + + if isinstance(loss, str): + losses = [loss] + self._loss = Avergeloss(losses) @property def metric(self): return self._metric - + @metric.setter - def metric(self,metric): + def metric(self, metric): + + if isinstance(metric, str): + metric = [metric] - if isinstance(metric,str): - metric = [metric] - self._metric = Avergeloss(metric) - @property def dataset(self): return self._dataset @dataset.setter - def dataset(self,dataset): + def dataset(self, dataset): self._dataset = dataset - def setup(self,stage:Optional[str]=None): + def setup(self, stage: Optional[str] = None): if stage == "fit": self.dataset.setup(stage) self.dataset.model = self - + def train_dataloader(self): return self.dataset.train_dataloader() @@ -94,9 +117,9 @@ class Model(pl.LightningModule): return self.dataset.val_dataloader() def configure_optimizers(self): - return Adam(self.parameters(), lr = self.hparams.lr) + return Adam(self.parameters(), lr=self.hparams.lr) - def training_step(self,batch, batch_idx:int): + def training_step(self, batch, batch_idx: int): mixed_waveform = batch["noisy"] target = batch["clean"] @@ -105,13 +128,16 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) if self.logger: - self.logger.experiment.log_metric(run_id=self.logger.run_id, - key="train_loss", value=loss.item(), - step=self.global_step) - self.log("train_loss",loss.item()) - return {"loss":loss} + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key="train_loss", + value=loss.item(), + step=self.global_step, + ) + self.log("train_loss", loss.item()) + return {"loss": loss} - def validation_step(self,batch,batch_idx:int): + def validation_step(self, batch, batch_idx: int): mixed_waveform = batch["noisy"] target = batch["clean"] @@ -119,48 +145,92 @@ class Model(pl.LightningModule): metric_val = self.metric(prediction, target) loss_val = self.loss(prediction, target) - self.log("val_metric",metric_val.item()) - self.log("val_loss",loss_val.item()) + self.log("val_metric", metric_val.item()) + self.log("val_loss", loss_val.item()) if self.logger: - self.logger.experiment.log_metric(run_id=self.logger.run_id, - key="val_loss",value=loss_val.item(), - step=self.global_step) - self.logger.experiment.log_metric(run_id=self.logger.run_id, - key="val_metric",value=metric_val.item(), - step=self.global_step) + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key="val_loss", + value=loss_val.item(), + step=self.global_step, + ) + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key="val_metric", + value=metric_val.item(), + step=self.global_step, + ) - return {"loss":loss_val} + return {"loss": loss_val} def on_save_checkpoint(self, checkpoint): checkpoint["enhancer"] = { - "version": { - "enhancer":__version__, - "pytorch":torch.__version__ + "version": {"enhancer": __version__, "pytorch": torch.__version__}, + "architecture": { + "module": self.__class__.__module__, + "class": self.__class__.__name__, }, - "architecture":{ - "module":self.__class__.__module__, - "class":self.__class__.__name__ - } - } def on_load_checkpoint(self, checkpoint: Dict[str, Any]): pass - @classmethod def from_pretrained( cls, checkpoint: Union[Path, Text], - map_location = None, + map_location=None, hparams_file: Union[Path, Text] = None, strict: bool = True, use_auth_token: Union[Text, None] = None, - cached_dir: Union[Path, Text]=CACHE_DIR, - **kwargs + cached_dir: Union[Path, Text] = CACHE_DIR, + **kwargs, ): + """ + Load Pretrained model + + parameters: + checkpoint : Path or str + Path to checkpoint, or a remote URL, or a model identifier from + the huggingface.co model hub. + map_location: optional + Same role as in torch.load(). + Defaults to `lambda storage, loc: storage`. + hparams_file : Path or str, optional + Path to a .yaml file with hierarchical structure as in this example: + drop_prob: 0.2 + dataloader: + batch_size: 32 + You most likely won’t need this since Lightning will always save the + hyperparameters to the checkpoint. However, if your checkpoint weights + do not have the hyperparameters saved, use this method to pass in a .yaml + file with the hparams you would like to use. These will be converted + into a dict and passed into your Model for use. + strict : bool, optional + Whether to strictly enforce that the keys in checkpoint match + the keys returned by this module’s state dict. Defaults to True. + use_auth_token : str, optional + When loading a private huggingface.co model, set `use_auth_token` + to True or to a string containing your hugginface.co authentication + token that can be obtained by running `huggingface-cli login` + cache_dir: Path or str, optional + Path to model cache directory. Defaults to content of PYANNOTE_CACHE + environment variable, or "~/.cache/torch/pyannote" when unset. + kwargs: optional + Any extra keyword args needed to init the model. + Can also be used to override saved hyperparameter values. + + Returns + ------- + model : Model + Model + + See also + -------- + torch.load + """ checkpoint = str(checkpoint) if hparams_file is not None: @@ -168,104 +238,133 @@ class Model(pl.LightningModule): if os.path.isfile(checkpoint): model_path_pl = checkpoint - elif urlparse(checkpoint).scheme in ("http","https"): + elif urlparse(checkpoint).scheme in ("http", "https"): model_path_pl = checkpoint else: - + if "@" in checkpoint: model_id = checkpoint.split("@")[0] revision_id = checkpoint.split("@")[1] else: model_id = checkpoint revision_id = None - + url = hf_hub_url( - model_id,filename=HF_TORCH_WEIGHTS,revision=revision_id + model_id, filename=HF_TORCH_WEIGHTS, revision=revision_id ) model_path_pl = cached_download( - url=url,library_name="enhancer",library_version=__version__, - cache_dir=cached_dir,use_auth_token=use_auth_token + url=url, + library_name="enhancer", + library_version=__version__, + cache_dir=cached_dir, + use_auth_token=use_auth_token, ) if map_location is None: map_location = torch.device(DEFAULT_DEVICE) - loaded_checkpoint = pl_load(model_path_pl,map_location) + loaded_checkpoint = pl_load(model_path_pl, map_location) module_name = loaded_checkpoint["enhancer"]["architecture"]["module"] - class_name = loaded_checkpoint["enhancer"]["architecture"]["class"] + class_name = loaded_checkpoint["enhancer"]["architecture"]["class"] module = import_module(module_name) Klass = getattr(module, class_name) try: model = Klass.load_from_checkpoint( - checkpoint_path = model_path_pl, - map_location = map_location, - hparams_file = hparams_file, - strict = strict, - **kwargs + checkpoint_path=model_path_pl, + map_location=map_location, + hparams_file=hparams_file, + strict=strict, + **kwargs, ) except Exception as e: print(e) + return model - return model + def infer(self, batch: torch.Tensor, batch_size: int = 32): + """ + perform model inference + parameters: + batch : torch.Tensor + input data + batch_size : int, default 32 + batch size for inference + """ - def infer(self,batch:torch.Tensor,batch_size:int=32): - - assert batch.ndim == 3, f"Expected batch with 3 dimensions (batch,channels,samples) got only {batch.ndim}" + assert ( + batch.ndim == 3 + ), f"Expected batch with 3 dimensions (batch,channels,samples) got only {batch.ndim}" batch_predictions = [] self.eval().to(self.device) with torch.no_grad(): - for batch_id in range(0,batch.shape[0],batch_size): - batch_data = batch[batch_id:batch_id+batch_size,:,:].to(self.device) + for batch_id in range(0, batch.shape[0], batch_size): + batch_data = batch[batch_id : batch_id + batch_size, :, :].to( + self.device + ) prediction = self(batch_data) batch_predictions.append(prediction) - + return torch.vstack(batch_predictions) def enhance( self, - audio:Union[Path,np.ndarray,torch.Tensor], - sampling_rate:Optional[int]=None, - batch_size:int=32, - save_output:bool=False, - duration:Optional[int]=None, - step_size:Optional[int]=None,): + audio: Union[Path, np.ndarray, torch.Tensor], + sampling_rate: Optional[int] = None, + batch_size: int = 32, + save_output: bool = False, + duration: Optional[int] = None, + step_size: Optional[int] = None, + ): + """ + Enhance audio using loaded pretained model. + + parameters: + audio: Path to audio file or numpy array or torch tensor + single input audio + sampling_rate: int, optional incase input is path + sampling rate of input + batch_size: int, default 32 + input audio is split into multiple chunks. Inference is done on batches + of these chunks according to given batch size. + save_output : bool, default False + weather to save output to file + duration : float, optional + chunk duration in seconds, defaults to duration of loaded pretrained model. + step_size: int, optional + step size between consecutive durations, defaults to 50% of duration + """ model_sampling_rate = self.hparams["sampling_rate"] if duration is None: duration = self.hparams["duration"] - waveform = Inference.read_input(audio,sampling_rate,model_sampling_rate) + waveform = Inference.read_input( + audio, sampling_rate, model_sampling_rate + ) waveform.to(self.device) window_size = round(duration * model_sampling_rate) - batched_waveform = Inference.batchify(waveform,window_size,step_size=step_size) - batch_prediction = self.infer(batched_waveform,batch_size=batch_size) - waveform = Inference.aggreagate(batch_prediction,window_size,waveform.shape[-1],step_size,) - - if save_output and isinstance(audio,(str,Path)): - Inference.write_output(waveform,audio,model_sampling_rate) + batched_waveform = Inference.batchify( + waveform, window_size, step_size=step_size + ) + batch_prediction = self.infer(batched_waveform, batch_size=batch_size) + waveform = Inference.aggreagate( + batch_prediction, + window_size, + waveform.shape[-1], + step_size, + ) + + if save_output and isinstance(audio, (str, Path)): + Inference.write_output(waveform, audio, model_sampling_rate) else: - waveform = Inference.prepare_output(waveform, model_sampling_rate, - audio, sampling_rate) + waveform = Inference.prepare_output( + waveform, model_sampling_rate, audio, sampling_rate + ) return waveform + @property def valid_monitor(self): - return "max" if self.loss.higher_better else "min" - - - - - - - - - - - - - - - \ No newline at end of file + return "max" if self.loss.higher_better else "min" From 451058c29dad413053536e6dc589fc7812a5821e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 12:50:26 +0530 Subject: [PATCH 176/375] refactor models --- enhancer/models/demucs.py | 284 +++++++++++++++++++++--------------- enhancer/models/waveunet.py | 191 +++++++++++++++--------- 2 files changed, 283 insertions(+), 192 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 7c9d8ff..76a0bf7 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -9,209 +9,255 @@ from enhancer.data.dataset import EnhancerDataset from enhancer.utils.io import Audio as audio from enhancer.utils.utils import merge_dict + class DemucsLSTM(nn.Module): def __init__( self, - input_size:int, - hidden_size:int, - num_layers:int, - bidirectional:bool=True - + input_size: int, + hidden_size: int, + num_layers: int, + bidirectional: bool = True, ): super().__init__() - self.lstm = nn.LSTM(input_size, hidden_size, num_layers, bidirectional=bidirectional) + self.lstm = nn.LSTM( + input_size, hidden_size, num_layers, bidirectional=bidirectional + ) dim = 2 if bidirectional else 1 - self.linear = nn.Linear(dim*hidden_size,hidden_size) + self.linear = nn.Linear(dim * hidden_size, hidden_size) - def forward(self,x): + def forward(self, x): - output,(h,c) = self.lstm(x) + output, (h, c) = self.lstm(x) output = self.linear(output) - return output,(h,c) + return output, (h, c) class DemucsEncoder(nn.Module): - def __init__( self, - num_channels:int, - hidden_size:int, - kernel_size:int, - stride:int=1, - glu:bool=False, + num_channels: int, + hidden_size: int, + kernel_size: int, + stride: int = 1, + glu: bool = False, ): super().__init__() activation = nn.GLU(1) if glu else nn.ReLU() multi_factor = 2 if glu else 1 self.encoder = nn.Sequential( - nn.Conv1d(num_channels,hidden_size,kernel_size,stride), + nn.Conv1d(num_channels, hidden_size, kernel_size, stride), nn.ReLU(), - nn.Conv1d(hidden_size, hidden_size*multi_factor,kernel_size,1), - activation + nn.Conv1d(hidden_size, hidden_size * multi_factor, kernel_size, 1), + activation, ) - def forward(self,waveform): - + def forward(self, waveform): + return self.encoder(waveform) -class DemucsDecoder(nn.Module): +class DemucsDecoder(nn.Module): def __init__( self, - num_channels:int, - hidden_size:int, - kernel_size:int, - stride:int=1, - glu:bool=False, - layer:int=0 + num_channels: int, + hidden_size: int, + kernel_size: int, + stride: int = 1, + glu: bool = False, + layer: int = 0, ): super().__init__() activation = nn.GLU(1) if glu else nn.ReLU() multi_factor = 2 if glu else 1 self.decoder = nn.Sequential( - nn.Conv1d(hidden_size,hidden_size*multi_factor,kernel_size,1), + nn.Conv1d(hidden_size, hidden_size * multi_factor, kernel_size, 1), activation, - nn.ConvTranspose1d(hidden_size,num_channels,kernel_size,stride) + nn.ConvTranspose1d(hidden_size, num_channels, kernel_size, stride), ) - if layer>0: + if layer > 0: self.decoder.add_module("4", nn.ReLU()) - def forward(self,waveform,): + def forward( + self, + waveform, + ): out = self.decoder(waveform) return out class Demucs(Model): + """ + Demucs model from https://arxiv.org/pdf/1911.13254.pdf + parameters: + encoder_decoder: dict, optional + keyword arguments passsed to encoder decoder block + lstm : dict, optional + keyword arguments passsed to LSTM block + num_channels: int, defaults to 1 + number channels in input audio + sampling_rate: int, defaults to 16KHz + sampling rate of input audio + lr : float, defaults to 1e-3 + learning rate used for training + dataset: EnhancerDataset, optional + EnhancerDataset object containing train/validation data for training + duration : float, optional + chunk duration in seconds + loss : string or List of strings + loss function to be used, available ("mse","mae","SI-SDR") + metric : string or List of strings + metric function to be used, available ("mse","mae","SI-SDR") + + """ ED_DEFAULTS = { - "initial_output_channels":48, - "kernel_size":8, - "stride":1, - "depth":5, - "glu":True, - "growth_factor":2, + "initial_output_channels": 48, + "kernel_size": 8, + "stride": 1, + "depth": 5, + "glu": True, + "growth_factor": 2, } LSTM_DEFAULTS = { - "bidirectional":True, - "num_layers":2, + "bidirectional": True, + "num_layers": 2, } - + def __init__( self, - encoder_decoder:Optional[dict]=None, - lstm:Optional[dict]=None, - num_channels:int=1, - resample:int=4, - sampling_rate = 16000, - lr:float=1e-3, - dataset:Optional[EnhancerDataset]=None, - loss:Union[str, List] = "mse", - metric:Union[str, List] = "mse" - - + encoder_decoder: Optional[dict] = None, + lstm: Optional[dict] = None, + num_channels: int = 1, + resample: int = 4, + sampling_rate=16000, + lr: float = 1e-3, + dataset: Optional[EnhancerDataset] = None, + loss: Union[str, List] = "mse", + metric: Union[str, List] = "mse", ): - duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None + duration = ( + dataset.duration if isinstance(dataset, EnhancerDataset) else None + ) if dataset is not None: - if sampling_rate!=dataset.sampling_rate: - logging.warn(f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}") + if sampling_rate != dataset.sampling_rate: + logging.warn( + f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}" + ) sampling_rate = dataset.sampling_rate - super().__init__(num_channels=num_channels, - sampling_rate=sampling_rate,lr=lr, - dataset=dataset,duration=duration,loss=loss, metric=metric) - - encoder_decoder = merge_dict(self.ED_DEFAULTS,encoder_decoder) - lstm = merge_dict(self.LSTM_DEFAULTS,lstm) - self.save_hyperparameters("encoder_decoder","lstm","resample") + super().__init__( + num_channels=num_channels, + sampling_rate=sampling_rate, + lr=lr, + dataset=dataset, + duration=duration, + loss=loss, + metric=metric, + ) + + encoder_decoder = merge_dict(self.ED_DEFAULTS, encoder_decoder) + lstm = merge_dict(self.LSTM_DEFAULTS, lstm) + self.save_hyperparameters("encoder_decoder", "lstm", "resample") hidden = encoder_decoder["initial_output_channels"] self.encoder = nn.ModuleList() self.decoder = nn.ModuleList() for layer in range(encoder_decoder["depth"]): - encoder_layer = DemucsEncoder(num_channels=num_channels, - hidden_size=hidden, - kernel_size=encoder_decoder["kernel_size"], - stride=encoder_decoder["stride"], - glu=encoder_decoder["glu"], - ) + encoder_layer = DemucsEncoder( + num_channels=num_channels, + hidden_size=hidden, + kernel_size=encoder_decoder["kernel_size"], + stride=encoder_decoder["stride"], + glu=encoder_decoder["glu"], + ) self.encoder.append(encoder_layer) - decoder_layer = DemucsDecoder(num_channels=num_channels, - hidden_size=hidden, - kernel_size=encoder_decoder["kernel_size"], - stride=1, - glu=encoder_decoder["glu"], - layer=layer - ) - self.decoder.insert(0,decoder_layer) + decoder_layer = DemucsDecoder( + num_channels=num_channels, + hidden_size=hidden, + kernel_size=encoder_decoder["kernel_size"], + stride=1, + glu=encoder_decoder["glu"], + layer=layer, + ) + self.decoder.insert(0, decoder_layer) num_channels = hidden hidden = self.ED_DEFAULTS["growth_factor"] * hidden - - self.de_lstm = DemucsLSTM(input_size=num_channels, - hidden_size=num_channels, - num_layers=lstm["num_layers"], - bidirectional=lstm["bidirectional"] - ) - def forward(self,waveform): + self.de_lstm = DemucsLSTM( + input_size=num_channels, + hidden_size=num_channels, + num_layers=lstm["num_layers"], + bidirectional=lstm["bidirectional"], + ) + + def forward(self, waveform): if waveform.dim() == 2: waveform = waveform.unsqueeze(1) - if waveform.size(1)!=1: - raise TypeError(f"Demucs can only process mono channel audio, input has {waveform.size(1)} channels") + if waveform.size(1) != 1: + raise TypeError( + f"Demucs can only process mono channel audio, input has {waveform.size(1)} channels" + ) length = waveform.shape[-1] - x = F.pad(waveform, (0,self.get_padding_length(length) - length)) - if self.hparams.resample>1: - x = audio.resample_audio(audio=x, sr=self.hparams.sampling_rate, - target_sr=int(self.hparams.sampling_rate * self.hparams.resample)) - + x = F.pad(waveform, (0, self.get_padding_length(length) - length)) + if self.hparams.resample > 1: + x = audio.resample_audio( + audio=x, + sr=self.hparams.sampling_rate, + target_sr=int( + self.hparams.sampling_rate * self.hparams.resample + ), + ) + encoder_outputs = [] for encoder in self.encoder: x = encoder(x) encoder_outputs.append(x) - x = x.permute(0,2,1) - x,_ = self.de_lstm(x) + x = x.permute(0, 2, 1) + x, _ = self.de_lstm(x) - x = x.permute(0,2,1) + x = x.permute(0, 2, 1) for decoder in self.decoder: skip_connection = encoder_outputs.pop(-1) - x += skip_connection[..., :x.shape[-1]] + x += skip_connection[..., : x.shape[-1]] x = decoder(x) - + if self.hparams.resample > 1: - x = audio.resample_audio(x,int(self.hparams.sampling_rate * self.hparams.resample), - self.hparams.sampling_rate) + x = audio.resample_audio( + x, + int(self.hparams.sampling_rate * self.hparams.resample), + self.hparams.sampling_rate, + ) return x - - def get_padding_length(self,input_length): + + def get_padding_length(self, input_length): input_length = math.ceil(input_length * self.hparams.resample) - - for layer in range(self.hparams.encoder_decoder["depth"]): # encoder operation - input_length = math.ceil((input_length - self.hparams.encoder_decoder["kernel_size"])/self.hparams.encoder_decoder["stride"])+1 - input_length = max(1,input_length) - for layer in range(self.hparams.encoder_decoder["depth"]): # decoder operaration - input_length = (input_length-1) * self.hparams.encoder_decoder["stride"] + self.hparams.encoder_decoder["kernel_size"] - input_length = math.ceil(input_length/self.hparams.resample) + for layer in range( + self.hparams.encoder_decoder["depth"] + ): # encoder operation + input_length = ( + math.ceil( + (input_length - self.hparams.encoder_decoder["kernel_size"]) + / self.hparams.encoder_decoder["stride"] + ) + + 1 + ) + input_length = max(1, input_length) + for layer in range( + self.hparams.encoder_decoder["depth"] + ): # decoder operaration + input_length = (input_length - 1) * self.hparams.encoder_decoder[ + "stride" + ] + self.hparams.encoder_decoder["kernel_size"] + input_length = math.ceil(input_length / self.hparams.resample) return int(input_length) - - - - - - - - - - - - - \ No newline at end of file diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index f799352..4d5cc0a 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -7,76 +7,117 @@ from typing import Optional, Union, List from enhancer.models.model import Model from enhancer.data.dataset import EnhancerDataset -class WavenetDecoder(nn.Module): +class WavenetDecoder(nn.Module): def __init__( self, - in_channels:int, - out_channels:int, - kernel_size:int=5, - padding:int=2, - stride:int=1, - dilation:int=1, + in_channels: int, + out_channels: int, + kernel_size: int = 5, + padding: int = 2, + stride: int = 1, + dilation: int = 1, ): - super(WavenetDecoder,self).__init__() + super(WavenetDecoder, self).__init__() self.decoder = nn.Sequential( - nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding,dilation=dilation), + nn.Conv1d( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + ), nn.BatchNorm1d(out_channels), - nn.LeakyReLU(negative_slope=0.1) + nn.LeakyReLU(negative_slope=0.1), ) - - def forward(self,waveform): - + + def forward(self, waveform): + return self.decoder(waveform) -class WavenetEncoder(nn.Module): +class WavenetEncoder(nn.Module): def __init__( self, - in_channels:int, - out_channels:int, - kernel_size:int=15, - padding:int=7, - stride:int=1, - dilation:int=1, + in_channels: int, + out_channels: int, + kernel_size: int = 15, + padding: int = 7, + stride: int = 1, + dilation: int = 1, ): - super(WavenetEncoder,self).__init__() + super(WavenetEncoder, self).__init__() self.encoder = nn.Sequential( - nn.Conv1d(in_channels,out_channels,kernel_size,stride=stride,padding=padding,dilation=dilation), + nn.Conv1d( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + ), nn.BatchNorm1d(out_channels), - nn.LeakyReLU(negative_slope=0.1) + nn.LeakyReLU(negative_slope=0.1), ) - - def forward( - self, - waveform - ): + def forward(self, waveform): return self.encoder(waveform) class WaveUnet(Model): + """ + Wave-U-Net model from https://arxiv.org/pdf/1811.11307.pdf + parameters: + num_channels: int, defaults to 1 + number of channels in input audio + depth : int, defaults to 12 + depth of network + initial_output_channels: int, defaults to 24 + number of output channels in intial upsampling layer + sampling_rate: int, defaults to 16KHz + sampling rate of input audio + lr : float, defaults to 1e-3 + learning rate used for training + dataset: EnhancerDataset, optional + EnhancerDataset object containing train/validation data for training + duration : float, optional + chunk duration in seconds + loss : string or List of strings + loss function to be used, available ("mse","mae","SI-SDR") + metric : string or List of strings + metric function to be used, available ("mse","mae","SI-SDR") + """ def __init__( self, - num_channels:int=1, - depth:int=12, - initial_output_channels:int=24, - sampling_rate:int=16000, - lr:float=1e-3, - dataset:Optional[EnhancerDataset]=None, - duration:Optional[float]=None, + num_channels: int = 1, + depth: int = 12, + initial_output_channels: int = 24, + sampling_rate: int = 16000, + lr: float = 1e-3, + dataset: Optional[EnhancerDataset] = None, + duration: Optional[float] = None, loss: Union[str, List] = "mse", - metric:Union[str,List] = "mse" + metric: Union[str, List] = "mse", ): - duration = dataset.duration if isinstance(dataset,EnhancerDataset) else None + duration = ( + dataset.duration if isinstance(dataset, EnhancerDataset) else None + ) if dataset is not None: - if sampling_rate!=dataset.sampling_rate: - logging.warn(f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}") + if sampling_rate != dataset.sampling_rate: + logging.warn( + f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}" + ) sampling_rate = dataset.sampling_rate - super().__init__(num_channels=num_channels, - sampling_rate=sampling_rate,lr=lr, - dataset=dataset,duration=duration,loss=loss, metric=metric + super().__init__( + num_channels=num_channels, + sampling_rate=sampling_rate, + lr=lr, + dataset=dataset, + duration=duration, + loss=loss, + metric=metric, ) self.save_hyperparameters("depth") self.encoders = nn.ModuleList() @@ -84,72 +125,76 @@ class WaveUnet(Model): out_channels = initial_output_channels for layer in range(depth): - encoder = WavenetEncoder(num_channels,out_channels) + encoder = WavenetEncoder(num_channels, out_channels) self.encoders.append(encoder) num_channels = out_channels out_channels += initial_output_channels - if layer == depth -1 : - decoder = WavenetDecoder(depth * initial_output_channels + num_channels,num_channels) + if layer == depth - 1: + decoder = WavenetDecoder( + depth * initial_output_channels + num_channels, num_channels + ) else: - decoder = WavenetDecoder(num_channels+out_channels,num_channels) + decoder = WavenetDecoder( + num_channels + out_channels, num_channels + ) - self.decoders.insert(0,decoder) + self.decoders.insert(0, decoder) bottleneck_dim = depth * initial_output_channels self.bottleneck = nn.Sequential( - nn.Conv1d(bottleneck_dim,bottleneck_dim, 15, stride=1, - padding=7), + nn.Conv1d(bottleneck_dim, bottleneck_dim, 15, stride=1, padding=7), nn.BatchNorm1d(bottleneck_dim), - nn.LeakyReLU(negative_slope=0.1, inplace=True) + nn.LeakyReLU(negative_slope=0.1, inplace=True), ) self.final = nn.Sequential( nn.Conv1d(1 + initial_output_channels, 1, kernel_size=1, stride=1), - nn.Tanh() + nn.Tanh(), ) - - def forward( - self,waveform - ): + def forward(self, waveform): if waveform.dim() == 2: waveform = waveform.unsqueeze(1) - if waveform.size(1)!=1: - raise TypeError(f"Wave-U-Net can only process mono channel audio, input has {waveform.size(1)} channels") + if waveform.size(1) != 1: + raise TypeError( + f"Wave-U-Net can only process mono channel audio, input has {waveform.size(1)} channels" + ) encoder_outputs = [] out = waveform for encoder in self.encoders: out = encoder(out) - encoder_outputs.insert(0,out) - out = out[:,:,::2] - + encoder_outputs.insert(0, out) + out = out[:, :, ::2] + out = self.bottleneck(out) - for layer,decoder in enumerate(self.decoders): + for layer, decoder in enumerate(self.decoders): out = F.interpolate(out, scale_factor=2, mode="linear") - out = self.fix_last_dim(out,encoder_outputs[layer]) - out = torch.cat([out,encoder_outputs[layer]],dim=1) + out = self.fix_last_dim(out, encoder_outputs[layer]) + out = torch.cat([out, encoder_outputs[layer]], dim=1) out = decoder(out) - out = torch.cat([out, waveform],dim=1) + out = torch.cat([out, waveform], dim=1) out = self.final(out) return out - - def fix_last_dim(self,x,target): + + def fix_last_dim(self, x, target): """ - trying to do centre crop along last dimension + centre crop along last dimension """ - assert x.shape[-1] >= target.shape[-1], "input dimension cannot be larger than target dimension" + assert ( + x.shape[-1] >= target.shape[-1] + ), "input dimension cannot be larger than target dimension" if x.shape[-1] == target.shape[-1]: return x - + diff = x.shape[-1] - target.shape[-1] - if diff%2!=0: - x = F.pad(x,(0,1)) + if diff % 2 != 0: + x = F.pad(x, (0, 1)) diff += 1 - crop = diff//2 - return x[:,:,crop:-crop] + crop = diff // 2 + return x[:, :, crop:-crop] From b92310c93d33b5daf495cf3ee34bfbca5b2d1b19 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:05:40 +0530 Subject: [PATCH 177/375] refactor data modules --- enhancer/data/__init__.py | 1 + enhancer/data/dataset.py | 173 ++++++++++++++++++++------------- enhancer/data/fileprocessor.py | 118 ++++++++++++---------- 3 files changed, 169 insertions(+), 123 deletions(-) diff --git a/enhancer/data/__init__.py b/enhancer/data/__init__.py index e69de29..3ec018e 100644 --- a/enhancer/data/__init__.py +++ b/enhancer/data/__init__.py @@ -0,0 +1 @@ +from enhancer.data.dataset import EnhancerDataset \ No newline at end of file diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 4c485c8..d194167 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -12,9 +12,9 @@ from enhancer.utils.io import Audio from enhancer.utils import check_files from enhancer.utils.config import Files + class TrainDataset(IterableDataset): - - def __init__(self,dataset): + def __init__(self, dataset): self.dataset = dataset def __iter__(self): @@ -23,88 +23,102 @@ class TrainDataset(IterableDataset): def __len__(self): return self.dataset.train__len__() + class ValidDataset(Dataset): - - def __init__(self,dataset): + def __init__(self, dataset): self.dataset = dataset - def __getitem__(self,idx): + def __getitem__(self, idx): return self.dataset.val__getitem__(idx) def __len__(self): return self.dataset.val__len__() -class TaskDataset(pl.LightningDataModule): +class TaskDataset(pl.LightningDataModule): def __init__( self, - name:str, - root_dir:str, - files:Files, - duration:float=1.0, - sampling_rate:int=48000, - matching_function = None, + name: str, + root_dir: str, + files: Files, + duration: float = 1.0, + sampling_rate: int = 48000, + matching_function=None, batch_size=32, - num_workers:Optional[int]=None): + num_workers: Optional[int] = None, + ): super().__init__() self.name = name - self.files,self.root_dir = check_files(root_dir,files) + self.files, self.root_dir = check_files(root_dir, files) self.duration = duration self.sampling_rate = sampling_rate self.batch_size = batch_size self.matching_function = matching_function self._validation = [] if num_workers is None: - num_workers = multiprocessing.cpu_count()//2 + num_workers = multiprocessing.cpu_count() // 2 self.num_workers = num_workers def setup(self, stage: Optional[str] = None): - if stage in ("fit",None): + if stage in ("fit", None): - train_clean = os.path.join(self.root_dir,self.files.train_clean) - train_noisy = os.path.join(self.root_dir,self.files.train_noisy) - fp = Fileprocessor.from_name(self.name,train_clean, - train_noisy, self.matching_function) + train_clean = os.path.join(self.root_dir, self.files.train_clean) + train_noisy = os.path.join(self.root_dir, self.files.train_noisy) + fp = Fileprocessor.from_name( + self.name, train_clean, train_noisy, self.matching_function + ) self.train_data = fp.prepare_matching_dict() - - val_clean = os.path.join(self.root_dir,self.files.test_clean) - val_noisy = os.path.join(self.root_dir,self.files.test_noisy) - fp = Fileprocessor.from_name(self.name,val_clean, - val_noisy, self.matching_function) + + val_clean = os.path.join(self.root_dir, self.files.test_clean) + val_noisy = os.path.join(self.root_dir, self.files.test_noisy) + fp = Fileprocessor.from_name( + self.name, val_clean, val_noisy, self.matching_function + ) val_data = fp.prepare_matching_dict() for item in val_data: - clean,noisy,total_dur = item.values() + clean, noisy, total_dur = item.values() if total_dur < self.duration: continue - num_segments = round(total_dur/self.duration) + num_segments = round(total_dur / self.duration) for index in range(num_segments): start_time = index * self.duration - self._validation.append(({"clean":clean,"noisy":noisy}, - start_time)) + self._validation.append( + ({"clean": clean, "noisy": noisy}, start_time) + ) + def train_dataloader(self): - return DataLoader(TrainDataset(self), batch_size = self.batch_size,num_workers=self.num_workers) + return DataLoader( + TrainDataset(self), + batch_size=self.batch_size, + num_workers=self.num_workers, + ) def val_dataloader(self): - return DataLoader(ValidDataset(self), batch_size = self.batch_size,num_workers=self.num_workers) + return DataLoader( + ValidDataset(self), + batch_size=self.batch_size, + num_workers=self.num_workers, + ) + class EnhancerDataset(TaskDataset): """ Dataset object for creating clean-noisy speech enhancement datasets paramters: name : str - name of the dataset + name of the dataset root_dir : str root directory of the dataset containing clean/noisy folders files : Files - dataclass containing train_clean, train_noisy, test_clean, test_noisy - folder names (refer cli/train_config/dataset) + dataclass containing train_clean, train_noisy, test_clean, test_noisy + folder names (refer enhancer.utils.Files dataclass) duration : float expected audio duration of single audio sample for training sampling_rate : int - desired sampling rate + desired sampling rate batch_size : int batch size of each batch num_workers : int @@ -114,71 +128,92 @@ class EnhancerDataset(TaskDataset): use one_to_one mapping for datasets with one noisy file for each clean file use one_to_many mapping for multiple noisy files for each clean file - + """ def __init__( self, - name:str, - root_dir:str, - files:Files, + name: str, + root_dir: str, + files: Files, duration=1.0, sampling_rate=48000, matching_function=None, batch_size=32, - num_workers:Optional[int]=None): - + num_workers: Optional[int] = None, + ): + super().__init__( name=name, root_dir=root_dir, files=files, sampling_rate=sampling_rate, duration=duration, - matching_function = matching_function, + matching_function=matching_function, batch_size=batch_size, - num_workers = num_workers, - + num_workers=num_workers, ) self.sampling_rate = sampling_rate self.files = files - self.duration = max(1.0,duration) - self.audio = Audio(self.sampling_rate,mono=True,return_tensor=True) + self.duration = max(1.0, duration) + self.audio = Audio(self.sampling_rate, mono=True, return_tensor=True) + + def setup(self, stage: Optional[str] = None): - def setup(self, stage:Optional[str]=None): - super().setup(stage=stage) def train__iter__(self): - rng = create_unique_rng(self.model.current_epoch) - + rng = create_unique_rng(self.model.current_epoch) + while True: - file_dict,*_ = rng.choices(self.train_data,k=1, - weights=[file["duration"] for file in self.train_data]) - file_duration = file_dict['duration'] - start_time = round(rng.uniform(0,file_duration- self.duration),2) - data = self.prepare_segment(file_dict,start_time) + file_dict, *_ = rng.choices( + self.train_data, + k=1, + weights=[file["duration"] for file in self.train_data], + ) + file_duration = file_dict["duration"] + start_time = round(rng.uniform(0, file_duration - self.duration), 2) + data = self.prepare_segment(file_dict, start_time) yield data - def val__getitem__(self,idx): + def val__getitem__(self, idx): return self.prepare_segment(*self._validation[idx]) - - def prepare_segment(self,file_dict:dict, start_time:float): - clean_segment = self.audio(file_dict["clean"], - offset=start_time,duration=self.duration) - noisy_segment = self.audio(file_dict["noisy"], - offset=start_time,duration=self.duration) - clean_segment = F.pad(clean_segment,(0,int(self.duration*self.sampling_rate-clean_segment.shape[-1]))) - noisy_segment = F.pad(noisy_segment,(0,int(self.duration*self.sampling_rate-noisy_segment.shape[-1]))) - return {"clean": clean_segment,"noisy":noisy_segment} - + def prepare_segment(self, file_dict: dict, start_time: float): + + clean_segment = self.audio( + file_dict["clean"], offset=start_time, duration=self.duration + ) + noisy_segment = self.audio( + file_dict["noisy"], offset=start_time, duration=self.duration + ) + clean_segment = F.pad( + clean_segment, + ( + 0, + int( + self.duration * self.sampling_rate - clean_segment.shape[-1] + ), + ), + ) + noisy_segment = F.pad( + noisy_segment, + ( + 0, + int( + self.duration * self.sampling_rate - noisy_segment.shape[-1] + ), + ), + ) + return {"clean": clean_segment, "noisy": noisy_segment} + def train__len__(self): - return math.ceil(sum([file["duration"] for file in self.train_data])/self.duration) + return math.ceil( + sum([file["duration"] for file in self.train_data]) / self.duration + ) def val__len__(self): return len(self._validation) - - diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index eab41a0..106f649 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -4,105 +4,115 @@ from re import S import numpy as np from scipy.io import wavfile -MATCHING_FNS = ("one_to_one","one_to_many") +MATCHING_FNS = ("one_to_one", "one_to_many") + class ProcessorFunctions: + """ + Preprocessing methods for different types of speech enhacement datasets. + """ @staticmethod - def one_to_one(clean_path,noisy_path): + def one_to_one(clean_path, noisy_path): """ One clean audio can have only one noisy audio file """ matching_wavfiles = list() - clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] - noisy_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(noisy_path,"*.wav"))] - common_filenames = np.intersect1d(noisy_filenames,clean_filenames) + clean_filenames = [ + file.split("/")[-1] + for file in glob.glob(os.path.join(clean_path, "*.wav")) + ] + noisy_filenames = [ + file.split("/")[-1] + for file in glob.glob(os.path.join(noisy_path, "*.wav")) + ] + common_filenames = np.intersect1d(noisy_filenames, clean_filenames) for file_name in common_filenames: - sr_clean, clean_file = wavfile.read(os.path.join(clean_path,file_name)) - sr_noisy, noisy_file = wavfile.read(os.path.join(noisy_path,file_name)) - if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==sr_noisy)): + sr_clean, clean_file = wavfile.read( + os.path.join(clean_path, file_name) + ) + sr_noisy, noisy_file = wavfile.read( + os.path.join(noisy_path, file_name) + ) + if (clean_file.shape[-1] == noisy_file.shape[-1]) and ( + sr_clean == sr_noisy + ): matching_wavfiles.append( - {"clean":os.path.join(clean_path,file_name),"noisy":os.path.join(noisy_path,file_name), - "duration":clean_file.shape[-1]/sr_clean} - ) + { + "clean": os.path.join(clean_path, file_name), + "noisy": os.path.join(noisy_path, file_name), + "duration": clean_file.shape[-1] / sr_clean, + } + ) return matching_wavfiles @staticmethod - def one_to_many(clean_path,noisy_path): + def one_to_many(clean_path, noisy_path): """ One clean audio have multiple noisy audio files """ - + matching_wavfiles = dict() - clean_filenames = [file.split('/')[-1] for file in glob.glob(os.path.join(clean_path,"*.wav"))] + clean_filenames = [ + file.split("/")[-1] + for file in glob.glob(os.path.join(clean_path, "*.wav")) + ] for clean_file in clean_filenames: - noisy_filenames = glob.glob(os.path.join(noisy_path,f"*_{clean_file}.wav")) + noisy_filenames = glob.glob( + os.path.join(noisy_path, f"*_{clean_file}.wav") + ) for noisy_file in noisy_filenames: - sr_clean, clean_file = wavfile.read(os.path.join(clean_path,clean_file)) + sr_clean, clean_file = wavfile.read( + os.path.join(clean_path, clean_file) + ) sr_noisy, noisy_file = wavfile.read(noisy_file) - if ((clean_file.shape[-1]==noisy_file.shape[-1]) and - (sr_clean==sr_noisy)): + if (clean_file.shape[-1] == noisy_file.shape[-1]) and ( + sr_clean == sr_noisy + ): matching_wavfiles.update( - {"clean":os.path.join(clean_path,clean_file),"noisy":noisy_file, - "duration":clean_file.shape[-1]/sr_clean} - ) + { + "clean": os.path.join(clean_path, clean_file), + "noisy": noisy_file, + "duration": clean_file.shape[-1] / sr_clean, + } + ) return matching_wavfiles class Fileprocessor: - - def __init__( - self, - clean_dir, - noisy_dir, - matching_function = None - ): + def __init__(self, clean_dir, noisy_dir, matching_function=None): self.clean_dir = clean_dir self.noisy_dir = noisy_dir self.matching_function = matching_function @classmethod - def from_name(cls, - name:str, - clean_dir, - noisy_dir, - matching_function=None - ): + def from_name(cls, name: str, clean_dir, noisy_dir, matching_function=None): if matching_function is None: if name.lower() == "vctk": - return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_one) + return cls(clean_dir, noisy_dir, ProcessorFunctions.one_to_one) elif name.lower() == "dns-2020": - return cls(clean_dir,noisy_dir, ProcessorFunctions.one_to_many) + return cls(clean_dir, noisy_dir, ProcessorFunctions.one_to_many) else: if matching_function not in MATCHING_FNS: - raise ValueError(F"Invalid matching function! Avaialble options are {MATCHING_FNS}") + raise ValueError( + f"Invalid matching function! Avaialble options are {MATCHING_FNS}" + ) else: - return cls(clean_dir,noisy_dir, getattr(ProcessorFunctions,matching_function)) - - + return cls( + clean_dir, + noisy_dir, + getattr(ProcessorFunctions, matching_function), + ) def prepare_matching_dict(self): if self.matching_function is None: raise ValueError("Not a valid matching function") - return self.matching_function(self.clean_dir,self.noisy_dir) - - - - - - - - - - - - + return self.matching_function(self.clean_dir, self.noisy_dir) From aca4521ef27725df97ac055297ef8301d7c04d11 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:19:17 +0530 Subject: [PATCH 178/375] refactor Audio --- enhancer/utils/io.py | 121 +++++++++++++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 38 deletions(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index afc19e8..3703285 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -1,41 +1,66 @@ import os import librosa -from typing import Optional +from pathlib import Path +from typing import Optional, Union import numpy as np import torch import torchaudio + class Audio: + """ + Audio utils + parameters: + sampling_rate : int, defaults to 16KHz + audio sampling rate + mono: bool, defaults to True + return_tensors: bool, defaults to True + returns torch tensor type if set to True else numpy ndarray + """ def __init__( - self, - sampling_rate:int=16000, - mono:bool=True, - return_tensor=True + self, sampling_rate: int = 16000, mono: bool = True, return_tensor=True ) -> None: - + self.sampling_rate = sampling_rate self.mono = mono self.return_tensor = return_tensor def __call__( self, - audio, - sampling_rate:Optional[int]=None, - offset:Optional[float] = None, - duration:Optional[float] = None + audio: Union[Path, np.ndarray, torch.Tensor], + sampling_rate: Optional[int] = None, + offset: Optional[float] = None, + duration: Optional[float] = None, ): - if isinstance(audio,str): + """ + read and process input audio + parameters: + audio: Path to audio file or numpy array or torch tensor + single input audio + sampling_rate : int, optional + sampling rate of the audio input + offset: float, optional + offset from which the audio must be read, reads from beginning if unused. + duration: float (seconds), optional + read duration, reads full audio starting from offset if not used + """ + if isinstance(audio, str): if os.path.exists(audio): - audio,sampling_rate = librosa.load(audio,sr=sampling_rate,mono=False, - offset=offset,duration=duration) + audio, sampling_rate = librosa.load( + audio, + sr=sampling_rate, + mono=False, + offset=offset, + duration=duration, + ) if len(audio.shape) == 1: - audio = audio.reshape(1,-1) + audio = audio.reshape(1, -1) else: raise FileNotFoundError(f"File {audio} deos not exist") - elif isinstance(audio,np.ndarray): + elif isinstance(audio, np.ndarray): if len(audio.shape) == 1: - audio = audio.reshape(1,-1) + audio = audio.reshape(1, -1) else: raise ValueError("audio should be either filepath or numpy ndarray") @@ -43,40 +68,60 @@ class Audio: audio = self.convert_mono(audio) if sampling_rate: - audio = self.__class__.resample_audio(audio,self.sampling_rate,sampling_rate) + audio = self.__class__.resample_audio( + audio, self.sampling_rate, sampling_rate + ) if self.return_tensor: return torch.tensor(audio) else: return audio @staticmethod - def convert_mono( - audio + def convert_mono(audio: Union[np.ndarray, torch.Tensor]): + """ + convert input audio into mono (1) + parameters: + audio: np.ndarray or torch.Tensor + """ + if len(audio.shape) > 2: + assert ( + audio.shape[0] == 1 + ), "convert mono only accepts single waveform" + audio = audio.reshape(audio.shape[1], audio.shape[2]) - ): - if len(audio.shape)>2: - assert audio.shape[0] == 1, "convert mono only accepts single waveform" - audio = audio.reshape(audio.shape[1],audio.shape[2]) - - assert audio.shape[1] >> audio.shape[0], f"expected input format (num_channels,num_samples) got {audio.shape}" - num_channels,num_samples = audio.shape - if num_channels>1: - return audio.mean(axis=0).reshape(1,num_samples) + assert ( + audio.shape[1] >> audio.shape[0] + ), f"expected input format (num_channels,num_samples) got {audio.shape}" + num_channels, num_samples = audio.shape + if num_channels > 1: + return audio.mean(axis=0).reshape(1, num_samples) return audio - @staticmethod def resample_audio( - audio, - sr:int, - target_sr:int + audio: Union[np.ndarray, torch.Tensor], sr: int, target_sr: int ): - if sr!=target_sr: - if isinstance(audio,np.ndarray): - audio = librosa.resample(audio,orig_sr=sr,target_sr=target_sr) - elif isinstance(audio,torch.Tensor): - audio = torchaudio.functional.resample(audio,orig_freq=sr,new_freq=target_sr) + """ + resample audio to desired sampling rate + parameters: + audio : Path to audio file or numpy array or torch tensor + audio waveform + sr : int + current sampling rate + target_sr : int + target sampling rate + + """ + if sr != target_sr: + if isinstance(audio, np.ndarray): + audio = librosa.resample(audio, orig_sr=sr, target_sr=target_sr) + elif isinstance(audio, torch.Tensor): + audio = torchaudio.functional.resample( + audio, orig_freq=sr, new_freq=target_sr + ) else: - raise ValueError("Input should be either numpy array or torch tensor") + raise ValueError( + "Input should be either numpy array or torch tensor" + ) return audio From 6c4bced3607f23db76d911f9a3471de22db53fa1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:20:43 +0530 Subject: [PATCH 179/375] reformat utils --- enhancer/utils/__init__.py | 2 +- enhancer/utils/config.py | 11 +++++------ enhancer/utils/random.py | 29 ++++++++++++----------------- enhancer/utils/utils.py | 21 +++++++++++++-------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py index 3da7ede..c9f5438 100644 --- a/enhancer/utils/__init__.py +++ b/enhancer/utils/__init__.py @@ -1,3 +1,3 @@ from enhancer.utils.utils import check_files from enhancer.utils.io import Audio -from enhancer.utils.config import Files \ No newline at end of file +from enhancer.utils.config import Files diff --git a/enhancer/utils/config.py b/enhancer/utils/config.py index 1bbc51d..252e6c9 100644 --- a/enhancer/utils/config.py +++ b/enhancer/utils/config.py @@ -1,10 +1,9 @@ from dataclasses import dataclass + @dataclass class Files: - train_clean : str - train_noisy : str - test_clean : str - test_noisy : str - - + train_clean: str + train_noisy: str + test_clean: str + test_noisy: str diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py index 3b1acac..51e09c0 100644 --- a/enhancer/utils/random.py +++ b/enhancer/utils/random.py @@ -3,17 +3,16 @@ import random import torch - -def create_unique_rng(epoch:int): +def create_unique_rng(epoch: int): """create unique random number generator for each (worker_id,epoch) combination""" rng = random.Random() - global_seed = int(os.environ.get("PL_GLOBAL_SEED","0")) - global_rank = int(os.environ.get('GLOBAL_RANK',"0")) - local_rank = int(os.environ.get('LOCAL_RANK',"0")) - node_rank = int(os.environ.get('NODE_RANK',"0")) - world_size = int(os.environ.get('WORLD_SIZE',"0")) + global_seed = int(os.environ.get("PL_GLOBAL_SEED", "0")) + global_rank = int(os.environ.get("GLOBAL_RANK", "0")) + local_rank = int(os.environ.get("LOCAL_RANK", "0")) + node_rank = int(os.environ.get("NODE_RANK", "0")) + world_size = int(os.environ.get("WORLD_SIZE", "0")) worker_info = torch.utils.data.get_worker_info() if worker_info is not None: @@ -24,17 +23,13 @@ def create_unique_rng(epoch:int): worker_id = 0 seed = ( - global_seed - + worker_id - + local_rank * num_workers - + node_rank * num_workers * global_rank - + epoch * num_workers * world_size - ) + global_seed + + worker_id + + local_rank * num_workers + + node_rank * num_workers * global_rank + + epoch * num_workers * world_size + ) rng.seed(seed) return rng - - - - diff --git a/enhancer/utils/utils.py b/enhancer/utils/utils.py index be74dc2..73673ed 100644 --- a/enhancer/utils/utils.py +++ b/enhancer/utils/utils.py @@ -1,19 +1,24 @@ - import os from typing import Optional from enhancer.utils.config import Files -def check_files(root_dir:str, files:Files): - path_variables = [member_var for member_var in dir(files) if not member_var.startswith('__')] +def check_files(root_dir: str, files: Files): + + path_variables = [ + member_var + for member_var in dir(files) + if not member_var.startswith("__") + ] for variable in path_variables: - path = getattr(files,variable) - if not os.path.isdir(os.path.join(root_dir,path)): + path = getattr(files, variable) + if not os.path.isdir(os.path.join(root_dir, path)): raise ValueError(f"Invalid {path}, is not a directory") - - return files,root_dir -def merge_dict(default_dict:dict, custom:Optional[dict]=None): + return files, root_dir + + +def merge_dict(default_dict: dict, custom: Optional[dict] = None): params = dict(default_dict) if custom: params.update(custom) From bf37570d4a70a5951e47921edcc1af7139616a5e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:27:20 +0530 Subject: [PATCH 180/375] relative imports --- enhancer/__init__.py | 2 +- enhancer/data/__init__.py | 2 +- enhancer/models/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/enhancer/__init__.py b/enhancer/__init__.py index b3c06d4..f102a9c 100644 --- a/enhancer/__init__.py +++ b/enhancer/__init__.py @@ -1 +1 @@ -__version__ = "0.0.1" \ No newline at end of file +__version__ = "0.0.1" diff --git a/enhancer/data/__init__.py b/enhancer/data/__init__.py index 3ec018e..7efd946 100644 --- a/enhancer/data/__init__.py +++ b/enhancer/data/__init__.py @@ -1 +1 @@ -from enhancer.data.dataset import EnhancerDataset \ No newline at end of file +from enhancer.data.dataset import EnhancerDataset diff --git a/enhancer/models/__init__.py b/enhancer/models/__init__.py index 534a608..368a9d7 100644 --- a/enhancer/models/__init__.py +++ b/enhancer/models/__init__.py @@ -1,3 +1,3 @@ from enhancer.models.demucs import Demucs from enhancer.models.waveunet import WaveUnet -from enhancer.models.model import Model \ No newline at end of file +from enhancer.models.model import Model From 670b70938acd5a357c473389a9798d7e8d3db362 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:47:13 +0530 Subject: [PATCH 181/375] ignore __init__.py --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index ed37421..861f69a 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,5 @@ [flake8] +per-file-ignores = __init__.py:F401 ignore = E203, E266, E501, W503 # line length is intentionally set to 80 here because black uses Bugbear # See https://github.com/psf/black/blob/master/README.md#line-length for more details From babe5776ce25eb9d532fa6add28fc4ffce3f6b22 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:47:25 +0530 Subject: [PATCH 182/375] flake8 changes --- enhancer/data/fileprocessor.py | 1 - enhancer/loss.py | 11 ++++++----- enhancer/models/model.py | 9 +-------- enhancer/utils/utils.py | 1 + 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index 106f649..5cc9b31 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -1,6 +1,5 @@ import glob import os -from re import S import numpy as np from scipy.io import wavfile diff --git a/enhancer/loss.py b/enhancer/loss.py index f2f62d3..db1d222 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -17,8 +17,8 @@ class mean_squared_error(nn.Module): if prediction.size() != target.size() or target.ndim < 3: raise TypeError( - f"""Inputs must be of the same shape (batch_size,channels,samples) - got {prediction.size()} and {target.size()} instead""" + f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""" ) return self.loss_fun(prediction, target) @@ -39,7 +39,7 @@ class mean_absolute_error(nn.Module): if prediction.size() != target.size() or target.ndim < 3: raise TypeError( - f"""Inputs must be of the same shape (batch_size,channels,samples) + f"""Inputs must be of the same shape (batch_size,channels,samples) got {prediction.size()} and {target.size()} instead""" ) @@ -65,7 +65,7 @@ class Si_SDR(nn.Module): if prediction.size() != target.size() or target.ndim < 3: raise TypeError( - f"""Inputs must be of the same shape (batch_size,channels,samples) + f"""Inputs must be of the same shape (batch_size,channels,samples) got {prediction.size()} and {target.size()} instead""" ) @@ -119,7 +119,8 @@ class Avergeloss(nn.Module): def validate_loss(self, loss: str): if loss not in LOSS_MAP.keys(): raise ValueError( - f"Invalid loss function {loss}, available loss functions are {tuple([loss for loss in LOSS_MAP.keys()])}" + f"""Invalid loss function {loss}, available loss functions are + {tuple([loss for loss in LOSS_MAP.keys()])}""" ) else: return LOSS_MAP[loss] diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 071bbb6..56f24db 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,16 +1,10 @@ -try: - from functools import cached_property -except ImportError: - from backports.cached_property import cached_property from importlib import import_module from huggingface_hub import cached_download, hf_hub_url -import logging import numpy as np import os from typing import Optional, Union, List, Text, Dict, Any from torch.optim import Adam import torch -from torch.nn.functional import pad import pytorch_lightning as pl from pytorch_lightning.utilities.cloud_io import load as pl_load from urllib.parse import urlparse @@ -19,7 +13,6 @@ from pathlib import Path from enhancer import __version__ from enhancer.data.dataset import EnhancerDataset -from enhancer.utils.io import Audio from enhancer.loss import Avergeloss from enhancer.inference import Inference @@ -300,7 +293,7 @@ class Model(pl.LightningModule): with torch.no_grad(): for batch_id in range(0, batch.shape[0], batch_size): - batch_data = batch[batch_id : batch_id + batch_size, :, :].to( + batch_data = batch[batch_id: batch_id + batch_size, :, :].to( self.device ) prediction = self(batch_data) diff --git a/enhancer/utils/utils.py b/enhancer/utils/utils.py index 73673ed..ebb41b4 100644 --- a/enhancer/utils/utils.py +++ b/enhancer/utils/utils.py @@ -19,6 +19,7 @@ def check_files(root_dir: str, files: Files): def merge_dict(default_dict: dict, custom: Optional[dict] = None): + params = dict(default_dict) if custom: params.update(custom) From 80d6795b61868c1d08a66f3b3f969fadff193483 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:52:50 +0530 Subject: [PATCH 183/375] move cli to enhancer --- {cli => enhancer/cli}/train.py | 0 {cli => enhancer/cli}/train_config/config.yaml | 0 .../cli}/train_config/dataset/DNS-2020.yaml | 0 .../cli}/train_config/dataset/Vctk.yaml | 0 enhancer/cli/train_config/dataset/Vctk_local.yaml | 13 +++++++++++++ .../cli}/train_config/hyperparameters/default.yaml | 0 .../cli}/train_config/mlflow/experiment.yaml | 0 .../cli}/train_config/model/Demucs.yaml | 0 .../cli}/train_config/model/WaveUnet.yaml | 0 .../cli}/train_config/optimizer/Adam.yaml | 0 .../cli}/train_config/trainer/default.yaml | 0 .../cli}/train_config/trainer/fastrun_dev.yaml | 0 12 files changed, 13 insertions(+) rename {cli => enhancer/cli}/train.py (100%) rename {cli => enhancer/cli}/train_config/config.yaml (100%) rename {cli => enhancer/cli}/train_config/dataset/DNS-2020.yaml (100%) rename {cli => enhancer/cli}/train_config/dataset/Vctk.yaml (100%) create mode 100644 enhancer/cli/train_config/dataset/Vctk_local.yaml rename {cli => enhancer/cli}/train_config/hyperparameters/default.yaml (100%) rename {cli => enhancer/cli}/train_config/mlflow/experiment.yaml (100%) rename {cli => enhancer/cli}/train_config/model/Demucs.yaml (100%) rename {cli => enhancer/cli}/train_config/model/WaveUnet.yaml (100%) rename {cli => enhancer/cli}/train_config/optimizer/Adam.yaml (100%) rename {cli => enhancer/cli}/train_config/trainer/default.yaml (100%) rename {cli => enhancer/cli}/train_config/trainer/fastrun_dev.yaml (100%) diff --git a/cli/train.py b/enhancer/cli/train.py similarity index 100% rename from cli/train.py rename to enhancer/cli/train.py diff --git a/cli/train_config/config.yaml b/enhancer/cli/train_config/config.yaml similarity index 100% rename from cli/train_config/config.yaml rename to enhancer/cli/train_config/config.yaml diff --git a/cli/train_config/dataset/DNS-2020.yaml b/enhancer/cli/train_config/dataset/DNS-2020.yaml similarity index 100% rename from cli/train_config/dataset/DNS-2020.yaml rename to enhancer/cli/train_config/dataset/DNS-2020.yaml diff --git a/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml similarity index 100% rename from cli/train_config/dataset/Vctk.yaml rename to enhancer/cli/train_config/dataset/Vctk.yaml diff --git a/enhancer/cli/train_config/dataset/Vctk_local.yaml b/enhancer/cli/train_config/dataset/Vctk_local.yaml new file mode 100644 index 0000000..b792b71 --- /dev/null +++ b/enhancer/cli/train_config/dataset/Vctk_local.yaml @@ -0,0 +1,13 @@ +_target_: enhancer.data.dataset.EnhancerDataset +name : vctk +root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk +duration : 1.0 +sampling_rate: 16000 +batch_size: 64 +num_workers : 0 + +files: + train_clean : clean_testset_wav + test_clean : clean_testset_wav + train_noisy : noisy_testset_wav + test_noisy : noisy_testset_wav \ No newline at end of file diff --git a/cli/train_config/hyperparameters/default.yaml b/enhancer/cli/train_config/hyperparameters/default.yaml similarity index 100% rename from cli/train_config/hyperparameters/default.yaml rename to enhancer/cli/train_config/hyperparameters/default.yaml diff --git a/cli/train_config/mlflow/experiment.yaml b/enhancer/cli/train_config/mlflow/experiment.yaml similarity index 100% rename from cli/train_config/mlflow/experiment.yaml rename to enhancer/cli/train_config/mlflow/experiment.yaml diff --git a/cli/train_config/model/Demucs.yaml b/enhancer/cli/train_config/model/Demucs.yaml similarity index 100% rename from cli/train_config/model/Demucs.yaml rename to enhancer/cli/train_config/model/Demucs.yaml diff --git a/cli/train_config/model/WaveUnet.yaml b/enhancer/cli/train_config/model/WaveUnet.yaml similarity index 100% rename from cli/train_config/model/WaveUnet.yaml rename to enhancer/cli/train_config/model/WaveUnet.yaml diff --git a/cli/train_config/optimizer/Adam.yaml b/enhancer/cli/train_config/optimizer/Adam.yaml similarity index 100% rename from cli/train_config/optimizer/Adam.yaml rename to enhancer/cli/train_config/optimizer/Adam.yaml diff --git a/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml similarity index 100% rename from cli/train_config/trainer/default.yaml rename to enhancer/cli/train_config/trainer/default.yaml diff --git a/cli/train_config/trainer/fastrun_dev.yaml b/enhancer/cli/train_config/trainer/fastrun_dev.yaml similarity index 100% rename from cli/train_config/trainer/fastrun_dev.yaml rename to enhancer/cli/train_config/trainer/fastrun_dev.yaml From 8ac01b846de688d4e060a3c52ed5106e8a8e63bd Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:54:10 +0530 Subject: [PATCH 184/375] black --- enhancer/cli/train.py | 79 ++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index dee3d2e..814fa0f 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -1,4 +1,3 @@ -from genericpath import isfile import os from types import MethodType import hydra @@ -7,61 +6,79 @@ from omegaconf import DictConfig from torch.optim.lr_scheduler import ReduceLROnPlateau from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import MLFlowLogger -os.environ["HYDRA_FULL_ERROR"] = "1" -JOB_ID = os.environ.get("SLURM_JOBID","0") -@hydra.main(config_path="train_config",config_name="config") +os.environ["HYDRA_FULL_ERROR"] = "1" +JOB_ID = os.environ.get("SLURM_JOBID", "0") + + +@hydra.main(config_path="train_config", config_name="config") def main(config: DictConfig): callbacks = [] - logger = MLFlowLogger(experiment_name=config.mlflow.experiment_name, - run_name=config.mlflow.run_name, tags={"JOB_ID":JOB_ID}) - + logger = MLFlowLogger( + experiment_name=config.mlflow.experiment_name, + run_name=config.mlflow.run_name, + tags={"JOB_ID": JOB_ID}, + ) parameters = config.hyperparameters dataset = instantiate(config.dataset) - model = instantiate(config.model,dataset=dataset,lr=parameters.get("lr"), - loss=parameters.get("loss"), metric = parameters.get("metric")) + model = instantiate( + config.model, + dataset=dataset, + lr=parameters.get("lr"), + loss=parameters.get("loss"), + metric=parameters.get("metric"), + ) direction = model.valid_monitor checkpoint = ModelCheckpoint( - dirpath="./model",filename=f"model_{JOB_ID}",monitor="val_loss",verbose=True, - mode=direction,every_n_epochs=1 + dirpath="./model", + filename=f"model_{JOB_ID}", + monitor="val_loss", + verbose=True, + mode=direction, + every_n_epochs=1, ) callbacks.append(checkpoint) early_stopping = EarlyStopping( - monitor="val_loss", - mode=direction, - min_delta=0.0, - patience=parameters.get("EarlyStopping_patience",10), - strict=True, - verbose=False, - ) + monitor="val_loss", + mode=direction, + min_delta=0.0, + patience=parameters.get("EarlyStopping_patience", 10), + strict=True, + verbose=False, + ) callbacks.append(early_stopping) - + def configure_optimizer(self): - optimizer = instantiate(config.optimizer,lr=parameters.get("lr"),parameters=self.parameters()) + optimizer = instantiate( + config.optimizer, + lr=parameters.get("lr"), + parameters=self.parameters(), + ) scheduler = ReduceLROnPlateau( optimizer=optimizer, mode=direction, - factor=parameters.get("ReduceLr_factor",0.1), + factor=parameters.get("ReduceLr_factor", 0.1), verbose=True, - min_lr=parameters.get("min_lr",1e-6), - patience=parameters.get("ReduceLr_patience",3) + min_lr=parameters.get("min_lr", 1e-6), + patience=parameters.get("ReduceLr_patience", 3), ) - return {"optimizer":optimizer, "lr_scheduler":scheduler} + return {"optimizer": optimizer, "lr_scheduler": scheduler} - model.configure_parameters = MethodType(configure_optimizer,model) + model.configure_parameters = MethodType(configure_optimizer, model) - trainer = instantiate(config.trainer,logger=logger,callbacks=callbacks) + trainer = instantiate(config.trainer, logger=logger, callbacks=callbacks) trainer.fit(model) - saved_location = os.path.join(trainer.default_root_dir,"model",f"model_{JOB_ID}.ckpt") + saved_location = os.path.join( + trainer.default_root_dir, "model", f"model_{JOB_ID}.ckpt" + ) if os.path.isfile(saved_location): - logger.experiment.log_artifact(logger.run_id,saved_location) + logger.experiment.log_artifact(logger.run_id, saved_location) - -if __name__=="__main__": - main() \ No newline at end of file +if __name__ == "__main__": + main() From 8a07cb8712a437faeff8bd7475d33e7c445fbb28 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 15:54:54 +0530 Subject: [PATCH 185/375] tests --- tests/loss_function_test.py | 20 +++++++++-------- tests/models/demucs_test.py | 36 ++++++++++++++---------------- tests/models/test_waveunet.py | 36 ++++++++++++++---------------- tests/test_inference.py | 24 +++++++++++--------- tests/utils_test.py | 42 ++++++++++++++++++++--------------- 5 files changed, 83 insertions(+), 75 deletions(-) diff --git a/tests/loss_function_test.py b/tests/loss_function_test.py index fbc982c..a4fdc62 100644 --- a/tests/loss_function_test.py +++ b/tests/loss_function_test.py @@ -6,26 +6,28 @@ from enhancer.loss import mean_absolute_error, mean_squared_error loss_functions = [mean_absolute_error(), mean_squared_error()] + def check_loss_shapes_compatibility(loss_fun): batch_size = 4 - shape = (1,1000) - loss_fun(torch.rand(batch_size,*shape),torch.rand(batch_size,*shape)) + shape = (1, 1000) + loss_fun(torch.rand(batch_size, *shape), torch.rand(batch_size, *shape)) with pytest.raises(TypeError): - loss_fun(torch.rand(4,*shape),torch.rand(6,*shape)) + loss_fun(torch.rand(4, *shape), torch.rand(6, *shape)) -@pytest.mark.parametrize("loss",loss_functions) +@pytest.mark.parametrize("loss", loss_functions) def test_loss_input_shapes(loss): check_loss_shapes_compatibility(loss) -@pytest.mark.parametrize("loss",loss_functions) + +@pytest.mark.parametrize("loss", loss_functions) def test_loss_output_type(loss): batch_size = 4 - prediction, target = torch.rand(batch_size,1,1000),torch.rand(batch_size,1,1000) + prediction, target = torch.rand(batch_size, 1, 1000), torch.rand( + batch_size, 1, 1000 + ) loss_value = loss(prediction, target) - assert isinstance(loss_value.item(),float) - - + assert isinstance(loss_value.item(), float) diff --git a/tests/models/demucs_test.py b/tests/models/demucs_test.py index a59fa04..6660888 100644 --- a/tests/models/demucs_test.py +++ b/tests/models/demucs_test.py @@ -10,37 +10,35 @@ from enhancer.data.dataset import EnhancerDataset @pytest.fixture def vctk_dataset(): root_dir = "tests/data/vctk" - files = Files(train_clean="clean_testset_wav",train_noisy="noisy_testset_wav", - test_clean="clean_testset_wav", test_noisy="noisy_testset_wav") - dataset = EnhancerDataset(name="vctk",root_dir=root_dir,files=files) + files = Files( + train_clean="clean_testset_wav", + train_noisy="noisy_testset_wav", + test_clean="clean_testset_wav", + test_noisy="noisy_testset_wav", + ) + dataset = EnhancerDataset(name="vctk", root_dir=root_dir, files=files) return dataset - -@pytest.mark.parametrize("batch_size,samples",[(1,1000)]) -def test_forward(batch_size,samples): +@pytest.mark.parametrize("batch_size,samples", [(1, 1000)]) +def test_forward(batch_size, samples): model = Demucs() model.eval() - data = torch.rand(batch_size,1,samples,requires_grad=False) + data = torch.rand(batch_size, 1, samples, requires_grad=False) with torch.no_grad(): _ = model(data) - data = torch.rand(batch_size,2,samples,requires_grad=False) + data = torch.rand(batch_size, 2, samples, requires_grad=False) with torch.no_grad(): with pytest.raises(TypeError): _ = model(data) -@pytest.mark.parametrize("dataset,channels,loss", - [(pytest.lazy_fixture("vctk_dataset"),1,["mae","mse"])]) -def test_demucs_init(dataset,channels,loss): +@pytest.mark.parametrize( + "dataset,channels,loss", + [(pytest.lazy_fixture("vctk_dataset"), 1, ["mae", "mse"])], +) +def test_demucs_init(dataset, channels, loss): with torch.no_grad(): - model = Demucs(num_channels=channels,dataset=dataset,loss=loss) - - - - - - - + model = Demucs(num_channels=channels, dataset=dataset, loss=loss) diff --git a/tests/models/test_waveunet.py b/tests/models/test_waveunet.py index 43fd14d..c83966b 100644 --- a/tests/models/test_waveunet.py +++ b/tests/models/test_waveunet.py @@ -10,37 +10,35 @@ from enhancer.data.dataset import EnhancerDataset @pytest.fixture def vctk_dataset(): root_dir = "tests/data/vctk" - files = Files(train_clean="clean_testset_wav",train_noisy="noisy_testset_wav", - test_clean="clean_testset_wav", test_noisy="noisy_testset_wav") - dataset = EnhancerDataset(name="vctk",root_dir=root_dir,files=files) + files = Files( + train_clean="clean_testset_wav", + train_noisy="noisy_testset_wav", + test_clean="clean_testset_wav", + test_noisy="noisy_testset_wav", + ) + dataset = EnhancerDataset(name="vctk", root_dir=root_dir, files=files) return dataset - -@pytest.mark.parametrize("batch_size,samples",[(1,1000)]) -def test_forward(batch_size,samples): +@pytest.mark.parametrize("batch_size,samples", [(1, 1000)]) +def test_forward(batch_size, samples): model = WaveUnet() model.eval() - data = torch.rand(batch_size,1,samples,requires_grad=False) + data = torch.rand(batch_size, 1, samples, requires_grad=False) with torch.no_grad(): _ = model(data) - data = torch.rand(batch_size,2,samples,requires_grad=False) + data = torch.rand(batch_size, 2, samples, requires_grad=False) with torch.no_grad(): with pytest.raises(TypeError): _ = model(data) -@pytest.mark.parametrize("dataset,channels,loss", - [(pytest.lazy_fixture("vctk_dataset"),1,["mae","mse"])]) -def test_demucs_init(dataset,channels,loss): +@pytest.mark.parametrize( + "dataset,channels,loss", + [(pytest.lazy_fixture("vctk_dataset"), 1, ["mae", "mse"])], +) +def test_demucs_init(dataset, channels, loss): with torch.no_grad(): - model = WaveUnet(num_channels=channels,dataset=dataset,loss=loss) - - - - - - - + model = WaveUnet(num_channels=channels, dataset=dataset, loss=loss) diff --git a/tests/test_inference.py b/tests/test_inference.py index 5eb7442..a6e2423 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -4,22 +4,26 @@ import torch from enhancer.inference import Inference -@pytest.mark.parametrize("audio",["tests/data/vctk/clean_testset_wav/p257_166.wav",torch.rand(1,2,48000)]) +@pytest.mark.parametrize( + "audio", + ["tests/data/vctk/clean_testset_wav/p257_166.wav", torch.rand(1, 2, 48000)], +) def test_read_input(audio): - read_audio = Inference.read_input(audio,48000,16000) - assert isinstance(read_audio,torch.Tensor) + read_audio = Inference.read_input(audio, 48000, 16000) + assert isinstance(read_audio, torch.Tensor) assert read_audio.shape[0] == 1 + def test_batchify(): - rand = torch.rand(1,1000) - batched_rand = Inference.batchify(rand, window_size = 100, step_size=100) + rand = torch.rand(1, 1000) + batched_rand = Inference.batchify(rand, window_size=100, step_size=100) assert batched_rand.shape[0] == 12 + def test_aggregate(): - rand = torch.rand(12,1,100) - agg_rand = Inference.aggreagate(data=rand,window_size=100,total_frames=1000,step_size=100) + rand = torch.rand(12, 1, 100) + agg_rand = Inference.aggreagate( + data=rand, window_size=100, total_frames=1000, step_size=100 + ) assert agg_rand.shape[-1] == 1000 - - - \ No newline at end of file diff --git a/tests/utils_test.py b/tests/utils_test.py index 413bfac..93a9094 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -7,40 +7,46 @@ from enhancer.utils.io import Audio from enhancer.utils.config import Files from enhancer.data.fileprocessor import Fileprocessor + def test_io_channel(): - input_audio = np.random.rand(2,32000) - audio = Audio(mono=True,return_tensor=False) + input_audio = np.random.rand(2, 32000) + audio = Audio(mono=True, return_tensor=False) output_audio = audio(input_audio) assert output_audio.shape[0] == 1 + def test_io_resampling(): - input_audio = np.random.rand(1,32000) - resampled_audio = Audio.resample_audio(input_audio,16000,8000) + input_audio = np.random.rand(1, 32000) + resampled_audio = Audio.resample_audio(input_audio, 16000, 8000) - input_audio = torch.rand(1,32000) - resampled_audio_pt = Audio.resample_audio(input_audio,16000,8000) + input_audio = torch.rand(1, 32000) + resampled_audio_pt = Audio.resample_audio(input_audio, 16000, 8000) assert resampled_audio.shape[1] == resampled_audio_pt.size(1) == 16000 + def test_fileprocessor_vctk(): - fp = Fileprocessor.from_name("vctk","tests/data/vctk/clean_testset_wav", - "tests/data/vctk/noisy_testset_wav",48000) + fp = Fileprocessor.from_name( + "vctk", + "tests/data/vctk/clean_testset_wav", + "tests/data/vctk/noisy_testset_wav", + 48000, + ) matching_dict = fp.prepare_matching_dict() - assert len(matching_dict)==2 + assert len(matching_dict) == 2 -@pytest.mark.parametrize("dataset_name",["vctk","dns-2020"]) + +@pytest.mark.parametrize("dataset_name", ["vctk", "dns-2020"]) def test_fileprocessor_names(dataset_name): - fp = Fileprocessor.from_name(dataset_name,"clean_dir","noisy_dir",16000) - assert hasattr(fp.matching_function, '__call__') + fp = Fileprocessor.from_name(dataset_name, "clean_dir", "noisy_dir", 16000) + assert hasattr(fp.matching_function, "__call__") + def test_fileprocessor_invaliname(): with pytest.raises(ValueError): - fp = Fileprocessor.from_name("undefined","clean_dir","noisy_dir",16000).prepare_matching_dict() - - - - - + fp = Fileprocessor.from_name( + "undefined", "clean_dir", "noisy_dir", 16000 + ).prepare_matching_dict() From 24f4c25a1b5cbc9fa92b0f532449cb4ff28613bb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 17:00:41 +0530 Subject: [PATCH 186/375] requirements --- requirements.txt | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index e7fcd24..8373578 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,14 @@ -joblib==1.1.0 -numpy==1.19.5 -librosa==0.9.1 -numpy==1.19.5 +joblib==1.2.0 +librosa==0.9.2 +numpy==1.23.3 hydra-core==1.2.0 -scikit-learn==0.24.2 -scipy==1.5.4 -torch==1.10.2 -tqdm==4.64.0 -mlflow==1.23.1 -protobuf==3.19.3 -boto3==1.23.9 -torchaudio==0.10.2 -huggingface-hub==0.4.0 -pytorch-lightning==1.5.10 +scikit-learn==1.1.2 +scipy==1.9.1 +torch==1.12.1 +tqdm==4.64.1 +mlflow==1.29.0 +protobuf==3.19.6 +boto3==1.24.86 +torchaudio==0.12.1 +huggingface-hu==0.10.0 +pytorch-lightning==1.7.7 \ No newline at end of file From 83fe8a29c008dad0ef2295e83996b286a7c2aa4b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 17:16:26 +0530 Subject: [PATCH 187/375] add flake --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8373578..b2f3638 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,6 @@ protobuf==3.19.6 boto3==1.24.86 torchaudio==0.12.1 huggingface-hu==0.10.0 -pytorch-lightning==1.7.7 \ No newline at end of file +pytorch-lightning==1.7.7 +flake8==5.0.4 +black==22.8.0 \ No newline at end of file From b53f9d5f9ef31010f909f58df9c76f8a421d6ce7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 17:40:49 +0530 Subject: [PATCH 188/375] pre-commit --- .pre-commit-config.yaml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5721482 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,39 @@ + +repos: + # # Clean Notebooks + # - repo: https://github.com/kynan/nbstripout + # rev: master + # hooks: + # - id: nbstripout + # Format Code + - repo: https://github.com/ambv/black + rev: 22.3.0 + hooks: + - id: black + + # Sort imports + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + args: ["--profile", "black"] + + # Formatting, Whitespace, etc + - repo: git://github.com/pre-commit/pre-commit-hooks + rev: v2.2.3 + hooks: + - id: trailing-whitespace + - id: check-added-large-files + args: ['--maxkb=1000'] + - id: check-ast + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: mixed-line-ending + args: ['--fix=no'] + - id: flake8 + args: ['--ignore=E203,E501,F811,E712,W503'] From 9adb915447d7467fb79cef9273268a5cd7bd3a05 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 17:47:40 +0530 Subject: [PATCH 189/375] pre-commit conf --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5721482..8eac0a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: # - id: nbstripout # Format Code - repo: https://github.com/ambv/black - rev: 22.3.0 + rev: 22.8.0 hooks: - id: black @@ -20,7 +20,7 @@ repos: # Formatting, Whitespace, etc - repo: git://github.com/pre-commit/pre-commit-hooks - rev: v2.2.3 + rev: v2.20.0 hooks: - id: trailing-whitespace - id: check-added-large-files From d20b7a166fdfcaee6ab0fcbd6c58c5d66401f8a4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:35:17 +0530 Subject: [PATCH 190/375] tests --- tests/loss_function_test.py | 3 ++- tests/models/demucs_test.py | 6 +++--- tests/models/test_waveunet.py | 6 +++--- tests/utils_test.py | 7 ++++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/loss_function_test.py b/tests/loss_function_test.py index a4fdc62..cd60177 100644 --- a/tests/loss_function_test.py +++ b/tests/loss_function_test.py @@ -1,6 +1,7 @@ from asyncio import base_tasks -import torch + import pytest +import torch from enhancer.loss import mean_absolute_error, mean_squared_error diff --git a/tests/models/demucs_test.py b/tests/models/demucs_test.py index 6660888..1ea50c5 100644 --- a/tests/models/demucs_test.py +++ b/tests/models/demucs_test.py @@ -1,10 +1,10 @@ import pytest import torch -from enhancer import data -from enhancer.utils.config import Files -from enhancer.models import Demucs +from enhancer import data from enhancer.data.dataset import EnhancerDataset +from enhancer.models import Demucs +from enhancer.utils.config import Files @pytest.fixture diff --git a/tests/models/test_waveunet.py b/tests/models/test_waveunet.py index c83966b..798ed5d 100644 --- a/tests/models/test_waveunet.py +++ b/tests/models/test_waveunet.py @@ -1,10 +1,10 @@ import pytest import torch -from enhancer import data -from enhancer.utils.config import Files -from enhancer.models import WaveUnet +from enhancer import data from enhancer.data.dataset import EnhancerDataset +from enhancer.models import WaveUnet +from enhancer.utils.config import Files @pytest.fixture diff --git a/tests/utils_test.py b/tests/utils_test.py index 93a9094..1cc171a 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,11 +1,12 @@ from logging import root + +import numpy as np import pytest import torch -import numpy as np -from enhancer.utils.io import Audio -from enhancer.utils.config import Files from enhancer.data.fileprocessor import Fileprocessor +from enhancer.utils.config import Files +from enhancer.utils.io import Audio def test_io_channel(): From e5d9eb7e95737565066ddc3ee3ff7c73c6e36d88 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:35:33 +0530 Subject: [PATCH 191/375] models --- enhancer/models/__init__.py | 2 +- enhancer/models/demucs.py | 9 +++++---- enhancer/models/model.py | 22 +++++++++++----------- enhancer/models/waveunet.py | 5 +++-- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/enhancer/models/__init__.py b/enhancer/models/__init__.py index 368a9d7..2d97568 100644 --- a/enhancer/models/__init__.py +++ b/enhancer/models/__init__.py @@ -1,3 +1,3 @@ from enhancer.models.demucs import Demucs -from enhancer.models.waveunet import WaveUnet from enhancer.models.model import Model +from enhancer.models.waveunet import WaveUnet diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 76a0bf7..65f119d 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -1,11 +1,12 @@ import logging -from typing import Optional, Union, List -from torch import nn -import torch.nn.functional as F import math +from typing import List, Optional, Union + +import torch.nn.functional as F +from torch import nn -from enhancer.models.model import Model from enhancer.data.dataset import EnhancerDataset +from enhancer.models.model import Model from enhancer.utils.io import Audio as audio from enhancer.utils.utils import merge_dict diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 56f24db..39dbe80 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,20 +1,20 @@ -from importlib import import_module -from huggingface_hub import cached_download, hf_hub_url -import numpy as np import os -from typing import Optional, Union, List, Text, Dict, Any -from torch.optim import Adam -import torch -import pytorch_lightning as pl -from pytorch_lightning.utilities.cloud_io import load as pl_load -from urllib.parse import urlparse +from importlib import import_module from pathlib import Path +from typing import Any, Dict, List, Optional, Text, Union +from urllib.parse import urlparse +import numpy as np +import pytorch_lightning as pl +import torch +from huggingface_hub import cached_download, hf_hub_url +from pytorch_lightning.utilities.cloud_io import load as pl_load +from torch.optim import Adam from enhancer import __version__ from enhancer.data.dataset import EnhancerDataset -from enhancer.loss import Avergeloss from enhancer.inference import Inference +from enhancer.loss import Avergeloss CACHE_DIR = "" HF_TORCH_WEIGHTS = "" @@ -293,7 +293,7 @@ class Model(pl.LightningModule): with torch.no_grad(): for batch_id in range(0, batch.shape[0], batch_size): - batch_data = batch[batch_id: batch_id + batch_size, :, :].to( + batch_data = batch[batch_id : batch_id + batch_size, :, :].to( self.device ) prediction = self(batch_data) diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index 4d5cc0a..ebb4b1f 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -1,11 +1,12 @@ import logging +from typing import List, Optional, Union + import torch import torch.nn as nn import torch.nn.functional as F -from typing import Optional, Union, List -from enhancer.models.model import Model from enhancer.data.dataset import EnhancerDataset +from enhancer.models.model import Model class WavenetDecoder(nn.Module): From 64d61e25a422d0a81028e9a397cd4559c3154a89 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:35:53 +0530 Subject: [PATCH 192/375] cli --- enhancer/cli/train.py | 5 +++-- enhancer/cli/train_config/config.yaml | 2 +- enhancer/cli/train_config/dataset/DNS-2020.yaml | 1 - enhancer/cli/train_config/dataset/Vctk.yaml | 3 --- enhancer/cli/train_config/dataset/Vctk_local.yaml | 2 +- enhancer/cli/train_config/hyperparameters/default.yaml | 1 - enhancer/cli/train_config/mlflow/experiment.yaml | 2 +- enhancer/cli/train_config/model/Demucs.yaml | 2 -- 8 files changed, 6 insertions(+), 12 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 814fa0f..cb3c7c1 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -1,11 +1,12 @@ import os from types import MethodType + import hydra from hydra.utils import instantiate from omegaconf import DictConfig -from torch.optim.lr_scheduler import ReduceLROnPlateau -from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping +from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint from pytorch_lightning.loggers import MLFlowLogger +from torch.optim.lr_scheduler import ReduceLROnPlateau os.environ["HYDRA_FULL_ERROR"] = "1" JOB_ID = os.environ.get("SLURM_JOBID", "0") diff --git a/enhancer/cli/train_config/config.yaml b/enhancer/cli/train_config/config.yaml index 61551bd..c0b2cf6 100644 --- a/enhancer/cli/train_config/config.yaml +++ b/enhancer/cli/train_config/config.yaml @@ -4,4 +4,4 @@ defaults: - optimizer : Adam - hyperparameters : default - trainer : default - - mlflow : experiment \ No newline at end of file + - mlflow : experiment diff --git a/enhancer/cli/train_config/dataset/DNS-2020.yaml b/enhancer/cli/train_config/dataset/DNS-2020.yaml index f59cb2b..3bd0e67 100644 --- a/enhancer/cli/train_config/dataset/DNS-2020.yaml +++ b/enhancer/cli/train_config/dataset/DNS-2020.yaml @@ -10,4 +10,3 @@ files: test_clean : clean_test_wav train_noisy : clean_test_wav test_noisy : clean_test_wav - diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 129d9a8..5c19320 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -10,6 +10,3 @@ files: test_clean : clean_testset_wav train_noisy : noisy_trainset_28spk_wav test_noisy : noisy_testset_wav - - - diff --git a/enhancer/cli/train_config/dataset/Vctk_local.yaml b/enhancer/cli/train_config/dataset/Vctk_local.yaml index b792b71..ba44597 100644 --- a/enhancer/cli/train_config/dataset/Vctk_local.yaml +++ b/enhancer/cli/train_config/dataset/Vctk_local.yaml @@ -10,4 +10,4 @@ files: train_clean : clean_testset_wav test_clean : clean_testset_wav train_noisy : noisy_testset_wav - test_noisy : noisy_testset_wav \ No newline at end of file + test_noisy : noisy_testset_wav diff --git a/enhancer/cli/train_config/hyperparameters/default.yaml b/enhancer/cli/train_config/hyperparameters/default.yaml index 82ac3c2..7e4cda3 100644 --- a/enhancer/cli/train_config/hyperparameters/default.yaml +++ b/enhancer/cli/train_config/hyperparameters/default.yaml @@ -5,4 +5,3 @@ ReduceLr_patience : 5 ReduceLr_factor : 0.1 min_lr : 0.000001 EarlyStopping_factor : 10 - diff --git a/enhancer/cli/train_config/mlflow/experiment.yaml b/enhancer/cli/train_config/mlflow/experiment.yaml index 2995c60..e8893f6 100644 --- a/enhancer/cli/train_config/mlflow/experiment.yaml +++ b/enhancer/cli/train_config/mlflow/experiment.yaml @@ -1,2 +1,2 @@ experiment_name : shahules/enhancer -run_name : baseline \ No newline at end of file +run_name : baseline diff --git a/enhancer/cli/train_config/model/Demucs.yaml b/enhancer/cli/train_config/model/Demucs.yaml index 1006e71..d91b5ff 100644 --- a/enhancer/cli/train_config/model/Demucs.yaml +++ b/enhancer/cli/train_config/model/Demucs.yaml @@ -14,5 +14,3 @@ encoder_decoder: lstm: bidirectional: False num_layers: 2 - - From 761e6492bbcccb1e8ec5586a913abbf9bf68a348 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:36:12 +0530 Subject: [PATCH 193/375] utils --- enhancer/utils/__init__.py | 4 ++-- enhancer/utils/io.py | 3 ++- enhancer/utils/random.py | 1 + enhancer/utils/utils.py | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/enhancer/utils/__init__.py b/enhancer/utils/__init__.py index c9f5438..de0db9f 100644 --- a/enhancer/utils/__init__.py +++ b/enhancer/utils/__init__.py @@ -1,3 +1,3 @@ -from enhancer.utils.utils import check_files -from enhancer.utils.io import Audio from enhancer.utils.config import Files +from enhancer.utils.io import Audio +from enhancer.utils.utils import check_files diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 3703285..9e9ce32 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -1,7 +1,8 @@ import os -import librosa from pathlib import Path from typing import Optional, Union + +import librosa import numpy as np import torch import torchaudio diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py index 51e09c0..dd9395a 100644 --- a/enhancer/utils/random.py +++ b/enhancer/utils/random.py @@ -1,5 +1,6 @@ import os import random + import torch diff --git a/enhancer/utils/utils.py b/enhancer/utils/utils.py index ebb41b4..ad45139 100644 --- a/enhancer/utils/utils.py +++ b/enhancer/utils/utils.py @@ -1,5 +1,6 @@ import os from typing import Optional + from enhancer.utils.config import Files From 459c927f0b2ca57d5778b5e80cee0cf42ff7cf55 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:36:24 +0530 Subject: [PATCH 194/375] inference --- enhancer/inference.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index 1abd8bb..ae399f1 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -1,11 +1,12 @@ -import numpy as np -from scipy.signal import get_window -from scipy.io import wavfile +from pathlib import Path from typing import Optional, Union + +import numpy as np import torch import torch.nn.functional as F -from pathlib import Path from librosa import load as load_audio +from scipy.io import wavfile +from scipy.signal import get_window from enhancer.utils import Audio From d0f8ad2f37bacaac294729bfa619a24a07456d8e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:36:50 +0530 Subject: [PATCH 195/375] dataset --- enhancer/data/dataset.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index d194167..95c73a1 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,16 +1,17 @@ -import multiprocessing import math +import multiprocessing import os -import pytorch_lightning as pl -from torch.utils.data import IterableDataset, DataLoader, Dataset -import torch.nn.functional as F from typing import Optional +import pytorch_lightning as pl +import torch.nn.functional as F +from torch.utils.data import DataLoader, Dataset, IterableDataset + from enhancer.data.fileprocessor import Fileprocessor -from enhancer.utils.random import create_unique_rng -from enhancer.utils.io import Audio from enhancer.utils import check_files from enhancer.utils.config import Files +from enhancer.utils.io import Audio +from enhancer.utils.random import create_unique_rng class TrainDataset(IterableDataset): From 09e800392e557e49c2a482a280bddba23cfdb6e7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:37:30 +0530 Subject: [PATCH 196/375] fileprocessor --- enhancer/data/fileprocessor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index 5cc9b31..66d4d75 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -1,5 +1,6 @@ import glob import os + import numpy as np from scipy.io import wavfile From ec76466b00045bc2d86fd8c434a6dc10a08791f9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:37:39 +0530 Subject: [PATCH 197/375] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e462afa..743a823 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# enhancer \ No newline at end of file +# enhancer From ab0805e1ac4ad77a320fcb9cd58c397fd06e1035 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:42:09 +0530 Subject: [PATCH 198/375] flake8 tests --- tests/loss_function_test.py | 2 -- tests/models/demucs_test.py | 3 +-- tests/models/test_waveunet.py | 3 +-- tests/utils_test.py | 5 +---- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/loss_function_test.py b/tests/loss_function_test.py index cd60177..4d14871 100644 --- a/tests/loss_function_test.py +++ b/tests/loss_function_test.py @@ -1,5 +1,3 @@ -from asyncio import base_tasks - import pytest import torch diff --git a/tests/models/demucs_test.py b/tests/models/demucs_test.py index 1ea50c5..f5a0ec4 100644 --- a/tests/models/demucs_test.py +++ b/tests/models/demucs_test.py @@ -1,7 +1,6 @@ import pytest import torch -from enhancer import data from enhancer.data.dataset import EnhancerDataset from enhancer.models import Demucs from enhancer.utils.config import Files @@ -41,4 +40,4 @@ def test_forward(batch_size, samples): ) def test_demucs_init(dataset, channels, loss): with torch.no_grad(): - model = Demucs(num_channels=channels, dataset=dataset, loss=loss) + _ = Demucs(num_channels=channels, dataset=dataset, loss=loss) diff --git a/tests/models/test_waveunet.py b/tests/models/test_waveunet.py index 798ed5d..9c4dd96 100644 --- a/tests/models/test_waveunet.py +++ b/tests/models/test_waveunet.py @@ -1,7 +1,6 @@ import pytest import torch -from enhancer import data from enhancer.data.dataset import EnhancerDataset from enhancer.models import WaveUnet from enhancer.utils.config import Files @@ -41,4 +40,4 @@ def test_forward(batch_size, samples): ) def test_demucs_init(dataset, channels, loss): with torch.no_grad(): - model = WaveUnet(num_channels=channels, dataset=dataset, loss=loss) + _ = WaveUnet(num_channels=channels, dataset=dataset, loss=loss) diff --git a/tests/utils_test.py b/tests/utils_test.py index 1cc171a..65c723d 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,11 +1,8 @@ -from logging import root - import numpy as np import pytest import torch from enhancer.data.fileprocessor import Fileprocessor -from enhancer.utils.config import Files from enhancer.utils.io import Audio @@ -48,6 +45,6 @@ def test_fileprocessor_names(dataset_name): def test_fileprocessor_invaliname(): with pytest.raises(ValueError): - fp = Fileprocessor.from_name( + _ = Fileprocessor.from_name( "undefined", "clean_dir", "noisy_dir", 16000 ).prepare_matching_dict() From c0623db75d5961339de06b0992f49171bf372d87 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:42:18 +0530 Subject: [PATCH 199/375] setup --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index adad46c..43adc89 100644 --- a/setup.sh +++ b/setup.sh @@ -10,4 +10,4 @@ conda env create -f environment.yml || conda env update -f environment.yml source activate enhancer echo "copying files" -# cp /scratch/$USER/TIMIT/.* /deep-transcriber \ No newline at end of file +# cp /scratch/$USER/TIMIT/.* /deep-transcriber From 71603d78dbf8d0a0e2588e13974598cc0f81a13c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:42:26 +0530 Subject: [PATCH 200/375] requirements --- requirements.txt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index b2f3638..afa3641 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -joblib==1.2.0 -librosa==0.9.2 -numpy==1.23.3 -hydra-core==1.2.0 -scikit-learn==1.1.2 -scipy==1.9.1 -torch==1.12.1 -tqdm==4.64.1 -mlflow==1.29.0 -protobuf==3.19.6 -boto3==1.24.86 -torchaudio==0.12.1 -huggingface-hu==0.10.0 -pytorch-lightning==1.7.7 -flake8==5.0.4 -black==22.8.0 \ No newline at end of file +black>=22.8.0 +boto3>=1.24.86 +flake8>=5.0.4 +huggingface-hu>=0.10.0 +hydra-core>=1.2.0 +joblib>=1.2.0 +librosa>=0.9.2 +mlflow>=1.29.0 +numpy>=1.23.3 +protobuf>=3.19.6 +pytorch-lightning>=1.7.7 +scikit-learn>=1.1.2 +scipy>=1.9.1 +torch>=1.12.1 +torchaudio>=0.12.1 +tqdm>=4.64.1 From e127987e3a24c66ef104f3c8d445efa88ef40c23 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:42:37 +0530 Subject: [PATCH 201/375] pre commit hooks --- .pre-commit-config.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8eac0a2..807429c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,9 +18,15 @@ repos: - id: isort args: ["--profile", "black"] + - repo: https://gitlab.com/pycqa/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + args: ['--ignore=E203,E501,F811,E712,W503'] + # Formatting, Whitespace, etc - - repo: git://github.com/pre-commit/pre-commit-hooks - rev: v2.20.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 hooks: - id: trailing-whitespace - id: check-added-large-files @@ -35,5 +41,3 @@ repos: - id: requirements-txt-fixer - id: mixed-line-ending args: ['--fix=no'] - - id: flake8 - args: ['--ignore=E203,E501,F811,E712,W503'] From d4f1087c4565fd593dbfc6b9d20b31ffe0e52b02 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:42:51 +0530 Subject: [PATCH 202/375] toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8f12f30..b3e5d7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,4 +12,4 @@ exclude = ''' | \.venv )/ ) -''' \ No newline at end of file +''' From 61741c528fce6081cf1ac7f794661d8ee279da39 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:43:04 +0530 Subject: [PATCH 203/375] flake8 --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 861f69a..abbbc73 100644 --- a/.flake8 +++ b/.flake8 @@ -6,4 +6,4 @@ ignore = E203, E266, E501, W503 max-line-length = 80 max-complexity = 18 select = B,C,E,F,W,T4,B9 -exclude = tools/kaldi_decoder \ No newline at end of file +exclude = tools/kaldi_decoder From 95a998d824c0cd476f7603622a3af43409743367 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:43:25 +0530 Subject: [PATCH 204/375] environment --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 4f211bf..8da22e1 100644 --- a/environment.yml +++ b/environment.yml @@ -5,4 +5,4 @@ dependencies: - python=3.8 - pip: - -r requirements.txt - - --find-links https://download.pytorch.org/whl/cu113/torch_stable.html \ No newline at end of file + - --find-links https://download.pytorch.org/whl/cu113/torch_stable.html From b82ba4e1bba9b92acb9903941b4290dc94763721 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 20:43:33 +0530 Subject: [PATCH 205/375] hawk --- hpc_entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh index 7372eb9..6d6a3a0 100644 --- a/hpc_entrypoint.sh +++ b/hpc_entrypoint.sh @@ -33,7 +33,7 @@ mkdir temp pwd #python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TRAIN --output ./data/train -#python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test +#python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test echo "Start Training..." python cli/train.py From d8a4d664a0d7e77dca2a2f3e4f0238f9b21ae666 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 5 Oct 2022 21:01:46 +0530 Subject: [PATCH 206/375] update readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 743a823..97e0c7e 100644 --- a/README.md +++ b/README.md @@ -1 +1,6 @@ # enhancer +Enhancer is a Pytorch-based opensource toolkit for speech enhancement. It is designed to save time for audio researchers. Is provides easy to use pretrained audio enhancement models and facilitates highly customisable custom model training . Enhancer provides + +* Various pretrained models nicely integrated with huggingface that users can select and use without any hastle. +* Ability to train and validation your own custom speech enhancement models with just under 10 lines of code! +* A command line tool that facilitates training of highly customisable speech enhacement models from the terminal itself! \ No newline at end of file From 1c81d629c42dacc1233492af9e267108e09e1bf0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 10:17:32 +0530 Subject: [PATCH 207/375] num steps --- enhancer/cli/train.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index cb3c7c1..5ad61b1 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -32,7 +32,7 @@ def main(config: DictConfig): loss=parameters.get("loss"), metric=parameters.get("metric"), ) - + direction = model.valid_monitor checkpoint = ModelCheckpoint( dirpath="./model", @@ -79,6 +79,11 @@ def main(config: DictConfig): ) if os.path.isfile(saved_location): logger.experiment.log_artifact(logger.run_id, saved_location) + logger.experiment.log_param(logger.run_id, "num_train_steps_per_epoch", + dataset.train__len__() / dataset.batch_size) + logger.experiment.log_param(logger.run_id, "num_valid_steps_per_epoch", + dataset.val__len__() / dataset.batch_size) + if __name__ == "__main__": From b071bb171d1caf5e986d25d07292230e4dc51748 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 10:18:31 +0530 Subject: [PATCH 208/375] log num steps --- enhancer/cli/train.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 5ad61b1..a9c66e0 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -32,7 +32,7 @@ def main(config: DictConfig): loss=parameters.get("loss"), metric=parameters.get("metric"), ) - + direction = model.valid_monitor checkpoint = ModelCheckpoint( dirpath="./model", @@ -79,11 +79,16 @@ def main(config: DictConfig): ) if os.path.isfile(saved_location): logger.experiment.log_artifact(logger.run_id, saved_location) - logger.experiment.log_param(logger.run_id, "num_train_steps_per_epoch", - dataset.train__len__() / dataset.batch_size) - logger.experiment.log_param(logger.run_id, "num_valid_steps_per_epoch", - dataset.val__len__() / dataset.batch_size) - + logger.experiment.log_param( + logger.run_id, + "num_train_steps_per_epoch", + dataset.train__len__() / dataset.batch_size, + ) + logger.experiment.log_param( + logger.run_id, + "num_valid_steps_per_epoch", + dataset.val__len__() / dataset.batch_size, + ) if __name__ == "__main__": From 3083e1ad7afe3ce277d9791d4631ca4039d39011 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 10:18:58 +0530 Subject: [PATCH 209/375] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97e0c7e..8ade768 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ Enhancer is a Pytorch-based opensource toolkit for speech enhancement. It is des * Various pretrained models nicely integrated with huggingface that users can select and use without any hastle. * Ability to train and validation your own custom speech enhancement models with just under 10 lines of code! -* A command line tool that facilitates training of highly customisable speech enhacement models from the terminal itself! \ No newline at end of file +* A command line tool that facilitates training of highly customisable speech enhacement models from the terminal itself! From fc7a7996bf8fcaa6d553010f2ecbba2cf6c843a7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 10:33:02 +0530 Subject: [PATCH 210/375] fix typo --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index afa3641..a16acec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ black>=22.8.0 boto3>=1.24.86 flake8>=5.0.4 -huggingface-hu>=0.10.0 +huggingface-hub>=0.10.0 hydra-core>=1.2.0 joblib>=1.2.0 librosa>=0.9.2 From 86e56f2b50307a4a5a4e1d953f8e688df949b3fb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:29:35 +0530 Subject: [PATCH 211/375] fix window type --- enhancer/data/fileprocessor.py | 4 ++++ enhancer/inference.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index 66d4d75..03afc73 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -98,6 +98,10 @@ class Fileprocessor: return cls(clean_dir, noisy_dir, ProcessorFunctions.one_to_one) elif name.lower() == "dns-2020": return cls(clean_dir, noisy_dir, ProcessorFunctions.one_to_many) + else: + raise ValueError( + f"Invalid matching function, Please use valid matching function from {MATCHING_FNS}" + ) else: if matching_function not in MATCHING_FNS: raise ValueError( diff --git a/enhancer/inference.py b/enhancer/inference.py index ae399f1..fd3b518 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -91,7 +91,7 @@ class Inference: window_size: int, total_frames: int, step_size: Optional[int] = None, - window="hanning", + window="hamming", ): """ stitch batched waveform into single waveform. (Overlap-add) From 8c9511967c9d29f6abb6ef13c8b7a1b17f332bb0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:29:52 +0530 Subject: [PATCH 212/375] warning fix --- enhancer/models/demucs.py | 2 +- enhancer/models/waveunet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 65f119d..bf9d429 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -143,7 +143,7 @@ class Demucs(Model): ) if dataset is not None: if sampling_rate != dataset.sampling_rate: - logging.warn( + logging.warning( f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}" ) sampling_rate = dataset.sampling_rate diff --git a/enhancer/models/waveunet.py b/enhancer/models/waveunet.py index ebb4b1f..ea5646a 100644 --- a/enhancer/models/waveunet.py +++ b/enhancer/models/waveunet.py @@ -107,7 +107,7 @@ class WaveUnet(Model): ) if dataset is not None: if sampling_rate != dataset.sampling_rate: - logging.warn( + logging.warning( f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}" ) sampling_rate = dataset.sampling_rate From 124ee8318064932b702fbb23bd3c3246a5c5d31f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:30:00 +0530 Subject: [PATCH 213/375] fix setter --- enhancer/models/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 39dbe80..6e6b4e1 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -74,9 +74,9 @@ class Model(pl.LightningModule): def loss(self, loss): if isinstance(loss, str): - losses = [loss] + loss = [loss] - self._loss = Avergeloss(losses) + self._loss = Avergeloss(loss) @property def metric(self): From 75a2f264cd8b6c68a99506af6d98760b8dc9b9df Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:30:11 +0530 Subject: [PATCH 214/375] requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index a16acec..3ba763a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,8 @@ librosa>=0.9.2 mlflow>=1.29.0 numpy>=1.23.3 protobuf>=3.19.6 +pytest>=7.1.3 +pytest-lazy-fixture>=0.6.3 pytorch-lightning>=1.7.7 scikit-learn>=1.1.2 scipy>=1.9.1 From 8da721308fefbf193a15fd6012df591293efc1ea Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:30:21 +0530 Subject: [PATCH 215/375] fix tests utils --- tests/utils_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utils_test.py b/tests/utils_test.py index 65c723d..cd5240c 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -31,7 +31,6 @@ def test_fileprocessor_vctk(): "vctk", "tests/data/vctk/clean_testset_wav", "tests/data/vctk/noisy_testset_wav", - 48000, ) matching_dict = fp.prepare_matching_dict() assert len(matching_dict) == 2 @@ -39,7 +38,7 @@ def test_fileprocessor_vctk(): @pytest.mark.parametrize("dataset_name", ["vctk", "dns-2020"]) def test_fileprocessor_names(dataset_name): - fp = Fileprocessor.from_name(dataset_name, "clean_dir", "noisy_dir", 16000) + fp = Fileprocessor.from_name(dataset_name, "clean_dir", "noisy_dir") assert hasattr(fp.matching_function, "__call__") From 06ccbb86342cf4f3a8c445aec2ced1cbdbbb2438 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:31:43 +0530 Subject: [PATCH 216/375] ci/cd --- .github/workflows/ci.yaml | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..f07c39e --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,49 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Enhancer + +on: + push: + branches: [ dev ] + pull_request: + branches: [ dev ] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.8] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1.1.1 + env : + ACTIONS_ALLOW_UNSECURE_COMMANDS : true + with: + python-version: ${{ matrix.python-version }} + - name: Cache pip + uses: actions/cache@v1 + with: + path: ~/.cache/pip # This path is specific to Ubuntu + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + # You can test your matrix by printing the current Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install black flake8 pytest + - name: Run black + run: + black --check . + - name: Run flake8 + run: flake8 + - name: Test with pytest + run: | + pytest tests \ No newline at end of file From 9b3359d3ec943360d9d06fcdaf20b8d3b922587d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:32:55 +0530 Subject: [PATCH 217/375] fix yml --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f07c39e..25c74d5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,5 +45,5 @@ jobs: - name: Run flake8 run: flake8 - name: Test with pytest - run: | + run: pytest tests \ No newline at end of file From eb7d09f375c0433a4ce9fcd13882b137e2e43553 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:40:28 +0530 Subject: [PATCH 218/375] add coverage --- .github/workflows/ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 25c74d5..6b3c716 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v1.1.1 - env : + env : ACTIONS_ALLOW_UNSECURE_COMMANDS : true with: python-version: ${{ matrix.python-version }} @@ -38,12 +38,12 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install black flake8 pytest + pip install black flake8 pytest-cov - name: Run black run: black --check . - name: Run flake8 run: flake8 - name: Test with pytest - run: - pytest tests \ No newline at end of file + run: + pytest tests --cov=enhancer/ From 972ddfdc034ce2518ecd62240593219c2f7beda7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 11:49:12 +0530 Subject: [PATCH 219/375] requirements --- requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3ba763a..bb13983 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ -black>=22.8.0 boto3>=1.24.86 -flake8>=5.0.4 huggingface-hub>=0.10.0 hydra-core>=1.2.0 joblib>=1.2.0 @@ -8,8 +6,6 @@ librosa>=0.9.2 mlflow>=1.29.0 numpy>=1.23.3 protobuf>=3.19.6 -pytest>=7.1.3 -pytest-lazy-fixture>=0.6.3 pytorch-lightning>=1.7.7 scikit-learn>=1.1.2 scipy>=1.9.1 From 4fcecdd22f7701e6273b3bddf98bbdff97c1e0a5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 16:09:55 +0530 Subject: [PATCH 220/375] add models dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6eb0fe3..fb3f3de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ #local +models/ cli/train_config/dataset/Vctk_local.yaml .DS_Store outputs/ From 2683940ff4287fa158fb73d249e69c9e2715d6da Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 16:11:50 +0530 Subject: [PATCH 221/375] control mlflow logging --- enhancer/models/model.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 6e6b4e1..cbbfad8 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -120,7 +120,11 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) - if self.logger: + if ( + (self.logger) + and (self.global_step > 50) + and (self.global_step % 50 == 0) + ): self.logger.experiment.log_metric( run_id=self.logger.run_id, key="train_loss", @@ -141,7 +145,11 @@ class Model(pl.LightningModule): self.log("val_metric", metric_val.item()) self.log("val_loss", loss_val.item()) - if self.logger: + if ( + (self.logger) + and (self.global_step > 50) + and (self.global_step % 50 == 0) + ): self.logger.experiment.log_metric( run_id=self.logger.run_id, key="val_loss", From 8be9fe614f28dfbaf9fe5e04545ddc712db15365 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 16:12:05 +0530 Subject: [PATCH 222/375] ignore ckpt files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fb3f3de..9cd222c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ #local -models/ +*.ckpt cli/train_config/dataset/Vctk_local.yaml .DS_Store outputs/ From 0d72b3d81807c79f18589aa0eebe68916dda71cb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:24:39 +0530 Subject: [PATCH 223/375] setup files --- enhancer/version.py | 1 + setup.cfg | 100 ++++++++++++++++++++++++++++++++++++++++++++ setup.py | 63 ++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 enhancer/version.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/enhancer/version.py b/enhancer/version.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/enhancer/version.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8916eaa --- /dev/null +++ b/setup.cfg @@ -0,0 +1,100 @@ +# This file is used to configure your project. +# Read more about the various options under: +# http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files + +[metadata] +name = enhancer +description = Deep learning for speech enhacement +author = Shahul Ess +author-email = shahules786@gmail.com +license = mit +long-description = file: README.md +long-description-content-type = text/markdown; charset=UTF-8; variant=GFM +# Change if running only on Windows, Mac or Linux (comma-separated) +platforms = Linux, Mac +# Add here all kinds of additional classifiers as defined under +# https://pypi.python.org/pypi?%3Aaction=list_classifiers +classifiers = + Development Status :: 4 - Beta + Programming Language :: Python + +[options] +zip_safe = False +packages = find: +include_package_data = True +# DON'T CHANGE THE FOLLOWING LINE! IT WILL BE UPDATED BY PYSCAFFOLD! +setup_requires = setuptools +# Add here dependencies of your project (semicolon/line-separated), e.g. +# install_requires = numpy; scipy +# Require a specific Python version, e.g. Python 2.7 or >= 3.4 +python_requires = >=3.8 + +[options.packages.find] +where = . +exclude = + tests + +[options.extras_require] +# Add here additional requirements for extra features, to install with: +# `pip install fastaudio[PDF]` like: +# PDF = ReportLab; RXP +# Add here test requirements (semicolon/line-separated) +testing = + pytest>=7.1.3 + pytest-cov>=4.0.0 +dev = + pre-commit>=2.20.0 + black>=22.8.0 + flake8>=5.0.4 +cli = + hydra-core >=1.1,<=1.2 + + +[options.entry_points] + +console_scripts = + enhancer-train=enhancer.cli.train:train + +[test] +# py.test options when running `python setup.py test` +# addopts = --verbose +extras = True + +[tool:pytest] +# Options for py.test: +# Specify command line options as you would do when invoking py.test directly. +# e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml +# in order to write a coverage file that can be read by Jenkins. +addopts = + --cov pyannote --cov-report term-missing + --verbose +norecursedirs = + dist + build + .tox +testpaths = tests + +[aliases] +dists = bdist_wheel + +[bdist_wheel] +# Use this option if your package is pure-python +universal = 1 + +[build_sphinx] +source_dir = doc +build_dir = build/sphinx + +[devpi:upload] +# Options for the devpi: PyPI server and packaging tool +# VCS export must be deactivated since we are using setuptools-scm +no-vcs = 1 +formats = bdist_wheel + +[flake8] +# Some sane defaults for the code style checker flake8 +exclude = + .tox + build + dist + .eggs diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..79282b3 --- /dev/null +++ b/setup.py @@ -0,0 +1,63 @@ +import os +import sys +from pathlib import Path + +from pkg_resources import VersionConflict, require +from setuptools import find_packages, setup + +with open("README.md") as f: + long_description = f.read() + +with open("requirements.txt") as f: + requirements = f.read().splitlines() + +try: + require("setuptools>=38.3") +except VersionConflict: + print("Error: version of setuptools is too old (<38.3)!") + sys.exit(1) + + +ROOT_DIR = Path(__file__).parent.resolve() +# Creating the version file + +with open("version.txt") as f: + version = f.read() + +version = version.strip() +sha = "Unknown" + +if os.getenv("BUILD_VERSION"): + version = os.getenv("BUILD_VERSION") +elif sha != "Unknown": + version += "+" + sha[:7] +print("-- Building version " + version) + +version_path = ROOT_DIR / "enhancer" / "version.py" + +with open(version_path, "w") as f: + f.write("__version__ = '{}'\n".format(version)) + +if __name__ == "__main__": + setup( + name="enhancer", + namespace_packages=["enhancer"], + version=version, + packages=find_packages(), + install_requires=requirements, + description="Deep learning toolkit for speech enhancement", + long_description=long_description, + long_description_content_type="text/markdown", + author="Shahul Es", + author_email="shahules786@gmail.com", + url="", + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Scientific/Engineering", + ], + ) From 283a07f197767dc64f1f54cb5585eb6d21f1e2b2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:24:55 +0530 Subject: [PATCH 224/375] version --- version.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 version.txt diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.0.1 From cb20c08095f024aa24d29fd5d148dad692fb89b4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:25:32 +0530 Subject: [PATCH 225/375] namespace --- enhancer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/__init__.py b/enhancer/__init__.py index f102a9c..5284146 100644 --- a/enhancer/__init__.py +++ b/enhancer/__init__.py @@ -1 +1 @@ -__version__ = "0.0.1" +__import__("pkg_resources").declare_namespace(__name__) From 078b9274fadab84d3c76f96639e9d606d499c2ea Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:26:00 +0530 Subject: [PATCH 226/375] install enhancer dev --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6b3c716..d206744 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,6 +39,9 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install black flake8 pytest-cov + - name: Install enhancer + run: | + pip install -e .[dev,testing] - name: Run black run: black --check . From 465f5a9262d1e6a1a8bfc2d15eff6167ce103e40 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:40:08 +0530 Subject: [PATCH 227/375] rmv flake8 --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d206744..745b749 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,9 +44,9 @@ jobs: pip install -e .[dev,testing] - name: Run black run: - black --check . + black --check . --exclude version.py - name: Run flake8 - run: flake8 + run: flake8 --max-line-length 120 --exclude __init__.py --ignore errors E203 - name: Test with pytest run: pytest tests --cov=enhancer/ From 72287dd42c079089e774f76acb2fe6c74cd7d29b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:42:41 +0530 Subject: [PATCH 228/375] rmv flake8 --- .github/workflows/ci.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 745b749..1cf8570 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,15 +38,13 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install black flake8 pytest-cov + pip install black pytest-cov - name: Install enhancer run: | pip install -e .[dev,testing] - name: Run black run: - black --check . --exclude version.py - - name: Run flake8 - run: flake8 --max-line-length 120 --exclude __init__.py --ignore errors E203 + black --check . --exclude version.py¯ - name: Test with pytest run: pytest tests --cov=enhancer/ From f6d5e924f788c77975b463d8952e0a80db20ccb8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:51:21 +0530 Subject: [PATCH 229/375] ignore version.py --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1cf8570..a4124c9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,7 @@ jobs: pip install -e .[dev,testing] - name: Run black run: - black --check . --exclude version.py¯ + black --check . --exclude enhancer/version.py¯ - name: Test with pytest run: pytest tests --cov=enhancer/ From 975e8bc50e5433d5c36c61083e71d0eb6fce709e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 17:51:37 +0530 Subject: [PATCH 230/375] ignore version.py --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a4124c9..43b9e5e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,7 @@ jobs: pip install -e .[dev,testing] - name: Run black run: - black --check . --exclude enhancer/version.py¯ + black --check . --exclude enhancer/version.py - name: Test with pytest run: pytest tests --cov=enhancer/ From 3084ffac198edeb64c19065f22c06192c088675d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 20:51:18 +0530 Subject: [PATCH 231/375] fix version --- enhancer/models/model.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index cbbfad8..e7c5879 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -11,10 +11,10 @@ from huggingface_hub import cached_download, hf_hub_url from pytorch_lightning.utilities.cloud_io import load as pl_load from torch.optim import Adam -from enhancer import __version__ from enhancer.data.dataset import EnhancerDataset from enhancer.inference import Inference from enhancer.loss import Avergeloss +from enhancer.version import __version__ CACHE_DIR = "" HF_TORCH_WEIGHTS = "" @@ -298,10 +298,9 @@ class Model(pl.LightningModule): ), f"Expected batch with 3 dimensions (batch,channels,samples) got only {batch.ndim}" batch_predictions = [] self.eval().to(self.device) - with torch.no_grad(): for batch_id in range(0, batch.shape[0], batch_size): - batch_data = batch[batch_id : batch_id + batch_size, :, :].to( + batch_data = batch[batch_id : (batch_id + batch_size), :, :].to( self.device ) prediction = self(batch_data) From ae3064549764ee0d17caa249ab8d8c73ec425b53 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 20:51:35 +0530 Subject: [PATCH 232/375] add sndfile --- .github/workflows/ci.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43b9e5e..c653451 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,6 +37,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install sndfile pip install -r requirements.txt pip install black pytest-cov - name: Install enhancer @@ -44,7 +45,7 @@ jobs: pip install -e .[dev,testing] - name: Run black run: - black --check . --exclude enhancer/version.py + black --check . --exclude enhancer/version.pyß - name: Test with pytest run: pytest tests --cov=enhancer/ From 48a95343efcb034fd1ed5bc49849a032e07bbec4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 20:54:56 +0530 Subject: [PATCH 233/375] add sndfile --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c653451..469d88f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,7 +37,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install sndfile + pip install soundfile pip install -r requirements.txt pip install black pytest-cov - name: Install enhancer From a31af50c8f7ecce8d0af86d064b697df843fb7fa Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 20:58:49 +0530 Subject: [PATCH 234/375] fix typo --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 469d88f..fa07cc3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,7 +45,7 @@ jobs: pip install -e .[dev,testing] - name: Run black run: - black --check . --exclude enhancer/version.pyß + black --check . --exclude enhancer/version.py - name: Test with pytest run: pytest tests --cov=enhancer/ From cf0372b7d9b8ed95b338768b040f4d370b88f22e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:07:40 +0530 Subject: [PATCH 235/375] add soundfile --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index bb13983..5f1c4f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ protobuf>=3.19.6 pytorch-lightning>=1.7.7 scikit-learn>=1.1.2 scipy>=1.9.1 +soundfile>=0.11.0 torch>=1.12.1 torchaudio>=0.12.1 tqdm>=4.64.1 From 2e0a6b636b080cc451fd2f113d3de370e0073819 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:08:02 +0530 Subject: [PATCH 236/375] fix docs --- enhancer/models/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index e7c5879..7ff15e4 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -217,8 +217,7 @@ class Model(pl.LightningModule): to True or to a string containing your hugginface.co authentication token that can be obtained by running `huggingface-cli login` cache_dir: Path or str, optional - Path to model cache directory. Defaults to content of PYANNOTE_CACHE - environment variable, or "~/.cache/torch/pyannote" when unset. + Path to model cache directory kwargs: optional Any extra keyword args needed to init the model. Can also be used to override saved hyperparameter values. From 69ece5b811f766c1cae23f7493d0d4bca7ae5efa Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:08:20 +0530 Subject: [PATCH 237/375] to numpy --- enhancer/inference.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/enhancer/inference.py b/enhancer/inference.py index fd3b518..d9282fd 100644 --- a/enhancer/inference.py +++ b/enhancer/inference.py @@ -139,7 +139,9 @@ class Inference: if filename.is_file(): raise FileExistsError(f"file {filename} already exists") else: - wavfile.write(filename, rate=sr, data=waveform.detach().cpu()) + wavfile.write( + filename, rate=sr, data=waveform.detach().cpu().numpy() + ) @staticmethod def prepare_output( From 428d3548a0a994d33e5e9a4bd948ea0ce780b72b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:08:46 +0530 Subject: [PATCH 238/375] rmv typo --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 8916eaa..309ac9a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,7 +66,7 @@ extras = True # e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml # in order to write a coverage file that can be read by Jenkins. addopts = - --cov pyannote --cov-report term-missing + --cov enhancer --cov-report term-missing --verbose norecursedirs = dist From e15011d668f97cdd24eacb43a258768190c69c44 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:09:11 +0530 Subject: [PATCH 239/375] mv install to requirements --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fa07cc3..43b9e5e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,7 +37,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install soundfile pip install -r requirements.txt pip install black pytest-cov - name: Install enhancer From 95c30d0835d572a4ed44161151970abb8868556b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:15:35 +0530 Subject: [PATCH 240/375] sudo install --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43b9e5e..4c64745 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,6 +37,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + sudo apt-get install libsndfile1 pip install -r requirements.txt pip install black pytest-cov - name: Install enhancer From bf81df8970f24fe54506d3bc36bbe061e97e48aa Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 6 Oct 2022 21:27:20 +0530 Subject: [PATCH 241/375] lazy fixture --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 5f1c4f0..3762fd2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ librosa>=0.9.2 mlflow>=1.29.0 numpy>=1.23.3 protobuf>=3.19.6 +pytest-lazy-fixture>=0.6.3 pytorch-lightning>=1.7.7 scikit-learn>=1.1.2 scipy>=1.9.1 From e4b2965b45c560cb7ba67eeee4c570f8899228df Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 8 Oct 2022 10:12:13 +0530 Subject: [PATCH 242/375] log config --- enhancer/cli/train.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index a9c66e0..81fd443 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -3,7 +3,7 @@ from types import MethodType import hydra from hydra.utils import instantiate -from omegaconf import DictConfig +from omegaconf import DictConfig, OmegaConf from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint from pytorch_lightning.loggers import MLFlowLogger from torch.optim.lr_scheduler import ReduceLROnPlateau @@ -15,12 +15,16 @@ JOB_ID = os.environ.get("SLURM_JOBID", "0") @hydra.main(config_path="train_config", config_name="config") def main(config: DictConfig): + yaml_conf = OmegaConf.to_yaml(config) + OmegaConf.save(yaml_conf, "config_log.yaml") + callbacks = [] logger = MLFlowLogger( experiment_name=config.mlflow.experiment_name, run_name=config.mlflow.run_name, tags={"JOB_ID": JOB_ID}, ) + logger.experiment.log_artifact(logger.run_id, "config_log.yaml") parameters = config.hyperparameters From 43a22d24325d8b331d045455fb08a81eb29ad979 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 8 Oct 2022 11:04:06 +0530 Subject: [PATCH 243/375] log config to mlflow --- enhancer/cli/train.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 81fd443..a32c41f 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -15,8 +15,7 @@ JOB_ID = os.environ.get("SLURM_JOBID", "0") @hydra.main(config_path="train_config", config_name="config") def main(config: DictConfig): - yaml_conf = OmegaConf.to_yaml(config) - OmegaConf.save(yaml_conf, "config_log.yaml") + OmegaConf.save(config, "config_log.yaml") callbacks = [] logger = MLFlowLogger( @@ -24,7 +23,6 @@ def main(config: DictConfig): run_name=config.mlflow.run_name, tags={"JOB_ID": JOB_ID}, ) - logger.experiment.log_artifact(logger.run_id, "config_log.yaml") parameters = config.hyperparameters @@ -78,6 +76,10 @@ def main(config: DictConfig): trainer = instantiate(config.trainer, logger=logger, callbacks=callbacks) trainer.fit(model) + logger.experiment.log_artifact( + logger.run_id, f"{trainer.default_root_dir}/config_log.yaml" + ) + saved_location = os.path.join( trainer.default_root_dir, "model", f"model_{JOB_ID}.ckpt" ) From 37fe86063d52b582e162d5cff92edbdc282a1991 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 12:38:51 +0530 Subject: [PATCH 244/375] add model testing --- enhancer/cli/train.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index a32c41f..7b245d8 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -75,6 +75,7 @@ def main(config: DictConfig): trainer = instantiate(config.trainer, logger=logger, callbacks=callbacks) trainer.fit(model) + trainer.test(model) logger.experiment.log_artifact( logger.run_id, f"{trainer.default_root_dir}/config_log.yaml" From 3e654d10a7dab7ebcfe823fd5ddf64fda1715993 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 12:45:23 +0530 Subject: [PATCH 245/375] add test dataloader --- enhancer/data/dataset.py | 77 ++++++++++++++++++++++++++-------- enhancer/data/fileprocessor.py | 4 +- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 95c73a1..d2b7526 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -5,6 +5,7 @@ from typing import Optional import pytorch_lightning as pl import torch.nn.functional as F +from sklearn.model_selection import train_test_split from torch.utils.data import DataLoader, Dataset, IterableDataset from enhancer.data.fileprocessor import Fileprocessor @@ -36,12 +37,24 @@ class ValidDataset(Dataset): return self.dataset.val__len__() +class TestDataset(Dataset): + def __init__(self, dataset): + self.dataset = dataset + + def __getitem__(self, idx): + return self.dataset.test__getitem__(idx) + + def __len__(self): + return self.dataset.test__len__() + + class TaskDataset(pl.LightningDataModule): def __init__( self, name: str, root_dir: str, files: Files, + valid_size: float = 0.20, duration: float = 1.0, sampling_rate: int = 48000, matching_function=None, @@ -60,8 +73,15 @@ class TaskDataset(pl.LightningDataModule): if num_workers is None: num_workers = multiprocessing.cpu_count() // 2 self.num_workers = num_workers + if valid_size > 0.0: + self.valid_size = valid_size + else: + raise ValueError("valid_size must be greater than 0") def setup(self, stage: Optional[str] = None): + """ + prepare train/validation/test data splits + """ if stage in ("fit", None): @@ -70,25 +90,33 @@ class TaskDataset(pl.LightningDataModule): fp = Fileprocessor.from_name( self.name, train_clean, train_noisy, self.matching_function ) - self.train_data = fp.prepare_matching_dict() - - val_clean = os.path.join(self.root_dir, self.files.test_clean) - val_noisy = os.path.join(self.root_dir, self.files.test_noisy) - fp = Fileprocessor.from_name( - self.name, val_clean, val_noisy, self.matching_function + train_data = fp.prepare_matching_dict() + self.train_data, self.val_data = train_test_split( + train_data, test_size=0.20, shuffle=True, random_state=42 ) - val_data = fp.prepare_matching_dict() - for item in val_data: - clean, noisy, total_dur = item.values() - if total_dur < self.duration: - continue - num_segments = round(total_dur / self.duration) - for index in range(num_segments): - start_time = index * self.duration - self._validation.append( - ({"clean": clean, "noisy": noisy}, start_time) - ) + self._validation = self.prepare_mapstype(self.val_data) + + test_clean = os.path.join(self.root_dir, self.files.test_clean) + test_noisy = os.path.join(self.root_dir, self.files.test_noisy) + fp = Fileprocessor.from_name( + self.name, test_clean, test_noisy, self.matching_function + ) + test_data = fp.prepare_matching_dict() + self._test = self.prepare_mapstype(test_data) + + def prepare_mapstype(self, data): + + metadata = [] + for item in data: + clean, noisy, total_dur = item.values() + if total_dur < self.duration: + continue + num_segments = round(total_dur / self.duration) + for index in range(num_segments): + start_time = index * self.duration + metadata.append(({"clean": clean, "noisy": noisy}, start_time)) + return metadata def train_dataloader(self): return DataLoader( @@ -104,6 +132,13 @@ class TaskDataset(pl.LightningDataModule): num_workers=self.num_workers, ) + def test_dataloader(self): + return DataLoader( + TestDataset(self), + batch_size=self.batch_size, + num_workers=self.num_workers, + ) + class EnhancerDataset(TaskDataset): """ @@ -137,6 +172,7 @@ class EnhancerDataset(TaskDataset): name: str, root_dir: str, files: Files, + valid_size=0.2, duration=1.0, sampling_rate=48000, matching_function=None, @@ -148,6 +184,7 @@ class EnhancerDataset(TaskDataset): name=name, root_dir=root_dir, files=files, + valid_size=valid_size, sampling_rate=sampling_rate, duration=duration, matching_function=matching_function, @@ -183,6 +220,9 @@ class EnhancerDataset(TaskDataset): def val__getitem__(self, idx): return self.prepare_segment(*self._validation[idx]) + def test__getitem__(self, idx): + return self.prepare_segment(*self._test[idx]) + def prepare_segment(self, file_dict: dict, start_time: float): clean_segment = self.audio( @@ -218,3 +258,6 @@ class EnhancerDataset(TaskDataset): def val__len__(self): return len(self._validation) + + def test__len__(self): + return len(self._test) diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index 03afc73..e718f15 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -55,7 +55,7 @@ class ProcessorFunctions: One clean audio have multiple noisy audio files """ - matching_wavfiles = dict() + matching_wavfiles = list() clean_filenames = [ file.split("/")[-1] for file in glob.glob(os.path.join(clean_path, "*.wav")) @@ -73,7 +73,7 @@ class ProcessorFunctions: if (clean_file.shape[-1] == noisy_file.shape[-1]) and ( sr_clean == sr_noisy ): - matching_wavfiles.update( + matching_wavfiles.append( { "clean": os.path.join(clean_path, clean_file), "noisy": noisy_file, From 5945ddccaaab990f2df2bd06cf42d75c7e3b56ca Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 12:46:36 +0530 Subject: [PATCH 246/375] add pesq/stoi --- enhancer/loss.py | 49 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index db1d222..9ef90d2 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -1,5 +1,9 @@ +import logging + import torch import torch.nn as nn +from torchmetrics.audio.pesq import PerceptualEvaluationSpeechQuality +from torchmetrics.audio.stoi import ShortTimeObjectiveIntelligibility class mean_squared_error(nn.Module): @@ -12,6 +16,7 @@ class mean_squared_error(nn.Module): self.loss_fun = nn.MSELoss(reduction=reduction) self.higher_better = False + self.name = "mse" def forward(self, prediction: torch.Tensor, target: torch.Tensor): @@ -34,6 +39,7 @@ class mean_absolute_error(nn.Module): self.loss_fun = nn.L1Loss(reduction=reduction) self.higher_better = False + self.name = "mae" def forward(self, prediction: torch.Tensor, target: torch.Tensor): @@ -46,13 +52,12 @@ class mean_absolute_error(nn.Module): return self.loss_fun(prediction, target) -class Si_SDR(nn.Module): +class Si_SDR: """ SI-SDR metric based on SDR – HALF-BAKED OR WELL DONE?(https://arxiv.org/pdf/1811.02508.pdf) """ def __init__(self, reduction: str = "mean"): - super().__init__() if reduction in ["sum", "mean", None]: self.reduction = reduction else: @@ -60,8 +65,9 @@ class Si_SDR(nn.Module): "Invalid reduction, valid options are sum, mean, None" ) self.higher_better = False + self.name = "Si-SDR" - def forward(self, prediction: torch.Tensor, target: torch.Tensor): + def __call__(self, prediction: torch.Tensor, target: torch.Tensor): if prediction.size() != target.size() or target.ndim < 3: raise TypeError( @@ -90,7 +96,40 @@ class Si_SDR(nn.Module): return si_sdr -class Avergeloss(nn.Module): +class Stoi: + """ + STOI (Short-Time Objective Intelligibility, see [2,3]), a wrapper for the pystoi package [1]. + Note that input will be moved to cpu to perform the metric calculation. + parameters: + sr: int + sampling rate + """ + + def __init__(self, sr: int): + self.sr = sr + self.stoi = ShortTimeObjectiveIntelligibility(fs=sr) + self.name = "stoi" + + def __call__(self, prediction: torch.Tensor, target: torch.Tensor): + + return self.stoi(prediction, target) + + +class Pesq: + def __init__(self, sr: int, mode="nb"): + + self.pesq = PerceptualEvaluationSpeechQuality(fs=sr, mode=mode) + self.name = "pesq" + + def __call__(self, prediction: torch.Tensor, target: torch.Tensor): + try: + return self.pesq(prediction, target) + except Exception as e: + logging.warning(f"{e} error occured while calculating PESQ") + return 0.0 + + +class LossWrapper(nn.Module): """ Combine multiple metics of same nature. for example, ["mea","mae"] @@ -137,4 +176,6 @@ LOSS_MAP = { "mae": mean_absolute_error, "mse": mean_squared_error, "SI-SDR": Si_SDR, + "pesq": Pesq, + "stoi": Stoi, } From 1aca956ed44606b9e609e16120144287ceec938e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 12:46:59 +0530 Subject: [PATCH 247/375] update requirements --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 3762fd2..95f145d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,9 @@ joblib>=1.2.0 librosa>=0.9.2 mlflow>=1.29.0 numpy>=1.23.3 +pesq==0.0.4 protobuf>=3.19.6 +pystoi==0.3.3 pytest-lazy-fixture>=0.6.3 pytorch-lightning>=1.7.7 scikit-learn>=1.1.2 From 2587d5b8675edf5239ebcf14265e80958e6764e5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 12:47:24 +0530 Subject: [PATCH 248/375] add test step --- enhancer/models/model.py | 48 ++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 7ff15e4..07564cd 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -13,7 +13,7 @@ from torch.optim import Adam from enhancer.data.dataset import EnhancerDataset from enhancer.inference import Inference -from enhancer.loss import Avergeloss +from enhancer.loss import LOSS_MAP, LossWrapper from enhancer.version import __version__ CACHE_DIR = "" @@ -76,7 +76,7 @@ class Model(pl.LightningModule): if isinstance(loss, str): loss = [loss] - self._loss = Avergeloss(loss) + self._loss = LossWrapper(loss) @property def metric(self): @@ -84,11 +84,21 @@ class Model(pl.LightningModule): @metric.setter def metric(self, metric): - + self._metric = [] if isinstance(metric, str): metric = [metric] - self._metric = Avergeloss(metric) + for func in metric: + if func in LOSS_MAP.keys(): + if func in ("pesq", "stoi"): + self._metric.append( + LOSS_MAP[func](self.hparams.sampling_rate) + ) + else: + self._metric.append(LOSS_MAP[func]()) + + else: + raise ValueError(f"Invalid metrics {func}") @property def dataset(self): @@ -109,6 +119,9 @@ class Model(pl.LightningModule): def val_dataloader(self): return self.dataset.val_dataloader() + def test_dataloader(self): + return self.dataset.test_dataloader() + def configure_optimizers(self): return Adam(self.parameters(), lr=self.hparams.lr) @@ -140,9 +153,7 @@ class Model(pl.LightningModule): target = batch["clean"] prediction = self(mixed_waveform) - metric_val = self.metric(prediction, target) loss_val = self.loss(prediction, target) - self.log("val_metric", metric_val.item()) self.log("val_loss", loss_val.item()) if ( @@ -156,15 +167,28 @@ class Model(pl.LightningModule): value=loss_val.item(), step=self.global_step, ) - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key="val_metric", - value=metric_val.item(), - step=self.global_step, - ) return {"loss": loss_val} + def test_step(self, batch, batch_idx): + + metric_dict = {} + mixed_waveform = batch["noisy"] + target = batch["clean"] + prediction = self(mixed_waveform) + + for metric in self.metric: + value = metric(target, prediction) + metric_dict[metric.name] = value + + self.logger.experiment.log_metrics( + run_id=self.logger.run_id, + metrics=metric_dict, + step=self.global_step, + ) + + return metric_dict + def on_save_checkpoint(self, checkpoint): checkpoint["enhancer"] = { From ab0f51ea176c4a1e2e65b5e16b08dfc751be0448 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 15:33:08 +0530 Subject: [PATCH 249/375] log metric --- enhancer/models/model.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 07564cd..4f1d75b 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -181,11 +181,13 @@ class Model(pl.LightningModule): value = metric(target, prediction) metric_dict[metric.name] = value - self.logger.experiment.log_metrics( - run_id=self.logger.run_id, - metrics=metric_dict, - step=self.global_step, - ) + for k, v in metric_dict.items(): + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key=k, + value=v, + step=self.global_step, + ) return metric_dict From a72f3072832853c3f0b92d7d7587f916ece3410b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 17:41:58 +0530 Subject: [PATCH 250/375] readme' --- README.md | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8ade768..d02e39e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,40 @@ -# enhancer -Enhancer is a Pytorch-based opensource toolkit for speech enhancement. It is designed to save time for audio researchers. Is provides easy to use pretrained audio enhancement models and facilitates highly customisable custom model training . Enhancer provides +# mayavoz +mayavoz is a Pytorch-based opensource toolkit for speech enhancement. It is designed to save time for audio researchers. Is provides easy to use pretrained audio enhancement models and facilitates highly customisable custom model training . -* Various pretrained models nicely integrated with huggingface that users can select and use without any hastle. -* Ability to train and validation your own custom speech enhancement models with just under 10 lines of code! +| **[Quick Start]()** | **[Installation]()** | **[Tutorials]()** | **[Available Recipes]()** +## Key features :key: + +* Various pretrained models nicely integrated with huggingface :hugs: that users can select and use without any hastle. +* :package: Ability to train and validation your own custom speech enhancement models with just under 10 lines of code! * A command line tool that facilitates training of highly customisable speech enhacement models from the terminal itself! +* Supports multi-gpu training integrated with Pytorch Lightning. + +## Quick Start +``` python +from mayavoz import Mayamodel + +model = Mayamodel.from_pretrained("mayavoz/waveunet") +model("noisy_audio.wav") +``` + +## Installation +Only Python 3.8+ is officially supported (though it might work with Python 3.7) + +- With Pypi +``` +pip install mayavoz +``` + +- With conda + +``` +conda env create -f environment.yml +conda activate mayavoz +``` + +- From source code +``` +git clone url +cd mayavoz +pip install -e . +``` From 4363821c7525ab1382c3d63de6e2b7cbdc4cd5cf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 17:45:34 +0530 Subject: [PATCH 251/375] add emojis --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d02e39e..f721ab0 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ mayavoz is a Pytorch-based opensource toolkit for speech enhancement. It is desi * Various pretrained models nicely integrated with huggingface :hugs: that users can select and use without any hastle. * :package: Ability to train and validation your own custom speech enhancement models with just under 10 lines of code! -* A command line tool that facilitates training of highly customisable speech enhacement models from the terminal itself! -* Supports multi-gpu training integrated with Pytorch Lightning. +* :magic_wand: A command line tool that facilitates training of highly customisable speech enhacement models from the terminal itself! +* :zap: Supports multi-gpu training integrated with Pytorch Lightning. -## Quick Start +## Quick Start :fire: ``` python from mayavoz import Mayamodel From c193a48e8e27a2779273bb28d7600ef133fea858 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 10 Oct 2022 21:03:39 +0530 Subject: [PATCH 252/375] empty cache --- enhancer/models/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 4f1d75b..7dbc065 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -110,6 +110,7 @@ class Model(pl.LightningModule): def setup(self, stage: Optional[str] = None): if stage == "fit": + torch.cuda.empty_cache() self.dataset.setup(stage) self.dataset.model = self From 5eb15b41c4409f0758311f4e7240cf05d562504c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 11 Oct 2022 11:11:16 +0530 Subject: [PATCH 253/375] fix architecture --- enhancer/models/demucs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index bf9d429..5d7e99f 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -49,7 +49,7 @@ class DemucsEncoder(nn.Module): self.encoder = nn.Sequential( nn.Conv1d(num_channels, hidden_size, kernel_size, stride), nn.ReLU(), - nn.Conv1d(hidden_size, hidden_size * multi_factor, kernel_size, 1), + nn.Conv1d(hidden_size, hidden_size * multi_factor, 1, 1), activation, ) @@ -72,7 +72,7 @@ class DemucsDecoder(nn.Module): activation = nn.GLU(1) if glu else nn.ReLU() multi_factor = 2 if glu else 1 self.decoder = nn.Sequential( - nn.Conv1d(hidden_size, hidden_size * multi_factor, kernel_size, 1), + nn.Conv1d(hidden_size, hidden_size * multi_factor, 1, 1), activation, nn.ConvTranspose1d(hidden_size, num_channels, kernel_size, stride), ) @@ -116,7 +116,7 @@ class Demucs(Model): ED_DEFAULTS = { "initial_output_channels": 48, "kernel_size": 8, - "stride": 1, + "stride": 4, "depth": 5, "glu": True, "growth_factor": 2, @@ -179,7 +179,7 @@ class Demucs(Model): num_channels=num_channels, hidden_size=hidden, kernel_size=encoder_decoder["kernel_size"], - stride=1, + stride=encoder_decoder["stride"], glu=encoder_decoder["glu"], layer=layer, ) @@ -236,7 +236,7 @@ class Demucs(Model): self.hparams.sampling_rate, ) - return x + return x[..., :length] def get_padding_length(self, input_length): From c996798dd340e0b37288a9ed4d74511115847578 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 11 Oct 2022 11:11:44 +0530 Subject: [PATCH 254/375] change stride --- enhancer/cli/train_config/model/Demucs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train_config/model/Demucs.yaml b/enhancer/cli/train_config/model/Demucs.yaml index d91b5ff..3c565ee 100644 --- a/enhancer/cli/train_config/model/Demucs.yaml +++ b/enhancer/cli/train_config/model/Demucs.yaml @@ -7,7 +7,7 @@ encoder_decoder: depth: 5 initial_output_channels: 32 kernel_size: 8 - stride: 1 + stride: 4 growth_factor: 2 glu: True From fdce8bb601232ca5e485b061074c5333266a246e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 11 Oct 2022 15:11:50 +0530 Subject: [PATCH 255/375] rmv inplace operation --- enhancer/models/demucs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 5d7e99f..95d6a6f 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -226,7 +226,7 @@ class Demucs(Model): x = x.permute(0, 2, 1) for decoder in self.decoder: skip_connection = encoder_outputs.pop(-1) - x += skip_connection[..., : x.shape[-1]] + x = x + skip_connection[..., : x.shape[-1]] x = decoder(x) if self.hparams.resample > 1: @@ -236,7 +236,8 @@ class Demucs(Model): self.hparams.sampling_rate, ) - return x[..., :length] + out = x[..., :length] + return out def get_padding_length(self, input_length): From abcdc29309e83660ecd223e06a71283370087782 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 11 Oct 2022 16:48:49 +0530 Subject: [PATCH 256/375] log average metrics --- enhancer/models/model.py | 51 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 7dbc065..04f79a8 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -1,7 +1,8 @@ import os +from collections import defaultdict from importlib import import_module from pathlib import Path -from typing import Any, Dict, List, Optional, Text, Union +from typing import List, Optional, Text, Union from urllib.parse import urlparse import numpy as np @@ -192,6 +193,51 @@ class Model(pl.LightningModule): return metric_dict + def training_epoch_end(self, outputs): + train_mean_loss = 0.0 + for output in outputs: + train_mean_loss += output["loss"] + train_mean_loss /= len(outputs) + + if self.logger: + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key="train_loss_epoch", + value=train_mean_loss, + step=self.current_epoch, + ) + + def validation_epoch_end(self, outputs): + valid_mean_loss = 0.0 + for output in outputs: + valid_mean_loss += output["loss"] + valid_mean_loss /= len(outputs) + + if self.logger: + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key="valid_loss_epoch", + value=valid_mean_loss, + step=self.current_epoch, + ) + + def test_epoch_end(self, outputs): + + test_mean_metrics = defaultdict(int) + for output in outputs: + for metric, value in output.items(): + test_mean_metrics[metric] += value.item() + for metric in test_mean_metrics.keys(): + test_mean_metrics[metric] /= len(outputs) + + for k, v in test_mean_metrics.items(): + self.logger.experiment.log_metric( + run_id=self.logger.run_id, + key=k, + value=v, + step=self.current_epoch, + ) + def on_save_checkpoint(self, checkpoint): checkpoint["enhancer"] = { @@ -202,9 +248,6 @@ class Model(pl.LightningModule): }, } - def on_load_checkpoint(self, checkpoint: Dict[str, Any]): - pass - @classmethod def from_pretrained( cls, From a57a7ff0fcb853eb9072c5f775f888be262b3d76 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 11 Oct 2022 21:30:13 +0530 Subject: [PATCH 257/375] print metrics report --- enhancer/models/model.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 04f79a8..3f74a74 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -230,13 +230,10 @@ class Model(pl.LightningModule): for metric in test_mean_metrics.keys(): test_mean_metrics[metric] /= len(outputs) - for k, v in test_mean_metrics.items(): - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key=k, - value=v, - step=self.current_epoch, - ) + print("----------TEST REPORT----------\n") + for metric in test_mean_metrics.keys(): + print(f"|{metric.upper()} | {test_mean_metrics[metric]} |") + print("--------------------------------") def on_save_checkpoint(self, checkpoint): From 551ee4436f67d7a0ff9fe53adeace78d7b6e9448 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 11 Oct 2022 21:36:34 +0530 Subject: [PATCH 258/375] change verbose --- enhancer/cli/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 7b245d8..38300fd 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -40,7 +40,7 @@ def main(config: DictConfig): dirpath="./model", filename=f"model_{JOB_ID}", monitor="val_loss", - verbose=True, + verbose=False, mode=direction, every_n_epochs=1, ) From 89ab58c5df359ab3dbe7aa8ae0d5b6c7b55343a5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 10:32:31 +0530 Subject: [PATCH 259/375] return tensor --- enhancer/loss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index 9ef90d2..fd1a609 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -126,7 +126,7 @@ class Pesq: return self.pesq(prediction, target) except Exception as e: logging.warning(f"{e} error occured while calculating PESQ") - return 0.0 + return torch.tensor(0.0) class LossWrapper(nn.Module): From e389acefa03c4c576413602ebc55c0c815f67d28 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:26:10 +0530 Subject: [PATCH 260/375] commentout earlystop --- enhancer/cli/train.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 38300fd..de48e64 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -4,7 +4,7 @@ from types import MethodType import hydra from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf -from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint +from pytorch_lightning.callbacks import ModelCheckpoint from pytorch_lightning.loggers import MLFlowLogger from torch.optim.lr_scheduler import ReduceLROnPlateau @@ -45,15 +45,15 @@ def main(config: DictConfig): every_n_epochs=1, ) callbacks.append(checkpoint) - early_stopping = EarlyStopping( - monitor="val_loss", - mode=direction, - min_delta=0.0, - patience=parameters.get("EarlyStopping_patience", 10), - strict=True, - verbose=False, - ) - callbacks.append(early_stopping) + # early_stopping = EarlyStopping( + # monitor="val_loss", + # mode=direction, + # min_delta=0.0, + # patience=parameters.get("EarlyStopping_patience", 10), + # strict=True, + # verbose=False, + # ) + # callbacks.append(early_stopping) def configure_optimizer(self): optimizer = instantiate( From 144b9d612803b6779bbf1a4b1fc22202f4f7b4ef Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:26:51 +0530 Subject: [PATCH 261/375] fix logging --- enhancer/models/model.py | 90 +++++++++++++--------------------------- 1 file changed, 28 insertions(+), 62 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 3f74a74..8eb19a8 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -132,45 +132,39 @@ class Model(pl.LightningModule): mixed_waveform = batch["noisy"] target = batch["clean"] prediction = self(mixed_waveform) - loss = self.loss(prediction, target) - if ( - (self.logger) - and (self.global_step > 50) - and (self.global_step % 50 == 0) - ): - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key="train_loss", - value=loss.item(), - step=self.global_step, - ) - self.log("train_loss", loss.item()) + self.log( + "train_loss", + loss.item(), + on_epoch=True, + on_step=True, + logger=True, + prog_bar=True, + ) + return {"loss": loss} def validation_step(self, batch, batch_idx: int): + metric_dict = {} mixed_waveform = batch["noisy"] target = batch["clean"] prediction = self(mixed_waveform) - loss_val = self.loss(prediction, target) - self.log("val_loss", loss_val.item()) + for metric in self.metric: + value = metric(target, prediction) + metric_dict[f"valid_{metric.name}"] = value.item() - if ( - (self.logger) - and (self.global_step > 50) - and (self.global_step % 50 == 0) - ): - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key="val_loss", - value=loss_val.item(), - step=self.global_step, - ) + self.log_dict( + metric_dict, + on_step=True, + on_epoch=True, + prog_bar=True, + logger=True, + ) - return {"loss": loss_val} + return metric_dict def test_step(self, batch, batch_idx): @@ -183,44 +177,16 @@ class Model(pl.LightningModule): value = metric(target, prediction) metric_dict[metric.name] = value - for k, v in metric_dict.items(): - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key=k, - value=v, - step=self.global_step, - ) + self.log_dict( + metric_dict, + on_step=True, + on_epoch=True, + prog_bar=True, + logger=True, + ) return metric_dict - def training_epoch_end(self, outputs): - train_mean_loss = 0.0 - for output in outputs: - train_mean_loss += output["loss"] - train_mean_loss /= len(outputs) - - if self.logger: - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key="train_loss_epoch", - value=train_mean_loss, - step=self.current_epoch, - ) - - def validation_epoch_end(self, outputs): - valid_mean_loss = 0.0 - for output in outputs: - valid_mean_loss += output["loss"] - valid_mean_loss /= len(outputs) - - if self.logger: - self.logger.experiment.log_metric( - run_id=self.logger.run_id, - key="valid_loss_epoch", - value=valid_mean_loss, - step=self.current_epoch, - ) - def test_epoch_end(self, outputs): test_mean_metrics = defaultdict(int) From 1831dc201385a47d286acc804e9c172ed3514570 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:28:04 +0530 Subject: [PATCH 262/375] config --- enhancer/cli/train_config/dataset/Vctk.yaml | 2 +- enhancer/cli/train_config/trainer/default.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 5c19320..df50da2 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -3,7 +3,7 @@ name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 1.0 sampling_rate: 16000 -batch_size: 64 +batch_size: 128 files: train_clean : clean_trainset_28spk_wav diff --git a/enhancer/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml index 55101de..92ae56a 100644 --- a/enhancer/cli/train_config/trainer/default.yaml +++ b/enhancer/cli/train_config/trainer/default.yaml @@ -22,8 +22,8 @@ limit_predict_batches: 1.0 limit_test_batches: 1.0 limit_train_batches: 1.0 limit_val_batches: 1.0 -log_every_n_steps: 1 -max_epochs: 10 +log_every_n_steps: 50 +max_epochs: 3 max_steps: null max_time: null min_epochs: 1 From 6fb0aeae7fe76aa2b81a21738c348196d35012c3 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:56:50 +0530 Subject: [PATCH 263/375] name loss wraper --- enhancer/loss.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enhancer/loss.py b/enhancer/loss.py index fd1a609..cdd15a5 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -151,9 +151,11 @@ class LossWrapper(nn.Module): ) self.higher_better = direction[0] + self.name = "" for loss in losses: loss = self.validate_loss(loss) self.valid_losses.append(loss()) + self.name += f"{loss().name}_" def validate_loss(self, loss: str): if loss not in LOSS_MAP.keys(): From 226758e91f46e0ada2b84fe3d4a30869ef096798 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:57:11 +0530 Subject: [PATCH 264/375] hawk --- hpc_entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh index 6d6a3a0..4c77127 100644 --- a/hpc_entrypoint.sh +++ b/hpc_entrypoint.sh @@ -36,4 +36,4 @@ pwd #python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test echo "Start Training..." -python cli/train.py +python enhancer/cli/train.py From ad56a160e4074153602c81697a5061a4c3c09d40 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:57:42 +0530 Subject: [PATCH 265/375] train log rename --- enhancer/models/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 8eb19a8..3ad5fa7 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -135,7 +135,7 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) self.log( - "train_loss", + f"train_{self.loss.name}", loss.item(), on_epoch=True, on_step=True, @@ -175,7 +175,7 @@ class Model(pl.LightningModule): for metric in self.metric: value = metric(target, prediction) - metric_dict[metric.name] = value + metric_dict[f"test_{metric.name}"] = value self.log_dict( metric_dict, From 00ef644179ca9d34438de9f2922d08e2a7ba2d80 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 17:58:22 +0530 Subject: [PATCH 266/375] maxsteps -1 --- enhancer/cli/train_config/trainer/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml index 92ae56a..dfc020f 100644 --- a/enhancer/cli/train_config/trainer/default.yaml +++ b/enhancer/cli/train_config/trainer/default.yaml @@ -24,7 +24,7 @@ limit_train_batches: 1.0 limit_val_batches: 1.0 log_every_n_steps: 50 max_epochs: 3 -max_steps: null +max_steps: -1 max_time: null min_epochs: 1 min_steps: null From cb0040b508f29ccc55f68ff936dbe1cf863a3cba Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 18:44:22 +0530 Subject: [PATCH 267/375] rename loss --- enhancer/models/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 3ad5fa7..4f055b4 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -135,7 +135,7 @@ class Model(pl.LightningModule): loss = self.loss(prediction, target) self.log( - f"train_{self.loss.name}", + "train_loss", loss.item(), on_epoch=True, on_step=True, @@ -152,6 +152,7 @@ class Model(pl.LightningModule): target = batch["clean"] prediction = self(mixed_waveform) + metric_dict["valid_loss"] = self.loss(target, prediction).item() for metric in self.metric: value = metric(target, prediction) metric_dict[f"valid_{metric.name}"] = value.item() From 2e58091543b6bb76d94811b208e67369d6ba21aa Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 12 Oct 2022 18:45:07 +0530 Subject: [PATCH 268/375] rename loss --- enhancer/cli/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index de48e64..49f7b3b 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -39,7 +39,7 @@ def main(config: DictConfig): checkpoint = ModelCheckpoint( dirpath="./model", filename=f"model_{JOB_ID}", - monitor="val_loss", + monitor="valid_loss", verbose=False, mode=direction, every_n_epochs=1, From 88112e6ae31b917764511af13787677caa2af1cb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 13 Oct 2022 10:38:58 +0530 Subject: [PATCH 269/375] fix pesq --- enhancer/loss.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index cdd15a5..5092656 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -1,5 +1,6 @@ import logging +import numpy as np import torch import torch.nn as nn from torchmetrics.audio.pesq import PerceptualEvaluationSpeechQuality @@ -116,17 +117,24 @@ class Stoi: class Pesq: - def __init__(self, sr: int, mode="nb"): + def __init__(self, sr: int, mode="wb"): - self.pesq = PerceptualEvaluationSpeechQuality(fs=sr, mode=mode) + self.sr = sr self.name = "pesq" + self.mode = mode + self.pesq = PerceptualEvaluationSpeechQuality(fs=sr, mode=mode) def __call__(self, prediction: torch.Tensor, target: torch.Tensor): - try: - return self.pesq(prediction, target) - except Exception as e: - logging.warning(f"{e} error occured while calculating PESQ") - return torch.tensor(0.0) + + pesq_values = [] + for pred, target_ in zip(prediction, target): + try: + pesq_values.append( + self.pesq(pred.squeeze(), target_.squeeze()).item() + ) + except Exception as e: + logging.warning(f"{e} error occured while calculating PESQ") + return torch.tensor(np.mean(pesq_values)) class LossWrapper(nn.Module): @@ -177,7 +185,7 @@ class LossWrapper(nn.Module): LOSS_MAP = { "mae": mean_absolute_error, "mse": mean_squared_error, - "SI-SDR": Si_SDR, + "si-sdr": Si_SDR, "pesq": Pesq, "stoi": Stoi, } From 3e42d843985fea42a3cbf81b893ea0dd2a7daf5c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 13 Oct 2022 10:49:05 +0530 Subject: [PATCH 270/375] add logo --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f721ab0..586df14 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# mayavoz +

+ +

+ mayavoz is a Pytorch-based opensource toolkit for speech enhancement. It is designed to save time for audio researchers. Is provides easy to use pretrained audio enhancement models and facilitates highly customisable custom model training . | **[Quick Start]()** | **[Installation]()** | **[Tutorials]()** | **[Available Recipes]()** From 891446f7db2fd20f0e2e86ee5394dc30d6436fd2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 13 Oct 2022 11:34:28 +0530 Subject: [PATCH 271/375] add tagline --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 586df14..13b8e14 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

- +

-mayavoz is a Pytorch-based opensource toolkit for speech enhancement. It is designed to save time for audio researchers. Is provides easy to use pretrained audio enhancement models and facilitates highly customisable custom model training . +mayavoz is a Pytorch-based opensource toolkit for speech enhancement. It is designed to save time for audio researchers. Is provides easy to use pretrained audio enhancement models and facilitates highly customisable model training. | **[Quick Start]()** | **[Installation]()** | **[Tutorials]()** | **[Available Recipes]()** ## Key features :key: From 204de08a9ac065f32997aab12c34738e0debced2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 14 Oct 2022 10:52:56 +0530 Subject: [PATCH 272/375] add early stopping --- enhancer/cli/train.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 49f7b3b..08f4d3e 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -4,7 +4,7 @@ from types import MethodType import hydra from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf -from pytorch_lightning.callbacks import ModelCheckpoint +from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint from pytorch_lightning.loggers import MLFlowLogger from torch.optim.lr_scheduler import ReduceLROnPlateau @@ -45,15 +45,16 @@ def main(config: DictConfig): every_n_epochs=1, ) callbacks.append(checkpoint) - # early_stopping = EarlyStopping( - # monitor="val_loss", - # mode=direction, - # min_delta=0.0, - # patience=parameters.get("EarlyStopping_patience", 10), - # strict=True, - # verbose=False, - # ) - # callbacks.append(early_stopping) + if parameters.get("Early_stop", False): + early_stopping = EarlyStopping( + monitor="val_loss", + mode=direction, + min_delta=0.0, + patience=parameters.get("EarlyStopping_patience", 10), + strict=True, + verbose=False, + ) + callbacks.append(early_stopping) def configure_optimizer(self): optimizer = instantiate( From 6a3c67fc13c8d11ef00e75eb79e47e79d7eb9bf9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 14 Oct 2022 11:32:18 +0530 Subject: [PATCH 273/375] print train/val duration --- enhancer/models/model.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 4f055b4..714b0e5 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -113,6 +113,20 @@ class Model(pl.LightningModule): if stage == "fit": torch.cuda.empty_cache() self.dataset.setup(stage) + print( + "Total train duration", + self.dataset.train_dataloader().__len__() + * self.dataset.duration + / 60, + "minutes", + ) + print( + "Total validation duration", + self.dataset.val_dataloader().__len__() + * self.dataset.duration + / 60, + "minutes", + ) self.dataset.model = self def train_dataloader(self): From 4f25690e2ad6ea92df47d8a0462a4762c35b7f49 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 14 Oct 2022 12:43:22 +0530 Subject: [PATCH 274/375] fix duration logging --- enhancer/models/model.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 714b0e5..b283a45 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -115,16 +115,12 @@ class Model(pl.LightningModule): self.dataset.setup(stage) print( "Total train duration", - self.dataset.train_dataloader().__len__() - * self.dataset.duration - / 60, + self.dataset.train_dataloader().dataset.__len__() / 60, "minutes", ) print( "Total validation duration", - self.dataset.val_dataloader().__len__() - * self.dataset.duration - / 60, + self.dataset.val_dataloader().dataset.__len__() / 60, "minutes", ) self.dataset.model = self From d11a53e0ea4cbe70f7c5581928e4862080c16320 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 14 Oct 2022 12:46:30 +0530 Subject: [PATCH 275/375] log test duration --- enhancer/models/model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index b283a45..aa0199b 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -123,6 +123,11 @@ class Model(pl.LightningModule): self.dataset.val_dataloader().dataset.__len__() / 60, "minutes", ) + print( + "Total test duration", + self.dataset.test_dataloader().dataset.__len__() / 60, + "minutes", + ) self.dataset.model = self def train_dataloader(self): From 0910d9ac841a1990eb5a701c659590aca558781c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 15 Oct 2022 12:23:51 +0530 Subject: [PATCH 276/375] fix dns loader --- enhancer/data/fileprocessor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/enhancer/data/fileprocessor.py b/enhancer/data/fileprocessor.py index e718f15..5b099d4 100644 --- a/enhancer/data/fileprocessor.py +++ b/enhancer/data/fileprocessor.py @@ -62,25 +62,24 @@ class ProcessorFunctions: ] for clean_file in clean_filenames: noisy_filenames = glob.glob( - os.path.join(noisy_path, f"*_{clean_file}.wav") + os.path.join(noisy_path, f"*_{clean_file}") ) for noisy_file in noisy_filenames: - sr_clean, clean_file = wavfile.read( + sr_clean, clean_wav = wavfile.read( os.path.join(clean_path, clean_file) ) - sr_noisy, noisy_file = wavfile.read(noisy_file) - if (clean_file.shape[-1] == noisy_file.shape[-1]) and ( + sr_noisy, noisy_wav = wavfile.read(noisy_file) + if (clean_wav.shape[-1] == noisy_wav.shape[-1]) and ( sr_clean == sr_noisy ): matching_wavfiles.append( { "clean": os.path.join(clean_path, clean_file), "noisy": noisy_file, - "duration": clean_file.shape[-1] / sr_clean, + "duration": clean_wav.shape[-1] / sr_clean, } ) - return matching_wavfiles From d99fd0eb61ff4d969f15ac4a05db0c209c209dc9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 16 Oct 2022 11:13:44 +0530 Subject: [PATCH 277/375] fix duration estimation --- enhancer/models/model.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index aa0199b..dc5219d 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -115,17 +115,23 @@ class Model(pl.LightningModule): self.dataset.setup(stage) print( "Total train duration", - self.dataset.train_dataloader().dataset.__len__() / 60, + self.dataset.train_dataloader().dataset.__len__() + * self.dataset.duration + / 60, "minutes", ) print( "Total validation duration", - self.dataset.val_dataloader().dataset.__len__() / 60, + self.dataset.val_dataloader().dataset.__len__() + * self.dataset.duration + / 60, "minutes", ) print( "Total test duration", - self.dataset.test_dataloader().dataset.__len__() / 60, + self.dataset.test_dataloader().dataset.__len__() + * self.dataset.duration + / 60, "minutes", ) self.dataset.model = self From dab7e73d53a8357db3afe3b870a184d589aa6cc1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 16 Oct 2022 11:14:13 +0530 Subject: [PATCH 278/375] DNS 2020 --- enhancer/cli/train_config/dataset/DNS-2020.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/enhancer/cli/train_config/dataset/DNS-2020.yaml b/enhancer/cli/train_config/dataset/DNS-2020.yaml index 3bd0e67..09a14fb 100644 --- a/enhancer/cli/train_config/dataset/DNS-2020.yaml +++ b/enhancer/cli/train_config/dataset/DNS-2020.yaml @@ -1,12 +1,12 @@ _target_: enhancer.data.dataset.EnhancerDataset -root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk_test +root_dir : /Users/shahules/Myprojects/MS-SNSD name : dns-2020 -duration : 1.0 +duration : 2.0 sampling_rate: 16000 batch_size: 32 +valid_size: 0.05 files: - root_dir : /Users/shahules/Myprojects/enhancer/datasets/vctk_test - train_clean : clean_test_wav - test_clean : clean_test_wav - train_noisy : clean_test_wav - test_noisy : clean_test_wav + train_clean : CleanSpeech_training + test_clean : CleanSpeech_training + train_noisy : NoisySpeech_training + test_noisy : NoisySpeech_training From e118c31f18238c860aec65c6ceee11f2c1607577 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 17 Oct 2022 13:10:22 +0530 Subject: [PATCH 279/375] specify valid size in mins --- enhancer/data/dataset.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index d2b7526..02a1d3b 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -5,7 +5,6 @@ from typing import Optional import pytorch_lightning as pl import torch.nn.functional as F -from sklearn.model_selection import train_test_split from torch.utils.data import DataLoader, Dataset, IterableDataset from enhancer.data.fileprocessor import Fileprocessor @@ -54,7 +53,7 @@ class TaskDataset(pl.LightningDataModule): name: str, root_dir: str, files: Files, - valid_size: float = 0.20, + valid_minutes: float = 0.20, duration: float = 1.0, sampling_rate: int = 48000, matching_function=None, @@ -73,10 +72,10 @@ class TaskDataset(pl.LightningDataModule): if num_workers is None: num_workers = multiprocessing.cpu_count() // 2 self.num_workers = num_workers - if valid_size > 0.0: - self.valid_size = valid_size + if valid_minutes > 0.0: + self.valid_minutes = valid_minutes else: - raise ValueError("valid_size must be greater than 0") + raise ValueError("valid_minutes must be greater than 0") def setup(self, stage: Optional[str] = None): """ @@ -91,8 +90,8 @@ class TaskDataset(pl.LightningDataModule): self.name, train_clean, train_noisy, self.matching_function ) train_data = fp.prepare_matching_dict() - self.train_data, self.val_data = train_test_split( - train_data, test_size=0.20, shuffle=True, random_state=42 + self.train_data, self.val_data = self.train_valid_split( + train_data, valid_minutes=self.valid_minutes, random_state=42 ) self._validation = self.prepare_mapstype(self.val_data) @@ -105,6 +104,28 @@ class TaskDataset(pl.LightningDataModule): test_data = fp.prepare_matching_dict() self._test = self.prepare_mapstype(test_data) + def train_valid_split( + self, data, valid_minutes: float = 20, random_state: int = 42 + ): + + valid_minutes *= 60 + valid_min_now = 0.0 + valid_indices = [] + random_indices = list(range(0, len(data))) + rng = create_unique_rng(random_state) + rng.shuffle(random_indices) + i = 0 + while valid_min_now <= valid_minutes: + valid_indices.append(random_indices[i]) + valid_min_now += data[random_indices[i]]["duration"] + i += 1 + + train_data = [ + item for i, item in enumerate(data) if i not in valid_indices + ] + valid_data = [item for i, item in enumerate(data) if i in valid_indices] + return train_data, valid_data + def prepare_mapstype(self, data): metadata = [] @@ -172,7 +193,7 @@ class EnhancerDataset(TaskDataset): name: str, root_dir: str, files: Files, - valid_size=0.2, + valid_minutes=5.0, duration=1.0, sampling_rate=48000, matching_function=None, @@ -184,7 +205,7 @@ class EnhancerDataset(TaskDataset): name=name, root_dir=root_dir, files=files, - valid_size=valid_size, + valid_minutes=valid_minutes, sampling_rate=sampling_rate, duration=duration, matching_function=matching_function, From 415ed8e3d0733bccf1d94b25100dfb03ba4fc4ad Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 18 Oct 2022 15:22:34 +0530 Subject: [PATCH 280/375] normalize input --- enhancer/models/demucs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 95d6a6f..86afb6c 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -133,10 +133,12 @@ class Demucs(Model): num_channels: int = 1, resample: int = 4, sampling_rate=16000, + normalize=True, lr: float = 1e-3, dataset: Optional[EnhancerDataset] = None, loss: Union[str, List] = "mse", metric: Union[str, List] = "mse", + floor=1e-3, ): duration = ( dataset.duration if isinstance(dataset, EnhancerDataset) else None @@ -161,6 +163,8 @@ class Demucs(Model): lstm = merge_dict(self.LSTM_DEFAULTS, lstm) self.save_hyperparameters("encoder_decoder", "lstm", "resample") hidden = encoder_decoder["initial_output_channels"] + self.normalize = normalize + self.floor = floor self.encoder = nn.ModuleList() self.decoder = nn.ModuleList() @@ -204,7 +208,10 @@ class Demucs(Model): raise TypeError( f"Demucs can only process mono channel audio, input has {waveform.size(1)} channels" ) - + if self.normalize: + waveform = waveform.mean(dim=1, keepdim=True) + std = waveform.std(dim=-1, keepdim=True) + waveform = waveform / (self.floor + std) length = waveform.shape[-1] x = F.pad(waveform, (0, self.get_padding_length(length) - length)) if self.hparams.resample > 1: From edb7f020f7ca8fa05d40087db73c2b556e400c77 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 18 Oct 2022 15:23:07 +0530 Subject: [PATCH 281/375] stride waveform --- enhancer/data/dataset.py | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 02a1d3b..28f19a6 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -132,11 +132,14 @@ class TaskDataset(pl.LightningDataModule): for item in data: clean, noisy, total_dur = item.values() if total_dur < self.duration: - continue - num_segments = round(total_dur / self.duration) - for index in range(num_segments): - start_time = index * self.duration - metadata.append(({"clean": clean, "noisy": noisy}, start_time)) + metadata.append(({"clean": clean, "noisy": noisy}, 0.0)) + else: + num_segments = round(total_dur / self.duration) + for index in range(num_segments): + start_time = index * self.duration + metadata.append( + ({"clean": clean, "noisy": noisy}, start_time) + ) return metadata def train_dataloader(self): @@ -195,6 +198,7 @@ class EnhancerDataset(TaskDataset): files: Files, valid_minutes=5.0, duration=1.0, + stride=0.5, sampling_rate=48000, matching_function=None, batch_size=32, @@ -217,6 +221,7 @@ class EnhancerDataset(TaskDataset): self.files = files self.duration = max(1.0, duration) self.audio = Audio(self.sampling_rate, mono=True, return_tensor=True) + self.stride = stride or duration def setup(self, stage: Optional[str] = None): @@ -234,9 +239,22 @@ class EnhancerDataset(TaskDataset): weights=[file["duration"] for file in self.train_data], ) file_duration = file_dict["duration"] - start_time = round(rng.uniform(0, file_duration - self.duration), 2) - data = self.prepare_segment(file_dict, start_time) - yield data + num_segments = self.get_num_segments( + file_duration, self.duration, self.stride + ) + for index in range(0, num_segments): + start_time = index * self.stride + yield self.prepare_segment(file_dict, start_time) + + @staticmethod + def get_num_segments(file_duration, duration, stride): + + if file_duration < duration: + num_segments = 1 + else: + num_segments = math.ceil((file_duration - duration) / stride) + 1 + + return num_segments def val__getitem__(self, idx): return self.prepare_segment(*self._validation[idx]) @@ -273,8 +291,16 @@ class EnhancerDataset(TaskDataset): return {"clean": clean_segment, "noisy": noisy_segment} def train__len__(self): + return math.ceil( - sum([file["duration"] for file in self.train_data]) / self.duration + sum( + [ + self.get_num_segments( + file["duration"], self.duration, self.stride + ) + for file in self.train_data + ] + ) ) def val__len__(self): From e4f13946e83f01172e0a82b2bf52ebc83bbfceec Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 19 Oct 2022 12:38:29 +0530 Subject: [PATCH 282/375] fix demucs output --- enhancer/models/demucs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index 86afb6c..e5fa945 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -212,6 +212,8 @@ class Demucs(Model): waveform = waveform.mean(dim=1, keepdim=True) std = waveform.std(dim=-1, keepdim=True) waveform = waveform / (self.floor + std) + else: + std = 1 length = waveform.shape[-1] x = F.pad(waveform, (0, self.get_padding_length(length) - length)) if self.hparams.resample > 1: @@ -244,7 +246,7 @@ class Demucs(Model): ) out = x[..., :length] - return out + return std * out def get_padding_length(self, input_length): From 2ad49faa677a66d49406fdc108cfe9d1fa46dd29 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 20 Oct 2022 09:49:27 +0530 Subject: [PATCH 283/375] debug iterative dataset --- enhancer/data/dataset.py | 102 ++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 28f19a6..8110a2a 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,9 +1,11 @@ import math import multiprocessing import os +from itertools import chain, cycle from typing import Optional import pytorch_lightning as pl +import torch import torch.nn.functional as F from torch.utils.data import DataLoader, Dataset, IterableDataset @@ -55,6 +57,7 @@ class TaskDataset(pl.LightningDataModule): files: Files, valid_minutes: float = 0.20, duration: float = 1.0, + stride=None, sampling_rate: int = 48000, matching_function=None, batch_size=32, @@ -65,6 +68,7 @@ class TaskDataset(pl.LightningDataModule): self.name = name self.files, self.root_dir = check_files(root_dir, files) self.duration = duration + self.stride = stride or duration self.sampling_rate = sampling_rate self.batch_size = batch_size self.matching_function = matching_function @@ -90,10 +94,11 @@ class TaskDataset(pl.LightningDataModule): self.name, train_clean, train_noisy, self.matching_function ) train_data = fp.prepare_matching_dict() - self.train_data, self.val_data = self.train_valid_split( + train_data, self.val_data = self.train_valid_split( train_data, valid_minutes=self.valid_minutes, random_state=42 ) + self.train_data = self.prepare_traindata(train_data) self._validation = self.prepare_mapstype(self.val_data) test_clean = os.path.join(self.root_dir, self.files.test_clean) @@ -112,7 +117,7 @@ class TaskDataset(pl.LightningDataModule): valid_min_now = 0.0 valid_indices = [] random_indices = list(range(0, len(data))) - rng = create_unique_rng(random_state) + rng = create_unique_rng(random_state, 0) rng.shuffle(random_indices) i = 0 while valid_min_now <= valid_minutes: @@ -126,6 +131,33 @@ class TaskDataset(pl.LightningDataModule): valid_data = [item for i, item in enumerate(data) if i in valid_indices] return train_data, valid_data + def prepare_traindata(self, data): + train_data = [] + for item in data: + samples_metadata = [] + clean, noisy, total_dur = item.values() + num_segments = self.get_num_segments( + total_dur, self.duration, self.stride + ) + for index in range(num_segments): + start = index * self.stride + samples_metadata.append( + ({"clean": clean, "noisy": noisy}, start) + ) + train_data.append(samples_metadata) + print(train_data[:10]) + return train_data + + @staticmethod + def get_num_segments(file_duration, duration, stride): + + if file_duration < duration: + num_segments = 1 + else: + num_segments = math.ceil((file_duration - duration) / stride) + 1 + + return num_segments + def prepare_mapstype(self, data): metadata = [] @@ -142,11 +174,33 @@ class TaskDataset(pl.LightningDataModule): ) return metadata + def train_collatefn(self, batch): + + output = {"noisy": [], "clean": []} + for item in batch: + output["noisy"].append(item["noisy"]) + output["clean"].append(item["clean"]) + + output["clean"] = torch.stack(output["clean"], dim=0) + output["noisy"] = torch.stack(output["noisy"], dim=0) + return output + + def worker_init_fn(self): + worker_info = torch.utils.data.get_worker_info() + dataset = worker_info.dataset + worker_id = worker_info.id + split_size = len(dataset.data) // worker_info.num_workers + dataset.data = dataset.data[ + worker_id * split_size : (worker_id + 1) * split_size + ] + def train_dataloader(self): return DataLoader( TrainDataset(self), - batch_size=self.batch_size, + batch_size=None, num_workers=self.num_workers, + collate_fn=self.train_collatefn, + worker_init_fn=self.worker_init_fn, ) def val_dataloader(self): @@ -227,24 +281,24 @@ class EnhancerDataset(TaskDataset): super().setup(stage=stage) + def random_sample(self, index): + rng = create_unique_rng(self.model.current_epoch, index) + return rng.sample(self.train_data, len(self.train_data)) + def train__iter__(self): + return zip( + *[ + self.get_stream(self.random_sample(i)) + for i in range(self.batch_size) + ] + ) - rng = create_unique_rng(self.model.current_epoch) + def get_stream(self, data): + return chain.from_iterable(map(self.process_data, cycle(data))) - while True: - - file_dict, *_ = rng.choices( - self.train_data, - k=1, - weights=[file["duration"] for file in self.train_data], - ) - file_duration = file_dict["duration"] - num_segments = self.get_num_segments( - file_duration, self.duration, self.stride - ) - for index in range(0, num_segments): - start_time = index * self.stride - yield self.prepare_segment(file_dict, start_time) + def process_data(self, data): + for item in data: + yield self.prepare_segment(*item) @staticmethod def get_num_segments(file_duration, duration, stride): @@ -264,6 +318,7 @@ class EnhancerDataset(TaskDataset): def prepare_segment(self, file_dict: dict, start_time: float): + print(file_dict["clean"].split("/")[-1], "->", start_time) clean_segment = self.audio( file_dict["clean"], offset=start_time, duration=self.duration ) @@ -292,16 +347,7 @@ class EnhancerDataset(TaskDataset): def train__len__(self): - return math.ceil( - sum( - [ - self.get_num_segments( - file["duration"], self.duration, self.stride - ) - for file in self.train_data - ] - ) - ) + return sum([len(item) for item in self.train_data]) def val__len__(self): return len(self._validation) From a6a2e4a4ae2d607f9b168ae0f2eda6885350f209 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 20 Oct 2022 09:50:04 +0530 Subject: [PATCH 284/375] add batch info --- enhancer/utils/random.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py index dd9395a..2feb581 100644 --- a/enhancer/utils/random.py +++ b/enhancer/utils/random.py @@ -4,7 +4,7 @@ import random import torch -def create_unique_rng(epoch: int): +def create_unique_rng(epoch: int, index: int): """create unique random number generator for each (worker_id,epoch) combination""" rng = random.Random() @@ -29,6 +29,7 @@ def create_unique_rng(epoch: int): + local_rank * num_workers + node_rank * num_workers * global_rank + epoch * num_workers * world_size + + index ) rng.seed(seed) From c5824cb34a88d5871a4237f877448e3da0f3badc Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 20 Oct 2022 09:53:06 +0530 Subject: [PATCH 285/375] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9cd222c..cd1b1e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ #local *.ckpt +*_local.yaml cli/train_config/dataset/Vctk_local.yaml .DS_Store outputs/ From f2561d7cf7f6cd1069310ddbdc4e0074da1e9b0d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 20 Oct 2022 09:53:27 +0530 Subject: [PATCH 286/375] config --- enhancer/cli/train_config/config.yaml | 2 +- enhancer/cli/train_config/dataset/Vctk.yaml | 5 +++-- enhancer/cli/train_config/trainer/default.yaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/enhancer/cli/train_config/config.yaml b/enhancer/cli/train_config/config.yaml index c0b2cf6..8d0ab14 100644 --- a/enhancer/cli/train_config/config.yaml +++ b/enhancer/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : WaveUnet + - model : Demucs - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index df50da2..c128404 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -1,9 +1,10 @@ _target_: enhancer.data.dataset.EnhancerDataset name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 -duration : 1.0 +duration : 4.5 +stride : 0.5 sampling_rate: 16000 -batch_size: 128 +batch_size: 16 files: train_clean : clean_trainset_28spk_wav diff --git a/enhancer/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml index dfc020f..01914e4 100644 --- a/enhancer/cli/train_config/trainer/default.yaml +++ b/enhancer/cli/train_config/trainer/default.yaml @@ -9,7 +9,7 @@ benchmark: False check_val_every_n_epoch: 1 detect_anomaly: False deterministic: False -devices: -1 +devices: 1 enable_checkpointing: True enable_model_summary: True enable_progress_bar: True From ba10719520d6fbb906d84ac4a3e7374b3279b893 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 20 Oct 2022 21:03:38 +0530 Subject: [PATCH 287/375] add arg --- enhancer/data/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 8110a2a..06a6f67 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -185,7 +185,7 @@ class TaskDataset(pl.LightningDataModule): output["noisy"] = torch.stack(output["noisy"], dim=0) return output - def worker_init_fn(self): + def worker_init_fn(self, _): worker_info = torch.utils.data.get_worker_info() dataset = worker_info.dataset worker_id = worker_info.id From 178a4523ef06e272128e431d9d8adad1fa1c7592 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 09:48:28 +0530 Subject: [PATCH 288/375] fix worker init fn --- enhancer/data/dataset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 06a6f67..218cd7c 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -189,8 +189,8 @@ class TaskDataset(pl.LightningDataModule): worker_info = torch.utils.data.get_worker_info() dataset = worker_info.dataset worker_id = worker_info.id - split_size = len(dataset.data) // worker_info.num_workers - dataset.data = dataset.data[ + split_size = len(dataset.dataset.train_data) // worker_info.num_workers + dataset.data = dataset.dataset.train_data[ worker_id * split_size : (worker_id + 1) * split_size ] From 0d3bfd341210f2f43d9162e1be68a5e389f1de2c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 11:13:17 +0530 Subject: [PATCH 289/375] debug --- enhancer/cli/train_config/dataset/Vctk.yaml | 3 ++- enhancer/data/dataset.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index c128404..2ea4018 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -4,7 +4,8 @@ root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 4.5 stride : 0.5 sampling_rate: 16000 -batch_size: 16 +batch_size: 4 +valid_minutes : 1 files: train_clean : clean_trainset_28spk_wav diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 218cd7c..3722763 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -145,8 +145,7 @@ class TaskDataset(pl.LightningDataModule): ({"clean": clean, "noisy": noisy}, start) ) train_data.append(samples_metadata) - print(train_data[:10]) - return train_data + return train_data[:25] @staticmethod def get_num_segments(file_duration, duration, stride): @@ -175,12 +174,14 @@ class TaskDataset(pl.LightningDataModule): return metadata def train_collatefn(self, batch): - + names = [] output = {"noisy": [], "clean": []} for item in batch: output["noisy"].append(item["noisy"]) output["clean"].append(item["clean"]) + names.append(item["name"]) + print(names) output["clean"] = torch.stack(output["clean"], dim=0) output["noisy"] = torch.stack(output["noisy"], dim=0) return output @@ -318,7 +319,6 @@ class EnhancerDataset(TaskDataset): def prepare_segment(self, file_dict: dict, start_time: float): - print(file_dict["clean"].split("/")[-1], "->", start_time) clean_segment = self.audio( file_dict["clean"], offset=start_time, duration=self.duration ) @@ -343,7 +343,11 @@ class EnhancerDataset(TaskDataset): ), ), ) - return {"clean": clean_segment, "noisy": noisy_segment} + return { + "clean": clean_segment, + "noisy": noisy_segment, + "name": file_dict["clean"].split("/")[-1] + "->" + start_time, + } def train__len__(self): From 5d7ea582c9664610c7a753500f4ff74a80697dbf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 11:18:24 +0530 Subject: [PATCH 290/375] debug --- enhancer/data/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 3722763..9b512ab 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -346,7 +346,7 @@ class EnhancerDataset(TaskDataset): return { "clean": clean_segment, "noisy": noisy_segment, - "name": file_dict["clean"].split("/")[-1] + "->" + start_time, + "name": file_dict["clean"].split("/")[-1] + "->" + str(start_time), } def train__len__(self): From 9c7a650130aa9ba896846eb35ff05942ecda1395 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 11:37:26 +0530 Subject: [PATCH 291/375] div by batchsize in __len__ --- enhancer/data/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 9b512ab..cc89083 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -351,7 +351,7 @@ class EnhancerDataset(TaskDataset): def train__len__(self): - return sum([len(item) for item in self.train_data]) + return sum([len(item) for item in self.train_data]) // self.batch_size def val__len__(self): return len(self._validation) From 20c12556fff9e577045edecd700db1ef33ffe2a2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 16:25:24 +0530 Subject: [PATCH 292/375] debug --- enhancer/data/dataset.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index cc89083..fb4d04c 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -100,6 +100,9 @@ class TaskDataset(pl.LightningDataModule): self.train_data = self.prepare_traindata(train_data) self._validation = self.prepare_mapstype(self.val_data) + print( + "train_data_size", sum([len(item) for item in self.train_data]) + ) test_clean = os.path.join(self.root_dir, self.files.test_clean) test_noisy = os.path.join(self.root_dir, self.files.test_noisy) From a7fb27bb0f94b9e7674c1a97005b1c8202fe86f9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 17:17:02 +0530 Subject: [PATCH 293/375] debug --- enhancer/data/dataset.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index fb4d04c..a8b3896 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -353,8 +353,11 @@ class EnhancerDataset(TaskDataset): } def train__len__(self): - - return sum([len(item) for item in self.train_data]) // self.batch_size + worker_info = torch.utils.data.get_worker_info() + num_workers = worker_info.num_workers if worker_info else 1 + return sum([len(item) for item in self.train_data]) // ( + self.batch_size * num_workers + ) def val__len__(self): return len(self._validation) From a75f3c32a35e9b8192f0efce4ee38bc7553ba80e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 19:23:59 +0530 Subject: [PATCH 294/375] num_workers --- enhancer/cli/train_config/dataset/Vctk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 2ea4018..0db0e7e 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -6,7 +6,7 @@ stride : 0.5 sampling_rate: 16000 batch_size: 4 valid_minutes : 1 - +num_workers: 0 files: train_clean : clean_trainset_28spk_wav test_clean : clean_testset_wav From cd9ffc1a684fe255b3c8a221145e68de9cb410b7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 23:22:56 +0530 Subject: [PATCH 295/375] fix randomization --- enhancer/data/dataset.py | 18 +++++++++++------- enhancer/utils/random.py | 3 +-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index a8b3896..d4686b5 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,6 +1,7 @@ import math import multiprocessing import os +import random from itertools import chain, cycle from typing import Optional @@ -120,7 +121,7 @@ class TaskDataset(pl.LightningDataModule): valid_min_now = 0.0 valid_indices = [] random_indices = list(range(0, len(data))) - rng = create_unique_rng(random_state, 0) + rng = create_unique_rng(random_state) rng.shuffle(random_indices) i = 0 while valid_min_now <= valid_minutes: @@ -285,14 +286,15 @@ class EnhancerDataset(TaskDataset): super().setup(stage=stage) - def random_sample(self, index): - rng = create_unique_rng(self.model.current_epoch, index) - return rng.sample(self.train_data, len(self.train_data)) + def random_sample(self, train_data): + return random.sample(train_data, len(train_data)) def train__iter__(self): + rng = create_unique_rng(self.model.current_epoch) + train_data = rng.sample(self.train_data, len(self.train_data)) return zip( *[ - self.get_stream(self.random_sample(i)) + self.get_stream(self.random_sample(train_data)) for i in range(self.batch_size) ] ) @@ -353,8 +355,10 @@ class EnhancerDataset(TaskDataset): } def train__len__(self): - worker_info = torch.utils.data.get_worker_info() - num_workers = worker_info.num_workers if worker_info else 1 + if self.num_workers > 1: + num_workers = 2 + else: + num_workers = 1 return sum([len(item) for item in self.train_data]) // ( self.batch_size * num_workers ) diff --git a/enhancer/utils/random.py b/enhancer/utils/random.py index 2feb581..dd9395a 100644 --- a/enhancer/utils/random.py +++ b/enhancer/utils/random.py @@ -4,7 +4,7 @@ import random import torch -def create_unique_rng(epoch: int, index: int): +def create_unique_rng(epoch: int): """create unique random number generator for each (worker_id,epoch) combination""" rng = random.Random() @@ -29,7 +29,6 @@ def create_unique_rng(epoch: int, index: int): + local_rank * num_workers + node_rank * num_workers * global_rank + epoch * num_workers * world_size - + index ) rng.seed(seed) From 8457e1cbe2cda4299bdd5fe56058fab2d07a2247 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 21 Oct 2022 23:23:37 +0530 Subject: [PATCH 296/375] debug num_workers --- enhancer/cli/train_config/dataset/Vctk.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 0db0e7e..0e1f38f 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -6,7 +6,6 @@ stride : 0.5 sampling_rate: 16000 batch_size: 4 valid_minutes : 1 -num_workers: 0 files: train_clean : clean_trainset_28spk_wav test_clean : clean_testset_wav From c4a27686daa6bca8ad905f33ef177f193c45d4e4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 09:57:27 +0530 Subject: [PATCH 297/375] debug --- enhancer/data/dataset.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index d4686b5..1ae48ed 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -77,6 +77,7 @@ class TaskDataset(pl.LightningDataModule): if num_workers is None: num_workers = multiprocessing.cpu_count() // 2 self.num_workers = num_workers + print("num_workers-main", self.num_workers) if valid_minutes > 0.0: self.valid_minutes = valid_minutes else: @@ -184,7 +185,6 @@ class TaskDataset(pl.LightningDataModule): output["noisy"].append(item["noisy"]) output["clean"].append(item["clean"]) names.append(item["name"]) - print(names) output["clean"] = torch.stack(output["clean"], dim=0) output["noisy"] = torch.stack(output["noisy"], dim=0) @@ -359,8 +359,9 @@ class EnhancerDataset(TaskDataset): num_workers = 2 else: num_workers = 1 + print("num_workers", num_workers) return sum([len(item) for item in self.train_data]) // ( - self.batch_size * num_workers + self.batch_size * self.num_workers ) def val__len__(self): From 7fa54fc414f438a1cae888e93a31edb1f604c01e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 10:30:27 +0530 Subject: [PATCH 298/375] debug --- enhancer/data/dataset.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 1ae48ed..05dd287 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -355,14 +355,12 @@ class EnhancerDataset(TaskDataset): } def train__len__(self): - if self.num_workers > 1: - num_workers = 2 + worker_info = torch.utils.data.get_worker_info() + if worker_info is None: + train_data = self.train_data else: - num_workers = 1 - print("num_workers", num_workers) - return sum([len(item) for item in self.train_data]) // ( - self.batch_size * self.num_workers - ) + train_data = worker_info.dataset.data + return sum([len(item) for item in train_data]) // (self.batch_size) def val__len__(self): return len(self._validation) From 6314d210c35f89fe61698d13213a613270628d3e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 11:05:19 +0530 Subject: [PATCH 299/375] debug git commit -m debug ' --- enhancer/data/dataset.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 05dd287..c808c8e 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -360,7 +360,9 @@ class EnhancerDataset(TaskDataset): train_data = self.train_data else: train_data = worker_info.dataset.data - return sum([len(item) for item in train_data]) // (self.batch_size) + len = sum([len(item) for item in train_data]) // (self.batch_size) + print(len) + return len def val__len__(self): return len(self._validation) From 9b155348126d6d63b72488ccb3edc79db92fbfd7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 11:05:39 +0530 Subject: [PATCH 300/375] print len --- enhancer/data/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index c808c8e..c48aded 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -361,7 +361,7 @@ class EnhancerDataset(TaskDataset): else: train_data = worker_info.dataset.data len = sum([len(item) for item in train_data]) // (self.batch_size) - print(len) + print("workers", len) return len def val__len__(self): From 05e40f84b6eefee8831508e90252cd8f9cca7bc2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 11:17:22 +0530 Subject: [PATCH 301/375] replace pesq --- enhancer/loss.py | 10 +++++++--- requirements.txt | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index 5092656..f2be8df 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -3,7 +3,7 @@ import logging import numpy as np import torch import torch.nn as nn -from torchmetrics.audio.pesq import PerceptualEvaluationSpeechQuality +from pesq import pesq from torchmetrics.audio.stoi import ShortTimeObjectiveIntelligibility @@ -122,7 +122,6 @@ class Pesq: self.sr = sr self.name = "pesq" self.mode = mode - self.pesq = PerceptualEvaluationSpeechQuality(fs=sr, mode=mode) def __call__(self, prediction: torch.Tensor, target: torch.Tensor): @@ -130,7 +129,12 @@ class Pesq: for pred, target_ in zip(prediction, target): try: pesq_values.append( - self.pesq(pred.squeeze(), target_.squeeze()).item() + pesq( + self.sr, + target_.squeeze().detach().numpy(), + pred.squeeze().detach().numpy(), + self.mode, + ) ) except Exception as e: logging.warning(f"{e} error occured while calculating PESQ") diff --git a/requirements.txt b/requirements.txt index 95f145d..fa5e41c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ joblib>=1.2.0 librosa>=0.9.2 mlflow>=1.29.0 numpy>=1.23.3 -pesq==0.0.4 +git+https://github.com/ludlows/python-pesq#egg=pesq protobuf>=3.19.6 pystoi==0.3.3 pytest-lazy-fixture>=0.6.3 From 5f1ed8c725931d0a59df7f0eb163af5db93665cf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 11:17:37 +0530 Subject: [PATCH 302/375] iterable dataset --- enhancer/data/dataset.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index c48aded..80055b6 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -179,13 +179,11 @@ class TaskDataset(pl.LightningDataModule): return metadata def train_collatefn(self, batch): - names = [] output = {"noisy": [], "clean": []} for item in batch: output["noisy"].append(item["noisy"]) output["clean"].append(item["clean"]) - names.append(item["name"]) - print(names) + output["clean"] = torch.stack(output["clean"], dim=0) output["noisy"] = torch.stack(output["noisy"], dim=0) return output @@ -355,14 +353,7 @@ class EnhancerDataset(TaskDataset): } def train__len__(self): - worker_info = torch.utils.data.get_worker_info() - if worker_info is None: - train_data = self.train_data - else: - train_data = worker_info.dataset.data - len = sum([len(item) for item in train_data]) // (self.batch_size) - print("workers", len) - return len + return sum([len(item) for item in self.train_data]) // (self.batch_size) def val__len__(self): return len(self._validation) From 9f658424a6ae23ea9b6f0048f9d6323e92c141d7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 11:18:32 +0530 Subject: [PATCH 303/375] rmv slicing --- enhancer/data/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 80055b6..fad1e92 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -150,7 +150,7 @@ class TaskDataset(pl.LightningDataModule): ({"clean": clean, "noisy": noisy}, start) ) train_data.append(samples_metadata) - return train_data[:25] + return train_data @staticmethod def get_num_segments(file_duration, duration, stride): From 6eb905c1bb67590e62ff2dc1598ee3a1257aa06c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 12:00:18 +0530 Subject: [PATCH 304/375] rmv print statements --- enhancer/data/dataset.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index fad1e92..7f7ae67 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -77,7 +77,6 @@ class TaskDataset(pl.LightningDataModule): if num_workers is None: num_workers = multiprocessing.cpu_count() // 2 self.num_workers = num_workers - print("num_workers-main", self.num_workers) if valid_minutes > 0.0: self.valid_minutes = valid_minutes else: @@ -102,9 +101,6 @@ class TaskDataset(pl.LightningDataModule): self.train_data = self.prepare_traindata(train_data) self._validation = self.prepare_mapstype(self.val_data) - print( - "train_data_size", sum([len(item) for item in self.train_data]) - ) test_clean = os.path.join(self.root_dir, self.files.test_clean) test_noisy = os.path.join(self.root_dir, self.files.test_noisy) @@ -349,7 +345,6 @@ class EnhancerDataset(TaskDataset): return { "clean": clean_segment, "noisy": noisy_segment, - "name": file_dict["clean"].split("/")[-1] + "->" + str(start_time), } def train__len__(self): From 02192e556729f38ecfff00c1415bb9d251b3b96b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 22 Oct 2022 12:00:30 +0530 Subject: [PATCH 305/375] to cpu --- enhancer/loss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index f2be8df..2150699 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -131,8 +131,8 @@ class Pesq: pesq_values.append( pesq( self.sr, - target_.squeeze().detach().numpy(), - pred.squeeze().detach().numpy(), + target_.squeeze().detach().cpu().numpy(), + pred.squeeze().detach().cpu().numpy(), self.mode, ) ) From 40e2d6e0b02553c5f9eff361959a08f068f37ccf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 12:32:58 +0530 Subject: [PATCH 306/375] change to mapstyle --- enhancer/data/dataset.py | 86 ++++++++++++---------------------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 7f7ae67..08b402f 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,14 +1,12 @@ import math import multiprocessing import os -import random -from itertools import chain, cycle from typing import Optional import pytorch_lightning as pl import torch import torch.nn.functional as F -from torch.utils.data import DataLoader, Dataset, IterableDataset +from torch.utils.data import DataLoader, Dataset from enhancer.data.fileprocessor import Fileprocessor from enhancer.utils import check_files @@ -16,13 +14,15 @@ from enhancer.utils.config import Files from enhancer.utils.io import Audio from enhancer.utils.random import create_unique_rng +LARGE_NUM = 2147483647 -class TrainDataset(IterableDataset): + +class TrainDataset(Dataset): def __init__(self, dataset): self.dataset = dataset - def __iter__(self): - return self.dataset.train__iter__() + def __getitem__(self, idx): + return self.dataset.train__getitem__(idx) def __len__(self): return self.dataset.train__len__() @@ -135,16 +135,11 @@ class TaskDataset(pl.LightningDataModule): def prepare_traindata(self, data): train_data = [] for item in data: - samples_metadata = [] clean, noisy, total_dur = item.values() num_segments = self.get_num_segments( total_dur, self.duration, self.stride ) - for index in range(num_segments): - start = index * self.stride - samples_metadata.append( - ({"clean": clean, "noisy": noisy}, start) - ) + samples_metadata = ({"clean": clean, "noisy": noisy}, num_segments) train_data.append(samples_metadata) return train_data @@ -175,31 +170,20 @@ class TaskDataset(pl.LightningDataModule): return metadata def train_collatefn(self, batch): - output = {"noisy": [], "clean": []} - for item in batch: - output["noisy"].append(item["noisy"]) - output["clean"].append(item["clean"]) + raise NotImplementedError("Not implemented") - output["clean"] = torch.stack(output["clean"], dim=0) - output["noisy"] = torch.stack(output["noisy"], dim=0) - return output - - def worker_init_fn(self, _): - worker_info = torch.utils.data.get_worker_info() - dataset = worker_info.dataset - worker_id = worker_info.id - split_size = len(dataset.dataset.train_data) // worker_info.num_workers - dataset.data = dataset.dataset.train_data[ - worker_id * split_size : (worker_id + 1) * split_size - ] + @property + def generator(self): + generator = torch.Generator() + generator = generator.manual_seed(self.model.current_epoch + LARGE_NUM) + return generator def train_dataloader(self): return DataLoader( TrainDataset(self), - batch_size=None, + batch_size=self.batch_size, num_workers=self.num_workers, - collate_fn=self.train_collatefn, - worker_init_fn=self.worker_init_fn, + generator=self.generator, ) def val_dataloader(self): @@ -280,35 +264,16 @@ class EnhancerDataset(TaskDataset): super().setup(stage=stage) - def random_sample(self, train_data): - return random.sample(train_data, len(train_data)) + def train__getitem__(self, idx): - def train__iter__(self): - rng = create_unique_rng(self.model.current_epoch) - train_data = rng.sample(self.train_data, len(self.train_data)) - return zip( - *[ - self.get_stream(self.random_sample(train_data)) - for i in range(self.batch_size) - ] - ) - - def get_stream(self, data): - return chain.from_iterable(map(self.process_data, cycle(data))) - - def process_data(self, data): - for item in data: - yield self.prepare_segment(*item) - - @staticmethod - def get_num_segments(file_duration, duration, stride): - - if file_duration < duration: - num_segments = 1 - else: - num_segments = math.ceil((file_duration - duration) / stride) + 1 - - return num_segments + for filedict, num_samples in self.train_data: + if idx >= num_samples: + idx -= num_samples + continue + start = 0 + if self.duration is not None: + start = idx * self.stride + return self.prepare_segment(filedict, start) def val__getitem__(self, idx): return self.prepare_segment(*self._validation[idx]) @@ -348,7 +313,8 @@ class EnhancerDataset(TaskDataset): } def train__len__(self): - return sum([len(item) for item in self.train_data]) // (self.batch_size) + _, num_examples = list(zip(*self.train_data)) + return sum(num_examples) def val__len__(self): return len(self._validation) From ea5c78798add582922f36fd6bbc38194776b13f8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 12:33:38 +0530 Subject: [PATCH 307/375] model assigment' --- enhancer/models/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index dc5219d..3b60b85 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -113,6 +113,8 @@ class Model(pl.LightningModule): if stage == "fit": torch.cuda.empty_cache() self.dataset.setup(stage) + self.dataset.model = self + print( "Total train duration", self.dataset.train_dataloader().dataset.__len__() @@ -134,7 +136,6 @@ class Model(pl.LightningModule): / 60, "minutes", ) - self.dataset.model = self def train_dataloader(self): return self.dataset.train_dataloader() From fc41de1530b926785624d40dbc812042b4a7fd75 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 12:36:43 +0530 Subject: [PATCH 308/375] VCTK + DEMUCS --- enhancer/cli/train_config/dataset/Vctk.yaml | 4 ++-- enhancer/cli/train_config/trainer/default.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 0e1f38f..a5a5b5f 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -4,8 +4,8 @@ root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 4.5 stride : 0.5 sampling_rate: 16000 -batch_size: 4 -valid_minutes : 1 +batch_size: 128 +valid_minutes : 15 files: train_clean : clean_trainset_28spk_wav test_clean : clean_testset_wav diff --git a/enhancer/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml index 01914e4..958c418 100644 --- a/enhancer/cli/train_config/trainer/default.yaml +++ b/enhancer/cli/train_config/trainer/default.yaml @@ -1,5 +1,5 @@ _target_: pytorch_lightning.Trainer -accelerator: auto +accelerator: gpu accumulate_grad_batches: 1 amp_backend: native auto_lr_find: True @@ -9,7 +9,7 @@ benchmark: False check_val_every_n_epoch: 1 detect_anomaly: False deterministic: False -devices: 1 +devices: 2 enable_checkpointing: True enable_model_summary: True enable_progress_bar: True @@ -23,7 +23,7 @@ limit_test_batches: 1.0 limit_train_batches: 1.0 limit_val_batches: 1.0 log_every_n_steps: 50 -max_epochs: 3 +max_epochs: 200 max_steps: -1 max_time: null min_epochs: 1 @@ -38,7 +38,7 @@ precision: 32 profiler: null reload_dataloaders_every_n_epochs: 0 replace_sampler_ddp: True -strategy: null +strategy: ddp sync_batchnorm: False tpu_cores: null track_grad_norm: -1 From 3128fed71e68e13c7624fe661778cfe45b5fbe4a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 12:38:20 +0530 Subject: [PATCH 309/375] params --- enhancer/cli/train_config/hyperparameters/default.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/enhancer/cli/train_config/hyperparameters/default.yaml b/enhancer/cli/train_config/hyperparameters/default.yaml index 7e4cda3..4d8b391 100644 --- a/enhancer/cli/train_config/hyperparameters/default.yaml +++ b/enhancer/cli/train_config/hyperparameters/default.yaml @@ -1,7 +1,7 @@ -loss : mse -metric : mae -lr : 0.0001 -ReduceLr_patience : 5 -ReduceLr_factor : 0.1 +loss : mae +metric : [stoi,pesq,si-sdr] +lr : 0.0003 +ReduceLr_patience : 10 +ReduceLr_factor : 0.5 min_lr : 0.000001 EarlyStopping_factor : 10 From 460366bd8b4e5e3f0e90176d542635680edc1281 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 17:15:17 +0530 Subject: [PATCH 310/375] min conf acc ablation study --- enhancer/cli/train_config/model/Demucs.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/enhancer/cli/train_config/model/Demucs.yaml b/enhancer/cli/train_config/model/Demucs.yaml index 3c565ee..513e603 100644 --- a/enhancer/cli/train_config/model/Demucs.yaml +++ b/enhancer/cli/train_config/model/Demucs.yaml @@ -1,11 +1,11 @@ _target_: enhancer.models.demucs.Demucs num_channels: 1 -resample: 2 +resample: 4 sampling_rate : 16000 encoder_decoder: - depth: 5 - initial_output_channels: 32 + depth: 4 + initial_output_channels: 64 kernel_size: 8 stride: 4 growth_factor: 2 From 97b4a61d9c96bf7b2568312a6f55e5cf5ffff14c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 19:07:53 +0530 Subject: [PATCH 311/375] half BS --- enhancer/cli/train_config/dataset/Vctk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index a5a5b5f..0acbb36 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -4,7 +4,7 @@ root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 4.5 stride : 0.5 sampling_rate: 16000 -batch_size: 128 +batch_size: 64 valid_minutes : 15 files: train_clean : clean_trainset_28spk_wav From 101ee563cb553db16c145a1d36ea81d1f76b63c5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sun, 23 Oct 2022 19:30:46 +0530 Subject: [PATCH 312/375] decrease precision --- enhancer/cli/train_config/trainer/default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml index 958c418..ca866fb 100644 --- a/enhancer/cli/train_config/trainer/default.yaml +++ b/enhancer/cli/train_config/trainer/default.yaml @@ -34,7 +34,7 @@ num_nodes: 1 num_processes: 1 num_sanity_val_steps: 2 overfit_batches: 0.0 -precision: 32 +precision: 16 profiler: null reload_dataloaders_every_n_epochs: 0 replace_sampler_ddp: True From 75ebef24621560d4a6b8b289c5b886c373326342 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 24 Oct 2022 10:01:54 +0530 Subject: [PATCH 313/375] Waveunet w/o stride --- enhancer/cli/train_config/config.yaml | 2 +- enhancer/cli/train_config/dataset/Vctk.yaml | 5 ++--- enhancer/cli/train_config/hyperparameters/default.yaml | 4 ++-- enhancer/cli/train_config/model/WaveUnet.yaml | 2 +- enhancer/cli/train_config/trainer/default.yaml | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/enhancer/cli/train_config/config.yaml b/enhancer/cli/train_config/config.yaml index 8d0ab14..c0b2cf6 100644 --- a/enhancer/cli/train_config/config.yaml +++ b/enhancer/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : Demucs + - model : WaveUnet - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 0acbb36..2f22146 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -1,10 +1,9 @@ _target_: enhancer.data.dataset.EnhancerDataset name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 -duration : 4.5 -stride : 0.5 +duration : 2 sampling_rate: 16000 -batch_size: 64 +batch_size: 128 valid_minutes : 15 files: train_clean : clean_trainset_28spk_wav diff --git a/enhancer/cli/train_config/hyperparameters/default.yaml b/enhancer/cli/train_config/hyperparameters/default.yaml index 4d8b391..0291c8e 100644 --- a/enhancer/cli/train_config/hyperparameters/default.yaml +++ b/enhancer/cli/train_config/hyperparameters/default.yaml @@ -1,6 +1,6 @@ -loss : mae +loss : mse metric : [stoi,pesq,si-sdr] -lr : 0.0003 +lr : 0.001 ReduceLr_patience : 10 ReduceLr_factor : 0.5 min_lr : 0.000001 diff --git a/enhancer/cli/train_config/model/WaveUnet.yaml b/enhancer/cli/train_config/model/WaveUnet.yaml index d641bcd..29d48c7 100644 --- a/enhancer/cli/train_config/model/WaveUnet.yaml +++ b/enhancer/cli/train_config/model/WaveUnet.yaml @@ -1,5 +1,5 @@ _target_: enhancer.models.waveunet.WaveUnet num_channels : 1 -depth : 12 +depth : 9 initial_output_channels: 24 sampling_rate : 16000 diff --git a/enhancer/cli/train_config/trainer/default.yaml b/enhancer/cli/train_config/trainer/default.yaml index ca866fb..958c418 100644 --- a/enhancer/cli/train_config/trainer/default.yaml +++ b/enhancer/cli/train_config/trainer/default.yaml @@ -34,7 +34,7 @@ num_nodes: 1 num_processes: 1 num_sanity_val_steps: 2 overfit_batches: 0.0 -precision: 16 +precision: 32 profiler: null reload_dataloaders_every_n_epochs: 0 replace_sampler_ddp: True From 5dc5fd8f901e916aabcaa16ba173a353a5cd582e Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 24 Oct 2022 21:15:25 +0530 Subject: [PATCH 314/375] default stride None --- enhancer/data/dataset.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 08b402f..ab2f1ce 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -14,6 +14,9 @@ from enhancer.utils.config import Files from enhancer.utils.io import Audio from enhancer.utils.random import create_unique_rng +# from torch_audiomentations import Compose + + LARGE_NUM = 2147483647 @@ -63,6 +66,7 @@ class TaskDataset(pl.LightningDataModule): matching_function=None, batch_size=32, num_workers: Optional[int] = None, + # augmentations: Optional[Compose] = None, ): super().__init__() @@ -82,6 +86,8 @@ class TaskDataset(pl.LightningDataModule): else: raise ValueError("valid_minutes must be greater than 0") + # self.augmentations = augmentations + def setup(self, stage: Optional[str] = None): """ prepare train/validation/test data splits @@ -161,7 +167,9 @@ class TaskDataset(pl.LightningDataModule): if total_dur < self.duration: metadata.append(({"clean": clean, "noisy": noisy}, 0.0)) else: - num_segments = round(total_dur / self.duration) + num_segments = self.get_num_segments( + total_dur, self.duration, self.duration + ) for index in range(num_segments): start_time = index * self.duration metadata.append( @@ -175,8 +183,11 @@ class TaskDataset(pl.LightningDataModule): @property def generator(self): generator = torch.Generator() - generator = generator.manual_seed(self.model.current_epoch + LARGE_NUM) - return generator + if hasattr(self, "model"): + seed = self.model.current_epoch + LARGE_NUM + else: + seed = LARGE_NUM + return generator.manual_seed(seed) def train_dataloader(self): return DataLoader( @@ -235,11 +246,12 @@ class EnhancerDataset(TaskDataset): files: Files, valid_minutes=5.0, duration=1.0, - stride=0.5, + stride=None, sampling_rate=48000, matching_function=None, batch_size=32, num_workers: Optional[int] = None, + # augmentations: Optional[Compose] = None, ): super().__init__( @@ -252,6 +264,7 @@ class EnhancerDataset(TaskDataset): matching_function=matching_function, batch_size=batch_size, num_workers=num_workers, + # augmentations=augmentations, ) self.sampling_rate = sampling_rate From 542ab23d8a9d63ddddefa11e94634f641a4ecef6 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 24 Oct 2022 21:50:30 +0530 Subject: [PATCH 315/375] add torch-augmentations --- enhancer/data/dataset.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index ab2f1ce..1e0ec04 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -7,6 +7,7 @@ import pytorch_lightning as pl import torch import torch.nn.functional as F from torch.utils.data import DataLoader, Dataset +from torch_audiomentations import Compose from enhancer.data.fileprocessor import Fileprocessor from enhancer.utils import check_files @@ -14,9 +15,6 @@ from enhancer.utils.config import Files from enhancer.utils.io import Audio from enhancer.utils.random import create_unique_rng -# from torch_audiomentations import Compose - - LARGE_NUM = 2147483647 @@ -66,7 +64,7 @@ class TaskDataset(pl.LightningDataModule): matching_function=None, batch_size=32, num_workers: Optional[int] = None, - # augmentations: Optional[Compose] = None, + augmentations: Optional[Compose] = None, ): super().__init__() @@ -86,7 +84,7 @@ class TaskDataset(pl.LightningDataModule): else: raise ValueError("valid_minutes must be greater than 0") - # self.augmentations = augmentations + self.augmentations = augmentations def setup(self, stage: Optional[str] = None): """ @@ -178,7 +176,25 @@ class TaskDataset(pl.LightningDataModule): return metadata def train_collatefn(self, batch): - raise NotImplementedError("Not implemented") + + output = {"clean": [], "noisy": []} + for item in batch: + output["clean"].append(item["clean"]) + output["noisy"].append(item["noisy"]) + + output["clean"] = torch.stack(output["clean"], dim=0) + output["noisy"] = torch.stack(output["noisy"], dim=0) + + if self.augmentations is not None: + output["clean"] = self.augmentations( + output["clean"], sample_rate=self.sampling_rate + ) + self.augmentations.freeze_parameters() + output["noisy"] = self.augmentations( + output["noisy"], sample_rate=self.sampling_rate + ) + + return output @property def generator(self): @@ -251,7 +267,7 @@ class EnhancerDataset(TaskDataset): matching_function=None, batch_size=32, num_workers: Optional[int] = None, - # augmentations: Optional[Compose] = None, + augmentations: Optional[Compose] = None, ): super().__init__( @@ -264,7 +280,7 @@ class EnhancerDataset(TaskDataset): matching_function=matching_function, batch_size=batch_size, num_workers=num_workers, - # augmentations=augmentations, + augmentations=augmentations, ) self.sampling_rate = sampling_rate From 03d0dc57fc2c3617db0e21a1a43bcbed1cb74029 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 24 Oct 2022 22:13:19 +0530 Subject: [PATCH 316/375] add torch audiomentations --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index fa5e41c..cf8992d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,6 @@ scikit-learn>=1.1.2 scipy>=1.9.1 soundfile>=0.11.0 torch>=1.12.1 +torch-audiomentations==0.11.0 torchaudio>=0.12.1 tqdm>=4.64.1 From cdffe5c4852e51951ba9a775f9eb76c72cf61e21 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 25 Oct 2022 10:57:07 +0530 Subject: [PATCH 317/375] DEMUCS w/o stride --- enhancer/cli/train_config/dataset/Vctk.yaml | 4 ++-- enhancer/cli/train_config/hyperparameters/default.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 2f22146..3f8def6 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -1,9 +1,9 @@ _target_: enhancer.data.dataset.EnhancerDataset name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 -duration : 2 +duration : 4.5 sampling_rate: 16000 -batch_size: 128 +batch_size: 32 valid_minutes : 15 files: train_clean : clean_trainset_28spk_wav diff --git a/enhancer/cli/train_config/hyperparameters/default.yaml b/enhancer/cli/train_config/hyperparameters/default.yaml index 0291c8e..b6bba46 100644 --- a/enhancer/cli/train_config/hyperparameters/default.yaml +++ b/enhancer/cli/train_config/hyperparameters/default.yaml @@ -1,7 +1,7 @@ loss : mse metric : [stoi,pesq,si-sdr] -lr : 0.001 -ReduceLr_patience : 10 -ReduceLr_factor : 0.5 +lr : 0.0001 +ReduceLr_patience : 5 +ReduceLr_factor : 0.2 min_lr : 0.000001 EarlyStopping_factor : 10 From d1bafb3dc637aa51493656f677a15434af17b742 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 25 Oct 2022 12:43:54 +0530 Subject: [PATCH 318/375] add augmentations --- enhancer/cli/train.py | 9 ++++++++- enhancer/data/dataset.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 08f4d3e..6d5f182 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -7,6 +7,7 @@ from omegaconf import DictConfig, OmegaConf from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint from pytorch_lightning.loggers import MLFlowLogger from torch.optim.lr_scheduler import ReduceLROnPlateau +from torch_audiomentations import BandPassFilter, Compose, Shift os.environ["HYDRA_FULL_ERROR"] = "1" JOB_ID = os.environ.get("SLURM_JOBID", "0") @@ -25,8 +26,14 @@ def main(config: DictConfig): ) parameters = config.hyperparameters + apply_augmentations = Compose( + [ + Shift(min_shift=0.0, max_shift=1.0, shift_unit="seconds", p=0.5), + BandPassFilter(p=0.5), + ] + ) - dataset = instantiate(config.dataset) + dataset = instantiate(config.dataset, augmentations=apply_augmentations) model = instantiate( config.model, dataset=dataset, diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 1e0ec04..f71d612 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -211,6 +211,7 @@ class TaskDataset(pl.LightningDataModule): batch_size=self.batch_size, num_workers=self.num_workers, generator=self.generator, + collate_fn=self.train_collatefn, ) def val_dataloader(self): From b070613b647dfe6637fb52230e8cc14948a36d6b Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 25 Oct 2022 12:48:37 +0530 Subject: [PATCH 319/375] config" --- enhancer/cli/train_config/config.yaml | 2 +- enhancer/cli/train_config/dataset/Vctk.yaml | 1 + enhancer/cli/train_config/mlflow/experiment.yaml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/enhancer/cli/train_config/config.yaml b/enhancer/cli/train_config/config.yaml index c0b2cf6..8d0ab14 100644 --- a/enhancer/cli/train_config/config.yaml +++ b/enhancer/cli/train_config/config.yaml @@ -1,5 +1,5 @@ defaults: - - model : WaveUnet + - model : Demucs - dataset : Vctk - optimizer : Adam - hyperparameters : default diff --git a/enhancer/cli/train_config/dataset/Vctk.yaml b/enhancer/cli/train_config/dataset/Vctk.yaml index 3f8def6..c33d29a 100644 --- a/enhancer/cli/train_config/dataset/Vctk.yaml +++ b/enhancer/cli/train_config/dataset/Vctk.yaml @@ -2,6 +2,7 @@ _target_: enhancer.data.dataset.EnhancerDataset name : vctk root_dir : /scratch/c.sistc3/DS_10283_2791 duration : 4.5 +stride : 2 sampling_rate: 16000 batch_size: 32 valid_minutes : 15 diff --git a/enhancer/cli/train_config/mlflow/experiment.yaml b/enhancer/cli/train_config/mlflow/experiment.yaml index e8893f6..d597333 100644 --- a/enhancer/cli/train_config/mlflow/experiment.yaml +++ b/enhancer/cli/train_config/mlflow/experiment.yaml @@ -1,2 +1,2 @@ experiment_name : shahules/enhancer -run_name : baseline +run_name : Demucs + Vtck with stride + augmentations From 4acad6ede8c27fb4368940e771618ebdf297504d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 25 Oct 2022 15:10:13 +0530 Subject: [PATCH 320/375] fix augmentation --- enhancer/data/dataset.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index f71d612..34ecb8f 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -186,12 +186,14 @@ class TaskDataset(pl.LightningDataModule): output["noisy"] = torch.stack(output["noisy"], dim=0) if self.augmentations is not None: + noise = output["noisy"] - output["clean"] output["clean"] = self.augmentations( output["clean"], sample_rate=self.sampling_rate ) self.augmentations.freeze_parameters() - output["noisy"] = self.augmentations( - output["noisy"], sample_rate=self.sampling_rate + output["noisy"] = ( + self.augmentations(noise, sample_rate=self.sampling_rate) + + output["clean"] ) return output From 58de41598e90d6f62fc62bbe04f4a24636453031 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 25 Oct 2022 15:10:36 +0530 Subject: [PATCH 321/375] change matrix --- enhancer/cli/train_config/hyperparameters/default.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enhancer/cli/train_config/hyperparameters/default.yaml b/enhancer/cli/train_config/hyperparameters/default.yaml index b6bba46..1782ea9 100644 --- a/enhancer/cli/train_config/hyperparameters/default.yaml +++ b/enhancer/cli/train_config/hyperparameters/default.yaml @@ -1,6 +1,6 @@ -loss : mse +loss : mae metric : [stoi,pesq,si-sdr] -lr : 0.0001 +lr : 0.0003 ReduceLr_patience : 5 ReduceLr_factor : 0.2 min_lr : 0.000001 From 485a74fc4e35ddab68c15333d19c2b0dab56ba70 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 09:36:28 +0530 Subject: [PATCH 322/375] convt stft --- enhancer/utils/transforms.py | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 enhancer/utils/transforms.py diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py new file mode 100644 index 0000000..bf481e8 --- /dev/null +++ b/enhancer/utils/transforms.py @@ -0,0 +1,63 @@ +from typing import Optional + +import numpy as np +import torch +from scipy.signal import get_window +from torch import nn + + +class ConvFFT(nn.Module): + def __init__( + self, + window_len: int, + nfft: Optional[int] = None, + window: str = "hamming", + ): + self.window_len = window_len + self.nfft = nfft if nfft else np.int(2 ** np.ceil(np.log2(window_len))) + self.window = get_window(window, window_len) + + @property + def init_kernel(self): + + fourier_basis = np.fft.rfft(np.eye(self.nfft))[: self.window_len] + real, imag = np.real(fourier_basis), np.imag(fourier_basis) + kernel = np.concatenate([real, imag], 1).T + kernel *= self.window + return torch.from_numpy(kernel) + + +class ConvSTFT(ConvFFT): + def __init__( + self, + window_len: int, + hop_size: Optional[int] = None, + nfft: Optional[int] = None, + window: str = "hamming", + ): + super(self, ConvSTFT).__init__( + window_len=window_len, nfft=nfft, window=window + ) + self.hop_size = hop_size if hop_size else window_len // 2 + self.register_buffer("weight", self.init_kernel) + + def forward(self, input): + pass + + +class ConviSTFT(ConvFFT): + def __init__( + self, + window_len: int, + hop_size: Optional[int] = None, + nfft: Optional[int] = None, + window: str = "hamming", + ): + super(self, ConvSTFT).__init__( + window_len=window_len, nfft=nfft, window=window + ) + self.hop_size = hop_size if hop_size else window_len // 2 + self.register_buffer("weight", self.init_kernel) + + def forward(self, input): + pass From 23da02d47d890cba6a36f02720fb0ce185b7f203 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 09:36:55 +0530 Subject: [PATCH 323/375] dccrn --- enhancer/models/complexnn/conv.py | 0 enhancer/models/complexnn/rnn.py | 0 enhancer/models/dccrn.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 enhancer/models/complexnn/conv.py create mode 100644 enhancer/models/complexnn/rnn.py create mode 100644 enhancer/models/dccrn.py diff --git a/enhancer/models/complexnn/conv.py b/enhancer/models/complexnn/conv.py new file mode 100644 index 0000000..e69de29 diff --git a/enhancer/models/complexnn/rnn.py b/enhancer/models/complexnn/rnn.py new file mode 100644 index 0000000..e69de29 diff --git a/enhancer/models/dccrn.py b/enhancer/models/dccrn.py new file mode 100644 index 0000000..e69de29 From 04782ba6e931ef8a87bc9197bccc0859c6fe9774 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 10:26:27 +0530 Subject: [PATCH 324/375] fix optimizer scheduler --- enhancer/cli/train.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 6d5f182..131db4f 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -4,10 +4,14 @@ from types import MethodType import hydra from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf -from pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint +from pytorch_lightning.callbacks import ( + EarlyStopping, + LearningRateMonitor, + ModelCheckpoint, +) from pytorch_lightning.loggers import MLFlowLogger from torch.optim.lr_scheduler import ReduceLROnPlateau -from torch_audiomentations import BandPassFilter, Compose, Shift +from torch_audiomentations import Compose, Shift os.environ["HYDRA_FULL_ERROR"] = "1" JOB_ID = os.environ.get("SLURM_JOBID", "0") @@ -29,7 +33,6 @@ def main(config: DictConfig): apply_augmentations = Compose( [ Shift(min_shift=0.0, max_shift=1.0, shift_unit="seconds", p=0.5), - BandPassFilter(p=0.5), ] ) @@ -52,6 +55,8 @@ def main(config: DictConfig): every_n_epochs=1, ) callbacks.append(checkpoint) + callbacks.append(LearningRateMonitor(logging_interval="epoch")) + if parameters.get("Early_stop", False): early_stopping = EarlyStopping( monitor="val_loss", @@ -63,11 +68,11 @@ def main(config: DictConfig): ) callbacks.append(early_stopping) - def configure_optimizer(self): + def configure_optimizers(self): optimizer = instantiate( config.optimizer, lr=parameters.get("lr"), - parameters=self.parameters(), + params=self.parameters(), ) scheduler = ReduceLROnPlateau( optimizer=optimizer, @@ -77,9 +82,13 @@ def main(config: DictConfig): min_lr=parameters.get("min_lr", 1e-6), patience=parameters.get("ReduceLr_patience", 3), ) - return {"optimizer": optimizer, "lr_scheduler": scheduler} + return { + "optimizer": optimizer, + "lr_scheduler": scheduler, + "monitor": f'valid_{parameters.get("ReduceLr_monitor", "loss")}', + } - model.configure_parameters = MethodType(configure_optimizer, model) + model.configure_optimizers = MethodType(configure_optimizers, model) trainer = instantiate(config.trainer, logger=logger, callbacks=callbacks) trainer.fit(model) From 24a06ba9be6ef5d85d521587d66a4985883ba957 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 10:27:23 +0530 Subject: [PATCH 325/375] rename loss --- enhancer/loss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index 2150699..fc8afae 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -66,7 +66,7 @@ class Si_SDR: "Invalid reduction, valid options are sum, mean, None" ) self.higher_better = False - self.name = "Si-SDR" + self.name = "si-sdr" def __call__(self, prediction: torch.Tensor, target: torch.Tensor): From f07c8741ba650d67f2c1f1dc01c449ff9913c49a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 11:59:58 +0530 Subject: [PATCH 326/375] fix resampling --- enhancer/utils/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/utils/io.py b/enhancer/utils/io.py index 9e9ce32..d151ef8 100644 --- a/enhancer/utils/io.py +++ b/enhancer/utils/io.py @@ -70,7 +70,7 @@ class Audio: if sampling_rate: audio = self.__class__.resample_audio( - audio, self.sampling_rate, sampling_rate + audio, sampling_rate, self.sampling_rate ) if self.return_tensor: return torch.tensor(audio) From ee40259a8df9cf4467a14b7b16ba9ee742c660cd Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 12:00:57 +0530 Subject: [PATCH 327/375] fix iterator --- enhancer/data/dataset.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 34ecb8f..f05fd6b 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -302,10 +302,11 @@ class EnhancerDataset(TaskDataset): if idx >= num_samples: idx -= num_samples continue - start = 0 - if self.duration is not None: - start = idx * self.stride - return self.prepare_segment(filedict, start) + else: + start = 0 + if self.duration is not None: + start = idx * self.stride + return self.prepare_segment(filedict, start) def val__getitem__(self, idx): return self.prepare_segment(*self._validation[idx]) From 1edc10e9f590a40ac54a35cba5b72dbdc87c2860 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 12:01:19 +0530 Subject: [PATCH 328/375] time shift --- enhancer/cli/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 131db4f..5562cfd 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -32,7 +32,7 @@ def main(config: DictConfig): parameters = config.hyperparameters apply_augmentations = Compose( [ - Shift(min_shift=0.0, max_shift=1.0, shift_unit="seconds", p=0.5), + Shift(min_shift=0.5, max_shift=1.0, shift_unit="seconds", p=0.5), ] ) From c51dea68859f20458801b2e4c8fd0a73f690175a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 21:46:19 +0530 Subject: [PATCH 329/375] revert to torchmetric pesq --- enhancer/loss.py | 14 +++++--------- requirements.txt | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index fc8afae..32b30cf 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -3,7 +3,7 @@ import logging import numpy as np import torch import torch.nn as nn -from pesq import pesq +from torchmetrics.audio.pesq import PerceptualEvaluationSpeechQuality from torchmetrics.audio.stoi import ShortTimeObjectiveIntelligibility @@ -122,20 +122,16 @@ class Pesq: self.sr = sr self.name = "pesq" self.mode = mode + self.pesq = PerceptualEvaluationSpeechQuality( + fs=self.sr, mode=self.mode + ) def __call__(self, prediction: torch.Tensor, target: torch.Tensor): pesq_values = [] for pred, target_ in zip(prediction, target): try: - pesq_values.append( - pesq( - self.sr, - target_.squeeze().detach().cpu().numpy(), - pred.squeeze().detach().cpu().numpy(), - self.mode, - ) - ) + pesq_values.append(self.pesq(pred.squeeze(), target_.squeeze())) except Exception as e: logging.warning(f"{e} error occured while calculating PESQ") return torch.tensor(np.mean(pesq_values)) diff --git a/requirements.txt b/requirements.txt index cf8992d..fb54920 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ joblib>=1.2.0 librosa>=0.9.2 mlflow>=1.29.0 numpy>=1.23.3 -git+https://github.com/ludlows/python-pesq#egg=pesq +pesq==0.0.4 protobuf>=3.19.6 pystoi==0.3.3 pytest-lazy-fixture>=0.6.3 From 47bbee2c32ee87ed006630ba984395ebc2185fdf Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 26 Oct 2022 21:47:29 +0530 Subject: [PATCH 330/375] rmv augmentations --- enhancer/cli/train.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/enhancer/cli/train.py b/enhancer/cli/train.py index 5562cfd..c00c024 100644 --- a/enhancer/cli/train.py +++ b/enhancer/cli/train.py @@ -11,7 +11,8 @@ from pytorch_lightning.callbacks import ( ) from pytorch_lightning.loggers import MLFlowLogger from torch.optim.lr_scheduler import ReduceLROnPlateau -from torch_audiomentations import Compose, Shift + +# from torch_audiomentations import Compose, Shift os.environ["HYDRA_FULL_ERROR"] = "1" JOB_ID = os.environ.get("SLURM_JOBID", "0") @@ -30,13 +31,13 @@ def main(config: DictConfig): ) parameters = config.hyperparameters - apply_augmentations = Compose( - [ - Shift(min_shift=0.5, max_shift=1.0, shift_unit="seconds", p=0.5), - ] - ) + # apply_augmentations = Compose( + # [ + # Shift(min_shift=0.5, max_shift=1.0, shift_unit="seconds", p=0.5), + # ] + # ) - dataset = instantiate(config.dataset, augmentations=apply_augmentations) + dataset = instantiate(config.dataset, augmentations=None) model = instantiate( config.model, dataset=dataset, From 085a85d9ae84e6e5ae2d7d751899e26c2d08d3f3 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 27 Oct 2022 11:32:50 +0530 Subject: [PATCH 331/375] fourier transforms using cnn --- enhancer/utils/transforms.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py index bf481e8..bbbae90 100644 --- a/enhancer/utils/transforms.py +++ b/enhancer/utils/transforms.py @@ -2,6 +2,7 @@ from typing import Optional import numpy as np import torch +import torch.nn.functional as F from scipy.signal import get_window from torch import nn @@ -42,7 +43,22 @@ class ConvSTFT(ConvFFT): self.register_buffer("weight", self.init_kernel) def forward(self, input): - pass + + if input.dim() < 2: + raise ValueError( + f"Expected signal with shape 2 or 3 got {input.dim()}" + ) + elif input.dim() == 2: + input = input.unsqueeze(1) + else: + pass + input = F.pad( + input, + (self.window_len - self.hop_size, self.window_len - self.hop_size), + ) + output = F.conv1d(input, self.weight, stride=self.hop_size) + + return output class ConviSTFT(ConvFFT): @@ -58,6 +74,11 @@ class ConviSTFT(ConvFFT): ) self.hop_size = hop_size if hop_size else window_len // 2 self.register_buffer("weight", self.init_kernel) + self.register_buffer("enframe", np.eye(window_len).unsqueeze(1)) - def forward(self, input): - pass + def forward(self, input, phase=None): + + if phase is not None: + real = input * torch.cos(phase) + imag = input * torch.sin(phase) + input = torch.cat([real, imag], 1) From e1963ff001fe7785ca809a0df48799077938c8fc Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 27 Oct 2022 15:19:02 +0530 Subject: [PATCH 332/375] split validation criterion --- enhancer/data/dataset.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index f05fd6b..dac2c50 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -1,8 +1,10 @@ import math import multiprocessing import os +from pathlib import Path from typing import Optional +import numpy as np import pytorch_lightning as pl import torch import torch.nn.functional as F @@ -119,16 +121,29 @@ class TaskDataset(pl.LightningDataModule): ): valid_minutes *= 60 - valid_min_now = 0.0 + valid_sec_now = 0.0 valid_indices = [] - random_indices = list(range(0, len(data))) - rng = create_unique_rng(random_state) - rng.shuffle(random_indices) - i = 0 - while valid_min_now <= valid_minutes: - valid_indices.append(random_indices[i]) - valid_min_now += data[random_indices[i]]["duration"] - i += 1 + all_speakers = np.unique( + [ + (Path(file["clean"]).name.split("_")[0], file["duration"]) + for file in data + ] + ) + possible_indices = list(range(0, len(all_speakers))) + rng = create_unique_rng(len(all_speakers)) + + while valid_sec_now <= valid_minutes: + speaker_index = rng.choice(possible_indices) + possible_indices.remove(speaker_index) + speaker_name = all_speakers[speaker_index] + file_indices = [ + i + for i, file in enumerate(data) + if speaker_name == Path(file["clean"]).name.split("_")[0] + ] + for i in file_indices: + valid_indices.append(i) + valid_sec_now += data[i]["duration"] train_data = [ item for i, item in enumerate(data) if i not in valid_indices From fb2543e81ee85658835ea820386f1009d76cd7fa Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 27 Oct 2022 16:18:31 +0530 Subject: [PATCH 333/375] fix typo --- enhancer/data/dataset.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index dac2c50..8851ea2 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -124,10 +124,7 @@ class TaskDataset(pl.LightningDataModule): valid_sec_now = 0.0 valid_indices = [] all_speakers = np.unique( - [ - (Path(file["clean"]).name.split("_")[0], file["duration"]) - for file in data - ] + [Path(file["clean"]).name.split("_")[0] for file in data] ) possible_indices = list(range(0, len(all_speakers))) rng = create_unique_rng(len(all_speakers)) From aa52d1ed93293d0f22f47c5447b576197e770aa5 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Fri, 28 Oct 2022 13:06:49 +0530 Subject: [PATCH 334/375] add random sampler --- enhancer/data/dataset.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index 8851ea2..d6ab415 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -8,7 +8,7 @@ import numpy as np import pytorch_lightning as pl import torch import torch.nn.functional as F -from torch.utils.data import DataLoader, Dataset +from torch.utils.data import DataLoader, Dataset, RandomSampler from torch_audiomentations import Compose from enhancer.data.fileprocessor import Fileprocessor @@ -133,6 +133,7 @@ class TaskDataset(pl.LightningDataModule): speaker_index = rng.choice(possible_indices) possible_indices.remove(speaker_index) speaker_name = all_speakers[speaker_index] + print(f"Selected f{speaker_name} for valid") file_indices = [ i for i, file in enumerate(data) @@ -220,11 +221,13 @@ class TaskDataset(pl.LightningDataModule): return generator.manual_seed(seed) def train_dataloader(self): + dataset = TrainDataset(self) + sampler = RandomSampler(dataset, generator=self.generator) return DataLoader( - TrainDataset(self), + dataset, batch_size=self.batch_size, num_workers=self.num_workers, - generator=self.generator, + sampler=sampler, collate_fn=self.train_collatefn, ) @@ -327,7 +330,6 @@ class EnhancerDataset(TaskDataset): return self.prepare_segment(*self._test[idx]) def prepare_segment(self, file_dict: dict, start_time: float): - clean_segment = self.audio( file_dict["clean"], offset=start_time, duration=self.duration ) From ad208ca0a01cdcd8d4cafdbf7a7f41990d4bf925 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 29 Oct 2022 09:41:56 +0530 Subject: [PATCH 335/375] add padding --- enhancer/data/dataset.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index d6ab415..a6b6ba1 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -59,7 +59,7 @@ class TaskDataset(pl.LightningDataModule): name: str, root_dir: str, files: Files, - valid_minutes: float = 0.20, + min_valid_minutes: float = 0.20, duration: float = 1.0, stride=None, sampling_rate: int = 48000, @@ -81,10 +81,10 @@ class TaskDataset(pl.LightningDataModule): if num_workers is None: num_workers = multiprocessing.cpu_count() // 2 self.num_workers = num_workers - if valid_minutes > 0.0: - self.valid_minutes = valid_minutes + if min_valid_minutes > 0.0: + self.min_valid_minutes = min_valid_minutes else: - raise ValueError("valid_minutes must be greater than 0") + raise ValueError("min_valid_minutes must be greater than 0") self.augmentations = augmentations @@ -102,7 +102,9 @@ class TaskDataset(pl.LightningDataModule): ) train_data = fp.prepare_matching_dict() train_data, self.val_data = self.train_valid_split( - train_data, valid_minutes=self.valid_minutes, random_state=42 + train_data, + min_valid_minutes=self.min_valid_minutes, + random_state=42, ) self.train_data = self.prepare_traindata(train_data) @@ -117,10 +119,10 @@ class TaskDataset(pl.LightningDataModule): self._test = self.prepare_mapstype(test_data) def train_valid_split( - self, data, valid_minutes: float = 20, random_state: int = 42 + self, data, min_valid_minutes: float = 20, random_state: int = 42 ): - valid_minutes *= 60 + min_valid_minutes *= 60 valid_sec_now = 0.0 valid_indices = [] all_speakers = np.unique( @@ -129,7 +131,7 @@ class TaskDataset(pl.LightningDataModule): possible_indices = list(range(0, len(all_speakers))) rng = create_unique_rng(len(all_speakers)) - while valid_sec_now <= valid_minutes: + while valid_sec_now <= min_valid_minutes: speaker_index = rng.choice(possible_indices) possible_indices.remove(speaker_index) speaker_name = all_speakers[speaker_index] @@ -257,10 +259,15 @@ class EnhancerDataset(TaskDataset): files : Files dataclass containing train_clean, train_noisy, test_clean, test_noisy folder names (refer enhancer.utils.Files dataclass) + min_valid_minutes: float + minimum validation split size time in minutes + algorithm randomly select n speakers (>=min_valid_minutes) from train data to form validation data. duration : float expected audio duration of single audio sample for training sampling_rate : int desired sampling rate + padding_mode: str + padding mode (silent,reflect) batch_size : int batch size of each batch num_workers : int @@ -271,6 +278,7 @@ class EnhancerDataset(TaskDataset): use one_to_many mapping for multiple noisy files for each clean file + """ def __init__( @@ -278,10 +286,11 @@ class EnhancerDataset(TaskDataset): name: str, root_dir: str, files: Files, - valid_minutes=5.0, + min_valid_minutes=5.0, duration=1.0, stride=None, sampling_rate=48000, + padding_mode: str = "silent", matching_function=None, batch_size=32, num_workers: Optional[int] = None, @@ -292,7 +301,7 @@ class EnhancerDataset(TaskDataset): name=name, root_dir=root_dir, files=files, - valid_minutes=valid_minutes, + min_valid_minutes=min_valid_minutes, sampling_rate=sampling_rate, duration=duration, matching_function=matching_function, @@ -306,6 +315,7 @@ class EnhancerDataset(TaskDataset): self.duration = max(1.0, duration) self.audio = Audio(self.sampling_rate, mono=True, return_tensor=True) self.stride = stride or duration + self.padding_mode = padding_mode def setup(self, stage: Optional[str] = None): @@ -344,6 +354,7 @@ class EnhancerDataset(TaskDataset): self.duration * self.sampling_rate - clean_segment.shape[-1] ), ), + mode=self.padding_mode, ) noisy_segment = F.pad( noisy_segment, @@ -353,6 +364,7 @@ class EnhancerDataset(TaskDataset): self.duration * self.sampling_rate - noisy_segment.shape[-1] ), ), + mode=self.padding_mode, ) return { "clean": clean_segment, From 6f1acf0423525f3e66f3b648e948aafffcf5f869 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 29 Oct 2022 10:33:59 +0530 Subject: [PATCH 336/375] Revert "add random sampler" This reverts commit aa52d1ed93293d0f22f47c5447b576197e770aa5. --- enhancer/data/dataset.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index a6b6ba1..e370526 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -8,7 +8,7 @@ import numpy as np import pytorch_lightning as pl import torch import torch.nn.functional as F -from torch.utils.data import DataLoader, Dataset, RandomSampler +from torch.utils.data import DataLoader, Dataset from torch_audiomentations import Compose from enhancer.data.fileprocessor import Fileprocessor @@ -135,7 +135,6 @@ class TaskDataset(pl.LightningDataModule): speaker_index = rng.choice(possible_indices) possible_indices.remove(speaker_index) speaker_name = all_speakers[speaker_index] - print(f"Selected f{speaker_name} for valid") file_indices = [ i for i, file in enumerate(data) @@ -223,13 +222,11 @@ class TaskDataset(pl.LightningDataModule): return generator.manual_seed(seed) def train_dataloader(self): - dataset = TrainDataset(self) - sampler = RandomSampler(dataset, generator=self.generator) return DataLoader( - dataset, + TrainDataset(self), batch_size=self.batch_size, num_workers=self.num_workers, - sampler=sampler, + generator=self.generator, collate_fn=self.train_collatefn, ) @@ -340,6 +337,7 @@ class EnhancerDataset(TaskDataset): return self.prepare_segment(*self._test[idx]) def prepare_segment(self, file_dict: dict, start_time: float): + clean_segment = self.audio( file_dict["clean"], offset=start_time, duration=self.duration ) From 7f3dcf39c5301810e1837f0be6d049be0c3af683 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 29 Oct 2022 10:39:32 +0530 Subject: [PATCH 337/375] rmv padding_mode --- enhancer/data/dataset.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index e370526..ada1ff3 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -8,7 +8,7 @@ import numpy as np import pytorch_lightning as pl import torch import torch.nn.functional as F -from torch.utils.data import DataLoader, Dataset +from torch.utils.data import DataLoader, Dataset, RandomSampler from torch_audiomentations import Compose from enhancer.data.fileprocessor import Fileprocessor @@ -135,6 +135,7 @@ class TaskDataset(pl.LightningDataModule): speaker_index = rng.choice(possible_indices) possible_indices.remove(speaker_index) speaker_name = all_speakers[speaker_index] + print(f"Selected f{speaker_name} for valid") file_indices = [ i for i, file in enumerate(data) @@ -222,11 +223,13 @@ class TaskDataset(pl.LightningDataModule): return generator.manual_seed(seed) def train_dataloader(self): + dataset = TrainDataset(self) + sampler = RandomSampler(dataset, generator=self.generator) return DataLoader( - TrainDataset(self), + dataset, batch_size=self.batch_size, num_workers=self.num_workers, - generator=self.generator, + sampler=sampler, collate_fn=self.train_collatefn, ) @@ -263,8 +266,6 @@ class EnhancerDataset(TaskDataset): expected audio duration of single audio sample for training sampling_rate : int desired sampling rate - padding_mode: str - padding mode (silent,reflect) batch_size : int batch size of each batch num_workers : int @@ -287,7 +288,6 @@ class EnhancerDataset(TaskDataset): duration=1.0, stride=None, sampling_rate=48000, - padding_mode: str = "silent", matching_function=None, batch_size=32, num_workers: Optional[int] = None, @@ -312,7 +312,6 @@ class EnhancerDataset(TaskDataset): self.duration = max(1.0, duration) self.audio = Audio(self.sampling_rate, mono=True, return_tensor=True) self.stride = stride or duration - self.padding_mode = padding_mode def setup(self, stage: Optional[str] = None): @@ -337,7 +336,6 @@ class EnhancerDataset(TaskDataset): return self.prepare_segment(*self._test[idx]) def prepare_segment(self, file_dict: dict, start_time: float): - clean_segment = self.audio( file_dict["clean"], offset=start_time, duration=self.duration ) @@ -362,7 +360,6 @@ class EnhancerDataset(TaskDataset): self.duration * self.sampling_rate - noisy_segment.shape[-1] ), ), - mode=self.padding_mode, ) return { "clean": clean_segment, From c18a85b5c87976c16cce7ca44b709bd60497221c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 29 Oct 2022 11:34:51 +0530 Subject: [PATCH 338/375] stft --- enhancer/utils/transforms.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py index bbbae90..acfc323 100644 --- a/enhancer/utils/transforms.py +++ b/enhancer/utils/transforms.py @@ -14,9 +14,12 @@ class ConvFFT(nn.Module): nfft: Optional[int] = None, window: str = "hamming", ): + super().__init__() self.window_len = window_len self.nfft = nfft if nfft else np.int(2 ** np.ceil(np.log2(window_len))) - self.window = get_window(window, window_len) + self.window = torch.from_numpy( + get_window(window, window_len).astype("float32") + ) @property def init_kernel(self): @@ -24,8 +27,9 @@ class ConvFFT(nn.Module): fourier_basis = np.fft.rfft(np.eye(self.nfft))[: self.window_len] real, imag = np.real(fourier_basis), np.imag(fourier_basis) kernel = np.concatenate([real, imag], 1).T + kernel = torch.from_numpy(kernel.astype("float32")).unsqueeze(1) kernel *= self.window - return torch.from_numpy(kernel) + return kernel class ConvSTFT(ConvFFT): @@ -36,9 +40,7 @@ class ConvSTFT(ConvFFT): nfft: Optional[int] = None, window: str = "hamming", ): - super(self, ConvSTFT).__init__( - window_len=window_len, nfft=nfft, window=window - ) + super().__init__(window_len=window_len, nfft=nfft, window=window) self.hop_size = hop_size if hop_size else window_len // 2 self.register_buffer("weight", self.init_kernel) @@ -69,12 +71,10 @@ class ConviSTFT(ConvFFT): nfft: Optional[int] = None, window: str = "hamming", ): - super(self, ConvSTFT).__init__( - window_len=window_len, nfft=nfft, window=window - ) + super().__init__(window_len=window_len, nfft=nfft, window=window) self.hop_size = hop_size if hop_size else window_len // 2 self.register_buffer("weight", self.init_kernel) - self.register_buffer("enframe", np.eye(window_len).unsqueeze(1)) + self.register_buffer("enframe", torch.eye(window_len).unsqueeze(1)) def forward(self, input, phase=None): @@ -82,3 +82,10 @@ class ConviSTFT(ConvFFT): real = input * torch.cos(phase) imag = input * torch.sin(phase) input = torch.cat([real, imag], 1) + out = F.conv_transpose1d(input, self.weight, stride=self.hop_size) + coeff = self.window.unsqueeze(1).repeat(1, 1, input.size(-1)) ** 2 + coeff = F.conv_transpose1d(coeff, self.enframe, stride=self.hop_size) + out = out / coeff + pad = self.window_len - self.hop_size + out = out[..., pad:-pad] + return out From cf1e5c07a9a4ea810e8f8214c79ff93d9a50a3a1 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 29 Oct 2022 11:35:35 +0530 Subject: [PATCH 339/375] test transforms --- tests/transforms_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/transforms_test.py diff --git a/tests/transforms_test.py b/tests/transforms_test.py new file mode 100644 index 0000000..3053b09 --- /dev/null +++ b/tests/transforms_test.py @@ -0,0 +1,14 @@ +import torch + +from enhancer.utils.transforms import ConviSTFT, ConvSTFT + + +def test_stft_istft(): + sample_input = torch.rand(1, 1, 16000) + stft = ConvSTFT(window_len=400, hop_size=100, nfft=512) + istft = ConviSTFT(window_len=400, hop_size=100, nfft=512) + + with torch.no_grad(): + spectrogram = stft(sample_input) + waveform = istft(spectrogram) + assert sample_input.shape == waveform.shape From 6f6e7f7ad85ab92d9989404957624969a1c3e8f7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 29 Oct 2022 13:20:04 +0530 Subject: [PATCH 340/375] init --- enhancer/models/complexnn/__init__.py | 1 + enhancer/models/complexnn/conv.py | 77 +++++++++++++++++++++++++++ tests/models/complexnn_test.py | 13 +++++ 3 files changed, 91 insertions(+) create mode 100644 enhancer/models/complexnn/__init__.py create mode 100644 tests/models/complexnn_test.py diff --git a/enhancer/models/complexnn/__init__.py b/enhancer/models/complexnn/__init__.py new file mode 100644 index 0000000..68f376e --- /dev/null +++ b/enhancer/models/complexnn/__init__.py @@ -0,0 +1 @@ +# from enhancer.models.complexnn.conv import ComplexConv2d diff --git a/enhancer/models/complexnn/conv.py b/enhancer/models/complexnn/conv.py index e69de29..eddcce3 100644 --- a/enhancer/models/complexnn/conv.py +++ b/enhancer/models/complexnn/conv.py @@ -0,0 +1,77 @@ +from typing import Tuple + +import torch +import torch.nn.functional as F +from torch import nn + + +def init_weights(nnet): + nn.init.xavier_normal_(nnet.weight.data) + nn.init.constant(nnet.bias, 0.0) + return nnet + + +class ComplexConv2d(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Tuple[int, int] = (1, 1), + stride: Tuple[int, int] = (1, 1), + padding: Tuple[int, int] = (0, 0), + groups: int = 1, + dilation: int = 1, + ): + """ + Complex Conv2d (non-causal) + """ + super().__init__() + self.in_channels = in_channels // 2 + self.out_channels = out_channels // 2 + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + self.groups = groups + self.dilation = dilation + + self.real_conv = nn.Conv2d( + self.in_channels, + self.out_channels, + kernel_size=self.kernel_size, + stride=self.stride, + padding=(self.padding[0], 0), + groups=self.groups, + dilation=self.dilation, + ) + self.imag_conv = nn.Conv2d( + self.in_channels, + self.out_channels, + kernel_size=self.kernel_size, + stride=self.stride, + padding=(self.padding[0], 0), + groups=self.groups, + dilation=self.dilation, + ) + self.imag_conv = init_weights(self.imag_conv) + self.real_conv = init_weights(self.real_conv) + + def forward(self, input): + """ + forward + complex axis should be always 1 dim + """ + input = F.pad(input, [self.padding[1], self.padding[1], 0, 0]) + + real, imag = torch.chunk(input, 2, 1) + real_real = self.real_conv(real) + real_imag = self.imag_conv(real) + + imag_imag = self.imag_conv(imag) + imag_real = self.real_conv(imag) + + real = real_real - imag_imag + imag = real_imag - imag_real + + out = torch.cat([real, imag], 1) + + return out diff --git a/tests/models/complexnn_test.py b/tests/models/complexnn_test.py new file mode 100644 index 0000000..9ca5811 --- /dev/null +++ b/tests/models/complexnn_test.py @@ -0,0 +1,13 @@ +import torch + +from enhancer.models.complexnn.conv import ComplexConv2d + + +def test_complexconv2d(): + sample_input = torch.rand(1, 2, 256, 13) + conv = ComplexConv2d( + 2, 32, kernel_size=(5, 2), stride=(2, 1), padding=(2, 1) + ) + with torch.no_grad(): + out = conv(sample_input) + assert out.shape == torch.Size([1, 32, 128, 14]) From 26cccc67721803c2b3fd0b0f988d76fce3f6278a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 31 Oct 2022 11:43:32 +0530 Subject: [PATCH 341/375] complex tranposed conv --- enhancer/models/complexnn/conv.py | 64 ++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/enhancer/models/complexnn/conv.py b/enhancer/models/complexnn/conv.py index eddcce3..55acc07 100644 --- a/enhancer/models/complexnn/conv.py +++ b/enhancer/models/complexnn/conv.py @@ -7,7 +7,7 @@ from torch import nn def init_weights(nnet): nn.init.xavier_normal_(nnet.weight.data) - nn.init.constant(nnet.bias, 0.0) + nn.init.constant_(nnet.bias, 0.0) return nnet @@ -57,7 +57,6 @@ class ComplexConv2d(nn.Module): def forward(self, input): """ - forward complex axis should be always 1 dim """ input = F.pad(input, [self.padding[1], self.padding[1], 0, 0]) @@ -75,3 +74,64 @@ class ComplexConv2d(nn.Module): out = torch.cat([real, imag], 1) return out + + +class ComplexConvTranspose2d(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Tuple[int, int] = (1, 1), + stride: Tuple[int, int] = (1, 1), + padding: Tuple[int, int] = (0, 0), + output_padding: Tuple[int, int] = (0, 0), + groups: int = 1, + ): + super().__init__() + self.in_channels = in_channels // 2 + self.out_channels = out_channels // 2 + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + self.groups = groups + self.output_padding = output_padding + + self.real_conv = nn.ConvTranspose2d( + self.in_channels, + self.out_channels, + kernel_size=self.kernel_size, + stride=self.stride, + padding=self.padding, + output_padding=self.output_padding, + groups=self.groups, + ) + + self.imag_conv = nn.ConvTranspose2d( + self.in_channels, + self.out_channels, + kernel_size=self.kernel_size, + stride=self.stride, + padding=self.padding, + output_padding=self.output_padding, + groups=self.groups, + ) + + init_weights(self.real_conv) + init_weights(self.imag_conv) + + def forward(self, input): + + real, imag = torch.chunk(input, 2, 1) + + real_real = self.real_conv(real) + real_imag = self.imag_conv(real) + + imag_imag = self.imag_conv(imag) + imag_real = self.real_conv(imag) + + real = real_real - imag_imag + imag = real_imag - imag_real + + out = torch.cat([real, imag], 1) + + return out From 7abd266ab21a47d7757385938f5abad993975992 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 31 Oct 2022 11:43:50 +0530 Subject: [PATCH 342/375] test complexnn --- tests/models/complexnn_test.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/models/complexnn_test.py b/tests/models/complexnn_test.py index 9ca5811..53ffba2 100644 --- a/tests/models/complexnn_test.py +++ b/tests/models/complexnn_test.py @@ -1,6 +1,6 @@ import torch -from enhancer.models.complexnn.conv import ComplexConv2d +from enhancer.models.complexnn.conv import ComplexConv2d, ComplexConvTranspose2d def test_complexconv2d(): @@ -11,3 +11,19 @@ def test_complexconv2d(): with torch.no_grad(): out = conv(sample_input) assert out.shape == torch.Size([1, 32, 128, 14]) + + +def test_complexconvtranspose2d(): + sample_input = torch.rand(1, 512, 4, 13) + conv = ComplexConvTranspose2d( + 256 * 2, + 128 * 2, + kernel_size=(5, 2), + stride=(2, 1), + padding=(2, 0), + output_padding=(1, 0), + ) + with torch.no_grad(): + out = conv(sample_input) + + assert out.shape == torch.Size([1, 256, 8, 14]) From 0b50a573e83eca67773778dda5f743e4f4652287 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 1 Nov 2022 10:35:30 +0530 Subject: [PATCH 343/375] complex lstm --- enhancer/models/complexnn/rnn.py | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/enhancer/models/complexnn/rnn.py b/enhancer/models/complexnn/rnn.py index e69de29..7d19425 100644 --- a/enhancer/models/complexnn/rnn.py +++ b/enhancer/models/complexnn/rnn.py @@ -0,0 +1,66 @@ +from typing import List, Optional + +import torch +from torch import nn + + +class ComplexLSTM(nn.Module): + def __init__( + self, + input_size: int, + hidden_size: int, + num_layers: int = 1, + projection_size: Optional[int] = None, + bidirectional: bool = False, + ): + super().__init__() + self.input_size = input_size // 2 + self.hidden_size = hidden_size // 2 + self.num_layers = num_layers + + self.real_lstm = nn.LSTM( + self.input_size, + self.hidden_size, + self.num_layers, + bidirectional=bidirectional, + batch_first=False, + ) + self.imag_lstm = nn.LSTM( + self.input_size, + self.hidden_size, + self.num_layers, + bidirectional=bidirectional, + batch_first=False, + ) + + bidirectional = 2 if bidirectional else 1 + if projection_size is not None: + self.projection_size = projection_size // 2 + self.real_linear = nn.Linear( + self.hidden_size * bidirectional, self.projection_size + ) + self.imag_linear = nn.Linear( + self.hidden_size * bidirectional, self.projection_size + ) + + def forward(self, input): + + if isinstance(input, List): + real, imag = input + else: + real, imag = torch.chunk(input, 2, 1) + + real_real = self.real_lstm(real)[0] + real_imag = self.imag_lstm(real)[0] + + imag_imag = self.imag_lstm(imag)[0] + imag_real = self.real_lstm(imag)[0] + + real = real_real - imag_imag + imag = imag_real + real_imag + + if self.projection_size is not None: + real = self.real_linear(real) + imag = self.imag_linear(imag) + + return [real, imag] From b1144e7b818822c57e2bc083e6832741f91531f8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Tue, 1 Nov 2022 10:35:49 +0530 Subject: [PATCH 344/375] tests complexnn --- tests/models/complexnn_test.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/models/complexnn_test.py b/tests/models/complexnn_test.py index 53ffba2..74c2baa 100644 --- a/tests/models/complexnn_test.py +++ b/tests/models/complexnn_test.py @@ -1,6 +1,7 @@ import torch from enhancer.models.complexnn.conv import ComplexConv2d, ComplexConvTranspose2d +from enhancer.models.complexnn.rnn import ComplexLSTM def test_complexconv2d(): @@ -27,3 +28,13 @@ def test_complexconvtranspose2d(): out = conv(sample_input) assert out.shape == torch.Size([1, 256, 8, 14]) + + +def test_complexlstm(): + sample_input = torch.rand(13, 2, 128) + lstm = ComplexLSTM(128 * 2, 128 * 2, projection_size=512 * 2) + with torch.no_grad(): + out = lstm(sample_input) + + assert out[0].shape == torch.Size([13, 1, 512]) + assert out[1].shape == torch.Size([13, 1, 512]) From 2f85f48d69ce6d12edf9d6947123ffd0acbb8257 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 2 Nov 2022 17:57:30 +0530 Subject: [PATCH 345/375] add support for custom loss --- enhancer/models/model.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 3b60b85..92d30ae 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -2,7 +2,7 @@ import os from collections import defaultdict from importlib import import_module from pathlib import Path -from typing import List, Optional, Text, Union +from typing import Any, List, Optional, Text, Union from urllib.parse import urlparse import numpy as np @@ -10,6 +10,7 @@ import pytorch_lightning as pl import torch from huggingface_hub import cached_download, hf_hub_url from pytorch_lightning.utilities.cloud_io import load as pl_load +from torch import nn from torch.optim import Adam from enhancer.data.dataset import EnhancerDataset @@ -49,7 +50,7 @@ class Model(pl.LightningModule): dataset: Optional[EnhancerDataset] = None, duration: Optional[float] = None, loss: Union[str, List] = "mse", - metric: Union[str, List] = "mse", + metric: Union[str, List, Any] = "mse", ): super().__init__() assert ( @@ -86,20 +87,25 @@ class Model(pl.LightningModule): @metric.setter def metric(self, metric): self._metric = [] - if isinstance(metric, str): + if isinstance(metric, (str, nn.Module)): metric = [metric] for func in metric: - if func in LOSS_MAP.keys(): - if func in ("pesq", "stoi"): - self._metric.append( - LOSS_MAP[func](self.hparams.sampling_rate) - ) + if isinstance(func, str): + if func in LOSS_MAP.keys(): + if func in ("pesq", "stoi"): + self._metric.append( + LOSS_MAP[func](self.hparams.sampling_rate) + ) + else: + self._metric.append(LOSS_MAP[func]()) else: - self._metric.append(LOSS_MAP[func]()) + ValueError(f"Invalid metrics {func}") + elif isinstance(func, nn.Module): + self._metric.append(func) else: - raise ValueError(f"Invalid metrics {func}") + raise ValueError("Invalid metrics") @property def dataset(self): From 7e298b811fd08b98e992f29388e5f126b84d94b4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 2 Nov 2022 17:57:44 +0530 Subject: [PATCH 346/375] rmv typo --- enhancer/data/dataset.py | 1 - 1 file changed, 1 deletion(-) diff --git a/enhancer/data/dataset.py b/enhancer/data/dataset.py index ada1ff3..284dfdb 100644 --- a/enhancer/data/dataset.py +++ b/enhancer/data/dataset.py @@ -350,7 +350,6 @@ class EnhancerDataset(TaskDataset): self.duration * self.sampling_rate - clean_segment.shape[-1] ), ), - mode=self.padding_mode, ) noisy_segment = F.pad( noisy_segment, From b8577546264a3aeee025f2763dd63fde44c9dea0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Wed, 2 Nov 2022 18:00:05 +0530 Subject: [PATCH 347/375] add documentation --- enhancer/models/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/models/model.py b/enhancer/models/model.py index 92d30ae..c679669 100644 --- a/enhancer/models/model.py +++ b/enhancer/models/model.py @@ -37,7 +37,7 @@ class Model(pl.LightningModule): Enhancer dataset used for training/validation duration: float, optional duration used for training/inference - loss : string or List of strings, default to "mse" + loss : string or List of strings or custom loss (nn.Module), default to "mse" loss functions to be used. Available ("mse","mae","Si-SDR") """ From e932dc6c75b73e3a3f25221ac78cbdf9d0dd8862 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 3 Nov 2022 11:37:58 +0530 Subject: [PATCH 348/375] batchnorm --- enhancer/models/complexnn/norm.py | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 enhancer/models/complexnn/norm.py diff --git a/enhancer/models/complexnn/norm.py b/enhancer/models/complexnn/norm.py new file mode 100644 index 0000000..eec2130 --- /dev/null +++ b/enhancer/models/complexnn/norm.py @@ -0,0 +1,72 @@ +import torch +from torch import nn + + +class ComplexBatchNorm(nn.Module): + def __init__( + self, + num_features: int, + eps: float = 1e-5, + momentum: bool = True, + affine: bool = True, + track_running_stats: bool = True, + ): + self.num_features = num_features // 2 + self.affine = affine + self.momentum = momentum + self.track_running_stats = track_running_stats + + if self.affine: + values = torch.Tensor(self.num_features) + self.Wrr = nn.parameter.Parameter(values) + self.Wri = nn.parameter.Parameter(values) + self.Wii = nn.parameter.Parameter(values) + self.Br = nn.parameter.Parameter(values) + self.Bi = nn.parameter.Parameter(values) + else: + self.register_parameter("Wrr", None) + self.register_parameter("Wri", None) + self.register_parameter("Wii", None) + self.register_parameter("Br", None) + self.register_parameter("Bi", None) + + if self.track_running_stats: + values = torch.Tensor(self.num_features) + self.register_buffer("Mean_real", values) + self.register_buffer("Mean_imag", values) + self.register_buffer("Var_rr", values) + self.register_buffer("Var_ri", values) + self.register_buffer("Var_ii", values) + self.register_buffer( + "num_batches_tracked", torch.tensor(0, dtype=torch.long) + ) + else: + self.register_parameter("Mean_real", None) + self.register_parameter("Mean_imag", None) + self.register_parameter("Var_rr", None) + self.register_parameter("Var_ri", None) + self.register_parameter("Var_ii", None) + self.register_parameter("num_batches_tracked", None) + + self.reset_parameters() + + def reset_parameters(self): + if self.affine: + self.Wrr.data.fill_(1) + self.Wii.data.fill(1) + self.Wri.data.uniform_(-0.9, 0.9) + self.Br.data.fill_(0) + self.Bi.data.fill_(0) + self.reset_running_stats() + + def reset_running_stats(self): + if self.track_running_stats: + self.Mean_real.zero_() + self.Mean_imag.zero_() + self.Var_rr.fill_(1) + self.Var_ri.zero_() + self.Var_ii.fill_(1) + self.num_batches_tracked.zero_() + + def forward(self, input): + pass From da1b986d311579f60445e599d29cdf42306815d8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 3 Nov 2022 16:05:55 +0530 Subject: [PATCH 349/375] complex batchnorm 2d --- enhancer/models/complexnn/norm.py | 95 ++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/enhancer/models/complexnn/norm.py b/enhancer/models/complexnn/norm.py index eec2130..5dd0104 100644 --- a/enhancer/models/complexnn/norm.py +++ b/enhancer/models/complexnn/norm.py @@ -2,7 +2,7 @@ import torch from torch import nn -class ComplexBatchNorm(nn.Module): +class ComplexBatchNorm2D(nn.Module): def __init__( self, num_features: int, @@ -11,10 +11,18 @@ class ComplexBatchNorm(nn.Module): affine: bool = True, track_running_stats: bool = True, ): + """ + Complex batch normalization 2D + https://arxiv.org/abs/1705.09792 + + + """ + super().__init__() self.num_features = num_features // 2 self.affine = affine self.momentum = momentum self.track_running_stats = track_running_stats + self.eps = eps if self.affine: values = torch.Tensor(self.num_features) @@ -53,7 +61,7 @@ class ComplexBatchNorm(nn.Module): def reset_parameters(self): if self.affine: self.Wrr.data.fill_(1) - self.Wii.data.fill(1) + self.Wii.data.fill_(1) self.Wri.data.uniform_(-0.9, 0.9) self.Br.data.fill_(0) self.Bi.data.fill_(0) @@ -69,4 +77,85 @@ class ComplexBatchNorm(nn.Module): self.num_batches_tracked.zero_() def forward(self, input): - pass + + real, imag = torch.chunk(input, 2, 1) + exp_avg_factor = 0.0 + + training = self.training and self.track_running_stats + if training: + self.num_batches_tracked += 1 + if self.momentum is None: + exp_avg_factor = 1 / self.num_batches_tracked + else: + exp_avg_factor = self.momentum + + redux = [i for i in reversed(range(real.dim())) if i != 1] + vdim = [1] * real.dim() + vdim[1] = real.size(1) + + if training: + batch_mean_real, batch_mean_imag = real, imag + for dim in redux: + batch_mean_real = batch_mean_real.mean(dim, keepdim=True) + batch_mean_imag = batch_mean_imag.mean(dim, keepdim=True) + if self.track_running_stats: + self.Mean_real.lerp_(batch_mean_real.squeeze(), exp_avg_factor) + self.Mean_imag.lerp_(batch_mean_imag.squeeze(), exp_avg_factor) + + else: + batch_mean_real = self.Mean_real.view(vdim) + batch_mean_imag = self.Mean_imag.view(vdim) + + real -= batch_mean_real + imag -= batch_mean_imag + + if training: + batch_var_rr = real * real + batch_var_ri = real * imag + batch_var_ii = imag * imag + for dim in redux: + batch_var_rr = batch_var_rr.mean(dim, keepdim=True) + batch_var_ri = batch_var_ri.mean(dim, keepdim=True) + batch_var_ii = batch_var_ii.mean(dim, keepdim=True) + if self.track_running_stats: + self.Var_rr.lerp_(batch_var_rr.squeeze(), exp_avg_factor) + self.Var_ri.lerp_(batch_var_ri.squeeze(), exp_avg_factor) + self.Var_ii.lerp_(batch_var_ii.squeeze(), exp_avg_factor) + + batch_var_rr += self.eps + batch_var_ii += self.eps + + # Covariance matrics + # | batch_var_rr batch_var_ri | + # | batch_var_ir batch_var_ii | here batch_var_ir == batch_var_ri + # Inverse square root of cov matrix by combining below two formulas + # https://en.wikipedia.org/wiki/Square_root_of_a_2_by_2_matrix + # https://mathworld.wolfram.com/MatrixInverse.html + + tau = batch_var_rr + batch_var_ii + s = batch_var_rr * batch_var_ii - batch_var_ri * batch_var_ri + t = (tau + 2 * s).sqrt() + + rst = 1 / (s * t) + Urr = (batch_var_ii + s) * rst + Uri = -batch_var_ri * rst + Uii = (batch_var_rr + s) * rst + + if self.affine: + Wrr, Wri, Wii = ( + self.Wrr.view(vdim), + self.Wri.view(vdim), + self.Wii.view(vdim), + ) + Zrr = (Wrr * Urr) + (Wri * Uri) + Zri = (Wrr * Uri) + (Wri * Uii) + Zir = (Wii * Uri) + (Wri * Urr) + Zii = (Wri * Uri) + (Wii * Uii) + else: + Zrr, Zri, Zir, Zii = Urr, Uri, Uri, Uii + + yr = (Zrr * real) + (Zri * imag) + yi = (Zir * real) + (Zii * imag) + + outputs = torch.cat([yr, yi], 1) + return outputs From d3e052c5f36d441393bf8df7294e15cc5545712c Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 3 Nov 2022 16:06:14 +0530 Subject: [PATCH 350/375] complex batchnorm 2d test --- tests/models/complexnn_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/models/complexnn_test.py b/tests/models/complexnn_test.py index 74c2baa..8c18ed5 100644 --- a/tests/models/complexnn_test.py +++ b/tests/models/complexnn_test.py @@ -1,6 +1,7 @@ import torch from enhancer.models.complexnn.conv import ComplexConv2d, ComplexConvTranspose2d +from enhancer.models.complexnn.norm import ComplexBatchNorm2D from enhancer.models.complexnn.rnn import ComplexLSTM @@ -38,3 +39,12 @@ def test_complexlstm(): assert out[0].shape == torch.Size([13, 1, 512]) assert out[1].shape == torch.Size([13, 1, 512]) + + +def test_complexbatchnorm2d(): + sample_input = torch.rand(1, 64, 64, 14) + batchnorm = ComplexBatchNorm2D(num_features=64) + with torch.no_grad(): + out = batchnorm(sample_input) + + assert out.size() == sample_input.size() From 981763207ae7694d1298de2a7443a512329eded4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:35:57 +0530 Subject: [PATCH 351/375] init dccrn --- enhancer/models/dccrn.py | 240 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) diff --git a/enhancer/models/dccrn.py b/enhancer/models/dccrn.py index e69de29..38c8145 100644 --- a/enhancer/models/dccrn.py +++ b/enhancer/models/dccrn.py @@ -0,0 +1,240 @@ +import logging +from typing import Any, List, Optional, Tuple, Union + +from torch import nn + +from enhancer.data import EnhancerDataset +from enhancer.models import Model +from enhancer.models.complexnn import ComplexConv2d, ComplexLSTM +from enhancer.models.complexnn.conv import ComplexConvTranspose2d +from enhancer.models.complexnn.utils import ComplexBatchNorm2D, ComplexRelu +from enhancer.utils.transforms import ConviSTFT, ConvSTFT +from enhancer.utils.utils import merge_dict + + +class DCCRN_ENCODER(nn.Module): + def __init__( + self, + in_channels: int, + out_channel: int, + kernel_size: Tuple[int, int], + complex_norm: bool = True, + complex_relu: bool = True, + stride: Tuple[int, int] = (2, 1), + padding: Tuple[int, int] = (2, 1), + ): + super().__init__() + batchnorm = ComplexBatchNorm2D if complex_norm else nn.BatchNorm2d + activation = ComplexRelu() if complex_relu else nn.PReLU() + + self.encoder = nn.Sequential( + ComplexConv2d( + in_channels, + out_channel, + kernel_size=kernel_size, + stride=stride, + padding=padding, + ), + batchnorm(out_channel), + activation, + ) + + def forward(self, waveform): + + return self.encoder(waveform) + + +class DCCRN_DECODER(nn.Module): + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: Tuple[int, int], + complex_norm: bool = True, + complex_relu: bool = True, + stride: Tuple[int, int] = (2, 1), + padding: Tuple[int, int] = (2, 0), + output_padding: Tuple[int, int] = (1, 0), + ): + super().__init__() + batchnorm = ComplexBatchNorm2D if complex_norm else nn.BatchNorm2d + activation = ComplexRelu() if complex_relu else nn.PReLU() + + self.decoder = nn.Sequential( + ComplexConvTranspose2d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + output_padding=output_padding, + ), + batchnorm(out_channels), + activation, + ) + + def forward(self, waveform): + + return self.decoder(waveform) + + +class DCCRN(Model): + + STFT_DEFAULTS = { + "window_len": 400, + "hop_size": 100, + "nfft": 512, + "window": "hamming", + } + + ED_DEFAULTS = { + "initial_output_channels": 32, + "depth": 6, + "kernel_size": 5, + "growth_factor": 2, + "stride": 2, + "padding": 2, + "output_padding": 1, + } + + LSTM_DEFAULTS = { + "num_layers": 2, + "hidden_size": 256, + } + + def __init__( + self, + stft: Optional[dict] = None, + encoder_decoder: Optional[dict] = None, + lstm: Optional[dict] = None, + complex_lstm: bool = True, + complex_norm: bool = True, + complex_relu: bool = True, + masking_mode: str = "E", + num_channels: int = 1, + sampling_rate=16000, + lr: float = 1e-3, + dataset: Optional[EnhancerDataset] = None, + duration: Optional[float] = None, + loss: Union[str, List, Any] = "mse", + metric: Union[str, List] = "mse", + ): + duration = ( + dataset.duration if isinstance(dataset, EnhancerDataset) else None + ) + if dataset is not None: + if sampling_rate != dataset.sampling_rate: + logging.warning( + f"model sampling rate {sampling_rate} should match dataset sampling rate {dataset.sampling_rate}" + ) + sampling_rate = dataset.sampling_rate + super().__init__( + num_channels=num_channels, + sampling_rate=sampling_rate, + lr=lr, + dataset=dataset, + duration=duration, + loss=loss, + metric=metric, + ) + + encoder_decoder = merge_dict(self.ED_DEFAULTS, encoder_decoder) + lstm = merge_dict(self.LSTM_DEFAULTS, lstm) + stft = merge_dict(self.STFT_DEFAULTS, stft) + self.save_hyperparameters( + "encoder_decoder", + "lstm", + "stft", + "complex_lstm", + "complex_norm", + "masking_mode", + ) + self.complex_lstm = complex_lstm + self.complex_norm = complex_norm + self.masking_mode = masking_mode + + self.stft = ConvSTFT( + stft["window_len"], stft["hop_size"], stft["nfft"], stft["window"] + ) + self.istft = ConviSTFT( + stft["window_len"], stft["hop_size"], stft["nfft"], stft["window"] + ) + + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + + num_channels *= 2 + hidden_size = encoder_decoder["initial_output_channels"] + growth_factor = 2 + + for layer in range(encoder_decoder["depth"]): + + encoder_ = DCCRN_ENCODER( + num_channels, + hidden_size, + kernel_size=(encoder_decoder["kernel_size"], 2), + stride=(encoder_decoder["stride"], 1), + padding=(encoder_decoder["padding"], 1), + complex_norm=complex_norm, + complex_relu=complex_relu, + ) + self.encoder.append(encoder_) + + decoder_ = DCCRN_DECODER( + hidden_size + hidden_size, + num_channels, + kernel_size=(encoder_decoder["kernel_size"], 2), + stride=(encoder_decoder["stride"], 1), + padding=(encoder_decoder["padding"], 0), + output_padding=(encoder_decoder["output_padding"], 0), + complex_norm=complex_norm, + complex_relu=complex_relu, + ) + + self.decoder.insert(0, decoder_) + + if layer < encoder_decoder["depth"] - 3: + num_channels = hidden_size + hidden_size *= growth_factor + else: + num_channels = hidden_size + + kernel_size = hidden_size / 2 + hidden_size = stft["nfft"] / 2 ** (encoder_decoder["depth"]) + + if self.complex_lstm: + lstms = [] + for layer in range(lstm["num_layers"]): + + if layer == 0: + input_size = int(hidden_size * kernel_size) + else: + input_size = lstm["hidden_size"] + + if layer == lstm["num_layers"] - 1: + projection_size = int(hidden_size * kernel_size) + else: + projection_size = None + + kwargs = { + "input_size": input_size, + "hidden_size": lstm["hidden_size"], + "num_layers": 1, + } + + lstms.append( + ComplexLSTM(projection_size=projection_size, **kwargs) + ) + self.lstm = nn.Sequential(*lstms) + else: + self.lstm = nn.LSTM( + input_size=hidden_size * kernel_size, + hidden_sizs=lstm["hidden_size"], + num_layers=lstm["num_layers"], + dropout=0.0, + batch_first=False, + ) + + def forward(self, waveform): + + return waveform From b98599f21e0e940246f041395b6cd2fe6f40e451 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:36:27 +0530 Subject: [PATCH 352/375] rename module --- .../models/complexnn/{norm.py => utils.py} | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) rename enhancer/models/complexnn/{norm.py => utils.py} (91%) diff --git a/enhancer/models/complexnn/norm.py b/enhancer/models/complexnn/utils.py similarity index 91% rename from enhancer/models/complexnn/norm.py rename to enhancer/models/complexnn/utils.py index 5dd0104..d5de558 100644 --- a/enhancer/models/complexnn/norm.py +++ b/enhancer/models/complexnn/utils.py @@ -76,6 +76,11 @@ class ComplexBatchNorm2D(nn.Module): self.Var_ii.fill_(1) self.num_batches_tracked.zero_() + def extra_repr(self): + return "{num_features}, eps={eps}, momentum={momentum}, affine={affine}, track_running_stats={track_running_stats}".format( + **self.__dict__ + ) + def forward(self, input): real, imag = torch.chunk(input, 2, 1) @@ -159,3 +164,17 @@ class ComplexBatchNorm2D(nn.Module): outputs = torch.cat([yr, yi], 1) return outputs + + +class ComplexRelu(nn.Module): + def __init__(self): + super().__init__() + self.real_relu = nn.PReLU() + self.imag_relu = nn.PReLU() + + def forward(self, input): + + real, imag = torch.chunk(input, 2, 1) + real = self.real_relu(real) + imag = self.imag_relu(imag) + return torch.cat([real, imag], dim=1) From a3b20d5ddb6673a302c455e44f14ac6ead3239b7 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:40:19 +0530 Subject: [PATCH 353/375] fix imports --- enhancer/models/dccrn.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/enhancer/models/dccrn.py b/enhancer/models/dccrn.py index 38c8145..00d9f9d 100644 --- a/enhancer/models/dccrn.py +++ b/enhancer/models/dccrn.py @@ -5,9 +5,13 @@ from torch import nn from enhancer.data import EnhancerDataset from enhancer.models import Model -from enhancer.models.complexnn import ComplexConv2d, ComplexLSTM -from enhancer.models.complexnn.conv import ComplexConvTranspose2d -from enhancer.models.complexnn.utils import ComplexBatchNorm2D, ComplexRelu +from enhancer.models.complexnn import ( + ComplexBatchNorm2D, + ComplexConv2d, + ComplexConvTranspose2d, + ComplexLSTM, + ComplexRelu, +) from enhancer.utils.transforms import ConviSTFT, ConvSTFT from enhancer.utils.utils import merge_dict From e2e413f8f3b545abfd132d9f4dfddd22671566a8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:55:23 +0530 Subject: [PATCH 354/375] rmv --- enhancer/models/complexnn/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 enhancer/models/complexnn/__init__.py diff --git a/enhancer/models/complexnn/__init__.py b/enhancer/models/complexnn/__init__.py deleted file mode 100644 index 68f376e..0000000 --- a/enhancer/models/complexnn/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# from enhancer.models.complexnn.conv import ComplexConv2d From 438882092141cd4c2200329eae4e357bac4692b4 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:58:16 +0530 Subject: [PATCH 355/375] add imports --- enhancer/models/complexnn/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 enhancer/models/complexnn/__init__.py diff --git a/enhancer/models/complexnn/__init__.py b/enhancer/models/complexnn/__init__.py new file mode 100644 index 0000000..8f9cef6 --- /dev/null +++ b/enhancer/models/complexnn/__init__.py @@ -0,0 +1 @@ +from enhancer.models.complexnn.conv import ComplexConv2d # noqa From 2e4a3cd25403ceff5def92d79267ddcce97cbf52 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:58:50 +0530 Subject: [PATCH 356/375] add imports --- enhancer/models/complexnn/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/enhancer/models/complexnn/__init__.py b/enhancer/models/complexnn/__init__.py index 8f9cef6..aa47ad2 100644 --- a/enhancer/models/complexnn/__init__.py +++ b/enhancer/models/complexnn/__init__.py @@ -1 +1,4 @@ from enhancer.models.complexnn.conv import ComplexConv2d # noqa +from enhancer.models.complexnn.conv import ComplexConvTranspose2d # noqa +from enhancer.models.complexnn.rnn import ComplexLSTM # noqa +from enhancer.models.complexnn.utils import ComplexBatchNorm2D # noqa From 70d17f6586eb2d03b69c71f76962f0265519b0ef Mon Sep 17 00:00:00 2001 From: shahules786 Date: Sat, 5 Nov 2022 16:59:04 +0530 Subject: [PATCH 357/375] add imports --- enhancer/models/complexnn/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/models/complexnn/__init__.py b/enhancer/models/complexnn/__init__.py index aa47ad2..918a261 100644 --- a/enhancer/models/complexnn/__init__.py +++ b/enhancer/models/complexnn/__init__.py @@ -2,3 +2,4 @@ from enhancer.models.complexnn.conv import ComplexConv2d # noqa from enhancer.models.complexnn.conv import ComplexConvTranspose2d # noqa from enhancer.models.complexnn.rnn import ComplexLSTM # noqa from enhancer.models.complexnn.utils import ComplexBatchNorm2D # noqa +from enhancer.models.complexnn.utils import ComplexRelu # noqa From c21f05e3073053e6a2d0cea063b441ffdb799c8d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:23:46 +0530 Subject: [PATCH 358/375] fix padding & init --- enhancer/models/complexnn/conv.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/enhancer/models/complexnn/conv.py b/enhancer/models/complexnn/conv.py index 55acc07..d9a4d0f 100644 --- a/enhancer/models/complexnn/conv.py +++ b/enhancer/models/complexnn/conv.py @@ -59,9 +59,10 @@ class ComplexConv2d(nn.Module): """ complex axis should be always 1 dim """ - input = F.pad(input, [self.padding[1], self.padding[1], 0, 0]) + input = F.pad(input, [self.padding[1], 0, 0, 0]) real, imag = torch.chunk(input, 2, 1) + real_real = self.real_conv(real) real_imag = self.imag_conv(real) @@ -72,7 +73,6 @@ class ComplexConv2d(nn.Module): imag = real_imag - imag_real out = torch.cat([real, imag], 1) - return out @@ -116,13 +116,12 @@ class ComplexConvTranspose2d(nn.Module): groups=self.groups, ) - init_weights(self.real_conv) - init_weights(self.imag_conv) + self.real_conv = init_weights(self.real_conv) + self.imag_conv = init_weights(self.imag_conv) def forward(self, input): real, imag = torch.chunk(input, 2, 1) - real_real = self.real_conv(real) real_imag = self.imag_conv(real) From 60fc4607d03c907d8832dc35fcbed363426da5d9 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:24:18 +0530 Subject: [PATCH 359/375] init projection_size as None --- enhancer/models/complexnn/rnn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enhancer/models/complexnn/rnn.py b/enhancer/models/complexnn/rnn.py index 7d19425..847030b 100644 --- a/enhancer/models/complexnn/rnn.py +++ b/enhancer/models/complexnn/rnn.py @@ -42,6 +42,8 @@ class ComplexLSTM(nn.Module): self.imag_linear = nn.Linear( self.hidden_size * bidirectional, self.projection_size ) + else: + self.projection_size = None def forward(self, input): From d7f384791755456ec97963103099f819b6414d1f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:24:47 +0530 Subject: [PATCH 360/375] add complex-cat --- enhancer/models/complexnn/utils.py | 37 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/enhancer/models/complexnn/utils.py b/enhancer/models/complexnn/utils.py index d5de558..1b6ff78 100644 --- a/enhancer/models/complexnn/utils.py +++ b/enhancer/models/complexnn/utils.py @@ -7,7 +7,7 @@ class ComplexBatchNorm2D(nn.Module): self, num_features: int, eps: float = 1e-5, - momentum: bool = True, + momentum: float = 0.1, affine: bool = True, track_running_stats: bool = True, ): @@ -25,12 +25,11 @@ class ComplexBatchNorm2D(nn.Module): self.eps = eps if self.affine: - values = torch.Tensor(self.num_features) - self.Wrr = nn.parameter.Parameter(values) - self.Wri = nn.parameter.Parameter(values) - self.Wii = nn.parameter.Parameter(values) - self.Br = nn.parameter.Parameter(values) - self.Bi = nn.parameter.Parameter(values) + self.Wrr = nn.parameter.Parameter(torch.Tensor(self.num_features)) + self.Wri = nn.parameter.Parameter(torch.Tensor(self.num_features)) + self.Wii = nn.parameter.Parameter(torch.Tensor(self.num_features)) + self.Br = nn.parameter.Parameter(torch.Tensor(self.num_features)) + self.Bi = nn.parameter.Parameter(torch.Tensor(self.num_features)) else: self.register_parameter("Wrr", None) self.register_parameter("Wri", None) @@ -39,7 +38,7 @@ class ComplexBatchNorm2D(nn.Module): self.register_parameter("Bi", None) if self.track_running_stats: - values = torch.Tensor(self.num_features) + values = torch.zeros(self.num_features) self.register_buffer("Mean_real", values) self.register_buffer("Mean_imag", values) self.register_buffer("Var_rr", values) @@ -111,8 +110,8 @@ class ComplexBatchNorm2D(nn.Module): batch_mean_real = self.Mean_real.view(vdim) batch_mean_imag = self.Mean_imag.view(vdim) - real -= batch_mean_real - imag -= batch_mean_imag + real = real - batch_mean_real + imag = imag - batch_mean_imag if training: batch_var_rr = real * real @@ -141,7 +140,7 @@ class ComplexBatchNorm2D(nn.Module): s = batch_var_rr * batch_var_ii - batch_var_ri * batch_var_ri t = (tau + 2 * s).sqrt() - rst = 1 / (s * t) + rst = (s * t).reciprocal() Urr = (batch_var_ii + s) * rst Uri = -batch_var_ri * rst Uii = (batch_var_rr + s) * rst @@ -162,6 +161,10 @@ class ComplexBatchNorm2D(nn.Module): yr = (Zrr * real) + (Zri * imag) yi = (Zir * real) + (Zii * imag) + if self.affine: + yr = yr + self.Br.view(vdim) + yi = yi + self.Bi.view(vdim) + outputs = torch.cat([yr, yi], 1) return outputs @@ -178,3 +181,15 @@ class ComplexRelu(nn.Module): real = self.real_relu(real) imag = self.imag_relu(imag) return torch.cat([real, imag], dim=1) + + +def complex_cat(inputs, axis=1): + + real, imag = [], [] + for data in inputs: + real_data, imag_data = torch.chunk(data, 2, axis) + real.append(real_data) + imag.append(imag_data) + real = torch.cat(real, axis) + imag = torch.cat(imag, axis) + return torch.cat([real, imag], axis) From c1d5e56ec0c4b0290cddf185c371f8e0ede886f0 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:25:27 +0530 Subject: [PATCH 361/375] transforms test --- enhancer/utils/transforms.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py index acfc323..fbdb8f9 100644 --- a/enhancer/utils/transforms.py +++ b/enhancer/utils/transforms.py @@ -18,15 +18,16 @@ class ConvFFT(nn.Module): self.window_len = window_len self.nfft = nfft if nfft else np.int(2 ** np.ceil(np.log2(window_len))) self.window = torch.from_numpy( - get_window(window, window_len).astype("float32") + get_window(window, window_len, fftbins=True).astype("float32") ) - @property - def init_kernel(self): + def init_kernel(self, inverse=False): fourier_basis = np.fft.rfft(np.eye(self.nfft))[: self.window_len] real, imag = np.real(fourier_basis), np.imag(fourier_basis) kernel = np.concatenate([real, imag], 1).T + if inverse: + kernel = np.linalg.pinv(kernel).T kernel = torch.from_numpy(kernel.astype("float32")).unsqueeze(1) kernel *= self.window return kernel @@ -42,7 +43,7 @@ class ConvSTFT(ConvFFT): ): super().__init__(window_len=window_len, nfft=nfft, window=window) self.hop_size = hop_size if hop_size else window_len // 2 - self.register_buffer("weight", self.init_kernel) + self.register_buffer("weight", self.init_kernel()) def forward(self, input): @@ -73,7 +74,7 @@ class ConviSTFT(ConvFFT): ): super().__init__(window_len=window_len, nfft=nfft, window=window) self.hop_size = hop_size if hop_size else window_len // 2 - self.register_buffer("weight", self.init_kernel) + self.register_buffer("weight", self.init_kernel(True)) self.register_buffer("enframe", torch.eye(window_len).unsqueeze(1)) def forward(self, input, phase=None): @@ -85,7 +86,7 @@ class ConviSTFT(ConvFFT): out = F.conv_transpose1d(input, self.weight, stride=self.hop_size) coeff = self.window.unsqueeze(1).repeat(1, 1, input.size(-1)) ** 2 coeff = F.conv_transpose1d(coeff, self.enframe, stride=self.hop_size) - out = out / coeff + out = out / (coeff + 1e-8) pad = self.window_len - self.hop_size out = out[..., pad:-pad] return out From fc33bd83b68757cff1a3c73501cb3c7bb34740e8 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:25:54 +0530 Subject: [PATCH 362/375] transforms test --- tests/transforms_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/transforms_test.py b/tests/transforms_test.py index 3053b09..89425ad 100644 --- a/tests/transforms_test.py +++ b/tests/transforms_test.py @@ -12,3 +12,7 @@ def test_stft_istft(): spectrogram = stft(sample_input) waveform = istft(spectrogram) assert sample_input.shape == waveform.shape + assert ( + torch.isclose(waveform, sample_input).sum().item() + > sample_input.shape[-1] // 2 + ) From 511d2141d4ee2229fcb52dad59966e4d581d918a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:26:51 +0530 Subject: [PATCH 363/375] DCCRN implementation --- enhancer/models/dccrn.py | 124 +++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 19 deletions(-) diff --git a/enhancer/models/dccrn.py b/enhancer/models/dccrn.py index 00d9f9d..c6ee837 100644 --- a/enhancer/models/dccrn.py +++ b/enhancer/models/dccrn.py @@ -1,6 +1,8 @@ import logging from typing import Any, List, Optional, Tuple, Union +import torch +import torch.nn.functional as F from torch import nn from enhancer.data import EnhancerDataset @@ -12,6 +14,7 @@ from enhancer.models.complexnn import ( ComplexLSTM, ComplexRelu, ) +from enhancer.models.complexnn.utils import complex_cat from enhancer.utils.transforms import ConviSTFT, ConvSTFT from enhancer.utils.utils import merge_dict @@ -54,6 +57,7 @@ class DCCRN_DECODER(nn.Module): in_channels: int, out_channels: int, kernel_size: Tuple[int, int], + layer: int = 0, complex_norm: bool = True, complex_relu: bool = True, stride: Tuple[int, int] = (2, 1), @@ -64,18 +68,30 @@ class DCCRN_DECODER(nn.Module): batchnorm = ComplexBatchNorm2D if complex_norm else nn.BatchNorm2d activation = ComplexRelu() if complex_relu else nn.PReLU() - self.decoder = nn.Sequential( - ComplexConvTranspose2d( - in_channels, - out_channels, - kernel_size=kernel_size, - stride=stride, - padding=padding, - output_padding=output_padding, - ), - batchnorm(out_channels), - activation, - ) + if layer != 0: + self.decoder = nn.Sequential( + ComplexConvTranspose2d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + output_padding=output_padding, + ), + batchnorm(out_channels), + activation, + ) + else: + self.decoder = nn.Sequential( + ComplexConvTranspose2d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + output_padding=output_padding, + ) + ) def forward(self, waveform): @@ -187,6 +203,7 @@ class DCCRN(Model): decoder_ = DCCRN_DECODER( hidden_size + hidden_size, num_channels, + layer=layer, kernel_size=(encoder_decoder["kernel_size"], 2), stride=(encoder_decoder["stride"], 1), padding=(encoder_decoder["padding"], 0), @@ -231,14 +248,83 @@ class DCCRN(Model): ) self.lstm = nn.Sequential(*lstms) else: - self.lstm = nn.LSTM( - input_size=hidden_size * kernel_size, - hidden_sizs=lstm["hidden_size"], - num_layers=lstm["num_layers"], - dropout=0.0, - batch_first=False, + self.lstm = nn.Sequential( + nn.LSTM( + input_size=hidden_size * kernel_size, + hidden_sizs=lstm["hidden_size"], + num_layers=lstm["num_layers"], + dropout=0.0, + batch_first=False, + )[0], + nn.Linear(lstm["hidden"], hidden_size * kernel_size), ) def forward(self, waveform): - return waveform + waveform_stft = self.stft(waveform) + real = waveform_stft[:, : self.stft.nfft // 2 + 1] + imag = waveform_stft[:, self.stft.nfft // 2 + 1 :] + + mag_spec = torch.sqrt(real**2 + imag**2 + 1e-9) + phase_spec = torch.atan2(imag, real) + complex_spec = torch.stack([mag_spec, phase_spec], 1)[:, :, 1:] + + encoder_outputs = [] + out = complex_spec + for _, encoder in enumerate(self.encoder): + out = encoder(out) + encoder_outputs.append(out) + + B, C, D, T = out.size() + out = out.permute(3, 0, 1, 2) + if self.complex_lstm: + + lstm_real = out[:, :, : C // 2] + lstm_imag = out[:, :, C // 2 :] + lstm_real = lstm_real.reshape(T, B, C // 2 * D) + lstm_imag = lstm_imag.reshape(T, B, C // 2 * D) + lstm_real, lstm_imag = self.lstm([lstm_real, lstm_imag]) + lstm_real = lstm_real.reshape(T, B, C // 2, D) + lstm_imag = lstm_imag.reshape(T, B, C // 2, D) + out = torch.cat([lstm_real, lstm_imag], 2) + else: + out = out.reshape(T, B, C * D) + out = self.lstm(out) + out = out.reshape(T, B, D, C) + + out = out.permute(1, 2, 3, 0) + for layer, decoder in enumerate(self.decoder): + skip_connection = encoder_outputs.pop(-1) + out = complex_cat([skip_connection, out]) + out = decoder(out) + out = out[..., 1:] + mask_real, mask_imag = out[:, 0], out[:, 1] + mask_real = F.pad(mask_real, [0, 0, 1, 0]) + mask_imag = F.pad(mask_imag, [0, 0, 1, 0]) + if self.masking_mode == "E": + + mask_mag = torch.sqrt(mask_real**2 + mask_imag**2) + real_phase = mask_real / (mask_mag + 1e-8) + imag_phase = mask_imag / (mask_mag + 1e-8) + mask_phase = torch.atan2(imag_phase, real_phase) + mask_mag = torch.tanh(mask_mag) + est_mag = mask_mag * mag_spec + est_phase = mask_phase * phase_spec + # cos(theta) + isin(theta) + real = est_mag + torch.cos(est_phase) + imag = est_mag + torch.sin(est_phase) + + if self.masking_mode == "C": + + real = real * mask_real - imag * mask_imag + imag = real * mask_imag + imag * mask_real + + else: + + real = real * mask_real + imag = imag * mask_imag + + spec = torch.cat([real, imag], 1) + wav = self.istft(spec).squeeze(1) + wav = wav.clamp_(-1, 1) + return wav From 15c1d1ad947b655ca0d92058a1dc21e23ebbecad Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:52:11 +0530 Subject: [PATCH 364/375] fix batchnorm eval() mode --- enhancer/models/complexnn/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/enhancer/models/complexnn/utils.py b/enhancer/models/complexnn/utils.py index 1b6ff78..0c28f9b 100644 --- a/enhancer/models/complexnn/utils.py +++ b/enhancer/models/complexnn/utils.py @@ -125,6 +125,10 @@ class ComplexBatchNorm2D(nn.Module): self.Var_rr.lerp_(batch_var_rr.squeeze(), exp_avg_factor) self.Var_ri.lerp_(batch_var_ri.squeeze(), exp_avg_factor) self.Var_ii.lerp_(batch_var_ii.squeeze(), exp_avg_factor) + else: + batch_var_rr = self.Var_rr.view(vdim) + batch_var_ii = self.Var_ii.view(vdim) + batch_var_ri = self.Var_ri.view(vdim) batch_var_rr += self.eps batch_var_ii += self.eps From 40e8722014b0e12ef47638046975a00951000b70 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:52:35 +0530 Subject: [PATCH 365/375] fix o/p shape --- enhancer/models/dccrn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/models/dccrn.py b/enhancer/models/dccrn.py index c6ee837..72d8a23 100644 --- a/enhancer/models/dccrn.py +++ b/enhancer/models/dccrn.py @@ -325,6 +325,6 @@ class DCCRN(Model): imag = imag * mask_imag spec = torch.cat([real, imag], 1) - wav = self.istft(spec).squeeze(1) + wav = self.istft(spec) wav = wav.clamp_(-1, 1) return wav From 1a4102cc53d66525f0dc732b5c0c824185968ce2 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 10:53:08 +0530 Subject: [PATCH 366/375] dccrn --- enhancer/cli/train_config/model/DCCRN.yaml | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 enhancer/cli/train_config/model/DCCRN.yaml diff --git a/enhancer/cli/train_config/model/DCCRN.yaml b/enhancer/cli/train_config/model/DCCRN.yaml new file mode 100644 index 0000000..3190391 --- /dev/null +++ b/enhancer/cli/train_config/model/DCCRN.yaml @@ -0,0 +1,25 @@ +_target_: enhancer.models.dccrn.DCCRN +num_channels: 1 +sampling_rate : 16000 +complex_lstm : True +complex_norm : True +complex_relu : True +masking_mode : True + +encoder_decoder: + initial_output_channels : 32 + depth : 6 + kernel_size : 5 + growth_factor : 2 + stride : 2 + padding : 2 + output_padding : 1 + +lstm: + num_layers : 2 + hidden_size : 256 + +stft: + window_len : 400 + hop_size : 100 + nfft : 512 From 77699ce7f90dc17a9904dd3c6955e528f4a2100d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 11:15:30 +0530 Subject: [PATCH 367/375] fix tests --- tests/models/complexnn_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/models/complexnn_test.py b/tests/models/complexnn_test.py index 8c18ed5..524a6cf 100644 --- a/tests/models/complexnn_test.py +++ b/tests/models/complexnn_test.py @@ -1,8 +1,8 @@ import torch from enhancer.models.complexnn.conv import ComplexConv2d, ComplexConvTranspose2d -from enhancer.models.complexnn.norm import ComplexBatchNorm2D from enhancer.models.complexnn.rnn import ComplexLSTM +from enhancer.models.complexnn.utils import ComplexBatchNorm2D def test_complexconv2d(): @@ -12,7 +12,7 @@ def test_complexconv2d(): ) with torch.no_grad(): out = conv(sample_input) - assert out.shape == torch.Size([1, 32, 128, 14]) + assert out.shape == torch.Size([1, 32, 128, 13]) def test_complexconvtranspose2d(): From 6573bc4c5e7b1798c8747c499f65210be9e7993d Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 11:33:00 +0530 Subject: [PATCH 368/375] ensure num_channels --- enhancer/models/dccrn.py | 8 ++++++++ enhancer/models/demucs.py | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/enhancer/models/dccrn.py b/enhancer/models/dccrn.py index 72d8a23..7b1e5b1 100644 --- a/enhancer/models/dccrn.py +++ b/enhancer/models/dccrn.py @@ -261,6 +261,14 @@ class DCCRN(Model): def forward(self, waveform): + if waveform.dim() == 2: + waveform = waveform.unsqueeze(1) + + if waveform.size(1) != self.hparams.num_channels: + raise ValueError( + f"Number of input channels initialized is {self.hparams.num_channels} but got {waveform.size(1)} channels" + ) + waveform_stft = self.stft(waveform) real = waveform_stft[:, : self.stft.nfft // 2 + 1] imag = waveform_stft[:, self.stft.nfft // 2 + 1 :] diff --git a/enhancer/models/demucs.py b/enhancer/models/demucs.py index e5fa945..fafb84e 100644 --- a/enhancer/models/demucs.py +++ b/enhancer/models/demucs.py @@ -204,9 +204,9 @@ class Demucs(Model): if waveform.dim() == 2: waveform = waveform.unsqueeze(1) - if waveform.size(1) != 1: - raise TypeError( - f"Demucs can only process mono channel audio, input has {waveform.size(1)} channels" + if waveform.size(1) != self.hparams.num_channels: + raise ValueError( + f"Number of input channels initialized is {self.hparams.num_channels} but got {waveform.size(1)} channels" ) if self.normalize: waveform = waveform.mean(dim=1, keepdim=True) From 6626ad75e71f8639549e372ccad0f856f1ceb373 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 11:34:21 +0530 Subject: [PATCH 369/375] fix tests --- tests/models/demucs_test.py | 2 +- tests/models/test_dccrn.py | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/models/test_dccrn.py diff --git a/tests/models/demucs_test.py b/tests/models/demucs_test.py index f5a0ec4..29e030e 100644 --- a/tests/models/demucs_test.py +++ b/tests/models/demucs_test.py @@ -30,7 +30,7 @@ def test_forward(batch_size, samples): data = torch.rand(batch_size, 2, samples, requires_grad=False) with torch.no_grad(): - with pytest.raises(TypeError): + with pytest.raises(ValueError): _ = model(data) diff --git a/tests/models/test_dccrn.py b/tests/models/test_dccrn.py new file mode 100644 index 0000000..96a853b --- /dev/null +++ b/tests/models/test_dccrn.py @@ -0,0 +1,43 @@ +import pytest +import torch + +from enhancer.data.dataset import EnhancerDataset +from enhancer.models.dccrn import DCCRN +from enhancer.utils.config import Files + + +@pytest.fixture +def vctk_dataset(): + root_dir = "tests/data/vctk" + files = Files( + train_clean="clean_testset_wav", + train_noisy="noisy_testset_wav", + test_clean="clean_testset_wav", + test_noisy="noisy_testset_wav", + ) + dataset = EnhancerDataset(name="vctk", root_dir=root_dir, files=files) + return dataset + + +@pytest.mark.parametrize("batch_size,samples", [(1, 1000)]) +def test_forward(batch_size, samples): + model = DCCRN() + model.eval() + + data = torch.rand(batch_size, 1, samples, requires_grad=False) + with torch.no_grad(): + _ = model(data) + + data = torch.rand(batch_size, 2, samples, requires_grad=False) + with torch.no_grad(): + with pytest.raises(ValueError): + _ = model(data) + + +@pytest.mark.parametrize( + "dataset,channels,loss", + [(pytest.lazy_fixture("vctk_dataset"), 1, ["mae", "mse"])], +) +def test_demucs_init(dataset, channels, loss): + with torch.no_grad(): + _ = DCCRN(num_channels=channels, dataset=dataset, loss=loss) From 47cfc84295639acc2b646b493f41fb5f5ec4639a Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 12:01:20 +0530 Subject: [PATCH 370/375] add si-snr --- enhancer/loss.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enhancer/loss.py b/enhancer/loss.py index 32b30cf..ec753d4 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -3,6 +3,7 @@ import logging import numpy as np import torch import torch.nn as nn +from torchmetrics import ScaleInvariantSignalNoiseRatio from torchmetrics.audio.pesq import PerceptualEvaluationSpeechQuality from torchmetrics.audio.stoi import ShortTimeObjectiveIntelligibility @@ -188,4 +189,5 @@ LOSS_MAP = { "si-sdr": Si_SDR, "pesq": Pesq, "stoi": Stoi, + "si-snr": ScaleInvariantSignalNoiseRatio, } From 82308750dc67758368e58281c2d525042f65a7eb Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 12:28:25 +0530 Subject: [PATCH 371/375] add direction si-snr --- enhancer/loss.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/enhancer/loss.py b/enhancer/loss.py index ec753d4..75527bb 100644 --- a/enhancer/loss.py +++ b/enhancer/loss.py @@ -66,7 +66,7 @@ class Si_SDR: raise TypeError( "Invalid reduction, valid options are sum, mean, None" ) - self.higher_better = False + self.higher_better = True self.name = "si-sdr" def __call__(self, prediction: torch.Tensor, target: torch.Tensor): @@ -183,11 +183,34 @@ class LossWrapper(nn.Module): return loss +class Si_snr(nn.Module): + """ + SI-SNR + """ + + def __init__(self, **kwargs): + super().__init__() + + self.loss_fun = ScaleInvariantSignalNoiseRatio(**kwargs) + self.higher_better = True + self.name = "si_snr" + + def forward(self, prediction: torch.Tensor, target: torch.Tensor): + + if prediction.size() != target.size() or target.ndim < 3: + raise TypeError( + f"""Inputs must be of the same shape (batch_size,channels,samples) + got {prediction.size()} and {target.size()} instead""" + ) + + return self.loss_fun(prediction, target) + + LOSS_MAP = { "mae": mean_absolute_error, "mse": mean_squared_error, "si-sdr": Si_SDR, "pesq": Pesq, "stoi": Stoi, - "si-snr": ScaleInvariantSignalNoiseRatio, + "si-snr": Si_snr, } From 3cbd0ba7cc5555ed09d11183a1d0b1845973a0df Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 13:00:34 +0530 Subject: [PATCH 372/375] mv coeff to device --- enhancer/utils/transforms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py index fbdb8f9..2acbb08 100644 --- a/enhancer/utils/transforms.py +++ b/enhancer/utils/transforms.py @@ -85,6 +85,7 @@ class ConviSTFT(ConvFFT): input = torch.cat([real, imag], 1) out = F.conv_transpose1d(input, self.weight, stride=self.hop_size) coeff = self.window.unsqueeze(1).repeat(1, 1, input.size(-1)) ** 2 + coeff.to(self.device) coeff = F.conv_transpose1d(coeff, self.enframe, stride=self.hop_size) out = out / (coeff + 1e-8) pad = self.window_len - self.hop_size From ed210a8c60ef9f35c1dc243ab71873c80da7a17f Mon Sep 17 00:00:00 2001 From: shahules786 Date: Mon, 7 Nov 2022 16:00:47 +0530 Subject: [PATCH 373/375] mv coeff to input device --- enhancer/utils/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py index 2acbb08..f8e4b50 100644 --- a/enhancer/utils/transforms.py +++ b/enhancer/utils/transforms.py @@ -85,7 +85,7 @@ class ConviSTFT(ConvFFT): input = torch.cat([real, imag], 1) out = F.conv_transpose1d(input, self.weight, stride=self.hop_size) coeff = self.window.unsqueeze(1).repeat(1, 1, input.size(-1)) ** 2 - coeff.to(self.device) + coeff.to(input.device) coeff = F.conv_transpose1d(coeff, self.enframe, stride=self.hop_size) out = out / (coeff + 1e-8) pad = self.window_len - self.hop_size From e941235ec0cda9cdb37b197157e8bc9b91797184 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 10 Nov 2022 10:34:48 +0530 Subject: [PATCH 374/375] mv coeff to device --- enhancer/utils/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enhancer/utils/transforms.py b/enhancer/utils/transforms.py index f8e4b50..5af1f92 100644 --- a/enhancer/utils/transforms.py +++ b/enhancer/utils/transforms.py @@ -85,7 +85,7 @@ class ConviSTFT(ConvFFT): input = torch.cat([real, imag], 1) out = F.conv_transpose1d(input, self.weight, stride=self.hop_size) coeff = self.window.unsqueeze(1).repeat(1, 1, input.size(-1)) ** 2 - coeff.to(input.device) + coeff = coeff.to(input.device) coeff = F.conv_transpose1d(coeff, self.enframe, stride=self.hop_size) out = out / (coeff + 1e-8) pad = self.window_len - self.hop_size From d90db16bce9e6fa8081e3e0d16694fb44f17eb34 Mon Sep 17 00:00:00 2001 From: shahules786 Date: Thu, 10 Nov 2022 10:35:50 +0530 Subject: [PATCH 375/375] remove hawk files --- hpc_entrypoint.sh | 39 --------------------------------------- setup.sh | 13 ------------- 2 files changed, 52 deletions(-) delete mode 100644 hpc_entrypoint.sh delete mode 100644 setup.sh diff --git a/hpc_entrypoint.sh b/hpc_entrypoint.sh deleted file mode 100644 index 4c77127..0000000 --- a/hpc_entrypoint.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -set -e - -echo '----------------------------------------------------' -echo ' SLURM_CLUSTER_NAME = '$SLURM_CLUSTER_NAME -echo ' SLURMD_NODENAME = '$SLURMD_NODENAME -echo ' SLURM_JOBID = '$SLURM_JOBID -echo ' SLURM_JOB_USER = '$SLURM_JOB_USER -echo ' SLURM_PARTITION = '$SLURM_JOB_PARTITION -echo ' SLURM_JOB_ACCOUNT = '$SLURM_JOB_ACCOUNT -echo '----------------------------------------------------' - -#TeamCity Output -cat << EOF -##teamcity[buildNumber '$SLURM_JOBID'] -EOF - -echo "Load HPC modules" -module load anaconda - -echo "Activate Environment" -source activate enhancer -export TRANSFORMERS_OFFLINE=True -export PYTHONPATH=${PYTHONPATH}:/scratch/c.sistc3/enhancer -export HYDRA_FULL_ERROR=1 - -echo $PYTHONPATH - -source ~/mlflow_settings.sh - -echo "Making temp dir" -mkdir temp -pwd - -#python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TRAIN --output ./data/train -#python transcriber/tasks/embeddings/timit.py --directory /scratch/$USER/TIMIT/data/lisa/data/timit/raw/TIMIT/TEST --output ./data/test - -echo "Start Training..." -python enhancer/cli/train.py diff --git a/setup.sh b/setup.sh deleted file mode 100644 index 43adc89..0000000 --- a/setup.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -echo "Loading Anaconda Module" -module load anaconda - -echo "Creating Virtual Environment" -conda env create -f environment.yml || conda env update -f environment.yml - -source activate enhancer - -echo "copying files" -# cp /scratch/$USER/TIMIT/.* /deep-transcriber

d5o3r~ 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%0Ag)LBbnPp7f6-y?IaUKg99ZJ*Om(+c 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%0AGEz&{xdYw@-rC+)o;+yx-+Di@ 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