From 30b7ff0357f24f74c727b52d1c078db9fd5968ad Mon Sep 17 00:00:00 2001 From: David Rotermund <54365609+davrot@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:23:38 +0200 Subject: [PATCH] Add files via upload --- gui/GUICombiData.py | 44 +++++++ gui/GUIEvents.py | 113 ++++++++++++++++++ gui/GUIMasterData.py | 26 +++++ gui/GUIMasterGUI.py | 25 ++++ gui/gui_alphabet.py | 214 ++++++++++++++++++++++++++++++++++ gui/gui_contour_extraction.py | 102 ++++++++++++++++ gui/gui_logo.py | 31 +++++ gui/gui_outputmode.py | 102 ++++++++++++++++ gui/gui_sparsifier.py | 156 +++++++++++++++++++++++++ gui/gui_yolo_class.py | 143 +++++++++++++++++++++++ gui/logo/ISee2.png | Bin 0 -> 31166 bytes 11 files changed, 956 insertions(+) create mode 100644 gui/GUICombiData.py create mode 100644 gui/GUIEvents.py create mode 100644 gui/GUIMasterData.py create mode 100644 gui/GUIMasterGUI.py create mode 100644 gui/gui_alphabet.py create mode 100644 gui/gui_contour_extraction.py create mode 100644 gui/gui_logo.py create mode 100644 gui/gui_outputmode.py create mode 100644 gui/gui_sparsifier.py create mode 100644 gui/gui_yolo_class.py create mode 100644 gui/logo/ISee2.png diff --git a/gui/GUICombiData.py b/gui/GUICombiData.py new file mode 100644 index 0000000..c2408f5 --- /dev/null +++ b/gui/GUICombiData.py @@ -0,0 +1,44 @@ +from gui.gui_yolo_class import GUIYoloClassData +from gui.gui_contour_extraction import GUIContourExtractionData +from gui.gui_alphabet import GUIAlphabetData +from gui.gui_outputmode import GUIOutputModeData +from gui.gui_sparsifier import GUISparsifierData + + +class GUICombiData: + def __init__(self) -> None: + self.yolo_class = GUIYoloClassData() + self.contour_extraction = GUIContourExtractionData() + self.alphabet = GUIAlphabetData() + self.output_mode = GUIOutputModeData() + self.sparsifier = GUISparsifierData() + + self.gui_running: bool = True + + def update(self, input) -> None: + self.yolo_class.update(input.yolo_class) + self.contour_extraction.update(input.contour_extraction) + self.alphabet.update(input.alphabet) + self.output_mode.update(input.output_mode) + self.sparsifier.update(input.sparsifier) + + def check_for_change(self) -> bool: + if self.yolo_class.data_changed is True: + return True + if self.contour_extraction.data_changed is True: + return True + if self.alphabet.data_changed is True: + return True + if self.output_mode.data_changed is True: + return True + if self.sparsifier.data_changed is True: + return True + + return False + + def reset_change_detector(self) -> None: + self.yolo_class.data_changed = False + self.contour_extraction.data_changed = False + self.alphabet.data_changed = False + self.output_mode.data_changed = False + self.sparsifier.data_changed = False diff --git a/gui/GUIEvents.py b/gui/GUIEvents.py new file mode 100644 index 0000000..270f30e --- /dev/null +++ b/gui/GUIEvents.py @@ -0,0 +1,113 @@ +import tkinter as tk +from tkinter import ttk + +from gui.gui_logo import GUILogoGUI +from gui.gui_yolo_class import GUIYoloClassGUI +from gui.gui_contour_extraction import GUIContourExtractionGUI +from gui.gui_alphabet import GUIAlphabetGUI +from gui.gui_outputmode import GUIOutputModeGUI +from gui.gui_sparsifier import GUISparsifierGUI +from gui.GUICombiData import GUICombiData + + +class GUIEvents: + tk_root: tk.Tk + exit_button: tk.ttk.Button + + def __init__(self, tk_root: tk.Tk, confdata: GUICombiData | None = None): + super().__init__() + assert confdata is not None + + width_element: int = 10 + width_label: int = 20 + width_button_extra: int = 5 + + self.tk_root = tk_root + self.tk_root.title("Percept Simulator 2023") + + self.confdata: GUICombiData = confdata + self.confdata.gui_running = True + + self.logo = GUILogoGUI(self.tk_root) + + # frame -> + self.frame = ttk.Frame(self.tk_root) + self.frame.pack() + # <- frame + + self.frame_left = ttk.Frame(self.frame) + self.frame_left.grid(row=0, column=0, sticky="nw") + + self.frame_right = ttk.Frame(self.frame) + self.frame_right.grid(row=0, column=1, sticky="nw") + + # gui_element_list -> + self.gui_element_list: list = [] + + self.gui_element_list.append( + GUIContourExtractionGUI( + self.frame_left, + name="Contour Extraction", + row_id=0, + column_id=0, + data_class=self.confdata.contour_extraction, + ) + ) + + self.gui_element_list.append( + GUIOutputModeGUI( + self.frame_left, + name="Display Options", + row_id=1, + column_id=0, + data_class=self.confdata.output_mode, + ) + ) + + self.gui_element_list.append( + GUISparsifierGUI( + self.frame_left, + name="Sparsifier Options", + row_id=2, + column_id=0, + data_class=self.confdata.sparsifier, + ) + ) + + self.gui_element_list.append( + GUIAlphabetGUI( + self.frame_right, + name="Alphabet", + row_id=0, + column_id=0, + data_class=self.confdata.alphabet, + ) + ) + + self.gui_element_list.append( + GUIYoloClassGUI( + self.frame_right, + name="Yolo", + row_id=1, + column_id=0, + data_class=self.confdata.yolo_class, + ) + ) + + # <- gui_element_list + + self.exit_button = ttk.Button( + self.frame, + text="--> EXIT <--", + command=self.exit_button_pressed, + width=2 * (width_label + width_element + width_button_extra), + ) + self.exit_button.grid(row=2, column=0, sticky="we", columnspan=2, pady=5) + + # windows close button -> + self.tk_root.protocol("WM_DELETE_WINDOW", self.exit_button_pressed) + # <- windows close button + + def exit_button_pressed(self) -> None: + self.confdata.gui_running = False + self.tk_root.destroy() diff --git a/gui/GUIMasterData.py b/gui/GUIMasterData.py new file mode 100644 index 0000000..07b7200 --- /dev/null +++ b/gui/GUIMasterData.py @@ -0,0 +1,26 @@ +class GUIMasterData: + data_changed: bool = False + data_type: str = "" + do_not_update: list[str] = ["do_not_update", "data_type"] + + def __init__(self) -> None: + self.data_type = str(type(self)).split(".")[-1][:-2] + + def update(self, input) -> None: + to_update: list[str] = [] + + something_todo = getattr(input, "data_changed", None) + if (something_todo is None) or (something_todo is False): + return + + for vars in dir(self): + if vars.startswith("__") is False: + if not callable(getattr(self, vars)): + if (vars in self.do_not_update) is False: + to_update.append(vars) + + input_name = getattr(input, "data_type", None) + if (input_name is not None) and (input_name == self.data_type): + for vars in to_update: + data_in = getattr(input, vars, None) + setattr(self, vars, data_in) diff --git a/gui/GUIMasterGUI.py b/gui/GUIMasterGUI.py new file mode 100644 index 0000000..96519b4 --- /dev/null +++ b/gui/GUIMasterGUI.py @@ -0,0 +1,25 @@ +import tkinter as tk +from tkinter import ttk + + +class GUIMasterGUI: + my_tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame + + verbose: bool = True + + def __init__( + self, + tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame, + name: str, + row_id: int, + column_id: int, + data_class=None, + ): + assert data_class is not None + super().__init__() + + self.my_tk_root = tk_root + self.data = data_class + + self.frame = ttk.LabelFrame(self.my_tk_root, text=name) + self.frame.grid(row=row_id, column=column_id, sticky="nw", pady=5) diff --git a/gui/gui_alphabet.py b/gui/gui_alphabet.py new file mode 100644 index 0000000..a96e9f1 --- /dev/null +++ b/gui/gui_alphabet.py @@ -0,0 +1,214 @@ +from gui.GUIMasterData import GUIMasterData +from gui.GUIMasterGUI import GUIMasterGUI +import tkinter as tk +from tkinter import ttk + + +class GUIAlphabetData(GUIMasterData): + phosphene_sigma_width: float + size_DVA: float + clocks_n_dir: int + clocks_pointer_width: float + clocks_pointer_length: float + selection: int = 0 + + tau_SEC: float = 0.3 + p_FEATperSECperPOS: float = 3.0 + + def __init__(self) -> None: + super().__init__() + + self.phosphene_sigma_width = 0.18 + self.size_DVA = 1.0 + + self.clocks_n_dir = 8 + self.clocks_pointer_width = 0.07 + self.clocks_pointer_length = 0.18 + + +class GUIAlphabetGUI(GUIMasterGUI): + def __init__( + self, + tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame, + name: str = "Alphabet", + row_id: int = 0, + column_id: int = 2, + data_class=None, + ) -> None: + super().__init__( + tk_root, + name=name, + row_id=row_id, + column_id=column_id, + data_class=data_class, + ) + width_element: int = 10 + width_label: int = 20 + width_button_extra: int = 5 + + # patch_size -> + self.label_size_DVA = ttk.Label( + self.frame, text="Patch size [DVA]", width=width_label + ) + self.label_size_DVA.grid(row=0, column=0, sticky="nw") + + self.entry_string_var_size_DVA = tk.StringVar( + value=f"{self.data.size_DVA}", + ) + + self.entry_size_DVA = ttk.Entry( + self.frame, + textvariable=self.entry_string_var_size_DVA, + width=width_element, + ) + self.entry_size_DVA.grid(row=0, column=1, sticky="nw") + # <- patch_size + + # tau_SEC -> + self.label_tau_SEC = ttk.Label( + self.frame, text="Patch decay [s]", width=width_label + ) + self.label_tau_SEC.grid(row=1, column=0, sticky="nw") + + self.entry_string_var_tau_SEC = tk.StringVar( + value=f"{self.data.tau_SEC}", + ) + + self.entry_tau_SEC = ttk.Entry( + self.frame, + textvariable=self.entry_string_var_tau_SEC, + width=width_element, + ) + self.entry_tau_SEC.grid(row=1, column=1, sticky="nw") + # <- tau_SEC + + # p_FEATperSECperPOS -> + self.label_p_FEATperSECperPOS = ttk.Label( + self.frame, text="Patch prob [1/s pos]", width=width_label + ) + self.label_p_FEATperSECperPOS.grid(row=2, column=0, sticky="nw", pady=[0, 15]) + + self.entry_string_var_p_FEATperSECperPOS = tk.StringVar( + value=f"{self.data.p_FEATperSECperPOS}", + ) + + self.entry_p_FEATperSECperPOS = ttk.Entry( + self.frame, + textvariable=self.entry_string_var_p_FEATperSECperPOS, + width=width_element, + ) + self.entry_p_FEATperSECperPOS.grid(row=2, column=1, sticky="nw", pady=[0, 15]) + # <- p_FEATperSECperPOS + + # Phosphene vs Clock -> + self.radio_selection_var = tk.IntVar() + self.radio_selection_var.set(self.data.selection) + + self.option0 = ttk.Radiobutton( + self.frame, + text="Phosphene", + variable=self.radio_selection_var, + value=0, + ) + self.option0.grid(row=3, column=0, sticky="w", pady=5) + + # sigma_width -> + self.label_sigma_width = ttk.Label( + self.frame, text="Sigma Width", width=width_label + ) + self.label_sigma_width.grid(row=4, column=0, sticky="w") + + self.entry_string_var_sigma_width = tk.StringVar( + value=f"{self.data.phosphene_sigma_width}", + ) + + self.entry_sigma_width = ttk.Entry( + self.frame, + textvariable=self.entry_string_var_sigma_width, + width=width_element, + ) + self.entry_sigma_width.grid(row=4, column=1, sticky="w") + # <- sigma_width + + self.option1 = ttk.Radiobutton( + self.frame, + text="Clock", + variable=self.radio_selection_var, + value=1, + ) + self.option1.grid(row=5, column=0, sticky="w", pady=[15, 0]) + # <- Phosphene vs Clock + + # clocks_n_dir -> + self.label_clocks_n_dir = ttk.Label( + self.frame, text="Number Orientations", width=width_label + ) + self.label_clocks_n_dir.grid(row=6, column=0, sticky="w") + + self.spinbox_clocks_n_dir = ttk.Spinbox( + self.frame, + values=list("{:d}".format(x) for x in range(1, 33)), + width=width_element, + ) + self.spinbox_clocks_n_dir.grid(row=6, column=1, sticky="w") + self.spinbox_clocks_n_dir.set(self.data.clocks_n_dir) + # <- clocks_n_dir + + # clocks_pointer_width -> + self.label_clocks_pointer_width = ttk.Label( + self.frame, text="Pointer Width", width=width_label + ) + self.label_clocks_pointer_width.grid(row=7, column=0, sticky="w") + + self.entry_string_var_clocks_pointer_width = tk.StringVar( + value=f"{self.data.clocks_pointer_width}" + ) + self.entry_clocks_pointer_width = ttk.Entry( + self.frame, + textvariable=self.entry_string_var_clocks_pointer_width, + width=width_element, + ) + self.entry_clocks_pointer_width.grid(row=7, column=1, sticky="w") + # <- clocks_pointer_width + + # clocks_pointer_length -> + self.label_clocks_pointer_length = ttk.Label( + self.frame, text="Pointer Length", width=width_label + ) + self.label_clocks_pointer_length.grid(row=8, column=0, sticky="w") + + self.entry_string_var_clocks_pointer_length = tk.StringVar( + value=f"{self.data.clocks_pointer_length}" + ) + self.entry_clocks_pointer_length = ttk.Entry( + self.frame, + textvariable=self.entry_string_var_clocks_pointer_length, + width=width_element, + ) + self.entry_clocks_pointer_length.grid(row=8, column=1, sticky="w") + # <- clocks_pointer_length + + # button_configure -> + self.button_configure = ttk.Button( + self.frame, + text="Configure", + command=self.button_configure_pressed, + width=(width_label + width_element + width_button_extra), + ) + self.button_configure.grid(row=9, column=0, sticky="w", columnspan=2, pady=5) + + # <- button_configure + + def button_configure_pressed(self) -> None: + self.data.phosphene_sigma_width = float(self.entry_sigma_width.get()) + + self.data.size_DVA = float(self.entry_size_DVA.get()) + self.data.tau_SEC = float(self.entry_tau_SEC.get()) + self.data.p_FEATperSECperPOS = float(self.entry_p_FEATperSECperPOS.get()) + + self.data.clocks_n_dir = int(self.spinbox_clocks_n_dir.get()) + self.data.clocks_pointer_width = float(self.entry_clocks_pointer_width.get()) + self.data.clocks_pointer_length = float(self.entry_clocks_pointer_length.get()) + + self.data.selection = int(self.radio_selection_var.get()) + self.data.data_changed = True diff --git a/gui/gui_contour_extraction.py b/gui/gui_contour_extraction.py new file mode 100644 index 0000000..ec6a095 --- /dev/null +++ b/gui/gui_contour_extraction.py @@ -0,0 +1,102 @@ +from gui.GUIMasterData import GUIMasterData +from gui.GUIMasterGUI import GUIMasterGUI +import tkinter as tk +from tkinter import ttk + +import torch +import torchvision as tv + + +class GUIContourExtractionData(GUIMasterData): + sigma_kernel_DVA: float = 0.06 + # sigma_kernel: float + # lambda_kernel: float + n_orientations: int = 8 + + # padding_x: int + # padding_y: int + # padding_fill: int + + def __init__(self) -> None: + super().__init__() + + # self.calculate_setting() + # self.padding_fill: int = int( + # tv.transforms.functional.rgb_to_grayscale( + # torch.full((3, 1, 1), 0) + # ).squeeze() + # ) + + # def calculate_setting(self) -> None: + # self.sigma_kernel = 1.0 * self.scale_kernel + # self.lambda_kernel = 2.0 * self.scale_kernel + # self.padding_x: int = int(3.0 * self.scale_kernel) + # self.padding_y: int = int(3.0 * self.scale_kernel) + + +class GUIContourExtractionGUI(GUIMasterGUI): + def __init__( + self, + tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame, + name: str = "Contour extraction", + row_id: int = 0, + column_id: int = 0, + data_class=None, + ): + super().__init__( + tk_root, + name=name, + row_id=row_id, + column_id=column_id, + data_class=data_class, + ) + width_element: int = 10 + width_label: int = 20 + width_button_extra: int = 5 + + # orientations -> + self.label_orientations = ttk.Label( + self.frame, text="Number orientations", width=width_label + ) + self.label_orientations.grid(row=0, column=0, sticky="w") + + self.spinbox_n_orientations = ttk.Spinbox( + self.frame, + values=list("{:d}".format(x) for x in range(1, 33)), + width=width_element, + ) + self.spinbox_n_orientations.grid(row=0, column=1, sticky="w") + self.spinbox_n_orientations.set(self.data.n_orientations) + # <- orientations + + # scale_kernel -> + self.label_scale_kernel = ttk.Label( + self.frame, text="Scale sigma [DVA]", width=width_label + ) + self.label_scale_kernel.grid(row=1, column=0, sticky="w") + + self.string_var_scale_kernel = tk.StringVar( + value=f"{self.data.sigma_kernel_DVA}" + ) + + self.entry_scale_kernel = ttk.Entry( + self.frame, textvariable=self.string_var_scale_kernel, width=width_element + ) + self.entry_scale_kernel.grid(row=1, column=1, sticky="w") + # <- scale_kernel + + # button_configure -> + self.button_configure = ttk.Button( + self.frame, + text="Configure", + command=self.button_configure_pressed, + width=(width_label + width_element + width_button_extra), + ) + self.button_configure.grid(row=2, column=0, sticky="w", columnspan=2, pady=5) + # <- button_configure + + def button_configure_pressed(self) -> None: + self.data.n_orientations = int(self.spinbox_n_orientations.get()) + self.data.sigma_kernel_DVA = float(self.entry_scale_kernel.get()) + # self.data.calculate_setting() + self.data.data_changed = True diff --git a/gui/gui_logo.py b/gui/gui_logo.py new file mode 100644 index 0000000..54089ce --- /dev/null +++ b/gui/gui_logo.py @@ -0,0 +1,31 @@ +import tkinter as tk + +from PIL import Image, ImageTk +import os + + +class GUILogoGUI: + logo: tk.Canvas + logo_image: int + my_tk_root: tk.Tk + + pic_path: str = os.path.join("gui", "logo") + + def __init__(self, tk_root: tk.Tk): + self.my_tk_root = tk_root + + logo_filename: str = os.path.join(self.pic_path, "ISee2.png") + pil_image = Image.open(logo_filename) + self.pil_imagetk = ImageTk.PhotoImage(pil_image) + canvas_width: int = pil_image.width + canvas_height: int = pil_image.height + + self.logo = tk.Canvas(self.my_tk_root, width=canvas_width, height=canvas_height) + self.logo.pack() + + self.logo_image = self.logo.create_image( + 0, + 0, + anchor=tk.NW, + image=self.pil_imagetk, + ) diff --git a/gui/gui_outputmode.py b/gui/gui_outputmode.py new file mode 100644 index 0000000..f842655 --- /dev/null +++ b/gui/gui_outputmode.py @@ -0,0 +1,102 @@ +from gui.GUIMasterData import GUIMasterData +from gui.GUIMasterGUI import GUIMasterGUI +import tkinter as tk +from tkinter import ttk + + +class GUIOutputModeData(GUIMasterData): + + enable_cam: bool = True + enable_yolo: bool = True + enable_contour: bool = True + enable_percept: bool = True + + +class GUIOutputModeGUI(GUIMasterGUI): + def __init__( + self, + tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame, + name: str = "Output Filter", + row_id: int = 0, + column_id: int = 3, + data_class=None, + ) -> None: + super().__init__( + tk_root, + name=name, + row_id=row_id, + column_id=column_id, + data_class=data_class, + ) + + width_element: int = 10 + width_label: int = 20 + width_button_extra: int = 5 + + # option0 -> + self.option0_var = tk.IntVar() + self.option0_var.set(int(self.data.enable_cam)) + self.option0 = ttk.Checkbutton( + self.frame, + text="CAM", + onvalue=1, + offvalue=0, + variable=self.option0_var, + command=self.selection_changed, + width=(width_label + width_element + width_button_extra), + ) + self.option0.pack(anchor=tk.W) + # <- option0 + + # option1 -> + self.option1_var = tk.IntVar() + self.option1_var.set(int(self.data.enable_yolo)) + self.option1 = ttk.Checkbutton( + self.frame, + text="YOLO", + onvalue=1, + offvalue=0, + variable=self.option1_var, + command=self.selection_changed, + width=(width_label + width_element + width_button_extra), + ) + self.option1.pack(anchor=tk.W) + # <- option1 + + # option2 ->bool(self.option0_var.get()) + self.option2_var = tk.IntVar() + self.option2_var.set(int(self.data.enable_contour)) + self.option2 = ttk.Checkbutton( + self.frame, + text="Contour", + onvalue=1, + offvalue=0, + variable=self.option2_var, + command=self.selection_changed, + width=(width_label + width_element + width_button_extra), + ) + self.option2.pack(anchor=tk.W) + # <- option2 + + # option3 -> + self.option3_var = tk.IntVar() + self.option3_var.set(int(self.data.enable_percept)) + self.option3 = ttk.Checkbutton( + self.frame, + text="Percept", + onvalue=1, + offvalue=0, + variable=self.option3_var, + command=self.selection_changed, + width=(width_label + width_element + width_button_extra), + ) + self.option3.pack(anchor=tk.W) + # <- option3 + + def selection_changed(self): + self.data.enable_cam = bool(self.option0_var.get()) + self.data.enable_yolo = bool(self.option1_var.get()) + self.data.enable_contour = bool(self.option2_var.get()) + self.data.enable_percept = bool(self.option3_var.get()) + + self.data.data_changed = True diff --git a/gui/gui_sparsifier.py b/gui/gui_sparsifier.py new file mode 100644 index 0000000..3872611 --- /dev/null +++ b/gui/gui_sparsifier.py @@ -0,0 +1,156 @@ +from gui.GUIMasterData import GUIMasterData +from gui.GUIMasterGUI import GUIMasterGUI +import tkinter as tk +from tkinter import ttk + + +class GUISparsifierData(GUIMasterData): + + number_of_patches: int = 10 + + use_exp_deadzone: bool = True + size_exp_deadzone_DVA: float = 1.0 + + use_cutout_deadzone: bool = True + size_cutout_deadzone_DVA: float = 1.0 + + +class GUISparsifierGUI(GUIMasterGUI): + def __init__( + self, + tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame, + name: str = "Sparsifier Options", + row_id: int = 0, + column_id: int = 4, + data_class=None, + ) -> None: + super().__init__( + tk_root, + name=name, + row_id=row_id, + column_id=column_id, + data_class=data_class, + ) + + width_element: int = 10 + width_label: int = 20 + width_button_extra: int = 5 + + # number_of_patches -> + self.label_number_of_patches = ttk.Label( + self.frame, text="Number Patches", width=width_label + ) + self.label_number_of_patches.grid(row=0, column=0, sticky="w") + + self.spinbox_number_of_patches = ttk.Spinbox( + self.frame, + values=list("{:d}".format(x) for x in range(1, 301)), + width=width_element, + ) + self.spinbox_number_of_patches.grid(row=0, column=1, sticky="w") + self.spinbox_number_of_patches.set(self.data.number_of_patches) + # <- number_of_patches + + self.label_forbidden_zone = ttk.Label( + self.frame, text="Forbidden Zone:", width=width_label + ) + self.label_forbidden_zone.grid(row=1, column=0, sticky="w", pady=[15, 0]) + # use_cutout_deadzone -> + + self.label_use_cutout_deadzone = ttk.Label( + self.frame, text="Hard Circle", width=width_label + ) + self.label_use_cutout_deadzone.grid(row=2, column=0, sticky="w") + + self.use_cutout_deadzone_var = tk.IntVar() + self.checkbox_use_cutout_deadzone = ttk.Checkbutton( + self.frame, + text="Enable", + onvalue=1, + offvalue=0, + variable=self.use_cutout_deadzone_var, + width=width_element, + ) + self.checkbox_use_cutout_deadzone.grid( + row=2, + column=1, + sticky="w", + ) + self.use_cutout_deadzone_var.set(int(self.data.use_cutout_deadzone)) + # <- use_cutout_deadzone + + # size_cutout_deadzone_DVA -> + self.label_size_cutout_deadzone_DVA = ttk.Label( + self.frame, text="Size Hard Circle", width=width_label + ) + self.label_size_cutout_deadzone_DVA.grid(row=3, column=0, sticky="w") + + self.entry_size_cutout_deadzone_DVA_var = tk.StringVar() + self.entry_size_cutout_deadzone_DVA_var.set( + str(self.data.size_cutout_deadzone_DVA) + ) + self.entry_size_cutout_deadzone_DVA = ttk.Entry( + self.frame, + textvariable=self.entry_size_cutout_deadzone_DVA_var, + width=width_element, + ) + self.entry_size_cutout_deadzone_DVA.grid(row=3, column=1, sticky="w") + # <- size_cutout_deadzone_DVA + + # use_exp_deadzone -> + + self.label_use_exp_deadzone = ttk.Label( + self.frame, text="Exponential Decay", width=width_label + ) + self.label_use_exp_deadzone.grid(row=4, column=0, sticky="w") + + self.checkbox_use_exp_deadzone_var = tk.IntVar() + self.checkbox_use_exp_deadzone = ttk.Checkbutton( + self.frame, + text="Enable", + onvalue=1, + offvalue=0, + variable=self.checkbox_use_exp_deadzone_var, + width=width_element, + ) + self.checkbox_use_exp_deadzone.grid(row=4, column=1, sticky="w") + self.checkbox_use_exp_deadzone_var.set(int(self.data.use_exp_deadzone)) + # <- use_exp_deadzone + + # size_exp_deadzone_DVA -> + self.label_size_exp_deadzone_DVA = ttk.Label( + self.frame, text="Size Exponential Decay", width=width_label + ) + self.label_size_exp_deadzone_DVA.grid(row=5, column=0, sticky="w") + + self.entry_size_exp_deadzone_DVA_var = tk.StringVar() + self.entry_size_exp_deadzone_DVA_var.set(str(self.data.size_exp_deadzone_DVA)) + self.entry_size_exp_deadzone_DVA = ttk.Entry( + self.frame, + textvariable=self.entry_size_exp_deadzone_DVA_var, + width=width_element, + ) + self.entry_size_exp_deadzone_DVA.grid(row=5, column=1, sticky="w") + # <- size_exp_deadzone_DVA + + # button_configure -> + self.button_configure = ttk.Button( + self.frame, + text="Configure", + command=self.button_configure_pressed, + width=(width_label + width_element + width_button_extra), + ) + self.button_configure.grid(row=6, column=0, sticky="w", columnspan=2, pady=5) + # <- button_configure + + def button_configure_pressed(self): + self.data.number_of_patches = int(self.spinbox_number_of_patches.get()) + self.data.use_cutout_deadzone = bool(self.use_cutout_deadzone_var.get()) + self.data.use_exp_deadzone = bool(self.checkbox_use_exp_deadzone_var.get()) + self.data.size_cutout_deadzone_DVA = float( + self.entry_size_cutout_deadzone_DVA_var.get() + ) + self.data.size_exp_deadzone_DVA = float( + self.entry_size_exp_deadzone_DVA_var.get() + ) + self.data.data_changed = True diff --git a/gui/gui_yolo_class.py b/gui/gui_yolo_class.py new file mode 100644 index 0000000..09f55ac --- /dev/null +++ b/gui/gui_yolo_class.py @@ -0,0 +1,143 @@ +from gui.GUIMasterData import GUIMasterData +from gui.GUIMasterGUI import GUIMasterGUI +import tkinter as tk +from tkinter import ttk + + +class GUIYoloClassData(GUIMasterData): + default_value: str = "--: None" + + available_classes: list[str] = [ + "--: None", + "00: person", + "01: bicycle", + "02: car", + "03: motorcycle", + "04: airplane", + "05: bus", + "06: train", + "07: truck", + "08: boat", + "09: traffic light", + "10: fire hydrant", + "11: stop sign", + "12: parking meter", + "13: bench", + "14: bird", + "15: cat", + "16: dog", + "17: horse", + "18: sheep", + "19: cow", + "20: elephant", + "21: bear", + "22: zebra", + "23: giraffe", + "24: backpack", + "25: umbrella", + "26: handbag", + "27: tie", + "28: suitcase", + "29: frisbee", + "30: skis", + "31: snowboard", + "32: sports ball", + "33: kite", + "34: baseball bat", + "35: baseball glove", + "36: skateboard", + "37: surfboard", + "38: tennis racket", + "39: bottle", + "40: wine glass", + "41: cup", + "42: fork", + "43: knife", + "44: spoon", + "45: bowl", + "46: banana", + "47: apple", + "48: sandwich", + "49: orange", + "50: broccoli", + "51: carrot", + "52: hot dog", + "53: pizza", + "54: donut", + "55: cake", + "56: chair", + "57: couch", + "58: potted plant", + "59: bed", + "60: dining table", + "61: toilet", + "62: tv", + "63: laptop", + "64: mouse", + "65: remote", + "66: keyboard", + "67: cell phone", + "68: microwave", + "69: oven", + "70: toaster", + "71: sink", + "72: refrigerator", + "73: book", + "74: clock", + "75: vase", + "76: scissors", + "77: teddy bear", + "78: hair drier", + "79: toothbrush", + ] + + value: list[int] | None = None + + def __init__(self) -> None: + super().__init__() + self.do_not_update.append(str("default_value")) + self.do_not_update.append(str("available_classes")) + + +class GUIYoloClassGUI(GUIMasterGUI): + def __init__( + self, + tk_root: tk.Tk | tk.ttk.Labelframe | tk.ttk.Frame, + name: str = "Yolo", + row_id: int = 1, + column_id: int = 0, + data_class=None, + ) -> None: + super().__init__( + tk_root, + name=name, + row_id=row_id, + column_id=column_id, + data_class=data_class, + ) + + width_element: int = 10 + width_label: int = 20 + width_button_extra: int = 5 + + self.selection: tk.ttk.Combobox = ttk.Combobox( + self.frame, width=(width_label + width_element + width_button_extra) + ) + self.selection["state"] = "readonly" + self.selection["values"] = self.data.available_classes + self.selection.pack() + self.selection.bind("<>", self.selection_changed) + self.selection.set(self.data.default_value) + self.selection_value = None + + def selection_changed(self, event) -> None: + temp: str = self.selection.get() + if temp[0] == "-": + self.data.value = None + else: + self.data.value = [int(temp[0:2])] + + if self.verbose is True: + print(f"Class changed to: {self.data.value}") + + self.data.data_changed = True diff --git a/gui/logo/ISee2.png b/gui/logo/ISee2.png new file mode 100644 index 0000000000000000000000000000000000000000..1ac6b5c3d96f08a9ed2bf30fe803aa7079f7b0f0 GIT binary patch literal 31166 zcmeEtbx>SQ_vR2J6Lg5+fkA@1yA1A5a2p06oZyxaT!RF6*WeDp-GY07V8Mb1-^u%V z?N)95s&@bTm}2VQ?$b}7^YrP{eeUfERZ)`0LMK88003CBG7@S4z!Po6wJh)n;@b{) zJPiQAMEBItc2P5N13Ng`n_Jn!z%CvRFfh#B${YZ2U(8Q3aH6cj3S9FDBMAVDy%)sx zD|PxnB0^?vW-+($S+T5|Dw>lj#Ez2)d&lUpb)D?)2Q5*pc4^z}K=ivqt6v4ZR~CZf z&o4ZDedWUUoG%*o#cp>Bdh$nXd>?Hud=e`6Gl|;`VZSQws#Wwdakh`iT<*)(d>PLd zXuTvzmmV0WMKHF`eZiTieO_hSUNuJ1tmC&gO^=Gm9i5p+a^pzD-Xgcx4MYn|AAOwU zXyl^=chXh?)sNdTPwnZ)>Ef{4r!yHI%EsSrDGp?wp5J%ZuX`5V^t0Rt4PCX6Sq|R+ z*rm};5jGkW`J~@r^yn@mbIn1o{L^niXKBaa=DxY^Go1Wk;34j=Wyxo3gf2)+%|Imh zhJWKM>KG~bW|j%08`)=X^301gJ@~jLt1F5@iB!Is>U(oeHEbZ^td&_OuxKyPtX0+u zDaFWkK~0%K!1pIk)8JQ!9kh!5fY+QI&{&Hl(zovKYz|sKR#Ka=ttWQ)26nWq8?vC{ zj=z~04!4)fuXZE_6ZQC8byU3<%8z$AqL(KQ;^z7TN*yf$ToPIy*#P3V8+AgjM)F~Y0z9qr9VKjivz)H*8`;-Li z&!Kbhmc#b`SmcDL45yz}Q%WM6kh;|F89lqUcT0zE-no|n7agR20V&=p@;GV_lDATk zrRGNRA;YqcBLGI!S0J8X0EDKWky}+UDh-M!aQa!EVqhZ)?${%$5}oI?h0zjYDDk@K z5u?HQhS+Y;Pm-C}#V>T(Z}XC7x_*Df6XaDo5l6%^GyIUA76Tnl)d%0Hr9=GP|Nci1E-apV$X3%J5{YxyIMOMl3KUdp>hHU-Pw#!+P^F9i!d17~S#mzo$e+VR>$TyI z^?0z|S%q{FwSBTD@VYW>E|6NNSVXGY240_6*Oz}2s=!{y?i_EbeiX{9lIqwRsZ#}% z3=`1{9t)=ICnyzuOlzu-3x1QJTC%MtP+a`ZoV2B$9li0(@=?Kn$GhNXbVS38d~>|+jCJa-sPt$sX&r)}q_+hIUCnC@Usm(8g3GnWlwg&m z6@G;@#C6I;NJLyp3v!%H0%Bn;*I#80fUL-J@tR6Ci%9Kj-MX5E2bfNA0^05k)lyBK z13&cZ-IyA#&=vsh0pbJaV{&?FBsM$@AHw6ab*Y_R<<0@95iXj=2RM?CVs5ufm?xgbhNosG3-;7t zY*C&sJBGMH9lV5sjp-^RY3h9`__LPBZasC!G)@=M2LdDx&uZX)W>sfs&<7<7Rl=%U%%R|MnyPAaQB%qIgZL`p_rl!O`P zi-vKHo=vah`HU?Y0v=dtbL7wWi~yd0T0*KjgrSB6gh54cK)%IMi*pvxTa%L-TGKMlFYJn%)af0j9{k?#IpFYuif|ZS_Gf|$V zA>V1A70|k5CX8y_(DNmTI!zEzQOPr*%us?ZT#K2whu5B-yUy+SZ{7M|k+W7={^q9) ztGUCp)$P5w)s?Q1PaAMA*Qfho$#i3^k0R54bYWH6lvUDl?Jj_9BanO#~f@lwMLX#{HmrlHIvUH$^v^-RZQ z`$Ot){=|+TizPkc*56!B#a;XgGD{UGLONuE8a z)sLnnzRUE-j>K?5pCNaV%J#P`&n9)sKK@>&>ug!be`|j{i@ce;#BOx_g zlSS1ljKnvS;+H5_t^5#$Y^oG>2V7K&n>C6Vbew%!Zo*;vmZ z;#6R-HYWKbw%`9!<)=Gph*MK|RfKs)#g8@~_lP~t+%DTOz$KG;<2TWwD8u2X5g6aP ziqvAPZ{N$Rm0|LApdeNl%F?UqfN}t_kS#G_cF$j1T13P&WVBBJ+Z|2kHo%9sX(7?n z;+?_yXW2MA9|~k@6iW1#4;}qFYRE4+woHG|9oUc&t@kBN=vg&kaP=DWbS=JChW%D5 zc|Gg}Ch|gK%^D4>nR!I5LXRaMTh--9iz!flX|vDnSyE`n2Se?S{Ou9Zzf?IZs)a2J zjs`9~XR*@fXd?5m*S#chU^fv6UuxV&RmXPG218gRo}*cDm1GL73BNia`FcmIY;Sa` zM#vgg%N2`5vtJPXT`L)%e|kX0u|@X>xRP3nd?Z!GM({~*=rlFn>r+(@b=9Ic*e9Lt zkT^ASoFr~5A6@Vd^(eb*(wSVx{-I`fgP@3@4^0Q_PsE9%MfoU+#|{ARrCSVACq6C8yI#+QqK}Xj!5^91`9-lDj9S zXusf}fMBuoDEQA2{MM9k{O$Kz&W&t*M4x{jnpiO>{!8O46(iPtrd;{_t;J`#PKf0} zyUm*rg0E0UbdCOq^`NR$sf8aAcWEl}{1Ixj#O3dAbVH6REYWA73WrSHwZB1OCP$*K ztU1UEyka_Wz*7M=jD*|`{m|I~x}NUg?4ciDShT(9P$Jm;Zw5T#aGsWn38Ei!dM3FP za43NYRG8~Yy~1u+G$5Iwwe(r3Q%QVV$6)IsNll%Q>+ev~9;__9fJ?-YF6){Y&VTI2=+F@8h$FLkE`{3t2)5)?joZHVB!h%m! z!))`{d(ovS>J`bxMsI4JCGfC7+8@M(}3I8SJCW8v?nAND$4^;kuRwMU0bj)n(`@Lp~Nxw z^@3kkF-vVY>Wp5SZ8Eu63=P#;QOk}$vR4Uk?v3i@NrF_8Gr@$~AUder4CzLs#Ie5{ z|GCrKAF|O7zzT}--(K5X-@@)VWEKUbQoG~nEme(be@SvZ^>lF!`4SPiP!K*9(X=0x zkLmtfk;e2~X*rCTRgN@OK6>AUG_&CblmY?27!BsT@ryP`39a7gKcT=;;14G4Mq#t9 za8&s8U+XCBK||E%%lHI6F*=4UC^GHqnWSq8aTQ_!7!ng z#I?Rn4+)lBn+m{cvuq*qq`$U`N}ay0?%UL(CaN%|ZukmG<#`aupwR2{lx5y<+Z)@udkdr%4|6&-Wbdz;SA z=-!eK;WZW574N~nkv4vAa%u zfnqfIQS6*N&^I*sri`}WJ74?J`sThtgoQ`ut@AmpM;wxH0PmPfH-#Zh#%it0*liE4 z)~A;n&Kr&&m}Tw;To<-@#w3I91vyO3zB%{cnr6@UIT6=<%6_hlEB#6{*V}Iv_VEnQ zqD~(+>UD}Abhdca2?P*o+Gb&5u<3HCDdbs+tgn;3yQ63_bAtQFLP=j0 z#gK^nCc8VDWmHgVlwC)exHerP%N2FNx16@Uy`8TorV;&4*!WHNLGE?ZHC5Z}w;XBF zV{Qk>m2W?UzB@f7@ci~h+$j&B=$8#>&79=f@(<9<@|rm;Z;GCJ3KloWxAXrXZd@j_ zU_*M0U3YbPoNVqVBB$Y$Xsi|-hRELBtq@t7wxR+*)E>@kVrFj&V|Ir-AhI|BKv2Zp z!31gpa{-&eEUfH=D2`h?D8N=`LKJVg6j>D=#9@|JGM-K_bx$P?sHY8-&x}Gu7+uhv z9{~V|xtM_6;kI_p{O&>&f8p{YuKyIXP=Nmuaj_Ai&{k9di`zTFz?{sS%&bh3?pE*E zDTL9%f=*`U{Av|9LTne3b?|3Lf=LjvXub+U4Bv9h-V|G_jdwRd$9qM$(3ga4r)+(A+CpYV3he`f)~ z2aCIj0}C57D+?UX^6xF2T_oQjK>n`K|FMO$1|r#IQG+?#yE;K(lJ8)4E|mWcVFvwY zdk0r1+rP{)gR;PEVQ_?~Gon|v|L9U$R#D}jE&fnoVFh>is}+Lm|B!UCGXED@|DoHT zn!n8XcSR7*|B3q_(*LmiFJXj~q9VV9J=FD&da@Ei6o2~XH?xOYneqR12{mEk<$-at zGx2b9a58bS!OWQW%vjBtcul!XdCa)kIn4Pu{|!pk&e_Gp4hs7Ng#c%^Lg27+n(}e+ znwl{2@Nsf5aUuqn$&{Oyhlz*Hl$+0#kC%td+~nUNl%1>)UTI?c?^^wVGDAQ?*;#qm zxj1>4cv<<_5!#r;m`u%C`Iw;GP&Qr|7aOaYx#?d}W>9`9dndRFVmPhfCKfOj2Rnl|0uWmKez%Hw>cLl8@D-=nHd)k6Q?N;ACn0m4?B|?n+Xhx z@FfmTUf6%4JKLMPxS2S?#4HdzBDg}h=U-fbY5y81y8o1RvxNN_1q5MCtlUihJ7Fw; zH<;y5%=m}Jf-L_VCxU+o{9BMgwEJ5bB6uNUAH{}BWKBjNvg*Z(uR(EpWA!R!#bAU8y^q>J+S29ao? znaWE`03QE*vf2vc5GBtXWOSSX0A|WR|48onBJU7|PhDgcC7+&mv;lxke;K+yqBet)am^6ic!aK*l<|D(92>hBbmcO ziAh^|#F6)zKp@+>Aq!ZM3kbZ41mC)Hl_FnDRj9r3aB2P3H+ zySckhT&?Iel-SKYzI{}Ad>_H~ODq7_RIBvk{@hfdQi4+27kqq?fd-yQ5C~dT&h&d) z{iL4^2L}@fC2aQC$+WbvJqlhd8o8bdm7C+Y`%s`nDOqVsoXI0O-@ z-45ueYUM#j?%6vvnHO$uluV9W6&wIO)GcQhNEPyH|8+WS&cE_l=ts0rk7c@ikV>6mnIju`g*DUERONRkZR1$A z$zZhSek#lNTqm|`)ZU>Z!FxXDkh@)Edg{eelXG*a&4a*NGCEqm$SsGxjZFT{lzMPTR7|VPKXXq?yilZQC6h=}T58hY z9l%vq|I+SfT*fIRbg^%Qzvj`Ol44qa_|s!xfAnB!?&j|7wX$I6ifXs`z>DbHtAoM& zZ65RDJc(RI8ao8VlT=-|xdkzDl2&kd-&TiT(b#zO!}q7XYY&OZ)S^IUXVRLo#bgfk zWK6u?s9fn|(35Tg7FknX1vR=BEoeNqxH*F6&}cznpm|RHs4X!M<>?v^f-pYE9G6q7 z8b*K6>1}=hlGVNz3FZ?Z5@h!L{D8&RM2p^y5uGBC7ki$YGS8~V4ncGZ(YjApuvi8= zJ^;}7on4q@4b-)`Vd~+Yq*V%lFyrn2KK(3^>4{Fk>GpH_+^rotK8_5CSXek6iFk<) zuNMax130mA5f(|r0m%rMk_)UBxM7$80C^8k_EXP_z5HwP9#%zzIWB_+MtL60z9cHy zPqahTjPjpo!|uc@hpn$$)5lx#XzdohdE7O=L};sr{hA&dXQ!Z$e4L}7Z3y;Nd+Klw z!9M57#0&Dk1gGnC=N)FlCN*`yrd5BE0env)mn;@_I_2Nu6 zG0(O-9Rvu#BwtREu_YszZ*$&RHE3yYc_&4Q>-$3WGY)zWYmo_~D=CB_N=OdqoR(k+ z-wwC>L(4oTuCQerdBWh`DNo14#eOBrBn~ucGbE?c)%bgRyuHrsMQrTbHTTU-zf+cc zxsN-%fuP`%u)gHMEwzP%Qv3E$@ZE@f=I;X<5f9_z)!;-7k4k(GK|5)kWCcfWL67hL zV67u30`ZYL$!$5Do3UOHXgj5d_zPP6Ru;6s{(r z7P_;^&N~lBnGbvEXFF!HpNQb}{vcCe6&L{Uy%F(E9yEU(_PbHeHvYAK7uKGLS6nwk zK#B(7U9h#XWvq7H@47zY5P7KL62juc2EXKY%1dS}Kp1Sffh}Q~IP6TLTwv&{)8dZH zT0kP0C?yCHNUf;zrIim+xX^0GPqKv-jWR^*Pl4M4HJSGzpp4 zG|tzj=REBuiVBT=wTpvs+sh<0vMb{vj~NQqIB|T;aib5rJ0dN@>}txccCD6-Bzs2( zFHwZw(vFOcmzJR8LO@RdwE6y3!Q9_aOqZ+uI?j(DePGsM>Mu*R)9xOwF0#o#qqa#$ zVR~PbK14;elnB}eMf~Bq!v8~;BA)OI;IH|(JK6#BWOatP zM;jZ^%~Bb!#tgc-S+M7uMZPFvyn2V9Q;17gbxYgI{csS`hCgO}CBtQ#o?)7*^?3Zt z`kgX^&^vsT(b?ON@Y8O%HGgxYd8(oK%F@rUw%}w}9OQ)V0kRkft|W-nHHKC^%1AL# zjg?@rttCd`6Qpy71_0&Fknz6TY|FekKTVKIFlj*RONkvNKn9J0^V+@EJH6(>jG99! z6*ET8Aulkb^DeMD2sTw%8wmvn@~2rN1uRcbMO~Xx3$PO&ggh&0V`mbUdRr#xy=y z=;*i_Hi|l4bd4T_kSmN&pL!E!U4+Jz_e)`+phAZqgKe0(GIkH5nvwM=fV1CCp(`J_ z;@=$9%W*f?k(+#3T5S)IF}CRu*1Iz8UNtM5yz%D(y*?Q8Y49HZiT33JkD?68>I+(> z{jWm;n_8er}SM zEldeQj^;{8 zc&2cBQkaNcs~3&6?1)8-Tg7nwz5A0*Dk7FQOP!TYm(;^sXs*jE!7E591*1%itRmaX{U z*nE1q;qcEBgpV8zRPUdDmoj}))}gT^L$esNIH%(?C8f}HBonxAX-4eQOC)7Q`W6(D zIXbAbvvXzZNnb1WCIooBJJNW$bwq(rK4M-Y@;Fo3eiHqtqCS(* zb{)YM4C+ft{n7_xkxYCV99)Q4cOTXsHvF!CgtJuaxTGbe-HqY+{faYs_yp>5z6)DP z0CfSr5|{)U7|aHv+>Y|#uX%F}U>ILB$$%o9JZ^prCJ==g+Pf@qPafNUGx@VXq5u$a zgI_z@J&z!awiT6lS9H1>uJ{!2D-6@)A=d>@dE)rk(k4z~l66#XnI){l{d!A*`)b2^ zoOH3FqqIi-Q=VeIuI9{VBdzN=9 z?oWRQ{Qh82GfWpO(dO=W@MBO1WI5Qj}~{`)7Ays9`;pixOWY${sWwh|kZm4>p`S#ANeuu;Lx0(HJW#M-& zYey@HZS=z8S_k#9g1COG_w`Q4jb^c$4mp#~r;eLm7Gbx$Vr|Yawt5BUcP8t>BKL#D zg-T6V;6V1KWgOqrxYv3O)GtCkQww1!h?V-`!rq8OVX94u5UyPBds34ryC3| z4o`ny`}LvyS{lJ;fMpCl8hNAcoXe?)l#KH{&z~e<`UG62WecIpFI6u~C@AP_zir!K zsGKlG0t7+U7w5H)KX*LeJ@moxT<^|TIa;dQFYYIMTp#zj3=uBm7~8Pv-MuFxUW@COmw|CpQwXcZemF4!kIW? zK(JYSeO-Of`_9Kr`v>!O4U)oX_g!1V;ym{4K9?h#(WD-G*EM9YMQ*JQ`>+~UpGgqgZRyZ^ybFpS zju3vS4hu^#TUE52@dhaYZIpu%*McU137ZIg2K9Q1#xYuF+m4Q7=W3Tf?EKwM|_fyzMnxc_*E<99JPE_8p+#6cI>t7i4iceVaCBQvfEFTnvKvJm&C zLjqq)?)}_NO3suV_FYKPMMUvQnI0e4Dvgw+lrC4a5edfCxv43K50qWJFERTl*)-KV ztG!MaFZPwD@P7-S0g?JC`~0C(SI;*`F@U0$dOygcBgKKI6cpSwWi2r@Ngqhbj^{N> z`BIy7Kvi(nrDhz8@XgqbEdr=@Z}vIzp?*l#+_)o2iKgvR9?l^E4>p3KQi3e1o5ERtxT=pO4FY2>VzZXK((n6?`RRHd%k@jL$s6 z!fea>h}+esSAVcl7p`~zE>K0D;$%zY_XnRAw~p(}CHzW#(q>0-9!&34%1AaX`p=JN zez%pK#@}~yrL``J5*fCxdq3XXdSuW9n$_rd3T%mM)8c)cxz-I8kJy54AwlzzZsdeMv{r!QR|Mze5HsY}aY=)&%tq&BJ*h67QUi7PiEG88Y71AZeS88o`o zKJ&@29sTt$%0ooBSg9!#78B1sN5%?uuxgL*lO|_bzAC|&&wt#}TjO`z+dw<}R$V4? zn_KzL)kPwAa%oTEQy#L{)#cSiR0L@oC$IbZx85my=A^{l6{+A}r)#qItNV5)A^6ck zJ?Lk1?C{P$&zOaC6WPN-p?%w#Quhr(kfV>wt8rB+tYQ5e7K8k)ORJ*3P{b@j)q zHNR4mT5RMjtQ`qw+;1GW$An&m&G&x35d=U2CIq_>hoPe;tQ#BL19RRq1O zNA;MAk|k%@2ikhw&M1`XJv@+DMzrSMK!D!9+4UOdsP$H>_-{ynvO(+1>rl2n0{MC`{^k!60eh1}Uepk3uSQWB z7u&v*u;t~Q9evf15nw+xcYN-+ zVoy%j=w?HkalSou6DhxSgAyO9+~(z{^l|Iw8!@w``u2|Bp4K~2i!(L=*6}fe&|GO~ z%N6HHT&?GM*_41?SlyMyy=78?Rmzibd$TRkqJmis#qbKh=>P zuz(6gVXCaC&XYoe>*D;{d4Ezc&>`|>0FYZ&jJt|K5?rFWRQ+t6JX-lH!OEm(5MQ8! z54AY}w}*fq8yYVLWDN#D89uqZv8RB5$ai^N7Z$#M+XO;3R2n!($O8j5S%0#+O_x>0 zwxDXs&Hf(EsaOb-0<^l~j8hIJL8lTrgFs(Na^feK0$Yj&HhIEHqK72+wKNI83O)fe zegp$49j&e&e*bW%OPwV&VbFXJbRwx$7$Oh_ItiLaEZ~)o^96%`cu4G)(3@a-AmMhU z@l)pMQA0q^6Pz+O!4`85J-Zj)#m0+x21(^ez#-HrI4Tt29J3P(SMN8GA2+21hNzkA zn0-Jw`c;1(01`v_I&G=*Hhm;#dtQE!67?B!U)h^|ia-ZV)8PbRi+Cgy30!fY0(8)- zdv-Vm3kk(v&7Y4JIL|&%p8e$W;zzb>CI}rg-tozE6x*d~T{b87uK*AX6NwcILWdFf z(I=Y9N7%%%qChKM&zuj9m-HD0J7zTn`La^tO9dc@i`ux*rwGK>quqrg7eE2_Vnv=% zNx#we{1ibR6x4i~Mg%1Hx7{*8JR#|nvuX`-$4%yUwJp(13%5{TFXH9b?;?Pp0FdJW zh7iGMmziwaVsQfG_aPuB=|v%GgEbV~C;pOb?e-lVHS*&-NXTkbjFJ?fuHZR1fX5l5 z^$Z?C7gp`uAC+-*WUy3!yj*{gx^{n*5^SCFZu{<9S@5xyHq4)`-;r^tIX#VgKK~27 z4l@)0faxqz#_J~bk{@x4KV8f^eoNDACf1R;^P9SK<&a)a{cvGVjvJQ5k8Acaa`^R% zZ*TY7i5NcIKv-v;;SI;jSQMnvl5}~ihb$0KKH|Nt+$hZDtLR%J5V;C-N>yIhiFSF? z83&g^4W%PrqJ|tQq6JCtn1TiY>fk$NFfB!dZR*SFn~gG}2pPCRJ48x(v^YxK2PwOB zu2SpJR!&_oEj63>`RBO9RfyIVDoUo23&|%#6?_Yu`1djj)`#aqq#0^bB-`_xbt>}X zHV#d6VZob{n7A=Q`ff&FtBRphGWt@$S57z6mV6{}{0%4sfY>DO?=q%NWhj8%gA;sq zPgV*?!4y&!dG^niCn3-K2}(7~LSxnPZN&gx&x%`kvfMo&+Scvd9IE**T`q1mn8Luu z5KD3kg=kG{O@YLi23xF`)l#$+CfHhX=6Z!>kR;oSA@pO?PRO%5%r1iOyz{G@Hv6Zt z&n*bX&fWPqD(mb|>T12-Bts(-HSb*5q{@5vpPG918E>}Ys9Ho0?Q*+tV0LP?7Ib1biTNiXeis1{pu#Xnxl)>(LE zwJq(OoPfUS&ZIQzq>z?ZG{h`6)4U9m>rIQbJfRgfEY<1$e0Daan`5x*4Ax>KsP01T z{`O3hvb_FqmmzIh75IdF1Xh=PBDYqbHBR$OP}r%J zNhk~3V5o^buWfj4X7lqa;mjG9jIsDLysj^~{Vh=Y9()kc1jBj82LE*H{VUm!+vpxj zHbOv*ZjpgtI@3TdBEY#|1Xw-CSK7`6*BU6pejyozD84&sVg!3&LCU1Qt?t^qjk2PM@D_KSQT6;ShZ1N=&FepY&hjfDhb?CG^w&HH@4kAxd zt6*%PsG-;-Q)&`GHu$?kyd;#pD`XSIVjTVXt!Dppnnbv?+HcF{E|LuolF*^}faK6> z5!!lJlpHqc7>7DrJa12`irU)S*Qwfg&!m5-Xa|A#xg%-Xsz(ygrOF{M;2E>nj~n`R zT6K&3_r339Q`GOpvdg>im1%+EfGk=Q$@Q5MxL%&U7cc0O9O}x>i#FkG-MUj9pWdM! z$D+D!7P{qWZB+7!#iJLnv3gpFO1uLVwGEdZD3%BO1YnjN-Sd6JTQS~2`Ym{JcYfpU^cDeLVSkpneED}bzY59-RdqH zT_3^d3LLyU^6XNY>2KRWib5Oiag*q`{vdkN0DM-c|7HxWW3^hlZ}Eii_a$f6qZ9PH zl2<@kf==CD(s@E$l(PN$aT;pfR{wxbjD;YwvHcg#UgCn$+tfDi16#b14fNh8_oi&D zd!cnj1WcDT?;F>)@MtjZ)=N^T!p79G`tz84OvH+o8`Zv}PA{(SA7N~9=$ebIwbN`| zgg2XGJh3|jy0?0h+i;SECy5Vo+dtNaR4m1_s&2bud$dBBg?ly)W+3Pk%e@sB(GhsS(#3y-k_}}IEY~GN0>V39f zs*s@0S6nSZr}()p%y>ixQk1JEM)$@)54eUr8M&5JJu#J2EyGV=)_DEnhd5 zsb`X-JG%+I9P@|P7(H5Uf32FQ?B}P4t*{S%t!K?Qwq*4o+j=qQkbavzP=be!_BfjP zupm$7<%?Y3M{sp@iFWre%JU2|pQ0fAc+OGT`p`Fhpm1L-8wIsV*e1(&z?LH#8s(h5RVQ)$l1YVN3Le?s);TSv`WK)ILPTKd%doRPhIo zxkPG)?TKx5CFs5NXhtf%*F9vd9?0TU6Srl(aIivjdLyaRx2>jtRjM_zP%OuJv^W1e zq%UFGd(_~m7^Za4S$^yK1x8h1KYRM*dTRk66MXfk-MD&`gIm8WL9tR9-pRpOOuxzr$(zzBbqO zblJij>t{PCN$gJoq2J~&a%k5Ny*83H4!04DiLLATZvw#kfk;f4v(-wqC7ZoAH5=CR z&|xne8Va_L_>QeJFXY{;tz38264}5EB>80)U@GA6#jTMITc(BWg?5_R*{#gfiso6l zjQ;Mo>cL~O6G?%;=I@{5?SPy^?n zN;B6PHkZP-r3M>6!4wrD7E5qaYQ-fyT*5Rg&!PIMw5eR3HC}bl#yS!*=*dW~j6!yN zNBv)%fc zA3?PamBCup{Kr{RqgPDhX@c!^Cz98@;#!3W{x3@-Sg0eroF{Pq{jehT`x39 zTyDO0*9SgM-x^kSjUEzqEH!2#!;UQXK)2N-+0o*PdI`eR%yNC<)Wv3oRv)+4@+mhT z*0l}>lCUAl?xv|qmRk4;saFY9-p}&flWp>6F9Ei}m{MdSTl5g0%_Fp`KI-d%%k|;h zjytyRfqEO$Vf&b&JukgHyWiHz^!MvB+HCJdvwbpyU9@^}U>7bQD88UC+`f%_e}0;s zHWvVCblLs1zZd_Tcn&X!+2YnM5L;w+a`o_StG-?^j+n!Cc~dXp1%}bm`bjAMsuCY| z+tt=ZpM(r%E+?N(dOB*v^`gKNA-ylOLTM5MXLs56w^xS?HC4==jwJ2FdGx?+8QxaE z@r=pAXR08g>Now)VP#)tl!!aV*Jftw1|umh9w31+;9rvtX0FH|Wq6lY76y;1Yi|LJ z3i`|nrx@7c#Mdo6Ha@MHc9*Ug##g6bHoq*DGX#FI9$Xg0Wk;*Fs*C4YNhr%+N=BWT zc-_{0f=V^Q+64Lgt7hUr^p>(VDB%6?Nh z{E3B5Ax&GVF=8xnd4sZ)Q{ArO!@nm&gKZc--!jNYK`nIpuVQ?2c3VE&{JW!x@@}%2_qd(x&rrahXY33a%<<31 zgey1JU1tbi(-*%A-ceoC;sC`{eH`AYMJ(;B-Au8K?A~ua%M(`jSHG5{8!o&!c)yNu z0s>4q6!gh&cF%~)9IVJm;5w5iPze#_S}vdUoE_%$Jfo1N+vH7>EnY7=osTUCPV~{->9A zyK?V-*&4>o`jSpLR2YmzVZG(We(har079(WpuV&Dofna`u~cLAGR*lkSA)`sH{WqA z9j(9PGC!$36ZQX)-P13!dUl(+)%NGr{juCrCmc_s7q!(Hx1;(>zjYp)-&B%9Np(W| zbK<|fN-UqX?sW>Cc}?Q}_7`nNxscC`DRl<^q5_*or1wv40 zb;8iV@yvV0fm(6}@Hk_M=MoWYBL%L7u^$2(Kh|uu?t`@C6Rl!Yv{$cWzG)inSd_Q{ zGxJ8u>GNF+>AKOVogqb=DgaSl`Xj#hUpFXq*0`PwU!PL;5P;*^zJmSZ*`hvxAk8GQ z5_Gu{VlMk-Y{9G>UzXDEzbIKr5}2Y8qB5kO5x}yG&ga$Pq?IbANdW)IohEqfc@7a! z)}Rfk&Rn(YRHw~1Hyi$~^NdhVTiVUo*n%@fWvrF2V<-g4zL(7X{w{9ZO=6R@&OAA& zW)KbtWbY%$pSahSR~GxuJH0Zs$D9Dn?lA#8*(ZVES_EYy{D8qFI0{q#Yqmr`5$QJjuR%oTESB|Dq@2%l2v@;~yUbIFPvh4)9 zkq7J$AYZ3d%pBa-XS@MYb*)qed^nx98RBKGV+4! zimkg?AM>4Q)Ef|IxhNu#`gKYwH@`p(3nPRcZ1M9kA*}AcVwEf7LQ@SPG5ktDyFOmq z`%VpRB?_x?w-l8?Sm!S``uBTfkzY(5ijYvNGf6-7voMTB!V|*DNEAyj5=Si0_cw%iyfS;`$ z66lP9@isUZ`)J9H3@ambTkKmE$G4f4y7gxl`=m2%W6v_PvfECTCYNl5rf8A((^Q6t z<_gFgwB-7(8>Vci6TsOjKOSgy#ekk|5LwnXUolV>>^WpNWpNRY96O<{mDVwWFCYd; z?m%L?o2OgtGJ_pN)!U5>WT$ggXZG&Pk<~dl-TRnQv6!gnWYwlteKZuO`>4tqjPfkC zPnYc_Fm%0$okOSf(ryeNMCy_A3hhNKzoIqkroUr#Qd2DV1v233EK?#WY;$jGc{J-hL?`MmQrHZd}l zgToLp$e|7HSfg5+3^hd+6dpj^@b=qx)^}1T6IF>a%P%m)?HbEA)`bkmh(X@IlQX7z z{DRpb`gDYq9fG46yT$m;M~y~L$O(X94>4c{?UP^eRdR#RB{W1txn>Fi2hs6mWfmlb zNL~mTioJ#NjGb-ljYO7yr0mygI1knnXsz&hP7WT@R{P~y3h47M&<&heDE=nUVPF>F zD3}wtSCad?ajL_h(UWxwpgS)bBWyzL7bdK%qJCa}7&Lfq`a z?c5)LAMvaFP*~ipPx>_IC=b6bwzT)Eu?A*9!5P+zcFc znL5$6?$1L88}4Zi02rk%7Po{LNAM_!?Q${n#oEoPTc-O#)ws{8`h8oQZ>I9gUM`dA zz?fc_n>wD=fges60raGje2s3$&$-y~Fj4rI1nz`8VC6k>Rc@8~P3~JmC(wQrvH2{b zAd~s1%@cQrbYylC^ow_HFXY}D>Xgqg>$l(SW_JrT*!L6g6i@;~#WBU7g6ONg$*}T3 zfC%tc(DEMW+I1YXmIOpvUVe7r(EZ{ku|db7BypO`@dbv@5^}CMOq^1_YLj$%VIL!? zN5E~uoWIJq`lPXZ`1l0FFcTcYTT`S;=b^Ye1U{(`1jJH1-9 zdTY&LvUrKmQq6KFM?P+(_nzrktCzlSm9eB@nq5(&FQa1=i<42|niSER%3U{D`V1d= z@!nu#NN=Uz=BnquX>1ONXO}D9z}4k%s|xILoM#W+oY2=@`?f=++j<)X0A>|Ad>+el zBIH`wUaAvGG%S^pQ%p4cHkP`z`EwL9r5BA&s67vOsP? z3>AYQim`dM0$rSt{@V_bg>78PG3_z;3D!%7Og8#A94RVPp{c3NJ8Dw*D9@={k7EN1 zbn7MAqHV*qsZ~qsHz#BiJPwk%Revm_zCig}PnWL{-!w*=$pZHDd2hn&(7pbIoMayN z!X34Dn1NLI^K5mvr(Q#0d-cbNWX00*!w2YiJ>vPduD~5>G^;n4I-Pd3-~t4wY7+%E z?ayxB-c5lfM}a*rUn`jlOlJ=_EwxgEL`7e7a~U2TVO`lp&f`tGKF192O>=~P`Q38{ zuPcEOVbd57Q>Scegqg3s%PkbKO;O4>^Ar@^p7^M~2ls+J#KezVei~67vev9o0)KIu zWW};Dwor&~)b65`OS9sQ2spfj=-s?et|bD7fdFrDjkrSg3y=dF9-N^Ti01~)cEM0N zT6*k^ahQ4J?W+m5aNui$hHqB>vlXV(>NL`Z*B3$b*sUM*W2nG`=VBI*>+)+B@>SG;%9$c$SkmpkZadai7WOXOS^Jr;)1Or_4#QRX zQ=8ZjKUUk29~Y?>Lp8I^6GH8+YS^Fv0S@P1 z#IAcwH;6pf1ZM9^&P)vaN+9u{AOp4BLVB{Dr?#s0Q4O3iyXihhAog8z%%Cn?Ie8`x zO4Tn@XoD0;G)5s6UCtv+hB4R#|rD8D3Gj~12xr3O(rSAzAQH|CYUNP%5dBQ&sCgK z9ok#3CKtF+Z>lNvt2MW_--vqh;53h_#=Jb1ScINOP`Y@_0!T_c>FiD zNnqd`G0}xu&n<7=a{dCPFurm%Zjw|n0ApO5K$F97M?9dco?9PDYQ*Y*L0e3@rY%5Q z*^hv{hajDRDFXAA4b00khjGxGvj7AkK<>>^qMgxr3dyojs>(_TFMv;0Wvg*!{5-<2 zTYF)F2@0O2C!G7)=qmk^LgrZ;U;OJft&+q;OA4Vcl;1j-h6uoc%ww8}-cbZvko7#p zBdYcWH@-I=3_WNBt`E$Z%BMDzQ^weyy*gd&(tiW`X1zppeV&2Vwrx1(nIEFbLY!+XOBxx zBMV_fvn8Ik;fSD%7EW6q#(|Z-1oXY zZuPg++by+0rD+LXqI5bQ*tR>Ip6}_CcTh{ALGSiH3s26hV(%*)x2>!MUmlZ@kj_TF zVR;eU#Ixq%W#V`ORTil!?_eQ8cNRQ8YG+9tZqm22{a*oVE|k%7^33`3!=pC?KL4}F zkB34bc*`k;cW&GM_>m+3@~{6|KUIG8i=R);Oh5DF@t&?OpU;OdW{jDJQ79JEx!mxL z>+hU9H@CR3f7hPpo_<l-Jz7#D)!Y2nLt={99AxxfB{|0SSE3+S1DP87c#-H1RyBoOhG~r0>>tdf}2+h zi*q%_!&@dP-BW1?`faGK{5^rx1c-&boXi?GK5J zfqYtr2%Og!49!6pmGjocw^O00B#2n8*@djZ7&!OlVk98}2#Z;R7ZCu!(1~r)GjGnj zyUy#EWt6a+gT?fMMhPnyEeHXGaB`%aOKY{NtyP_~Z!Sg>GS4HmY8SGmZBYQ(*|$>3 zwld~WK4aJx4TOYB(J~Ek`i;eSYl#<7xnPZL%O@t!8Hy3|(^|1n<1QIB~NiDV(7> zsd?Sd2!!C`+u8Z)nyheztX?hI=T9yQBF?8xgyGzjs%x``P7IwAhrapp{EfCkAS5!t zs%1N~pj!rc?H4nxU5Y4iYSmtx)lH26$WFboICi}hii!w9t!8KDjatPy|5j#ZqU!bY zmO*AGYleC&OD%*Tmoi`d`E+Zi2OuyzUgJgN5Avy5-LceMS`Wu0O4;I!R?M0R!?SOt zJic6Z(a>w;t>4V?0?MTfO4;I!{;Qvis#T|>$J5>!sFaLf{dhbzqd)zr_M^u;Ls9uY z1_<6FLMapq@84hO?sifs;F1<&IGJqu)Tg_je*rFCS;t3s`%kO>gthZ$;e$Q~h!+H& z7r?R)U*LIx=O<^TM<&K8C5$luAjVkW`86zPTGs#c#V4GJ~DFR z)Txz}E&?x1&dmJnH~wFq<9t4^BubQ0!!T-^YMK_$^8(NNy*>`(+q1)jHwqw7XZQytq8MSTy4m#TaU5DIm@bFfCMz1ZPTABoM^kMGfMpR%S!;*S7Z7+JOEOn38O6L=|I%{NV#~=^ z+*i2&HVtCvWMwKFI??B}-ELn zn%%~&YPhxwt?H;%$916^8krlfxp_|EQn(IqY&tukGP-n8S9SL%+oH1*wUx|Z)shV% zV65@0A%rd^FHEWKehl|d(;y2o+8Ps^UUMdIRM@hibE)kHnI5e%zyN~~0SEzLz_IDv zxXPBd(_|M-cPj{lz|_dxgtk%>O4!1*Hb12TSNBE$0AQ$OY^bsl=>pMGYf`iN;*7Qw zs{|p0z|_g~&Dzou4K^?WRf=}GP;`3^08mU@Zj%-!^|=WREQLn_N?2yz0LyE#`3c?i zX1K#?Y^cfz@C60SasnagnNM_@CP}vV4BZ~PQD~fl?L879;`zkEgXW+A`P{d^ZQZ;H zAP{={TEF_$)?>#Y&)=hvy^l4$50Z}W>*-0x66tJag_RLP;)eb_hmb${!$17;AACL@ zi>>8y#(KKD|M*K^s?}=eE?uk-t8q0mT{m>ySRqKEF5xQ2@wbPxA%ynt-TSq#eC5CW ze}6YMGjq!fi?K&m7yvc(R*G#|lvd8CXN)Pb+&ppcJ$sSw!h;IyypOA#FsoAvE?d4? zE~D~1GIVl#s-}kDZN1#5O9kG3Cu?AZb~i}OO;sJ62E(!&B|kM@OfRUlidDaTVoeh^ zux!^iu%QH8-SADd@eUVPyHV@gTTh}5>Ht>t%m$|WRs9TCKZpQeu)!nC#`CpZ2jQB2 zyZzOU!!nDiC~?Jt2@!w|#{*m41IyxIbxt?{0bCPt=3aC|7$MxKZ(R*|P1{%UC$IVF zux{irR`;THW{&&Fsyu=&As}~eGF;thOHFq9c;zXCAb?46<#uTPL9Zegx6 zIaa!Mq42(FgsxK9{o==?d-m4m<^TYmwsucTi{SI#A=l_V|McF25$;mXJ$dvfA>`XX z_~-G-NwSIEep@;_|L6-}eDc__WIT>ggBUQ3@t$3~{=>ihx8MKKk6!-mYldmoM|Lz+ zf+2($;TN8HCKw3ZeuE;*$B!Kg1_J-^-S3{ec+nxws{UA~KuMB*?}ZlyzL{U`Aq6FD zdaR5PyypWV-V4;KJvCYu#2UhoQf8afwwpO=A0&uFM}|t%6IHF+%#wd+m>Riywm5mS zA}bgGpzEZPx9_k0Ywd=5-scVW_4&KH0RV{OzymrI!{_yOw6-N$&5{A3b!aqpeNe zFd9?f`GMZvfA^Pv`RwuIub(`5>da}~FdT=FC37o)0AP$c%wdd@iNujZhYs&QuzlOM zU?9-2K#!sv-oL-Qv-90cmtJ|}jVsr#IgVo!hb~tVLdYS^aU92Sz1`hM4j(?ae}8{Z zk1R>eIrGrsfl%Nr$xE27HD&HY1VbZ+);v9Q^I8n66zodg2Jg>Syap-OAVPTATF4&A z{y)YNiNs%h{p(-(!sn~i8f8op#c(L(@hI=z4JJ!cS7&EiYwO3JekNZi%+Aftr&9TR z-m-0skTv1n8jgnP)I(?k@K*n2XweO}_2%(!d$LIC-ba$_*Bw!3KM7;KOv_TUZzY#3m&l(MwHP_dx zSY0`6qp#fP*ZQ}}3b*&s*sk3B*9)2SE^d~yw zZ3<(I66TiU=0aLF3xTCHT=iw%N~p~kUAG~0`P>q~fUMw;eX8r(-|Lh;xV}o1f|V*U z1|kXZW1s4J;)S+Am|rP*sVK6lTNr3?@&2rwJ^;ZZ$K!#$`%**KW?p$k`O=pJpRdU{ z4=1#Ie&OUvV{C%|(jP#@`%!8d0{}v(csNnwEQ@GW+nuf$Lyu43cw}2tty%8rKZ1Y+ z3F{i+d8BwbQ+L#wgAnlf1&Cp_Y&kZC5Qs7d5j1p1@$jOA4b4%jjg3nlKQGCc5T;fv z)1-ocB!zPvrmHq(OqMxbL|V;Rb26eJVqZW20H{@4t1Xr6@$rh6V+^P@N2`0C*Uu{+ z-q0Peyy2Y?noaArO^Z35Qs(jV7{gk{_WF5A<}}r*mQ1k3RWJ}1IUc#0qaB;A^v{x* z;7}ke2ofeXtCUP#BLDzN;ba9@OO_~e{(ztxq?|Wb&IAaA1&^0^52DVj~79a%GvKRw-|;h(&*p_Xh=tV6|+Q^6oJU&>!L{1zOb!#U(*N zrGi;4*@A>)ElNwfj~8(vYvfXfp*b9nA_+;7ILji1tf8w80Kntp6fa-QnTnSShDEh% z7qZ4m&Pa$0Uqlt!n2}hTBo$m?%gnmogliVjf0fl4H|C z)}oYod|Wgka~v!c%tFRobLh0k$3+t&FJeP;QggaH1{edPh~sUtD4|lpDrAgsTx##~ zhr&uBt6L^P2u_ZaFo)9fS}-aoUapWe-9uEoK|T-`ig{D5*uk(6P0ARWiuZ}D~ahE>&^+ghcxTq)Yr0nwz`(xEU0((_to-b4tv`fGcShZj&jZOl(WMHMlY06?vWJHds8xIXW}#d(yZZvkHZLz=27qmn`I+kU zSOoyq(&_E)4Tuuw*wnV^(y4Y3Mq*M|Z*T&DO3Ciu5%&3od`6G8C>)1P!;R#N&Hw-& z2}wjjR2jcnoSUr59&X#7NF;hIGf}l{4qwhyN|t-U8{l^xi23{?LXa_5DVaB~<+CZh zt1r;rwiJKLS;lS=tcW*$Fc>sWElDX;fwF`M!!FvwH5k`!% zU|2#J)hgztb7_y4?;QwpJc`DYM;~vyel3?-P!AkQ`uw7sQlOYOE}c!~GseKKaA!|o zVW!&J;S(j!w#fCX*^$8_A*_4AziWS7@$diuU3YF?%iSC-G6uHp4sPEaRlEX((6Pzf zbmh{SjAhdON8-_h=ftn3GK+d3C_MRmSGlC0d}TfumiHe?gu?Rw-`|be5UU{TpQZ2Rg}2)@G8m*_e78 zwZNX{&L%)?1)xxSR_>9Jv3Z2MKOLefD>Eyzun-`c&3rgiATr$j`TE@7d;UFyFs1BY z{&};$=O0cwICn93;pJi~trCJchib+7!*6%(R#cqTUwgY8$J_`4cKcf6)IzFIOh__$ z;g#vt3;Ednh%w>0(d=CE^lI+rwZ_N4t0IIh|D?RIocZ$OilR!-zf{n41!ELN{Ps7E zZ@y^$=>56elp)LH(wj3cT`I+#ut<1rWVgc7yz%3o&F71zBood>ec%1$%HHOhbK%vg z7hfrD-|w7SNSjH8MdHS_`oI3B648UsB@$jbm-@*E3(2Gg2-U03otv$fE={YN41j+2 zZ%U)a`{(=DKewJd#M)Ecjhm(N@<08DJOAy! zR(|mdXMMeR`Eq7yNu8jA{dtJM(D$u-cN?F4+Wq<}TE3*d_17eu|4XVP#wa^w%*>}* z#9MVwQDwuFWtA+R$)>ZKZ3T9R<_l(_lnANV-sq^BoJgsfA-hgU14w5yKJP&OO;o~FxF1v8|~Y6qGwr!(5Z=}fy3^y^iNGv@4kng(q9ZabM)PtB)!B<|gA zGbVC{#LQfhF_FpX^{N}VtnVyH&Ya6-az?Z6c}|!}s%Anl63W8qtl*-uFnvVQ(Y@;RDCn4=8LB9MUARARWu8w#LD^H z-J9)hhZ?3F1gzQcoKC14vZ~4(>sH{ih2_lLLb|^1*zM3vDA}B0CKRg~L?NG^NiLtw zRd+qk#LJhSd}$@Oaz5|5(e*D{$+Viu zYB9{WtdLAr=Uk{7Iek7mJD0w5vr*gkvQxU?1M2kn;CTCbgELrtzA(R-Svl`s{jBlA zrNZgesb~^OLMk=GVnn9tI&<(|E1znqBjOG?Hsg$N^lJB!zK5=S+>PB5R<(X5d zWJQ!%gCP@fxHGIbjC5e<~lKG-(BxF@5KmO~5 z`Grii(%s$a&M#%otmY|Y?FLOGmE4q(&S=es->iEJOWCvMr#A1}_iuJ%^O|Omi?2^l z<;@${YW023Ovr9Gs_(m{atZ)d_niHTlT0g#l)AmysaM_krOfim)T?ilfBVbLdeu#5 z^(f@Eiqoii<$3e1cjq-t`t%Pwnl8Wc+RV$B%UkQ+SKcfwo|(G-b#;5w&JDKY_e2mP zgbB&6te*eR|7G(R|I_~0kDQygh^}EGP5crcLACevl-G=N&lf@=FTJn4@iS7K{cgOd zzB7^)lFq7k0E4$sDjjpPaisliW1d%WyXyv(_rc1Z4TRZn{ zOt5Z9Q+cycGCLMcWi+ep-?`cH9Cq%-{Oo*cw7nCd-7Wj>&30x=d*$+sZYYK!ClU(A zcz?%fHEBAlX}a9CXulwA)q>CduqQ-pe1c*rp_{U-;5frp)BEbL)LZKFk~Yk zn+c`e^sjzi69S&U$d}LNj`awZ2*Ct7-Ketd^h3U_-Ds}gwz387&3EQfDJ_@RXXca1 zlzQu%#?3!9V<6Bo=kvv~*{B^Ht*(7p-M;V4FQqQOb1IoqS~a?}VNVqds~vp(Sru^=Q3z@8j5Gu`?<(VW6*l&Kh(X7$)FL{{{wDN2!SJdKR*{Hj}{2zCd z8CCmRIhEE^83h3FArQBw$%J4e(8e9x?M5^Zt$MI}p}-mXf4|;tRRb=dSzE^h2`;P_ zU0BMb(z`}?_6TDyCzaqC(;kyPdu()pq}GiPeLBuj)4 zytnDx{Icaa{App0K3!QTNvXx9^Z)TbSwDTh`T1ww)|LqTUa;TcISXUv@k2<6!3Y>* zLPU*x{V@3hjOTMiU=B2B>Fs+l2;Vw3#1$G{-neqdT-bsW+7M%8nq=jay84PVyMz>N z7&rR+Pm)AbUE)kopD_j)14k$`rD1}jh1fP6P%Ovnm}v4j91@L3!MO>hwb@;w$s zoCYlRIEZr=9DFwlDQA(0LZRuhs!Bb-10rEnmn4Gcm(sKINsN)ANKwcM!OfcAwZr1H zi4fY~a~wPDXV@@gLa^&bVIUY25Fs-mshZ3=cy1Jh0!!F+qI?l+hAg|0qDsCS`7R47 zckJ+l;nZoANm$bl+;4;cs~H3yvpaEg3yc6b7qRX!3I*q2 zCgtUG+1a_IX2_Z@%QEQ!J;%ykuglCN3qzhrN|@kuMwKKoJ(K*|-!Ef?WJQV{j?x)b zmWkE!sn1y`>^5agD5})&lG8WMFptFEwo{r-t(?!#FJH*5 zGv{9@q%)ePNvbAe914L#Dq^P|>hq9_Oh%I>a{BDl?0gy_q-ipxT$0JHtIe6&)cFg= ze$_@58eAHv=hL93>p+gM|14( zNep;T4W=E8Yz~KF_y`DQ=+|Ru<9~;KS$*-R4xf7NTQKzB!vqI^`va`^Jl_t}_17Ja zvoxFuz;QtcBLpFg5}$xERy3q22a$FD5@R{V?TIn&xp5Qe02iNN6q{#=gfYe-px2j2 zSo0?arH=C?Qv4T&H8y|%P_ynib{K~Q3Vh}|k#5SmE^z^bU^A(x8VP(p)L)Wx5C?&f zLpY(Mm z7aj9oMPRjq&AV2-D1}t)ZMySI=>tyUy}e8@#)1Q9hw&IBf-qLp0{}%#(Cw@3ZYNy5 zFtxCpec|P48n7=vs>NT+3c37FdFf1Ucgx<{=uDRrvkU3N5eJe)Bnbl`LQrgAKG?R# zQ@*dBt{v@cb(dDA%BNChLg4~cwjEU?KmBkilhfDNTHQ{#d^VTP=tDzy_^1F7kDu6N zP6(DH%sB`S2p>O13^X*0;OZw$+X^qfR$N%lz4^|9-3d3=x(DR|0zl_p%D(%-GNtU^ z?IuF#;-#6PT;g63006MR=iFOs3jr7*--|ZZy3Kvs?SvOEPp_QIzxw(q5a@sX_xnWp z9ty&Kkw|o%X!>ycLKu*8;{;Og6&&|*hrUB5!0~SeBoIRJ-6rUJ9x4|`GSe$%AWs;S zYGVcAP>lxUF%0wB&&F^O0I}f5BmMY~ovQJWEd0TMeQ+iMpXKr-oz;AgNfK66DexKR zLe=E_wBb5YE^nxsWVQT$Kk|n-YvO#t418{wvZ_lg0@sO*#KE+Rp{_Go06^_#z?o1~ z68nzNE+ox_TCF(gjFw2MGxMqYYgR8?&xtsPR9Y*Rjlg5NDMwWJK9eMpFPgr`BpGL? z49=m`qG2d#z*1?gP%=Wwi>1WK`=2AlBOy4K*D*pnn|2fm-;JDZ7%zXlRM{yVV^pm; z8}~X(XEP_8GcZD~6LAhxMPqu_L;&U4#PKjmsjRAL()ya^d(q3U&lm|s)uhIiU}&uCZmvd+7w`n_6DPPKVz73p=5Mg;Z(sC0_|+s zcWziOzWRW*6C>1a27-e`aBbi79Hyv*5KJj=H-q^lFn=^WAmu^U@v?c_)d9`6RX(oFhs2n@= z-DtX;TsV_L7?NpKaH#HiLVzNZse&=PXxia5#QOcQ)%_wg~IGys=n`aI|mVQ+YQ=k`lWL6{ENjXLo!O0Hfg#7htA3zmAM#zd%hvjQJR&JzL!K9gj!csd*J z0BFFr);nAG?B03UY6Zn|;?zPqmC;gZ4FnwJM+TZD&a=MnTkT-FoV@sY88{$}K)}#t z?b1x*%(a0quq#cr5;6x}3-tOjVNrL9OCgO}f2dPZf-rQ>jE!#TX}2YPI5Q z-R~aMZm?(K7fvVi+$dj6TzX4hyV0^*{>)r*c_o)GnyHL-1p0^(CVi)0e2~TNrc+yW z%d^S1-<$W`P}60n8-De1b>mKFW&oM4d3Xtk=daioG(!q9$l`8|&LNlaGZ_lLC8ueLz+H5!dX3h6q)@u0kOWAinSlZd@+`ZLu zyL9n%?!$jj%!DRM2O(KqhqYV&;!^G>f3=iO@2y>LFQ3iL&ZYnM->rCVXqZZ?;s5%7 zZ@&6wxjdV6x?#Rx20q)kXW=IY6CwTl=Rcn;l;7(U(nJnE3`LX&0pnaSCY)~A*mpgb zx!sU6Ap~#^U7J>R9IF{{0SF<+c&8P34vTeBjuYB#${1jbITuzdpaGvM7-mAL?m4~} zg#mBWyq(Q1i$uKAbz0Q1L*HdIV4MkzkpSd5QLFBA4j7|a#cekOw;Lghd@pjHD86+U z+r8Xw$Qb8L(116pUMwa9KVnP(Am5Lw6=&<79e6B?M6YU{cCdZlrU7d=ecOsK!Hn@n z)pH#tDcErxV6P$aTW{zSnI>z8`VUS;SjSf8(Czdr`gWb=qNX=@bB} zN+gBQfbVYE8+SX@=cmtQbwjy(t4TxVxzWysy}s57JnnU)*gy+_0-x<|+jnlZTh#y~ zL}cuA=-!qSh5}0BE`-%cNrk_ine=uC;s0Ue3VxBF+VoFb&xKyX}re_qLt- zeqdW+KqD3jN?B#6yLP?RX@#B_2@pZR8Z~!!%f%RVyTQgCJHCO+xridRzt_EWwP`g& z775p-2m>Lw-3)fN>{i3yyl1;MbGjiS!uO(9!{5AX0|L*Z>$f|dR#@BjIA<*4j6<#J zUi-3o>uW2dLeVheY;)bN?FN8AR&m#+8+Yuk#d=y(qJU}2M8b`l_x0!dcW>C#7Xgig z5S|xRcDpyfw!$!sFX3#iTlK25zw6vzvwepLGy)K=6K-wT_paORrWY|5D{t)%-MD9M z-f<8CO_vmvSnc4()!OG*>QN}Z<$_+s73*f8wdvq8l@UNri~mhBA%og9X2lZsYU_D{q*Ejqa%xmRigq%%S9OFb8}2J z3L}rs4}p6!LS=oT-DpH60OK=%z-fM{TZar(Bl!V@BncCVa3+o{c>t0^2*Her-U z5sZ7rS4<8NI^r_~!B_}TRq4$6T(Ok6^-aCL?@1DgLf-2&LNN5HvWbLa(;bUM++VR7 zruU375-<$dfiaR52>_x{^mO>Lgk^W^Df_ps2A=l!u(Mv)I z3aRL=xv|kPzL666oU>jdBT2%VL0AMd;IWA`hJ8^~tZGseiZI|XIPcI32j`~v_|HKJ$|^*LigBDe=b zBgsh9r6?3}hhmHbKLDV?7#Rs!R&d}6>T`r3)-Omh;SAz&*h?~GA<_&<)k)}!z-KW} zLLkkM6omv{?5Boe!-3~SaVDG{3=B<|BpFkWa}GiPk#Qm=6M_SmQ$HSxpz4xgN|XxU zVLest=%rvtpd1q`2q6&Y+^gw-{P!2q8C{;76(;nCc+-`Im~E3q>JLH{4wB#8R-I!`^8XM4y*d-&Y_2`aqUB6P(2>>+oLfpiMXg zJ{vGTL9E!sQ7C%(9A$VITlfGl8qo1wpx^_3+B6P^3@GFdEsVpfmW8X?qQyPm-ApC;q(@KK)!-r=o=15^hT20Zk6 zEKLEB*JTINb1)O?a|B1J1P8kv9b|J@{V+HXickFB-9!*hXhM&rYmLO-X17)Mtyb_o zg@wQvvq-Fc)2wWF4O1Z!4k_<;BDWiU_soERZ~j!*4cTf2PfFX5$6Oa0oBycaPWt{C zblD>^gC8@`gO`sUKWp?{zz^z`QLJJ-LIe4f?siQC;e;mim$BR#jlhHMy@0ccna6%J$?8O_2zX(^Q18w26WMHX$5<5o34& zYez`T{m8px+60ffzX)TpMKCT3%ZN9FU-_Ls!d=lz{0p14Tg(> z(Tp7eUp#n30gP&p;{$SJK`_3ZVL18mj)O5TM-~L*m^J{Kk9JZ~b^7nNk%E6A9xJU^{waam0)r(}hDJkcQIsuN5N$2@Q;G7>4kC_SW^bF>IdB(i9UnS2-p`i44pF&f?^Ok;^@16ZeieWe`Wi@ZjeA? z0On^PKE~k^c`Voy8U_FJFiSs-?wzqftuR_r49`;-^%jkF(+4yf7H34nu4BV@%*s1z z;e}&NoDlB|#)Q}cc<`5_SQxj+9$(90)cQX9!_g}&jBDN@A#vhf0Ur4BXmUMh8v%dvP$v=gQ#IC-1+1Sw z+5%5XXEjKKU`ZmXCdo2UHLPej#?`~m;QA0eg;xERBOAWmCy%;(KfdY@p2=K=XE8RO zrY!jGGKFVa`V-$0JxLy()x|~l^BqX=Oh+s{qrnN^?NF4Y7_v1E;d$`#{{sN9Oja{r RlFtAD002ovPDHLkV1gJIl8pcW literal 0 HcmV?d00001