From 3d9e4977c59dbeb5c6242eda82b91ba33fa4e955 Mon Sep 17 00:00:00 2001 From: Rodrigo-P Date: Fri, 9 Aug 2019 08:32:20 -0300 Subject: [PATCH 1/3] interface atualizada --- interface/get_adc.py | 31 + interface/get_img.py | 31 + interface/interface.py | 1514 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1576 insertions(+) create mode 100644 interface/get_adc.py create mode 100644 interface/get_img.py create mode 100644 interface/interface.py diff --git a/interface/get_adc.py b/interface/get_adc.py new file mode 100644 index 0000000..31e1f8c --- /dev/null +++ b/interface/get_adc.py @@ -0,0 +1,31 @@ +import socket + + +fileName="../adc_cv.dat" +#MASTER +socket_host="172.20.10.7" +socket_port=32001 + +# Creates socket +s = socket.socket() + +# Connect +s.connect((socket_host, socket_port)) +# Send start message +s.send("Start message".encode()) + +# Opens the file to write +with open(fileName, 'wb') as f: + print('File openned') + while True: + print('Receiving...') + data = s.recv(1024) + if not data: + break + # Escreve dados + f.write(data) + +f.close() +print('Receiving done') +s.close() +print('Closing connection') \ No newline at end of file diff --git a/interface/get_img.py b/interface/get_img.py new file mode 100644 index 0000000..1add7c8 --- /dev/null +++ b/interface/get_img.py @@ -0,0 +1,31 @@ +import socket + + +fileName="../captura.jpg" +#MASTER +socket_host="172.20.10.7" +socket_port=32001 + +# Creates socket +s = socket.socket() + +# Connect +s.connect((socket_host, socket_port)) +# Send start message +s.send("Start message".encode()) + +# Opens the file to write +with open(fileName, 'wb') as f: + print('File openned') + while True: + print('Receiving...') + data = s.recv(1024) + if not data: + break + # Escreve dados + f.write(data) + +f.close() +print('Receiving done') +s.close() +print('Closing connection') \ No newline at end of file diff --git a/interface/interface.py b/interface/interface.py new file mode 100644 index 0000000..b93b3b4 --- /dev/null +++ b/interface/interface.py @@ -0,0 +1,1514 @@ +# -*- coding: UTF-8 -*- +import Tkinter as tk +import tkFileDialog as filedialog +from io import StringIO +import requests +import subprocess +import arff +import scipy.ndimage as ndi +import pandas as pd +import os,os.path +import cv2 +import numpy as np +from scipy import ndimage +from sklearn.cluster import KMeans +from sklearn.neighbors import KNeighborsClassifier + +#Funcao de troca de frame +# Toda vez que ela é chamada, muda-se o frame que esta no topo sendo +# mostrado ao usuário. +def raise_frame(frame): + if(frame == menu): + print("foi para menu") + elif(frame == crop): + print("foi para crop") + elif(frame == createDatabase): + print("foi para createDatabase") + elif(frame == supervised): + print("foi para supervised") + elif(frame == auto): + print("foi para automatico") + elif(frame == manual): + print("foi para manual") + frame.tkraise() + + + +############## +#Area do MENU# +############## + +#Globais +defaultColorButton = "" + +def sair(): + global window + window.destroy() + +def configureMenu(): + global defaultColorButton + text1 = tk.Label(menu, text = "Menu", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + + #instrCrop = "CROP: se refere ao processo de cortar uma imagem para isolar apenas a imagem que o INPE\ + # disponibilizou. Neste modo o usuário escolher uma imagem e nela seleciona 4 pontos\ + # que serão utilizados para cortar aquela parte da imagem tranformando-a em um retangulo" + #textCrop = tk.Label(menu, text = instrCrop, wraplength=(largura - 50)) + #textCrop.place(relx=0.5,rely=0.1,anchor = tk.CENTER) + btCrop = tk.Button(menu, text='Crop', command=lambda:raise_frame(crop), width=20) + btCrop.place(relx=0.50, rely = 0.3 ,anchor = tk.CENTER) + # Pega cor padrão do botao + defaultColorButton = btCrop['bg'] + + btCreateDatabase = tk.Button(menu, text='Criar Base de Dados', command=lambda:raise_frame(createDatabase), width=20) + btCreateDatabase.place(relx=0.50, rely = 0.4 ,anchor = tk.CENTER) + + btUnsupervised = tk.Button(menu, text='Automatico', command=lambda:raise_frame(auto), width=20) + btUnsupervised.place(relx=0.50, rely = 0.5 ,anchor = tk.CENTER) + + btSupervised = tk.Button(menu, text='Supervisionado', command=lambda:raise_frame(supervised), width=20) + btSupervised.place(relx=0.50, rely = 0.6 ,anchor = tk.CENTER) + + btUnsupervised = tk.Button(menu, text='Manual', command=lambda:raise_frame(manual), width=20) + btUnsupervised.place(relx=0.50, rely = 0.7 ,anchor = tk.CENTER) + + btUnsupervised = tk.Button(menu, text='Posicionar', command=lambda:raise_frame(getPosFrame), width=20) + btUnsupervised.place(relx=0.50, rely = 0.8 ,anchor = tk.CENTER) + + btExit = tk.Button(menu, text='SAIR', command=lambda:sair(), width=20) + btExit.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + +##################### +#Fim da Area do MENU# +##################### + + + +######################## +#Inicio da Area do CROP# +######################## + +#Globais +imgFile = "" +whereToSaveCropped = "" +mouseX = -1 +mouseY = -1 + +def selectImage(textChooseImage): + global imgFile + text = filedialog.askopenfilename(title = "Escolha a imagem",filetypes = (("png files","*.png"),("jpg files","*.jpg"),("jpeg files","*.jpeg"),("all files","*.*"))) + print(text[-4:]) + print(text[-4:] == ".png") + if(text[-4:] == ".png" or text[-4:] == ".jpg" or text[-5:] == ".jpeg"): + print("aqui") + textChooseImage['text'] = text + imgFile = text + else: + print("aqui2") + textChooseImage['text'] = 'Arquivo escolhido não é imagem' + #Avisar do erro + +def chooseWhereToSaveCropped(textCroppedImage): + global whereToSaveCropped + text = filedialog.asksaveasfilename(title = "De um nome ao arquivo novo",defaultextension=".png") + whereToSaveCropped = text + textCroppedImage['text'] = text + +def four_point_transform(image, pts): + # obtain a consistent order of the points and unpack them + # individually + rect = order_points(pts) + (tl, tr, br, bl) = rect + + # compute the width of the new image, which will be the + # maximum distance between bottom-right and bottom-left + # x-coordiates or the top-right and top-left x-coordinates + widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) + widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) + maxWidth = max(int(widthA), int(widthB)) + + # compute the height of the new image, which will be the + # maximum distance between the top-right and bottom-right + # y-coordinates or the top-left and bottom-left y-coordinates + heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) + heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) + maxHeight = max(int(heightA), int(heightB)) + + # now that we have the dimensions of the new image, construct + # the set of destination points to obtain a "birds eye view", + # (i.e. top-down view) of the image, again specifying points + # in the top-left, top-right, bottom-right, and bottom-left + # order + dst = np.array([ + [0, 0], + [maxWidth - 1, 0], + [maxWidth - 1, maxHeight - 1], + [0, maxHeight - 1]], dtype = "float32") + + # compute the perspective transform matrix and then apply it + M = cv2.getPerspectiveTransform(rect, dst) + warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) + + # return the warped image + return warped + +# mouse callback function +def get_click(event,x,y,flags,param): + global mouseX,mouseY + if event == cv2.EVENT_LBUTTONDBLCLK: + mouseX=x + mouseY=y + print(mouseX) + print(mouseY) + +def order_points(pts): + # initialzie a list of coordinates that will be ordered + # such that the first entry in the list is the top-left, + # the second entry is the top-right, the third is the + # bottom-right, and the fourth is the bottom-left + rect = np.zeros((4, 2), dtype = "float32") + + # the top-left point will have the smallest sum, whereas + # the bottom-right point will have the largest sum + s = pts.sum(axis = 1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + + # now, compute the difference between the points, the + # top-right point will have the smallest difference, + # whereas the bottom-left will have the largest difference + diff = np.diff(pts, axis = 1) + rect[1] = pts[np.argmin(diff)] + rect[3] = pts[np.argmax(diff)] + + # return the ordered coordinates + return rect + + +def cropImage(): + global imgFile, mouseX, mouseY + mouseX=-1 + mouseY=-1 + + border=[] + cv2.namedWindow('image') + cv2.setMouseCallback('image',get_click) + img = cv2.imread(imgFile) + cv2.imshow('image',img) + + i=0 + prevX=mouseX + prevY=mouseY + while(i<4): + cv2.waitKey(25) + if(mouseX!=prevX or mouseY!=prevY): + prevX=mouseX + prevY=mouseY + print(str(mouseX)+','+str(mouseY)) + border.append([mouseX,mouseY]) + i+=1 + + #Transformando e salvando a nova imagem + pts=np.array(eval(str(border)),dtype="float32") + warped=four_point_transform(img,pts) + cv2.imwrite(whereToSaveCropped,warped) + + cv2.destroyAllWindows() + +def configureCrop(): + + text1 = tk.Label(crop, text = "Crop", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + textChooseImage = tk.Label(crop, text = imgFile, wraplength=(largura - 20)) + textChooseImage.place(relx=0.5,rely=0.3,anchor = tk.CENTER) + + btSelectImageCrop = tk.Button(crop, text='Selecionar Imagem', command=lambda:selectImage(textChooseImage), width=20) + btSelectImageCrop.place(relx=0.50, rely = 0.2 ,anchor = tk.CENTER) + + textCroppedImage = tk.Label(crop, text = whereToSaveCropped, wraplength=(largura - 20)) + textCroppedImage.place(relx=0.5,rely=0.5,anchor = tk.CENTER) + + btWhereToSaveCrop = tk.Button(crop, text='Onde Salvar', command=lambda:chooseWhereToSaveCropped(textCroppedImage), width=20) + btWhereToSaveCrop.place(relx=0.50, rely = 0.4 ,anchor = tk.CENTER) + + btCut = tk.Button(crop, text='Cortar', command=lambda:cropImage(), width=20) + btCut.place(relx=0.50, rely = 0.8 ,anchor = tk.CENTER) + + btBack = tk.Button(crop, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) + btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + +##################### +#Fim da Area do CROP# +##################### + + + +################################### +#Inicio da Area do CREATE_DATABASE# +################################### + +#Globais +fileToCreateDB = "" +imgFiles = [] +append = False +mode = 0 +db = pd.DataFrame() +refPt = [] +image = [] +clone = [] + +mode_DEBUG=1 + +def selectImagesForDB(textImages): + global imgFiles + listOfImages = filedialog.askopenfilenames(title = "Escolha as imagens para compor a base",filetypes = (("png files","*.png"),("jpg files","*.jpg"),("jpeg files","*.jpeg"),("all files","*.*"))) + imgFiles = listOfImages + print(imgFiles) + if(len(imgFiles) > 0): + textImages['text'] = 'Imagens coletadas com sucesso' + else: + textImages['text'] = 'Imagens não foram escolhidas' + +def selectDB(textChooseDB): + global fileToCreateDB + text = filedialog.asksaveasfilename(title = "Escolha uma base de dados ou crie uma",filetypes = (("csv files","*.csv"),("all files","*.*"))) + if(text[-4:] == ".csv"): + textChooseDB['text'] = text + fileToCreateDB = text + else: + textChooseDB['text'] = 'Arquivo escolhido não é base de dados' + +def appendControl(btAppend): + global append + if(append == True): + append = False + btAppend['bg'] = defaultColorButton + else: + append = True + btAppend['bg'] = '#7ccd7c' + +def click_and_crop(event, x, y, flags, param): + # Faz referencias as variáveis globais dentro da função + global db + global mode + global refPt + global image + global clone + + # se o botao esquerdo do mouse é pressionado ele salva as coordenadas iniciais e checa + # o modo para decidir o que fazer + + # Se o modo for 0, espera o botao esquerdo ser solto para capturar uma area da imagem + if event == cv2.EVENT_LBUTTONDOWN and mode == 0: + # Salva a coordenada em que o botao do mouse eh pressionado + refPt = [[x, y]] + + # Se o modo for 1, cria um quadrado com a cor do pixel para representar + # para o usuário, para que ele decida adicionar ou não na base aquela cor + elif event == cv2.EVENT_LBUTTONDOWN and mode == 1: + refPt = [[x, y]] + #Cria o quadrado a ser mostrado para o usuário + roi = np.zeros((300,300,3), np.uint8) + roi[:] = image[y,x,:] + # Adiciona as versão HSV e LAB da imagem + roiHSV = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) + roiLAB = cv2.cvtColor(roi, cv2.COLOR_BGR2LAB) + cv2.namedWindow('ROI',cv2.WINDOW_NORMAL) + cv2.moveWindow('ROI', 900, 50) + cv2.resizeWindow('ROI', 300,300) + cv2.imshow("ROI", roi) + + # Esse loop basicamente espera um comando do usuário para dizer qual o label daquele quadrado + # Apertando '1' o usuário adiciona aquela cor na base como vermelho + # Apertando '0' o usuário adiciona aquela cor na base como laranja + # Apertando ESC o usuário cancela aquela ação + while True: + keyColor = cv2.waitKey(20) + if keyColor == 27 or keyColor == ord('q'): + cv2.destroyWindow('ROI') + break + elif keyColor == ord("0"): + print("Other color") + vet = np.concatenate([roi[0,0,:], roiHSV[0,0,:], roiLAB[0,0,:], [0]]) + db = db.append(pd.Series(vet, index=db.columns ), ignore_index=True) + cv2.destroyWindow('ROI') + break + elif keyColor == ord("1"): + print("Target color") + vet = np.concatenate([roi[0,0,:], roiHSV[0,0,:], roiLAB[0,0,:], [1]]) + db = db.append(pd.Series(vet, index=db.columns ), ignore_index=True) + cv2.destroyWindow('ROI') + break + + # Se o modo for 0, e o botão esquerdo for solto, o código recorta a area + #selecionada e mostra para o usuário + elif event == cv2.EVENT_LBUTTONUP and mode == 0: + # Salva a coordenada em que o botao do mouse e solto + refPt.append([x, y]) + + #Ajusta valores selecionados (para pode arrastar o mouse de qualquer direção) + if refPt[0][0] > refPt[1][0]: + aux = refPt[0][0] + refPt[0][0] = refPt[1][0] + refPt[1][0] = aux + if refPt[0][1] > refPt[1][1]: + aux = refPt[0][1] + refPt[0][1] = refPt[1][1] + refPt[1][1] = aux + + #Cria uma imagem chamada roi, clonando a parte da imagem selecionada pelo usuario + roi = clone[refPt[0][1]:refPt[1][1], refPt[0][0]:refPt[1][0]] + # Adiciona as versão HSV e LAB da imagem + roiHSV = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) + roiLAB = cv2.cvtColor(roi, cv2.COLOR_BGR2LAB) + + # Mostra a imagem recortada para o usuário e espera ele decidir o que fazer com ela + cv2.imshow("ROI", roi) + # Esse loop basicamente espera um comando do usuário para dizer qual o label daquele quadrado + # Apertando '1' o usuário adiciona aquela cor na base como vermelho + # Apertando '0' o usuário adiciona aquela cor na base como laranja + # Apertando ESC o usuário cancela aquela ação + while True: + keyColor = cv2.waitKey(20) + if keyColor == 27 or keyColor == ord('q'): + cv2.destroyWindow('ROI') + break + elif keyColor == ord("0"): + print("Outra Cor") + listOfDF = [] + # Cria uma lista onde serão salvos os valores de cada pixel em BGR, HSV e LAB junto + # com a classe para depois adicionar na base de dados + for i in range(roi.shape[0]): + for j in range(roi.shape[1]): + if roi[i,j,0] != 0 and roi[i,j,0] != 1 and roi[i,j,2] != 0: + vet = np.concatenate([roi[i,j,:], roiHSV[i,j,:], roiLAB[i,j,:], [0]]) + listOfDF.append(pd.Series(vet, index=db.columns)) + db = db.append(listOfDF , ignore_index=True) + cv2.destroyWindow('ROI') + break + elif keyColor == ord("1"): + print("Target color") + listOfDF = [] + # Cria uma lista onde serão salvos os valores de cada pixel em BGR, HSV e LAB junto + # com a classe para depois adicionar na base de dados + for i in range(roi.shape[0]): + for j in range(roi.shape[1]): + if roi[i,j,0] != 0 and roi[i,j,0] != 1 and roi[i,j,2] != 0: + vet = np.concatenate([roi[i,j,:], roiHSV[i,j,:], roiLAB[i,j,:], [1]]) + listOfDF.append(pd.Series(vet, index=db.columns)) + db = db.append(listOfDF , ignore_index=True) + cv2.destroyWindow('ROI') + break + + +def criaBase(): + global append + global fileToCreateDB + global imgFiles + global mode + global db + global image + global clone + + #Checa se deve fazer append ou nao + if(append): + db = pd.read_csv(fileToCreateDB, sep='\t') + print(db) + else: + db = pd.DataFrame(columns=['Bl', 'G', 'R', 'H', 'S', 'V', 'L', 'A','B', 'Color']) + + #Seleciona imagens + index=0 + img_list=imgFiles + border=[] + + # mode controla o tipo de seleção de pixels, + # podendo esta ser em area (clicando e arrastando) + # ou por pixel expecífico + mode = 0 + + l=len(imgFiles) + + # Cria uma nova janela do OpenCV + cv2.namedWindow('image',cv2.WINDOW_NORMAL) + cv2.moveWindow('image', 700, 500) + cv2.resizeWindow('image', 800,800) + # Comando designado para o mouse esta associado a função click_and_crop + cv2.setMouseCallback("image", click_and_crop) + + + while True: + image = cv2.imread(img_list[index]) + clone = image.copy() + + # Mostra a imagem e espera por um botão ser apertado + cv2.imshow("image", image) + key = cv2.waitKey(1) & 0xFF + + # Fecha a janela se ESC (27) ou 'q' forem apertados + if key == 27 or key == ord('q'): + break + + #Troca o modo de captura de cor + elif key == ord("1"): + if mode == 0: + print("Modo de pontos") + mode = 1 + else: + print("Modo de quadrados") + mode = 0 + + # Muda para a imagem anterior + elif(key == ord('[') or key == 81): + index-=1 + index%=l + + # Muda para a próxima imagem + elif(key == ord(']') or key == 83): + index+=1 + index%=l + + # Salva a base de dados criada no programa + # Futuramente na interface precisamos mudar o nome deste arquivo + elif(key == ord('s')): + print('salvando') + db.to_csv(fileToCreateDB, sep='\t', index=False) + break + + cv2.destroyAllWindows() + +def configureCreateDatabase(): + text1 = tk.Label(createDatabase, text = "Cria base de dados", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + textImages = tk.Label(createDatabase, text = '', wraplength=(largura - 20)) + textImages.place(relx=0.5,rely=0.3,anchor = tk.CENTER) + + btSelectImagesForDB = tk.Button(createDatabase, text='Selecionar imagens', command=lambda:selectImagesForDB(textImages), width=20) + btSelectImagesForDB.place(relx=0.50, rely = 0.2 ,anchor = tk.CENTER) + + textSavedDB = tk.Label(createDatabase, text = '', wraplength=(largura - 20)) + textSavedDB.place(relx=0.5,rely=0.5,anchor = tk.CENTER) + + btWhereToSave = tk.Button(createDatabase, text='Selecionar base', command=lambda:selectDB(textSavedDB), width=20) + btWhereToSave.place(relx=0.50, rely = 0.4 ,anchor = tk.CENTER) + + btAppend = tk.Button(createDatabase, text='Append', command=lambda:appendControl(btAppend), width=20) + btAppend.place(relx=0.50, rely = 0.6 ,anchor = tk.CENTER) + + btSaveSB = tk.Button(createDatabase, text='Rodar', command=lambda:criaBase(), width=20) + btSaveSB.place(relx=0.50, rely = 0.8 ,anchor = tk.CENTER) + + + btBack = tk.Button(createDatabase, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) + btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + + +################################ +#Fim da Area do CREATE_DATABASE# +################################ + + + +################################ +#Inicio da Area do UNSUPERVISED# +################################ + +# Abre uma janela para selecionar o arquivo da imagem a ser usada +# Essa imagem é carregada em BGR e é retornado a versão BGR, HSV e LAB +# nesta ordem +def getImages(): + global imgFile + imgBGR = cv2.imread(imgFile,1) + imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) + imgLAB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2LAB) + return imgBGR, imgHSV, imgLAB + +# Cria uma base de dados que contem 9 colunas para cada atributo de cor, sendo eles +# Blue, Green, Red, Hue, Saturation, Value, ligh, A e B para ser usado na clusterização +# Retorna a base criada sem nenhum dado ainda +def createTestDataBase(imgBGR, imgHSV, imgLAB): + dbTest = pd.DataFrame(columns=['Bl', 'G', 'R', 'H', 'S', 'V', 'L', 'A','B']) + listOfDF = [] + for i in range(imgBGR.shape[0]): + for j in range(imgBGR.shape[1]): + vet = np.concatenate([imgBGR[i,j,:], imgHSV[i,j,:], imgLAB[i,j,:] ]) + listOfDF.append(pd.Series(vet, index=dbTest.columns)) + dbTest = dbTest.append(listOfDF , ignore_index=True) + return dbTest + +# Usa o kmeans com 5 clusters apenas com BGR - essa primeira clusterização é feita +# para separa apenas uma imagem com vermelho e laranja +# Em geral 5 clusters separa bem as duas cores +# Retorna os centros e os labels da clusterização +def firstKmeans(imgBGR): + canais = [0,1,2] + num_clusters = 7 + + tst = imgBGR + tst = tst[:,:,canais] + data = tst.reshape(tst.shape[0]*tst.shape[1], len(canais)) + km = KMeans(n_clusters=num_clusters) + km.fit(data) + labels = km.predict(data) + img_labels = labels.reshape(tst.shape[:2]) + centers=km.cluster_centers_ + return img_labels, centers + +# Usa o kmeans com 5 clusters e todos os atibutos BGR, HSV e LAB da imagem +# essa primeira clusterização é feita +# para separa apenas uma imagem com vermelho e laranja +# Em geral 5 clusters separa bem as duas cores +# Retorna os centros e os labels da clusterização +def firstKmeansAllAtrib(db, imgBGR): + + X_train = db.iloc[:, :].values + + canais = [0,1,2] + num_clusters = 9 + + km = KMeans(n_clusters=num_clusters) + km.fit(X_train) + labels = km.predict(X_train) + img_labels = labels.reshape(imgBGR.shape[:2]) + centers=km.cluster_centers_ + return img_labels, centers + +# Acha qual o label que contem as cores vermelho e laranja +# Separa esse cluster verificando qual cluster possui o maior valor vermelho +# do espectro BGR +def getLabelRedRGB(centers): + maxVerm=0 + clusVerm=[] + for i in range(1,len(centers)): + if centers[i][2] > maxVerm: + maxVerm=centers[i][2] + + for i in range(1,len(centers)): + if centers[i][2] >= (0.5*maxVerm): + if((centers[i][2]>centers[i][0]) and (centers[i][2]>centers[i][1])): + clusVerm.append(i) + + return clusVerm + + + +# Acha qual o label que contem as cores vermelho e laranja +# Separa esse cluster verificando qual cluster possui o maior valor vermelho +# do espectro BGR +def getLabelGreenRGB(centers): + global mode_DEBUG + maxVerd=0 + clusVerd=[] + for i in range(1,len(centers)): + if centers[i][1] > maxVerd: + maxVerd=centers[i][1] + if(mode_DEBUG): + print(maxVerd) + for i in range(1,len(centers)): + if(mode_DEBUG): + print(centers) + if centers[i][1] >= (0.4*maxVerd): + if((centers[i][1]>centers[i][0]) and (centers[i][1]>centers[i][2])): + clusVerd.append(i) + if(mode_DEBUG): + print("\n\n") + + return clusVerd + + +# Faz o kmeans utilizando a imagem LAB apenas +# essa segunda clusterização recebe uma mascara, para evitar que +# outras cores alem de laranja e vermelho estejam na imagem. +# São clusterizados 10 clusters, porém futuramente estes serão agrupados +# selecionando qual é vermelho e qual é laranja, excluido a parte preta da imagem +# Retorna os centros e os labels da clusterização +def secondKmeans(imgBGR, img_mask,num_clusters): + # Retira as outras cores (deixando só vermelho e laranja) + imgAux = 255*(img_mask).astype(np.uint8) + imgFinal = cv2.bitwise_and(imgBGR, imgBGR, mask=imgAux) + + canais = [0,1,2] + + tst = cv2.cvtColor(imgFinal, cv2.COLOR_RGB2LAB) + tst = tst[:,:,canais] + + data = tst.reshape(tst.shape[0]*tst.shape[1], len(canais)) + + km = KMeans(n_clusters=num_clusters) + km.fit(data) + labels = km.predict(data) + img_labels = labels.reshape(imgBGR.shape[:2]) + centers = km.cluster_centers_ + return img_labels, centers + + +# Cria uma mascara para a cor vermelha, buscando nos centros retornadas pela clusterização +# Uma mascara binaria é criada para cada centro escolhido, e ao final uma operação OR +# é feita com todas as mascaras para gerar a mascara final +def getRedImageMask(imgBGR, centers, labels): + global mode_DEBUG + redList = [] + med=0 + num=0 + for i,cent in enumerate(centers): + tmp=np.zeros((1,1,3),np.uint8) + cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) + bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] + if (cent[0]>12): + minimum=bgrVers[2] + med+=bgrVers[2] + num+=1 + + if(num): + med/=num + + if(mode_DEBUG): + print(num) + print(med) + + for i,cent in enumerate(centers): + tmp=np.zeros((1,1,3),np.uint8) + cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) + bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] + if (bgrVers[2] > med and 0.3*bgrVers[2]>bgrVers[1]): + redList.append(i) + + + img_mask_r = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) + for label in redList: + currentMask = labels==label + img_mask_r = (img_mask_r) | (currentMask) + return redList, img_mask_r + +# Cria uma mascara para a cor laranja, buscando nos centros retornadas pela clusterização +# Uma mascara binaria é criada para cada centro escolhido, e ao final uma operação OR +# é feita com todas as mascaras para gerar a mascara final +def getDarkGreenImageMask(imgLAB, centers, labels): + global mode_DEBUG + greenList = [] + medLum=0 + medGreen=0 + num=0 + for i,cent in enumerate(centers): + tmp=np.zeros((1,1,3),np.uint8) + cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) + bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] + print(bgrVers) + if (cent[0]>12): + medGreen+=bgrVers[1] + num+=1 + if(num): + medGreen/=num + + if(mode_DEBUG): + print("\n") + print(medGreen) + + num=0 + for i,cent in enumerate(centers): + tmp=np.zeros((1,1,3),np.uint8) + cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) + bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] + if (cent[0]>12 and bgrVers[1]>medGreen): + medLum+=cent[0] + num+=1 + if(num): + medLum/=num + + if(mode_DEBUG): + print(medLum) + + for i,cent in enumerate(centers): + tmp=np.zeros((1,1,3),np.uint8) + cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) + bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] + + if(mode_DEBUG): + print("\n") + print(bgrVers) + print(cent) + if (cent[0] < medLum and cent[0]>12 and bgrVers[1]>medGreen): + greenList.append(i) + + if(mode_DEBUG): + print("PASS") + + + img_mask_g = np.zeros((imgLAB.shape[0], imgLAB.shape[1]),dtype=bool) + for label in greenList: + currentMask = labels==label + img_mask_g = (img_mask_g) | (currentMask) + return img_mask_g + +#Para Jupyter notebook para mostrar as imagens recortadas +# a partir das mascaras. Se estiver rodando no terminal, +# pode comentar essa parte +def showMaskAsImage(imgBGR, img_mask): + imgAux = 255*(img_mask).astype(np.uint8) + imgFinal = cv2.bitwise_and(imgBGR, imgBGR, mask=imgAux) + plt.figure(figsize=(8,8)) + plt.imshow(imgFinal[:,:,::-1]) + +# Faz um fechamento na imagem +# Funcão pq eu sempre esqueço o que um fechamento faz +def tiraBuraco(img_mask, num): + return ndi.binary_closing(img_mask, iterations=num) + +# Faz uma abertura na imagem +# Funcão pq eu sempre esqueço o que uma abertura faz +def tiraRuido(img_mask, num): + return ndi.binary_opening(img_mask, iterations=num) + +# Calcula o numero de objetos vermelhos da imagem e retorna em nr_objects +# Essa função depende da quantidade de ruido que vai vir no cluster vermelho +# Se tiver muito ruido, passar um num, maior para tirar o ruido da mascara +# utilizando abertura +def calcFires(img_mask, num): + if num > 0: + img_mask_sem_ruido = tiraRuido(img_mask, num) + else: + img_mask_sem_ruido = img_mask + labeled, nr_objects = ndimage.label(img_mask_sem_ruido) + + # Calculo das posicoes dos focos + f = open("focos.txt", "w") + print('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape)) + f.write('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape) + '\n') + focos = ndimage.measurements.center_of_mass(img_mask_sem_ruido, labeled, range(1,nr_objects+1)) + print(nr_objects) + for i in range(len(focos)): + print('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2))) + f.write('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2)) + '\n') + f.close() + + return labeled, nr_objects + + +# Calcula a area de desmatamento em percentual da area da imagem toda +def calcDeforestation(img_mask): + whitePixels = sum(sum(img_mask)) + totalPixels = img_mask.shape[0]*img_mask.shape[1] + area = round(float(100*whitePixels)/totalPixels, 2) + return area + +# Aumenta a saturação da imagem, colocando a saturação em 250 para toda a imagem +# Essa modificação é feina na imagem para a segunda clusterização, para que o vermelho +# se destaque mais e facilite a sepração do laranja +def increaseSaturation(imgBGR): + imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) + for i in range(imgHSV.shape[0]): + for j in range(imgHSV.shape[1]): + #if imgHSV[i,j,1] + 100 > 254: + # imgHSV[i,j,1] = 255 + #else: + # imgHSV[i,j,1] += 100 + imgHSV[i,j,1] = 250 + + imgBGR2 = cv2.cvtColor(imgHSV, cv2.COLOR_HSV2BGR) + return imgBGR2 + +# Algoritmo geral +def autoClaro(label): + global mode_DEBUG + imgBGR, imgHSV, imgLAB = getImages() + + # Cria uma base de dados para usar os 9 atributos + # B,G,R, H,S,V e L,A,B + db = createTestDataBase(imgBGR, imgHSV, imgLAB) + + #Realiza o primeiro kmeans para separar apenas as cores vermelho e laranja + # utilizando todos os atributos + labels1, centers1 = firstKmeansAllAtrib(db, imgBGR) + + #Identifica qual o cluster das cores vermelho e laranja + clusRedOrange = getLabelRedRGB(centers1) + + #Cria uma mascara do cluster vermelho e laranja + img_mask_RandO = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) + for i in clusRedOrange: + img_mask_RandO = img_mask_RandO|(labels1==i) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_RandO]=imgBGR[img_mask_RandO] + cv2.imshow('Red and Orange',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + + #Aumento de saturacao para a segunda clusterização + imgHigherSat = increaseSaturation(imgBGR) + + # Faz um segundo k means para separar o vermelho e laranja + labelsR, centersR = secondKmeans(imgHigherSat, img_mask_RandO,10) + #print(centers2) + + # Cria a mascara da cor vermelha a partir dos clusters do segundo kmeans + redList, img_mask_Red = getRedImageMask(imgBGR, centersR, labelsR) + img_mask_Red=tiraRuido(img_mask_Red,2) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_Red]=imgBGR[img_mask_Red] + cv2.imshow('Red',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + labels, nro = calcFires(img_mask_Red, 1) + + + + #Identifica qual o cluster dos verdes + clusGreen = getLabelGreenRGB(centers1) + + #Cria uma mascara do cluster verde + img_mask_AllGreen = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) + for i in clusGreen: + img_mask_AllGreen = img_mask_AllGreen|(labels1==i) + + img_mask_AllGreen=tiraRuido(img_mask_AllGreen,2) + img_mask_AllGreen=tiraBuraco(img_mask_AllGreen,1) + img_mask_AllGreen=tiraRuido(img_mask_AllGreen,3) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_AllGreen]=imgBGR[img_mask_AllGreen] + cv2.imshow('AllGreen',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + # Faz um segundo k means para separar obter o verde escuro + labelsG, centersG = secondKmeans(imgBGR, img_mask_AllGreen,6) + + + # Cria a mascara do verde escuro a partir dos clusters do segundo kmeans + img_mask_Green = getDarkGreenImageMask(imgLAB, centersG, labelsG) + img_mask_Green = tiraRuido(img_mask_Green,1) + img_mask_Green = tiraBuraco(img_mask_Green,1) + + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_Green]=imgBGR[img_mask_Green] + cv2.imshow('Green',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + label['text']="Area:{}%".format(calcDeforestation(img_mask_Green))+"\nFocos:{}".format(nro) + + +def autoEscuro(label): + global mode_DEBUG + imgBGR, imgHSV, imgLAB = getImages() + + #Realiza o primeiro kmeans para separar apenas as cores vermelho e laranja + labels1, centers1 = firstKmeans(imgBGR) + + #Identifica qual o cluster das cores vermelho e laranja + clusRedOrange = getLabelRedRGB(centers1) + + #Cria uma mascara do cluster vermelho e laranja + img_mask_RandO = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) + for i in clusRedOrange: + img_mask_RandO = img_mask_RandO|(labels1==i) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_RandO]=imgBGR[img_mask_RandO] + cv2.imshow('Red and Orange',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + + # Faz um segundo k means para separar as duas cores + labels2, centers2 = secondKmeans(imgBGR, img_mask_RandO,10) + + # Cria a mascara da cor vermelha a partir dos cluster do segundo kmeans + redList, img_mask_Red = getRedImageMask(imgBGR, centers2, labels2) + img_mask_Red=tiraRuido(img_mask_Red,2) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_Red]=imgBGR[img_mask_Red] + cv2.imshow('Red',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + + labels, nro = calcFires(img_mask_Red, 1) + + + #Identifica qual o cluster dos verdes + clusGreen = getLabelGreenRGB(centers1) + + #Cria uma mascara do cluster verde + img_mask_AllGreen = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) + for i in clusGreen: + img_mask_AllGreen = img_mask_AllGreen|(labels1==i) + + + img_mask_AllGreen=tiraRuido(img_mask_AllGreen,2) + img_mask_AllGreen=tiraBuraco(img_mask_AllGreen,1) + img_mask_AllGreen=tiraRuido(img_mask_AllGreen,3) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_AllGreen]=imgBGR[img_mask_AllGreen] + cv2.imshow('AllGreen',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + + # Faz um segundo k means para separar obter o verde escuro + labelsG, centersG = secondKmeans(imgBGR, img_mask_AllGreen,6) + + # Cria a mascara do verde escuro a partir dos clusters do segundo kmeans + img_mask_Green = getDarkGreenImageMask(imgLAB, centersG, labelsG) + img_mask_Green = tiraRuido(img_mask_Green,1) + img_mask_Green = tiraBuraco(img_mask_Green,1) + + if(mode_DEBUG): + background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) + background[img_mask_Green]=imgBGR[img_mask_Green] + cv2.imshow('Green',background) + cv2.waitKey(0) + cv2.destroyAllWindows() + + + label['text']="Area:{}%".format(calcDeforestation(img_mask_Green))+"\nFocos:{}".format(nro) + +def configureAuto(): + text1 = tk.Label(auto, text = "Automatico", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + textAutoImage = tk.Label(auto, text = supImgFile, wraplength=(largura - 20)) + textAutoImage.place(relx=0.5,rely=0.4,anchor = tk.CENTER) + + btSelectImageAuto = tk.Button(auto, text='Selecionar Imagem', command=lambda:selectImage(textAutoImage), width=20) + btSelectImageAuto.place(relx=0.50, rely = 0.3 ,anchor = tk.CENTER) + + textAutoDados=tk.Label(auto,text="Area:\nFocos:",wraplength=(largura-20)) + textAutoDados.place(relx=0.5,rely=0.7,anchor=tk.CENTER) + + btCut = tk.Button(auto, text='Modo Claro', command=lambda:autoClaro(textAutoDados), width=20) + btCut.place(relx=0.25, rely = 0.55 ,anchor = tk.CENTER) + + btCut = tk.Button(auto, text='Modo Escuro', command=lambda:autoEscuro(textAutoDados), width=20) + btCut.place(relx=0.75, rely = 0.55 ,anchor = tk.CENTER) + + btBack = tk.Button(auto, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) + btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + +############################# +#Fim da Area do UNSUPERVISED# +############################# + + + +############################## +#Inicio da Area do SUPERVISED# +############################## + +#Globais +supImgFile = "" +supDBFile = "" + +# Abre uma janela para selecionar o arquivo da imagem a ser usada +# Essa imagem é carregada em BGR e é retornado a versão BGR, HSV e LAB +# nesta ordem +def getImagesSup(imgFile): + imgBGR = cv2.imread(imgFile,1) + imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) + imgLAB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2LAB) + return imgBGR, imgHSV, imgLAB + +# Cria o dataframe do pandas no python das bases escolhidas nas janelas +# Se mais de uma base foi selecionada, o algoritmo une elas +def createDataFrame(fileName): + finalDB = pd.read_csv(fileName, sep='\t') + return finalDB + +def selectDBSup(textChooseDB): + global supDBFile + text = filedialog.askopenfilename(title = "Escolha uma base de dados",filetypes = (("csv files","*.csv"),("all files","*.*"))) + if(text[-4:] == ".csv"): + textChooseDB['text'] = text + supDBFile = text + else: + textChooseDB['text'] = 'Arquivo escolhido não é base de dados' + +def selectImageSup(textChooseImage): + global supImgFile + text = filedialog.askopenfilename(title = "Escolha a imagem",filetypes = (("png files","*.png"),("jpg files","*.jpg"),("jpeg files","*.jpeg"),("all files","*.*"))) + print(text[-4:]) + print(text[-4:] == ".png") + #if(text[-4:] == ".png" or text[-4:] == ".jpg" or text[-5:] == ".jpeg"): + print("aqui") + textChooseImage['text'] = text + supImgFile = text + #else: + #print("aqui2") + #textChooseImage['text'] = 'Arquivo escolhido não é imagem' + #Avisar do erro + + +# Percorre cada pixel da imagem e verifica o label atribuido pelo KNN +# Se este label e 1 significa que aquele pixel foi classificado como target +# e deve ser adicionado a uma mascara binaria da imagem. +# Esta mascara é retornada +def getImageMaskSup(imgBGR, y_pred): + img_mask = np.zeros((imgBGR.shape[0], imgBGR.shape[1]), dtype = bool) + for i in range(imgBGR.shape[0]): + for j in range(imgBGR.shape[1]): + if y_pred[i*imgBGR.shape[1] + j] == 1: + img_mask[i,j] = True + return img_mask + + +def showMaskAsImage(imgBGR, img_mask): + imgAux = 255*(img_mask).astype(np.uint8) + imgFinal = cv2.bitwise_and(imgBGR, imgBGR, mask=imgAux) + imgFinalBoth = np.hstack((imgBGR, imgFinal)) + cv2.imshow('imagemCortada',imgFinalBoth) + while True: + cv2.waitKey(100) + if (cv2.getWindowProperty('imagemCortada',cv2.WND_PROP_VISIBLE) < 1): + break + cv2.destroyWindow('imagemCortada') + cv2.imwrite('imagemComparacao.png',imgFinalBoth) + + +# Calcula o numero de objetos vermelhos da imagem e retorna em nr_objects +# Essa função depende da quantidade de ruido que vai vir no cluster vermelho +# Se tiver muito ruido, passar um num, maior para tirar o ruido da mascara +# utilizando abertura +def calcFiresSup(img_mask, imgBGR, transfString, textTransf): + if len(transfString) > 0: + img_mask_aux = img_mask + for i in range(len(transfString)//2): + if(transfString[i*2] == 'A' or transfString[i*2] == 'a'): + print('Fazendo abertura de ' + transfString[i*2+1]) + img_mask_aux = tiraRuido(img_mask_aux, int(transfString[i*2+1])) + else: + print('Fazendo fechamento de ' + transfString[i*2+1]) + img_mask_aux = tiraBuraco(img_mask_aux, int(transfString[i*2+1])) + img_mask_sem_ruido = img_mask_aux + else: + img_mask_sem_ruido = img_mask + labeled, nr_objects = ndimage.label(img_mask_sem_ruido) + # Calculo das posicoes dos focos + f = open("focos.txt", "w") + print('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape)) + f.write('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape) + '\n') + focos = ndimage.measurements.center_of_mass(img_mask_sem_ruido, labeled, range(1,nr_objects+1)) + imgBGR_aux = imgBGR + print(nr_objects) + for i in range(len(focos)): + print('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2))) + f.write('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2)) + '\n') + #Adicionando pontos na imagem + imgBGR_aux[int(focos[i][0]), int(focos[i][1]), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]), int(focos[i][1]+1), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]), int(focos[i][1]-1), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]+1), int(focos[i][1]-1), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]+1), int(focos[i][1]+1), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]+1), int(focos[i][1]), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]-1), int(focos[i][1]-1), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]-1), int(focos[i][1]+1), :] = [255,255,255] + imgBGR_aux[int(focos[i][0]-1), int(focos[i][1]), :] = [255,255,255] + f.close() + showMaskAsImage(imgBGR_aux, img_mask_sem_ruido) + textTransf['text'] = 'Nro de focos = ' + str(nr_objects) + return labeled, nr_objects, img_mask_sem_ruido + +# Calcula a area de desmatamento em percentual da area da imagem toda +# Essa função depende da quantidade de ruido que vai vir na mascara laranja +# Se tiver muito ruido, passar um num, maior para tirar o ruido da mascara +# utilizando abertura +def calcDeforestationSup(img_mask, imgBGR, transfString, textTransf): + print(transfString) + if len(transfString) > 0: + img_mask_aux = img_mask + for i in range(len(transfString)//2): + if(transfString[i*2] == 'A'): + print('Fazendo abertura de ' + transfString[i*2+1]) + img_mask_aux = tiraRuido(img_mask_aux, int(transfString[i*2+1])) + else: + print('Fazendo fechamento de ' + transfString[i*2+1]) + img_mask_aux = tiraBuraco(img_mask_aux, int(transfString[i*2+1])) + img_mask_sem_ruido = img_mask_aux + else: + img_mask_sem_ruido = img_mask + whitePixels = sum(sum(img_mask_sem_ruido)) + totalPixels = img_mask_sem_ruido.shape[0]*img_mask_sem_ruido.shape[1] + area = round((whitePixels/totalPixels)*100, 2) + showMaskAsImage(imgBGR, img_mask_sem_ruido) + textTransf['text'] = 'Area = ' + str(area) + '%' + return area + +#Algoritmo geral +def supervisedMethod(area, transfString, textTransf): + global supImgFile + global supDBFile + imgBGR, imgHSV, imgLAB = getImagesSup(supImgFile) + + #Cria o dataframe a partir das bases selecionadas + db = createDataFrame(supDBFile) + + #Separa os atributos da base criada para treinamento + # X = atributos + # y = labels + X_train = db.iloc[:, :-1].values + y_train = db.iloc[:, 9].values + + #Cria um KNN (deu bons resultados com ele e é rapido de treinar caso precisar + # criar uma nova base na hora da competição) + classifier = KNeighborsClassifier(n_neighbors=30) + # Treina o KNN com os dados da base + classifier.fit(X_train, y_train) + + # Cria a base de dados da imagem a ser classificada + dbTest = createTestDataBase(imgBGR, imgHSV, imgLAB) + + # Separa os atributos + X_test = dbTest.iloc[:, :].values + + #Realiza a predição utilizando o KNN + y_pred = classifier.predict(X_test) + + #Pega as mascaras de cada cor + img_mask = getImageMaskSup(imgBGR, y_pred) + + if(area): + print('Area = {}%'.format(calcDeforestationSup(img_mask,imgBGR, transfString, textTransf))) + else: + labels, nro, img_mask_final = calcFiresSup(img_mask, imgBGR, transfString, textTransf) + print('Nro de focos = {}'.format(nro)) + +def configureSupervised(): + text1 = tk.Label(supervised, text = "Supervisionado", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + textSupImage = tk.Label(supervised, text = supImgFile, wraplength=(largura - 20)) + textSupImage.place(relx=0.5,rely=0.25,anchor = tk.CENTER) + + btSelectImageSup = tk.Button(supervised, text='Selecionar Imagem', command=lambda:selectImageSup(textSupImage), width=20) + btSelectImageSup.place(relx=0.50, rely = 0.2 ,anchor = tk.CENTER) + + textSupDB = tk.Label(supervised, text = supDBFile, wraplength=(largura - 20)) + textSupDB.place(relx=0.5,rely=0.38,anchor = tk.CENTER) + + btWhereToSave = tk.Button(supervised, text='Selecionar Base de Dados', command=lambda:selectDBSup(textSupDB), width=20) + btWhereToSave.place(relx=0.50, rely = 0.33 ,anchor = tk.CENTER) + + textOut = tk.Label(supervised, text = '', wraplength=(largura - 200),bg="white") + textOut.place(relx=0.5,rely=0.62, anchor = tk.CENTER) + + btGetArea = tk.Button(supervised, text='Área', command=lambda:supervisedMethod(True, E1.get(), textOut), width=10) + btGetArea.place(relx=0.25, rely = 0.52 ,anchor = tk.CENTER) + + btGetPos = tk.Button(supervised, text='Posições', command=lambda:supervisedMethod(False, E1.get(), textOut), width=10) + btGetPos.place(relx=0.75, rely = 0.52 ,anchor = tk.CENTER) + + textTransf = tk.Label(supervised, text = 'Transformações', wraplength=(largura - 20)) + textTransf.place(relx=0.5,rely=0.47, anchor = tk.CENTER) + + textRes = tk.Label(supervised, text = 'Resultados:', wraplength=(largura - 20)) + textRes.place(relx=0.5,rely=0.57, anchor = tk.CENTER) + + E1 = tk.Entry(supervised, bd =2) + E1.place(relx=0.5, rely = 0.52 ,anchor = tk.CENTER) + + btBack = tk.Button(supervised, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) + btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + + +########################### +#Fim da Area do SUPERVISED# +########################### + + + +########################## +#Inicio da Area do MANUAL# +########################## + +def color_difference (color1, color2): + # Retorna a distancia euclidiana entre duas cores no espaco RGB + return math.sqrt(sum([(abs(component1-component2))**2 for component1, component2 in zip(color1, color2)])) + +#Funcao retorna a imagem contornada e a area desmatada da mesma +def rgbKMeansImageSeg(img, num_clusters, difThres, it): + + #Reshape da imagem deixar como um vetor de pixels contendo as 3 cores para o mesmo + data = img.reshape(img.shape[0]*img.shape[1], 3) + + #Cria um agrupamento K-means e ajusta o mesmo aos dados + km = KMeans(n_clusters=num_clusters) + km.fit(data) + + #Salva os labels encontrados + labels = km.predict(data) + img_labels = labels.reshape(img.shape[:2]) + + return img_labels, km.cluster_centers_ + +def draw(img,center): + inv=(255*(1-center[0]/127),255*(1-center[1]/127),255*(1-center[2]/127)) + + cv2.rectangle(img,(0,0),(600,400),center,-1) + + cv2.line(img,(0,200),(600,200),inv,2) + cv2.line(img,(200,0),(200,400),inv,2) + cv2.line(img,(400,0),(400,400),inv,2) + + cv2.rectangle(img,(75,75),(125,125),(255,0,0),-1) + cv2.rectangle(img,(75,275),(125,325),(0,255,0),-1) + + cv2.rectangle(img,(275,75),(325,125),(0,128,255),-1) + cv2.rectangle(img,(275,275),(325,325),(0,128,0),-1) + + cv2.rectangle(img,(475,75),(525,125),(0,0,255),-1) + cv2.rectangle(img,(475,275),(525,325),(0,0,0),-1) + + + cv2.rectangle(img,(75,75),(125,125),inv) + cv2.rectangle(img,(75,275),(125,325),inv) + + cv2.rectangle(img,(275,75),(325,125),inv) + cv2.rectangle(img,(275,275),(325,325),inv) + + cv2.rectangle(img,(475,75),(525,125),inv) + cv2.rectangle(img,(475,275),(525,325),inv) + + return img + +def classifyCenters(num_clusters,txtLabel): + global mouseX,mouseY,imgFile + mouseX=-1 + mouseY=-1 + + #AZUL,VERMELHO,LARANJA,VERDE_CLA,VERDE_ESC,PRETO + class_centers=[[],[],[],[],[],[]] + color=np.zeros((400,600,3),np.uint8) + cv2.namedWindow('Centers') + cv2.setMouseCallback('Centers',get_click) + + img = cv2.imread(imgFile) + + if(img.shape[0]>img.shape[1]): + prop = float(img.shape[1])/img.shape[0] + imgResized = cv2.resize(img,(int(prop*720),720)) + else: + prop = float(img.shape[0])/img.shape[1] + imgResized = cv2.resize(img,(720,int(prop*720))) + + + + cv2.imshow('Image',imgResized) + cv2.moveWindow('Image',670,0) + + + labels, centers = rgbKMeansImageSeg(imgResized, num_clusters, 30, 1) + + i=0 + for center in centers: + draw(color,center) + cv2.imshow('Centers',color) + cv2.moveWindow('Centers',0,0) + + + inv=(255*(1-center[0]/127),255*(1-center[1]/127),255*(1-center[2]/127)) + background = np.zeros((imgResized.shape[0], imgResized.shape[1],3),np.uint8) + cv2.rectangle(background,(0,0),(imgResized.shape[1], imgResized.shape[0]),inv,-1) + + mask=(labels==i) + + + background[mask]=imgResized[mask] + + cv2.imshow('Masked',background) + cv2.moveWindow('Masked',670,720) + + mouseX=-600 + Col=-1 + while(Col<0): + Col=(3*mouseX)/600 + Lin=(2*mouseY)/400 + cv2.waitKey(15) + class_centers[3*Lin+Col].append(i) + i+=1 + + cv2.destroyAllWindows() + + binImg = np.zeros((imgResized.shape[0], imgResized.shape[1]),dtype=bool) + for label in class_centers[4]: + mask = labels==label + print(label) + print(class_centers[4]) + binImg = (binImg) | (mask) + area=calcDeforestation(binImg) #1 + + binImg = np.zeros((imgResized.shape[0], imgResized.shape[1]),dtype=bool) + for label in class_centers[2]: + mask = labels==label + binImg = (binImg) | (mask) + labels, nro=calcFires(binImg,0) #2 + + txtLabel['text']="Area:{}%".format(area)+"\nFocos:{}".format(nro) + +def configureManual(): + text1 = tk.Label(manual, text = "Manual", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + textManImage = tk.Label(manual, text = supImgFile, wraplength=(largura - 20)) + textManImage.place(relx=0.5,rely=0.35,anchor = tk.CENTER) + + btSelectImageMan = tk.Button(manual, text='Selecionar Imagem', command=lambda:selectImage(textManImage), width=20) + btSelectImageMan.place(relx=0.5, rely = 0.27 ,anchor = tk.CENTER) + + textManDados=tk.Label(manual,text="Area:\nFocos:",wraplength=(largura-20)) + textManDados.place(relx=0.5,rely=0.8,anchor=tk.CENTER) + + textSpinBox=tk.Label(manual,text="Numero de Centros:",wraplength=(largura-20)) + textSpinBox.place(relx=0.5,rely=0.475,anchor=tk.CENTER) + + spinBox = tk.Spinbox(manual, from_=1, to=16) + spinBox.place(relx = 0.35, rely = 0.5) + spinBox.insert(0,8) + spinBox.delete(1) + + btCut = tk.Button(manual, text='Classificar centros', command=lambda:classifyCenters(int(spinBox.get()),textManDados), width=20) + btCut.place(relx=0.50, rely = 0.65 ,anchor = tk.CENTER) + + btBack = tk.Button(manual, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) + btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + + + +####################### +#Fim da Area do MANUAL# +####################### + +def posicionamento(coordenadas): + global supImgFile + i=0 + while(coordenadas[i]!=','): + i+=1 + realX=float(coordenadas[:i-1]) + realY=float(coordenadas[i+1:]) + + file=open(supImgFile,"r") + line=file.readline() + + i=0 + j=0 + while(line[i]!='('): + i+=1 + while(line[j]!=','): + j+=1 + sizeX=int(line[i+1:j-1]) + i=0 + while(line[i]!=')'): + i+=1 + sizeY=int(line[j+1:i]) + print(sizeY) + print(realY) + + prop=[float(realX/sizeX),float(realY/sizeY)] + + output=open("poscs.txt","w+") + + line=file.readline() + while(line!=""): + i=0 + j=0 + while(line[i]!='='): + i+=1 + while(line[j]!=','): + j+=1 + sizeX=float(line[i+2:j]) + + while(line[j]!='='): + j+=1 + sizeY=float(line[j+1:]) + print(prop) + print(sizeY) + + output.write("X=") + output.write(str(sizeX*prop[0])) + output.write(" Y=") + output.write(str(sizeY*prop[1])) + output.write("\n") + + line=file.readline() + + + +def configurePos(): + text1 = tk.Label(getPosFrame, text = "Supervisionado", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) + + textSupImage = tk.Label(getPosFrame, text = supImgFile, wraplength=(largura - 20)) + textSupImage.place(relx=0.5,rely=0.25,anchor = tk.CENTER) + + btSelectImageSup = tk.Button(getPosFrame, text='Selecionar Focos', command=lambda:selectImageSup(textSupImage), width=20) + btSelectImageSup.place(relx=0.50, rely = 0.3 ,anchor = tk.CENTER) + + textTransf = tk.Label(getPosFrame, text = 'Tamanho (x,y)', wraplength=(largura - 20)) + textTransf.place(relx=0.5,rely=0.42, anchor = tk.CENTER) + + btGetPos = tk.Button(getPosFrame, text='Posições', command=lambda:posicionamento(E1.get()), width=10) + btGetPos.place(relx=0.5, rely = 0.65 ,anchor = tk.CENTER) + + E1 = tk.Entry(getPosFrame, bd =2) + E1.place(relx=0.5, rely = 0.50 ,anchor = tk.CENTER) + + btBack = tk.Button(getPosFrame, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) + btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) + + +##################### +# MAIN # +##################### + + +# Cria a janela em que a interface irá rodar +largura = 600 +altura = 400 +window = tk.Tk() +window.title('Zenith - Missão INPE') +window.geometry(str(largura) + "x" + str(altura) + "+100+100") +window.resizable(0, 0) + +#Cria frames da interface +menu = tk.Frame(window,width=largura, height=altura) +crop = tk.Frame(window,width=largura, height=altura) +createDatabase = tk.Frame(window,width=largura, height=altura) +supervised = tk.Frame(window,width=largura, height=altura) +auto = tk.Frame(window,width=largura, height=altura) +manual = tk.Frame(window,width=largura, height=altura) +getPosFrame = tk.Frame(window,width=largura, height=altura) + +# Gruda os frames para que todos fechem juntos +for frame in (menu, crop, createDatabase, supervised, auto, manual, getPosFrame): + frame.grid(row=0, column=0, sticky='news') + +#Configura todos os frames +configureMenu() +configureCrop() +configureCreateDatabase() +configureSupervised() +configureAuto() +configureManual() +configurePos() + +#Inicia no frame do MENU +raise_frame(menu) + +#Loop da interface +window.mainloop() From 17dc4a0948e5891830f098d7234aea296add96b6 Mon Sep 17 00:00:00 2001 From: Rodrigo-P Date: Fri, 9 Aug 2019 09:07:18 -0300 Subject: [PATCH 2/3] Interface atualizada --- interface/Aker/Aker.pro | 2 + interface/Aker/Aker.pro.user | 20 +- interface/Aker/Aker.pro.user.360926d | 336 ++++++ interface/Aker/Blank.txt | 1 + interface/Aker/Images/left-border.png | Bin 41986 -> 41793 bytes interface/Aker/get_img.py | 0 interface/Aker/interface.py | 1430 ------------------------- interface/Aker/mm.cpp | 158 ++- interface/Aker/mm.h | 17 +- interface/Aker/mm.ui | 487 ++++++++- interface/Aker/resources.qrc | 1 + 11 files changed, 995 insertions(+), 1457 deletions(-) create mode 100644 interface/Aker/Aker.pro.user.360926d create mode 100644 interface/Aker/Blank.txt delete mode 100644 interface/Aker/get_img.py delete mode 100644 interface/Aker/interface.py diff --git a/interface/Aker/Aker.pro b/interface/Aker/Aker.pro index b6e76fb..9e418bd 100644 --- a/interface/Aker/Aker.pro +++ b/interface/Aker/Aker.pro @@ -64,3 +64,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin RESOURCES += \ resources.qrc + +DISTFILES += diff --git a/interface/Aker/Aker.pro.user b/interface/Aker/Aker.pro.user index 930d293..02882aa 100644 --- a/interface/Aker/Aker.pro.user +++ b/interface/Aker/Aker.pro.user @@ -1,10 +1,10 @@ - + EnvironmentId - {360926d0-14ca-4415-9b3b-b1666c5c5f86} + {d40eca0a-e6b7-47c3-a6e9-7974ea86adcb} ProjectExplorer.Project.ActiveTarget @@ -59,14 +59,14 @@ ProjectExplorer.Project.Target.0 - Desktop - Desktop - {5c32a2d9-ed2e-44a4-bbe3-cea0423e77f0} + Desktop Qt 5.7.0 GCC 64bit + Desktop Qt 5.7.0 GCC 64bit + qt.57.gcc_64_kit 0 0 0 - /home/tiago/Aker/interface/build-Aker-Desktop-Debug + /home/rodrigo/Desktop/Rodrigo/zenith/Aker/interface/build-Aker-Desktop_Qt_5_7_0_GCC_64bit-Debug true @@ -126,7 +126,7 @@ true - /home/tiago/Aker/interface/build-Aker-Desktop-Release + /home/rodrigo/Desktop/Rodrigo/zenith/Aker/interface/build-Aker-Desktop_Qt_5_7_0_GCC_64bit-Release true @@ -186,7 +186,7 @@ true - /home/tiago/Aker/interface/build-Aker-Desktop-Profile + /home/rodrigo/Desktop/Rodrigo/zenith/Aker/interface/build-Aker-Desktop_Qt_5_7_0_GCC_64bit-Profile true @@ -304,13 +304,13 @@ Aker - Qt4ProjectManager.Qt4RunConfiguration:/home/tiago/Aker/interface/Aker/Aker.pro + Qt4ProjectManager.Qt4RunConfiguration:/home/rodrigo/Desktop/Rodrigo/zenith/Aker/interface/Aker/Aker.pro true Aker.pro false - /home/tiago/Aker/interface/build-Aker-Desktop-Debug + /home/rodrigo/Desktop/Rodrigo/zenith/Aker/interface/build-Aker-Desktop_Qt_5_7_0_GCC_64bit-Debug 3768 false true diff --git a/interface/Aker/Aker.pro.user.360926d b/interface/Aker/Aker.pro.user.360926d new file mode 100644 index 0000000..930d293 --- /dev/null +++ b/interface/Aker/Aker.pro.user.360926d @@ -0,0 +1,336 @@ + + + + + + EnvironmentId + {360926d0-14ca-4415-9b3b-b1666c5c5f86} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + {5c32a2d9-ed2e-44a4-bbe3-cea0423e77f0} + 0 + 0 + 0 + + /home/tiago/Aker/interface/build-Aker-Desktop-Debug + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + + Qt4ProjectManager.Qt4BuildConfiguration + 2 + true + + + /home/tiago/Aker/interface/build-Aker-Desktop-Release + + + true + qmake + + QtProjectManager.QMakeBuildStep + false + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + + /home/tiago/Aker/interface/build-Aker-Desktop-Profile + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + true + false + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + -w + -r + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + 3 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy locally + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + Aker + + Qt4ProjectManager.Qt4RunConfiguration:/home/tiago/Aker/interface/Aker/Aker.pro + true + + Aker.pro + false + + /home/tiago/Aker/interface/build-Aker-Desktop-Debug + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 18 + + + Version + 18 + + diff --git a/interface/Aker/Blank.txt b/interface/Aker/Blank.txt new file mode 100644 index 0000000..c1b4616 --- /dev/null +++ b/interface/Aker/Blank.txt @@ -0,0 +1 @@ +12371269362631263982686891839663891639812639812639812639821639866982196832618698269382186938184168439469156765932646324832894639846234936238493824689326496834682346983289463489329846326823684698326832469832468324682399648623463264898362468236848623468326868346836868264832684682368468324683268326846823684326868248664868326x \ No newline at end of file diff --git a/interface/Aker/Images/left-border.png b/interface/Aker/Images/left-border.png index 62b65fae24df8f1372e48159c926d3d9976c8aef..be72421a0bc9a5a1ae02901f0d23f785b5d70039 100644 GIT binary patch literal 41793 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 delta 2179 zcmY+Cdpy(s9>>3%v5N`IqPdHra+%F-DTXN$iiCcJ+4dtFa%nDQ4(XaUSQKd_MnuUhm86{rtRzFQ6$RXr?|#Ho@CB=1>q@ zCo(F65z3_N#KcF^b?9-S3;>AhEjw~Fci0fU{5uwe=__H{J{Cta;NXv1iF>z8ab0~~ z+a9EoFyYwAw5ESe2Pm!%>JjAqdR&bShFUF-gkSZw-2Icq zKNi?N`oXAqgAGqG#c`+SLXCv!-d;^5OQUD)x^t~WlbMp1A8Boh33>Jp7R?H0RU>%C zdr9t;M-{F6c_ub5({1}Y=5qEve}_r>adLDpe8_R-!`)gwp^lTg-@dLD+hhDZ_*|Oi z{q7lGVZ)!O(S#4!N6nkyo&|`S^OBicm@UKZ`CBcb{9CL;1kLT~*e!E|F`}JDN3`IE z@dG2JkR`W3VJJ`w!w;M(Y6|cB8rU8fkMS`lZ#GMM+Vo)1V|p89s=oc=DRao~8^Bz- z>Ep^Q>42}S{W$00@QBDXE&2lgot!0vY}3s|R5T~uA9vvPy23k+Tm{`4ua+H1rn38# znex|*U@&TtzPI6a5TnbLJB_m)yHYCy$=0#`XfSpKCBXMToVnaxnVaT;AD6Y?w_RfS z&`L?r>K7$UB3$v5&(=@hr;N?nUUoY>{mP+gChx>GBIiNK!NcPF9oh7aJMIL6C(5D@ z+{7bmyvuyTSPITZ!(YFtcuIaC0jk-$QK)lnlBE50{9~`*;wwk|`(pn&uBp*|-p#Ed z;)&woQy;tQRS4J*|jqJt{j6E_eMb0+fl@&F}{p`u};wKLE=KLsXydM;0>X* z2dXoggF6oN1tR=+NCCg0{0!b`U`R9t6wO(;%bHl4nYjngEFW!4JEf41$ajAYRdBGk_DtPzi2uxu^v`fK(*9{rwl-1^- z(lLMV##h10-V1Wv&X)rjg*#m3uPzwM^Wt|QI%5^8>o9Eg1K!8cS-;B5t`&EZsi1m4 z{B=IKMZE+XbLMZ-$AbWobnAwRotj=^K;3}oec@X(f8=qnaaC(iY!e~z5)wQvi;&T$ z(##1|yoIGPEr?DtCem#zjcteox-r$-oWL-*qBDr*^qfnGY$V>ynrLlFw8Dc1$XSS< zIWdTC7G!Kru=$xq2&Nm`Pze^sR7*2UhP5S~#-QOr8+m6GtaX~Y7fi+GLy)V$&wI1s zZcdJYs-71{!=;DU0RTFHixb&9uJ=n{r9!Hw3XIHAbviq4M{)9=Hxx!CR}0;cdHnE> z_S3++lYEwsT=xLnbOd#Ew?S}WQZ8Ob@O^562wfLo7oQ{4@=^DbCSBeP4B9Qm;vm23 zacOXHMpF?Ddg_+JwBjW7j_SZa07OcWZqNCZNjQb#0aru#;k_EabwF~uv@_MKCWn0) zKr*{LdVQg#vulMYFm;-=rLr9WRv&kU!hY>En6(z5k3e_I7M}Sp05D6NCaYnU$c}{S zpaHUyFyZvYWlk#J`!0Bxi 0): - textImages['text'] = 'Imagens coletadas com sucesso' - else: - textImages['text'] = 'Imagens não foram escolhidas' - -def selectDB(textChooseDB): - global fileToCreateDB - text = filedialog.asksaveasfilename(title = "Escolha uma base de dados ou crie uma",filetypes = (("csv files","*.csv"),("all files","*.*"))) - if(text[-4:] == ".csv"): - textChooseDB['text'] = text - fileToCreateDB = text - else: - textChooseDB['text'] = 'Arquivo escolhido não é base de dados' - -def appendControl(btAppend): - global append - if(append == True): - append = False - btAppend['bg'] = defaultColorButton - else: - append = True - btAppend['bg'] = '#7ccd7c' - -def click_and_crop(event, x, y, flags, param): - # Faz referencias as variáveis globais dentro da função - global db - global mode - global refPt - global image - global clone - - # se o botao esquerdo do mouse é pressionado ele salva as coordenadas iniciais e checa - # o modo para decidir o que fazer - - # Se o modo for 0, espera o botao esquerdo ser solto para capturar uma area da imagem - if event == cv2.EVENT_LBUTTONDOWN and mode == 0: - # Salva a coordenada em que o botao do mouse eh pressionado - refPt = [[x, y]] - - # Se o modo for 1, cria um quadrado com a cor do pixel para representar - # para o usuário, para que ele decida adicionar ou não na base aquela cor - elif event == cv2.EVENT_LBUTTONDOWN and mode == 1: - refPt = [[x, y]] - #Cria o quadrado a ser mostrado para o usuário - roi = np.zeros((300,300,3), np.uint8) - roi[:] = image[y,x,:] - # Adiciona as versão HSV e LAB da imagem - roiHSV = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) - roiLAB = cv2.cvtColor(roi, cv2.COLOR_BGR2LAB) - cv2.namedWindow('ROI',cv2.WINDOW_NORMAL) - cv2.moveWindow('ROI', 900, 50) - cv2.resizeWindow('ROI', 300,300) - cv2.imshow("ROI", roi) - - # Esse loop basicamente espera um comando do usuário para dizer qual o label daquele quadrado - # Apertando '1' o usuário adiciona aquela cor na base como vermelho - # Apertando '0' o usuário adiciona aquela cor na base como laranja - # Apertando ESC o usuário cancela aquela ação - while True: - keyColor = cv2.waitKey(20) - if keyColor == 27 or keyColor == ord('q'): - cv2.destroyWindow('ROI') - break - elif keyColor == ord("0"): - print("Other color") - vet = np.concatenate([roi[0,0,:], roiHSV[0,0,:], roiLAB[0,0,:], [0]]) - db = db.append(pd.Series(vet, index=db.columns ), ignore_index=True) - cv2.destroyWindow('ROI') - break - elif keyColor == ord("1"): - print("Target color") - vet = np.concatenate([roi[0,0,:], roiHSV[0,0,:], roiLAB[0,0,:], [1]]) - db = db.append(pd.Series(vet, index=db.columns ), ignore_index=True) - cv2.destroyWindow('ROI') - break - - # Se o modo for 0, e o botão esquerdo for solto, o código recorta a area - #selecionada e mostra para o usuário - elif event == cv2.EVENT_LBUTTONUP and mode == 0: - # Salva a coordenada em que o botao do mouse e solto - refPt.append([x, y]) - - #Ajusta valores selecionados (para pode arrastar o mouse de qualquer direção) - if refPt[0][0] > refPt[1][0]: - aux = refPt[0][0] - refPt[0][0] = refPt[1][0] - refPt[1][0] = aux - if refPt[0][1] > refPt[1][1]: - aux = refPt[0][1] - refPt[0][1] = refPt[1][1] - refPt[1][1] = aux - - #Cria uma imagem chamada roi, clonando a parte da imagem selecionada pelo usuario - roi = clone[refPt[0][1]:refPt[1][1], refPt[0][0]:refPt[1][0]] - # Adiciona as versão HSV e LAB da imagem - roiHSV = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) - roiLAB = cv2.cvtColor(roi, cv2.COLOR_BGR2LAB) - - # Mostra a imagem recortada para o usuário e espera ele decidir o que fazer com ela - cv2.imshow("ROI", roi) - # Esse loop basicamente espera um comando do usuário para dizer qual o label daquele quadrado - # Apertando '1' o usuário adiciona aquela cor na base como vermelho - # Apertando '0' o usuário adiciona aquela cor na base como laranja - # Apertando ESC o usuário cancela aquela ação - while True: - keyColor = cv2.waitKey(20) - if keyColor == 27 or keyColor == ord('q'): - cv2.destroyWindow('ROI') - break - elif keyColor == ord("0"): - print("Outra Cor") - listOfDF = [] - # Cria uma lista onde serão salvos os valores de cada pixel em BGR, HSV e LAB junto - # com a classe para depois adicionar na base de dados - for i in range(roi.shape[0]): - for j in range(roi.shape[1]): - if roi[i,j,0] != 0 and roi[i,j,0] != 1 and roi[i,j,2] != 0: - vet = np.concatenate([roi[i,j,:], roiHSV[i,j,:], roiLAB[i,j,:], [0]]) - listOfDF.append(pd.Series(vet, index=db.columns)) - db = db.append(listOfDF , ignore_index=True) - cv2.destroyWindow('ROI') - break - elif keyColor == ord("1"): - print("Target color") - listOfDF = [] - # Cria uma lista onde serão salvos os valores de cada pixel em BGR, HSV e LAB junto - # com a classe para depois adicionar na base de dados - for i in range(roi.shape[0]): - for j in range(roi.shape[1]): - if roi[i,j,0] != 0 and roi[i,j,0] != 1 and roi[i,j,2] != 0: - vet = np.concatenate([roi[i,j,:], roiHSV[i,j,:], roiLAB[i,j,:], [1]]) - listOfDF.append(pd.Series(vet, index=db.columns)) - db = db.append(listOfDF , ignore_index=True) - cv2.destroyWindow('ROI') - break - - -def criaBase(): - global append - global fileToCreateDB - global imgFiles - global mode - global db - global image - global clone - - #Checa se deve fazer append ou nao - if(append): - db = pd.read_csv(fileToCreateDB, sep='\t') - print(db) - else: - db = pd.DataFrame(columns=['Bl', 'G', 'R', 'H', 'S', 'V', 'L', 'A','B', 'Color']) - - #Seleciona imagens - index=0 - img_list=imgFiles - border=[] - - # mode controla o tipo de seleção de pixels, - # podendo esta ser em area (clicando e arrastando) - # ou por pixel expecífico - mode = 0 - - l=len(imgFiles) - - # Cria uma nova janela do OpenCV - cv2.namedWindow('image',cv2.WINDOW_NORMAL) - cv2.moveWindow('image', 700, 500) - cv2.resizeWindow('image', 800,800) - # Comando designado para o mouse esta associado a função click_and_crop - cv2.setMouseCallback("image", click_and_crop) - - - while True: - image = cv2.imread(img_list[index]) - clone = image.copy() - - # Mostra a imagem e espera por um botão ser apertado - cv2.imshow("image", image) - key = cv2.waitKey(1) & 0xFF - - # Fecha a janela se ESC (27) ou 'q' forem apertados - if key == 27 or key == ord('q'): - break - - #Troca o modo de captura de cor - elif key == ord("1"): - if mode == 0: - print("Modo de pontos") - mode = 1 - else: - print("Modo de quadrados") - mode = 0 - - # Muda para a imagem anterior - elif(key == ord('[') or key == 81): - index-=1 - index%=l - - # Muda para a próxima imagem - elif(key == ord(']') or key == 83): - index+=1 - index%=l - - # Salva a base de dados criada no programa - # Futuramente na interface precisamos mudar o nome deste arquivo - elif(key == ord('s')): - print('salvando') - db.to_csv(fileToCreateDB, sep='\t', index=False) - break - - cv2.destroyAllWindows() - -def configureCreateDatabase(): - text1 = tk.Label(createDatabase, text = "Cria base de dados", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) - - textImages = tk.Label(createDatabase, text = '', wraplength=(largura - 20)) - textImages.place(relx=0.5,rely=0.3,anchor = tk.CENTER) - - btSelectImagesForDB = tk.Button(createDatabase, text='Selecionar imagens', command=lambda:selectImagesForDB(textImages), width=20) - btSelectImagesForDB.place(relx=0.50, rely = 0.2 ,anchor = tk.CENTER) - - textSavedDB = tk.Label(createDatabase, text = '', wraplength=(largura - 20)) - textSavedDB.place(relx=0.5,rely=0.5,anchor = tk.CENTER) - - btWhereToSave = tk.Button(createDatabase, text='Selecionar base', command=lambda:selectDB(textSavedDB), width=20) - btWhereToSave.place(relx=0.50, rely = 0.4 ,anchor = tk.CENTER) - - btAppend = tk.Button(createDatabase, text='Append', command=lambda:appendControl(btAppend), width=20) - btAppend.place(relx=0.50, rely = 0.6 ,anchor = tk.CENTER) - - btSaveSB = tk.Button(createDatabase, text='Rodar', command=lambda:criaBase(), width=20) - btSaveSB.place(relx=0.50, rely = 0.8 ,anchor = tk.CENTER) - - - btBack = tk.Button(createDatabase, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) - btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) - - -################################ -#Fim da Area do CREATE_DATABASE# -################################ - - - -################################ -#Inicio da Area do UNSUPERVISED# -################################ - -# Abre uma janela para selecionar o arquivo da imagem a ser usada -# Essa imagem é carregada em BGR e é retornado a versão BGR, HSV e LAB -# nesta ordem -def getImages(): - global imgFile - imgBGR = cv2.imread(imgFile,1) - imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) - imgLAB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2LAB) - return imgBGR, imgHSV, imgLAB - -# Cria uma base de dados que contem 9 colunas para cada atributo de cor, sendo eles -# Blue, Green, Red, Hue, Saturation, Value, ligh, A e B para ser usado na clusterização -# Retorna a base criada sem nenhum dado ainda -def createTestDataBase(imgBGR, imgHSV, imgLAB): - dbTest = pd.DataFrame(columns=['Bl', 'G', 'R', 'H', 'S', 'V', 'L', 'A','B']) - listOfDF = [] - for i in range(imgBGR.shape[0]): - for j in range(imgBGR.shape[1]): - vet = np.concatenate([imgBGR[i,j,:], imgHSV[i,j,:], imgLAB[i,j,:] ]) - listOfDF.append(pd.Series(vet, index=dbTest.columns)) - dbTest = dbTest.append(listOfDF , ignore_index=True) - return dbTest - -# Usa o kmeans com 5 clusters apenas com BGR - essa primeira clusterização é feita -# para separa apenas uma imagem com vermelho e laranja -# Em geral 5 clusters separa bem as duas cores -# Retorna os centros e os labels da clusterização -def firstKmeans(imgBGR): - canais = [0,1,2] - num_clusters = 7 - - tst = imgBGR - tst = tst[:,:,canais] - data = tst.reshape(tst.shape[0]*tst.shape[1], len(canais)) - km = KMeans(n_clusters=num_clusters) - km.fit(data) - labels = km.predict(data) - img_labels = labels.reshape(tst.shape[:2]) - centers=km.cluster_centers_ - return img_labels, centers - -# Usa o kmeans com 5 clusters e todos os atibutos BGR, HSV e LAB da imagem -# essa primeira clusterização é feita -# para separa apenas uma imagem com vermelho e laranja -# Em geral 5 clusters separa bem as duas cores -# Retorna os centros e os labels da clusterização -def firstKmeansAllAtrib(db, imgBGR): - - X_train = db.iloc[:, :].values - - canais = [0,1,2] - num_clusters = 7 - - km = KMeans(n_clusters=num_clusters) - km.fit(X_train) - labels = km.predict(X_train) - img_labels = labels.reshape(imgBGR.shape[:2]) - centers=km.cluster_centers_ - return img_labels, centers - -# Acha qual o label que contem as cores vermelho e laranja -# Separa esse cluster verificando qual cluster possui o maior valor vermelho -# do espectro BGR -def getLabelRedRGB(centers): - maxVerm=0 - clusVerm=[] - for i in range(1,len(centers)): - if centers[i][2] > maxVerm: - maxVerm=centers[i][2] - - for i in range(1,len(centers)): - if centers[i][2] >= (0.5*maxVerm): - if((centers[i][2]>centers[i][0]) and (centers[i][2]>centers[i][1])): - clusVerm.append(i) - - return clusVerm - - - -# Acha qual o label que contem as cores vermelho e laranja -# Separa esse cluster verificando qual cluster possui o maior valor vermelho -# do espectro BGR -def getLabelGreenRGB(centers): - global mode_DEBUG - maxVerd=0 - clusVerd=[] - for i in range(1,len(centers)): - if centers[i][1] > maxVerd: - maxVerd=centers[i][1] - if(mode_DEBUG): - print(maxVerd) - for i in range(1,len(centers)): - if(mode_DEBUG): - print(centers) - if centers[i][1] >= (0.4*maxVerd): - if((centers[i][1]>centers[i][0]) and (centers[i][1]>centers[i][2])): - clusVerd.append(i) - if(mode_DEBUG): - print("\n\n") - - return clusVerd - - -# Faz o kmeans utilizando a imagem LAB apenas -# essa segunda clusterização recebe uma mascara, para evitar que -# outras cores alem de laranja e vermelho estejam na imagem. -# São clusterizados 10 clusters, porém futuramente estes serão agrupados -# selecionando qual é vermelho e qual é laranja, excluido a parte preta da imagem -# Retorna os centros e os labels da clusterização -def secondKmeans(imgBGR, img_mask,num_clusters): - # Retira as outras cores (deixando só vermelho e laranja) - imgAux = 255*(img_mask).astype(np.uint8) - imgFinal = cv2.bitwise_and(imgBGR, imgBGR, mask=imgAux) - - canais = [0,1,2] - - tst = cv2.cvtColor(imgFinal, cv2.COLOR_RGB2LAB) - tst = tst[:,:,canais] - - data = tst.reshape(tst.shape[0]*tst.shape[1], len(canais)) - - km = KMeans(n_clusters=num_clusters) - km.fit(data) - labels = km.predict(data) - img_labels = labels.reshape(imgBGR.shape[:2]) - centers = km.cluster_centers_ - return img_labels, centers - - -# Cria uma mascara para a cor vermelha, buscando nos centros retornadas pela clusterização -# Uma mascara binaria é criada para cada centro escolhido, e ao final uma operação OR -# é feita com todas as mascaras para gerar a mascara final -def getRedImageMask(imgBGR, centers, labels): - global mode_DEBUG - redList = [] - med=0 - num=0 - for i,cent in enumerate(centers): - tmp=np.zeros((1,1,3),np.uint8) - cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) - bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] - if (cent[0]>12): - minimum=bgrVers[2] - med+=bgrVers[2] - num+=1 - - if(num): - med/=num - - if(mode_DEBUG): - print(num) - print(med) - - for i,cent in enumerate(centers): - tmp=np.zeros((1,1,3),np.uint8) - cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) - bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] - if (bgrVers[2] > med and 0.3*bgrVers[2]>bgrVers[1]): - redList.append(i) - - - img_mask_r = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) - for label in redList: - currentMask = labels==label - img_mask_r = (img_mask_r) | (currentMask) - return redList, img_mask_r - -# Cria uma mascara para a cor laranja, buscando nos centros retornadas pela clusterização -# Uma mascara binaria é criada para cada centro escolhido, e ao final uma operação OR -# é feita com todas as mascaras para gerar a mascara final -def getDarkGreenImageMask(imgLAB, centers, labels): - global mode_DEBUG - greenList = [] - medLum=0 - medGreen=0 - num=0 - for i,cent in enumerate(centers): - tmp=np.zeros((1,1,3),np.uint8) - cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) - bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] - print(bgrVers) - if (cent[0]>12): - medGreen+=bgrVers[1] - num+=1 - if(num): - medGreen/=num - - if(mode_DEBUG): - print("\n") - print(medGreen) - - num=0 - for i,cent in enumerate(centers): - tmp=np.zeros((1,1,3),np.uint8) - cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) - bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] - if (cent[0]>12 and bgrVers[1]>medGreen): - medLum+=cent[0] - num+=1 - if(num): - medLum/=num - - if(mode_DEBUG): - print(medLum) - - for i,cent in enumerate(centers): - tmp=np.zeros((1,1,3),np.uint8) - cv2.rectangle(tmp,(0,0),(1,1),(cent[0],cent[1],cent[2]),-1) - bgrVers=(cv2.cvtColor(tmp,cv2.COLOR_LAB2RGB))[0][0] - - if(mode_DEBUG): - print("\n") - print(bgrVers) - print(cent) - if (cent[0] < medLum and cent[0]>12 and bgrVers[1]>medGreen): - greenList.append(i) - - if(mode_DEBUG): - print("PASS") - - - img_mask_g = np.zeros((imgLAB.shape[0], imgLAB.shape[1]),dtype=bool) - for label in greenList: - currentMask = labels==label - img_mask_g = (img_mask_g) | (currentMask) - return img_mask_g - -#Para Jupyter notebook para mostrar as imagens recortadas -# a partir das mascaras. Se estiver rodando no terminal, -# pode comentar essa parte -def showMaskAsImage(imgBGR, img_mask): - imgAux = 255*(img_mask).astype(np.uint8) - imgFinal = cv2.bitwise_and(imgBGR, imgBGR, mask=imgAux) - plt.figure(figsize=(8,8)) - plt.imshow(imgFinal[:,:,::-1]) - -# Faz um fechamento na imagem -# Funcão pq eu sempre esqueço o que um fechamento faz -def tiraBuraco(img_mask, num): - return ndi.binary_closing(img_mask, iterations=num) - -# Faz uma abertura na imagem -# Funcão pq eu sempre esqueço o que uma abertura faz -def tiraRuido(img_mask, num): - return ndi.binary_opening(img_mask, iterations=num) - -# Calcula o numero de objetos vermelhos da imagem e retorna em nr_objects -# Essa função depende da quantidade de ruido que vai vir no cluster vermelho -# Se tiver muito ruido, passar um num, maior para tirar o ruido da mascara -# utilizando abertura -def calcFires(img_mask, num): - if num > 0: - img_mask_sem_ruido = tiraRuido(img_mask, num) - else: - img_mask_sem_ruido = img_mask - labeled, nr_objects = ndimage.label(img_mask_sem_ruido) - - # Calculo das posicoes dos focos - f = open("focos.txt", "w") - print('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape)) - f.write('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape) + '\n') - focos = ndimage.measurements.center_of_mass(img_mask_sem_ruido, labeled, range(1,nr_objects+1)) - print(nr_objects) - for i in range(len(focos)): - print('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2))) - f.write('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2)) + '\n') - f.close() - - return labeled, nr_objects - - -# Calcula a area de desmatamento em percentual da area da imagem toda -def calcDeforestation(img_mask): - whitePixels = sum(sum(img_mask)) - totalPixels = img_mask.shape[0]*img_mask.shape[1] - area = round(float(100*whitePixels)/totalPixels, 2) - return area - -# Aumenta a saturação da imagem, colocando a saturação em 250 para toda a imagem -# Essa modificação é feina na imagem para a segunda clusterização, para que o vermelho -# se destaque mais e facilite a sepração do laranja -def increaseSaturation(imgBGR): - imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) - for i in range(imgHSV.shape[0]): - for j in range(imgHSV.shape[1]): - #if imgHSV[i,j,1] + 100 > 254: - # imgHSV[i,j,1] = 255 - #else: - # imgHSV[i,j,1] += 100 - imgHSV[i,j,1] = 250 - - imgBGR2 = cv2.cvtColor(imgHSV, cv2.COLOR_HSV2BGR) - return imgBGR2 - -# Algoritmo geral -def autoClaro(label): - global mode_DEBUG - imgBGR, imgHSV, imgLAB = getImages() - - # Cria uma base de dados para usar os 9 atributos - # B,G,R, H,S,V e L,A,B - db = createTestDataBase(imgBGR, imgHSV, imgLAB) - - #Realiza o primeiro kmeans para separar apenas as cores vermelho e laranja - # utilizando todos os atributos - labels1, centers1 = firstKmeansAllAtrib(db, imgBGR) - - #Identifica qual o cluster das cores vermelho e laranja - clusRedOrange = getLabelRedRGB(centers1) - - #Cria uma mascara do cluster vermelho e laranja - img_mask_RandO = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) - for i in clusRedOrange: - img_mask_RandO = img_mask_RandO|(labels1==i) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_RandO]=imgBGR[img_mask_RandO] - cv2.imshow('Red and Orange',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - - #Aumento de saturacao para a segunda clusterização - imgHigherSat = increaseSaturation(imgBGR) - - # Faz um segundo k means para separar o vermelho e laranja - labelsR, centersR = secondKmeans(imgHigherSat, img_mask_RandO,10) - #print(centers2) - - # Cria a mascara da cor vermelha a partir dos clusters do segundo kmeans - redList, img_mask_Red = getRedImageMask(imgBGR, centersR, labelsR) - img_mask_Red=tiraRuido(img_mask_Red,2) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_Red]=imgBGR[img_mask_Red] - cv2.imshow('Red',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - labels, nro = calcFires(img_mask_Red, 1) - - - - #Identifica qual o cluster dos verdes - clusGreen = getLabelGreenRGB(centers1) - - #Cria uma mascara do cluster verde - img_mask_AllGreen = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) - for i in clusGreen: - img_mask_AllGreen = img_mask_AllGreen|(labels1==i) - - img_mask_AllGreen=tiraRuido(img_mask_AllGreen,2) - img_mask_AllGreen=tiraBuraco(img_mask_AllGreen,1) - img_mask_AllGreen=tiraRuido(img_mask_AllGreen,3) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_AllGreen]=imgBGR[img_mask_AllGreen] - cv2.imshow('AllGreen',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - # Faz um segundo k means para separar obter o verde escuro - labelsG, centersG = secondKmeans(imgBGR, img_mask_AllGreen,6) - - - # Cria a mascara do verde escuro a partir dos clusters do segundo kmeans - img_mask_Green = getDarkGreenImageMask(imgLAB, centersG, labelsG) - img_mask_Green = tiraRuido(img_mask_Green,1) - img_mask_Green = tiraBuraco(img_mask_Green,1) - - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_Green]=imgBGR[img_mask_Green] - cv2.imshow('Green',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - label['text']="Area:{}%".format(calcDeforestation(img_mask_Green))+"\nFocos:{}".format(nro) - - -def autoEscuro(label): - global mode_DEBUG - imgBGR, imgHSV, imgLAB = getImages() - - #Realiza o primeiro kmeans para separar apenas as cores vermelho e laranja - labels1, centers1 = firstKmeans(imgBGR) - - #Identifica qual o cluster das cores vermelho e laranja - clusRedOrange = getLabelRedRGB(centers1) - - #Cria uma mascara do cluster vermelho e laranja - img_mask_RandO = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) - for i in clusRedOrange: - img_mask_RandO = img_mask_RandO|(labels1==i) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_RandO]=imgBGR[img_mask_RandO] - cv2.imshow('Red and Orange',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - - # Faz um segundo k means para separar as duas cores - labels2, centers2 = secondKmeans(imgBGR, img_mask_RandO,10) - - # Cria a mascara da cor vermelha a partir dos cluster do segundo kmeans - redList, img_mask_Red = getRedImageMask(imgBGR, centers2, labels2) - img_mask_Red=tiraRuido(img_mask_Red,2) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_Red]=imgBGR[img_mask_Red] - cv2.imshow('Red',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - - labels, nro = calcFires(img_mask_Red, 1) - - - #Identifica qual o cluster dos verdes - clusGreen = getLabelGreenRGB(centers1) - - #Cria uma mascara do cluster verde - img_mask_AllGreen = np.zeros((imgBGR.shape[0], imgBGR.shape[1]),dtype=bool) - for i in clusGreen: - img_mask_AllGreen = img_mask_AllGreen|(labels1==i) - - - img_mask_AllGreen=tiraRuido(img_mask_AllGreen,2) - img_mask_AllGreen=tiraBuraco(img_mask_AllGreen,1) - img_mask_AllGreen=tiraRuido(img_mask_AllGreen,3) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_AllGreen]=imgBGR[img_mask_AllGreen] - cv2.imshow('AllGreen',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - - # Faz um segundo k means para separar obter o verde escuro - labelsG, centersG = secondKmeans(imgBGR, img_mask_AllGreen,6) - - # Cria a mascara do verde escuro a partir dos clusters do segundo kmeans - img_mask_Green = getDarkGreenImageMask(imgLAB, centersG, labelsG) - img_mask_Green = tiraRuido(img_mask_Green,1) - img_mask_Green = tiraBuraco(img_mask_Green,1) - - if(mode_DEBUG): - background = np.zeros((imgBGR.shape[0], imgBGR.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgBGR.shape[1], imgBGR.shape[0]),(0,0,0),-1) - background[img_mask_Green]=imgBGR[img_mask_Green] - cv2.imshow('Green',background) - cv2.waitKey(0) - cv2.destroyAllWindows() - - - label['text']="Area:{}%".format(calcDeforestation(img_mask_Green))+"\nFocos:{}".format(nro) - -def configureAuto(): - text1 = tk.Label(auto, text = "Automatico", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) - - textAutoImage = tk.Label(auto, text = supImgFile, wraplength=(largura - 20)) - textAutoImage.place(relx=0.5,rely=0.4,anchor = tk.CENTER) - - btSelectImageAuto = tk.Button(auto, text='Selecionar Imagem', command=lambda:selectImage(textAutoImage), width=20) - btSelectImageAuto.place(relx=0.50, rely = 0.3 ,anchor = tk.CENTER) - - textAutoDados=tk.Label(auto,text="Area:\nFocos:",wraplength=(largura-20)) - textAutoDados.place(relx=0.5,rely=0.7,anchor=tk.CENTER) - - btCut = tk.Button(auto, text='Modo Claro', command=lambda:autoClaro(textAutoDados), width=20) - btCut.place(relx=0.25, rely = 0.55 ,anchor = tk.CENTER) - - btCut = tk.Button(auto, text='Modo Escuro', command=lambda:autoEscuro(textAutoDados), width=20) - btCut.place(relx=0.75, rely = 0.55 ,anchor = tk.CENTER) - - btBack = tk.Button(auto, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) - btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) - -############################# -#Fim da Area do UNSUPERVISED# -############################# - - - -############################## -#Inicio da Area do SUPERVISED# -############################## - -#Globais -supImgFile = "" -supDBFile = "" - -# Abre uma janela para selecionar o arquivo da imagem a ser usada -# Essa imagem é carregada em BGR e é retornado a versão BGR, HSV e LAB -# nesta ordem -def getImagesSup(imgFile): - imgBGR = cv2.imread(imgFile,1) - imgHSV = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2HSV) - imgLAB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2LAB) - return imgBGR, imgHSV, imgLAB - -# Cria o dataframe do pandas no python das bases escolhidas nas janelas -# Se mais de uma base foi selecionada, o algoritmo une elas -def createDataFrame(fileName): - finalDB = pd.read_csv(fileName, sep='\t') - return finalDB - -def selectDBSup(textChooseDB): - global supDBFile - text = filedialog.askopenfilename(title = "Escolha uma base de dados",filetypes = (("csv files","*.csv"),("all files","*.*"))) - if(text[-4:] == ".csv"): - textChooseDB['text'] = text - supDBFile = text - else: - textChooseDB['text'] = 'Arquivo escolhido não é base de dados' - -def selectImageSup(textChooseImage): - global supImgFile - text = filedialog.askopenfilename(title = "Escolha a imagem",filetypes = (("png files","*.png"),("jpg files","*.jpg"),("jpeg files","*.jpeg"),("all files","*.*"))) - print(text[-4:]) - print(text[-4:] == ".png") - if(text[-4:] == ".png" or text[-4:] == ".jpg" or text[-5:] == ".jpeg"): - print("aqui") - textChooseImage['text'] = text - supImgFile = text - else: - print("aqui2") - textChooseImage['text'] = 'Arquivo escolhido não é imagem' - #Avisar do erro - - -# Percorre cada pixel da imagem e verifica o label atribuido pelo KNN -# Se este label e 1 significa que aquele pixel foi classificado como target -# e deve ser adicionado a uma mascara binaria da imagem. -# Esta mascara é retornada -def getImageMaskSup(imgBGR, y_pred): - img_mask = np.zeros((imgBGR.shape[0], imgBGR.shape[1]), dtype = bool) - for i in range(imgBGR.shape[0]): - for j in range(imgBGR.shape[1]): - if y_pred[i*imgBGR.shape[1] + j] == 1: - img_mask[i,j] = True - return img_mask - - -def showMaskAsImage(imgBGR, img_mask): - imgAux = 255*(img_mask).astype(np.uint8) - imgFinal = cv2.bitwise_and(imgBGR, imgBGR, mask=imgAux) - imgFinalBoth = np.hstack((imgBGR, imgFinal)) - cv2.imshow('imagemCortada',imgFinalBoth) - while True: - cv2.waitKey(100) - if (cv2.getWindowProperty('imagemCortada',cv2.WND_PROP_VISIBLE) < 1): - break - cv2.destroyWindow('imagemCortada') - cv2.imwrite('imagemComparacao.png',imgFinalBoth) - - -# Calcula o numero de objetos vermelhos da imagem e retorna em nr_objects -# Essa função depende da quantidade de ruido que vai vir no cluster vermelho -# Se tiver muito ruido, passar um num, maior para tirar o ruido da mascara -# utilizando abertura -def calcFiresSup(img_mask, imgBGR, transfString, textTransf): - if len(transfString) > 0: - img_mask_aux = img_mask - for i in range(len(transfString)//2): - if(transfString[i*2] == 'A' or transfString[i*2] == 'a'): - print('Fazendo abertura de ' + transfString[i*2+1]) - img_mask_aux = tiraRuido(img_mask_aux, int(transfString[i*2+1])) - else: - print('Fazendo fechamento de ' + transfString[i*2+1]) - img_mask_aux = tiraBuraco(img_mask_aux, int(transfString[i*2+1])) - img_mask_sem_ruido = img_mask_aux - else: - img_mask_sem_ruido = img_mask - labeled, nr_objects = ndimage.label(img_mask_sem_ruido) - # Calculo das posicoes dos focos - f = open("focos.txt", "w") - print('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape)) - f.write('Tamanho da imagem : ' + str(img_mask_sem_ruido.shape) + '\n') - focos = ndimage.measurements.center_of_mass(img_mask_sem_ruido, labeled, range(1,nr_objects+1)) - imgBGR_aux = imgBGR - print(nr_objects) - for i in range(len(focos)): - print('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2))) - f.write('X = ' + str(round(focos[i][1],2)) + ', Y = ' + str(round(focos[i][0],2)) + '\n') - #Adicionando pontos na imagem - imgBGR_aux[int(focos[i][0]), int(focos[i][1]), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]), int(focos[i][1]+1), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]), int(focos[i][1]-1), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]+1), int(focos[i][1]-1), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]+1), int(focos[i][1]+1), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]+1), int(focos[i][1]), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]-1), int(focos[i][1]-1), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]-1), int(focos[i][1]+1), :] = [255,255,255] - imgBGR_aux[int(focos[i][0]-1), int(focos[i][1]), :] = [255,255,255] - f.close() - showMaskAsImage(imgBGR_aux, img_mask_sem_ruido) - textTransf['text'] = 'Nro de focos = ' + str(nr_objects) - return labeled, nr_objects, img_mask_sem_ruido - -# Calcula a area de desmatamento em percentual da area da imagem toda -# Essa função depende da quantidade de ruido que vai vir na mascara laranja -# Se tiver muito ruido, passar um num, maior para tirar o ruido da mascara -# utilizando abertura -def calcDeforestationSup(img_mask, imgBGR, transfString, textTransf): - print(transfString) - if len(transfString) > 0: - img_mask_aux = img_mask - for i in range(len(transfString)//2): - if(transfString[i*2] == 'A'): - print('Fazendo abertura de ' + transfString[i*2+1]) - img_mask_aux = tiraRuido(img_mask_aux, int(transfString[i*2+1])) - else: - print('Fazendo fechamento de ' + transfString[i*2+1]) - img_mask_aux = tiraBuraco(img_mask_aux, int(transfString[i*2+1])) - img_mask_sem_ruido = img_mask_aux - else: - img_mask_sem_ruido = img_mask - whitePixels = sum(sum(img_mask_sem_ruido)) - totalPixels = img_mask_sem_ruido.shape[0]*img_mask_sem_ruido.shape[1] - area = round((whitePixels/totalPixels)*100, 2) - showMaskAsImage(imgBGR, img_mask_sem_ruido) - textTransf['text'] = 'Area = ' + str(area) + '%' - return area - -#Algoritmo geral -def supervisedMethod(area, transfString, textTransf): - global supImgFile - global supDBFile - imgBGR, imgHSV, imgLAB = getImagesSup(supImgFile) - - #Cria o dataframe a partir das bases selecionadas - db = createDataFrame(supDBFile) - - #Separa os atributos da base criada para treinamento - # X = atributos - # y = labels - X_train = db.iloc[:, :-1].values - y_train = db.iloc[:, 9].values - - #Cria um KNN (deu bons resultados com ele e é rapido de treinar caso precisar - # criar uma nova base na hora da competição) - classifier = KNeighborsClassifier(n_neighbors=30) - # Treina o KNN com os dados da base - classifier.fit(X_train, y_train) - - # Cria a base de dados da imagem a ser classificada - dbTest = createTestDataBase(imgBGR, imgHSV, imgLAB) - - # Separa os atributos - X_test = dbTest.iloc[:, :].values - - #Realiza a predição utilizando o KNN - y_pred = classifier.predict(X_test) - - #Pega as mascaras de cada cor - img_mask = getImageMaskSup(imgBGR, y_pred) - - if(area): - print('Area = {}%'.format(calcDeforestationSup(img_mask,imgBGR, transfString, textTransf))) - else: - labels, nro, img_mask_final = calcFiresSup(img_mask, imgBGR, transfString, textTransf) - print('Nro de focos = {}'.format(nro)) - -def configureSupervised(): - text1 = tk.Label(supervised, text = "Supervisionado", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) - - textSupImage = tk.Label(supervised, text = supImgFile, wraplength=(largura - 20)) - textSupImage.place(relx=0.5,rely=0.25,anchor = tk.CENTER) - - btSelectImageSup = tk.Button(supervised, text='Selecionar Imagem', command=lambda:selectImageSup(textSupImage), width=20) - btSelectImageSup.place(relx=0.50, rely = 0.2 ,anchor = tk.CENTER) - - textSupDB = tk.Label(supervised, text = supDBFile, wraplength=(largura - 20)) - textSupDB.place(relx=0.5,rely=0.38,anchor = tk.CENTER) - - btWhereToSave = tk.Button(supervised, text='Selecionar Base de Dados', command=lambda:selectDBSup(textSupDB), width=20) - btWhereToSave.place(relx=0.50, rely = 0.33 ,anchor = tk.CENTER) - - textOut = tk.Label(supervised, text = '', wraplength=(largura - 200),bg="white") - textOut.place(relx=0.5,rely=0.62, anchor = tk.CENTER) - - btGetArea = tk.Button(supervised, text='Área', command=lambda:supervisedMethod(True, E1.get(), textOut), width=10) - btGetArea.place(relx=0.25, rely = 0.52 ,anchor = tk.CENTER) - - btGetPos = tk.Button(supervised, text='Posições', command=lambda:supervisedMethod(False, E1.get(), textOut), width=10) - btGetPos.place(relx=0.75, rely = 0.52 ,anchor = tk.CENTER) - - textTransf = tk.Label(supervised, text = 'Transformações', wraplength=(largura - 20)) - textTransf.place(relx=0.5,rely=0.47, anchor = tk.CENTER) - - textRes = tk.Label(supervised, text = 'Resultados:', wraplength=(largura - 20)) - textRes.place(relx=0.5,rely=0.57, anchor = tk.CENTER) - - E1 = tk.Entry(supervised, bd =2) - E1.place(relx=0.5, rely = 0.52 ,anchor = tk.CENTER) - - btBack = tk.Button(supervised, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) - btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) - - -########################### -#Fim da Area do SUPERVISED# -########################### - - - -########################## -#Inicio da Area do MANUAL# -########################## - -def color_difference (color1, color2): - # Retorna a distancia euclidiana entre duas cores no espaco RGB - return math.sqrt(sum([(abs(component1-component2))**2 for component1, component2 in zip(color1, color2)])) - -#Funcao retorna a imagem contornada e a area desmatada da mesma -def rgbKMeansImageSeg(img, num_clusters, difThres, it): - - #Reshape da imagem deixar como um vetor de pixels contendo as 3 cores para o mesmo - data = img.reshape(img.shape[0]*img.shape[1], 3) - - #Cria um agrupamento K-means e ajusta o mesmo aos dados - km = KMeans(n_clusters=num_clusters) - km.fit(data) - - #Salva os labels encontrados - labels = km.predict(data) - img_labels = labels.reshape(img.shape[:2]) - - return img_labels, km.cluster_centers_ - -def draw(img,center): - inv=(255*(1-center[0]/127),255*(1-center[1]/127),255*(1-center[2]/127)) - - cv2.rectangle(img,(0,0),(600,400),center,-1) - - cv2.line(img,(0,200),(600,200),inv,2) - cv2.line(img,(200,0),(200,400),inv,2) - cv2.line(img,(400,0),(400,400),inv,2) - - cv2.rectangle(img,(75,75),(125,125),(255,0,0),-1) - cv2.rectangle(img,(75,275),(125,325),(0,255,0),-1) - - cv2.rectangle(img,(275,75),(325,125),(0,128,255),-1) - cv2.rectangle(img,(275,275),(325,325),(0,128,0),-1) - - cv2.rectangle(img,(475,75),(525,125),(0,0,255),-1) - cv2.rectangle(img,(475,275),(525,325),(0,0,0),-1) - - - cv2.rectangle(img,(75,75),(125,125),inv) - cv2.rectangle(img,(75,275),(125,325),inv) - - cv2.rectangle(img,(275,75),(325,125),inv) - cv2.rectangle(img,(275,275),(325,325),inv) - - cv2.rectangle(img,(475,75),(525,125),inv) - cv2.rectangle(img,(475,275),(525,325),inv) - - return img - -def classifyCenters(num_clusters,txtLabel): - global mouseX,mouseY,imgFile - mouseX=-1 - mouseY=-1 - - #AZUL,VERMELHO,LARANJA,VERDE_CLA,VERDE_ESC,PRETO - class_centers=[[],[],[],[],[],[]] - color=np.zeros((400,600,3),np.uint8) - cv2.namedWindow('Centers') - cv2.setMouseCallback('Centers',get_click) - - img = cv2.imread(imgFile) - - if(img.shape[0]>img.shape[1]): - prop = float(img.shape[1])/img.shape[0] - imgResized = cv2.resize(img,(int(prop*720),720)) - else: - prop = float(img.shape[0])/img.shape[1] - imgResized = cv2.resize(img,(720,int(prop*720))) - - cv2.imshow('Image',imgResized) - cv2.moveWindow('Image',670,0) - - - labels, centers = rgbKMeansImageSeg(imgResized, num_clusters, 30, 1) - - i=0 - for center in centers: - draw(color,center) - cv2.imshow('Centers',color) - cv2.moveWindow('Centers',0,0) - - - inv=(255*(1-center[0]/127),255*(1-center[1]/127),255*(1-center[2]/127)) - background = np.zeros((imgResized.shape[0], imgResized.shape[1],3),np.uint8) - cv2.rectangle(background,(0,0),(imgResized.shape[1], imgResized.shape[0]),inv,-1) - - mask=(labels==i) - - - background[mask]=imgResized[mask] - - cv2.imshow('Masked',background) - cv2.moveWindow('Masked',670,720) - - mouseX=-600 - Col=-1 - while(Col<0): - Col=(3*mouseX)/600 - Lin=(2*mouseY)/400 - cv2.waitKey(15) - class_centers[3*Lin+Col].append(i) - i+=1 - - cv2.destroyAllWindows() - - binImg = np.zeros((imgResized.shape[0], imgResized.shape[1]),dtype=bool) - for label in class_centers[4]: - mask = labels==label - print(label) - print(class_centers[4]) - binImg = (binImg) | (mask) - area=calcDeforestation(binImg) #1 - - binImg = np.zeros((imgResized.shape[0], imgResized.shape[1]),dtype=bool) - for label in class_centers[2]: - mask = labels==label - binImg = (binImg) | (mask) - labels, nro=calcFires(binImg,0) #2 - - txtLabel['text']="Area:{}%".format(area)+"\nFocos:{}".format(nro) - -def configureManual(): - text1 = tk.Label(manual, text = "Manual", font='Helvetica 18 bold').place(relx=0.5,y=15,anchor = tk.CENTER) - - textManImage = tk.Label(manual, text = supImgFile, wraplength=(largura - 20)) - textManImage.place(relx=0.5,rely=0.35,anchor = tk.CENTER) - - btSelectImageMan = tk.Button(manual, text='Selecionar Imagem', command=lambda:selectImage(textManImage), width=20) - btSelectImageMan.place(relx=0.5, rely = 0.27 ,anchor = tk.CENTER) - - textManDados=tk.Label(manual,text="Area:\nFocos:",wraplength=(largura-20)) - textManDados.place(relx=0.5,rely=0.8,anchor=tk.CENTER) - - textSpinBox=tk.Label(manual,text="Numero de Centros:",wraplength=(largura-20)) - textSpinBox.place(relx=0.5,rely=0.475,anchor=tk.CENTER) - - spinBox = tk.Spinbox(manual, from_=1, to=16) - spinBox.place(relx = 0.35, rely = 0.5) - spinBox.insert(0,8) - spinBox.delete(1) - - btCut = tk.Button(manual, text='Classificar centros', command=lambda:classifyCenters(int(spinBox.get()),textManDados), width=20) - btCut.place(relx=0.50, rely = 0.65 ,anchor = tk.CENTER) - - btBack = tk.Button(manual, text='Voltar ao menu', command=lambda:raise_frame(menu), width=20) - btBack.place(relx=0.50, rely = 0.9 ,anchor = tk.CENTER) - - - -####################### -#Fim da Area do MANUAL# -####################### - - - -##################### -# MAIN # -##################### - - -# Cria a janela em que a interface irá rodar -largura = 600 -altura = 400 -window = tk.Tk() -window.title('Zenith - Missão INPE') -window.geometry(str(largura) + "x" + str(altura) + "+100+100") -window.resizable(0, 0) - -#Cria frames da interface -menu = tk.Frame(window,width=largura, height=altura) -crop = tk.Frame(window,width=largura, height=altura) -createDatabase = tk.Frame(window,width=largura, height=altura) -supervised = tk.Frame(window,width=largura, height=altura) -auto = tk.Frame(window,width=largura, height=altura) -manual = tk.Frame(window,width=largura, height=altura) - -# Gruda os frames para que todos fechem juntos -for frame in (menu, crop, createDatabase, supervised, auto, manual): - frame.grid(row=0, column=0, sticky='news') - -#Configura todos os frames -configureMenu() -configureCrop() -configureCreateDatabase() -configureSupervised() -configureAuto() -configureManual() - -#Inicia no frame do MENU -raise_frame(menu) - -#Loop da interface -window.mainloop() diff --git a/interface/Aker/mm.cpp b/interface/Aker/mm.cpp index a86253f..68a077d 100644 --- a/interface/Aker/mm.cpp +++ b/interface/Aker/mm.cpp @@ -1,14 +1,156 @@ #include "mm.h" +#include #include "ui_mm.h" +#include -MM::MM(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MM) -{ - ui->setupUi(this); +#include +#include +#include +#include +#include + +#define portname "/dev/ttyUSB0" + +MM::MM(QWidget *parent):QMainWindow(parent), ui(new Ui::MM){ + + QTimer *mainTimer = new QTimer(this); + connect(mainTimer, &QTimer::timeout, [=]() { + int fd = ::open(portname, O_RDWR | O_NOCTTY | O_SYNC); + set_interface_attribs (fd, B115200, 0); + printf("Updating...\n"); + // set no blocking + //set_blocking (fd, 0); + if(fd>=0){ + QTimer::singleShot(1000,this,[=](){ + updateVals(fd); + ::close(fd); + }); + }else{ + //printf("error %d opening\n", errno); + } + }); + mainTimer->start(2000); + + + + ui->setupUi(this); + connect(ui->actionBack, &QAction::triggered, [=]() { + parentWidget()->show(); + this->close(); + }); +} + +void MM::updateVals(int fd){ + //package data; + char buff[21]; + char data[4][6]; + + memset(buff,0,sizeof(buff)); + memset(data,0,sizeof(data)); + + ::read(fd,&buff,20); + + if(buff[0]!='\0'){ + printf("\n|%s|\n",buff); + for(int i=0;i<4;i++){ + ::memcpy(data[i],&buff[5*i],5); + //printf("-%s-",data[i]); + } + + ui->val0->display(data[0]); + ui->val1->display(data[1]); + ui->val2->display(data[2]); + ui->val3->display(data[3]); + } } -MM::~MM() -{ - delete ui; +int MM::set_interface_attribs (int fd, int speed, int parity){ + struct termios tty; + memset (&tty, 0, sizeof tty); + if (tcgetattr (fd, &tty) != 0){ + return -1; + } + + cfsetospeed (&tty, speed); + cfsetispeed (&tty, speed); + + tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars + // disable IGNBRK for mismatched speed tests; otherwise receive break + // as \000 chars + tty.c_iflag &= ~IGNBRK; // disable break processing + tty.c_lflag = 0; // no signaling chars, no echo, + // no canonical processing + tty.c_oflag = 0; // no remapping, no delays + tty.c_cc[VMIN] = 0; // read doesn't block + tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout + + tty.c_iflag &= ~(IXON | IXOFF | IXANY);// shut off xon/xoff ctrl + + tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls, + // enable reading + tty.c_cflag &= ~(PARENB | PARODD);// shut off parity + tty.c_cflag |= parity; + tty.c_cflag &= ~CSTOPB; + tty.c_cflag &= ~CRTSCTS; + + if (tcsetattr (fd, TCSANOW, &tty) != 0){ + return -1; + } + return 0; +} + +void MM::set_blocking (int fd, int should_block){ + struct termios tty; + memset (&tty, 0, sizeof tty); + if (tcgetattr (fd, &tty) != 0){ + return; + } + + tty.c_cc[VMIN] = should_block ? 1 : 0; + // 0.5 seconds read timeout + tty.c_cc[VTIME] = 5; + + if (tcsetattr (fd, TCSANOW, &tty) != 0){ + return; + } +} + +MM::~MM(){ + delete ui; +} + +void MM::on_sendComm_clicked(bool checked){ + QList allButtons = ui->comandos->findChildren(); + char comm[4]; + + memset(comm,255,sizeof(comm)-1); + comm[3]='\0'; + printf("%s\n",comm); + + int n; + + for(n=0;nisChecked()){ + break; + } + } + + if(n!=allButtons.size()){ + printf("Button: %d\n",n); + int fd = ::open(portname, O_RDWR | O_NOCTTY | O_SYNC); + comm[0]=('\0'+n); + printf("%s\n",comm); + ::write(fd,comm,sizeof(comm)); + ::close(fd); + } + if(n==1){ + system("python ../get_adc.py"); + }else if(n==3){ + }if(n==5){ + system("python ../get_img.py"); + system("python ../interface.py"); + }); + } + + } diff --git a/interface/Aker/mm.h b/interface/Aker/mm.h index 1a2450b..ac0e941 100644 --- a/interface/Aker/mm.h +++ b/interface/Aker/mm.h @@ -9,14 +9,23 @@ class MM; class MM : public QMainWindow { - Q_OBJECT + Q_OBJECT public: - explicit MM(QWidget *parent = nullptr); - ~MM(); + void on_sendComm_clicked(); + int set_interface_attribs (int fd, int speed, int parity); + void set_blocking (int fd, int should_block); + explicit MM(QWidget *parent = nullptr); + ~MM(); +public slots: + void updateVals(int fd); + +private slots: + void on_sendComm_clicked(bool checked); private: - Ui::MM *ui; + Ui::MM *ui; + }; #endif // MM_H diff --git a/interface/Aker/mm.ui b/interface/Aker/mm.ui index 125e915..0029fe1 100644 --- a/interface/Aker/mm.ui +++ b/interface/Aker/mm.ui @@ -6,25 +6,502 @@ 0 0 - 800 - 600 + 1225 + 668 Aker System - Mission Monitor - + + + + + + + 1 + + + 0 + + + 1 + + + 0 + + + 0 + + + + + + 1 + 0 + + + + + 380 + 256 + + + + image: url(:/resources/Images/Logo.png); +border:0px; + + + QFrame::StyledPanel + + + QFrame::Raised + + horizontalSpacer_2 + horizontalSpacer + + + + + + Qt::Horizontal + + + + 300 + 20 + + + + + + + + Qt::Horizontal + + + + 300 + 20 + + + + + + + + 0 + + + + + 0 + + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 0, 0, 128); + + + + + -1.000000000000000 + + + + + + + + 1 + 1 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 255, 255, 220); +font: 36pt "Ubuntu Condensed"; + + + Tensão Charger + + + + + + + + 1 + 1 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 0, 0, 128); + + + + 5 + + + QLCDNumber::Filled + + + -1.000000000000000 + + + + + + + + 1 + 1 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 255, 255, 220); +font: 36pt "Ubuntu Condensed"; + + + 1 + + + Tensão Bateria + + + Qt::AutoText + + + + + + + + 1 + 1 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 255, 255, 220); +font: 36pt "Ubuntu Condensed"; + + + + Tensão Painel 2 + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 1 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 255, 255, 220); +font: 36pt "Ubuntu Condensed"; + + + Tensão Painel 1 + + + + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 0, 0, 128); + + + + -1.000000000000000 + + + + + + + + 1 + 1 + + + + + 0 + 0 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 0, 0, 128); + + + + false + + + 5 + + + -1.000000000000000 + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 225 + 20 + + + + + + + + 0 + + + + + + 1 + 0 + + + + + 0 + 50 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 255, 255, 220); +font: 42pt "Ubuntu Condensed"; + + + Comandos + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + border:1px solid rgba(255, 255, 255, 50); +color:rgba(255, 255, 255, 220); +font: 36pt "Ubuntu Condensed"; + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + color:rgba(255, 255, 255, 220); +font: 32pt "Ubuntu Condensed"; + + + Lançar Antena + + + + + + + + 0 + 0 + + + + color:rgba(255, 255, 255, 220); +font: 32pt "Ubuntu Condensed"; + + + Determinação de Atitude + + + + + + + + 0 + 0 + + + + color:rgba(255, 255, 255, 220); +font: 32pt "Ubuntu Condensed"; + + + Estabilização + + + + + + + + 0 + 0 + + + + Apontamento + + + + + + + + 0 + 0 + + + + color:rgba(255, 255, 255, 220); +font: 32pt "Ubuntu Condensed"; + + + Ler Parâmetros Elétricos + + + + + + + + 0 + 0 + + + + color:rgba(255, 255, 255, 220); +font: 32pt "Ubuntu Condensed"; + + + Imageamento + + + + + + + + + + + 0 + 0 + + + + + 0 + 42 + + + + color:rgba(255, 255, 255, 220); +font: 32pt "Ubuntu Condensed"; + + + Send + + + + + + + + + 0 0 - 800 - 22 + 1225 + 20 + + + color:rgb(255, 255, 255); + + + Help + + + + + + + Back + + diff --git a/interface/Aker/resources.qrc b/interface/Aker/resources.qrc index e6abc50..9222a1c 100644 --- a/interface/Aker/resources.qrc +++ b/interface/Aker/resources.qrc @@ -3,6 +3,7 @@ Images/Logo.png Images/cubesat.png Images/balloon.png + Images/left-border.png From c1ca2415cdd73974469f788d6376729d6e6998e3 Mon Sep 17 00:00:00 2001 From: Rodrigo-P Date: Wed, 21 Aug 2019 21:19:24 -0300 Subject: [PATCH 3/3] Imagem de teste --- imageProcessing/Interface/test.png | Bin 0 -> 112611 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 imageProcessing/Interface/test.png diff --git a/imageProcessing/Interface/test.png b/imageProcessing/Interface/test.png new file mode 100644 index 0000000000000000000000000000000000000000..68b7bffb02d8894a0df46a739e382b1ce4817c66 GIT binary patch literal 112611 zcmXtfc{o)48#mJwqq0nv>ah&YWQp{o#cqhqjBJyktSw6R$j&g9VkpMg9@#}WLS!FH zMM;S4j3s2>vJS@Z&hLG%_qyi%Gk=`xI_Li0pZl}ihh%K1cbe-w7aJSfX$)Ei$HvBv zU|lCUA*_FPqB(9IY=$8YJ+CUtB*5dike-BHmzunb;%|)Q4%ncuQ3RDtA+yD z&m7Y|xy}oMaVwuqc5nYiLv@HcWn=T%AV2?$&~P@A=yJ*{)4W^K2T|ddPuLBw$|7I_ zh=F5axu$^Ds~6$RqjnhlA5AAvJKSm6K_D~o?+5Va1w-d^ZSxOW@m`qW&FIaQjf1Vp zCf}nK^M;1F7nix7C~R{p#^dnkj^A?Ze~|1LVmLvgM`>q=>Prm$$Qb6&b3iJGeHv4I z&coyLc)KpQj^X%Zmhestg@5#9wmp|>bYkvNf5&`H$L=gOcVN-=C!;K>dz{u^W{DPf z6q|$vFIN@ajODvIYkF@#G@;YpvJkAHhSR0#-8j9|5;rB17@(KJSeN3zz%>Eu0PDn= zdS&~Ec(%FEc6fQE2fqukZMEsotj=%pewTvCKK0=Gd$bDD5|YsAY=D`dMkW*Ul#1dC)z5OEj+#utjHl1v0b$yjlBECd;X#^Ufw?tpQY+2~zmZi}sH2&YA z5?MG6EvuZ==a4Ne{!JX1icgOsJ8!UE_394cSEN0rP_V~`4EfkeQ|Epyo`1;5+3D!& z8eC%d>{;3#ZK%7AUkbY%Da~UbWzzODGE0OU;~Sz22ToGBPNze2-t3x&zk|NwpX#Zl zp98^$LRXQcn)hwZR6??62t}@H^AG2WGrpnl=VpfmtaRAAvI3Z?FA_DIYJmaPF9fvn zOi9A0tdGx}N+~Y)6fZ{#D4Ah|L*K;r3`pY>;P>0(UZq*}(HVYGT-KL9&g=h55jj_T z)%(JyiWpG;sok^DvALa&tsb06Gygx)*At-&kL7Sv#_F2tN56&k-%c=YGH2Hg24?pB zn>t$k^(D60!j)nq3Ja=4DQsFeaT#&w*1gC_qB?0xV9(8G@8Jku2s;MYB3=pVUPPfe zd5tlq+Rwxf6)*Dr8}%)Q_xu%|HV7urj31VbM_nNTh%8*FL18%MKb^RQTv1%WS_$Rq zbz|$fG<;^Fsr4?21z(nR2W=Tw76j zXld1gl*7NMRxYBoJR-dvyml=y-68y@{M)svx1I}G<4pndu%?mVt$^TyiG%bBm5kE4 zGR+-%lE$%9$l)-YX+8ywg#e)JFanChrzn-ZHkFMH`AZ597v&}8W&ucKoGHb{>sZW9 zQKCQkD^wB?cg{SA&HJ`-lh1f+$o=i=tfF|1lD>SLGa~%Ak})1fJE4CZ2$VSf<^+`T zN~#2G@KMjDQ~>-}+I;2oe-sQ-C`#<6p-Ni(J@WLw7xk_CevU7LSX>^3!b`xZ(Dx>T z0uC1&g7?2FH2zJs7sKb7VJ$2bdy7uxd#(~v3rpI!Q5}RUW^Gu=ciEyh3HHu9=9USL zl;^XxrTE9+cIZ><127& z%o8UMQ%#>ovC}`)1D49yT`LQe`$rWB-Aw*LwFBTn!&t`n(rO;erqY!?qaQjguXKGg^JdX4T-{I(Mgq3 zDYXvXd7x=g(WT}NtB-f{daU*{7#&-+R0h-LPQButWnYUMI~{%YMycoM12%b;Rm-}O zKjXF%RDOAT+_V35cm??OPk+p*25vDYyi*#ZBtD~#zI^4`C4aoRwcEzdV9EQs^tp!T zw(aRlw|7eZFbEBxZR#LxZ1P^v5S{ z>fqC6m4Scf#w05(u7yDwyOi4Gz88+9@JB3`$IN9eOt403ckq$wkzGU6>Q42cGzpU3 z_3dM>V>bLT754&#M;20s9!Xk4(})u6gDR9EZfM`NbE+h>{yPaA{GUv&ZX_81>%5n2 zDE=JrPyWdDV5cIWrgHaxlIG3CzV`f7d#a;UaB}9iqM0M<+l%S&8la!I=gfzb7W^yM z6#v9?LnHX^U`&e8P^+0LFtfA%gVE@`-+-6MTEyUTxy{f-xm$$MpFeul`!=@Y^vza6&=5U2 zgYlvkES}Ca%;>`RxlBy_r&e6w)Yr4rn!IFrsdat&p2LIS_S&|kk0mm(6PcH`8Z&Rz zX3~XrGzOUI`x@bNvCz%O!AFg0n>!u`8B5-IHm;bM=Y;JU^CjAzYm@a9Epao2;WzHa zpzLdDsxMLE*JkTwKK__rIjs8FG{StJ_s`smk|3&w{Epx3jNI1a4BH)MT7!aetjSM> z_dkEkrfwfh2C6n!H_R<;l+Q9FOqB{|md3j0{K^#6%^eh8H~9?YFPuPEducu`A$2|v6jFA7EsRy3r`L!Rb zJ8-(~j{TdQH36%qDrwOi0;pHS$f`Jtfd90PFE_q9larAtD5H(cdrnROeIey_y=D>3c);S&{ivp))0Oei{zZu||!$ znx%tcGlaKd^)SYNfrFbkKTAU?McZ^CgPA^+CD*l!Vljl-`p%Ulrf2_pfAJ2}>Oqt3 zqeBY?pRT-^_Th$Rn$6Fd!r5+xgDVZ03WGjw7k7jd2u@E#6oe)+HXp3*tb6rMH!hiN z1`-;Z)>7T*<3|01wN`=k)aN#Tsr$aef$sN;>G`>JRwYs!&(}&zCmI^lUe)0|JQ{48 z_N$Km93A@Xw;t`=b_k86#Eyd4JV%)y%5IDmGbZxl z%IyY5b9Sz^JVCRC1zv;xFLYk;NO;?~zcHmhwzzrWn*aQxmBf~|SM`c`0a?tw6|U?G zb55TpR~ldPet1{|V>^+%x_j@BE`)HTGlr#<148HQI8Xe$jZRc%jidwQD7} z?Q`NT9`{$t>mBK?jLm&s>hNIZ?-%sfW(O&%%j~;`?-s?a>8-pZxS68Q-dyZSo#v^6%zyXTcaOuY`$dzTZff9T|0apgN9?yYF%)JS-j5Cpz3UEJ zu-%`gwYztPS~qr%dN?}y3HYm+FOLZBYOA<{KS=5VNM-NAg!lzJamPn5qN#NkTT0?i zU_3|5iT~LZfVxYcKiqT}k#q}u@?OqMJ&pZR$vdl&AAL&C9mQ-oif$2-S3rTs-0#%? zbllzR83Y<`=I}pN$@g&l__}7GOj`Jq_*22cW$cbw&w``@P8^2+c;%s@B-JA~R%Y$j zIZQ(mPi9A9ui(I$r7I$69G==>{zpBrxk$o9d+6NS56d;nPQMNVhKCbnE1ye40jb35akm-Et}BU)p0tiY-!}?}13ZgO%-2GD3ceRjd%Q0t zx8Io9&OAC4TM%QeYPKg|{NXfq6D!abu=_PAz^|cxabdhFIbiVab?3btNxOS4qA%GD z3V(=9$Ehyzcx&sxugo_dRRUpZGDLaSw zSzP=YcC>-=O2k+?@uak3RGzd11jdeekJb(+8$}Y6>H^k!l@I`4E75s_2mxO?<2a=s zQ5`VE74ST=CMO~?C5|<=vwo0Zc@!wd`50bzp>s~A-DFAG$o+>O+p&*V?6h^XRHIb% zC43c)-`2d|^0XYAIfYoraG~TqN(N)nIif)o(5=>plGDSUUI+pdiPiJqb&Yb~R&L{c zyR`TfPWRVrdZC_G`Sq|J>hC@(Ms)LO~Q}egVjK0W#-|?irB&Y_}(Le+A#`_)zo`c zM+ZDdI!E(9hYpPzyrqQa_49`5bN7P%#$D;-YMmxyX%`Xz4U=|k=qs$C_?0MMX{1Rc zIqRa1q%JRK_YZp=BpC|0OYovS_@y!A4~iIBysXYviWF)4;=eKG^4Ut-h0HtaK`)v5O zUp-3gImS;);LxrFo~VlFfFD9JfH4t3!A&rxh7j~|tVG)|nQQ9#_4arlurW^~j( z;mhREtP$CJq%o>y@9;~nR4iy-lgZTlb+oH`XbApB{B=^XNA%HR50U3hYJv#G;9ZlvUfAfZ8q{#?zK#%xPD}fEh11IOA zD0@K4qJ%^sOQ+=+ZCqBwANT(*WhQ`xeZL4`i70ciV9-0{cKK%8W@+HlKPnpbdv8CYs^kDa(*SWD?v7?2O4fNSA z8^6?(OQfO|@W(MN$SrOYQ@+dW0DGLyj27PmXF?Ju+4O}A1}P4Iatm%uVf*(S;zYP9 zuJGG2e%;;oY%)QJ`Rt2{UBoGIt)4iJ%i?AdiKZxtAS5q>RmfwK`>{=2qPMGQWXV;;% zSPbf43MH#-OrD0rKp+7-U9Q1Qr>6DZO|iq>%AHJW8$uI#e2d87{#-c@Bd=4#Xw27_&SxV*oj+d8QcdPPqlV zMX+O@VPE*76LO`y71Gd#d-RX4z&3Bkni68>@e*0 z|8$^Gu8Z27qf_EAiSS5MUXSD5yYFRD65L4mGx(o~)t7%SYMt-uvI*XCYC3e<O0{=Sc)hMfFJeDvvb zUR2UG#Ux2l%8>h}u}MDqC3NfD{Z^j4h(hTN5|B$KGnQ9l5GI@#y|X-x*yvcPF;wA7a*m9l(N^5DcrjE;rT`u=efBvuteZlM_6R*qkjk|(uOfMi&bE9 zrCi=tcQG6xqeRw0TxOT}Du_NfT}W(``YMG1&Yc0iiX#iT5)&kp75#}VN<=KWko$ojQxAh$OtU9K-r zl63HRJN!;vc4}X)qF#9d`rU|Mq9N&S_XEr&2H(UtY;*@@EVi1vB#*Vh4X_ z{)(0Is1AR^i7-1`w}pat?e~j=nN5v^X`s3zMrhk+u)4Z>&&aL5CTFezV8^7rl4>J! zVqRd+U+_+A`d2RxE%$3Hp&e-|d5Uvr`GMiug4O>0yke|E^()Iym)!O==A>P~rNT>2 zEo0hV_b`T?EoYLU*kXOhmFa>-jVUZR_s{+3u$w;@8&uwjH!F!Fxsp79YB2OpZSrkC zxY+oFevaTMJ;Z+R)I0b+ZyH;=o9re03oB1wG?VdN?!EarrvP}IxYrT* z#NMaQ|4BJvVfsNpY^w?U;*0kcUQZJjD{d~kQ0w?F^h%@g6r$SDrSmH(X)=YNzqRp7 zy_45mLj_WQZudO*LbRQ-uHk~Ha?p~YkJOs_sH;(OwEpAZf}BNz>uRcNleoM#m#*j< zeYXvBlDA!}X|j1y#d?EXs9$gfE!Q!@-SwGwEwIE;miP;M!TC(nmAWJUc4<33 z!?bAaPnA=RaI)2+T9=Tk%+C&-ZRNnPWQV4o=eNtAx-gQX)$fQk)-AVxT^bUYaLh1k z^|w!wr<#|BU8zj;Uq$u>-BcqzZtTM9=*fw+d@h~?@A!RqI*E>%Tzx!;dWv8J&#!FN9 z*NWjk3mJ7n;t|?5PL{P>WmdO``jnlDBU6+E_UAt~d+Vy#d6@Pa(AF33NDt1=zRh6# z32O4R9?>-9p5y2Vk6Ru4Bzoq?!o0GDh#w&QzS2YA%oFA{ zku3XQif6WRTQISqZd}O8duKFex?;|Mn3jL~`8jhXQ>j2Jrb&GUT?lse;lB(29u_gW znvSN$b|#J%BJ_jSe248Qmo6nwr>-rQ9?fgcXCB%%F*h291B@>7@8gWbG=kDH#}?nl zS{Qu<(gW6pU1u3W%2eyTM?Uuk9U3&B%xpDQ4qD7n)%v=NN_+9!x|e$WmtGMxTq<{Z zJv_a#3522xM*XRQa_0Rj7M^3O8iZF#zA6EE8PA;qH#0hDy#x^_v(2R2j=pp4GrJp< z!<{bX`rMtlZNDR4&sBwJ%LjG~hE~|Fa)QmJ!V5;P(qq?GGfJ>OZ}Y{S3`znWk<|e| zSL0RQDr!)QOzn=h2#iybR>8t6N$>JJR7m%FoUY_GBw;_!eXXCDsV;3wL=%2*HUI0? zZ*kS-P3ljcpkX6I;97h1fPT8|?4m-<75cPB1`YqduC#KzAT@KxpZ@Ruv!<2mg!tbb zJ+I$)Ru2A~=<+3o&nlm)*|+t2bmv;@jQ>~V?Gt{4*87FAV@~w1B057EZ2Hs6$+0pk z!qkk|lRx#wq;_&*y_S^UB`JHCnq((e+J9SW$luzueZ;6aG9JlrdRh~eDE!M~b?V(H zuJF=|lT~WzO{6{D^5lZq$Rk4qUvArPz5YdY$*Z$&^+x5zB{#1GExdUcU)#lp-A+J| zZK5p~o^%;1gY_4F9xQa|FX(#2{c@Nsb=5pNGzyw;I*Ly&I-K87=`oDV%*bdwFxET@ zJIV^)2|n0~aNDUhlJqKFRPYF7w7YdR1uY4g75h9-So9w6+FX7_SoaC)YxQp!ohY%l z9ay3Y`^ax+Z+n)s*98r)HO|aT*JpGbf})Lg97rGu-+sNv_X_ z$KJP_)tT?xG~P(k6m=7~Uh*F-TSD9VOfbFiYbEjZ<-vYwpPnD>`c)q;FC6C2?Hg{B(Ek0Ebtd@{8_xLK><~;VL@ihkJHS0!gez9U6a8^-D-j;H~k6+t6P2! z@-Vy)irsu|j@H}F6Ib){Kb|kSljjdkPxpv4xw}422W>|um{SKE-^!DgTEqsFp5#&6 z`!KJ$>Dv}>4yJ}a*=-{F-&Mu-S!H>COUO;)$IzeJ*Cq|**fNA?GVUNN_cNB|iooQ3 zTF@P#a*e_|-KPeR$K3^+4rq*V=8lhU0Ja`u_VWIS@I$Rw`LTUI?gS027y zxo@kBrc*9XlCiC;r+u%dhCgx;Bfc=GRMtKa~Drj%8DUeYV z%luL{uxTahU)$8MOxqOdT02Y?BkX-mW>6Sq3QM~?dqH%KTH%8jFvHb3Sbmjg$uR?f3}eIJii+IH*O z1YvzuA=6ed$VIBQI{hx8c>8gTce?Kxo4JG4m4(NR!56c#tZqW${LlcBJx$nA%31)f zDskTuM`6*jkg2nP_%V{8e8TUuk)eO>DgR}G6veXsbRBE=n82CTeT75Uy&kcni&?%W zD7`2}E;^#X+(B zO5tvT{Bn-}e-}V&Qv8e7(B$Pc(mQ3MGn={@Pro%@IsL(!kNnzm@Z<%__j7Tf?DAlp zdPUM8TOkF-3B?k>YFBZY+#6!Y!0?GW*eSrcJ}FG~io>B(jmt`^Z^UeYPgj?Zf>{0D z<)hUVnj+KR>k$T@YgW`BW*N*VQec@cOI@}aqg{*Yh7Jw?(N?xQcZRL4{e)XTHd2N= zs8l~6e}9$3c0H-spRQ{&dmsJ@a3H5;XcQf@DWs*pDI9wJ{uWIMbA$cFFB1}z90Chsa0E|M4XS-%vV{Rnta_Bo6l;a+-ak#yP{A>`(2|(h| z=r3`mFuZ`EI!cgNo=hPsagwmG7)kfs7kfvX7z{uk$1HG0hKEu)z4RFVYBQ_FvHoj5 z^o--}&-t>og>&^)AE!2qTV+~}G8XM7@`sNNDZ1j-PfagY6nXXjz*!q@MjKFww=Bht?_UASPY<1VIlxByzcNDyx0c=qNzvsZCmcsZ&ZO}QDjVZdZ14+)+Le@V zRvoGvd$Anp>7$37#xI{`=_b@ri@5hkUG!xWdqYroFY;A%gAa-$anKV54+HIv;r6or z0R$k<)n*iDsW>jO(n!bV)!F3DKkd5gKeVcTCc$jnvMa@r8z7P2We%`Q<0q zG_5Cw-$9qJbk*o0<4`H(>Ionei+=bN?=5@`f0L5k4bkzxasK5%UBG2>i?=5mM0`6J z!v%*(sb`;@AsO!_mW#d?wW3&?8(b07cg;-qjkOoH;xTy?v#>(-f3?znAucLvrXc7zFW0|n3~^v&A_GAgxG=Va~9_QUEMER8IyYvw)2{YU78cNzCCJr z0j5qa>eXW_PZ$S=T@0m|d>g#|^jQ0dm6cW6!C~0?`ofbAp=oPvethST3wPkJjN?!I z5ZBH+j%*|4)?aA*X>5^zV9ypOxpNxhSJe@)1l}wzXxGtQ9LI?_{J5WgHwqPS^!A}+vfhwePm>_2^_%ZJIsy+GjMDxtKb+HlZbY9hl>eHbrnB#?`DTZ|(Y z^n9p!bI{-UVVmiYkO1Yi`>cg8Lp$qZb7X@qvO_Qc>%ss?UTt_h1&Rhixp|87M>IPh zK5-SG+QXKHWpW$O&i3 zb`fvEa;mAi8Ou-~eTn+#$a%R%umP zUW|d;6jEG_*FJJ{k)iW!v@C$a7Ef%+S@O0B4hr(~-~Q{y$?$)<>`9VECSkw(R?*)u zXZQasZ|x03myxZZhNvj`O%VUj$YR}Kvd_qNw4XH0UYyL2?g1%m_{XXzxhbXsB!ET) zq#z>5xCAIHl8;MV4wO9$0r=!k7MO8WA=$m}XJPI5VKM)_(Q%1L4+&DsvxNvQNGO z?eeLMFU!3%GPaX>#59~W8=ktQFIzUAy^b-(3G`@;?PO(xye)=Leudo4F};aJ17-1O zbRI<~GW|t?ENOL|YBG(60C+eS!(V}be9=)3VpXAdbIg96NvJ$E5qx+@w}vkSTN&AD z0vCke4>>~x3n67^Bw^p#qh;mc6m%$C1GO?K{Dl%l3H?5-JY5He(1AbvvEsS0@Rb=b z7<=fSF*lx36t@*Fi{XtFR!!-ftNymV(6$+%*j=Pzf__-wxU;Nb?sTo9-$1NsyewT| zR?GM^M#?~!7)aa4!% ziNipjk?P7!MhDqlm{w9{PNNA8&fS>qei`@fXUSUAs)Crh|JB22hT6TAi`yokI3c(|PjCLMx3I#8P5e&&L`VFd8fx zgh-?v!@P(@hB)g_Rmx^B?{^;liD&Sv+N$p_)fzolH~*M$JkD4kT$J9D$V_c^YzXrE zm7eg1^ce#Mf7jf>_50GyZQUxiXRb<|U-Xu;xXYmgD@9Lb(J#RYSb2&^IQLxuC7~VO zqRj`FxIUT5$!btg=?XfJ^U8jwMts*q>8t!|O^C7qocEZl_yeg2;xaI49bAl(Ea?T; zhxBksR@yVpxO|FJ>IHY?P55C7;v5(07-lsfVBnx=+_qvrw?bj1Sb5jchp@35c+h-< zp1cxYHy-v)E5&U3S$SKhTjQ^;!>O(vsm)sf3Vp3#)s{=V2fIptx|tO{5XL>;A^0aH zC({*-f{w}>n;MrG`AaZ%h)6guMH`L_? z0(cNm2^L9V57E&9Iz{l-F>a?d#?(6xs_Eh54Gi_2F9sDjc1(CkR`|q~vR-fsEl2r_ zl_7nJ18gu7F!Vz`J-2WWLVIF(rzeptn6fPoRut;p= zmT#tCjGu?MhyOWD8!5cp&mFN@ zelPRKKlr2nv$}oyMCv8l+=}BrXE`2En9c=^9n7W#?L=$RO6O=JK-t}5vuV@b{==DS z1qD~Lo2TDee0-;&K0UL?jQ%(g5Kr$~-Juyp6u)`thvxhZC6et?^VH^j$Q%{0t5UZL?yr|rSwp7|=vmTF_ zwdP@}GSwx(a9cbNAstS>JnZ+Tx7BBjrO|^FUDIys!lMCXq=>MEQ}g9T?;fEW%lnd< zoF0F4;4E`3(;hR4I#9%bvM@aiFwEjMEQ9`hi#vw+x*PIL1rZg7g`9BKj{=n1;xz&e zw+N4wCJy-j@Utf(*d@OB$O;dMzMLJ;V}X%-&Rb!GAnYV^@)`@}LWq=kX;?TQ2RyKZ z8+)wX*^b?zmbUIxREjtU7{s{c)7B3c_9udaeF_y8{cYv(uhYBt)W#2I|6%@8IP?sj zqE>nJtk+n)zO!A#jCQlN8hvkeE#`b*%Kp~tl2z-B%){NXAm6gJO})`N2R#1wU2Rzs z`4}F4naqa8MHw#;bGv@A%L)<<@yETiM-v=fxG`8=!SJd3h7W;6s5}0vX=T9nt(7%v z&9Q-_&1ZzVpy^p=S!u6s#djw>GU1r1EPib^Qy)?>;P0%b%;pY|bT4`!v|F@@V0k+0 zy!HcWN?IViY@X3LJkID0WAK!SF_~#ArV4KhbE4izU&iFo0=oE?ctlGEm+ZalugF{w ziO@kt-Yfv!&n|fnBv+5nBM9laxpGP$3ynQZDd6%tOeKVmttnq)Amg zI#CDrYzlXb9XG7b!mJph-JshVK}}aPMwzaVQ`#h`GHYg$WySUIh#rSxWs(RvAzV^R zkeu~aSc^>+k1RuzTYMf`awN7?g$s7<^c@Vl(zlH>&0M~g4Dtsmj&buC4Ib?Tq%yWW zYpQ;*yBV1+#aq*YwzPu}qBT*+t-8lIC zbuog6`S#=sBM-&oZy@_}44E@LZ2l962-yM-gOSNAvS0Z)E3!P;qya2OI|h$zi*aNd z^&y3sA`4#---6x)qW!A}v+XX-{^{P{-u5D?r8IcnTggw?jqxaWR^<~_7(0yLo}D$* zk(1KmFdfhkV25+LD^d7yaIz#u8JDQzBSiQaEpmvNFe^tO)gs+YB2gdJ07nBAODR(xjF+3W}w>+2EUMJf{nt4(+IWV5JeB1`;ZK z0zWp1h-JhaHOH?s@9g=CJnKn}cdr~7*!uOHIoGti7x<;Us;n6I&&%uWgPYMp3af60 z1LsmLp1V}j=8953M$pYPeSPW8y`cXp6~+=j*--g$gdL8k-RjYUT*h&Rk!zpOQlY#`D?Q2SNlMC8kBO?!MXVQ=3@Zs z@ihf_mS=ZB7lcJ2v>~kEDEuve!eh!$qp=b)C!lEAhjgI$+FK}##Kx2xV=UHJ>oYgn zRF@__35>T685ftpMQ;rL@js^$j|1Xy;VmZ4c-AP;LBP-uB@T*jCpRJvTHr_=LxyWw8m`=a2@ftBeU2kN30EPH#loqgh8HIs8Wp0kJrTsmKpH^|>0#Mlanr#I$Y$ckx zSURqvu1@e}n62FM!G`5NIP1UW{!mifrtGw=Obe?$Se0-nX1^Y;iy!`JB7$W1_@oW7 zi_@9<#OGKX2*A#M8(FFHNfxuQ%5b}M=-OjYO6`*~hC@tbTGq!Zbk7IVe>qw;6gYTH z$jHb#;`1b~WQ-kOdeA+l7F4_L>fbjAqc|U(GOXc7YKPJ=+r=Ec|Z$PgQDK0OrY@klLurdNgR)q$@bZd;`V5X0|a3l zp~5IQ1>~AwwTlw3u>{!|PdXz=p=^y?Iw6}A_HNKRg&qZ1RNgVD@XN@jI;d)BsP7HB zO=y6zi!-7Hf_u(Z6ZE#7|LKRrsn{jEn#oJBBCVT6DfL55HU55OmYG{+1&brD)e1&F z`EFKLjdb75urg{>!I;g<4`ePRy96-FCRi0;0TRWfVa9mC{VrN=N)|aol&A&aC|+b% z4y=-^#n6YX5F=4})+4TSW~|?PJGjZcN+b9geYVDuuG|eqO?YVEMGJqm;JpcvWv^n# z2uE_FA*?f62(r;oa2ALYfmR~98`2Yy4Aog(O^xR1Qs}A@YD!yrw{+|Uxf)xK>3C7jWYLpaN$7lC;4cfcydeSEqnGsF93e5}8Iz`CY4$jw_Z!n6AiUO$p z4rba{e0+S&^LvLalX*0KpEH{KuNo)}xu&qdNt<#;RfeUNA-A3Bxw#-j8CDP#j*!u# zut~`Q^Z4UmwcyWbd*NWWBAp!7{v-UA_yqVyI;eZOu>iJu%Bb6txa zzjs=SB^xlr6Vfjdpb}q9w7+QMK3zu?0sx&G%>}T&h+s*$L>EjI>%N>U`NonrP=5(& zz@5x8OpUy03Io&Td8dc-3}j2HeXO|ik7heNr?-WgpGZC_?+R?x@Vl7L2=;xax&Jlu z%DijC$Mg#F1xj&m&;- zhDH`n0DA)2m9B%!zA(cHiz7lkxQT2zAjLI6PCSITT3`X^D^84e)Z97V|lC+(hXd zBuTfySfYso9U7ji@*rSuDL$ZRWlwX*d#pBkM>vtQsu$e0OEti+AAVh2{-ewws7S*L zjFmgH6STpBk-!FPLfOLere#S?(PBfGV;uU2iBUH@`7D|Y{ewQq{Y48N1ImiC=Yi0w zf3;1tm9S`D3#fkN6Dmk~zrkdxE%nkZU$e9{269sno{c(5c?PIXW`(ajLsa}mB6{yg&zn9XwZQ%g%r%&97yb@HY+Zro7#W*x9J z9xv9oxe?)_xmL<>&DSt}jkLj>iV+NyB7G!lvmFly5WEzGY$?ksK;}!Gi+Y%y!}?xM z2hPbk@`dnH=~VNgN1&(QHq$6GsOs=XVF}@Hl^_^? z@9IivEhTAWnR?74xR2^AD(}Ql5{fkr;nE}nZ44gUQ&|Kqe2!?gpazH;dG%N3UBIB0 zae}-w>Hu<&8Bo+4zqCl*`cGHqKJhbG?%4>F9#5*eUtK!ASJ$>IQn{P8ezqc)OV-wY z$%}xMVq_y-P*c*ZuA9B@gI++~;(VCK(snu~MNhu2;w0w_4BU`^;_2At{nb{T>|xl` zI8&Lj0ycKLd@xe{B*qv;)PdhuWdRiBnBC_PHWI+n4v1{w&etW7$i-28kNm$pe2m5Vc+_^{F}O8;!-122uyrrn)@Ov29ag49~Z!p8dv zF}nT2cLQm6I0}h?W!<;L6VDW47oV5P%G!A5zGN9`kWPZ=%g^3$6bpG(aUsZc3pj@! z@^v0@IOWf}ROKQgAoKRhOqzjX2`IOW1BTVxZryQm30iZmuO9z)=V6`{ccj*}@C@j{ zv~SMj>Z137qv~3IbmK!la{)3G%1J>8Mm^LE4`XQvR5YR|o(O!Z;^t~wB`QVbmUn8O z*unyKqhq=u6!iIns({@;Ota~>St-PwhSaMe6vrX;Y>=J8^6C>2XzWADHW-5Zon^tW zNJC^UKv9C*eb&|k`0Q9zcU;~<7 z&(WAt?UO=(UG)LU*P^8Zm*mA=hD94A3uhw3fLk6Cumq@xT?jJNIMG!6_uTOmwRvMbD2j$ScupBrTOG~qCRyl3XQe+&9 zvg%9;7w~^5mPF{WT$lt|%v#9>*6WN4&lx%qLIfn;y6%)31?TratK0h*%)s)7Q4`U; zs!vT=qX9t1^;x98y9a+Do^Vz|K$>(0#R>l~#!6A}Gs0{zUJO#1L-_kDtf&mTS+Uoz z7JRKjLEpzNe;|Z5W}t4xN^9fCXw&A?D~Yo*8J4F(#`}#}g{{`3!OWr&1FBbE;c3U{ z!pOg1;OU z3Y?IjpcBc6MBs9ZL_zLL;#pNP|4Dt=>r;3Vb|uj3=ymD@^KUcHV5VkcGDFHw$+-(Q z6B1?t7@h*k3{3HGXu=0HOhH8jiwb|i^;lf+e<6`XK?2KAL+}=&VAt`wvp8j13!_!6 z`lFu)^PyvgAu_Zxs zgH8LL`_Xzrex4LY^K9tl%R}5qnUkt4m0R&!m5&|6KH3}yKymO-h9;4^yM`F;dy%>^ zcct^NA8uBlPME6FHPrXt|J>XMFDn z$pwy4BJWl7I|hGLKJrvF==B>YzJ_4Y&W8^;$O)1$lxMkb0pYK4(gNYe_^M+UPGa(= z5NM@P(YLzMcS3nlTq3-k-qYz!SI5lY%YDz>HqU6&*#L1~42qbg#IhunPMY8eRXV6C zgt!t>koZ5BghD~Yu}vbpD=(mXTrezSJf;Zy-?Dn}XnyI-9pva2jS=&r71!Ri>gc0Y z2CcfabkV=JXPx`aqRs2Q2)FIcri0A~XG8!5J>Wq=-&0k7UfLKZ9usv;8w-)) zbeDjggm<$?>cVL`;U3}P(uoL6tXINuB|cV`1_i%(S2C}zF#a)G@@!HisGOcilJn!1%2D{o^srcw2#+QcRZ zDuLZDQDMB?lxGRYBiGOnR_n1`QcQ^&o!JB zYf#1h%c|i_*8 zN65@bMzW40PLV?PO6b@eLS$uiaL77BR_NGH$Ebrt#F4#^ag0Lth!YOSc%jHnLiX?Z z{@i}wfBfMe&bgk~<8i<4<9eR$Ee(u7s8m%$KyS#O*QzkE)ge6*#)$=gclk%3Mug|J3s$EI91wAknJhG~{%pfB)3$ zyz#GbeoC(Dw7hdLxn;-4UX6W%=uN5UJ#bbxeqfB;%rvwk8#)BayOw6I9E8 zl|$1_pWV^*dbL2yWnQmK1ko78XGwhsU{R<|=3rJq=ru^3+$(l)2{m6z4sbV0pZ?*b zeB{)2Dr7$!X1TzEYO_s$KJe_0#Ti@U@!`c`o}O|y99C9z#nr3ru(9{(0J_&Ol{4(G z@_eQ}NxI|o&u>-l!8OIal!i;S+>r=9UN73(LPPFMNk;vtPBL0CBeB5Ru?DRz@p^Ma zjzH_;2Dy)xO8dqUFMCysEBkuc#>U2=PshLGG9Q$Wpz3>#C%r#kzDmbhpWc?CwS(}_~IfWUOl9PaQ1M%4p5(NsS(dxlO{Z%!(I@(^sh7aC% zEA@%Hk`_}Z8d!wbuSxg&9sV@+?5MPA`9rXuY*eSbaQ{esvL|G!D{aPFSSrQuj#x7@sgi^P3D%3jVu2P?3HYH*G3FVx7Yh7)+t$Ob*469d^Ry zuzMzURrXf`XT2gC4+ZZ*_4%2ZZ!G2M(Xfoz>w^aj`6!rICQOjN?zNo>7bdxv3CK}D5_geYbP|%0BGM--X zJYW2Heim^~f(_IV^km)C%56Q*=HTZqhBBvSpoMcbDt8ycSA;J9^%k%0J1cFPph|2o zh&kpUQE(1CNs`gUIp&#}Y?L=kj~$#SQD2?TknyPpS5M+)1X@SElM*zGBRsBD82x2)O-Jcnp`V+AL>(Z${Te4r2RG$MRnC^VI8hzJD1=!70V47q8V zn1fP}m?-w?{i8l0U}z_&{;wF+hH+N<_-%;wTXK-tC5i{m=jU=mxXs>kdyN;u;r{&=isIE0dHeH%}&2338GCZ4|w_8gl zN{tGc(EVoRdmB5G=+%T5(DlFDv@{}7Z)1|GMaP+4oFU0+50)n;0>jSwiBk$?L2N=s z(xq0*yEF2HLeqQt?)YY&{9n_bb%o>E?F$WGQ`sgkFp7Um=I)W#V(=<{AVSgM)OU{d zVjFpR;#rprVaigpSVk>QE;=-pFh=uWhEZ8m0E4)D z*o_s7@k%m<))BwN8)>J*W(J>5G0{B2k*7A>jrB@)ap&b-VS^)?-gp<*t+ zM-T%~Z_gER^3K5$fa^lzVi$78PZdjRZtR_fVq z_4&uulXv@JYF^QqXQp-)gS<k5zeU%I)kmaqm!?muOHWj6?#XxJw;m|`B zqmG8-zlozWX~#~Axp?#2T3~`C7zK7wA;r`FVqtb6E`@@0+>kYB8n8+^`t@gPf_0zd zYarCEXJd!?(j)P(!U<0MuV>6J_&76>VjiZ!>Qj{T{tJ`z zIHUydkzLssyr1{9KI4k_qd?|Ig;$B%imWjuMcx+3lR2Oc4dG*Dq#|04YF02eO$;yq z&B|U{q=QCkx?}sX2tE5qEt(`85Gxbzm#2x5deJgSUmo|GQ}#zO!nou3ZNXFT2qE}L z{o78gPEEBesIV^pA2pQDFy~)Tx82=CrUy@t)Pq%`;;LD-wedXv#erf+L-5>%tbMa- z=~%J2_bY2<$Pl$|;y1fb%_9|!q=4U~iLXNKsWr+`>HP&C*wI*Bm^Qvf6ByHkeI;y6 zM0Pz1N`rNg zia0OVDBXF}C1+AP0TU;xz?xic3PV6LBmxaxEmXwiohiyK`WksV{sHU(A$CO4DHp<3?GJu$P`{PMwG1Vkv$PWzG7*)tgPzfdd89Qlh+1huuZF>vL z+BAe6&f47WdXEj-^Y(9meQrzi={F&2TJ_RX6!>9J!3dpwAgln!O|{Bv;sCPtIv z?R0Z-H+dwTmMkfWrnyT{N~3kbk9hgtjF;6T+*_I&&qQ}B%8ETHWOc!VDOS7F;TjV@ z;ZFxW$mjgq$z+U-6pdKnTP_9_TqdY6H$Juooz8d@e3zVw_)Gv(Fp6okv&R)XVJ0!A>uUuc4 zTQ@mB)~KK14@-Gkj%pz7^*UdyFg$%O(4Dh&SIBm!OGq{3-ygVo`2s63I9I@iR~gP9 zn~bP2O<$seh*;bpDAhr0;y~IqP~Ic^f(eiNrKQ2!<_Vcb`EgXQ-VnD+t+_Hj1gw6> zr#uX6Xg>RCywl4=>fl=^7YC+aw?mTOS10t(6U8(+|1&JQmJc-t(U%=e;@~-i!dy2e zJ?fqm7}vegx-FQ#u<$)7`6V?s=LFI%6UHDNvOPTX5m+pN+vt@K7cY|I3tEBcFU^NO zZU|HHnR=JK_;tU2C4H}7inU1i^-w)Xi^HtCiucM%*sBJq?wJWVKO=-n1z|u#&54C) zhid6dGE}qvvj1o3PEbl@P~p)!98#Ox!&-pf_NxWeI_ppo6t?=;6n)yyYr7NHn2ByF zFWLRFy4PxpvA_aWmwG5UA4X2_>?^P$?%0Yg24Xs7$>#dK!(6F1QG zlZOXRxelwRE$epQ1%}LQ+_qCs4hq#zmq)!jBz)S`t}zH?d|cnJ9=q5xi}+J_ad%3c zc@;0KfA8&fO+YF4du8p%e{ma*#twYo6*R#8K-CB<( z!ozzLj~cBu4ED?5S)OjZW6{RlKjdPQEQ}@!mn?2J^>8p0Gk)|g^7N>FlS_VM&0rwu z6>?JoQ&*1DhC ztK$!L-T5Zs3jB|#Kw?FYJD-8rx>*FIBTVi9(H4MN@;cU_Y=UzyVVjM+N*G zi5&WlMSOPGq#X%6@bNouS(ozV8A}5c^)>Y9o6A50y?ioXb7G2q-xa;Q_%gra?32dH zM#p=U`k{P=WKsFE7lX^g>K*F0>L*KKwmw0{Qq|{ey=?6-OD``=ze#O7#M6SKG~<(F znJ}HY7(HiU*@o&Sajl~CyCk^ZsIW&#^pYY@F55I+`L4@EC64`N!tYPDzAiWUiscVC zGc64MD((j_Cw?eb_x=-hkb2}5!f~(sxHQz_GTR!Js7zMj7=%BMrG#4F*|j+sZBF>^ zNVo@kA{2auCLb-iJv|bT?qTbPe|VTwBPG_E!b0I7TgLbvs_${7dJlf2G|z7KR)I}Z z8D?1lE$&j?;Mt3NETck0JU`JSGbt`C8FIrcsk%#vrE8#uvSnFi{MDwJ$#+Fo6EoT* z9zIM1VFBQitFo*?ylIYcad2)TXejQ@8-gq{{@3G*@dTT@0jYD**Q6$L;3(cQRP6>S z7az9~Y#M}{I@y}vnl`O6ezgQIv{EWSzUD425xRm9>I&hNA6SQc_qV?Gcp7{De6_vj zzuTkH#u<~kf|X1C)(Qo>42Dd?2uFu1NhAAb6ItxYVO46lD9LIy?V(NZ(5ICj&G3V zXjspxz=L>q zj7@BDiQ<7}-yRdKN~#kDDj+%cWuAX4@_y_IEeH+J9{2(`jdpe$oPc{T_NEGH95wN^ zoP>wnICg#hcr|dXYbG1pWq~(WGQJlI)~`^yvS(`C^(*{wmAB0cs?X{H>)FOY=vq&%EIv;0#=#!R z?~KF~B;Alq3es8g8W{YD2dY&zF`~E2yrU}@r zWG7(m4QQ6zYPcZ#ktvIh`;~eRQTqpX5hx8<`S{Av3NV>ZGct&96COxJ4y9|`VEW^J zsUN6jTn^g((N(_Xd!|owpR%Thp`&@XslMXW%8M$V+S5^kt7ks>uN$<6y;J0Z-S2iu5j_G94mbE<{`?2&w)1(e>4XBz>jK9D`C; zn(bBRNfAe^`2IAjNH4!E^eHKW0r$p^xDnjUXZ$ePkMCW{^U$!v!PMP}v_~WZq9ctA zq8|zQy0GyC)Pew5KB<0pj<;A0sBTyo{jkafBN6$TG{=&0Xe-%xN-0JrY`c-A3ue%O43RT%*F9XuB5vMO( zXHP2@K$1^#V!@RE=y=gdR8BfEHwXsMCil|sPB#vHkc|qBKhsV2KwKNNwu->E!8#Rz z2eh}W6{Gby>51ALW3zV?ct5tRzNA#K?SDD`6Kt#30-e3ic|AMvOAYs>>oP$HT)E6? zh#QmWre@BEGMXiRCShbYKytD>avr!TfIamD9@jtEBrte6^f@(+3-{Wcx0`9^Pq+U5 zI4hrnpV`1h)4<=U7%8y+kuSZA&v^6H-%GFelhloM%#wDs)3_4k<^+3vm;Ls3ox{Rr zyPKYU;BTb@@HT=OOby>tb7JqL2UBAi)8mFW7gz4ew@Xe@>pZ~>LPWJ-ZVFgfGz*vY zn2huGM+-(7(FwF?zl72HuNB3Jre^H<>gW_(<@v&+psu?vRSm504Et4ep@|^=%TGEh zdf+1=f8Oki%JWZ3;k$NU_>YAgJT!iny?;MDaqc4}FYk1(H#Jp*Q`FM&g+cvY23p-7 zm@+j**&oR+fdfj3d`mhZt!4b#C{0}wmD5p}7xdXXT0drUm_5^NOI+^76wl*JH)Nic zWOh*2M&t#ykIC}9y{79NEu7L*`_k7{^u;1MO&(u(KeSL!g%^!1j_ zqdebHtR3c_7Q;ratF0m}EZu2r{zjuuW7dket3VM8)Y6fW;=h9_fYOkp9!ex9C&j+* zGZY3f`K_!Y!>dWriDf2~ZxzZ!c8-{wX_-__$XA=`F;?xqHWJYQlHWh_;nBBR`Kl70 zodYY%-1)OkdOtfSgeaPb9)i1nfNGe?!XA% zv|yQa0TGsVA>p7fl2k zJDFbOZVx81`wiN~>k^F~{9WAs=ETX|*P(Y^(f4igjQiQRkerUrG#LyK&S52fp4iAM za4q}3IG^9W#JPjwJbU%vzYEeD`eUUb2X~5bFc(COCe|M!r9C22wRKC$ED=!?5+JMH zW}muo_VZx%(b>{s7b$W7lrKy|3&uH3WC77oA{qBQ33MQYHWl0!daG6`$GGZORhhc> ztde^#xJJc`BEI}F?^XDo4AONz1HOG$NKRVTEjpdEjM%N~$hqU6yNcH?AF#Ib_VGDG zm`{C>X~FwXU5W2qXlgw@6W69*-|uoknaJ^=1L&BHBT_?^xrPw{6~6rC9~}HOJX3|sG%eV zJ^4egJx`1b390Q!%b>k?1ve0V$)r8oFT%QmUW@!jP^8%);C(UDMX<`U%$?1c$9I!- zXQi8!O^iN>f<Lv1=iS~noxNd7;F4QjEOheMMbY$!CQzxA?B%fg#7vKBZ!DK zj^E-E34|mIMD<&l zZW@StJ|0lkKRUskpSnjVIk?$=lX~+>v}4Qv@+e*Fw!-TZ_#zI8XyqEn% zSH21KmXobKJuMB_z);H7OAn|p){0+5>{xjc-gi)T7QFqvO*WK4F5DOzmN$Sr0F1UY zl=#j*fX_y{Rq8(unH@GEJoT2(!)3`bi!i&;z85qbXN;jC$;P7rqD}E?RDYqlGx?QQ zUTf&Zd35;b&j-#VH9Su`ECvYQAvWw^j54#ci}3%Z5slFd*bzV-|N55u>OP=fwI`ak z74hWGmS)MqB&bGEQVrz_u zNQ!6;ne5;-$t$g*6>)Xben|~?W{#__sbSTYc(D;*z+m_ctmL#nY5cKKV0-G#yYxM% z?JziZWwonI$IKyJIl`I{+#oaz((^LCElI+G>uZ z-k#}lpHdWqql@7Lvbpsk_bPMkwyOSoU%ASt^*};(+W2;jon>Or!3uI8Ie#~atI^Ol z(nVCH`rd-S+srS)g}UJ<+GK7o*hUb_XmH_<)qQ^wOv{m<5$a5g(Iik)B7vbzK2A2{ z?zu8$IQ(!oRZ#8a`Cp&P6!UCb#?NaE(}nkHue!#+4qI0%vY{VBs}**`^Jphq?4O#X zA|oQcYuIUcN^k5o&+u~CkTHHv{91q<^OiB-Dy&cKfmveCVI*jrnCYVyacwq6JzZ<6 zq~SN^_1x zf<85ZVt;=!&+fZM%hL+Wg$(424Q!kaV=LNF5#cZW!*m9I$w`aJ!pzM+>EpM*NR77X zx8bVx0O=Jk1khbc)GFpMC~#=!yX(e)DMjX+6X~Ok$ZAwE`Y+Su8)b0w7Xy{WZ5pnqbszptEytF7*QFM~P+@Wje~q0ipY(~coA z@c&QepkTZn(K`OiD5i9UpO8%CQ>!j0KWkhy=rD#*bY^wNHd>@#4yvll(TT+C|IIar zc9+^e6{up<$op$zWkOk9UW+}}XQI>sf0jQ!mWP+8A6ff?1D@m&vy}_^l%EUK%J1BD z(fQZS&Xnf;BSnGZ#NB=&;3k6+Rl}3?IHY(76*GZaSzh!yToiA8{cf|d4&hk>SyHy- zJ5(l|Sps<_+75|?Vvn=wpZ|QSdYxXC!B;0`ucGuBYlB1|QBA*fskVNv-O12-tLNaS zVrJYjWows$kPu|EB4Z&CErP6U0gXD)UK}D(n}2EaA?p0FF;(NUqe_RTPgUm+J!pCL zqP(KTWT2EaVeV;j8Yj_U>IY1*_gz-9NyWfcOTuYW#-rgfEUP2HPU>;|%YDVB$Ka)> z=|%&CN^a7&Al0@9e$M8d*M{a=Ex5W>{59-b-Cho}OM!7r=~bnIlm zfM7GZN=sL_&`^a|oTZem$NN~Y|ERpYqY8rkeQ51f(Lz&Yv8c}?70TH`#EF~^^E+iv zg5F8U@r&{&(Y;11$}Xho$R)n~e_nvkNH=}0!&e6e_*B%~-<@{eO8E%?hMj{Sq1p|s zF)=ad@J7pgT`d z`U}TPu$r7X;Y*#oPcX^JrVX2|*R6Dv+ZML$=h7uidOU|Z<#}14i=mQWYQiP3sql0U z+?L7FP{%+3Kgu@mC2x{)XD%n!LNo+_7tqJv3v(CBUWIW$<6ZlzkZg0Y!*PHLF6Cm` zy$oe_vE`v~_(p%HEs9H*Oowh!fnAUK_JN^aUu88nGTyY@D%&?A_#~)zmxnE{;a%C0 zY@^5xjLl7UVTkM(sA7&DWY&Nid#Z^k8cJ8$B<=j~SWIuKahDZMAuaxkan}eA-sAsC z*kQXZ4XZTkb+W!#`gHNH?xMcK!#|sUIwMN{`XSLSBB(TUa=Em{Z8UprBw8r^0DNBD z+qt9Y?Y}-9YU=rwlar|vdFvjOo<_2$Zhb=J=xp|*pwVzPV zw|}4B_5HY@_@^oKS2?1ZyQuWg8x4qHq{!_joV4y2LRK$2U8kSq!jHq}eb`pDwRN>e zqQRU_pChA6A4gQ7CgkN0Mn6sbUH3RytslI@j?s*!*5ku+_~OG3<`+)>rl3pKw>N5s z)fj(o+97;q(y~%JJ}C*cXwV=haOs-0Uz$QJGXp6as%>gj=96uYqNqx0fGPcIP?&?8 zkB`D|w(tFnJ6T#(1iJU&G_I%;Y~(ie$dA#0DmNOPb@MWXlfa*-VZFkW5@msI8n0?e zFn=^1fdxvl$;DYnlC1w1CP3J$zhIM|WD`1F*r427mDsY_NPnWA_Fs7v9p@)r51UWs zzHXWIc8nCP97mb=+>VJto?y0#P;1{#Jt;BgdXblbOx(o?Cstd#;)%YV6+3|E2!%sG zjI+Wje9|xQtyk;};_(3j__N>js%QNtJ`3(DLP$ggt+#&N`tgc_?b#oVi?i*39J||C z8GNE!$J|7abDjA@J&vM0nhm$je)V^~?BnE{oXe0y*kGHsI$3ppOf$vl=5(fk%I7%ll%8z8HQOlQ4o>ooqhz{qx zjzh<=x>%~1eW02KAQtq#yVq?=+6OMz_X!<*xk7lqu0fno79k{Kjnk;20b!s#thVrl ztQASELncqEQ5;p=54OEeWQ|tyY}Efo&_1N`OJ?V|fAzll7TM+l&^L{C( z;$6kN5*D|<54~ZxDBUW3mbPYEPTcp1AY8b7-x>Qjym{2T0tT1w3 zg>=pQx}9#_Fp{w6!8JJ6|@rV&WON z315&O=9PnqJ1eJ7s=q)jU0_Cxij0NTTo9AX6lsN~A9zuhA;XZZ?n}j(3Yz@$sM^p~8$R zh)aaXxIROuh?i(IczgOvK3S-@s$W{G@$1fJ>8G_>p`hixKTGaa3$3L)2aU^5F2y`A zs5c^>|NGE!GO^%Agj?d%KI~X2t#}i0mA&B$0d`ZPZ~f;Ci6T8bRrVtqYI!OIb?-on z7z>%SSXwy_ECj2f?;_3sL`<2Bw=SRQ8Sqs$o-Bmb*;kD|v*Y5WXllA$M^otm=mk_w z9qo(H##dk)CgXlV=H8R;zIyqsWkblKB}Q8}f9OZ<|MZdM3@j}nR*dT{ezG+5)96sy z;u=zdKUZi83x^eB*DSQ(YyX$t#Pgw?-piB2i3L*q1(T$D^54P1j{*wr{3aLWT(+|k z!pL`HR!??RDcEDAW&h*lsa2(6evVo)pS4)MT6!t~u{=)pw-Oc1Cje=$PDfWg-B zhM0QQ2?fp@eqP(Ac=m4JZOA6u*pdG|wF(dl<)XeO$>#rUg?Sg3k+pt+Xn{0wrA6OWxeIuOi65eE{S3b(!f2dTX}($6*gHC3X<1fht#FGVdsPD9 zd^H`R3uA5nrEGazD&l&GFZvbd_P*Hn^qd@!P`9qmSeU-Gp2QFzyY0FJmSq%~hWJX; zB55qGJi}B}`PM_yOpqaN4Rurg6-Mv}YkcgPR8gANYjPJ48C#*6Ooio2-~G=1^J@$K z>#&hyet3Ui{Z=7WloT<`x3X?^yTH~i$1ZPG(dhmSF)x2-f^4p)wRLNzNng@N*FPy)b@ zRJBk8nfQMo6at6T2%E9XIcp@%)fjJEHdsb&$-R;56mik2bzuN34AgqMk2%NE!`mXN zPh2GPa2oxG#O13VSMOkC%wgJvvQcsburL=k-o>`FM;AcQgv7g)<$w40R-cj5gKnCC z#QEcpY-fMmtE%R+6-eQ~x|#+MavR}~!~M%TjwT$~wp4FRN&F4dU?U;xaNa#>VG9Jr zJF((m2UJ*+NuIjY#qO@})>Z(y8z5?4gKu>9+yuplX)V&}=h!>G#YS-#8&R-bTizE( z04=Yc9Qg1HnI2$p>1Yc73IOsvGn6trrp6DkDd1b_TKwHrvvsW)UzN#-0&4+%8j6L) z-bkE&oKXKoIGgi<4Q$+y^h#E3VRR!|$R}7=E+fsx+$?hF38%a;W3|*Z!$AF7)^v6< zBeb?;qF2v~Cv`-g?V@=yYtGiZ!lU>a=Eag+mWOdJWsjhtF|GRjh3W|Sn?ynS`g$xE zN5do@-lDg>@*${~)C}kPUcWE3@IOxiSQv+Ca((~;tZDL&?pG83u1$P<+@4oPNKf;n zp~c2`3;6a5Yhn;UJWZr18C?DL?)m>;HOmnbb#?w8`Uxkk2jhaEd3B|6miG+vS zK8?M@FIpTj@N((OBSha0AN(P(7T|nrx_V?zbu+6Z9fOs3bgF-D?ebbwOCH_n==>w} zGn`Vvbwb%X-z$6EVdFXR4L}6Kp3gj~;qGoEIl$D3j0CX|ny*$A2(?kR!tXYCW{_N5 zb26oLBxG1ibovtIEP-@0fto+Ms6fkxsZ8L_GQOQ`RNQ#o177z#2B;v z&mM0^g-mAn3>Vh2md6<3_7uCV-ldK-@?1Ri42(!Fef$M3!nm9+HyT{-95hxXJyI#X zp=gxW`n;TOG2j^=-#Y^QX3wz5eDcPp9HZ`ZhMB?Z=)myG>iF~sEgcu#^9X#tr{rUQ}Yt1$272YeO_UNK!IFD|?Fypt{ zXL)uG^T0T&uiiQHcQ1_RV=9ZZ@0WBnX#mUm!;j~x7Wf;N7r#CH0d#WQzAR6Iek=wz2q6D0PCmR8k;r>FkIN<`0$`cge7wU7ocTdh8<*oS7k<|15o8uix)aTRrMI@S497GE}}NOY22Lh@B2#i+4_n407` zQ@q}zm<};5kB9e8{$_XG8c1EeLNsyK<`rFd8}qO;atCnCS@-bjF#sOpv&!v8B{q#P z!PSijwK<^;t|D@wt8*;3ydQ=g?#xqSMq4TNW|`gWe>zd%R{znz=Z6##AYZK1W<4wo zBIbDv zd!GILy7<65^~BE5h6+R_&U_!{$Se(>wiJetOxZ`YZ4~B8vNnRImOBu~cw2?CH_wWP z)tJ#<;OT2);i^tQpU+t5Zx6YTK0}QwMOZ7M= zG|a_qna08xKdnIO8b19G?SAqnH-1_TIkOg9z~!XEN|+(|P8t^1*l9VcBsGaU6=PBV zPEVKoB6h1R1+s5j+^*A$k7c1BEYX*8AD-^IymO}*o||H!R~R!6KR3K(G?sosvGbSB zGeJDir$o|fab{9e;2)^~o}1Me)VaWkL~@vgoln$Qooe3(Hr;7GfNI$ne$M>jC}OD3 z>%@EsgQnuAS-&D|tiz=k$&;}LqvV7bt&)#h}05ZEJd z0hcRqCrC(OkcUmsc$9b)Tf6UNDhVzo>Bi8uUowv7qLDF=tg$8w(~f&*RpFP#jg;hC zICUFVUfnn(iog0@w-dG>qHfq5mNsipvelAiLVvb&af&J#7U{_#%2xv#gmoGZc8E4G|H#&rzbl^kXyc?@8q zs0L0Z8#Tc}o_Fuwbsaq_StHv$)^)$hodTlP`vjb~y^6_+kKRqOt-`ZX%vft(6FJyC#5N0to)Qofz+JFIA%xRilM3~wZ7&0^iXS}(JR=!U4K28@Rp}xB) zOWg)hQ6~6Armiz_^sFU7(GZcOWNl>xXz6gz7saL#dVO`n#FFxH!z>eed3=+WR;TZY zkq!Z!zzBKFlfYeV#}!6YR(k3MoUjg0)pPP1mt~vHbpbnCR+XDy=&tV>MGc=-g7(*7ZIQ1;8R=r$kyTo;+%cEqp*xMyETP*K*B7`A!2%(5ir zhF{%yv(rSB+$s&Z|3uR3Dc65POqP8uX&JFz#bV%bEQs69EKFvq2|MD0-xDz*ge-ij zC9wVy{4aENP>!8Lp59$Qfq`<01w0L5M@wT}sIKURA$8-qzzH)9;^{*P8l9NhkPgb& zpUpO&MiaG^W%WlfF#8x5d1jStJiP2pw$xNFsRnLf&LuDGHO`^mJjoOTVxRz1NCrpL zTMLrrl8)J@0VcexBgp|JALbzYR5%R6sy1eOe1?ZJSdaYR_kw$U0ekF(F{zdQp1v zd+GQ4vlrxbD3r;+SOR=4S{4Th76HsHU?qsz__eVNH>2R7u!^cwwXM>CXzg z@q^I_O(+_B^V(}Fing|G);3ZQwR|%Dv~F>%!urkKFWe0ImEkA0oLkDB;@3_GYv@rZZWu{n>Jn^inMWWhd;;?jxwQkw5U zoV*F@F2F~@2IQs)TS7O4yu(%G9MnUs9gtn-CPk()SG)7zPF}au+b=Oh)m#N^U)tzQ zQ>a((ayI<93DOIjpW*)4?|Xw_ayD<$ezHQ`zbnvKM57BUw=COE!<`>o9HVAlz6;eX zfb$FRRi#k24#qb!+5PqxU%d2JRt-Pe{IUOY@!pA(V!-_xRtARp=M{G$Ci)ac!WWo! z-1km!L9*ZapY1u72>+*L8`|fVSJrl?`JunSO>~x3&I~cfM@v zVb&cbtPHoM8>dz+$yl3|F;w_08lcZPp#Bwv2OM9^SFqW;f9C>%%g-w84HkbD0X#AQ z-l7YuYo3rN1!Qisb3|^{Kh$3h-#_|#^!5CQgUl8Gy0YtA+L;^@;8;`K`u@J{mA$WB z1{c3-w_5B}lJ~4V>0rW9Nnm~3f$qgQU4`YJt+zP-2%v>c-E` zIdwSE*b2~4w1$9X(b)rJ=bT~&zOcs5<)IO%pbxGhz@a$|uKvOV%;O+VX?1>u=vV*N z{PU{R?S)dmL%z{?7=f!@G-C{Td5YA;ILx3@-z>_&rlkF1)ZUg)>ef3GX*|!?Z;4vw zM{c_5Lq8&_(NB1t&vAw`3=EpaQZ%nr1gq9h;qmzpAF*#1{(>o}IrI6G{tX}X@?QAt zeR~%-3L$J^<7377w$Md>=IoCVB&`vtr*Lh!iGmpQpBSU6oZmt!yxaftGmXS{CpGnz z;tc}Al5>ta2kKEy&yfY%i5%8~#Z=#0OMjkyAh*1}u5U$(KYmPn)`N@z1B2?y!i3EO zSd2DKOv%NmuKIxog|IL}dhyCETf82zvi4NA&X=DfbGS1o@|j+xBJ2Myu6ty9otU-N zCko5QbANwX6K%s3K(}sx1K`*tPUK9=a~V?4O)XA&EMp3TUb}ez!S2!?!KWu}q3FuA zm#nU{^MemM+CS2paco32pW<{r>zsD0Db`;r%dxWOm3x=D1f>lL%bO z^#U9O>N<92XIo6*YTfP%yVB{Vj=FW&ceal*5257dyYFdwt}xQnus;?fEh>SodsYT! zW{)3i={2eZA8yPYn@HaQ3b$z1H4f*J$_7)y~=^qmwYANWG?$w^Mj7Beks|JAMH|xpoH75F8-6uF80VCBEjiF ziR{$8=%=#glbue{_b^n_g)~s7OB*)J2V@5$Husjsqn7kf<(iQ@{}+?!lxIM*xcH4a z0UOa#V$8*zfo06 zM)`>Lhnt`eC9dsC##6+>8A+&DG;QHE(EHU}bRX&q({bGLyzXw4gJs3_`4+^wqT0Wu z7f8Q9JpM5SRcQ!7?gPdhxQqehbrv57L2UZ}d|$8_t_~regHlvp`|qQ5g)*lrUIwKg z*|Lhl+hVwD%;0N!5+pc=1(H-4&oZDFI=N*6EVKt+9GW(Gq@$vmvYDWd50u`~Te{H6 zV2eg z=5z7SfGL!9ge#r1NY~y7SbX8)VHLon2gqYYQpoQ5?plywTbPW%0V-N1`Wr4dHyi(! zD~4SdSnN@Mm9yRv2!;kArbfd<1B-%!v&tSX$cj4_Ye{Ynv7jvwnoRr;%+s+1k}eJS zft(S^$4zKe{7LSkDRSAe8LZIDz{#t)yEA`?Me8|rAuCxok>)M-U|?Im_k8`|-HDFm zooTo-BxxrS0WfrQE_g%?H$ai3g|?fEB^O8hGNCLV*GZmu$@i<{;^H_tEb6NEoIE

z^8mXB%-y4VvvPD6o*7&mVe!gSmpc}6ak=N!`rnE?hhYgjV6+xy^*StOoSqu07==B zfBwL(Soche`u&fTYHpnoLy9CAOo73(@N+}gSH-<^N!%wcH>iuKIkb@14cOlri|7A$ zF}P@W*>9j+mbgXkP*OJz^jVgERs5Ew8W;IF6AhC3AM``a`SuaKz)`?=_#R(k|7xNt z+xP6}4T>cHI}yE3gq+iGm)zWyt{1K3dLwv9mh#k`ob} zv<8AdT?67>(8^Qow-WNgXcu6{IizKP0DH^S1sWfI>`Kk^Yd_v}QEo9di;DmB~~Z9VNB+&(Y7Hr>!dLQrn5oa^1IGU%S{wa&;zt2kC9c zro*F>^~ovdFqIn%Tw(AX{s^fkU83Ndx$Wlplf@06b1l?blc(D@P@luFqbSY`I9$S!R=V+Xx9*}+_!_5og*+HHe!<_^dQ5}-pT%||3 ziHv=V+3>yvM`x5a12uBkFaxmEGrv}m$xI)JmHd<)i2Z+F01@la1|r{p)$-o>U!OCZ zge=yWCg*B2gCyM8E1HTT4DE}`A!;4;b#6YF7Uewt5 zPnAVn?x1TBo)|DxufYwn*i9&Go~$E`4s8cb6^n~UJf6f(3yQnT8%6Wn-%0>|k7Vag zd@<7lWLR;+(FnafBjUsAlYZGdQx3U?sGQfST&B5N_qoYz>?kZRE^%rIqx+pSIeNeVF)xJ%(blfL*n7;xntXy$o>*3FJ-R^mg2 zclX;qOe(WMk4ihJHHWNqE#8H>i|GPu@d>6MIXP}7$5w9|VpMc!d7C|ngyZ8a zVu+ghr;Q1ZuP)s5%X9#_I+Ub|*#Rs?6q#pEDs+S_{2G$aIc)QfJ@-_LpezV53^GdP z_`BI~lIgV>^vH}B)9l(3-)99UjC9g}qP?*O5tW;mvv}5O^w1MttFa8g}h< zEERa#8XjtYk?-KCe&mrrxz3*@l7RNlSs8Zfn9G}&*J2xJfnT4xY@Cv|s*D&=!IhkT zgyrvBa_o>Q0M_73$GgIlnQ$Fz>IT3Fc#O9>7qTbSq6 z(0Sc(e}BKj*ITkDC2ltzi9j}zmR%8j)xGpX+&T;RbEj@02?_aG=S%ceJKZdjHfdMo zy8`S`M!pt9MNX0#jnn)3@NqPLGefgA=mMXUiSsYRk5K1>y=<#IXQ$)-5IKTuMeIFY zNm|Z^5dx?)(p-x(F17?tOCY<=r;vOcB|@%(!V{{^dE8I- zeQFfbhW+b}=LuMiPEUQdPsVwf&~b|aHCB=&r1BxDfZCcjjcWCs@hX_ANbfV!>dN=9 zw31xC3}?nmvrA}S8tV&7)>MsCdGO3HxwiLOQTstnVdD>$0j+|MvF#WXU3VEO?_KbQ z7`Qk-AM*sL)E0mFguJ)QLNT6Oj(JI#TpecqbNimVr^H6|o?KD%J#t0N2+R9I-j41$ znaJTmnT;Krz7oZ`?I88}`gn?_f)|gg48l&wvEAFVE}6_0cT>3HSD$-dJ_$PRD7fH# z=`P7ZvL(fi@xdcOOP-EOhtwY9m9C@{ss?Lg{#9qC7?OSVnjja?4;}I+*0>6~FJhX? zL}*S_9J{)B25*(DJ5Rvb6)9asbN-ODQafK3XN{k%9@!4LD#jW=U=I?;%4js;Z`~Ws z`1^_i$~GFj+-@nXEZF|QbQo%kb*dn#G-|T1_K$q3^beI23uu1ol`-qzQD0IGzZz~K zeG7I%?$}x357=1QI5F(-dz5PLdGA%J*`kxvt`cR?oSgnHtWw6$Zn0;HrD_hHI23L` za!n7#BlL#rZwDMNAEo9D`rqc0*TrAb$Nl+!>B?_vE3VKZ9Evx&gqua3G??r4E-d@_ zRw<>D_k7mg|As?@KX`C(Nyonu1ma0BsPf1a@kj3~?Jy6ymaDs8+)lIA^%cFLX*Hhs zJ3-&!drM`k#B?q^PR>C<+1`EE$8$4vE-%}zaIfa}fWno`=8uro`y<8$7(uIIc8-=| zBV|pX#^A$_{7Ex^?L)m4CEeaA%Tl^~xA;Twp3kewNITvYUM@brD%bEQ%`Rz9kF$N` zN0gWbd)*H(=@{GTS5MOm7usKCFPCSQEYzw_#<0BPW#}LFnBH}ddx+~vsU%nS8qdc* zq81cyIOR|vICb$dQ{+Pm%bDBLJxYupDJAbnOk8>3yp+@(Zum9FTp9bPxZv~1FzzgiBhSr52SBs<&RR;EF{ls8;{<^aNjplGw2m9i1I1q9?yX&I>4IOOA zSQ7pbWaJTqN$oW<*(hA!-*D=GY3MYfI|Bl-WeVMR%mGKA>Ip&xgj$FeV2v^6#81rD zjhjEO88)_PrR0nULM*uHcq`$)7F=|HE{H(^I5MR;8b)mSSvS@1Ma`Mo)oqKWZH8QL z(0CFXL0ywO*Rm`Y(8uW9MiQ4gd&0!BPzxis)9bkPhDMaCX8aPSDmofQ$s-+v484U@OtxGk1|m>7DV$88Er+v zBH!ZVxI8dTKo@|Oextvf;Xy7iK8uLiSz11M**jzWVx!)=j2B*<7guUQ11*JUWJ}U= z3Au76!ie7~4gRDZmCI-F;q^mKK@-7vPma%mEWp*#i(J z1d5y5$sBi;t|xC4I`)+n%DxcB0`Fkxpnu~e`amQMIiy`MT!;d4P*ujP!@9O#uW^NX z2>P?XAg>6E3*VIvdn*?Ghy=;hp@Cib?*73wZN@Gih5`p*|j>k{(R`<@#4Kf$Hw%I7+Fzs#SiJ0%6&n_R*f|;YLDuo z5`U8ZvZ_*Jxu)sILuZ^~wETx7t#T`j$}zYX=u9_fRneDjtMvD%t1B>pjP#dV<)OcT_Vz=RXMEA>v{Ila<4%d1+-4$Jn^vPWxpbrGW$)R7}FR!gfi zO5n;WE9k91cAnAR>N-Zr)5z7wK(QyItFCo z%%#3SNNuw-{DQ#u2c_;;C#%oZT?MB9C=2=~vyi)6tIpdzm3#|Soyh-CK@a}*`ostP z-X1+)FPebIVej5*^GUa@Pc2`OX-5F%L#5@4U;L@vktB07g_S#f5yn#Bri3obSm$uZ z_WKU6uUTj=!gX9Na*=_n9#PUUCo8RMdnY?)BP$h;0YQV^>>>M*I;$7kU=gtqNCe8# zTK&10akH4kf3!bey3}6vyHf3ovBu>aUqUlJ4tvWLx<2D-s2Aq9vzPqaYo@7!dTxB8|`-_&I@I*{>~Btqo)_GN6Nfpd0fmIqbo;9gbITOa*gE={(8Tswfk&C`;kr^9`@)9nsoRXLUQxlK<{-r$A z-x9W=dr5kSd%2_5T=}^bc_;pceU}2CFSF{X3G%Ei7;1|@0w?EAz4|x=HU9A^id8I8 z=yH=yS&84Fi9^WNOL?x8)byWye1ASySS7^4o;{P4eEj*YOO)f<2fBV9flH|hlAqC4 zltL;mGkb|Ns=D$08nZh>x6Z!6UDRIO>XPhyzS^@n{2Q%mTz+Ed$J8uN*{`9YgJnJj zi~I{oqx~6@NqG$+7ivBdp7$Be>xU70+d7?Z7~UVOhzMukC??&`95uJXwoB+mmXnKJI%ZStV>8&l9xbvYX*?ns-haEs)d{V_nD{EvWF7U214X z7=bkG>lgnVZ!PRdHH?{+Pky;IBnTREw0+_&oS=XIMOg~H23*GWIlX6I;Aho=c(ab% z&U~JugL`|Xx6)XyzW+SVlnD&v17lS4=pTyjRX|#@l5SXKhDlR^t_W#U%d!^oIA3=o zPu_-kvf|F)e?LeMw3vmpjQ(Dex+{h-diWp_Rx1)=YX2Zy_lI>K{(a&lDTWY<#ikI% z%v*_>$GNLh5m4kJs$xi6`HKuOt z>*e6{_wt!@L{s%t;MX^~;7Y$6lV44?t1DpO5BL2{rZhG<3AWS!hD6Oj#AKvRD`=Z^ zQ>J=tFR=B@CS?CZRa_p9SLNbp&3_C-s<_R*uF0%rx_7B2`N_z4Dn_?!IG^Ns%W}Na zBK=SB#wYiAFxgME>OTb@_;4#b4ezknWRP1}6M`$9=ElwNAftolWn{O8$rJxqan)~m zliu(yh2&RUN_=pUZBXM5LYeNaGJ4h=W~2dc6bnl56a%f8|r0^gUk6>He1C z`w#rz{_8iZF*y$%AT=g%4k=uY&jpTb}b(bMQdq8XY@Z4dvEo9 z6%+k3oF(!6Q?Av81!;}1G}b9!=|BwJ1!`&Ljs8{U*K~FJMG_s6Jz(@hO(rZZT1>cH zAl7lVMetHil6SgR_~TnIbgM2~w6!bWS=jO$p{X-jI2h|&A&-z@{ENy*7qObZdw&#PXikH zC{aeN(AylgXBO9AS!l9MKE3}(4DlI~g8gi=b0<-=zpA7w53iJ$#Rb>5jq009u)6+0 z@Csw?FAfnq60fv`lEx$GRoGmM%l4>YW8}q?pXHxwyi`mKe}QkOluM45qSUP13WNV$ zZu`_eOK4+!y)16ZHaF$(=@~$t!ZG1PFC@NvpF{4R&uctfSh&M8w61ld6)SEnL38HS zaefv+c?S-J42~rXyCwT7beOX77=f&8Ha^hkDcw?H#WVNuKeVm% z8DDZq8(wMWKi|APm7kH($olqC;IrcF8aZxV4^rU;)hC~x%&@_}*(4d`J~F$D$FEuJ zPd#F(fW@i4_~HGYrK&okfB(zhTXVCgtKLg_!^>_Dw65qiAA6iPJLVpoIxnO!1--ej zS36ReFUz=b(CY42a;;I`RDrD1>Sma&0LJ2g&Cd*U|BBtB#P zF*KcsZ|<%&)H|73n@v#;s;|N$A`@i3v%*O*#%#FQW*mful~#zA1TJ}h-}87N)fVyV zO7BGv)E8Rf{qOG{%)fi8Ov88cIsx5635f1LN`xb&v)-cHH}JGVv#2Mb6oDdU7jn?`Qc{nOrtsmf zi19Mqot)Yk2`P@*#%L-NySX`imYKpy-0jD6tE_AvD8 z_>v;3a-F?+oCZS6)CUJN0Sq54tc**6ff!5s<-q#KfqWz7blBFw{H&GqGLc@u4JT~{ zX~DkFm2k$hDFmvAI3V8|u7Q7QEp)OOo$ux-G0Cn(3^EQeGpJv4E1|!@|N~_@2JLHLV3V+JYn}{mxPVP$n@l5&?sghN+7FVxQ&P z67)kVnqo3o>YiOJUbXbrPe7FwUqcUm5G+J?jjBlr&Lflg7uq7LAr?&`2ceT|WoNxq zTl)QO|NG=9`iHJVrCbI_8IDIq{wH~S;{|+TS@|nIw{?!z?>m;_V9`inYa8*c=K1B* z)pCmmN*v$g%?P5um-y6_KO=C(h!q$=Jwy_VLIVu9zX~x^++KZ4Uhq)zl?>i~RNWE; zG^u-BR;ME%P|-YC1RZW52H0y@rI>LVnEUOu?GJBkWT&c5zwMQ(dm&epbe=N4@o%>?hH>jt?ojSL)b z+c-LaA31O~D)5pB#ANAQnL&Du1Q* z_jyJhrFYlTP#2a5t}lzInW5K(4>YO-QG-&#Oej+IMz&3?No+?#vc z(W3!o))KT!$;J;Vvq7+c2<)L3D%^_a^;}`mMv`8=&s+TD9eo&0vE-c}e`H*6(mwrR z00e<<3L~Wiwg%rD@F;L6O*I6Zw?fJ!TE&3OrP|sx3S+2 zZ!s_xx+`dBe0Tt*{J&ET2x9e%K?t-9PCXc}S?S|c zTW6;?UGKBEyxGQIHF(17c!v|fXlz1dsEdnd=Y&9r3$BTd6;ADcCU5VpjOKXm5#$R{ z`C%#pm4A9*7h=FD4V!E$TnRoxU4+X6Bh9lCx!2cO zTG7SLrL(1?MGGtk4lg$rZ)9{P&YjJIQGK!HNd5@g4cZ=A%5kf5-l6C?f%8dA{K{Nq>2o&H-p+^g9 zfMb1dV~McXkN75d+Nsd8pZ7jrUTb;H6FW5;3wDp#5d4J7AO2rNCWxjoBrpL&w8H0m zI{fa-@Y$1Z|8#SLCZ&~Z`TDh5g?{2I!4_cl{gNV7UXh?lWISRrCsW(8yob|QM}=42=HqD!8k zxT2xiH4can{0*$MWGuuE{6`2|$zHwdJ?cc>$&*q#w>&=8JE^PfCJT$4axo?dYP5&| zB*13_uFMvr0WSU?7QYwr@MPv>qkki$%tso>D#8UvGVM+=It0PUkf;kbV~IF6;#y!a z@6Cbj!h50NuZC%~wsl&^efG993p-}&_>G;c^N}fln0a6CMp9C!Jo#wpJL4Uz<1mzu zbEW6V%19M^K$`1q-yy~0)wxr9rrC)T@AS`4NYaG3a!3;U zTixS#b};8w=Br4ly8G4Ps{fO+es*SezMM4OaQ^n;u7eS*3Gk9Gq_j!~ zP{@txp;JDWuVv-S?N;+T?t4{5s*BNr$o27COGnN%d>3Bzm{Fe!ZXjHUL~_OfRQUsB zGz>w<`q82Evdi<<;q3?eb*)LfsnMUF(Gq9l1ezcgJXy5KFtkRK2zC7fh}YQ7-SG6g z6W~RmR?1WLhjV||@=fQID?b^3jRl_bi~~@j)rNvrY&i^pB!(fY&5CYH7Ez(DtI0J3 z+dO@-arx3HsHQMr(n2#xM^A_9rsIOqXsAON60O_U{;SR6t20_QoU`QV*VVoD*67FA zH6bvqxUz_Nz|U9Sjq=k6g9_JJd<%x(i0D#%?3F%ox)FL*>35s5L&{r@dLu-t)SR$0 zu`H;lx+EMeeTSZe9izkk#8}YToMw)VEXqFB8(cPBgb^VG0K(K6{V@&Qg~al3S={Ez zRL1I4B{Ds_zkN{bSms*3a*>V;0?zgc8Xtkh9)_g*Za61oa!C;Py4ajHs6yJ1mYxM z2z88Ae8P7&xl)TD8R)Yw?iI#(`=S6ZKM!s_gT?;^u-IjYN;^=#9744TVMJ^+*OA)QgOP(B-{d)mD?6#%tTe%#1QZcUh`j%EB-? zvMt8Gi4F^pU_`VI8UZ2DI3Gfp`EJoChRyhMRfbBH)Zk3J@rLqLA7@xoB@kcE)P&(1 z7op$ml;$daXk_2RJ1R@&x4CV3{#vE3lWFbKjX#CF$brk z=L@n2?ppLXTqJ@#6PXoxZ;pS;T)KG9woFf-y2%u-PHnV>I6xd|P&8-@?TTOrxJV@7{eCxP=5vP9 z`RvZ*4u55Xy*Jh_B))|PBqj!$5D~wMp95)*6(c|)nV*LCe0Xb*I!2Y!R)2Wk;(8(J zWzkx>nL!Ja`){n113`58D-;KbO))O#Sr~p*bYRWvB^_7y<_J&nM}9n?(4!Fj$p7EQE#_^St#^41?bzM4oC9d5JMf z?|Qy|EEs=BkU$L@5!95@PD9f!M5e-`D_K8dnJjf18pqGZ-gZ^z4~R3uPg>6wT#sgJ z4GyLgX^tCgT152acrjjn{nMwt=Ttw7jo#O6*FW0-I@@n(SW)F||8lWSzcMRfds`;m zF+{+!Q{q9wBwLsMTU3HQKnK!%L-$->@ZJP79jsO@(sVvXR;p>NEr+rH;qyXY2y2W) zGtzF%3o9*n3dLBkBjNV%*AG0qZ8sFpc3UYC8I@k6+f4&h+Rq=N#AVvo>U6h*)lotC zE=&+4=r=*vwf5D{oZjM~7hh_Nm@g|l#V!OzMg@gf(|m*pX@EtzbjEab2mjWNnD8IS zn>?|tP&8#)oOAxvjs&a2#MqSwS4G0SFjcN)@{{xNA2L>teq*9LA%aj=^ap3UDcj1P zMyCP?il3>$>HP8b++r1{!;{p4bR#SP#%j~;&t#=V0PQ#1!KjM8Q87A6Yb9H23x1Aqdt1}k6Q{ZF=wyxlCBs}}nD|F{5{!BU^p zG!39VW1KD!r-fo_Mun-{CFy_UgKq{|t$k18iW|{1o!g6UJW4(H zHaA)6b;H5L94DMMLaU5EM?Sd9KvdL&Z_U_w`(vUjj-9_$s3dY&Nd^fgp)FDXdSfbv zfh>uUz~xNQk?wl_0VgeCul!}^!r{E{P-pa@iA0V}hs}&JktKDnKTyFsuK>@gHFLAZ z)5Es2qf{zLx^>G2piZfkS+$z4!Df|1m3iWVm78J$pT5JFjjvXDDp5A)=4H$h%aC^Q z5Ug6#`2(J2j}}xb_Od!Enqjc={XuQ(@|6ynE8n7LEN?3Eci#`aA^JKPz`aJv0UVG> z3B83&vO9x*W~(kGi!aHg%J0L(NQ6+y+(a(N&5gw)$8II7{iV76<_@VGDYfX5)>5k~ z<@;!W3@^tE5iQ(MP;g=w+k?LDohARJ^R4{J+4&jH&w7Gadp_%cmSp`wmMcgRZU{$wfVt z#k?_8EnoU3w6X~izApEBdip?$id+uIcTd;+{6<~joQmLN)*wKF9s08pXrt{!!);@hCH{bvg2^Ufllb|NhD(cJU{}x}6$-k*QrW+2U0X4;9!Ns6vnZR8k>Od6qr9yWp zmAu9;s$TFal%5^Q656_VbQWPwb={e?->^1+59NL^>-O|u)a2}V`gD(%ll+Az#Q383 zbMl(U4xdnnv6eD$->FhbJ~tz+$X?Q+Q3?(Lu^|y3q3p%%D6X9N_XNenx^aIp6?cu3tlhgt8igdFoRAQqFdJcVeXWnmz8E%1d5Q z7ozzCw=kjG3PTee<2dj{Occ_N=kE8eg-i!erSrqNC3*Rx#W#3*qz=H$PkTnM$$be6 zl`p;1MYyH6v+U-cX(V;nIfVH$7)edA?8Vd_EJ4GbUVQV(((Iq4ht_fHXD-ajrf|8ks(MjrS8-#DU1ja1-{w*`#nxJ-)lFQz zSZ~?KhmuY$8>WA&$NzQa4TZPWdc1sD)({0(l@yK)``4{9+m`L`gW=huL{sbKW~F$C zWQ$ld&UGq7pGqFT>py_ zf|*zWosNuulesonY-K|khj*T2h`yLZa+CxrV->-g0;r1&8jJiBuG8Hs!W%{XMN&$B z%SF1~5H~??L@fx7I32Uf9PN6Y@ zF#L^quPWcd8Mdrl*Z^jg4J2(n_8QHhtch}n-&In^y{6h%Ym(u=dOZzB0|@Y}o_snT zXnNB8% z#UpWnL75c=HPqDK6awVmQj0?ZJ2GyWFR|5M$!~Xkc8mn*SVw|{-d+VbWR2mHgYw(q$_ zzKa&wns0>#(-^y&$aWI~4OL0xBllavjg$FGiWwP+AChj_8lgZ48LEpDbuARep(fc% zFO}ricYc64SV`*ke5Uk?j@wE@v4!RcBm^cpC|-F}aFeKBNx*S55mN(npEB{U){k#2 zo4|T2Gl39%Y>kyPHaUd)lhLQD!+Tb^DNrY?W&}o) zTuP;^2vbWBbqGL8DFp;O9UE$W&U4HV*5OxZ;7qrA2iM}~2b~n10!kVVN+WCl+zs6o zEv4zLqVqNLwQm|=t%6q8iw_&CtGOIYMhvyBC!F`6XnT8<9{2{BCfRBq-QQnjATcDP5XDTl1R+@vqDWX+Q^q)$ zFg8~{=|~m$ZOak$E~(`WSgp_VAipNY#eQsowgQ0=YhE}Dfr#Xy>-sb#L{$ZIDw-um z_*_DP$W~+PPZCrZ*|-4E*tM)gwn6!wsnGfRPQeQg-<6f#HfQ2Y9oB3X$?0K6A|NrD z%wYQQ=PI|KBFBjI34MdKId{x$(sW!(H!6~BOF0w6I%PCJYX~wS&y=$Nyqrv2z0FMc z{`BgNs$2W-uO!sds_Or@n=^fXIR>JkpvC^*)SKn(p;Ix$r5{tTPBC^Pk^lUdv0pp> zm|ZqG`A~A_UuZRNqMp~**5jjX2?O6DId3>&OzxAzW$1(m{wiyQ2YK6mVsVDwe!NKW zeZckdg0iMaF_In-+oyv(Y$0vskHiw@zGF0VxNMxaVrCuuYCDdLi#Bekky4DP0>j9M za9YUod?nu8E;ZYJkpA>#;~0e1;uq zay5&p4@3Wxerj_1d>^ZpqAkS@XS%IB*V=Zv)URZI<~#OsyzG{kcH~aYK+K)9?TvGL z{xTF&E0-2t#>dCE92ENGMeeYG_{-di2IGSa2dX6(<3CO~UQ|~jNHo?(g|4-@K>w?S z5d;dB_^oy9e_$`m^zv!c-i>iC1+GgH+;lo@uDE1^R`;7WOzZzL3q1}@aXWYJ_hebj z*juvf*{JdX^W_;5J0c{q20$B)Yv4l^Y6hG`_j zi|uHThw2qiNP2pDTR{`QF`py6o;yO&oT)E3iv{t-& z>9wF+{HU6pq0o=dRb9jBNpBsuMS5|x77>tS)VqzGCGj6sgRYG$8?^=%r>prVUaxrE z4BHn(#un%Uflt9bAMwc*LuJP{ihtMn4G+jwbN$Eju&>QvYKIpGpfSbZ+t$2$vu_j7LL>UIHmns5pSf3Vqzlb`YB3;~q6( zeMx?f{w-d)n7#=T#|Eeg5QM2W*qUil^^=@%cKTW3EtumWO(IThpR zi#gcGrCH@Z0b}j-;OL?lRsI_S|I4zJy6VkxNjU2a%n#yr_( z|Lx1;YR8A^b1DNrYpGjx))g-OA6Tt%)*tBtZkoRn8@`I6v&#IjNN3 zGuH6Ie@e>aE2#|wG=>RsKS`5oi>E>$Y7QgEZbg37?DyF?HZ)W6u1Kxne%ECP;DS*k zz+nvB!wG^dNFz>-R^mQqtAZPuNGeA%sbl=&=wa^WhSv|YRZKNQuipm!Lbl=VQgYkIR2I( zYpTv9Pa%JFD%XnY0kd;y6+JQ2JT@3cRF16_-r+h81T;Wd30bw{yy=_2T8|cmukC$0 z$CQOLVod-YL>LrEF*0(HMM0ZTgrB~2V1&KwZv93_$A+UDA!$!Skl~&_eUn`f(8G?n z1f+J??H-sVTJJ$cFs7#Nlh#o?pSZQw9#(d8A$bZaz7nIJ3F#iHCxYmnbN>lP62TIj zNmfbozE5r?t{Ba~&bS>S^h8`#rBaiQhF+rDmptLudhk$D(J|u*ztf7Il=scZG+W6) zeAE90uFfg+ymlmKEpZaAcH_7$c_BW_oIp;(sNq6z4Q1^WW;h$w|b~oVjm$K*@MV-QrNB z{tIJUo{58)(^T{GZE>|`fGaEU@+$=e#kOms1?k1(()kS|5xtvBvD5`=SCQ9HHS`Vq zjkiM1f;$iq9ce*nVYW<%PC(nUL1Jy+c~9;`zuDS}ClGA^liDW5q>sykXfrh4LP>HZ z_`_hE;E>SCnd{T?X#wzgHYE>pTTB9#b4jF+h0xnjrJltH{{zJ%q3mi%V7G!N ze9a+sV%}NzKZvdbSc8mSVjl~ZEcQ-opBagm@L0c-P*zAT49T0lAqM>dLMq>9bd$bG z3SwlUVhZpkBN2jV6D7hB7uzu7AcU#lY}hEH!NK8k*G8$N-d|W69|o^^wjCgA<9(it|6Odw>*e& z5Ncgv((vfuy{Xi#yCDt$20}#uKZ`lw{vd=X1STyVFNNB=8^1wKmp1~ldVY0D_vr7R zw9d_M|LRvXJ+mx}nP-jAh=u=dM+dy>Z#=3sS8PjWMNlIdRqx4iXEm``Ukm5bR9vd| zlpvc?^jI7BcAMV(kXAPk4oVS4OOUCcRXMhpy0Pqa>AR>rM6y?AU(@*@Qua$RjGWV>(`?qhGPx!_Urz|UE;F(YpL=OkRJ9Wsg_cHg; ztd%qgzdhY=!Vb}V=gv8aNh>d#a@C~&1JJQ->Emp#30+-ta%Pj4a_B~eiyI`y_9wHQ zoy(*xNJPn3ZZ>MFXa*?t*s`GtY<)rcB3MA#xb*3NG;*~+l4lFxdR{^awc8%bycaD6 zls~rTSN5NVe4Acr9plR}s5zZ|d^Ag#c%HHIfYD`^-3Up2zO&MYM|YGv&U4z=7TjM| z^=a2ML8x;i>>c4a2v+*W>!usy$RJT_q5qL7=J?m1zgJnSjcnpcWSoFWrsf14R{sx$ zDkIF_W1IvfHj{Q|ynMeUE1k934)%wdN<8a|?Cf!$c6cV|`?9-{Qft2c%u(k?)4!VN zJ8OI%Q@!LYHJq}5MFlv+smeV6L6E>3cqWr3YO4jJ7s*bl#LuPNyWIaAhig3=*hHr* z=!`D;*3nd22J&q@vF7!%T4LJvJg?P7f+p7Nxzq zjgB$UGmR>J>sUKw7Fy+I@bq^+jF`!da>3g?NkYa6B7f3pYEqHzJ#YvdC7i)AJIxxG zEAir!(sFO)aOj~|oAi(S)Q`*rB}gGFzzvaWD%(e~_H#w(q^9=bya5o+}M zpM90&=-fl@^OC%SMW^k_uco&(A=HAqtC-5Y{qrW{7sieR2x?_ht1b&V5x@KOc}09T z<$ETBR=^BIbD*URP(Fm5zYVj<(f|pQ~M+?FJGNBMq7w$iv!{J|HwrjEDm-A>>r($;msFH%25w5*PKZ_ zIdO74H20@;KeJZ7?{Z7nSi(_sKx{BX* zJdN*6Skr&|!5K4EGxg4(mWOX~z0du+)&FJ-iB!LH*2KWqw%PReHK=7?l0FpjdAfIR z=5Wx^ygFfofr>>?Oel>AjE1$epawOOZr-(-0C-^Bm7h6^>&`c4svaUO#c82|3_Q^t z|9jb#&V7%Tii>tQBxx#x+`DHE*J#eW11Q6fCL$9e!wOf5olZ_VDpRGl^AiF&K*k`4 zDzay)ep=@fC<)`R^F|MvPTagfLTk*XL@AX-eS3PDzkQf;`u>+f8z&apCZVW_2ywAw zL~F2ObUTsC)P^Py;t*M9huE^oH}9iVHkebgD;ChYkxHM0%*aBFM%#>ZbihbTWX@WtnN;^pTv@U_YA?$!?gNZN%WJO>|scd zWUh{$1^N*ols6JhV1$TqSfNGwo!HzbNP5%MwN{#RUnw9z`tMosU<6?ElgP?rZzBE<&6LnQW~xH7Z!-_v6g8%4 zoBSpOWv$$aoS0UKis{Icm&!uYazDxH@9S$YY-7-8xCDv?4q-^z_+VHQmD!TT0hGcZ zK}6JCzO~=`kkqA{TzXq?##4r&M3OdjeV2$n4UFY@02&j7!)4On=!yK6fw?^z9N07fS!4(iU;5f6t+ycS(> zNnZ%O_~Whhn{O6tTKI?JAOVDmhFaKPl0n&%jTPHhW|4ISD*FS)0V#RW-8wL3JEiMO zX#I4ul>N$cr0*I3&X|ZQGLk(}e{|l*cj%12-lxu&JXNru{jCWimXeu~n^0_5n=oMf z84gz=_4I@REtoFkDrT9cCiHAic_pX1whNKk#vbuq6K8dqC>97}iZ(%|g(<|C_+bgP zF~BR36Jqh97-s{fQkm%4AQGu>!@3TbLbt!VD`ot*c#t<;PWZ^gjfu1sahbJ^gaRlB zAwu{9Jr+=PGhI5B(KVkIcUL5UINoPk5G$VKBvMvQO|q+3f2J*(6@?71M3WnoItFTu zoG;_n1e1Cw1j{#gqeSD|h**F%p-%nIg7n3*4Rmm=p=X|d?Uu{6{APWx>`=8q6@3*PYcVKLCaq(C ziqg8gmYf%wJ0+ry-s*wlx%*Wvfs1KS*iFOvBZct0Tih44-2Q zZZ{X$jgzA zDoJJ~Y)g-vIBgttH=e8rm)-?e8ol_H+>z6I=vJ5BrLkC z(-PFdIw$B0y4np=N`7lwx@ zfUVn&zpoXgB>*=NNErHODQyc~Byvk|QUeSks^OB0tzV41^7L#aUk?o}`F)cQA?RY6 zDkaTCe9aDqSd7s%u~lvf-q56z<1ubboqjhg{376~Jx;*n11MvZK$J}~pJtd-lJ7=W>5tDWoq|r`0+Oe^IlxKeK zYSX7vCzkamd1uZ=K5}uVrWAs0Wvi(3ryPHtdLfnAB9Ud4N*{}tLkK#wB837;TRKFC zWC(WW`&jld<&k1!2F>qxF_%LiwCPP;aI|p7pEnlrbNnEba;6?Disjpf!=H`O$F}hW zwZj{!oIg>@2ryEK2FpftAdz4pg#Y}%#GS9 zP3^7+2p#KxeJUpM4Gg9{9pUmNAnri@>TCm^QbCwl^7jBdO z+6*1(fFTc@@x9)9hQ~onQNu&orT97fwgF*AO$0_`oZU=nYHE8>)J*TGB#*wdQBW}P z!*036#*Jw`pPsRudsTj(xiL>-QZaUpy3JP9FxIu6oewXcEr0c^F*Q?704{Jrthp`G zRDEumVqUlk7|Ezv5y@8hP*7n-p@uw6u4ZC+R^KgmISSI%6KR2q1CqaoQ*);TD>~|& zzZ>$TgU4e>@A}iviPrRVr!Z>B=&UZ-(KFMnLBiQpe^``&7^o`G6;Be^Px$>0DUWzc z671Q4&#TNQl=1C-smA9@CQ9vcmPSZ6g+f`O|KkFP^q1WB_v|;P92argTTVoCB3^nU zI_A7E1y{6*u@V=Lm$$dqhy}LV_z~l>-eRFZsqeA((49S*7qbAWdO=j!M{i4lG4lyr ziBB=NVtux^$2g(VA$z{ zEAd`7P0;iv>OY<>2&Q|DWnHzWO5Q8+MK{*xR+xFD5*X(TS2~O1I^s@yKQ zmrXWsJ*su=ETMv!Lmb<^e&g)we*08rwg+N%eF?6pWgmsc%Za0TM_*622vb5%cS4P9 zAC4*TVnzy+{(WV2jiuA+QFM}PjOyQTtN;GB?TW)pii19` z%#plpy0ey`v>u{*aOAVsw&!blL*|)XibRRHfdhYNK+E#_vE`AMf_XEo2pj-S6yFsT zM8Tz`6DxrMkjgsJM+U^vk_jqmK_~co&`-f(T4qN{zm0VidODzxEN@GcfEgrG> z{ZQ*MDf8+^wE{_+^VBUW;{t$`-V(%u(3*qNnq*iv-N#4b%>&fIcT8hygizv_O0L)X zo+p!o9><4DbuXxKbxs?zIS!`feER>NOn=HK<91kzRk*RB{DQA6UrfhN<+>rweQj&z z3Ntgax^=&WE|0s1DNr25EE%HNv|TEpYM?PeXJMiHPX9SBxFm(_oOXJ4=q+x|?_^UL z`J74vg@X)9^DSuPU@=oGwM7CwhKT;1^6YSJ^d3$M2q^Y7XfQ*o5rb#gfh*>LFIC0Vp@*(eFH73X8FG!kIuH{$-K*>+*AR$9ccsKe>ttCzuo_7S<}#m zAg-sTj9=(Ko>>)-+2Ef#^Eckudr*kU*STP!QhbXz8J+RFYUPy~e|MH9h;-CLoT*PF zTOV1}EBy_^JUckTOgXp@uXW2*v^AHl5AXh)Q*wEEzVM%)j1DJGIPfDV!pnv#bniqU zEzzM335FtGV8gx#N;Qo}jUQfqXhcrhZ2qZ`t*N1`@A92cbK5p%;m zl8A82JAqk4J>_@N&MhKjDs-aqN@+`(=mJmb0%k+YMpw4^_etqB8cd%i7G z%7}p_=zvYyz=F~;r5uKSV=U8r_4|F^AKu;6>m1gOB7hVuSD`z^Pj-HFkIP+o4T2Fe zQh))1o}i~}?=!!h@i_8fIn(OP^Yv$!`!kJhOT~s4<6R#A8D|e4U+AONbOl$K07zP} zfoO&Ul?+&9*dhwTl87S7FM`jcHtWX2&HC+>tNUZzt4<^$m`jty5|T>k@JuHpO9@GE z5EOg@C?aer)kPf+tv(!ois4PSVr#G=OG1!AfUpCQd{S%8Op*X3k(6Y+M3E9F41$HR z*f%$K->F;VrB(nwDX}{o9yZe_|M8vQt?%PFa&e7RMk(i5?Y_I`-~9dmnZN-Fpq3g8 zjX8Zd91i<)Z+g@qgPf*m6}Y@Of4%N5xG1xL1a0HQ22e_APLRQ{G|zJ_wM3Bgi=;~w z*f5M?$<5(7-RIAQI>z;ctwFH>u+0;k2mlR`Edh`Wyf8tsr);%M2AlT>e^^}&{6BU} z%foP!b&0|Vq$I<3B_g7832@Rfso}%`;h+iF5m6WfkdDN1oW}q4zU#X4eiMiSwxx}a zY^J~K{)ZqCg(j<@Ygvp@1|!qzTwl^9V05Qb$N5P$@u6nmCuppX&K4MC;n zS*a9{8TYG;Vfo7+1cKYb}HVI%r^S%kg&~ z&d$!Zb=Bqt4Q*QTZoz$ah?QgL>)X|8wccFEq)ciKm;ekA1_)Ye)6yLXG7tb9VPKkP zDf8_nt~NIxm+!Ch2p?jCv@W zeQftjcgVVcjuK46meDe!s{kSZ(2}f(fItRbgkjiLoB(yqVZVH?a#i}SWsp$`5_OoC zIcZ5D6rwXBW~S{RY6;fDhz2V&L&d@7k{q7lb=d^l@w{{5%= zVz@8k%uF~USmG+S58vMMum7+AEczG?h*~?6ZEVYjX&lF`hBbNuPGx3aaelUWweGL6 zND2wCK>(Im=o8QYf$%etj3}TboD>=`P{=@kn1^v3-%lSm^mAE_>;cdr!K_9QJh8%N zE=&i=C<|jD2^s)>P%cTCB;XkP)#~Bnub0Su|ER82ph{#&vfX8cOw*t$)CX(W8gq7= zLG+~$MBlh-1pw{LgzB&w29|lAdCX;;bJxltI>uGEKHF^GR@7S8xr5RcNyv~9WC<8* zxlHqFteII#7ZIXP9@~skk6&%`&FX&R_v@CxQj4V4S}rY*3*_~rWm(!{7Ixe1>THAa zXl`>Z46eFD`=fsvyKfG!_VL<7Az{I=kl3_xdGEI$@{_EaiGyU2041}~1g$`G-e>!M zYB$IH{gb+4_0OWzn=M z+cKI9=aNs&4=r|C4|SlJ4j5vl)4|#@%g_O^4MUj@vqWXUkiE$PJ=%OzuZOZwFX`1JiyA?VZkR{d7*>?SUv%1ETnFWTO!6cQbS|GtEqFn4gibw-N z(l|-lz@cC+43xPQ&Fx`+wJd$(FnQw^fkM(jMwm^WK`SPr1QV_%1kzog%OyH0XqBdD zpb~okhwg{Djmu_MpHTx)b8F596-9~Cu`3SQB573Nq?ii3z{RlHl+Riz^=y6Fbsd|Q zdG4MRhxDXaq6@rUcbAt}S4f$)9cTe286eVvQ3A3Ki=;m5vb3($QffL|wx_oA+s*E0 z+lPg8d@8Hi0g+Yb_n6*4n>a)uA4=EXq>LYBgMcJNb8auYPQw zEUP{u0GKf3&KMr?_PsyNayG@~A|(k>0!S1(8=+bjwYzz~UE23!du*%S@a0+e^K)LT z4{YTrE)Ex6uHDLHyQ?S$wbU6z7#=AvwRI8EYfwv)ZPjc~WwaGTI33PtCBzQ3Vj-5~ zVb)Pd7o<|L09Xpu)a>GjL|($pM)M9?*B+$MgZxW=vv)m-}NXp!bcKw0ZBkL z3zH>1xJIDPf}GMqHhQ7w4y`SNy;7vIH><*}#ZWgH|ML5<_YW(hAo^5e5aeMzKApE$ z|LzliwSMa6QOit4DN)K5){o!Z@NfS9S5h9!u_^oLyXbPx`MY@@=N_eJB~ZX+SxSks z?dIZaea>0)Jit{>pp~#aM+g=zDDA1D03gFGMsE&gX4cfwm#Ib?3u3kGRh zwg`|tf)OPN*)Ri!OxX_5M2(&id6=hhjHebyg<^x%X6RlIIzKx*Z%5eKk~lGmKp`lp zSp}ivqEx4vnFTDREXrw`?uOjHx!S)wySc_bbU)bUJm-8ES2Mk6y{GMnkdy@sv{YI? z$M*WX4LmLrBUbCx>wO&m<^B2Z_McDdi!!W48@A=zvfJ_XUHPiTcI3s}T9Pnrnl!^O zlo1)&w|3Xs*OTrKy7~Wjf|p*|vg|s{W6Zhs-Vx`0@6Gp%tfGo0B|~<{zs`U=B1K9v zY>I&0Gt`zlmaVp6S%6vwWPq?NlEoq`t1>GyZ{GJgCnEM0k=oj^(B&~rjo5p|tVm6uQu8!TQVl-HHGE>26QTiC zqYLG^wJ4+&K^=WJse{oAb{w@JCnIU^`<+m@LZ z7Z+EzS7YgCl?p551}>mr4keI55|5#hdT;GQ-r zPK7R%bA%@{JR-zIM5JjHM~#R8JD>`Wl%e8gyFoApr4R@j5kX>%K?eRa+W;g*CjORj zmJtjXWt&E3x>!}d-`2-dK6>fd=&ROlt}dU1H$_iPu zB4S8~!{PK~y7>6ntIwCOZ~6+Kg0|On>ksSoV{>p>i69J+n?__>9-|IEUEW;A`}>~i z>eD=Z;C=t*&GhFlKD(cv&6mr70pn~MnvTAChmVeN^UyvR zd)USF2n-l4Kvea{gq#RE5)n$HIyMnbng$#Q$zdk~FacrMA%~-vJIx?%m#Sq>#ZWdd zje;SK02yR#zzJ3=gdL)FhY=;A!ZpuRx_$Xqznp%~J?3leC<7?qupJLqb^CiC>5KjS zv>j{fjRd9^x8A?~%}f5n|Lu<@oxBZK%bcg|+cuuB{kYAVFS)l(c#O`7i~Ysr%_S~W zRVR*)l7wJuO?0V-)R?=-LA!SVWCQ>Uwj*T^qFT$$v26f~6WD@@$Z)17I)wsddSI<8 zH2***gF&-sq8LaR#91hCrs!jg7L~!SDmWsfB59c*2!MbML_}ux-ti+7#(zQ}Nx%`& zGRHR7b;Y~B9k*%aWi9)bw{zTF?%$hgj1j$HFta4UgaMGW(R+u`PH8)el0Z$y!2uWd?V|z*lZ4M?nmA*S00y~2FUEMQ`gZf- z6mRnBy#L9~^#1kZ6;9h}s}7qW2oOb-1xglv6f_Jt;Y`v(9XQ9l548Sl*m@r5pT%|^5 zP;hqvAYmHB+@l4b;&kT}=2{H&kDq8?- z0S1C3eqdX)36f!(#F<F|i1_+-42m%-a zK+~oIpv5yxmW=`tBtc2hXge$dCIzcZOr^nK5CQ>{0&R<=Z88Da22hfL%%Cf?x$1B{ z>ex^Hw7=)Rw|%yoX}Os5Ne+?|aV8mdG64%985Ui^fMaYUPK*c$maWQ+`&tLQeSTdZ zUB14;S65p^^fA_V$N7yRU-rq;r|)#^+ehnC$zy6wy}6s#MrWM1b-ls-?9vV0<8Hd# ztMSX17hl~!TjvjtGe9^D6nOI8JO1{G`an-S`J?^((;GgWALicG1L+5#LT4I2Of0T?a<5dl!rW`faJ z5dj&5Y=f;$PsaSKpa1gW&+E#|b)R7qFnQ>whpW2#y$|^L{`GXK@Gv6CmRGfZ^_v&` z!~f%-tlK)!8O}@~4u?Z~Yac3#m)O|`U|VzGYPs0&ANN%uORyLe3jaR_BmE1R4-fBsf83 zjg*AonK;WyC_O;9DEGjGC}S`slFazy&ReCa9mGsAFm#BV(z!t z*6ri_g8aq(6vEbGsJ{E4t-E+k9cyg+u{^ufMO!&;+QqJ#fAwK{neM3 z|3Mcu-_{N;0?2%KdN^G9?(cob&-SnKpaz+?BXEPuH{ZSHKm3>fc-_{4LLeidI6OY) zgLo2#ue=9fyQ(xN?)GP=Sxx{x15lhIXoCQumVq!R5Gp_fFeH+VbGX?wW}wCx#sCR| zQLx30#bh#4X30Y>&M-x!?XuxYdFVtmgq+*ySU*ZS|;eztwR$RIJY7o8|KEX1BD_tB<{GcX(ljI5}cCw<4I4 z4k+EgtIpH$)b1lQC#EI9LA>mbBgvmWT|c>emD_KwMr6BxIIj1{r^k7Uyg9|`box6l zaQ5^>lxxl+esQ^uo8{PW7@L;5d~$)i-Jy*gr^V@CzTSO#|6VPh9u^ox7$~q~f3FXY zUf%6KS;sh(1)2mR5M&EBxEYhRr60l0cr?~cuTZDZyGK3v&Nn}O#!r_qd7ZrE)XIJI zl9G{`3tEB?wRJ0G1uHNT17&-sf}Qe|tvP};ETdNqTS2w-+&DVn*=vtFop@;}sm%(t~8$`-RdoE}d%eD+^{=#QsYc^rv`X;7lCdHLqsSNx-Y`QP{2 z3UY#=HXI%g?I@mv^1c?5ma9sw;qLbGa<@)W4p6qviVGv*Moh_AidCgDg9yk(R9R*x z9HdQY&P)p`Sy+L5JK6TVaIf zrc2uy6G=HFd!#|Ify$7ja{#_cKAb^YD%JTY%qhFo;E_M1yTUgVp--_%-jT|K*uC%eZs_R$tMeffI+ z#oPC4{`|C42__gekBIxcTd{k=5AEGaO}5jZ!7@tRG9!}^6IR(+Q5xe7s?T^_>(JZj z@`KCWXZxvlDwQi^NKDTY^QfW8lASFv0Y`T8$^767{5&;@;xtmQW zQ#OPx`+%L8gAGVy0Z^mCK_wuBOt2}D<*i%>jhbp9m~Id)N(3-XS4O#1-Ue%=%rR>c zd&0>yXa^A-&31UFLTMm6li{7Cn;kh2P!3n18qDwnBkB?c!*GYDW zi6@O?-yW~8cJKe}qthpfDIeSFQ&%B}L!2Bf7$k^JRSkw)#7*)0@4vtJ|BhG7#a(Y{ zk~n)+r>zm6-<-bKziH#E%h95(58L{1yuD9LH@%&Xr=PyU!hCj$$=pF zS3ZqdSMOc(_Tn*XM_!Qit5@@1zrE|zZyk5GlPS4`m3X+^_cnjuF5TkPlAi6EnxH_X z4I(o|nmoz1`KZ2*!&~&R=xpQE+f|P1KAM%)$O)L36Wc86)VHl`Y0;)WN7`Gs;FY=% zVa)0@CPi&ysZg>vDA^N(=oy7FJogG$#&14i{D;ACd7MJ%?d3m{ocrK!{`tOx+TFq{Bu$NWkzIM7aNEHcCZkI5KOZ)5RtGY(Np@GWPoRei$mzb zKo}0A)3YJ40Mm`0hzw02fRoPfMmjdXgziUwh*PfRtM{&WwY!fo0~cU@{d)H|Z=a6ki^m;o z%OEYB0uR?aEEnIkOEZtNWuxIo1}ttKk!p#Fp~SSUy&t#!@D5|_q+ng{aCbRhaaE}k zJ!)`t8W}~{MkIhm8NyT}$*E}a6r%^iUReeP9fbfB5r_a?q}T*cOk%|-YNQ=u;$pNw z(vy3+4`KjPq8b8RqnK8bfvCVhD2UisC3zS_NyA0M>a31p?55Uo6zv!lBIU-mB}UYV z+Om3b1G1t;KD~bO&9A@FH)!*sNJunHN^^gi(-bS};s^ni;vxFbNPOKnQ@5 zjF1bEDZ4;~X8^4=OR`GBkBG1nKae3npbU@%NH<&HDI$c&G1k<}(0$co)w`O6PutD2 zo5OE6jd7^eKLYHC3J{=So8*33KG*)+Z@zu+&)!~f`9RB5fC2lh`@@9%?7IK<{yyr< zWu2#abltysclpX>Tydv9ZeMTHX?%E$i;fYpN{B@v6JfS zHy6Knd!y;6rz?_>&m^Cy*H4=F^DnPNWjEx7 z5vXBU)XWJ3D{D;QW%ujn&)2u9OfkZM4W?8r@%hdEV%7tXTPwHbB}~ur{^j>C`49f- zzmgZpk|UzEhfzn>o5UNt=y!mC%*cqD_*rX9aW^pAG5VC5VHh&XQ4CNClT0#gR~1Q| zkpPGvF>TW(fM75TNM#Ek7z8N+Faw}%{HQDvNm>Sf0NWDJa*|{l07(EOZJhxiVf-xv zFyIRC%p~C-05Cuj2%LFdPlrF`uO(p;qJ*@|9jW-Zh7=NT(BVvFhL;w z0|NVH_l5W0ef#Z`KYM${<%62%0t}ib*Q5IO;cojcZr6o>y&N(d%*S^R`|r#4W$rES zk5yaWKW?*`%QR&>E^$bo;GT(_Iq!DWa7yhX=XKMs-`xJy{cY@iYh031Rgy}z1zz0+ z_RE)7Hqccd8N``<76>AV%xK0qJ)Zj8!*<+u>uzG)=Je!Z_u=BXk>gl4ve^I(m>`2g zqCp@c0B8sxfU=N8Fb$FhMvZU;$bo_t2qp~E2!sLIFhI!>JOf|?wq*bYWV0o$vneRa zMnnLRC7XtArkRO??G_-k76ifu*)kI@0Hgs+_kFw@{msOe zh4n3-x228WTkbx5dc5$l)-eVFlLgQY8vx0vwGXE2`!{c%{6EhxHhw&I`w=h#T%r5E z9`17e{X1MOzt|t}qxH1zUbt`lLQ^ny1L$>i96NE$bc@F+4)h>mYPy^I4Gk-K#n$dm z{p&X$e7RkqeK9Tp+AeFjdc*TOU}@i6rKBQkFlfUv3<42h0LJKbeEWF$x{coVZkM~| z*|J>h=9?_IqD}$~1{t79!2k%@=8t4sAm|_AM-1c40f4qa3m}mJgCJ>~$(9_J6fxT- zeFhMqBwHjRT141&MrM$f3>yX!$r=MjBw#?0%?JRJBB3fGZ4)K{8xVsf*rowe2Bm45 zhWP!Pci*h+Is%cq*KgkP5C6x1V_cAsMKbVUovI#) z=T*gc19p3ock?_){?u;3p1klGBeUtO1Og=NAZZ#9Xq$F0NZOVqAR@p32o)_GB}F#U zKZ4D(BymO>fCUR+o0?>3kOXN{h6@ofJmQRmEklOwO!^E0NE-%75H^6YO_TV6ZBWPp zph}*>0D{S&aTxWk`djKdd7Sdq7~}NOc<;ma_33VHbsTF^CJLY(u!S>{pIkhBe0aF} zcVAzu_;g%eY_uGtfK%Vz?e+M(H|@Razq&e(8WADs;&mJz5AEn=w8%Jv*$0gT2eH|n zV&3oSsvRyRC?;Mx+xPe5tG7S-YPy)_s}&Iez(CPG{M}Qk#kbc{Dv`DkCfUnDHUt^! zs!`f>csR^2&=^nWi@VFq_ZtG5VwD8Iut|deNswp^*!YouB+_75HdfFQS&2vl>L07SASAeB`T0)U4{X0pJbWJ9u&0okrL zwb2DYa4=vO27q9yDnS4?!p?{?Uc7ntU2hv=FsVVpku~~0^W*FJdctgEBtTdhuAE-J ze$7ApAO7{won&jiL-yMHo7mobzPtJK{hzb=ricVnw$OyyR+4?*l|5oara{Q*Hpk47Eh3m6Cu>Z(WHi|Z6p`6#1YxnMHi7{& znCz0u*+(TC8So5J=n*5LMs{mNXvrMw012kjPFIy}PEN27Mx*o@P zlz#&t@&R0BmRdyA{JFS8v1x&MLXa5N3CFj{MhRW3q=%GEW%>{y8g8VGm_H6jIM zCJor;W2B5K_pa3(!5Nr{1~D+9_VnDcwIwo!`}f;+5?)I>=r=;2ertdA`CY%oVd@=P zRe%~j#$e0F$G&aL11;b2^5*j5gE@j=KOU>UsI7Z!O><<6Kp6rB2nZmc z#z750{dz# zS~|nfhOq@YtKg0drsrMuoi>h&H8a#kbZ2GIEd!(2t8xe)OoGYWf}IA{jGcqStq_8% zKxL*ly?FiZ&DsW|gAIml0QS=I)2sPriWvg{IRT=J)AHiwEB^7H{@c<;RoT6@1{|s$ zM?WOLBEUR?Yn9g#!8qX=+RdEHjD_Tp7`9RJFs!yEo6|%F69h&;fr`d#NfYa&)|%Ri zQ1r^!JMI9vig4ACd~Pyet&ESW7sr^WM-sh#6`nCw-kb!xG-`QUOry`#jB;_XZN=R8wg-nWj{2# z#aEy0H0s?vcm`Sy{GjZJ5V#&r`Mj!+o1P@@rupvX=7zmjZQDs1^ZU`7qSC#gNi~|Zlhq8O>F&qV{Nm*+{?VWO8{=A5 z)fN%@=iHSXRnhFtUF$wL>(cirL+23et z?djM_nh*v=_o~N++%G@>FiJY+MiL0y5MehsBFACeKR(QF!thPC-8{d)-(Br@SL1#i z<7MCKcK7b@f8eSUYgSHdOUx)nY2)Y^68J2BsYW<~&(GEy6K0mHses}x4KHV;tY?J99$v%vZDSz`j%kK8-%a3uYaq=)>8bDJEC%eqbx_Cc&n|C>mr{2Hc*7?2bZ~pG>hI%&-17a9-WRR`Q5+#rY+axnV z0wLW4t4&r>LyT2`|I2r5RD9bjbUvW@-M z&mSHhe!q5C$fnUW0K*a*Oe2f{lT1_rQPDw%D=b^~hzN)Poa}|S<##PP?_OL2;>d1V zv?yDEBEmKRk^m&Z8E6n~1PdnUh%5_%=G%T)ws&oJ{9Cj$9Kn%Td4+Wi~+}419$Usv+?lF_qSgjzPMj5a^4jI|Ex&xK$XYL9$aNy?Srw;4f|k zz%Bp+Gm{KS)8@nBFunFR){Av%tv%f@H&>VM*E^D%e60Q@wu=w$Ui|FhIQpAu5ReQL z%m^}As)9%YFg&UnA{h{gAY?gP18~Baa$0bCdfxk=P5XLwS3jX9Xf_OxpkcsgQ&J@$ zKnjE%fFZ&FBG?Bq2+BZ7o4;@S$9(@~&-uU_$w|MdT=x{_oT2zvE+ zU85qtC0FAlJWBtOD>!YkEIZHhd_V8nd!Hf4%9^^Sp3o3!i3UKbYOgb|xDRXqTE@tz7Ijd#)i;MMo^XKj0m_D4PRbc3&)`}4_Mo7v`Nwy3E znLW#Z6Db^;TD@uYYKSuQ?+>w^MFvdzfkY|A&TOd&z-CVpI5WFaY`4r%fFerNqv?J= z-j~Jp%dYg^ExN8q)y~3Yt9Rpc)$+EoX1-Z|b~OErYy#*Z0fc z+`ay&Wf=}yv7MPA$?T@_(@NuelsDsUw>!O_JF~CjlP6DJ z3?7ErLE8Y6y{Xf$*&V-n_WqmMx=o+Ds!+=mgvf>z*v1jEu`_M`5eV6q6w%eGQq-4z z$?@iV-2FpY$4mXM|LLbwI|8gkcH8E3*;)&oN9+`%S=CyvSuYL`mY+Y->t%C( z9PVm12?-%tJaA@9n+Q@-!XQh=&~TK%HM8|)m<{uO%EN!Y*zIA~LHp6*2+|@yK?G05TKF zCL&5bnr`RgDwex1yRPr@thEf`Y^7}QdYaCs`mt#>m7APVzbR)=&u&kg&TVUkVs#O- z=wnwGzq@_@yUlS9&+1@sX6%{P+Riym)32V7C$FAeKJV9q@4H&VO*C80y-JjjAW*2Q zIowZWH64As8ZTF0kEiP5c@w}&z8Zk{^QJ!mBBHM9cHD7U$2L9LETWX> zhs%TI@+li&n6{pS{MobfA0Dj6?Z>_%3?a2T8AbHikVwWdj|39NjtJWj zwh0xLLp@o2*zNvHchH`lPG32T-3#qu*+#XL(wYHD7zi-3@FScyAqk~$z{okXo9^Xx z_N#4NC}bO)*(4!{OKYh@M6_CME5gPGQj%>+?D;{1(v`ky{pq&<)Q0T4>FXL{0yjZ?H zjkk-IdDBl*HaR7+XJJfAGBoF2DMb>tj3nFah0^X6i+&c>2mkyrr~1`2KWiPCa!M-d z{3nV~$hKq~%mbrz+RfQ@T`2-4d#v@|?Yxg+KKrdA*2}WjiRXSHo$CDX2q{l;*Cs zwyRukrp@;FavCUK+#Lp*PXdZl(iAb(`u?y#7>3`RO|I>3a43Y*DXEk)-HfZ%>fmlx zr+V6SaB%RpXILBz>}qRWR!I#u)9x4Ap1yki@y}0IsK3{E3_A()~anundpImYz;jC*+AOnktAmV7NvBhvHEyBTxMTO@4lmD zrc|7*@8a#@aI%)q^fKA*=o%hRV%?hkUO zT63zcDx&ZE!?jO-e;*(2zrGs!u9IY&v?QRlmd+|)9cg=5R%cy(GMkoV9LORIHEXQe zjPtuO*7f+(UXN#Yr5pN1Ef1=}fq|_p>D!}zHk++v2XO!1tf3_2L zCjrgug)NMtfo+jeNV4%>8c141Nb(*S-%sQ3dkzi`J}$e_*~sL9K>}G)W&+N{fTTc} z1Y{)HAOWF>cF=B)x2rs^{}X%DAw(zyc7jp0S9nGHZa1w~{r5Ge_BNipddg>usd#L= z+G@kmcL$W=Zp`nmfAEXxV3?n_J~P81DV^Sv(s|dlE$wEW?Wot`z@qfgS=83rG>zNs zu%0sWc-PCEt-3i(AV+I@_(a)oOKc6?LpfQ@@xm zKA)9-80Kv;xx!3cQzz9(a%F zhHiGyPVYZ#wm<6*d3IL676aL5f{~P&rbUDSgY`!y!GDAmQ2;-ZKyBN;?-%#O%j;V1 zH{C~Lkvu990oAT8!IA>fPVWJVATvuuYfTZ7u+xr+j&k?@YPeyjUDu*GC9&u1OL@7B zr$@_S+yY7>B$#5jxx3eY{7*lf+MyjK9FfrK_7AmC6IFI z)${HB^knyM5wxWpLKrX@wJ22{J$*M#yKVfowYKe7$4`#BZNcK>f)i zeO$l&`K{6~a{+V|jBQCKjmKc8ywUDT0{Xe8OlsaU@%66Bp%pcFyI8+;z8P0>1M_b~{`~2-3Fp?0^O> z6R_Q(=1hgsfPGd(D6q)JLoDM>Oc_a{Rp7+l;tMS?R?mb8j&tzA5y^@{I~Rnu7v zC^AChu5P#6*+sUg9ZvmnzIa|@F`FHA)oEIW_MEoDrIax})%ss^_2TWT^KWMBad+NV zNttPkWE)wCBqDg^JwVt7B-sY+L@^uYhkkPR{d)TkvqL?5GJPdFve?+90foHBzY0u5 zq*JoNHnt?E0EHz%Zrl4ZyDiT@Ww+W5SICf(fP4fY$x5d?NMrFweuN#R0GSEsQE99% z?)pp5-3=7dnHdqbyDr`ybZ1A)vRMfUNK4Q!KV4nxFaPGJcxe~O5ea1dw$%;3?czE@ zQR-&1zKxZS;>qi$`NL&v?cU?g)qA3p$Y#J`BM~6UfCSjG4VJ(bbWu)Yn5KI0+uMHA zp4a}>*kgxe8^SjKLzumEL;#t9i?H30LNd0S*xBoG_kP(8v)Rp@tujT!AQ3IoYKowx zkc87VMx_+GthELqgu#}I2(?+>KHqG{)A28af`v0|q`<w#-!gYMQpmr`>cn zooU4wv0}>+BQ!oa0$}VWI z1jQ1_N*iRbyqB54ZBC2D;tx-|6z9uq*f>h*BF1sYZ5_vPxU$Si42K7YPrC@kfp)^A zY|9ChA#%?eYyGcUJ^$?G`JWxE$L&R5C1qx7jSyfF3T*8egN-o)KL8}M&l%7F03ZNK zL_t(VL~Bjb(9aJ1@CL>7rs=lKa`m%PakdpZvYNfKD<>AnW^9*Y-_UE=vh9?RJ~n z*l<$LUOt_^o~3flv~^9V#h#6jjZG;fGb1DwEIUmC5@OHTo#IKq7{_t>o16L8XSIK| z?IZ=}fow2)cBUK=@*e?`C?Yd0@jy0~Mzh|o-!J=aKD(afwu4ZL%*0_kl@UQpY=w|o zOA(SX5-cNxjR_U0(0bpz+-|o=VV;3w8hDb{a3mhrHl5vJJ zb8_4J_+R;E-)aw;2WcY-VTXh0^s6=O*) z`cGHa`pcjE6wjcSFjZ3453R23yHYM9LX6{Xdmr0&63<`19KYzRa;s@n%RSNtBZVZR z%(Puf5yh6BrbdJew#8nNp7x7z9B043S*-GD>tC%)M35~aY(OE&v}{d8gtH0EUWA0x znMme=jnO2nR;!DHq0DDjbFaH3l-NeZUUQR>ai%d6dydRh5du3TKaxU}@WAc!&3bz> z{?FMWvKN5`lEMazDh~t%yV};yZ|%FaKkal|pE3LTXz}9cz8iPDEx1HEi9yow@7ndJ z%a`ZVL6)a=m{E#wCLm#hC81!?JjhH*nM8z0OhbYexmUx*x_CZI@$CT>S5dmsp;jCB z<8Hg_ZnM>P)cRRJe=+Y44-OV}L#r;1y^PG%Bhxa|QKo77NA6y~ee>HNE;ifEdD)aw z0NW9fX-lDSrm>x%hynrFEhNA;AgMX~ZaDGN>+kNyf1Drc>Dly^vzKR?3Fwh+9%MwZ z+4EjA#*tttGVRn}M3}Ve=nu2QO?~#=SZ)|Ltu=+kBje17&NifNz%~$3GOfT)((Flo zl%m^fzW@Guc^gCaY80m;uy7Xf_NaeybWk=cp&%kaOq7pTH~Ooe{2wiQX(%O7l{AeP zb-cy*eRqz4aU6FmZQE&l_WITKU71R5JdV+T%(M+5#3Mt>fCv$0uK}_#NESlR`b8b5 z;k(<}sy=D`7ptz6l36hrP)H#;U1j#10wg0Lj31;V9%Q0XaesIJ>2QeI>}J8LOA8C{ zrAjF#!9w_v5VB=EI;AaJ5|Ei797VnEUaZ&alimNK*2^=w*a?yXu#Flx(?DNb>y+*H zaNk&s)7`q<&pzu-u35Wgo`l{7}XFQ4s+kkCI5D2hMB4njWso0j(T9ya|v~7Do9IXBF z!&X<>t&AlL0s)z6i9d2;Yy(n&Ac4%31T=kjJk*aLzg3}RhO*BYS5{=tICVCs?2#Sr z?2%28y?xY~5l$JIXH#Tll{4;~qX>yR*_+?{`};k9|KA_)yVv_(8z!L1 zjVM!rO_F#iuc~46aLdI4Fn)TmP)x3z}PvAvHCbwsmxLsBk~vGxM)e5-Ibkpwkg0 z1+au>;2y>Dwz^!8qi`5%GPS+Y9a&HGN(+u+Ry4m^N{!H4$|Q}_fI$eiRWw3=wf*>D ze3r2C_uu}9D53XNzHdasfml?Y*Kpcf1zY)3`DaggihBQl9^qhAs6FJs;`c`T6tTJEZbM zxh*=KG9k-MR17mLpLDS1dwD`6ES}}}d(B6rq=K)jPrjm>QqqCaFoJL0kmJmtnX)n% zeSMr*L=2CTzgQ%i9G+E27S?3Q=xDg=HZZ_e9|9COezu0PC7V0ge)FOrxGW0&KfM~rNi^S~kH&FswM^O3FA3pw5YrPa2&0zs_(ll&e( z{H2sj-tbzC*pJf77lH5j&c_xN-K6dbl{$MO??_s8Wex}|yy9AZ63i8Zr2`z?coze( z85{F8I2|g4Q=H>O5IHX8vu2nBVpjwLT5`Yr*US9EC+vkOAk$4ab?k`!Y={8=2lr#7 zMln>Y#v@gw`Rioz9sUCEX2-}YyIs7;K!jT|4FR6%EK4WCsipL=1O&Td%obh$Wk6?f zY+#pT*-Hr-RiPxh=2*v&i)&=S%?M^c>#PbS%spNQcHYEs|MerkgWqG=Q>+bXM%jpd z>`zCn@<(FR9B;RM|Gum5MqmBt6W1C2a(T{zcd7?q^z>wqIINw7?@-FOv=P56?kTVh zehd-P9oZuV1^Ng3s8^n}?uo?5OX`VrEERkf7AhA{gl64BS;msS5=aNdCu8|7-cjd$ z{R*kY-`JlY#e6n&vTi7YC-Ld@W2A!S;4o(;)8yl&AtTA2E-&@`!aYgD4RgFw%fl?0 zdJWN#3>6?7$ZD|Q3~e;%w{)loflH-gUO)f`h*S1=v|fX=aVmf!_M9HNkn`oWHuDSt zU+A;zZ$v$+7ZxSForWZ&m8@KK`|&4mHFa`2BfCpWE*&@nr&voiJ7HA6SS(X4qAnkq zv8v&~OK6iu$_pngHoTTeC#~IKhhCw?trN^JQCBbnl_doT897(H=Z_)L069BGd#-$= z2x{L#IO-ewwGyBt)-yMbh-bn@BjQusaVmZ<%a%p~Gq&dwL9Pa2taAuC^W^be2idQ- zZaTS$S0IpWW?TVhiqNgV8d)X%EZS|6(tvV8SJZt2(({w{rgD`x@c@uR#qv*(Eo+l4 z*JBko7Z;IfXLRhIPuLDP*(6r2=R0r@ZZybzZ^)50T~B!bblhvOf6&+WejtI}Gq9K2@u$%Cs_&kV?FKKk&;HF9jOxs1$+J5xYVvmflT13c1M(ITN;t5;6}IP3!e}l zcE>y@wcz7QMM@V6%q^4Oqt$hI#D$Aocf=u?!+yVcrYdq&l{XF{FT&4t&b&Yk@lnD) z6Qdh)uD9TAapUN@%u!cYxm~1QV?PVA3Da57E7Nf2nyko?z*-0kZ8}dlysrcEa?bru zj%@Q7Tr-_-enrTY{U8J~UTPAe#N5}G249sHk1?<+q0zxm2bU*jPRA4Q;xfVgCArTs zd=`OLd}R9z2y^ctnx7a*6kgsrFjPO;UbK`6E5f&rqiUK*5PSlsN3;-w8ZUWdSg>?A za6jk0N+_v8lawkFo`Z)l2nZ9WpkB1IMs5Z&hUOtu<%bj!6CUON;yy29GT-K^T(6@M zKfy0Mr#Je`Bi;91t_M__juW}$#+sQ8g3ID+UuXhh942}u3euN>#IX`s;7|ac|7e*T zhi?>rQhKjke=&0A?(TZB1fzC(2O{^0?r=3mkm~s(7IzKJ(v^ssVrlQuQ(HoH{KiVQ z%W?3v^Dmn(pE{NcS&{+VtpI6f8x*+hZewRK-|S$p__pOw;-u&!=GPw{GtXY%kBCyn z9l0`JNxg6RGo`EcQ!{dwFy`9PIVv53cO6WAKfx~6{ub}&>M6j?&r(+(3t)r4T|2H+ z?VoTYpX}^cEk@vtn(g*vWim5T7zQ+PDgen;N1&yNK@Rp-b?h}h(Z1l4Y;isa!$faM$~81oXlXp~(> z1RTIM{lxQg(wFb-yMbHz5}Eoq36m5989FLO@dVl@(%CSoNpK`s+r+$ma-rv=vg>lk z_+V+{bpZXaM&b8^T&?&82tR4RU{(w~gHemqa)!PxZ4qYnJ(1_@PYEV13*|B-+CZVC zCYbo0WS&9Kzj9q>JR%0I&6TXvmHFE8|H&~q@u}8BApWZ6ec8bYFK7^Q`Q$8j<(rhUPfp_?c;i7+HqEY?#mV2hyfc z=(b?a&r$b1?H-$bN$*EQuaPUICRbt(UcA#svlv^kFgj=J@~8L;)*t8xmEUtp_l3bH*B~6)DacNX3GeCY451d5D^&)|)mz~J)4$Sgz)#iT(&c-qCZiXg@hIDghz#1;etL-SFein~-`uF5 zv4up}7rx{_xPb%Kg{1G)pEDwvjCB=BYbniLJ&LD&tH1vo?YD2R6le;#2y+T%4sh1h zT)R?dN|h-mO8b*{vBvx`xt2$4!0ydCjc#Js4FTtSg+UcyO-yo9>VLF1)>T#M$8;gf z$JuhZ{d*zi#R@_klo&Q0Ty7pX|1r>XC&K$3J+SB*%({Kk%nZpdNNPF1|5x%dVcES; zVdY6alQg6$a<-K!RLg*clm`8b7ye0hIXn`|-0$i!V0Bcd()nNL!iB+WI(5iv#IzTJTA0+^P{gM>Ov*x$k^4 zx6REV^Ohip@E~Vr4DUA+Ol+moq;1>KeXrcp{v$ndC(7kpkzP_(&{&YsIi`6gkgvRE zI1g~4B`q8u=W~S0b)b-gR>1-Xv9V0o%-eXlO{Ro>wx+i{&k44!u zLM6qAHN#@D>rnRItJ>>Up4t%*7bW$P`r>JG;$Ey6P!BUKJ(Fv%ZL%|CfV0|z(x!lR zZQr4z+GgZQafN%!n5XjKn}Y5a4Pn@=Rs7pLYky#XPa>9=XPiV^zE8uhp5O9W($XnI z1al5aWn6`bGG_v|OvrG>l?tBU;XlTT9xGZ)FzP<#LL#U%9YWHtVKlPpdH6s4=;Ux| zwmSE~gaK(Y>arBE@#2R8vo4qQ-7I5DWiI|V2jRI-{Vgvkfj7JnQaktzL=zD`_n^92U*1$bg~K0eVAF z>CD|k_4+YEt=T}{4IpG^X>HfcIc~pNK}2xa7mvF|$WPh6lX4HXbr0b4ChwU??G{@% zKDY+?Xlrc?>G5pN-TClUZn&N-mp^tiW3;H2?3SE=tT62^lHsgJ<)Q@o{1%MOrTNPH zYmYZwOm~*&vHgIO-Mrf682RYvsDDn&pLYB63^if>D@>)qRyXr^E+^E{=@-0R`824j zo3E5t(gST|o17r*Cd>|_`k!{pgXTNTr9ZnK(6Kk{DjbpFLyM5$m zYI}iVEbCox-H$!3-P88xV^QNxs@FhyC@bR6EoqsfCryud2|>Vq#qM-Czwy`cmwi8Z zgFueAm+^NMkQjvCL?giD;qk9)FYdF{kpC$C4 zZs1OV3VzMYOn!}Xt8cyoOl!|U6~i#Ig*QaI=xATr4U42u5G}XKnU*>vP4E?6j9m<8 zi%cBwTGMO78m7b2X+J;X5*tRr#b8csx}6y|x^2UqcoIgdY1e>nsV{>?H3!4mSoiN` z9EAXaj_y@VLPqJn&6G6s>)C-0d9Yrc>-7C5vz;Cl*rkHk*_HX(d*f*7b+V`+V4W6O zw3c{Q(sHh*UOBf|JtJv|9%-;{US0TeRnIR?E=!2eC)MPB;H^%%rk0wpWRpXY{h=vY zH>cFOgRs9LM?z!WZDiDH_x*q!jm?Pc=Pox8p5+B)R*CT_i)eUOqe3GMj6LY3Z#|^pORwP{(+ia%@{5b^|A=Y}cPXeb%y!Whw95K( z?8ylL`pD4>n8&Y(nfU$P%_q8Ae@m%OiYsV6^sQX9%faq@yk#t*b?(T?x?>l0R2Fq1 z5|!zWi)J(Mz@rx;hc>nOk}(7Rya%V}+i`pmyUzQYdws*T&2o6a37ua9ke{5?G1m2! zACLp`Oonrgg5)xQp{yO6M}?g4tv7O*0==HNjc45RnsrK0gwf8+sh8JE>L{p%mS)aF zTNa--CuiI3`L1ZQ-$qEMYFtH7XGQDBg1|QbE5IhitP(75LG5qHKCZatTr&+Jt0{(f zxMis;232CE$?DZndP?*A)t#C(=gaT@@~2NHh!;P?Pe?AuXNV4uv50Sq$oRS{(bp4rb{tba3xz5)Q|R$acf?k{Gy_I^hhVg3&A!z=ywEr;eXM(9@1Z+eq^8|kh_stSoU@#cco;R%^firSY(O`lFzcby9ZgfJC z;!Up6y#wh(k@v-EB*mL6qQi{Npk54EgNne9bSF8L~~2mb>`{t=s~^_em7HIHmMPBc!Nzhk;QUuew9G<>aisamN2fB z+acs8UizC*XA+4Pci}_d9$2t6lzmN>dYOimu#k!;V>%WKM~%9VHm6(iN#Wr#`bn?? zl!`GOLHfE*G*XI*{+&w&tBtO6XxBo)n2}viIBQR2cw1y>+qrg7+iXGx2rCCnUo1@8 zjGIt93e+GdO~d2e7wv2KctQEye>dmX0aS+lDt7+L6-$NBv@R9K8Y70$l-im4 zhFx6?NG~TJfe)xG08Zb&#<4u?@syxO0W_-I%lSs~C-2oSgONr3!lk8(5p~%YPM%@w zn6Esohs(cSD5#BKy(K5?ZMqcORCz^ACplkfQh}(r-e3D)x&EyFcO1S{T`MQUx6m1} zH!DB!si^aLpL;g8s~Ju|R6$$ETwXUszS15K<1z)wT!96X<*~ke+>(Y5a^#ySlQg0= zgs}!$;sh$NSjznd_5rz)QSJ@M%-QN>M`NalQtf#FXKX*4B) zoI~q`cFb3EzC<1z0$k?MSqa(5h@hS0{)^J)QKzkFB)e-pugAnfls{?jmPm}xo2deg zK-&9*4q@DCqkYv>P9NTAby<8e8fvzscNWpW>E=sV(ra0n4Gmot94Yfnh*n%$B_FJ~ z_nd966F$jz6hwW@)}=r}615Yw=N_@V4a1nm8VAqi(pa?N3xJ5(pOe>4_=@dni|ytk z(yuGxa&vU%vJlG>bc0k4u;!Qn{GmJR zH+_vPlGFgii89v{LXw(L-;(QS7=Ac_@1_4e3ST;H4D}6#uf6&l;?4Pb>fhgUk@MZC zI~7(W zZhkgfa3%aYKTh-rY5CT4YpJMAN{*rKN-LUtWvFbOpLrk)uvoXsIk=b%UK30oxaeh< zkmJ|5Nq}CvM%ln{a)~2%SwDARX9-e@`b&y9>6=>IYEDP@;afO%IrhF4t}OtbM7Z3h z+kdxTOl^@H_OAo`wM<&NlD+VPQwelm(}GEQwVvW)feP<8%f!6fC}s^M)1ORPa6i~1 z%p{IBRtPMnpD|wB+TC@$KF=Xp)OF$7yn)THw`>@r)?vrJy(U0uS7ypT5)I@BGcURP z-Gj5io+#OimBx{2>~6ky`Co2|#lC0`XdM&4X)5_lMnOIrxaPvaFPp?U;P8*5HL`Q*NBl^=a4O6$aenneVP?@zC3O3U zxE>TdFR#VMU&!2#Dhb2H+!2xyXGgg!g9_5Vm*zB>5I0Xd4!6|86xFQ?)zr{;38Ox+fTe$svEU?@XiKmayp?l3pnMbxKYw zX|pnKwn56Fm!)+3Zz?z9yP&iztjz3ma><{^X|4;DP$*~XyS)Jj7RBQsd;Q79-FL`- zq3WaTk;dPES%FJLwDicX0?S12YsB)b!$n@vVl?jE-JHw`7@23F zbRR>L7Q)1*qbc>uWE$hILZTCf;yP3fLQUm@L$`w_4L-F9SPpNM*9)}1(QpNQwz#Sa z#F||;Nk;jm&U#-}{;zR#8~wdWewG3!@x{V7*s*rQLH=0dmr5&RXzTMv_2V8v0_Feb z0tAcMkTOE2LNc*>^^(2`N3@%BL3!wK8?(g%^^l7_B0eY>XM__1r*LTZ|F0|w^}0K~ z;>&>}9>>0eT2>Vi$6H!jjO@qW)JOjQg=nC#hQr$a*uo($WL3}^V*n$SOT|aTq1Qiw zrgCrMr-@nFZ}Cqnq#m*KNr3>bC6nMHW>TRZZsNXJo|tm+rn^ z{`KyS^+cC+P9^_l%up^;-eAy)*!#PA8DXQ&v!M-n|KuUdv%>&(=8TL-ut(3h5R+MP z8nj#lmhXM?!NKZJ(N7+1c45 z+x?4|tHB$W|L`k+hmT3}!khOsSl|_fs%$*cjWc;DG5zSI6q$NvD5T7wVok^U@336) zg-SnCQl1`+xK~4aUElSk*0-VrI0%b3g+p=U`WkRoD~a;fm2EV^VFPP3%;qGh_gi7g zTh6!La`xC5Y)RwJy1bDte^PweZy)`YLR`B807@jQIS#PX#}pNmWC5tX8jNK6@p4hllY zq1f6rMOt3kb+{PSk)o2-bqx}{@;B=t=887uUj_p7 zf$wv848g{1;Q6qh58TT#s30;(%?5;+Gi#ZYf-Y+Qlec%I%CN{{`1a07=wK(8wC_}w z+`6_NI#rsKpdq~!Q>VcwUAPV?nA+^2C8JJvet%NyQ3?qSIqb3xWuL@lSqueLz>^&J zz3zcC6ZxqgOo)SYA-eesC^tJsk&g9kqZ+T%x>P2^O^sZpEGy3;s&X+AcB)^_nJ``S zcMwsqml*$McG=7`{b8)>BZ%T)No25@(M**z>3XkWHSL4Wi-4-A1IY_{R^t*~GsV%} z>Wjv3K?ChABp<=o!?Z`NPg0^tE#l;0(`)wT;DQws{9!p80mzoTzp1HNq;edUnVwZA z7}yrtuf;QN6-N#YR)2b6!}|l|vZm>5l2BKujR_k`U=eWUa-j^KRN&O;K+G!gsq{p3 zle-MGDxr?o1;nI6j2PP=MxRkQGyt#LXhKF6?PAg3Sa$EaLex#Dq=qImTI1m>ug9Pa zTI*JdikJ|7rjOd|2-J|{#zprjdz4BOv2fA9^klK1^JuBDc*D!iS+cx9W)%CNr*rs2 z@qki-181n~0yn`|;&jxBYt_$8{!L(@Vy%(hO)mu@t(Zv$xM*E{rl2n19tWa`VY+In zuhGJ4{xH|Cr7J>_%olOKgqFm;iL~gbex0i-<4$y^gfoI*Mi$>3_2_9zj2}#j%9zwg zH1wQp2dmqjqdDG)q#H6$fZjVB#PbMGCRKj*LHI+;t-8>VEQ9QHS^XQRy-2aZxM>;X zh^4;2o4|>E2_s9hMY==#j^&4;1_ zrwTWEI=Kh$XsNd<({92yHeUpHO@F%ByZpmr`c5;~@9kP*xM0-Fwm+&q&*MLg*-ZUn zlqd#b80z16=o^I5_&o1fL%-j74I2A}NwgI9F-w4-LqYepZddCzVb;4SeD6KCq^ylW zf=?P8%tZ^!e3_<&x4!p_Ho1+q&2@G2X2LCr;F_M|HTGld3MR*oTL(`|m+^xN0DPO9 zfE;Gc!o>1D^x8{-Cx0)>7Hk-l_*N(0r|fv-tyXHY@P9ZlR-wj($!R3Ll%{s*rFX1* zADH5q2vy~I5Tf4VxhvCD#7DjvOmdGpnBG0^Q9w1?hQ)`oF9#^>nFoh#idaooCNFOf z4GySnMAnY2-7iYbubN)Jd)MqcDc0*5Ytp2iy)rJX+iD8Aw|?Bl%NUt=MAQrTu?($TT|JWG>Dm+r|Lr_CMGTmN8?BFBqeRRzeFB-CuTqL zbg%Ep&0QSwUPj`R)G$-egj}8>{gfBaKuzz2uGeQg(WQg9@l1A!va}-WvpzvJUN}uRNu~)EJPxUmC$BLmAbYmZ#8u^-8BA6Gjt; z_TIJW&6Z%!=Mbo9{e2K9<3@}#rS9Xeu%WU-U++#b-$Hjsxap?~U)FX7SCd__%$ci? zKEt8l_n^?pu%IeIMNdd6#JVzG)924%`gulEu|!K?h)wH){ z=d0;j()$K^PT#yKCOdxf6BD{R%i{9iJ)Mq8lK$aBUcqfm0SNe7V}4%W!?*o5ua#^< z0&#prU8iT^U2ShiR8ThM@#L(#h4f7+An+}e_Q5~ykN`u<8%{USz(RzGx0UWn&EGCP zCiFcy3=Whd-{TLxI{FktH$nvKzV&}cfuY-iGR@P1`dju_k>6W6?xPz-~*%l*0Oym7H8-ElIv1zW~ zX$E`}-C|QhC!8>CfHJj-u6uvX9n|{$WZ_e-o>oQWMH}S7o0=61Pcaoj#Iu`94}J(b z7u+Gu$qc0pkR2J#-aS-+sU)kd`MljIVGOf%F+2#|B8>LL&94r&O&njVn&>{>>FbaA zXyq6d{Kh-Dyz#FPe7<>^6dAY?D7j{0sHe*F7q6hZrZ`Kqsi+`(41n{ll`36T_Oa0N zmo3X=F?oBBb)d`gLzdql#yQ(Ln0LVQ^~L%Qae?sUW0Izb-lHWa=QlNJqGPDsk)NFH zokRMWB`l@i+ZoVzZk{fix7e!y%880UD%g1@$79*G1;GCvxTQAe4tNa<)cvOuZ2nw~ zLCheieS)twad0EOC&8UW`dOC+?o-tydPzVbasv<+ksu>kj`+3ewEG?b#u9%%nyhv7 z9c-x`^___tI?p#k+5YTy-WlAQ*a)q>Px6GkT<8GlM?^FNiTG^sK+P4T3w?{6P$Vd? z+)K2o^({V8adS#JA>xnw`;o-OhGO^I#!sFIzBG0<+%3q&jEDt-Zd*H7C?&U|#abWn z#QSlpM;tYUMD1)Iy-=+kuyJwSzFHN$sZnmy`JKXViuM{$@Qk1Ef5gvK` z!m63~siu|@>dsxm$y_OERSRn}tMRnhsAm6-^ZJEcX3ZE9H0+m%RzMK1x z%!CO#+kEeJ75LDm{0C1GgO;^pRLALn?AtF8x*-oLKV2 zvTTg{16IcLuEqq^Laxg~YvzYg(85K0%dy8H)U8Y>o^7C}#(ep3%|-RbeZIOY?HoEG zVI9~{7n_SsYOYETpwI@=`P=jNJ80vnongfZd|p_JOC-C98`{)8zF-nhjClmtoFDO- znk>L$i8xuU5>2`fqP_eqx{%!n!^e;k z3)77P(irL3n_Q$Cy7Sw@+S60k$CZlt7S=zP|26nstj8AHuvsG;wc;mJIfEhp{yV$`F1i(}aF{b?1W*--VOlz4Ru+sNbCTk*@?JsrCxHCxrTlEtCio%Is4 z`6CV%@dKhxkTDKI-$`xp!s>MWwBmSa%K8_%T;Fe3A!P~rInNYd#r~1VO4)RFxbC)> zyW4r;tAgBJsj>?yd`|(WZILXfd3#-f^;?y+S7*Y3$y4%|PyoXEex9)u`gui_NY$7$6p7Qfk-$_ZkNr4iMI z+u1L!r(bN^yVW9nEWyDwd=0ku?Eiy*RQu)r*Z=)&{lQvu%0A~Vx2mo$=kxKeWqmoK z4HlftER|f#8dMG`GCV?aURXD|L6NoiPwys+D1u&UX9~)^XJt zVs&~<4&5b80KUJ6M6}lC;e4}#2PN~vyrx82I%TXzEXg(g$#=)`nMRKP68x-Q2hNhG zweN!1Q24^7B2DRXw5$DmGrA?-Z^ZnO_@f~K zHGS;V-fWOJ7VWNNII)Wlmi)Gsk0hsWdC&b)2>%u-&!C*%Rq$rpuS@Zh?_H`Zf7|DI zjlAcMYl?YJq&A+=_R3v%2xrt3pt=EU;ZPs0VZPv^Wn0u*_Km7`VDOF>xWI6dvV+ls0gFM;!P6G+%Z~Nm+x`8kL1-oz|L_1tcfHG6EGaw80YE3V)R99?_E7gdi>J621}64T%ZJW}Zuv7BrI)b1KA&-64wg)oPj5#ykc z)2+ol^NxuMtR|Zvn6?8N?$6?sD6m%lBqk1|e|&sm7= zf(V`I=o2G$Y~me`{8+2}o+wM{P>D^V_%oj94L7mFXc~ypGPD_Aa#O3Vs)ExiY;)F^ zsv}4bNct?CTx@KTKG1(*ARY9|KagnoGpusJpVQ-m=!oo6TMS;uGl=-SChS=(EIk+)T=r4CS;gWM-RHXMC||fZjwT!-4m^qh4jUnsLMLd>oth;}j1FLqCtlh} zPmsRP0&ZwXwJ_N?)(q~GctH?rY?jTC2Gm*jnIK(f0pLxB!XM-pT@W)UTI0zCu!FiQPm-4oleEM4Vmhc?})<<~R#z&-JR*6{6?^ zmCH27p^grk+A)Rh)Ux01zHTS+MVteNvm*_m4i78 zLCu^wzS)&S?>sk90TnNQ5iGk^mFDKQ+wxWoS(fmK^J@lP% z_n>c8T{UVu{DO@2#gR`))qTeC8eM_nnw*kSZ{x&=fw6@@F^-4X%_UHJaNo##;^F`( zsc!Z}%txl)GivIXf&g(b-WQovPc(9`P=!lwDy`s=r#s|#Mu1ThEkOY+duhAM9Z5L- z^Lw9NFb!|n@i2|Xw#=~gp3Phx9zgy0^LKjD_1JWe##*+{-DkKk3>hOs8vD#>!?PZg%)Aj~l5UtS_Oy_i+ODCE( zwlEsWi<2lX*x&g@JdKj}9+%IrT6VV9jJAX&CCub$9ZsqMfGp7rN~t54g<$pF+}-k< zEiSgd+;LPsiQta;>TW&6@{$H2nAgZe1?)+5m{@|SP!M+mo`dd-{f!Iq(f&3_3@`2A z>;YLU_=D)W<7ajUSRe*H_Vzji8rh4gJ<9{O`nsE{!N_jYOG3iu;1blc9kCDLZHd`) zK+G^&+VJ3(8bAT-T@(%MZ`b2kFxmw#>;e6A^rF40jZiSwlk;0nSAgz@)xXb|Jv87L zT}W3jA(~$k&Rao5V|yZZHZyyA2#hVKk@ABA>^BJ-(4&V+*=+Pw8Fk`xFf@%Yn_J)C za0$N1@QD53;B-Fq3y%#)DNU-|)GVcL)O19x0Yh;>aNv$0T^;q``r`1sZl%$cS3TPB zDI1Gsw!w|juHC2khvMFwisP#etZJiJ@;lzDhR_FJS3DHBUdy6XsUP3ac`)w1Rs}?B zjiWJMXAP#BDo$e|_Epn8HkDEb*BdP!+a@xszuW0uWu8AGSA0JIt;WePxQ=E9RO&3; zdRU~(1gm^hUx?4dy3DR zb@aW7Z-ga*qPP+(I|sW7J1kI*OK;$q@HHC+id7u$UTtB$_g&I?N<9ApC*1c|?nv>X`u3 zorg;?iK0nEh-l)^8vM3g}=DGh{}^3H!_qXbQmXuQ4z#6SNTN6Au} zES5L-4Nh03b{Bf;h+(lgHwj6+C2%zZEOP79Ch{w<0lW_>IBwQAtx&+>P!*`d1pf)M zUBHOU#_xRSfz_lbwcMu{F9@2DaImJOzb?cG9%_oC2Q_K~iY#j5yJTa#A~iB;bL_s_ z-4p~Ievm?I>`LF55uH~5>N^#e+Z?56*r(>3g+{=CKYh1 zaK4gADW1~mrW{w|61A=s6}i98fevnt;?9>5FGt1|eCZkmJHxmi34@rfqD>N+9{Eia zeami7>*zl3S^Q1t+bHuAW;hK(e>Hj$LD{IE(15X5Fa$o zdi1|ow%D$riq=j;dKArj4hF8m5&(J3yosb8n4!$;awW0< zo`4=wKwAjYqpM-Xqh0)SZMCDsx8{M|b1#_hXxUbNvJ74>DQw=(RwPu8Kb)3hc_v*5 zXOl(UWEM!NNz`#-){HiKk_S&>%>NZ+nLcKDc#qJUsR;rLFfS_rZZFZ4i^;TTX;aGH zpn0qmuke23EMsKD0XqJCvtoq!eNze`VIuq;5g}BtyII)x;@Yf2V8Z8yCPvSYmdCC1NVFO2-D%+ia}Hk!gBnH8Et5tP%2I_;XKaG(Htn9mi(UNpPlVjRmDj1 zTqyoVvzZ3-qh4GACs&Hk)w~xVVeQlmepOfD$zFh<`uRiO!U#|{+Qh*;qs!`!&WCr^xY^?s+;Gv!$RkOuivqrHZeAPx&i8M?!s^&=;!M``RX+2FA~q zvZV$phxM~*3!z^v7=wd@_eIog!n;?oUh*A(|JIHNaav_XYg0XV=1l3X6Rm~%kO3;qV@=*ueJA>lE(dellQeju zIS9yG{XGs{oH{fpMnzNVL~m&WbpvFJg?xa1kz>8m`>v_*KtGjn|6lsn(RX#lYQ5N$ z$+8cDip7_E;S1l8JXHAH*wWsr{K5<3*2RRCe0fg7(5Thn`r)$FgB9CEyMUdW3eNU< za5a?w!joC*2L(U(GYw2!x#$n)ira!TzjA2&YMP(({+JZsn8v8Dx*p9MxR(z= z2tRcMR5ID3!&zJ%;|$vG4ANQL3GT2x{BycHVWy(CFuoAH{H2kazjwfKiB*SL-=iiA zPC4}Kl@Mh~L*CegGC-$4!ME%q+K!vtifE-QAARKvJv%mK7;Q zBD)*g8&2|_%jlv?&Vr-_4}{h&iIukt_q*vOW=Rgyye;Jj4P6x_VlKO*zOVtdVrFZL zcRt9M%f37>EwG|I!)$c%V)?&V)}->mVe>LA(rgX zoT*BRM3MEc>@2JiISPekdNfSTy9=}C<~_XTRV#jt>bg`;G+77{Gicx=sE(Sn;lOR}OG0mI1F4de??*3>ap75&sm=x7CAxIA_eyAg}QIlam679N^ z6Pyn2mRzgh#U!Zm@N3d4-lh~Ok;1X(z{4el$}+A$bD5h;c-vpcD=8oN@8hM5b*_T+ z@lcQj>vbo}0Xbn!_uG^1+$>on_+PIUF13(|i{qC@?N$97UJ9=MDYOq=b+zfqcutjJ zeVq?=wqOC}FK=1KLhDtw_LusT*B19VlE*~62tN#+Vl$+&C`@H!N-01d284DuXC_vs zkz5o*bKADURc46q`=D4j*Y1%~-Kv2iGzFhClioz{<2Fqv||t(_T!VLojf+8+PpdIj2zn$aNIpPZ9fn3yoIAb=;d+%5QO13uC^_J!@eS{{)C3mcp zcc71NVJbKtO+>x3$Q0&rosqMz$=|Pb_mK3t12=iJu}?BtJUh6IzRoqA^=#-wT4p;; zA)(~?HEw@nleXs}IR2ZwDcUf~+7!d<0P0yr8iglqmB-R5M(f@!V}ndL-z+8_o+2+l z^~y9nqf{yh)ujEUk4mT;Q>9EyjZ1RY02IwO)Q=rb%Mu?i@9zZ4bI0T2OCdt>Hx#D- zKNmoz9P~A@&?zlTp}rrg$&$zr`z@%s`_D+v*@}JlN$q7o;DJNu%y zZfuTiBHrXPM)=E4RvDL7Kh361A>}A;t7>K)S1K`U_X3%^vc0?g3`2SA^&FIrvoH{(?|&TV^?#FE+L`%7H|%j zF@K#^X{rTf>F0%$sw z0V-@6G^<4v0@|kj$fcEwV}%>Dp+#mn!X*6+7J=dQQQ^;uBP|<#5#95}&2MV8e7(_? zCP^kU0mAf-uHv6MjJz2`YCa_#{FG%$8qLW;#V`%YSq!|RJ$@VxzFi=3_s3Li&+WX~ zuNjxUu@Xjh(9o26OS^%VCCev zy5FpZR1S}0)mcuvPu1FwmVT@lwE&Uns^+JLD;2Usp@y-f1b*?0Zx~HE%(m(~_dJ); zaKhsGg+bTfqWv|hr;n`$WX+S0cS(rpTqIst}MslIu5D6&m^C7`OA? z%In;%J`*TN99T*Oeq@&mN2q)m(2RNeTw(KYFkZd7=*6}|Eyt&uM2F|f8fKy(XSXr^ z8&H~eIV32ocR(PCh8CdZAE&8b?iLqCi58E_F?xz!BRr?7P0X=49MVVMr`1{Qk>S50 z0?)R&;dFlSuW_^!%TeKMm85_B2D@|?)$-hGN8^F3^5UmGEfYE3&j+HwlWO6>{Z=-I z#l+2ljTg1gZz68E1F8~^`44)*v>X@T=v8!5GC@ydT>cYHs^{{0y&AvLr&U|~`atRS zsHlT7aG71fHW1za^${HKSIt7Tb11pThlWM$Zg_RsiOT z=mAji&}b=B8IOSeQs4KUl(Y#xJGDg{tiAYEBihfH8!Tla^iI_751~_5dXRx>_92|R zTho}X|02WbV$W^$0Hcn^jHHr^lu^H#$S&j?A;(QkzxJWD$<*VMTC9A}Y0uCtHXj)C zjY0MF<@y|3Q}Skt-_WSn=JeOIFO%ukglp|N$O70BwBf!wi$e;M=>ap!5*c@A?0VyN z!eWG(@cEl>x7fd`x^rG-hGk$-Fx*dov&mXunDU*5A->P+#a__y1CI2(s+Eme<<-s! zeK#s49k;+aD6R2b5Ij@P&?olJhZ*{U;^JcBYVpOoIBaCwhkK)n?Cp6nQ=AHc=;q{odmv2-NpZHZwnq~3cl#asMvPyF z)m3HzX)G=$%{fvg+_#adcG=BLv0)#o52myW zrfg%SDjryd;>)S8?P5C>>Sq_ouFWFu@jUp@#{iSc)oO@+@mQ{Q`AtLl@}cP@wU7}1 zDIi)#JM6^0kp|tYgP}qualm*nkP3LGuj8Mu;}j zg|MKeTT`|B9RJuv9W}YnVbq*#2nis%{6v>m2$5&xI5J-0q1g*60T@pQSxT+dCQ?y? zS8i)Nu)6W<$bDkT9x^=rs`(eu`FC?*J|z-PSD4A{?_H zH4N#W-$F?w@RcJ4=n7DB*)LPFcC9w5er=t6X*q6HdUB^kKPE$5iZioyyp@QVb!$8i z2@zrm&PclZ!R#@cTeQGO0%3c7thQ%z@vTB#eGLn%As~>V{(m(c1PJc5S6KKrV}UNO z?MN!jN4Zw*#{Z$|%EO_2!!;UZB+HOUm>Hk-r^ON>jHQ~9br_^e*>yGF`@YY1KhJ$XHQSi8Hz&-~c`-`-ulUZo|048AxFmwg@OC^6~^2f)Q#&tjscn!xU9*zHQHvN4(Ggmn;uLtKhc02<2@InKY-G4oB0gYPaiv!j$@c(4d3Zu1U#fsytJ>^Sv)Gn zmsYZD_y1lTcoi_@HBc=ZhvWYEk}Zdcqx!3jU(Q9Y+99DNQ zeg#8Jb;!p=PeVYh3-=|nA$4i%0e_|LtVrx}BA?;S3PysL*HS7zdhJQ5m+yuZ3?}gU z1<2aDDGlP)b><))GzZNBDqclC&1>!cfD397N@YC#dm?E;cS7PkxVgxVN|G43pAed{`#BsNZ$Q=_;32P2(L=P{R0_=dZ~k z>xRP37`cxnNqIlZ{x|#9e8+JXs&HL*Y+NU7smXN~%Ns|;3D(HZ$a2NYub9+fJ$+z> z6UVz*GHTBgcxkyX1E=&@W0TuW0=sfE!8RlPe|%ohtWFu|@~J6^M{uMt5H!1JZFVt- zB-B}&;GWmSXidfki-X0_`+a{dhRmc&sCPqiTa5Tc?G0d;UDPs)<(s6;MFoFc9*~ zC@R${GS$*$0+VZv&++o{?E!Ot4SYgV%gSDbJjXutJ9`rIaUxDCZQ*Xd;?WZh2$@wi z4gZ$fd_c8(Yh9sCeFDr8A>bX4noB8oDzk%)pGYJNn}g7AU8L`?-80_b*Pr8UFM2t= z$p-Ni#I=foNcalOMBzmg$b7j{CC;4vp!&k|z@V%9->kue;ao#R5@#8j;y0~i>otWd z7%R|sTC|DX>Y>?ynYZXN|JetDQiWd|>Zb2CpAY1URGokM>1FB%CqF-b@<%OF{jqya zip&$Ri16Azf+r?isy;znPw|`O%y#Hrt=;5Uj!VGTbZiXoZFY?7?G&@&bVqwKvF+t= zz*BnJT66X3iuImfYs<@4+c4Uuwc_baOE6~0DI*x%Ux+!gVuLyWI!j^FNHatpqYC>DeodsJJa&Ewr{=FtIrDylez}xiW z(CG6gfrS3g{=Df6UIn7{>uV7<6F3f-6CdQ0qTITA-j>eMk8cyUodce69v-~a=cBWp zN|`K4b)Pn1uV)5vakzr#Qpbiv^ZI_HdTZb4=9?X&h(VDlf`)oY0OjlMlG5_nAoUo@ zJmRGZ9yx{&-e<2Tw8gq&fJ<1C$L==P$y^d9$!Y)!|F$V{qrcQx>y`Ff!jMNK8gO+Tjc7P>8gKMEE+iZI_9^B!13lVN~6o-ocvV?d-}w2GE?k%AgVO2 zK1xD~t%u~nvDG5F`gHWwK}Kj$RcPy>&$!=(Cy~}X*oiB`Jfz*KVF<#GgA@#V54h7k zlxS+U&A~xyEn%PBgjN<`qKd@YwuulDiNG^qR%{%u&7a$w6sa>rT(axsI#_Qv_TMMF z5ne->KTKdh_Ve6+bFU$8oXh;`2tKVq;GI(vpA(4_IcdrDFjJto^(C*qt)$vv04|f{S&6|AH!d zI;%c)m}Pp+A;Jvd8~2RbjXgLSN+;5S*syLvLNw`-~W8y91}KJ4L_PZ!KVtl`zq znu}w7jV^+(bZqsivWUO-G7SHjQs#NDo6rw;)eiE;Zq8TzvUXCs_s*d&wqBOMCnLIR z>SaEM!=316jFI)64G9l!?eyX_tTV}Z|3Y>^q;k1!D+$YrL2(87l!x*_+Rn2_;IGrsn~N$3IgIx zs6GTT^A7+J(?EU?`WYKsfsiNqs-`(zvuX2Bk^77}uI($7fRY4Te}X(|*V)z*(30EV zdyA6l2mS@4CI7lx9-KiaZSF{foTP?yOgwOsMq>FEq^dQw-PO7?7`nMLS~FR<8^ukgX5>c#G*Vn%jtBx+Labm;3^FVFr*QbUYWv@^7@d+`749zp60uFzd1V+^<67|j ztCbi_W=u@jqgF8KJ{ZV*`_kEo_i|R6sGH3_e}kqv;YGXhG)_;Y4BGQo^=S`JMf0&Q z*YY}?02eVGFRtQ=>6Mjv+~FFn*KJvW8y~QrNC~ug8VzS#EzHU_CdBx%W~M~Sbxwqz zN9GeA1opOw#<$PDI-t>3{{CF~lk;?nT8iivb;%8e6=*abl-E^J3CDR7`5^nQtZ3L zL1KF1i2=0>Y5QnNmvf7ow=T?c-EV4In!=Y@6=c;D?vkWg1M(B~kC8wG%-g75p(aUQ z$Oj*^z2r^xaHg%op|R(m$M}L<&(sv!rI2!PkOTZUyjI+fcM9Ki=CiV`M(E%2x4rG& zaR?q#RF@T>Ns{F-i!4N&SbyV5q?9?Kq>jaa_oUO@X4O%9bkmBarbm@fx5Fv3!F=g~ zv6?8QrZ=jK88e5@pPxmoRJ_9-$O?)9R9|F`8Fs< zuWLcm3;yQM|CiElzAhpZ;^Fm@m z;VHJ$Yz~gaS%Uc`{0x!4X<4U7ojI8*?ynVmKm-+tl1xv=+DwP2bF>$Rdie|vZL_@Y zBdo-H)Q2O%(&>D6Jpi_%Cyw*^^)6N2=@*?2c)R4pAlG*Grl)}$&D_tMI8I7@)Q^sI zJXXIcM)brK3UN{oD!W#iTnF|FjI`MmtFpphqVpx%U|?J4c9E`G?+yE~t1T{#TRmMf zn(nW@2;XKNy)K_3&2H*hV4oq{jn?bcg+-dVv+~KNy-=p_>~T%Mh{wBY@x)3?xqR-h za9%gyJsXtQE8Aw$x$%6-u=Vfu(KWa3G;;31xvcU2o7C%%j$O9zU8c6++uNhL_D+i4 z)O_RA4k%n^7SvG{Lwce}eFVvB9XV!?bni4*ddTctx;HJ>2oRj)=e>zQX8E(;1UJzG z5l9x9yYBXlc1=q?E&GdJHIq%*N5g2Y(${?9o`yrm^^uNtL|F-tOsM?AIH+qfVt5+!+CX0atQ8jVz*`Qw?h8U3Ee%#>7!XNwcz@Yuvmt}@r$IX>^O^me+ zCF_R~kEb34Uky^f?^B?8o$dHLkw^(kg%PH)^0D1ldtjThvge}5O4_)-$-Wp|BC6Nd zjRb1TycWh5iF?C%(g>Y$mDSB&7hgT}9v{%0C=eW0O5twPWt&rcPo9m*m4Kr_Zny;8 zRb%Ht!yXkZi2A^*@kGJb6U3vKXnHqU_$c5llXdhsv~{Ja?T5679@!y(7JDX7)2pDl zs^UK|($}yR4!TtpyeJ6;D7aq1g_Kqgz3A}ST6B>yvp~$Dd%iwqcHTHp;eM;CZTVw? z6SPdJPglmy;Ij_LTaTi#POr{$pIxz9DqJc9G1~_0DHV19kkqIf@gk?roJ$Y=__+ey zw1&*r<-0beBmD2!xcRw*lk_7KbOlpY(y2GjDaS4iX%g59V(!{M(PdirT#p1MJC74$ zU|V-@vy(TfL@T>~rHA=6OnQaAJgAzN5)=eqF&Pqto(m!|Ie-E{1bL)mWlB$<*Oit) z<-I}mO6MMz{^l297##+_3)|z2sV??#@^ObG4V+++bGr(SuH zS`>f;m=6=c9I968qa<0V-J5$aD^x}1_a36`E_ZjtDMtW zA~*i!N68po6#|<)GxYU)ZES3hYSF65;sgF^#e-o`cw39~;uMKR+FF6PH0g0xTAoy4 zMP1Pis3JjbyKA@Qdh6eYy-~N#_F3vNHS{HTo1u@9IEVbi{zu+Rc0%Itnme-n{(ixm zdCgZfDwiH9()v-L8vEpN2ii4>Iql2I zbD0w<=N`VX5DAmt3$61QJ9MbH+;-RXwYAN4Kc%Vkn@e23Gq%r@^ z5?&)LFO9`#fUK(bZ=c6Tbi1#&ABuFv;O`}%Jpur2Cc4&;vj`T6OUqarvru= z@6sap(7jx%+tIDUDeLvk0>_;{C9$b#p$im{!9z^25+@jlRaD>WwY5C=^pN&*B6xG* z`NdBL99=|DZC+4u$I4xo={W6i3^+fi!3;2isR&V_uaQ=8M7y{8^kTg81o?SXMek-) zNMl;XNs}N!OnpJEwQ|$M=5F82(Tvnxe%@34h^4sTWXnQj?8Qlu_%5;afZxCKHDah+ zSH|Sp6S3@fGZK}itlUX#px23Fop3_JjI5OW&@)+GhY?L4dzHX7KH9_|2nOYcWKIts z=3!5+b&q4?#y-3{h8Jt!3R5i~>uKm=;9H`SIM|U{u0BX8M+!2gc6b5GEETI8sFC5h zopw=E(V{Eo#gb)`XS@W#^Ig4R?2D*N^;$g4DM^-M$l))EeP2&F)H8Mm{5Q*;6<$}r z2!F>KK|o!p;eR?i=e(ZkSx|8(=wV^-D;)c5C0&8WU6>@gWh=ZFSTR5Pxk~RCqO|cb zXpEdR1hYeh!7xbAi%PrlZv;oMGZ2?So1FWvVb5Xx39=&H5FCTjmy-R5!K&4;RCA*j z^kv0Raf5%p&e0F&-)5%%)Y5bzNfUz7&2A$uNqMYF|GSD$d;-iQnd$fgd|%b0t?q3j zXXVBC2sxN$H5ea=SBNN*=PHIB4c268XXKXf2RlbCLiefj?X2~&l&W@*pDV*F_SP~w z7JML}l)td|?St2d^3mZDLFyo!FHg&C9 zQYQNs5Hi74Nm=|8HeFfY!&NgLG$V0Y8*UkX&1ou*P3?UiC`I67B-5gdmib(3Ky4A; zIn)WFACz4^`61X>*BC4X>zu%ao&25h0$@=8C+&}YXvlrmPtGZ5QnN*9K<)nVic_7~ zuSjrG$gHg{?t{Z%*ym6+_nx66J|1K7w<2O>w&le!*K5a`CN-CiTQ8NF(W>-&jzn<} zrW_|9*)06E8K4d%rQs}jkF&rhV_h0(?9x(PM+4;VM##;uFDqMTnt!zCRx0w+c zAK+0JzO6vi)rBFXF}O_)X!0i|@h%AvK6Gi4;N$x=vJm>(`e^6#4@2HLyONa1w8a3`kVozZ`>97aO7kj&y^#fb zB4PYD0$*@qDZ#2)O+VikMBm|R$e6nlXU2q)CAp;N*^9Gl!4Zn!z^Z^nU5d5nLcmgF>wePFMF;3UZR@>A`D zt{9thp}}W2pNVdj8+l0H7$v7~PJ14sFy;SzeziqN>*HDcWez#fdLD z^#j5;5GMIGEaE7b#p%0h7@U~a6(|D1Y~{X0tzsC34dJPFbt=LX_UgMuf)H&8JEo-i zd}-ZKZ&j8|KWS)X?Kf@n%KoqBfl%Z7zKg7F1mm4!lIR~mzA>gb9vi`8$PQ>ZQTz-0 z7WxjpiH2#?vkwizVs+%ro@Whj{Cc?E9!mpa;D(LxbE8md_kf$&?<5_p|tB z(V?SKz6HZ`+Q2~P3%tb{zvd}M=dddojr8UR4^|22XJfO-t*vB9Fcqp1r%Z6A&vOlVlmys}+c;yf1%iDE-9uG;Z~ zL_u9e8ty~e)}UgoRjbM?jj*~##!*O^Zp?Y(5=vxRq$fWHhzJL(RiH(z)QXY)Mr3cd zZ|A#LVBmLq#(!unso?<05XumxXk+-sQrJ|vMe2T0-Lq2&2i)Xwk`jlGk>4JzJ)Tpt z*;7+cJ$6cUv50?I@st53lbGA7jfK5SvvOZ`WUd|4X0&WRFg0o&+ns2r)NW9%fpo~0 z0FxUs%=L*V@1MapAhR5!WB8Cp&HbSJ4W4g)qp^?4W|rtru_fe>^-eehcb|5n>ph{E zH$q0X64>Qwo>q=5_8!luXBO+AQ^8@|=TNO^edn8Xn0jadS3@FBFqk3y&ew z00W`Q3rdY=w^HY1#ru&7WGpE>QwyPsgreKjFu=ED>XkcnG$Y*#Ls0^AG!Ur`OA_WO zWqT~cDmf7s6`4+i`84Ke+LqD>B|I1D`|HgEg%t@3CEi1aX<&nAub+abnxfqwv zPppoP6A^+Z;0#uWZNFfZDHQRI z>BL&_PG}TnG*TK0=eRN}jCoJTThdPrw5+bYx^Ih!=6u}tgVQ?y&XLCW|IY$gnHR!V zeJdir1l_EM?X}mDI}R#)y}h>p=*KNiv+` zw20cq&4r~b+6|-Dof0oW$=FD4H0p!5#5)X5?s;UOL@ot}QlJrVKfY^uc4i!WYrVQn z-$)Fcp+A(l0P=`$oXHTiVO$tVyUxKPUOPO|+L*PmZo8j@yJoIbSn7KvEAG=79PgL5 z3do;w+e>#|E!j`l#x~=h%Th{YEW!Ob7$yvcqKAdSp?5PZtz_ea3G+2F^j5B(o7dzhNx*ixD zoXQn9E%JzMB1Mk_H|hYJ1u~T08i`gOxz^a&{vII002f#;vc?=|_+oftWleI=lsekw zdS2Q!zUK?W=%Xbghq3+8SlB4>Tak&++xLf(|>{U-2V83VkUx~AwG1z@>duWUKa*~3(?{}`tt9t?$OWx1!ic#wEC!DP((5a` zmV{cBof-yo9!hkZ!zdM&+-Tw=( zQ+K65$#T0ULl_n(F%=%yLrVK%FnUqnr^O>kO#P0l#iR31Nrn>AF&>{Put_8-Ry(#u z^|(N+46j7aa@DZ3)#W@pRckLLT92Bu>W_Qq8NQkk<2tt`#Mp>5xNW59qcy#we99~{ z24iG)nR9e5krZ7Ns+>DK%uxL? zH+Pw!t)iW_+rF|GJx&!)+`WayAwEC2lG*aVMs!Hoo~~v2;4oZ8=^U<1qz>hc8objF zsV}gu96ytBu=PFdQd(7sF;2n|s4AfVQEc_%KpHo>ZSlt17U61bdC##VDL4-0$fjRd zlAj{@FuAik?-VwVID!#5Zb%q=>&a_VJL&7z^2*8b6)ntLL^&|}oZg<_77GG-)cQVGPa&?8SEe32Bwa(Ph z_EZUgaa%9MSk|@BloUVSYE!w0ZIu?(N4|iSs1R;aD4v6<%9>xcp9f{fS^8C+)9T9) zVCG>VcOnTgBoY%TkU_y)kZ;7me0m61jGs&iO*?h6H|YWBT&jw@Pk0r=D14>SS~# z=iE0kEfB&ag#)B&A|x_Ad;*25Lg!zDv3=L3wbKq37}*V`8mj7W6DhHeBcdx~ns26- z1(Xd!GFf8WmJYt{{Ggd=?tPe%;4w)0^-1Jqr_oA=c`=S^FqC#~yJD(+bIT21qrNfy z9*r;zCxu6H=W*v!n67E7ERpnCKnBo|g1d*zu?t_&@Aag$%e0tpnmuqRWWO5$Nqm{F zF_uL(xSrtL6?)G+?#rpQk(1A}T}m#<-qBu+>po8M2uxrinX7wNKPC=x`(Afn@l~BH zjRI)g7!-_#tDtzS-+<|0Sb>E`jjf)B;I2b<4`O!!3m-8CECvA9DNT=4kpW| z3N`-o`^?%6Ep&U(AV_|wLLioT*C*e z+w_AsXSIHe5%sU>Oi{2}$V7*_Axwx_pf^^ZNcps7T#-r5G+e;MYSqMOf-PZYB$55g zDYN8$8HD#NiDb5brBN-Ei(7-HB|_w_yE>)Ca`!%e`1zz~leKwh4Hzu1*p8k{RW3OE zliK=tOjuibR;gPtl&96TqH@ZUl>OOz#+YmTjKvvc+T6sA7)TqDT=z4IY<@mrr2|)? zdk({men%jh0oM{EGmtG}D)vRta_npxAY0{T#iL7r@#vo`o0>A!G2$E^O?q#srZ*x9 ziqzARst08U4azR)JE74sr?K1RX1$%$R*A3swv!?m*Pa9ZU^yMAT%!wPzk4j5IV<{x zZL_*fQ@Y0T)VV)!W}LP*j=BDH<@gc;ACj)|CAeP1xAbEA^>3t1)6*fJVyz@2PYQq+ zvJ=`ku|82JWXf5>hEPD+^9$+K$Y6#QncFFWjBCdlv&a;ZO9BL41Yi+xS<(a0I!V9O zaml{@>_o@g&t&Rt<67T>@qd3Z%c6LE>0%(G*dOV3mb=urub(|N(wwd#hh$~f6L+dU z&rOn^mx})ZCbhcQ4t%wX42{R-cm$L8ol#V72P}$IJ0{{1bo6KCVCK;OLC6&ib9kv2 zLH}Uw!_R_fs;9(e)oE`?yA$3gX#2+s`RGSwSaNM7R%S^Jl4ry8~*I4zYJ(a@rFO#n4 zht^x7(JUl1>qO=U@hCW1kA>o9&9yc6gEqIdx$&ewFy{tB3zaWuj0?G1H>OmFXJ}4@ zz7=^`dhuCeBzePB+H~7PKuPN-?CA_WEI`?2bu+-;{nT|OGz#V|f%ry*OG9%N?8SK6 zEMXWN>%?);pFsIp?GfGn$&EqaN=09oRD>Q1hMqlhF#KGyjGnWip4l>apl}zh-7j?+ zM|$?Ww!Uib<@8he2`7X!fha;bFaa8vg)Gi)SDWc z_h>V(0@c;7N|BAL;HF_swBX>7s=m?>&n!pWeDJMD%PS@SvgGHNJ|YdAqc-L^7kNr-!m8WC?~X_6sVb6q?EA|M8R07Q6L{6~|uKL&pPD+&iN$4VDb0^8uE$S1eR8t}v?b~ID6EBr@f5w%5LB*K-8Y33pK6rYji|f8o>h@I;zXZ29RBn?g?rSD9g@WQIp@B*B ze|X0~r}G*ttVrQ8;o2BX_-AWuvYwui`+0)H6W!S}!>v~X>Malcew$Xtr`r1C*}Kr; zPUzk?{p6QKG6hx^j&!{KIPrl*DVz{NsqWLp^2^quvA8_RZ~D3@l8lO1h5E?W%6jUY zW|N1tXx#W|wvAGtQS`n=&eg_=01wCG5xL(UTn#yN8W}iTyiYoXuBEv7%@oi1?w$=Z z{c!2ahmcPr!!`Npv}J$l-io)QzWuRMEN&7=2oEpOdo0GRH)M6}PaMC^Kfj5Tb|XDQ z-2wn+Z3Huc_rH`{oBKfwi>{VyTt<(4d-duTX0rg&G82xq>OA@uThulFT)~;=01bBy z4rfs@O@60<(8kH}Mv#_8Zcw;!5ir%%y|vFPt_Rg&L&Ka`4fB2?a)zIr8YF#madD0d9GBr`@TA<3l^uNyx2Y*Uehz0LY8 z|6Qj+xdlTDK{k?J(hI-Q=1}io#!p|rub&Ag#l(nar0PM-KnCvA_sA$G2>PYUH(?r1 zT>2ykz%h}+fzeO&8an}fnA77cFlQ1$cXs|j*@97F9`=wn-gSRGXHmA^gc8Z8Lxc!` zdcG5IGay|H4h~N7)ZfOPlZ25_2nYeZE*6glH-M+!5-6nrBvP&U_|%uC0Kc74hJ~wW zrpPB(@Zq0aaE3pu8t;#KN8xl_K1ePeD5H#?+K?OW z7JIVTY*#ZGON~v>G=3%~O<;kry>gR)>gk0qGg;|E?70d7^%8b8lCVnAW8-lKyXWEL z+(_=zfPr`^aiu2_UwT5!h*Nr^2$(edhmxd$a|dvqNbVR}fSL46&t44*#++OVi)jP! z02Y;IMk1lkSwMAPPF`p2^N7FNtX4nYB^uQ@zjlcuQNh#<$5(&NzS8HvT`vCte7kGe z{9;3%1?C>wnCSAde>ADDp{rQA_(agFuEvLa5SF#(2mETPWxXgY3G4BM%nl?Z2@0FJ zVFDzfkqVe_Crs_@EYP=0K~FJrM(u3`g#~U-2ObnR3@`q+Kd?+*wkiAy;8>rfVYI>D z-rczhQz(Tiz@e%><(CXhPZ_{K--`Q&&UaM9m@-SCK=L{numaPU>Xt`qZbxNjgeo+y z*nDLhv8J%cdEMbg94 zrl)X>%CU@1S9;f&@HT||6gLmEY)fLKF&|MEd&&N1=BZJ*okV{=gTFj>}bbaiR6Zr0^)WODnNq#e@O~i zb)PInohl}qjSRydzOL`ON}ZhFE)IL*R#{Anm0xnLC>^0cw^aHfGrX5EPyP00Y+Y+F zz)igSZuBMhe7_4_EukTS{tDJJ^Y0USN?=+TFdV}qfnOjizF!-O%cX#CJG0xcl$k_A z>z(6(MLl!^aHh+VX#jxd=7WfH7l&mGT*&4GMH*hTA8wZo03@^1cemYGCdWAP%y2C_ zlj?GC0$!Vi=SdKHi5#iW5CB@+hazLHm8+4|`7(YGHmEA7Ll3C5dD z53`rYM6GRK6^DrR!g!{3A6yOH-ObH8nhkp{%7SB++Fm%ZXa1MN#qXGdQO>NLdaNjA ztfE1G5d~MsI!(Ov-=sH35~T#d{Sds~OPUawI?K&Q3eQTiN+$iz0-Xb#pa+G+7jUCc zW38d&x{Nmh>xbE-dUJ;LN&dJMv>OhrkiyAey@JAmhi-Pdrb7t^myY1h7pyv;3}k?& z&Z(>2s}0nlkTohhlPm8puHWzK4$3wkouFGSH8;48e)8wREPeSpJUptLS~gr{!_g>d zO_mXF41#kE=0_L1ZvNTX@o&`l{rjW1n^_G8NwDPbW@09_foJlsmjgk) zjI?5Y@3GlOP;K;AvqdrhUgK9Lo>^IV%RB^>*adShoS!w-VkUU0_2}T!%E27Y{d~QI z`_HS*6T!YAZ&!jmQ1JZGpR1uqe=`a~w=9lKuIlA89-wEo7q0)UGb_A(Wivnz6y`b% z`1ovks0RZs_`zb>>u)1YK|AtM^(8Cs8FbNg&%=s7gRl{+rMzvht}??lZzD-#q5p}BR@mHm z)%843?tABk)QehVN7`kV+NG-dEp*MMG!fxP0pDe0D3evDxNv)|$i&=81{(>U{5=zV z>l@SV?~%~34keb3m+K*AMgI{(Q8GDXFl$cNxlvo{A>Yv9tiGwiPIzl~}4!?uGC zzx}vgw#|8Fwmi+U=y3YSR>2JmTIg>@{>Ns7`WDS|8eu*o1EFi88QB}t5$OxRLN()* z&mZ0I^L^~?tNCbZwSM@7qu771GI=mOXqI!J=>Ep^v7}o-DI6R*ZS%ifLE=6MfNKa7 zL8jcTNLeOaq6~B)y-rUXS*viSKX4;@+qJ^8^DAJ6ks*XbYgQ?L@+m+XL{G+12#g4L z`*mAsuy8SM^JmGY@H7UwSCFsxb7|G+#r!vQZu9-&pzY5Y(fHyJb@vK`=3}GB;V11$ zry5?Ytu4}eHeHL)^xa3A@|nl-k!pvCjj9GqsnOB*i;KIiURi@JN)}$xPEt-I=8qw`txM* zF)K#|0S_?i;NGRF*riO|QYeuDwCw?!C0dUK{Oj@_slW~421uDq>}+KBm6n6u?alc6 z^;Vj620Qz=4)gu#A3xip&n&GOVoKqU?R?gL{@m)xNDMvfIWoRlmqm6`eLItFh(C6e z^L?ynNb#-cRZy0BEu>mpuZt8|&I&gpdGbrdL}~K@5f87E#;nLdhq#laSzV;&tmP=2 z07tQRq_CK|W7xE*F3Ovu-;Fp~J#Ik!fwj2Puqeu+gR}XpEOQ^6VC?R>D9i%z)Q&OH z-~bI1Zmu^gf_g3jVqYDr{3Uuge{{iEP}qY8&4bWgJZW{4ZzO2E{4ADX4O_*Gec`;U zZXL3B*GJ%w{YQh-*pDEk*OJ%slh>cEzJ=Y*osFb2F|HSn^;xhHFv*k-{cBee(H={& zaQ=FWb|U1pa7(K0NjUVmEFjJ-OVQI~SexT8=e?8R?<@B|Y!S@E8Z}?7+T3ql&VE-> zCmV1B2JiMyBo<9ug6PXl7793M#*2_%)1T!6_b~BcWiC>lF_P?gNh9TULcSliHrG@4 z=a$ZwIY*xqkCItzR{pbWe{eYJq_%PsEVj#)Q!8htgTWo9tvgFc#KT)y5(Rc+JEQ&3 zYc$j}T~Bhsv6|7<=QKQ1cIWQr1ribv|IG1ro1(`8;l==9D*zGUsfR&@M*?8>boxDk z|1026MzV3E!}+e^jT*nLH(4-C}Zf5C@-d?lQBmuBuO>?baZB)~R zmDF9Af00Ob`(Zg`^;}IV4&aZ&i6^A#5Ot*uhs>5jG~T{_83T>M|{4*vE*OMWXbo6&{cRfBT#5ar9vz^aH>&v9&KB$amK&i;mPWyUylX3O^h ziK5r0eUjgj2?OsWL{btUB{ELOL9--yUV09YZf(3FUqQK_*TVjm)iSPlXLToV@8^fI z1x71fKKPQ&+imUh$*XxA0SZm{w=MR7IV^$vbOmIiEtCZve6T<ri@@Kx@v?XGA!o z`+mEYnVc(3oZAbX@M<_nduW-|c-0h#wkJ?hvISv~tVt&Xlv}3TKVE#T@^pIDgIT7k zy-OUH+-wvq4)y?7C?s{7vY8ZZo#8fM!zgo8%gc=trjaP0{M|=`j9y~w5s1?raV*%F z6&dd$00L%>;oZ%3DPGeFkSlOB92_bsVbLoa^eT>zFCHGBYxTME%Wf3hKrL-2Q3~l+ zy)P6knK5p$;*Yz3V)|JUn!`?lRs8gYPSXQ`aU@99hXs6=0M{u-Ie%JV_5G3Y`MX)+u&5nUFveE391@Pd_^(CKBbTthg$BAFM( zdUav-ANatUWH2BRz*0`fvC|h+Qty>gR_NVIZIIY_qY-X;{c!kkR_X3~k zus)gOR-%8(jz8Hjusx$b8S-UxFJYT}6HY_{CfE0VwTL#iw(hU)bblH}7>x}k-8#!Q zS%PEjRl&g}Q8-Ytpu@l!DM>nIC39)M90qC3$T9w#ouzx7BzGNG;S&`Z%3us=cK^+p zo_?`CV;!oXFkapxu)vwe{X&8HDat%QWlKVuRfp;HM~r95sxF>Q=E3`;GfBz@jpsm@T)|f${w$U`JaT#ZWa7vwJGU_jj1N~i9}y* zUDOJ1E@cD=|3vtN2{lWJBgpsE!C)9pjif$Ue>eLwKcU$M;)MBs7D21g_ z$T-5}G= zG(@R0r%7{Jqe(Mtaw9~PS(N)MH!n)QjB?L(>X9)HhyY}WqPWu_5Gagbj>?Rc`DD?KJm4#7N!>ZY6}PA$cd;_Y06{aD(+d6H5fs@wyBOd*Sr= zc#Ytu(BZHz%bXf%rMW2+3x;LlK*0>-O^Em5w-1xipQ7LI$&>R6YZAQ1Eib>4FlwQy z9r+&w|CqgCR8U%|20dg#vrN!YO2kzP%gDciGaZue-@wI=&i9ufQ$|#Z`95M`xyLc+ z(!Ajc9fiG}mX@ZS5&F@CkXsr}YI{X#o=T|XJQYk&r}^jXVcyzW>ZFSy_>Gr^9@tdx) z3`^!4tktfANNqTf>ogp$6P-*>!{mkoGLka2Tr$ks6J#Oi4wH7STdxXLGE(07%7fy8 zRkiNQSM&zzErjHvKmk>f#{kVD1DZc-A=8v~gz3>BJsTV}qsU&IO zo9?NyU4ANCbKBeV_v1BhzkM@jnRFIPpEOIptICaGQe!3&16X`T%y5QR7rfr_qOyF( zF!%u#bxgj$YTh%M`Lm~Q|7UP|sVU6D)+TsyHLOVkTqaH3Be8?fWTTpEl7W(0!VB&s zAR92LiC8sP>E0$Q8q4_OaeXQg68Bj1BHFloacCL}iO;YSxo$I5giduy2SsWoAap+*LQ{#_R=hd4oXzFcTnr~W~ zTlHvdny9?^>${hTtZSNN(qA_)^-4d#EayG7R!7%+NB4fE&_+fXkW)At%K+Rls4*gV|&8{E4* zl)jKn%+b_1+F0B-nZe`bnVIq9_~?t3;E_{X4QvzgDrzD(m*&cr_O0$Naxo1uer-18|q7&8@3{vNrDura>}vWZM!5H%LVkUPi-nb)_B z;=5K4pDZ<^NA>;8E-ipqk>LcIgYU*7@`YDk5 zd|1Fnlp%-E{N#=*qJo|>Ay zGbZttIxGm#s(kYOw*`(tV7iY6UCY4ONEH%j2i#!X`a^VF;kSsdPX_5=d|`uf@!c4$ z*}F_{eY5pU?yR#UGxI&(6<=nAuy&g+>6?rgB zbyRiBniA!MB^qsWbj`xrqO0)^HQeKpvE;&}&xN;dOK8sX%0`{aPiWT7lIKkgSW2Lm zRQL8m14FJJ{al@oU02It^#&F=r`Ry*ceQcc#)*dMqI{rv#ja-43qAOLd0obUZ79m> zaSA)wt^U?Q<2T#I$Y!F3{uLH(J7&C(hpJ@T!&kghuhyXmr0W$B{J)K*$sbM7H_kKM1FDhvjF+#dq(!7 zzW$BJusAoo}G*^2(*SkV^jLj3XL?4oq}U>8Ip^KkAt97|az+ zz9IhW%I=BrhV_;GALV5QsqWWdxt+gWn_z}~y89l51PA(Wdu&%fY^cPO@39~jN}!Ej z|K{biw9=YY51)B^GZH5kN|L!<+P1Y}Q85q_Y6O5!(6f72hk#7T$g260qqPO-A%cy# z(fEw16@MSYB-q2N)2-Xv@f!GTP}sL7o`JVDrCUZkHFS6vnTMA2Xd^t#M;n@O9F?SXIcV%cDlyn)sKRkDdhR`B=W|3&kt~y zm&r`AsaLpu>JoLYa$0o1X6B%H<54wg<6}|S%zAood0+5sys>(8H!f@A%M#%-|#+| zl`wL`<*MaT!pZ6w7Vs1Gl{2a-Bnct5BSThF3B4jtZr3rE`FmuJF*QS!@6xc_Ew|XE zxq8~RvEhMmKvoQsF*`>?pXuogW;a-J z&P$)`x(JfvEkyX6N2D5tO>^^6v9g8Ao=5Y!37>RW%AcC&KPLYk%A9zYYf{`3ok6YC zJh#7MdmyTg@0NdxtD*Ll*=YK3qzAK@*`gqAU08CWU@#Np5+FUX;&1fI!a2M?zP;bLK_4Hr)=-Za1u3 zUaGtK%Mf3N^EY~+!>#&09K%;fjp#VVjn02aisY@Mj)HJU35MyiYm6$jhncLu{W@tb zaJ#A{bbnng)F-4S?0+h{4rsRCH!MNTqPC)9l`1vTmYAVMX=@c#YE>nu`7>f~rKPA* zQY$rTl^QiFX4NPWTGR|JC03%O#LoZzb52fjPHxV<-@V`azR&x7&-;GVNUF-@^xBq5 z+q#sKI5P`NWz}u&@8BOSI^vw_m2wbaAc<=*H;dtlRVSvTr(6Vm{kvVYQ8n5bM!5+f zk|)=S@P+}#h7ZdPfwua)Te~O9RKk5j^VG^T6jkx#_9-x>i@s@h$hzRppT3`>62u0A zE0^9i)}psIPBxa#^i%yw-|cnl?o5#H+y)$MAj;heq=glMuce>IfIUzY$r~)`%(7f? zc%KQ6IrG=^8M*SqyMLMowoEy-AJv$JbQ^JZM-9aNg}g($RLSL$8z$oKbDOqdtD<2t zzl^veC3No=_=7K+v{kU06&EpMgfRe6ftfvIfbs&YWL|zNiyJe5$EyH`KltLUNp`%H%cI&*8fjDY zq<3z3AwpejW=3Af=vL&u8+Cno*f3&|5CE~dxp%hzcWI_R`MmAEvs5I0L{L3o@ z%9nxj&iF1qnz{${>))CDtzT|qUoPtQ<#LdrMeOwo*o9>9b1BH3OLgWq2_%4PzYk_~ zyBW<|;crsIa9;S8)T1Sp{_*3KCP$Irps)BOz2I~nQ@Ri(td%dmGDUfip6?FCMz0N^ zU9an8^O#I9_|jJps#$TcKufCzC&Gs!!lnKe3KFFW`!5p5eY@|+Ww$F@)jo<5)PwMd z&Ty@b)<1!v*Z9hg-6k)>XA%j&50+LJR1ZtO?fPlodLa2?R#a)-xIzyrjAOw_RRCx% zi2lD~IEvl_RSm3qTKIpV-IZpMIX*oL)~t1Rwi!}S+dkdhAYP05FQSn~1}MX)qAox+ zJs-393D!i(Gvi_$+)U${Rp6zHGNZ97+o>BY!a$_;oMyPs6npviZIU;iFOT}8py8GI zuA|kJUB?BTLS)f`qm=p8%U(ysv>?UWYnS#S+E}bA>N~NW$E(X5jwfUv4eShZNUse^ zoFi|qw$E&z-Is18s$(B*>~4G~A^lT7Kw}Wpbo*#VoUiJ_1Rx5|zF&O#w_&4QjT{gm+axQ* zf$_WFluIwgf=tzn7~ZBR{}_Ok^Y>izn3kExk9ub`jj6Vdr>w|W1}4H3RZw5};IQLD6N2({IR5%0}Q z#mL$c%&R?uWSldZ+)(s#GC;tul)J$q!(z+I@K=g69-b9=X>n(=#PKOLFK_M?C5B)4 zAktK|RrvobmGy&`6&&K(FK9$}Hv`R|Efvigpw5yGgo4xxxVn@_hKrypiztJ%E{ls$ z+VRO|)Un5j<95;N$@sE9iZ0IV4>D(%Vo1!PK!)B=+AP{HcC;6s9Xis=VafUQ*(@>J zh$xMzJOX(!;|=*Uo;<7Y@ZrO$qM89)nQwJNfm&7hQs&ii_2XJH7H{4dx4evJVlkrQ z%ebz?#b61K@odXuD!cq!_a69$9LxR5gHpoBkZ{!^FeEC?`88+|}B z2qX|zAVST1day@%W)iFMonqHlGY=GwgEfs`DXU|gr$$I8Tl>NLq@zCj2o>m0X5$1i zO=ips%D&2?z5O2wMR7baB0`n;l1KL9`E1DD^3(xE4nv1MvF739$e#sf-a`QARLNyV zA0q|fRM)-w;_dmtI8VrnAydGKH@TbNGZP0csH)A*5cRCTVmXwSswj5)gW%)s^TtP~ zunS!~222T{CEpl>Qa~(o{GG_Lm$L+$p}2gyZ(m1Lwr2FC0F-XE z_ered4T-SIZawnJuc=D6rYJR=O%AM8Iz0J7;WE2JEKdJp7nI#70 z9}yB2rP05g!;2#}T zFc7!+3WS9v%ldUX!&>@nK>Bn?#{0=+@J%hp;|=e~|KJ}|jZ2=~%mRT~%-)Q-cM|Cv z04}A9)r^?$@fL_<0kN`rC}!a%o?#}Xsr7I7CZ1011Vx61wFift{83l*K$(z2FN?>s zrr%b0+M!kCQ|K@$a}3;Y(T~Q-I9N5VKTGo3x?j}EiP9z&)%A5pT=CNf#yhbF@n2v_ z?&0SQHJZ8qhncrlq}(BxAf`d10;&3ZbbPTTt)3kDw$5#L)xLE_iA}-m`!i)sy>-wnfw^e*n54JNNu@jxak&fzR;6%7sb8GH zeC6hjH!o3T;4;4X0&b#|bmxbR3wx#c^17eHhtst|8I(7_@i%tiMt3(o*@~*)YxfaW z?*P+K3Q~O!fU{Rt>QgfWIhi-YgOAJk&8&<>7zHybI>bGwDs#8AyX?d|I-<_r6)o$L zn$$IwfO(k|0Z+5|hmc~56xvIX`N<_;CdOt&g zM{{y?@{V<2^Z60S&Qz}|fd=%ttGv^Xtbm(Pd#iOiai=?1-U7fk!RqD)pVN1g5)Z99sLLc=Uy)s~8P1%C?j3-k+WuaSGJ zvJnt|ytBVIvf)G9-7afGS!Ls%zF*Az$bm|vH0Jnzj4AX-9Q<1!pCeMdQ95BJdFgsJ z7oE7(2{j;a66ZNUl2=N6oVgXG(d4mC9xIfRlY;BPudL6APAEUW*fu_cGg0ZMd&yl? zL;3F4VY4~jn^798ZUA~6Z-Pn$-gf#DmO?AHcby=3v4`)mHJ8oS zXCOuc-ri|Ngaty^P!ZmDm{KKejbe_ulP&(FkG!gzKGXOp(fDaRYk=YRA1vs`*c--G zwjZ>A2X+ z@bXTpnByi0JcC;TwysBw5`S+^8Z5F}~q$*taqjBB%j z55Of-&4>Qzf(HNrp`N-CPZqfBX(n09s;Tywk4MF=%>Q#;*1b=sgixn{g0#BG$$6UR zgIo+;r>40ksxBt7XRppAhk zW8%`h=1eKktWEFT?`?K}Cm6{?R_eF@c-n`hgt@-YaRu>&nqZ)TQlhNqvQm>K1c%jc zHOLR=L(!?7jt-9Dp}90*eW7n}glzXFzsBn4qwR_Qu@TQkgi3vY79w|74JqSA9jrC< z${;(84;Sl;UHn9_%y#YO7rdKr-5oj(y8EH|K{GV&2ifvG2KzA;JLTAsPdhpCDC}4> z#F;ddwAmd@E;kY%3J?c0=wt46pOQxs=W?c~XZwn@PUJs%5K<*9QoeJGT;}iwWt4c% z`e`u*}D-oX&Q57Q=IGA{bH;ZstX@*78*LZK`uesA&uDc~o z`*5qu7;=A%w2&*tNL@Y4JGAy{Ue=g@qi~4pq+eYU4cwAa-$QPqBgEuB0Z!+ZZ zpu-lsA8*ur3lU{=Md07)L!qBOyrXJrT$=Kkq7_cqlX|jex2e>`IrAnNab=^H2g#NV zUlhK{4tRIn+&sMyFD+k&xxKZp%<*%^{dwwrR2~aUbv(}OXZp2QES90r+@IW3aqH1g z;^DwCk=jCRnaUBHswdAHVMDj}TJds*uy0!nHTtcO%hfD4@0GJlQKprL@$QJSp?cxQ z@?JM#R0-+Px|FE%giV!*ga&`j_mZk0YysOq$o%Kq0ZN{Y&vYWGXXLI(e{`eFXNVD! zMQTG#6BZU3__+&vO^T(aZp)#o>u~jGVexEbQ6^oBxzRs=GjPX;&>`$IJU0g26fE(B zQvC_0(pHV-3O@p%w=9S5+=HB35111+XL~#qsOz*|uXfM%E&PEM*ob=XJ-De zJfsoXmPeLSsT$57X?y%EzA=uV^-{Tii+r8jRzMK<9Aa|L^tEuq@bK_tO1Q>Kc^GAU zuHC569mPq;hbmzwfgiV1h7q?Rs4`jV89s)r|E%HT?4D+T2#391;JBxpmfo*ivn&^-D&lL0n(w*@ zqwnh`4f1z0&tK1rjuVnEm9!MB%amwNDEZjA$L8PM}(b3H7OB8|v8+C@a6U zLY_wgp-*k>EwS%WEN}m4Hx5A1?!2PwbSrL5R;9dENZ|~C6Seq+d35J@#m$(T><_1! z)LB-5KBQIZVj5`?XHjC;Hc#5A@C@m7vU0zon8Il7wNXyhGB{PlrF3KRcoT45QTtO- zIo+D{&Ou_tP#KyEv29|`TvdGJLzTg=PBLd8yyt!mhrugj1WHC1)Kn*X0}UmtGwav6 z4@<=|2l435)A1Yq3|VYM4bL>lXZhnju5u`BkpZJD{Q(p*FeEtt(2{@W?)E*`n_bt) z0>b&j*+pMimEidjSR}rI0kS0*b|*Y+sH`fC+nDEvDBtY07m_wF*1x%--eC~^PmAb^ z&EN=mQ6tm}^I$}VIo0!vtda3g?}SJ*m(o1-yDBg-7ZG5$OKjHeG{I^WWTa*e;kr?; zu~uQ97|+4gbp8I5$u+L{Xd36k;KpakZ!i4}acN(Cd_3`SaM0k_bBP0sy<2myZ&Upl z*IQjmdAftRdTs^?7&+aMlIEA@tF{TmFFcFf{`+{dPU7;Y$di3 z3R*KdxF6aXNjWV%J=!O2c2dHNpowVzbQ$XGa_DZ4_$3WKG|uJm^m~y-1sl6&g{hq{ z+`RIWDwhb@4-r3l?+Q%Y-wnrqdZx6}skMMuO^b}eYd)v9^l1$fOKYJm$()Hr0(){q z9(P()qm9lsS{7-O+l!|%G!pIc=f&@XWkkwh)7c*R_~0QjvV?qBIqu0JgO99|1h?>n zH=9ayX7>BtN?AN?gKXgyxgi=qnYkLJ8?1u%R3$$WC_D?;L> zu{FF1&;G+(nkz=Jul#$0f&*)?=B4fypGAbhN?f)rXroM^U`dCiKf(S#H&5R;F#rAM zp`MN9t<+5z@}v!`q=>CnX~Zt`ZEV%DT6&Z{V0tWv_sb!`xx#Yl_PjsL7I1 zVy1sI`du5F>^3%9{Z4&HMasA4m;r1a%)l#k^mTm=F?0XABYqjV*qAO>#Y86ODEiR) zOiz{_PfoBOy982sgnjXjr~AiUCwU*wmUT`6ZT^vp_R~3^QKH7lCXtc|rTr%D*kNV~ z2(xF?=$Vz?dwx`d?pyKTfc9U(+xiA>Va}`hkmgsW8-%$d6(GSNMI8KIy*s*@2&#IG z4p|t!n-5c;8~!Ob(CC_auAU(Q0}Y}NiaTMjj2@F|GR6OOmOJBU$`E7xc?6E(@_hG{ z5hT}8ryw_5k&Lb0SvumzUM21XYj&MtE<=$;rjCf9hx_^dibWqLHgmc?`7mV%cC~zC{Wl4`8Pgrc1b@8$NBqwH^qN#!&)sg94 zLQ(X6cy>m)i(~!CJYLBa4 zV=sDL#+HkmPA}n%x*&hVdt!~#L7g$~`SqXt%uUf8;8?8PuvkU5I+W_*goq~8;9-*# zZ#C$yxWkt}x6COSpM-~#WexNnoUgtNqK4pS?6nWwQAh-8^5p^jc=Kalk-gK;9(L`_ z72zBDXS53VjaKX|8y7DB2~oO&dL`T_WN zxzIH-f9fQT4qI*Q!HC0=cimCd{wDQ|sr_Jc@!afYFX+JQoXYI`0`m?F!DHH&D4bDe z2VpwL>vJvJSDmKO>zZc-M;dvNc1DQWRXm&Y{wJ8`c}(iuqG2OZtH(Pn@W)N|F8I<2 zuq6g)0aOfpSihosK?p{Fe_q_2jxhI|(B3ghD7H3>K_F08xgLn(r3Z@eHMgO}S>r<` zVn`8_V-USKvkb>m)22VSj1Rw*Ygd}p_azsi_rY>)RPU>u@jJSt%-9z{&uK&MyoeAI z<11PMGlkR>tPG37`k>=t2bD`@h}DH^?~!|+H`skqRu~J=h3UIlF`yl11BJ1kX427> zB!_d3p`dtvl?nr=Cx{>cHA!2nu#4Ef8dd|^ZxHYnq{~0wfV}E9yg!fh) zT+DDdDE0COzD6=v01NtcZc>G4qCYXABg?ip`EM|ewvHrjpKg^qE<{1nT{d{4_U5)} z8>qz-8QPX(w(lP|QcIZ6sn3qx;VJGT2xu{z&msqBJi6YC+p=yyU}-j$v$2Fexbkwa z0So9SK^H#AGAPL1H}u+8?}U}v($NWU7~R&jEJTOVTVlWy1S@~IuC`@2&a#`lTM6z5 z#UISb?=$`&$_strrt*9DqQ@pkuKAcW!KIByvwq zTXa#Of?{4Bc0w6s5-ToY z{7zRv%HrG49x+0g>nX38T$8C!8k6s~RZH;en>Q|&2PK0?VOYBziF6KgPzRS3w+9@A z_au>V6aD3{SM)9$i9dM;63XV3_?o;QL#IQ)mh3dVblW#d z0KL(7umzP1aEDh$x3shnDWauI(oVec;v(=rWh?q$r;o#{pyM)pQm+$i?F+IfqoNDf|eP-;DkGlMn?xIKb1 z%Q+#)9baU*7@!YEa2+icA<9b&dkMjPSih2Q3RC~Hq>1JgVFX?yMQ>_d1OjE?E0!mnithA3ftZ%{>$6h zMAKTs5^M4ld3MCLyyiTJ`N#8wlei4dVKK|d*_8+B#=XhC$xIdS6u#b#nO%v=pS$05 zeq|0NGAXjGU3tcrOGOTKJX9w#&k3hM)I5c&?W*CoENYtT%-JfHqT8^4*tA~~3efrI zlUaLmdZw{#F>j5NzgrVi9{--BJUU((&N*e!w2>_gPuCGFqucu|pwz77{K+D2IgwBk zo==#~`Acu3tBBo{(H^aN8?}&Uq6zpTsF=2Ff;0AKpvgi~yf7%oDox65#i{LD`(Kfic{nns}X) zzU3U9Q!>^^N08SS|LCx#>+tMh*J+Q=p#bf_uq_iUx`;07%vn{W*22Ka^PGjv*k7Fo znL^bBwvpg|APk=ys_0F;=Bc1kdZAO&^yr7p`((?sfe_701c&DBX4UVvRVA329a}XnGSRy=Ea$@ zX&6ovlAbHa3(3b^2SS3ejwH@_nd=VGUj2G`Z1q^s*ofdVHcD zD)JO(z(myZgnQ20x*3$NX4?Nbb$IZ}iqmv&xTD2;SE6L+Nh~RJYObT~Jyw~CU=Jq% zj`UnBb+@>kSBy1j7QLLZv%i0!7!?`0yMN%lm_R-%88~dD9RQ8504JBJa~kFCNKVj- z3?^%%Xl-W~7dX4n?%^AaV<6yQzF+0&naykZgEEdwu9P4|Ko?$c0H);k%hrE7-by^F zLbS%-zplPz$*`&24gM|hV&H*Lt0wu}2wHntkLkeY(Hdi zF+d6dJtwBlD5cuk0a~^zLYy+vf23hT*ys4o zyG7B{voCr|KB-!?R*VWusO1Rw(e(-Kt;fMg2Art;8N#t9InFNvv?Qt)Bh|N1An7tp zA&1~<`s!?^_4z|QlRG0o`wJ1Yn^5GrSS;H;0x;tV-_h&4XYGr&6Fc5`&_WRv%#jKe zZXvKE7+8M~&&}xo=qaUfTT)Uipg`w%Z#(kzbX@0foHnqV*-?i?YzdQgy^gD_z|F=KT;Z^Z2=^QNG` z!O=y6Wh&Fl(kGHEiA{6GoW!iTn|xFjH6qqye~x^!qoYHMN=n%))zDf$93U!2osCoc z&tf|-q55;-Xo_$9s{qes|@aq3Z-tZ ztJolMPBRdAn0_`4X~SUfzF``07WX1wFwKxwa%am|l!&;b$xn*Z-Qad*#Gbt}-vd?ckDwesJn z-(j)j7WwyQ>e(qpAZi<#=D7cnQzzvicTkJoXhr*c4z-$cu-h6ox0?Tl%n_$<38l1C zo1x>)*h$pJVwCvKRlBg} zZ$zM69O^j1au~2&RGnQ41T=a-0ipwH@+z@YF4_Cghkor4{QU(SE^P}g<+pGSbq*DZ zVFLX}=W1uO@^}9D;xFE1f`(6X zh9KUPf8rRhAoZs9L7A(%Wv>8~It)OFhy}vrfkptF(Ie+AnlQo%T9Ls7o9{1#Xa?~{)Pi9@4L$g#9{i0jIK4bw|rJ%K;RAQ zQk-JFoL)Q5#xsd}NZJYhV;R1Y$;$h5rJPW6VtJ^-L-MXs>%3PVmigUQdocOIT=2H` zJQ@=P?pi=Hh%{MCWNv>gU( z1{FmYkvqC^E>t-_jI}xhL)MNVFyce#gA|o`zb324>Q!5J>#dZ>uriym<)XtBf0YwE z4*ml~Wa-Vx7CTGBWC8$njBxL4xz_spf{Iez=af7cW$PniVg49`1DTvJ^gbvRvO_w| z_v5N|#p7n|Z*>Q*h8$BC&;F`zZ$?o@X^GgV_NMzcdfDA@-m_^oCv{6t+JP+_RO0nL zYx4cO31z%ZE4?-sq)OWXX9eK#zVe;kh2-e=!Ml0N(7WJU=EF=_St$KKNxcE_t2WxW zVG_$)`q z;p?*Uw-(}QGH~DK03Pnpk==rwq30wiEmee|MiK?_vBRe4;Z{Axud58 zg_t1eL0&L0z+4oHf~L^0Mr?FF_BC|yT;J+2yPjA8giF7Lvwglms42r->{N-M6MC|d zqw`sZLZhrYHZE>-c4)2nRCbe#cgxn>mr+C#)b(dd_) zB@LqlR|BSb`#_tzK2z!f`UP7mQm4Q_2w0E#7b8SXz{3p6m}0M)d#Ypl4@layrH@@_ ztBEMuH`*cXIW6SEfWS*qAE?s2LBB8INjKOcnUH`mPzahbg$yQIp#*Y;ek~NhCSfK2 z+*_DQA;@!}?4>d<{AVf)j=c^0b$Q2)K!C{s*|PSYQ3B%;!v-9i!#To!Cm}pZAmQaw z-)-@x`y-rAigsA$m%v2;58}%nPaT#hf{Ok__JHdrl_0w=QPVDU9nH*f>HwCCqRfSE zYmW+5o_Bw!AHyOs^CrCK0P?#Io88tTz-MD2mNpRKz!d1Cs6IrFO>d)a9B_XPX~m8b z2A02fti1>C&|D_k_>`be%(_*gy)bn*Sg(v?yV0M+M$tJ0FY~L}rU1k8|62|S&Xtx3 zR?egOUiyCz!waD{-|n8cp-wG9#QZQt%&t*Fb2T1E3DFm{A3Y4Ejz*mxFP{AUS9C_9 zX`iKa*S@=atm`lVmFzW9)OCskX~OiEor{cT+n$^~)B!%NJ1W!zg%OfPr#Pn>NJ zp50-MQAsyng1fo+2V-JRcLDmmXx*>WRAjK9qZ|es=hHq%5g;l?9ulUCbRzdwPJV*8 zZ({@;Vjb8o&{z41%+7*mXN3%%xeFBn1S=Ip<*}JLLj>Qf700gD<9!F>{JT0^3n?W*u6=U9YKDXqz>WX?a8_k+~ zK{6TeKA`y#F|WbqzF}NaR$IagMfeule7hrooxIetG8%b`UF`Zu`&V>|YS97zj^@K$ zmKMvZPvmfrO*#nJPKbl5$aA9*(Zz3t`qmG_t)cV;s*DrcR(~>m8Xa1wk(=vv%2MAB zh~HS@-xub2D)L6=cyg0h5Z=99J?_)-rE0ML2JSW}tVq>727EpsmGhHAJGBzYvEkjG zmBgbsSra(sN3hraHsz1^DrwnS*zAD%hx{><``bRXqBijCxUcJI#k5EVM*Cgg6{Plr z-IDu1D!uP2^96Ni1bD$m`nt=HoQr-p<`oc)Z5D)hH> z06OSPz;3VL26;z2CZUph%F~@b;psnOJgyj*ci@CNMtz!yxMsV6m!{fmzIa*QifBcf z0`v!a=YJ{j5FB&m0LZ{ZZGr4f1-zx_3nW9M4BQ zxzL};HyU2~i^X$ME~H363|}Jq4kHUxdJp`YKCiHTHQzvxj8B}v;8UOtPxu(Fc$aLtfP}5Es7;&y*%s(}}0^e?x0-b$c0+#mHFn%bc zEy%IG*x9MoztcPqCARA<(oUy3%V=v|M*!Q<&22FHrDFt#rvJzCCWqsm7Uk-%_Lvs2 zpf00CtIZj24u4)7r>qb6u0u2*r2psZ(H+(mqfn-_q=~OcocDS+opWt|d3zKVcokk| zXX-1=SJXXC{vs!5QWWrX*-Tx1go6cYZ*IL9&zXqPJIqJlj2L=11<(pI=;DaTccF=t^|CO?TA zT6kveuQ_yCj3JJnpOblRK}Ol1xitb*=s)qNuOgb&W+hV{$|uc2(vr}#49iLJ<8?t} zBV0ty9exU6EQCN!rjR;pMR-O2>wSlyh%2)fVyv^WJ+~VN*b&3pdtVd%FX12QAq^Pi zAc$w*ln;6oKhdM~PX=g{IzNi|A%Ys?-ho2|j{;SjIu0$akmm=?F3(VCYbLJU4-fGF z^j_(`;bwc9Ug#HpdOd>E6@>j%*UQ=YcTBZE>-lVYe#rxCDPu2BdU}*Vk2zB)D9dIg znA!`3oCKsjGx(q7-+2NW^p95=iMM70^JK)ZHr6}G5_hcIb!Y}p7hWn7Cw|J=UHG8X zKCCaX7TUtSUSbEB>}!QmxP_S9me|o?gBbNMPHu1e~TuDM58MYc$z-) zEy-fQ*lIARJ&#;dY5xGjkLCohL-@ZuTat>4LW=FqT`qQv^+v-eMH{9B@*Njbd?46wE8!uo9Q6R!q* zft(@#(P{DR_DWakVvpH@%A9mahVCtZ2Zesrw*C%ki3p93_^5~lJAR@98U3i1-V&R6-K7oUGR@c{sBF*R;M5B(XAc3TKFfR{*si`rgg zVK!or%l>$0=6rgZ$Fu9;3E-Rqo7}F|bBg^RS(i8A literal 0 HcmV?d00001