From 7589133e24efe46c261bb9176dccc8f3de18bd83 Mon Sep 17 00:00:00 2001 From: Kiss Date: Wed, 8 Apr 2020 17:50:23 -0400 Subject: [PATCH 1/7] Inital GUI development. Still need to test with real data. --- srx_autosave/__init__.py | 181 +++++++++++------- srx_autosave/api.py | 96 +++++++++- srx_autosave/gui/5-ID_TopAlign.png | Bin 0 -> 53610 bytes srx_autosave/gui/main_form.ui | 292 +++++++++++++++++++++++++++++ 4 files changed, 494 insertions(+), 75 deletions(-) create mode 100644 srx_autosave/gui/5-ID_TopAlign.png create mode 100644 srx_autosave/gui/main_form.ui diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index 9bd924d..59163b6 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -1,8 +1,110 @@ # Import packages -import numpy as np -import time as ttime import os -import glob +import sys + +from api import * + +from PyQt5 import QtWidgets +from PyQt5 import uic +from PyQt5.QtCore import QThread, pyqtSignal +from PyQt5.QtWidgets import QFileDialog + + +class MainWindow(QtWidgets.QMainWindow): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + uic.loadUi("gui/main_form.ui", self) + + self.pushButton_currentid.released.connect(self.update_scanid) + self.pushButton_browse.released.connect(self.get_dir) + self.pushButton_start.released.connect(self.start_loop) + self.pushButton_stop.released.connect(self.stop_loop) + + def update_scanid(self): + self.lineEdit_startid.setProperty("text", str(get_current_scanid())) + return + + def get_dir(self): + dialog = QFileDialog() + dialog.setFileMode(QFileDialog.DirectoryOnly) + + folder = dialog.getExistingDirectory(self, 'Save Location') + if folder != "": + if folder[-1] != "/" and folder[-1] != "\\": + folder += os.sep + self.lineEdit_savelocation.setProperty("text", folder) + return + + def start_loop(self): + # Check for a thread running the main loop + try: + if self.th.isRunning is True: + return + except AttributeError: + self.th = Tloop(self) + + # Check the scan parameters + self.start_id = int(self.lineEdit_startid.text()) + self.wd = self.lineEdit_savelocation.text() + self.N = int(self.lineEdit_numscan.text()) + self.dt = int(self.lineEdit_delay.text()) + (self.start_id, self.wd, self.N, self.dt) = check_inputs(self.start_id, self.wd, self.N, self.dt) + print(self.start_id, self.wd, self.N, self.dt) + self.lineEdit_savelocation.setProperty("text", self.wd) + self.lineEdit_startid.setProperty("text", str(self.start_id)) + self.lineEdit_numscan.setProperty("text", str(self.N)) + self.lineEdit_delay.setProperty("text", str(self.dt)) + + # Change to the proper working directory + os.chdir(self.wd) + + # Start the thread + self.th.start() + return + + def stop_loop(self): + self.th.stop() + return + + def update_progress(self, x): + self.progressBar.setProperty("value", x) + return + + +class Tloop(QThread): + signal_update_progressBar = pyqtSignal(float) + DT = 0.01 # sleep time + + def __init__(self, form): + super(QThread, self).__init__() + self.form = form + self.isRunning = False + self.signal_update_progressBar.connect(self.form.update_progress) + + def __del__(self): + self.isRunning = False + self.wait() + + def stop(self): + self.__del__() + + def run(self): + self.isRunning = True + + try: + while self.isRunning: + xrf_loop(self.form.start_id, self.form.N) + loop_sleep(self.form.dt, gui=self) + except KeyboardInterrupt: + print("\n\nStopping SRX Autosave loop.") + pass + + +app = QtWidgets.QApplication(sys.argv) +window = MainWindow() +window.show() +app.exec_() # %% Main loop for XRF maps -> HDF5 @@ -33,82 +135,17 @@ def autosave_xrf(start_id, wd="", N=1000, dt=60): >>> autosave_xrf(1234, wd='/home/xf05id1/current_user_data/, N=1000, dt=60) """ - # Check the input parameters - if start_id < 0: - # Need to add 1 to scan ID - # Otherwise it will grab the current value (which would be from the previous user) - start_id = get_current_scanid() + 1 - print(f"Using startind scan ID: {start_id}") - if wd == "": - wd = os.getcwd() - print(f"Using current directory.\n{wd}") + (start_id, wd, N, dt) = check_inputs(start_id, wd, N, dt) os.chdir(wd) - if N < 1: - # Add logic later that if N < 1, then always use current scan ID - print("Warning: N changed to 100.") - N = 100 - if dt < 1: - print("Warning: dt changed to 1 second.") - dt = 1 print("--------------------------------------------------") - # Build the possible list of scan IDs and filenames - num = np.arange(start_id, start_id + N, 1) - # Maybe a function for this - # make_XRF_fn(scan_id) -> return 'scan2D_{scanid}.h5' - # filelist_h5 = ['scan2D_' + str(n) + '.h5' for n in num] - - # Enter the main loop - def mainloop(): - for i in range(N): - # Check if the scan ID exists - # We could make a function to check if current scan ID - # >= this value. Or same thing but return True/False - scanid = int(num[i]) - try: - h = db[scanid] - except Exception: - print(f"{scanid} does not exist!") - break - - # Output to command line that we are on a given scan - print(scanid, end="\t", flush=True) - print(h.start["plan_name"], end="\t", flush=True) # This might change - - # Check if fly scan - # Should be more generic, if XRF scan - if h.start["plan_name"] == "scan_and_fly": - # fname = filelist_h5[i] - # Check if the file noes not exist - # if not os.path.isfile(fname): - if not glob.glob(f"scan2D_{scanid}_*.h5") and not os.path.isfile( - f"scan2D_{scanid}.h5" - ): - # Check if the scan is done - try: - # db[scanid].stop['time'] - make_hdf(scanid, completed_scans_only=True) - except Exception: - pass - else: - print("XRF HDF5 already created.") - else: - print() - - print("\nSleeping for %d seconds...Press Ctrl-C to exit" % (dt), flush=True) - t0 = ttime.monotonic() - del_t = 0.0 - while del_t < dt: - print(" %02d seconds remaining..." % (dt - del_t), end="\r", flush=True) - ttime.sleep(0.5) - del_t = ttime.monotonic() - t0 - print("--------------------------------------------------") - try: while True: - mainloop() + xrf_loop(start_id, N) + loop_sleep(dt) + except KeyboardInterrupt: print("\n\nExiting SRX AutoSave.") pass diff --git a/srx_autosave/api.py b/srx_autosave/api.py index 301146e..4689a82 100644 --- a/srx_autosave/api.py +++ b/srx_autosave/api.py @@ -1,6 +1,8 @@ # Import packages import numpy as np import time as ttime +import os +import glob from databroker import Broker # from pyxrf.api import * @@ -11,12 +13,11 @@ Helper functions for SRX Autosave Andy Kiss ------------------------------------------------------------------------ """ # Register the data broker -db = Broker.named("srx") +db = Broker.named("temp") # ---------------------------------------------------------------------- @@ -75,7 +76,11 @@ def _get_current_scanid_db(): The current scan ID """ - return db[-1].start["scan_id"] + try: + x = db[-1].start["scan_id"] + except IndexError: + x = 1 + return x def _get_current_scanid_pv(): @@ -103,3 +108,88 @@ def _get_current_scanid_pv(): # # db.add_filter(user='Andy') # hdr = db(saf, cycle) # return hdr + + +def check_inputs(start_id, wd, N, dt): + # Check the input parameters + if start_id < 0: + # Need to add 1 to scan ID + # Otherwise it will grab the current value (which would be from the previous user) + start_id = get_current_scanid() + 1 + print(f"Using starting scan ID: {start_id}") + + if wd == "": + wd = os.getcwd() + print(f"Using current directory.\n{wd}") + + if N < 1: + # Add logic later that if N < 1, then always use current scan ID + print("Warning: N changed to 100.") + N = 100 + + if dt < 1: + print("Warning: dt changed to 1 second.") + dt = 1 + + return (start_id, wd, N, dt) + + +def xrf_loop(start_id, N): + num = np.arange(start_id, start_id + N, 1) + for i in range(N): + # Check if the scan ID exists + # We could make a function to check if current scan ID + # >= this value. Or same thing but return True/False + scanid = int(num[i]) + try: + h = db[scanid] + except Exception: + print(f"{scanid} does not exist!") + break + + # Output to command line that we are on a given scan + print(scanid, end="\t", flush=True) + print(h.start["plan_name"], end="\t", flush=True) # This might change + + # Check if fly scan + # Should be more generic, if XRF scan + if h.start["plan_name"] == "scan_and_fly": + # fname = filelist_h5[i] + # Check if the file noes not exist + # if not os.path.isfile(fname): + if not glob.glob(f"scan2D_{scanid}_*.h5") and not os.path.isfile( + f"scan2D_{scanid}.h5" + ): + # Check if the scan is done + try: + # db[scanid].stop['time'] + make_hdf(scanid, completed_scans_only=True) + except Exception: + pass + else: + print("XRF HDF5 already created.") + else: + print() + return + + +def loop_sleep(dt, gui=None): + if gui is not None: + DT = gui.DT + else: + DT = 0.5 + + print("\nSleeping for %d seconds...Press Ctrl-C to exit" % (dt), flush=True) + t0 = ttime.monotonic() + del_t = 0.0 + while del_t < dt: + print(" %02d seconds remaining..." % (dt - del_t), end="\r", flush=True) + if gui is not None: + gui.signal_update_progressBar.emit(100 * del_t / dt) + if gui.isRunning is False: + break + ttime.sleep(DT) + del_t = ttime.monotonic() - t0 + print("--------------------------------------------------") + return + diff --git a/srx_autosave/gui/5-ID_TopAlign.png b/srx_autosave/gui/5-ID_TopAlign.png new file mode 100644 index 0000000000000000000000000000000000000000..ae8b7a88a43ca007e9f7b66c68c6ec6d5a86528a GIT binary patch literal 53610 zcmeFZc{J7C`#-!58A4p(0adCWOqCp%BVE z9Yx4^GSAL$@8iC|pYM8p>v`69J?r_c_55+a*ShbH_j~W_eeG*}UDw{{Jk-k9w6!v7Ck;dToJ=uuaxioTR? zzk0rB(Mnj1TgnYG#w#7b0F2q@6l5!;~x4x=ChhQqmvcd~qf3Zo%VmahFV#7J4i!{Eo83=~gYU#aWzhm=V`V;s8LM zfr=}6HdO52t14rN0Y;AqvM`^jSQth=q<9|XW{uN%J6jxwqYBk- ze>2)W=Ey|~$a$g7&3xRVBmq4ZDl$pmyS5E}zGI{79BtS4T77<5G|{<}0VD^It0B$c z+>Uar;(R^F-(K^YuqDL)oG!B=R|i;%J$^iOt2jfdc)aiyCg$fGp7W$H#`s@oC`;r1 zYd_Fa-@|-zd7T$FhU(;;*kjoqrH}s7&y?TcFMzK&w)cvV*xN&KCRIFB7ysMMv!jO# z{shZ&WZ%)SH(o?xOCizMXgy5+JO>OAa}< z%vn4fdTDZ3@cRXxtpQMoFdD4B=r`uYS1f10(@}2yB z@hbfakKzlsz>5Iz@3E!lRn_$9?!5oU+iC!NaF30QNI55_dvcJK4k$s(#qtWe!vLGI z;>6@P`lag?p^}dj(4K}wkBQB+vMT^!-?Q0~mRa;ij{DSpAi)c58UXA-^W=fIZJLBO ze#isG-*2<;Z}iP{B`{@Ht&oJ1gAXKoJg@Xlz3uTGIR_UROAVY4@7<03X5tB@@6)YD z0&syCDsbMcWI;!r5?DIGrMzjYcH)o#wH1(Q?3Wog#A89T2NxqX932ctLny@mFbII& zJ>7H|AAu`Ugu;cF^b0L2;4HJpX(JPwf~sn!7+rC8c>rcod8qN8^?yjPe~<(U6|R!O zw+{dE=Fx#HvH}paN0Z^4{m+7DP@q!)W4y=oPq%q*rE$)?CQ5+e$sQL$7Te;7Q zZBFn2Dovs`cwak=Rd?Z*SXwB-$AfSM51Vc7a>7@~JjTmE8*mLhK@OS<9&k?l`SGwC;%@NaTK^WA~d+7*DQBf zVn0Y;*mH^X+@4|on8MW@7XTvBQl7dm-KIDIFgM2amolqn!dc0oh>*lD%NBpFB>@@l zchk*eFHlBM0oy$>fV{MwI#szNAX##6aUUKzwD^@mkv8s`^ACy-XQ+TQGKur&;VZ7e z*O@vVBn21v(8^tx?tY>G$vq~6Dz^gwW^T_woCBekD;**O8OnR>rRac+jFAGn3ww-^ z{2Ekw4kh@Q1Qd6dKqtV4m2F!I`|YlmCmkQza&Dl|S8jOo^mwAr$xU|NS2}XVrT$_k zR0(uY;dXyP8pn>|TiIo6>4ePv`B+%i-!5&Df1GJH_ddRJmiGt6knQ=ln|R;JkwH%z zRjrGXOZ0Dl5eNWt0kT(He_e`4+5zys=6v*}y=F_bS#I|H)WqEJr2fQ0Q(RKv{3{p( z@N8WQU(zS^y=Ta7jW6r-sj31!C!%67bjyxx={UNvCCxkqhw3Se-R)|@8>Ksb&9d`F z&D70~gtTW+l*u7Pd15(c_I>h`_Sxm!F%8MR?LXwdeFGoDITq7?`22?doDV;a6m)4h zUO{g4wS&f>rQM(Z1kZ-CiSnu~OlQUYk~5T70QQATAOo-@^>NMPXl}63;2{A4IhEUq z&&+pq2+Cgli57R#oXsY9W=C-3AcG0YJU*e#?en!b7}7_!e@U^(Qv-%?=kSNU(v!|5 zDsum|feYQ?U@tKDoU5W-oWqB}Stp~|_d&7F%s)IeJ8mT6Oa*w$chfWp<8_zK6b|_^ z^Y;#{PKEeFOPPWz!%B+8#YM1#jG)ocg7=F~^!K(m9*hsF^k=k_0qG=YEfS95f@na* zopA@k!em2!waxLg?^CNID^x&_ZBLY#mL`Il09T5`OcUo4T;>E5OuLOaX7}12kYLX- zQfoF@pC!R6AP@gCHN9pxayQ+GulLf|-1gPSM`$F5o?YwDZ8HGax?DT3wx=IFwHZ#yr?y~&ml&2J_V`8VAbeuJYS z&)N^?jF*vM<@a7)2`we#cqvuyv046A60FId8-IA)tv@0c&sQw#-6{n1u0r|Ova?J^ zv5P}@t{Q21#`ehc2LpVKy~oYJd8G2c&jFHOg|Y48CPJcb_ghp*kpUN?FfW-Y+bO9CS?frx4Oau!4*#sCQwVozF)?e;nrk7=yjYP?^5CT*J}9 zP#Frj6o#U(Jl|ZT%XtEHP>G}`EyjQ2^Q7kkdIozjuy?Q{EdNPq+fx0FJH&yugZk}T+OMoWrkj?+B^iLqx-J~EkLC^Yp?ziUGQUg$#3 z0QgZ3!AhnQKjQ(17EQammVlR($U)eWCgj-m*=R4pDut{Wx_!^9-0tTjd-e#x2fl`~ zB#thI!siusP&~=~L`e&YB}yBDWS6k;rb{N~@B2s~=+MtzA_ZRsQEYyEyHctvxv1AX ztM)hmXY(un>{AU7`jEQVzN)8H}sfk zqM0Ut@M$)NbcMyYhwe~58(!QsNn``IvTz!Eh0ndZXcEBj+GnLE*A;WcLpQgL21vhz z4i#aM&guAh{Cm5w^L~I`5Q^1_pT!nNdh|?J0!$IorL|sOf(%@L0hFGohaSXd14Sze zQjiIxh>R2B9LGV2Ck*1TO34lrdBnF8nXPZ8X(vAGw->*%h+TX>T&y4tI?VP|P9_4y zVN2HR=;cpjCJr%k^}Xuxt75yUq%c{t=Pxdb98*fpsYo}3qI{tZMcF7ebH0gVaj{`P zgwU3iVJ+$BJu%}aN_!Evu83QH!~H=31K@&kyf7PGKL15q!m8mrH9u5IzS$}JFY2Gu z*@!Ckf+~hr$fZ}O?3@Fq-Dcd)LGojy_k&9xP5|DpJ6>P%D7yxH_C)rSC^8R^3_kB6 zR>07hs+monBXzZij0{{5fYx9g=YDgP*2}AaG!%gNh&vmpf6)ug%x5CS-eiQ~9Hx3qY;2lJYf6i2Y;~B|(P<5-(Luc#VV(;Efm^m2BZw zgXTI6&GjY>l;G?aZ%=3@j~ph%_(?%e3{iewEps8XphIu`d72>&(365RB^Wq&P&qH)n7qpI)$+s8HrbJpW&3<;lOPjo=gLKXkWubqhkFEZ{ZS~DG54jr zWE8-*aQw{7;eQq}fz0;o>q@s~Arh=JbS_a}s{Yoi#fs3h&@N7GhS_fMMN|~%dF`2u z{g`ALS&I|Su!sqY8{wab&#(}RU<6c|P^t~|LKPc3L>m)D>o(lIE)8dc%i%~g1{tb{ zd^d=OgEjd*Um^&RDvELZmnwJf!`JY|*d&##liWyrPPAZFxFq65ddpN6)DFbhJnFFu z+jvX1v!=GnvokO}On5p2Eic{2Za1XntjpAO2ud;uBl$zD#>#8w2?w(sArtn`(HJL(stx0?@rKLho+Ug7 zyuZDZ_pcWh2UN*s)wW*&kU=XR>wbPf^2Z&oOwx2Mh;y}a2YEc(dF2fg9aTA2{mdSQ zG_(*Bnv2?Bk1j_INswbhgkyDH$Bskr5kizg&oGm9ZMb01od4!I=JTaHKQU4J6QB9a zTb^^J)E7}6@MpGPP#w_~5%*8OGf58O-{bIOP&ciILA*Q^NpeWxs)OC_oy*N0yHlG( z%v_i7H?L){jX?xE8mIfZTc7a5kPk2zrQ2`(U9X)@HoNC|#CkTkZgsW&jZd%5%nscf z9l3FDo_T_h-%8c{%@!w`U?gfLqDC>26CMn*$7s#ftH<=3OMMId)11c}YA%d7_ctdV z1fEVXEFO~%s+4*fHju~xY>APbIhL*IWaF{Wy6o-Mv)8n^Ko^&&jSqQC`eJs}W0 zKlT16gs~`Mk9C*H)MLZLo6hda47!GH;itow9bfp^sl3^$jwy^I2<2{18VwyoiG-bu zXxR>9DrDSxuro2>;dlIo>1yxvP#Vm}=jY~e?>F)EQAewhnQFuisz6JD88*t{J_28f zx=M!>4w!$dxX}0VjQ8$Rw>$&1>S3Rkk74Pr2K~x7d)%mZ`{=z=3a8CJ`c3@LF}qxP z0}2xQ(_Pch5CDUc6{;si)su$#0-|!N{?2Qe<>b-<@e$+ESp5U z&q6M+#45SRZlPY!BAKn?p4`|yxnHoOLf8Avd)96;&0mlaF`I)zPDEt%(mcc~TLsx3 zQja)$D{dTz4T#-l*VavwEp?0h6&sk|uFQBpCjs$XP?VOrv20GiX_iMV z?SD#+aX*H3M>zZlV9wBPnBN587)qDMX%K17_>s6cE8W98&whn>ea0+a>6&t;H&%Ms`J2G#*E(AD<6mmTR` z|9LaNYB`T^o+}{%>bqEVc}0ka4br!~K+N=?q6WauZJ{HH7n~T&kis`XR9V3)Q4Mt~U|W z&U7e%axhOuEjwiut89OeG(2BDQ*?afozx~fd=7A^F=3pxnkT;MlygxMmYZ{%_jA#c zN))OjUXOnNBFBBAe1?xdC&Q2-mT%`RkA%*MO+Y1KFxwk&C3Qo&kHz#^rTvCxOq{~~0(ksImxQe@U@b92FH0|SWH+}Ei-eYhK!z)NMLNnd|-Q`O&tCXS?@E#Q^Re7KJY$X}P zN*XYr1z<#>M4a5iA$ASH@pq7bW5huTuzopl7|lUogg3#bDFwAFxX~R{tTrBjN_=`6kW&s_geOyZXY$Vh>oI$}GLFTq4-M8O(8`v$JN7eDOi@!X?21 zDfK5$d5(FudA(|JT}*4VRkE3AO_<#Bu@HAQitE{En^e>ene?q5p|m`G{p$@1?Kd}! z%5E}4&a@Da!93x}R=G6>HH#HM!h4;0>QJN6h~pBv^9vb*C$YKZCx~~iJ5@Wz13+x9X_=2Tf6a`71c4T+`Y?-)h7RX^k$qv* z;54|J7AINb)W^A%-9&r;0=)|x{4GJ+IQ6G8Z`t|IHD1)hVMs{nV5ViVeg;v?EVuXk&zA^7<^t;63aB zXK4cfX>mzP3?ExNQ{=bJKI>js>3Pc2e{Vu#u>Z$^apAG++Ijo?n@!bAre!NiK;c79 zwfMZ@(tU8cGYN`?L1E8trz}lJc}xBo{*}HKqPN>k6JkRk%MXr@%%`>P-M;!+W&2Hs zad{8xfxiw}v}};Vsa?+@%MzsH59{WaM!ec3*8RfT9DZ|o@?NL(XKl>iI%hlN7I&NL zzQe}X81s=N-+9bF!@A@FU7X}3!GaQU9{l4l6i~S6$eQi%?bRi9YKj{|4mEcz#4$m4 zTMXNbTo+r5^Wcks4C;8P8&!)$dBoK$e(Jz^d5F)_=^x@e^O5h=R+}aqf-Q%T7Ms!% zYjz_8pStx?xvJBT=BR4H*m;5L=1^rPF>p|F4-_i)Oh<*`axEFnZg8`fafsH$wL4W) zNCm~{Eu!y6f4AX^Q^ViTK5l509aPZlAJ6-YBsGov&RA>C@V7&a>o?6IuPMqB_eX`} zoT+AV@G+(a(cahPJH8v8gsb??FP6`7B46f)BS(wu3obZ7&lKuj!bdw#wyVe}T&&TX zazxvseTRQ*;G4b9TNT7XawAdcg>P^_S?~4w?2c*G_<7y=RzWp+)1l!ldmIWT9c)Np zB+ssisTR8bO8e?h=zN^dcTT4cse~vfQEXebYF@puVyCH^ZK?3PCm-bZ(ZGj$`ntaH zg)By1*;7Yl#Z!ir@iElSo_S|TBEp};hUqWcy>3`$Vlh5*{l(KvT>vKPLmdDZMJ<9) zy_H9XQ%X!z6n9$oi~iCzlTwh2yuoR0CJQ2B>H8t_K+!xz%(R-XKP9%;i#4%-f6gzp zjaGdF6CEz4AINX)h&6lae3x|$myP6>!#=}lrdC0mvkuYGLt{u~vFj z{Q!gdqxNFjdrpV?Y2eeH-@P%1Z}?e-t}z_2`%VpA<>2~ZI>1h}X05PKio(jxqrbJ` z8k7nrG^Y6hU=0}g2Cx^AccwHq8ofQ$aaY9cZRur8K74NcuzB|zgG(jb6aS3~S_4@Z z?I&bkZC+*73XA2+@7l%087Y7L(k1vGKFJ$qi(+`J{Tyu2BChM!BP9^+i1xJK`t#-} zN!6-S((DJ?S%Owj!PtAKWSPhw-oNqi@WqIN_Y9JGyErG`Jdb*^Dg^}CI= z1pX6d=n!bolX=1Pg-$wTjsBI#wB8w3^rG4f7#M5yZZOFS$aQS5&+pGkRc%->vNpcy zE*H=`3HPr#ti?%&|hefwLxDwH>A z_HSLB3U{NGvF(}bPvzdKz|uCLjFcO&?}Nr+ECJb`By_WByzdp;4eMWTj=nJTeQRs+ z#rGw_gD+d!G+&G^+N)bn4V=4e?BWJx@~vicJ-k4a1@O}4VJX2`3^Weml;5o(qYfIa zEd)v5DFYd?3YDOvy@?xJrHMMUPsc}+yy!=*5_Ci#RTuuSu#Rs&C54?}d5b)->k+gK zjpM3SazTsvD*5XUW@Q_#1n$+TdMee0tFP|0#yVxxp37=afMc-=C>{v>qH$9w@Lum< zZ|qeu3^)TH0106vYem|73X-x7UoxX}3un;nOuIfWfUVc?JTWblSn&!$y?;7&6JJB^ zCf?d6J#gsoqoT8W2e}N8w9~L2W8h>kmc_?H+cNlJrDSls8i=`6WDt+}aMc z!nMx(UC%xN!yeFzN1+~F)Tns=ap^@%1SJ%1!vY%}8!&c*lQCffCkAop4vURKJ=CB+ z^LOjODPZ{xnLf4fqw4E}h%6(jXVFDWMDX8<+;0gIkS#5gF9B#Zm30Z^JCGq?E|s-H z3RVz$6v(ihE1dIfHXLScP$0ol6-V{U080V1Cgx(@K6s=OV9|CK@!HE!u#uE3f9l38 zsQsI+#QyXHhZKPXBU&Z81kgU4mY&PD!QcTv+xsM&4sLm%rCHJwV0qiW$x`-F!Nyj1**bIcBSCJ*{uE+{CysamR-lQ>_ zn_gp^4U*3;@|)fkgzwLT-D5}s7uJ$5^WT?kJ!PU!4jv%X2CwkeieM7#Mbz;?*Io)n zpNMS3`Q|9eV#$PWmTDHmj*B@I2D~P0zPM9R0fvK+HQqDntAqcM4Ry0FzIYDS>s`v* zISXWfA8m|4!xE322qGZ`8h>v)n!`Lx>#UdO0}X#~M-O{uqS*sjqFz1mA|eXt$J?T1 z1AtPm)gk<5F2E@7$-h+_O6x!5uxBnOB%E{vmambr$f4WdO2|M&bEf)PdJuu4W_0S& z-u}D5(zei`^w0zdTYYHT1AEm7UcoC|NVR(K0}1x=e*`fm%L#s1Re3B|jd8RUu)Gx8 zZa-0`a9z7MfzY%~9~InIr+J>G)?!fP_b+$F^VN8y2a-^HhNS z6eJZNcPhAQf`!kty6y)Fu=|lZvIAQ-O7O{VZ7=u9^;}iPRj4HH{k`AG087U^YJjnb zH4{8Q3Y-hRtc0AzrurUvQ4Gy{&l@Cc@IIQ-aj`rjaDTM+GZN(=6=*P#Q~;;Fv_I{T zMg>c|2?=&!PYm%=y&d$&R15gu!oGye9*No`pLMw8 z3&@Z|7I8ZA;Y!J2VELFO_v+aL;4*@5(aAYwtxzdTvL#ke`3@v|E=3YGfSQ{iPLFH& zmJ;}5?(^H0cVl7rU@mM}OF>eW#!9@|>8k}Oui%frto-Io;bP~nWDgtt2yw78(tifn zF6KB3`*uo*EI7YH?umH>cJ-b%elMozO}Dyf$~;33zM~BN`m=imOeHe0x1~RlFbk%q zLdVndCpx29=s=CtU2jqCeb|QX1nEDg6BL+5Ie&i<@H+IPAQttBX?zD6;b{Yd)K^cxr-5$r#+T`d)#S}cMr;UR(v z)xR`LI4-Lmkx%T6H?{~c&`9+ah4HBN42Ot{qY#?(p5Y`1bYU#Bc105DZW6dS5iBsBW#>0)?nN)~(e z=a7O27x6oR2hB3gSs-je*epW`z60RYU8d>MNU)^~yLBYRcQuR60595i?~ccP{Yq78LFJ7KheMe0#+KaMY)+3elbg51-;fL%4Hj%lzB5CZ{K{zOFQXUoatc>?FX$ zZcWyCq<}(Ui?Qcd{S5XQl7RZR9#t7HsVGKw0s$rldM;)pY?b=cv}Q2Rs~b<{3@uq) zoKLWW?Z57Jr=Pb5jz{2O868Mu6FIxFFS}_$W}$sc?1&k}i>DbVu~IOu^KkU~^hi_u z2s{fg-6$A*D)}{i?$LW6kLc{WO~D6bwTG zfH!EtzM2_Uq%Z2h;&Y-KMx(t5b}hd}Y`{v2kCE>9`Mmtq4&nOtQy}4SF5$&rv>Bdb z4>Njbz-v^(Q#`5KD2F2pQtziz2|8uf9vS^H$psR)6SBAel*qFk00PK}DK5Q_;xy(d z6zMUrQyzbKmVSNH2rA-6CU|F=z|Ca_f!JqVeB^D2UG780SW4s$Q}l#7j5moCzdHBI zHs>7-pwL>P<DqYcH@ zz7vE_-C8a;f)JE|3-Xr$D#0Dj?2gQ;#ux3EEm;Rg+6gCgC>-jBW zkZam~1(DtN}!ixftnO=;#B4E^MefZQc@c77# z{@IF#lYUEt2RCI6E^!S$g(}M^TQm%uXe7)f2C84$)k#RgnTF%{0WH*EZT-vTPV)~6 z>D_OHVtEUjxn8xB>5?-WChkvpSgT1g@)x>GL+Q8Ua&Z@xG&Xp+v%jvvoP_Lj;rlX1|&|LM5Ma8gH1_-rP{SH97o=;2}Q9{=$FZ&wYP-_n3C27{vYaleCX= zx60QT#iM#L`LS+dLcgha`N z>&G9YGxKFNJtI7OXD=jdAQHuwzf4;U7^aLJVOD7OSNtV-w{>R3pxU*gICZHP2G`jU zQb0X(rjGC@lOb=n1+__{K9Y*~)8Caj1sG6{k1t9s*5*wJ#VJy&?zHovhKG)_?4Qh5 zB&|j|iC_}Qxom6>b$?h?@VjXy9ZK4ZUa%>~ zdU+`fcf97b^b01wmmhw%`di=e{oL$t?pMSvZ#9m0NDPb;P$mPse#p;khCdB{j9;L; zAikcf$Yl1_-G!xSP=E6-33~wUE6MoQ{f8p~{v{6qW|^Y|t}r`Q%6p=VQ=<)@Bg9s} zH%%!b@D-7Q)JbwkOI1oY^hcJqRga?=fj4F;KzG{$?-{94rt802_RZh;nE!Za%FclA z?I~V9E1el4DU}=Rz1o6&*QKuLE0xDxHgOF$bi`KktJscnL^0- zs#)4IHuu8q&pr&BIYWZgKJLW%d)EJR1i+f0>NUmXt5AV+l@R#j2fH%z%2c2$7J49U zB%BaJoFH_w1fg_zwkqQU$}Jqd5sWmw;iMq$J8Vx)kby4LLZ&sSCQMSCds7DS%7{kVQo1-VUGhjlX@I}Co|>JUmJB6!7wPlBm-uz*StSI&ZQs&0>nO09&8ga44epj zb0@6V|C&C)hA-!_`u^lZ1Ju#`^D1MHC*6tU@BZ%nM{e%$b5Pr0c49wxjp8t?25gVP zMlo}~vK2gF8ve_h+pSm#M5EmsV}@~W9Bf*KXv;uT^ZsKaO9nn*V5+mlZ|0={@CBXU z+1Kf{(iEb7^qCQ^T$UC-uV7Ia#7i>)-oK!Mzq4%4evxqG^?oxi8c?afR_3(gSOu$d zv>PQVlumW1*k>e-dzS>T35_WBJ;zxI0Zbv%_vuq-K4+YQ+Niwh_s0mQNCgCtyjTAh_2W`h6&C@2bRbS(V4P!K?4$W~h-wfy{12)JxcG_TEqJw2K+6EBapH6kf zp~{2)Ro}IFfDu1Xl2rNWD>P0Ul)zGjbPvYjy#pFXhXXhk%wY(zEJ<9G_~zr+G54jKI>_ETt6#m9Bs?Vg8~{QV{(X)vr?mMBIipBzxc z&sS+u;B@g-ft1`5f1E+uq&mG5PQs}YTZ0&FSv>uKaR{9!bRP{A123YKzE=`9Pv|uq8+#cuzV-}d4Q&gcbdUkHq^Jrw$3%U6 z%yRMv|Ce@nY$GeapLL*S*Uc7RIq04W8xE)qBCIQLJ`QrR(oGYni#v$Bw;5jLp+^SZ zc|s}i?w>qz4MrF`?I1M7?KHbvusm!tMQ|#@lON^+KGQ8J8()%w95yJn42{szUE?T2 z;8BusMT!djs0}F?Vo4Dl;c@R!!g*0(5?~MgYBUU zQA3=l{J7cp7MeX<%}96^z$Ey`PU=U6T5UdrUV15D9|`E7hwa|D@$Jiwyv2PQW`@j~DF&Vc%(yAR)z)tvuWmhjIPdV`;zi*?W%|%n`?WX9lprGm zM_reePjw&c)ygI!ftO1NDH7H5Z5_sI1}~LKQ9`!qt*Yuw1hu~tqXL?Cl_7jn?579O z=)8GG6|Oj~qMDOq%a5_K$5&ZeWqIGrYO2M+n*GCZcz05C9*2!|Bw>F_u_ALijtT;5 zb=dBQT{qN2{(dne%V%;prC@RwqBf_i%H^15roiFMri8Q5!VM{EJ5*R(y@6fff78>q|&+Rhn3g7d8q*^@LZPP z(CpHj+4979jcoX>{2(ko_K z2$BA_ikKUyqb5NKxy2;GT~O{q7Ebk&#WC0;OIfvs;6E64aenVE$m#5dIb!cLss!cu z)JKQWScwuV{m+Itj>2)d(2j%%k7wh>DN?$@9Rr8ULzC!W50AwdZMp&)l%Lt3G~Vb9 zefO)}T$)xp??C@|89q~Y)A7bZ#Y3KVgoE@$?@3A|4Dc*=dW*a}iDt2)L;de++Y)6( zS4OW*IbddQnM*zs(Qf(Uv5Rk~sJ$dk8jC;ZCXBkdyk6)P57c4zkQ7P|%I=#4)Xp@@ z5b!w5f1PS#L_rQ+|Fp4T4n{kz#EaM=N1y(;wk-hIAk;HL*FTMF*b{Ht**XD76Mq-r z|5L*M65HFZK=S^dD&t?7{VTKo8$bQmH2%+<#`U3_WbP-cH&>%`cQ1d2-3xeFVHQS5 z09*~Zh7X0M;<4I>?+Fv)@=z{`AssN#w8f@h4%BBfo#$y~WI{uj90X#pfVdJwhH_D5M z^7n=ZWjmD~#JItC3d2ojiPZxCcel`^%W4}rSw1)SF~F~%JZN5z96=0sq9++46ThC@ z7=nWbsRC@J;TAK(7o6yf73R&e_MB!LBT&GA9vS*K5|8cZ{q^c9i6n7!&%K{gh(`CX zeikC-vn4)(s2T&wPn0T1t)S^%v^6X1KZ|(QyY=cC2_%I+|33#t3EH_>Nz#k(twIQ& z38C@ACtwLfL4t1DGD{6Qu%eqW|MgzK-xjz3FSpiyv=5CPfTJ)g?@etW&l>RbX6i+^ zo9fk**Z%hfX1J}YGQ$p$1N?$1q^zfsEu3_Pq#i=^)JuMh*;^GAW-{osRPFB=qww8% zv2@CS-&8>uw?!sJwY3KPAUcKyYb&3#1}fR458!z`jZD`+k30Mp4+z4q-~nLlGiQkJ zzq}E#pZ6>&RQC4go({iJCpF2Uug8$Y-U&6^TpX9mT(+tXp1N!}dkyS1U{J zZr23Emia0p9EMF1`DD4fl@!vp%=&3xDr_zkfB!JM*UTJr~lsqh<^jq|M|eQw?Y0t%nUyWMIHpn zyM}9t>+QbtW~_EkB|pCsk4UVu?;{UN$$iSGk?}x|O7D!>Gc5B#a)Z~?;mq}_UrK}r zV~))EJ>0yyNN;u2FedeWf)JzXFAips)S1*{Le~NZFC<_V8}gp_ktrJgc<5qT=9;wC z?G-`I@NTjx(YxL2RqqUs*2}&m*S6NgZ%ckEN$@*n53qk9Q{d3}2cXg&;YCm$u7%>@ zKR7m!>hExne46}kgxbMNszw|JY#B)S_CEbf?qAvcYbgICuQ)mJjsz?kB@`5!sJm=7 zsb?1{1hq|faHtXz-HUt7*wt)&{+gMsSzLA^?EJ1{z)TD7D6c3xo>c@Uw^Kw%NDw|Xz zbQGm1Er#n=m$bxj!g;k1ig7-_(tJZMOCUbkN#JX0AJjiiX{a|yk>8TS9~$cyFbMsrts8<1NKEnZEfq&c*|2;JFyzo) zEV!p_>(s~kvEL}fs{2{4e;hmO>Y8Kk6_f(seMBKhWHwQ!F7tLV_hAJs7us8Ri<+^v zj~XmC4!1X1MTa=W_gBJXuJs6pH_PCN3l227bV)P~zq(62A74)?uqm;*;(vAL zrG5I#>V1Koi8MQ(RK44IdR3RlMs~LzTZ9yPzuxo+I2G8{zqw=LR@lULuDpVv6mX9H z_4<)>lbvffs?wd5OO7s2(jV1>XDBE+i^Sp4l1>g&&c?QlB8InlQfh7>OL^#yjjivt z;c32=u0w=GMXeD+(jeZ{leW#Y<`l!GCVj}CM(q?rV@rz9nB{Mtjm*$*qQ+cPTw>My zTXf;#KbciWUh9|M9GJ=fdhhi3E~O7Q8Tir(v48|{iLl+Ogn)O)pqtF!Fyzrk>EFEP zO$jmSBh>WX`6~9U$9aURJMJ{gRM;1s8h7;woyS!$W(1wykTW7k&y1wsYae2ru<u0E$;ZY{5v+ygxq5+o9@1a2$c`!9pq)+%S z8Z~;>eW_G#US41SzB-t4&U0Kdg)l=9f0nPp)Z9Ds?Ly;g!;YrpmT<(=cZQA@R z40xxuD;uNg7gS+ilnEvs>=}8#CQFyPxsnqk3-g)!Jy$vI5w>|E7w@0RnqT+X%IjJq z56Rzk6|(R(3B5*Ek2&$m`|x}#{Msco)e@V3dzk8=YmefOwS$0{p2%cJUsrC5Z=KPQ zWW3OD&?oD9TdDes)jM&!n!k7Ogp`VRjaf;z*+wpg@2g(vV^t+PDJSctZ|hVhHF$>I zg9iM1?Q8;oAZt@>;+>7PN6HwM1IMzbk8RK9O;=*a!_$1MOWh`=6xD2I*3>7r^Fysx zSQSmBs-9EY7bUOO%TQmgyTkm_vGpEd)4NnqZy%`rh_bDU2A3-f&x59l`O&~TCp!-1 zw`IG|_a?B?>bW-!dReft_UO%i5Lr{z3lARW7L&PJIdW=kq_nwTkTHW4SYAUu!ozzv zC30us(R6_PMrQddsJ8uH0d@C0jM*!)$Fwjb_FglGecrRqWR#ol>+Kglme*XqD?dFn zoC`2IXg+>%KCf<_A;E`*(}^lk8_#A|yFV=8e5Ls8GyA(nDV5I0bjR&0vMM^KldGtw@;EeM{_3^#t^bocP0nIc8@--4_|<=an8D{!VFtcxoe8tmN98CEWQ$;`|<`!$hzCFSPotdTw&soH$0r8rp6xD zQ{Dpo*wZK_S%L>ja;6}<&6E9Q$C>z-LyD>`fx4I1yhy<>glVKrWG=Za$*FIRD!!;@ zS?M{%#`rP1!P#6dK8oG3Ua@OBx%j8+1=Y6g1m7Dy<7+mNZ2Q0>KQB4p$d-5SEO?yC z>GbCe{1Fm0cIwmDR5mHOGaD4bzGP=7E&SrU>ocKbE{z*GrCK2sa1J&THPH(%xsUu{ z@ppP{+et-z?K+N$nSE%cz-;ew3+u#-dp;nO^BqxO@G#nCe&GS=x{7a6$04RM+ba?rx`$3zGn2 z4n)EJ<7w8^olt_Vkk0V+eBOPD3mqHFzTLw?5gp5qOlJKGrW(n!egrb>d(|X#`fp^C zXTgF`0nIdZaeM6?y>BWav|z+T_2_E>+y&3(@ew9mlotNSr($ExN~fM)y?dk2>x;e^ zdWf~QJ2-7hl7i;`Ds z6+9YE5QpROlI~Y~w!SH@+mgaF_M%bHJ2GO}=iiHOYKJyIiM+bbCQHxAOt$TrFZxLe zn&dOh^revn`LiZ$b|**ib`4pX4l_|BOweZ;GsuCZ7xZz=?e~p6A#LgSY;N+8mk!D7 zp9))RZuF)en^aamS(V1BI6UyDj693i&grvKj_=eL@-Qec6(lek19{J>osXv}QeosI zXNb<2Hvg!NUsF~P*fTbmY*Xaj^+{u*w?6mccDI;NSlU3ZJQ0ydFzQyfW`7|6Xnwn< zFfKY{j4wq|-N<}nqFS#_$vnK6naSv=-kBnUG;=Ax=h^(K#{-046sB<^A!vb27p2xI?44DD(sFH z%W(%Dvq;|kY0+n_b;ZU^OP+kGNKqYvoGo|e?6*F-5qexSW;yK@;YBD0K`GOQKq-GV z^y!dZr6e>|1?I#rDzm6 zun+!l33_4TdUWOvC857gQo5Xqk6u!Mt74r~$3D30O<>mg?_%o9-$duW%oLeBdHs2a zoPb@FI7{B-y|K*r4|a~LLD5ltZ@T)oF9?>9W*x|yH(Q;_Q0W}La_VwJS4qwcevWXL zD4w499ddBT>izG~-)+IWKa+xYhnD!S~g7a(C0#o zYqIsF!^HKP%%ez#0m5n{!jw7~im*tzpaLVpDd<@qW@Lud>v|{r4wTB_xSA(xUSW=| zuzl)Fz(4Yw>3{zhw1ro-{97L?yb}1=WS3hO_`TjJ8Fg>rgdr#M0uB(vHTKXy9 zL+xEv)9yi@Sr!{}Ruh}uYg98T-F@?4`(iF{`*dPfIgGvLPI!bY_NuN~`q$pu!sj=I zE7({Zx{q}hvSNQ(JayPz75-LB3Nl;S# z{7^p!$2h=Q5)6G+I|pwo3_yw zF>9v5N0Aq9+-Q)HwT6DPMGB9qe1YE~!>}SqzI8ue8vhO^M4z9gDOYA^~icSg6?8YP1dFf`J!GhhRy%eEjr_kY)$)uM?E3!(3z?l z;==B{Qeybz{FDrRw+zOuE9{E+lC$pF4IL*!oSb$(^$163ul&4RIg(v5vp7K>45KGB z=KwWqaM)OYHKjtJ{_tF1VvC>VSZ_$2ilL$RSX^OVjo3A~!%c}=1z)n~YqLN3YSK6> zu}7qs`hb@$#+4rTKPC-b2@h=)yyQ?WIXsG`XIAX67Gw(4M=HKsr0nHOYS3jgTiJfa zX|>=j4-75VNU>fExYA=nNfnB%giiM+1*wxPn#p&QHaqPEjIK6#OIX{|U4Jdb-MIUs zM!tY#dakC1b;dT)TOZQWuN;${oT7(ddB_5OTVNo5Y#+E}=uYognbKp!{OSD$!CmwA z{D+4#o->*jw27`YtYI{cy2+_^_D+?jp0)c|Iksd~+0o7~Ke`^<4~=zgdvX_l`}1aA zURQ&&s-Kwq8m@aem;J%|6&`;-SYySmD2%R|&5A{?&73sOeJE0h`?fo)PkosLyt{)8 z=le8HnZ1HyX7imwp8afOocdO3P0q;9W1^i&DCUlEQ8~R?-YHr8M6fW3o4z#}ld1y8=UQ zV~x#fmH!o=KRAV*)DD#;CcX%NrJw~GH*T@ZTr!(m9)?%jL@l->@;_AiJCN|^B z=I=c)YrN>-KD3Cx+~>GOZQzzR56(>ahtm^b0iU-w*A6yHrbR#Kb78M2EBCIxCn~Ys zG?R6(aUq!8&uHdj%{vjY?}PsSCweaWEDw-pWrR+9eE5=GbXD8u%SQ-tlc|x0o(yn0 zx4r6{>wNG0=R@BDvtq*Z=z9D`t&1$p_$ragl^xa0a+(&l>NiDyu8yubO%CNB z)z@vak{^c7JllIvQ9HXr^-77=XjYC~L5+tu3XVBSD5xfQ))>09vDvvxKzM@!!by|v z#2|5>Y}pyfo6orFLHgEqZ+6>9oK$J|k=8>&qm`yvNphkVMCT2kTS?MQ4%=qY_aZom z+9YWu!yl4~5k&IteD&dNIemuM=qJygKb#tpQ^l<5P3^`1O5Sh$VWU6sXD*NLg0a!h zUCI|#t_!#_*Im&7wM90I-HZn1BK_W@dKx$@8(;gc6Arx$nsrTaFi*a~z&t6CzOrCy z?zyB53Utn^RE)b;qMK40uMV8@F%cFt@oz%xva z=ew!h)^IG%X`NqsO7(J!l+MG$m#{4PmR1D=Pt?W7;d(hW%~ylS82oYFeAmTfaX)6R zJ-p-pC0l7dD6Sk}&k|$T1NvX1fddLK;#A!G(e`jf6x{C=O;duol@k#yW;Rm{`nw5jJA1`T}yQJ1_&$b~% z%g&9gC!##UE`bnNqmvxw(@F@_jDp|H-8&1gSYuNYGTN#Q>pcooZZ^%lki7F=%5U~4 zOKh&sLdvXMLGjHZHdWSJ4pyc(aj&Z7>!h&9LK}zTf||?pTfOt56#5#*XYa1 zT=%#ez6>rJ6Wgt;M>*&~H#udX1s$?Kid^M!-#!-qV+Bf8{Y`c*9yb=g z*)wGv@h!va!}^2>j}YXQxKmc+#XT)?M2e(LRnHN&y8?tQ$K8{va(l+k4nFM_)H{Q- ziZ|o-IkM$m9~b8qnq)kblwh$aY{Q*jZB7DSXhHXiFq_E{DO3sSyTGJ*y|C4{bvxjW z{q{C_7B*(B?ZdaU&f=jtD?3xT^!!r7AgnvQcP1e(SIb8DhW{>D*y)JOK+`Nz30gFY zI;M9wVu(oa^oXx58sFBv}egecwuRnu7din8{Bb08$e*p9gleztb^)UPInUXQ-LX*l?eV z^>VnXIsfe<5HHyc^ct>VDtG0ovJ0U^*_`9LhJ7zhH-lF9;2u6yME9^j-Q#uhF8=GH z!En6wt2tJ;Yd*tba$LiC1&oelQAuHCcaZ}A*y_By^Y_qDHotl)u_-UT7Fg`u(bw z5JgM+n|h)#Fp-_?x^W+s2etpI&xYxe_7;yS3yPcr2uji>tAR_-oo%R3jp&uyFXEnS zcXQz7)}VN?Tl=|8a`5w_2Kh-fs%qRwJBAqw6|DX7Q%#Gf-fEjy9#{z--xfWLctap! zcs<6xGu(Y@DR0I0@x%rdPNbtwJe!v5;4O2nNA6D-&q3T^c2Dwq7!*IcGG)GKxAZ|j zZ>VW;lj=Go3IutKz6$$KF`QYywFg19W@~&RWox!bw+DMhN(!v6c-Dd5u#;+FpIG$l2U~p{E=O_Kx zO&S=oCqMa<2`4MnZkHS4VN0>3QB!*P2k#%MF?TmxQ-$ABRh)Z{#l6O{(scDuwY*vQ zVW@^#+C&4LLJ+zs6=TI4JJ!2Hd?&a*itfsL^{?}IS3&;Zq5LF*8;6OpPOP`j_o`b} zu>pN>LABYoSlH@E1>UX)2U@`;6rT9!IS%OdTe~fFo6}}|6m402&E0$HuD6*R=_=KW zk3I5uLs(;Z2(NlMsS?kW4r)=z14t^&ttPbmPV%rfJ{SR4rpQ6Sq!@8&9KG$#`}|xC zdni|{T6R&zj67+Ch;kvN*)4rbUWLTK-IvZgk$bG)y(Aja7-=(1CqjVmPZIp_6gQDS z`;7Gx@+FbDHkOXc9ZtW;>#hB(ri9AtDbCDAus*C^?TKlFDe+bN>;zm}nfpj2Epl>t zzR~ISj>LL$qYT0?lo0gKE19h07B*BBIs=&fd;Vle!rjE93bL5ZOue|9(6T^hG*6vh z-_gC<7>mBgH}_LP8`=-`G4q-s%y2?Yh*obuMuUDek$i?gCRl5oe&6j^Q3W zzErzncTnqUCnMiAPtvpQ6Zvbb0t1BVuiQm&k^w-6%CsoL-> zqDbYho)}!KgN{pj5|Ut3uUm}2tTK4@y8&i#XP^GrfG8}T6mT3#Lj>u~xhIg=o?klh zcm8q_Pj#H@&^l=F52G;(`82j$`z-7-Yo7D+&w1x`9gLPmNUl=in~96}Y?|FttO*n8 zD06?-6BY`2)4Rm-Od-5{^*djovFu(eu!V9GP(K}=_&HP6+jE*~;g#hHt_Q9t>twTn zE$>9_6vq7`x?c9$Yb)XFl2s3GPOeg&N?aLsDVP!ea(jKty2C3dP)YcLYQyR`xx^P>t|Mn_T}?SXL-uQ!mS{Pf+J|p?_b)I3dik2X$9Cd^)Vzxv;K(foh$!w z<*R35xK3hEpPs(ZImgeH6l6&ov4@Bj;o<0$w)V zh$OirS|DOnT=K}i<@^U#)JF0sk+X*I*WQOODeK#0!(sb0=Lfaj3n9jL5(8;b3S(kb zIY*ox8l33{(AzBpzeSF9i7jyJ3(1r_zkz1~G%>F=YuL?3(bK_*xG(+M7xV|3-;pLL zR7!&g8GY;TGLU$fPD=Zr{Pkkg4?Xki#hiJ!oenpg*3VYUD4zuau(SlOYlwk_aX(2c z`zamtZ3$o4`)!C5T z3Y&^5nP&OK#Kmn0kjp@k6j4B_VumarK_{w5E6C)hLZV_S>@q_7N=Ot1{?YM8!B2#8nrUVak-evUX}2&`CIZj8zgy}QE*f;hpM6;+PC2RM zNj3U+Tq8r;AyKWmQ1ibYF^`i`DA$|_qMFbZL61?R!5xKPbH_VeIp~qUd6eVl2N`Rx z7S@XUzZFPn)mrjiyn%S%KucWiUl}9&oK!erBPVrLVXUaUnCY{s2N1RAgig-M3y&35 zvyCr)O@=(~>5S&_E+5oJUPHBC@Gwqur}0KCiO_GUeL?w{7ct z`MFv7_oc?`27!U{O; z4|(m(i1$}?TJ;-^LGUDxJ+D5h4a9q7meqx04{MQ6*jFXgs1195Hkim~X)AR?yfsnV zNXq&#IMNUug|<5DyAs@oy*N#wPeq0;uc8-l#CGDdX4~uAw>$AQH|E+TBcH-Sk4!WT z%WAX4w|o88H=hts3;G&l{JVR*2uJ0+T3#u;#Odshf1NP!*rm38wyG>;&+VX`tpW7D zb(iGg6sVUDGx^71Y+gy}@2v%(Eu2K7#yGe(#tNknO}aIE_Kb2WuRg!T5Xpd-1NbMo z*}+~*@o-Cl?x33m`LW(JQX|#^{7|plhFpS-)?Q`@>^Ie7swquy%Q0H7tU@l3;3qU1A|OS)@-I@sxNc@1=&ThO`}r zEQjJ5`38f1zZFcZ91iWN@M!X)ht6^CRBgdWzE}t!x&>11sSOD~y?PHKI;SBT+}|E{ z$vhP+8C-_ft#Pg(jA2c%y(2UDV>VSfvEl-3^th2hYQ575_jVeg4*!E~F_t(paGKOt z4WI?eg9qYv0P<q zMCj@Gs!TVL=9ZwZ#+WVUCNUQW!VRY}35)Wzu^TZh^sFtrymbtJ=(5Xvp-FLwP!#kk z`v~5gQjxIyfvz8}_zk@{4`0c!a&-GLAT}3Bl4aq)?MPeSx2-aI_HR1mH02PwpTJgb zG7b)Jr_$}%>rV|ApTjKjzx!PQXH4$BK?BUI)oBNwYc=nyD$a}?{&HKAPdwecTDBne z)zyZmTcIPuvDr_ocDx(%zQr}|MivqKjMAQ{!Tx;jo_CFLgjmcYDY=fjO{&ULuRL2P}lCy@n zT61fX@xn%T63LWPfTqgXG7mTV(R86yt2W_5rZ~M@QP_ko48Qenc-VgNJw{{T%6MhG zX)SWg`R5j9Sa%+x&1W!V^I9pW#aO>5{ElI;5+36RL_-ExtiK1&WF14X_KB6RDy|L{ zlCk+g)PH2P4Ash=1gmrBS#E!%lr*R+%7bddb*Zz<@duuL?3f~?#3SnMT>>Ols@ z16NI>aPjpFYt~N*-_Zr$x;1OXL~8Hi;*R~BJ)G*J}$qKYsiyNCrd*v82_g*}c1PtyW~|2az|tgPgd6U&&EP28;xP5t#@+$#)w@pv%x%z4_uv z)%4%)L-~(&W^KdZwTTP={ayZ2JD!I?bp@K8DcUE$aD^C$Z)|QshzF@fhM4c&?)ROk z`%4${SzUPQe@RuPSI7gr8YMnl3mAq0W@$({CRVdOKt?iDL)h%Dej++oyHc2+68tSZ z6IM<)KdH}Q{1@I?WAKR4k5BJ@ix=fGBWLAN$4+_TOf@<(-!y!OyU zKIVfo9rrDuwu^@Iq3#M|(PhCby(Cl`Y&>FVMMEnSYN@ZY#wC0sK+&;>6Q|`OxnHbj zK8fM86gm-`1MR^MuV7dA`mu@wgL6`~rX@MWc9Sce2~zK#!Zk~xXoO{_W37mUdq`c$ zo^@HEM7=8c7qWPecT^_qWwczl43za;H~o}{$uoja=G?ukcXYjw($Vbk_YP!_;Ni4#VX1xrC5^<}wx4F9zpc(@m-)6C!Zl`BO;?ysgD#%Yg>{IY@%8rhq85i8$% zG-}?;liJhFrAo4I#xAmxoXHefE1ZwQ5;l&(zNvq^co*ajS5{_tEKQqbL*OW14qt^=&6Zm;oOqw2({ z;J^*->&~>nH zU=1cfe%OLPpVCv#SrhO{==HO7XqAx!B8bHWaJg{bo#=%P7Qx+8)A8dnGKn;^{6@u? z>3))|`{j15Cb~H>M*%`gEfcgIXRWJDriG5*0f9w-4Oq=k^6Udg4)C4D7$MFKkn2l9!UDohhs`;N>;$M_ACP&BWh#wkW@ zZ$SJ*do3Z?-(TX`U-vYzIyX{aA}!dR>drrlvpHa5xbcWJTdc3J`?S=iI0!f&Rr(Ge zfMfp1MKrYBo+XS8PqN&FY62944(&L-gAEAxUBe2}@ne^sojkwMl^}Jp1z5-G(jGqy z4Zw49%|e%&S}1MV(A~W%Ra9MI=-WgG&Z)f9<&0G%LLQ6g0w>7X67IF- zpIyR2s6h~v^9;X2^;*Uvw~{+=u3CDiE%{Y}NYclGd=tt!6IGULd)3A@mea`<~rX%0yrxw(I?8pkDi3cHhF3 zfl*fXf2%1hRV1-awe(DNiFRrKml9d93=_{l`QJso1y88tAYSn|>|MZl2{7UM(_=o} zMZ9y+#Kzr9n6DMfp!yg~&G5XPb1_pAGkY$y@IwN2Mlthw%j2nMp>nE<{RH~OdFoFe#ilUTyRDfKGt4y>XR?6mhxeKQb`h%$7tWnWS3YF8qn1X- zx&684`*s&D-p>+oAgs)){E8fTYPFiChZT1*fSB?4WL;;2`$`Lrs&AXFOFYpX#Da3D zdoiLqwVgUo%6SgZo(6!r=~#T*NX{bpAqHOt%F?nS-OB3VQPuq&8G8_$&aP`KfMQ@6 zzNiEDe8Ga=t8ib+V!8Ir4*ClqzQfXi@S{ZkOX11vvaTyk!ESrC@zQj`>OzhC)5U^1 z2Nqr6&I`1u_Pu?-cE3bcKLZqe#vTC$$zR?ybcFUr7|27J(SyC2TI*aHqH|zkp0-4v z7X6|QtmXW-fO3%Lihq`KF`u7~tVK(`!c0bnT#plR5Zc~Xb?hVSL8j>5>&S5)q_KG86mqCI89Ef5v!<&cKCs)@u zM-uFJ;L~AfIC^3R6~xAJ)B%tUj=dO^1%+=2vtP{1_^$=BB2GukdJKDV%of`HcIcsa zl~FKc3VZCZ^9wwVrtT2(Kn9k%>yGo1;VqxzIt5R%j?$Wzy)H-&PY2$|?CYNhf{r7Z zu-Z}r!AwZ<#Ho*9J)U|m?A*+qgy7Z-wT{LWOZTJ_iV`wiB~47@On+=3D(`YZg` z3UZHbi6`{`?7PoDTf6G;K7r2QlPws~LeGglxbe;dHNAmX%;!K#!Oda7_H2F{6rN;I zg?HO|hF3lbo-Q}eZaf5{?sV7Wn^5p} z&w@GubX|jcE~)F@OTcB+Sx3n+wu+7zqv>GrDLT~B{^g{)O~04%6^+{mkq2n!pi6IO*P3mV`s9?S zXq;U;z06WNh8AeCUaxBYqXN|2?NuDbJznQd&hc(UJ6*XIbOpgx56XV->;3sZDh%-o zK$ls9Z8Y4WF}olcScl^vi^pVpl18Q-J%q85MP)kw3EGl6^5p{gxEXj_?0`1*b)u&J zaMy-V_M4X)xCGQ%&fHKCx!xFBSBFY2duHhUK;}b7;=d9WaBqLhF{=OTLtso3W22wH zWU~iKk?TT!X%g$XP?HUHYDC(L1R|3e8F>IWQw*0AzIumSlK%O?JW`YRvRb>zXGI)9Y!BuNv+h`o965j}onZY_ zMwnhy&55yFl>LR7cx)zb`FF%XJbua)bZ3}t^B;J#;Jv##&UtP$-^`Eu^z`p~)(U&P zTzs1#&`yt>m4$$t_18uP? zh(PzjKstbWcir>%kAQHoML=%Vy^E34qkDQJl$iq5|z2y_Z+9VNk@ra zeiYlR`i-V^Le2RTOD~WqA;9bEoPc2cWe`j+V3EptW!Z*=D9^r(mkd_I4ty>-1g%JM zr>Gpt3;vJY0v0HnY6wF+qeNJ_OiQ@q7P?(7S zwoJ!2I#k>CMIB-=qI`BZ|2GI@WC_bGgI5p2Vw%i{Is1arBSc?(S+eeiy1*ulJPqcS zP1RS$>j-ihjrMUjnK(CzJJ*4IntkhRsK6XZ(mF-AEMbuCU60%BH92fd9EWp@9NL+C zL1jZ+#H;8#9t827V~+d8bTJR(a|pTh7o#wIC)2H;3=JeTXW1?X2y63Me#vM^xKU6U zZ>Bdj{D2cduArR?9pBXwLlb;L-Z2mcS`Cr`%R?DY+yX0082k#5b++v%Da`ksx4xUZ z9pW#YUm4;Xy`d&Vam*)$l^c%E9on)>(Supy1q_8~j<-{pjw3jVQwg0i{x-dcw;w(G zIJ%qqEP2>*@X7P80Ee2W_Zl@X?l6?rjDxZe$w*CPq*U)^*Pve z;S3Mn=cPhSX<8kSCPZFAll~&R&W?Pj3~SLe3sS*N#MU zi*|7*YmYoYT7m5HAJ4Ce?ng3xddj`-ace#wNp+?&t}%@I>cD z&LPi$J#y`r$}&0?P+)KDq?msd$Q+&8pE2PdF0STzUNHK8F(dJJZ=HLQhI(OJZNd3Idg{QAFL zJ(Vf(EQBYyGz&>;vvU3T^6S%9593>bQ^L`K+e#jqwKTaMBD+sE6y>aIL zduZZ5sLhN=gHG3hBIrdDyY&yOL36m=BV~0Qtq)or2F6puVV(~#KnEC~nTk}ev^@I^ zpGWYRHz@Iu@f=PZbYk7LrzLly;S|N9LR1p4hbB-kSE!oQcdJxX16)Sb!vK%`aP%Z;! z5pyRh5X25;yE2l%??-c&2)CyfYU**mU~N=pm}TQ&72mfnot=;V&h*)KHMXkys+$-7 zxy&mKy*d?rm>C2wjJs4)qOF?gvH%=4x{}3kE4g|_(dolHYPK&gx57!se}8MgG^DcK zl~9pj`x=^bSa~weB*B+rTxq4EO>{kCy&Z51PSg$Y4R0hEQ%$^f#!azHMmqB}nH zFV7JtG)6Y(CjkXW7Jb-P%RJmW{BDWxGz(KCP`!u2ieB-O(%7i&qnF*}Ef2&N zt6aoV=yU8wFTJlj%4>Q?q1%3G)}Zp`vwd~Gjvt~^UXtqBBe?zcauw_)>=G`1WcF9m zM>luL%)_~kM{WeR#BWtt%c?6cJ>w8Sq&ZNB;OS}QyuO)gI^k}tz#Gm`y871GDm2L_ z$z@^Z@2053ksD89M2o~`0?a)=PAd<0M|Q)P1e9R&rR|4$XN{LGXC-)I9~m_ErQ@}V zOI}9}H_JBXa<(dUoxb@*Oc>KH#8_z8dngyKu$In2A9ppzx=y=BFUP~&SS>;Fkp}Na z``-&~^S2m^XIp=AU??j!w&YX~=cu@L=Y5Q;_-%b$SEN*59=;kkjUtt8Z|b}Ivz4{! zvCAha6857iQWyfCNLKUKwdaza%Q)DR9yer`ek(&64ZDnJVbNjyzRq-3qLQWZ@KgzLGFJ;U(rB)`hvj@URG{nRru*I z*SZ`bHZq@6p}5>r2};%pmA~EDGY<0d4b{sgw!I8iIf-qqoQL-#L*MUn&ZBFiid6K~ ztozT@a<~U`>>DLFrEaN_x@N0L#t-evc;^Ye>`P^Nu;sOCPNP{MG{IwZ0mKC;VS>yR zRybtnh_3F1GD)PvsxEhv`Sw+K^}=Nby_1*4|3wR2Q^sw)}bme9>{RpQz=Pdn$W2 zGP)j>*K^9N<>@c+bHDMKvx+d1;9{DxVb#CwOaBzEazPJyeo6BScvrjY>|E@|o5U;Q zpX4=h-)_&KeR!nR&F6iJSIUIn)(fyN_Uu&S7X7NbK;RyQbcmFu7)fa5co334#_Ty+ z3aitz`P}146%OE^R>sobptJoD<{>$Q{k&{MZPaxecRs#dsZ2ON{y!15H;<)Eotg2I zrwSQC;Wx5}C5$wKIxduT#Ovb>sKW}H-DCC~vu1^jtQHT`XN5{d?w93=S4+JBIoSXj z0;NZ;vW@;2v6}FoJ@=q7{-LW0ssajpetT;?8dFjvUP;9=VV!D%`f^R6Z1GFkweMvcCWYE`Z|*x z_DvxanAgrsJQ$E4peY;vC3qXpeK5&r8c^|w#qYp zM5{(c!ifJRrih-s8Nvr#+A$f1lt(>%b@t5*1v{n`8qRK*Uwh1xPAhKl@E}t@t@Q->JM@ZOu7ef}> z?-JjVsGqv^XhWWGVb0YzK8%d)^rzkrDs};F^k$u6Zxa;V!phuyZE1Ng;P*=6hR4xm zIe>Qd_^-5GuS>u^ETvDh$36X@9afjizcGl0>8X=N$fQ4MaXS`JX5i^K_Nutne4JL! zc{PvhC@7kUk><5tJmHpF?TTqBeNx;IE&8slXB1a5jMf?$Qo3YQZ5?bkCXeakoz*>` z|M8f}BOSYV1UV~+F6%ApjV9yf&2tW=7%?znUB~?b4sG)q2A?+`8CMCK#`tJW@rlyQN4bRIdTfh4z`DRG|T;ya}7RcP`^aoP+bw+w~_h zO~x*d(~M*2$?iqUSLg7c`3M?R_YpKq+)T!+5ac82llQ_TA)fus0ULdjJvZcb7AA1J zd~G;I`}b04?d0JXep9=EiYXmn)CG*}cyFOEuo* zT=V7b_{~2F`01fIr!ps@t%bDl{3XI-baFH6D7JiN^JCQ$ZITEeFwn|NS~E-7_1NTE0HV4MLIR$+ZJ6{|!wA(hhd> z=jMmEXVAEoq?jS;$=yunMD#cHd&-Nq-49&;VIFprC46{noZaY8PpYIbNYkznm;0?5 zy@~mrIV|pw*4kqy8YiNsrEqU7btZ<9QOZa8v)_GtP4byjI|qVzqMY8@JLQfvPI&}P z60Pt`%}U=E%STN0Khpe`l(#8Utr+ATut3DQu$7CtthJbYbaL zGDt9qg2I=2HjA&VcvW#p%ru%e7}qCRl$ofU-|{|87CG@5jiVz= z?V*(bmXNbgi4#9n!94rBsqnJ`+rGC+MmrNvR@v0sp}9qe0cH~l73a;QVrTb<<WKIL7K74|Ixn})ZVuViOh|4 z$KzKo7t>*b{10c^%*|~*oFPu4=p;oEo%woJ>eZ=C!@Hthh8}XSQ{Q2%D~M22;F#dF z`clgx4-*U4e9G2itF?v<+Tg@ykoWY2L#xG9MHoQRr_^57SqQWswdo_ z4@Dqdnm8n|dga%wjD44^WksE*kC;ix-yfGu(q2M9~#^*jGWbG+rA9&*lxeVxWhAc+vVr!+B~jCDFcVR+J!@x z^C6e0Rj+?ql5MSfZN-YKPl=)qRrWQR*en5uuujVESiY;+ zvWxz|;dPT1=Y5)&Zrc}|b7Tq1$A&h(lkF{uP8~HOMZWC9I(b`MmG>~JF1ncRKAzqY z>9`?3oY3D>KI256J>gdCQZPOF>F}sK@7V|MClf9Os(1;M^KAV|I2Q5S{mt;(CoBDv z79H<3*8&2q#|)!Q9%b&4AYU5MUdnUY{hxfJNl#|uDfy39`*8+c`Re^?`A@1jWm6ruKkU?)4*`52O$@J7czKbCZiCfDLe6f`l4ki$oy!;- zgRE)BZ7JJgNX4Pa^Iy@VaA%`ur2PIT^6FIcH`r2Qsq{i^7L6$c*r;2o4Gn&Io3jf$ z42t??oot3imEy0Tz(v<+z_3-G+EwWfB+)bvO{;zRDokFL=yo??{HI4Ab@w=kh05GgurZ{sVzp`qj(#oYblL=| zrZhiW5#MhY71=zpBgZ5TI###LNXVI6xu0o(Z&{Ja4~Y}MM&0j|FP-fsFxwvhzW z4Z69vc2O%a|KY$fE=cgEu7n$&)S(UO zfjL86UN^;#o2uWBa11@HN)z8xWrC}5ZlXO}X*+e%tmCFyZLD`T2mbPhg>GET#~On3 z@yuYePJOsM+pPhn=5^8ebpA!>VMcj)QyP?i4^Pn)Pdt01x}oW9HKhrQ;d@1Se1-Vp zZLg4xU$4os`rfDg*bbrBxU?f2qH@wqBb4I^@|g*p=VLwBAAUYcN12rXD>qvkJI~g@ zEIZYg4jwDoue)1&GWDwPih7H_10#Gm13iEK=Q@^A>Z$k%wc6^=O&)KaOekgNk2qO( zpMF;f1=6pC-k4?XT1ZN5``_LV^2b&OPSOti4QUD}L|2H3q^x5iJ=Iy)3bnn}x@gl*J|awUg$u<0=Ga zy8T4U0j8s_2XIk?2iN;(vq2pMKo01pPgU-+m$K7T8}ENw&`0zm?E*AJ-7!B;53<}i zzpgJ@&bcFzJNbK&g+k`b=@;$q{H(_(KkFfg7bQ~ zGcmRXPen>kA>POE%7t*vcZq>jz|SWD;*m+P&w|t@Yw~ zacP&GB)(gwV(X<4b_VwhPDxYKz<%*lZ?y2e9qQ||j;# zBb1r=EDedc|3spr%#=opeIpa=`_E9=@0?#h^&vPh@GEMS`)5$2-A7L|FuWY*^Rh5I z{dsp?AeO@O-D;db6KR^I)#`_|V^<%JZmDf7uH@wZ26`;T_#78H%mY2j_~#&i_%mS}{6w@`zDxizw;%e3?;|E2Y?a#M(t)|U ze-&5)&dRmX?Ju?l=VySqh}!Rlq+r;U4gdXM`3>gPS%w6zinq>ks!*+2plR?qKqw_= zvnI}STrW1Jha4(BUr1VtC8>Kgv-^x+8cd11E!CMD6?#*(VL4_0#Mc<$;ZUpMmqCM8 z5SPv8`cfqKq*zjxpMyD#|M;aJ*-mn*+m6g*582y30;zENQUgp5W@KIeLCDs*5 z+v8q@am$7l`*CI9Kld#02c_{a`>aGd3!i!7(d;yMA%eX^G z-%-?%`7d^4%>2Xj`j^cO^grIK-jPwOmPlBXi$Pb+;I?Qr_~>lBmG;)3nBgJzXD15} zi+T`%;X!EtTY>T3fm4ZCBX`OEiKyPzlV)#cI!b@^Lk0iW9N&{3mMUTIdMwL1On6RZ zVEbGD67al=#C6Kl_KKdLbh;o(1JzTO*OX&#V#3%($pc7nBx+pe>Q)?XF3!g1QB&y5GRuIQ zgIW36eddKotn_l|r1$`W{L=yqKiZhh{HUiO#j(&XdGZDw&>5&E=SUiUVuZ80eB!t> zyo+cSW=Eqpg5xT)%*ELLsi0HI6nTmOHOaCVs?lODKj}Yyj>r}Cth1u(Y9A${vYE&ZsD5}(_? z9+WgXclwLl+Y%Iu;nOf}Xc{~8cGv1d`zGYZ!{syk^|yQ9rGU-d5$oekU*v8fS|k)k zauCQ@_d4}O;8`ovJdz*w&kS)Q-q+9=awuX|cA|YFAu#bM@8hxY+lxx^+dnIdyDU36 zs}~p7@nl(vz#6T(Q{(e8hdtO{+!=eltM<(j9bbxyArD13lyzjRV&n#3U&c7(z9_K%i1m!ugiuzbxQABmVc9Z%|x}>pd;$*@dFLLw; zVu{cdEMe=mjqSg>_gIAR(ChgRUA6mrHRwNoTh}>UB^NqA$xz=sQ1g;iK=e;*K)9g3eZub}hTLUjM z+0B`t?7oF&r<;3!ZLQ46YO4M`d$=(tZ_2k42!-g&DhN(^w9Y-G4WVKELX@!AwHKE} zjI-oxdu(E*6b9}^QPMTE*cDoG(#rk zEAaVhJ~Z;!#&?CC#w}xjxryWJPBxw^!`G5!dFN&-Uw^$$Ioa73`Gz~x(m+>|F|>#` z6NXQUj8)TsK7x2X)%`!S8ko167e>}qAkqpYF`n<%z1Pi$VYxx~NvQhmW76|UvAN}8 z{3k`3dUbfN1IC@1Zj*Skf2QSs#VNQ93cdJkH^lQftDD=@G$yHUC0DI%KCsNk?EFSw zg}msYhLl(Zky~D+_j2|hNy?mSlNi`kEN27Kvgm#JAh87is0zKGkiq~k{rTGFN$SRA z`*A7d!bR!~VyYl1%>&wepi%@i-Bn1a{5wMa?#^Tl{eNnz-9&bneKEEPFS*aTMm%4Vq5YZuJ3qUjMvOVldz^9(8H76T|KJGzW~b|AM#{jeUQG zQvpHBQSX%nSJE(fM3+oI>3`nc09&}@^Ov28O@~J^cHNB_0s=i8IhXD@8O`sgQ`O6# zcW9mBuM`O2)j_-!Q74jBFRe=RGVHa^{^QDOk=4x>(>7CQgw=?6c!kp z{@DF?|EO3QhW5$7Fx=s*zcu^-@svSQ#)zBfP6*^#-WQh!a_#LadK2B&{Lo)Plh!`X z3u2Ugoj_gXWPNALFH}gh^(SMh?Kcn4U7dj-h6Itpid^DU4?HcMdQ{`UTa%_hDJ%6CvGdCuCr8L?^#4!s2h)i3vvJ-OXc5rbd*8bAdA{U#_F zhW|b)7enoq&zp+5`iNV7=_>C&jud%-9-p94TB4V_iz%uXNY)@d$CY9T$RpLgYI?>j zG?U#DisQG&MnrlgCEqpLY!kmuzdEo+bMkcChyy7X=~xV!)tDVttUOF+Db&~TWPWxThgYvlrz5?Pi- z0PqPjdG-%QW7~B<#{vD`wk7c}p4qvwQNOdQ^%?GIb5CtrOs|9pS<>&%dLGwy9JlwV zknhBhn8iIw?JxDwX?UoNOl*?v=RhG*<%m9aPU!`FW>P0jE~ zvMBMC6Om|O-3SJp1Px@e5lX_{+cyPD*Qm~MaoA)oJ;POu%RLTryNFwI|F?*lal>!- z&&UMmlU1G$?oNf)wrtbbEO+0gE7RA>2)JsQP6IiglIJ*yGTBYxo~u}WjEWGuxBFLuM)OT1_rbR)E3tB)#P4se@Rq{roIR7kf2XhwKsG&WqG06_4du2s|~<2*-3Klijlzj}2JeRQPUR?cFgBlPT&$?v=oCTQ+j zD%_=%uMi7mKosK9y!dK4&CA71ioWf&gYeMkk77Bqgl)TBs#xtvX1+VwevAE276)(J zJYP)Q0HRhYsrB+K&-K%;eml*h3ffIHhmcG(R3@(ttm!`W+HxjDr5y@wdmMTyzFKMr zd(4m~ls_>x2##r(Map>En=y9vlLfmBeM{dGK415`g|(PkJ5xyF35M#~Z?w(E zenb@YQlotCId{*f!t~E(lco9}CZX?15p`6RcPk56S?u9y{AR*c=4Uw_eM|eUKg;o@ z+)j#|NB8xX9vs=)iuR;ANt4j28(r@y-rC~8u|EBmKfn09#VJmtQxY|d6t|$TDdz*g zL@r|19M{NtP4-I(MWT&-$1eX6Q~PsWw3Wd!H1tCiCjdCKR}kd*m;bV$W3^v>b3R)f z%czayzk>t!Q)0@R=o8u&&-n(lj1$&rsDgQu$w|$Z!b)pGvW)PN$|pZdQyPvz0FZ~T zGk_ujh%E@|MORK|{#OY9<&z%7qvJzOhAxws1ds@aS9+J$YWV06G+}Rgb3FH$XlC7xakIc+n<0N_*i+=*b0q zyY{(^NBr7N#I}N*;!1q{WghK0}LSsli3di zXazNhX-r3i=VNYD^C4sg{W-PA8h=iR3CIYWOc(x`F!==)&xLJnDLA04f5p6l)WmF7 zn=-A|3=P7j+jT*#CN+Ai_acKJspM8TT_ms7m=iXF=vVGLgGl4i$!ZRC$$h@y(C>HJ zbF}ZLrcWI;Md-*1_G9H!e_VfoQzaMEcD4{UgB`e~u zZ#5g!LSb9>TeDZ0`X}-`ob>;~<79HGcbk)iw-mecdy~XxN|O)s zL-tW$;^g6QXjhDs5~~N(&n^0!j|xJEWp+n37D2snjc=6WsmHljwc`ldWR!Lo=#z7) z=~QDlSVmptmsj_`LQ4k3|7-FMHn<`}83SS8IS`HaIJJ-TNA0`r8SOkVkh^it?mgk2 zGPZx{*xPaGO&v(D#d^3`4AmfOfwK2=%L#UHG4Agc5$hf4*-eNV4#PhM2o+PeU$)qi zz(Fv*0S=>9@>lNprylMq_$K+B#dFYvm>Wyq(IM-W{cL?t0KRRFnrmuW(DH=M1%h`* zf=urn0Y~$n5?eMv6VPo)ai45h?Fib9?+u!b_nXwogM#fi=851QKgk_p;{LC_w?Pd2Z7E`^yR`;{YgwkM@&%1U0a$u>$iGY$viI5`U#!{bU=eygCoEuSZ z03gzJD^kg$mvJ|sm7PUy%|3mD%|ca~qr6U@wu^1g_3OvJnz&Fqz9!Tzd*|x7&V3|| zMT7>=QCPBN>ge*`!>mq}E*-SeeSav*O`5^z16h+)K1#yGd?mMR9hG~%T&c>bt(P?( zCe)Ohom^eb%nVXcQbkv^4AN)0HFaUfWzX`aGZ&Td4HghV2swVi` zO=HbZp2ALI3ZGx70NA&bTiD@0;mE=kp(kY`v8Kbtn*S#!hOprL!!=Pf{slHzGQs$* z_Moakeq}up!+uPwG2W4UA8LR^U+W&G6YtFvR@Jj-kCpR=a4@o36g-uXFif8S|Y zg`)DVR}ZYfp%IoK_}c@y6=UdRD!mmqDukKOFxXqZtX(NS7_#q!ov@k1d$1+%wj5@^ zgG&R#3{K{Su;*dt`v&(uR5bK4!RQEh#c;6nY+$YJa$i2}@0e{J0L(()*NRxeIGaO6 zoa_SF%@oe1ev(hDHk)UVt^-FWI23!#?t5I3WU4t<^Q7XsjX2Li)&lPtO=j6Ay%Dco z<&mn10&mrEQ-;XnwBkQBEos&su)#xyHi9v|ZXSh#HO|zk3)4o)H!oQ&;(aOwCtK|$ zYztg}!JI8gG}qus*i;2xZ|F$i2W9LX<8$*^-T7@<7qah@0d+nw)Qif-E4i)niFfpXs-)xtwCq0j$fra!3gwm#(H6+Fhmc!W;Po zgA!}Ja_AZ0)R%jyif8w#a+AD7)mfF}ke@_fTk>RU90?S`SS?Ckbc35wmTlvi^o=}h zI4@j!BU>abHM$s)Co5y|$hZ78e%k$6h44jpLm{eX6y$p|A z@Kx4qsYX+9DL}i3N&$G^(frPhZ$Thz>A_>Ix?Jbvx3dNu@B3VCLpd;nB#3%H=g|WD zP+y?;K(3t8Z1D#8I@-mt4cj$Os!M@mok{uloDn#C#uas8L5y9qG6wI)h2Ek zdN^=Kfb}usCuG+RaX7O`XS(tD#-%_t@y=TyB3_`27MC3$#H-{PJwD@k*vJrCV9XC_ zY~0WG1z|tP`+r44v!(f>D?u1aBV?daY4YY1m*#MRG6@QxKt1Ef+N?zq&1qpCe~9$E zM{fbE-dzW7#e;HAO3IiI-M^!%xBX+T`Qs~**M8s*i?I>*ptMi-4%^yZpoqDw2C`P5 zz{>TJ9}VSbk&TN~oyY6HI7a^7kBWhIlqF0Y5=A?!b%Gp{rDspJssT9L=JahlQ)$j(QTQSO`(^cXIUN zbp6%Hq^Ioo+6G=$1=mkZAx$`Gm#W%N^uK)SK?(xMRuvD@0^XNx&DnES3@+1>!Z|=t z_M^HCfnCfvg%^#5vO-;d97{5hHCP!;aEn#KyHNVrq%91u&yMKd+;hw6?-KQs$UXXV^U z7?>9ZaXAoXnDcgxdR|9Xo>Ac}_2=g~Yl{=8Xg*-)x=hAey5kmpoCN6`K9q-6{RT$2 zPJgLmicUp(fN2$0_+ljO^o{cGAnWI99YtRF9S9xrzja+7YZx!8+q~;gL)p%IUfqk8 zb@J@&5uDGxO;;M)op3Z5Loe_jX3@f4%@t*GLU7_#90}?VDd8PWd|?}BWV5Jf&Nx_2aq2Ly=GC8=^|TPluh{8$lnF3V zl#rCF7Swup>VrIl?|r>$Q?n_Z%rZ+$;LWAs&%54Rji!p_Zgy3q9|Iev4XI!6tP z-fn7{LRA2MLdk0o1TaHq7YLNXq4`3Pp+*Ks4nY_6*+v+yLIGtM%KqLNXaeQi!65Ya z-n;+r?*D1`e@0MLl)Ui!DPRc=FqEPv!AlQ-+!6>(tUKtzdukB;k20=_M%PaSk-`m> zPWot2{P#J5S72O);oD@vTMT&Uswx=1*#6VA%$`QcPs+HPvn;Tu<(a!7?OKM)-~3|N z0GFgepcG!PAE#lqS7#DSIr~>Zjj7O6|JHz08;p~|{=bTvW-O)0Z1v6ft&y>$FMUKR z$T9l?VW@Q~`{7G?n>YyV`Jjzddu<(tDo-zpxJ7^b0_#M>zS9`)qaHbk>Nhoa@r=ex#i4 zjnmyJS(X<|2JE;7`UDV)hakr7L&1W}zD()8j-Q{i_f4Qew{wxf^#Ky*Vsnt+7Xl?e zNe;T3L$dzW{9&A%VsIHs$-vJ?c&;Y5)%~9tODD9iLjpmO3(=qM(^*yJi%|1umcN#} zWs(%{>JZDZb)IWl?4H z8cioovWaql4XMd`T?;s|Y9w$|{+|v)N2LZ0lbdY;15sG*C2RY>x7kMjnn4P*h0xUf8Ps z4~yth58AX;84HTiir#6jimxDH74hJm$;0pj<~r=bu36`3PPJLV-B z==|DOiQ>lZC{;7$42heMl+DWfm*DCJ2qHd3b=r9zElHxmkM4RDBBZo>DjE0LN%B7e zr+(u?((rQ7VGrf97a)wfsptwEuws!=6oxuK1Zss}ZpiCjxTBA)x+bnc6y8{*?0{jtuxT(2QPQNURAiSGY3^JHlk&H%#zvnqt50FnE>ZaxZ}_&3ZyctRv1deDl4svC zHY1SWfkx=Es4W%xZQn&IlqO84N-FE79sVK8bWNSTv+qG@bi4Zcj2at%ZtdAgcIkwM zRqcA&|0rhHY2VHfU|!d@muo$}>|gSSrTXrM5N)Kqjw2A9p@lx0tYEB{m(niyMgjz7 z%}Qbwfo^on4mD^n9#PR~f1*?`m%~yWe0eMbh*l|)SXRB{@vjfcV9Yp1>uHfLq)l|h zFkj0(cgps3XRx>^we<_RP4qv+8;lP0pk)rGL3bWc(LJ~PzCGVXW(qai1cXg!yF9I$BC^CzcqT@Y!g z*_BR{4f?eK{k{e!GGzS@QV|{W-PQPu9<>lW;uS0lQTHCKf2k65;=lj-pPp%1udi8& z{s_=x;X;so-s;s*1OqucQ%?rgFiKKpPnzvuq7-rP0W+V@?s2M7!N#qW3#_}3@vdL>fTra!dWjHc0e6S&6Y0DmLr10`s|UN9S;@EcOAfY^;Phx z9SE@88XcfU^drzYTJ^2HQb(Tl7WE^0wjn*Tgg7hKEJgW*M-SU_WzoS!Olq&#l!L4GYY5+BHvO6D4itjJ={VcAscjC z18YEjJ%c2g`>H7E1NzWbliPZ~UmV8UdY+S1t4nu9;SaV6h85-Zh1>-Rnwa*8Xmzj0X;ZcG(seM;4TY}D z2Wud-4e;HNe_bs2O+r~}BT?9aXc~_=8j!ZC8wO&{KUTJl63vJeXMA3u-9)oTP5XR1 zjjty={Vj1XqUx>dpDu0oP8*u}UT))7Qg$6F4hV8`8Ge#vhj}8@?^*TJ23fNL_|_AX zjXX-sL}gy>H6*dCGI5KU1H-*UoW$c}Lw#AX1*DmK1&q9Kfl-7`!4agR43u#ZbLzJ` z8#kLiqP3E4^?N=2G*s#8UO^I4z?9AKLLHfLiyr+8HNIyBtf^0oPay_TNnJ9}nkhS1 zNy@Zamw6~OEBHDu zs}HLzHLT&4wkHpIIlp2S*uP#!#mmn4&mOcst6L-`{eU7og8zOjFi z>iQgCDHP7g8;UDe+RiHwrRcX+?3W9d@~Ln$HZ8LjMDA0Z*8Q0$a7u?xy&ALZ``Zo|)V#ATz(Hjlpxraj6lD-)9v!Y=}h>88V7ajtK6aB^$dkU2{9(M~Yk;acw6x zZfBnBHKeWiK#6DnwL}r!4m=SMEPgf2qd)ax24a3z9c`0@^Fz4;VR|_?fqq1J=37Qe_*Oq9U z>|W>jp}_3RTO6%<0T<~9|M@5)!G}| z#MP7Ln;3UTa|0(I-FzT%%Fc8Ub7pe=Bbl=AM*D?whV630Q$hXNz|hQxP~zo}Y~0+^ z->FF!K}ZlCvIay0Uo%J>=-hd~PWEBwT&S0E9}A*>QnlKuZ4Q8>r3B8C>T81AN>lW& z@@K=5nXvV1vH5WDqSHTgc@}Yx3viDZ{@IUIrF8;E0k%^()cTW*i5~@89kTgyRs_7s z3ZPgK9;RG+6jOGm;C2APXdAo<3Bm7)H&W|c=jk|Fx;%5}Z?n^jq^#rN8dOxpZNlhS zB1SHHC0OAs%XNhDi_L>NI!z6YqRL)m^T&}nt+)9GYgh#>qCMu1f0bDKj&xloacJ!z z#{)_{kJcJw$L8#5k&3JBzN$}*Pk77mqNDh?7vF2HYm|l{&n&y*$elAjdOHcz*3{)W zMZZ+k--#gQ_J@47+5RvmuMmjkx%Pb;Fy`7evNH&{Ucha0?K+mzFv9-WwsjlNc*$xh z(qBGSGO9`{k%a-5bYx>*P@AF7aSv(SvFFohoKXU2AlCK_;Z0cR677`JoCESS#tB~T z4j|gWM)7=s#i%ocRE;K$UPNk@-o49fhyqNEO?Z4du$T^1K8?$MP6=ZMeEs?~F3WS| z{0}p;^q2h?pfnj z&FASPZKi8P87(f$A_ml_u~NrP+O%MSO=p`7|Bl-CwC)WJLYW`3F;*(*k?Fpn(M=Or ztUu$2kD_0yCh{SGd^jrgtw6UVCGH^*%?KRe>>iH-$Q|El2Lp0uCkzV*MvES*(|V)_ zvLXNk$T;;dU9kl^8ac#Ew`?h!xtOklEW!CpDcBa!d{=yB_-u9hVrnl!ejD@WcgYuO zRXiwhJ_9=Za;4tLQW1uIdFYKMLogmgI7YWz@l_u&S)X0sW1>LUS5>%RI$qIW{q})O z#SdshYnk85YX)Q-!8N*TR2EX+Xv)`3U}m9-DS|LPGB7U~_rE{eU@8*H zMu}szb~rebBufR4zNM{Qhf(5$=5I2UFu2ni$H@WUv|&MGg;s5MZ8=$-}j z35Jh!8oJyN%6E?0^Px@HwYtev@?)M;P04b4vcM9&$YXc{?YEP7=qWD=*QHGX*0f|S zJc?1-ca#mL%F)bdoP@)4&9Ie}p9M(bg+SuFLi5`pM$)1;ekR;>J++w&IvzG-j5La=*wRAa)(C&+JIJe9o9?%j|EAU3rxT3k>GgL~>WnN|laiSH9kQm=any z`uV|}2aUu2g(qMZN=0lkqHcNao?=2r@zLSael#@}DOzv9v>WZd*|WPeKL=j^-{hjh z@B69RH25Gi_K%USjTk*NKK{=VGZk~*Q}qiTJDy9lG{?DjNFzoaPFM6-2|)E{)pS%} Is#pd67qa4=>;M1& literal 0 HcmV?d00001 diff --git a/srx_autosave/gui/main_form.ui b/srx_autosave/gui/main_form.ui new file mode 100644 index 0000000..6b0950b --- /dev/null +++ b/srx_autosave/gui/main_form.ui @@ -0,0 +1,292 @@ + + + MainWindow + + + + 0 + 0 + 1573 + 1166 + + + + MainWindow + + + + + + + + + + 256 + 256 + + + + + + + 5-ID_TopAlign.png + + + true + + + Qt::AlignCenter + + + + + + + + + + Arial + 24 + 75 + true + + + + SRX Autosave + + + Qt::AlignBottom|Qt::AlignHCenter + + + + + + + + Arial + + + + version: + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + Qt::Horizontal + + + + + + + + + + Arial + 12 + + + + Save Location: + + + + + + + + Arial + 12 + + + + + + + + + Arial + 12 + + + + Browse + + + + + + + + Arial + 12 + + + + Starting Scan ID: + + + + + + + + Arial + 12 + + + + + + + + + Arial + 12 + + + + Get Current Scan ID + + + + + + + + Arial + 12 + + + + Number of Scans: + + + + + + + + Arial + 12 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + Arial + 12 + + + + Scan Delay: + + + + + + + + Arial + 12 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + Arial + 12 + + + + 0 + + + + + + + + + + Arial + 12 + + + + Start + + + + + + + + Arial + 12 + + + + Stop + + + + + + + + + + + + From a0119d2cacfa43cb5383f22e05dc3f4faf863cdd Mon Sep 17 00:00:00 2001 From: Kiss Date: Wed, 8 Apr 2020 17:51:35 -0400 Subject: [PATCH 2/7] Forgot to add this to last commit --- srx_autosave/gui/main_form.ui | 70 +++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/srx_autosave/gui/main_form.ui b/srx_autosave/gui/main_form.ui index 6b0950b..8765fa2 100644 --- a/srx_autosave/gui/main_form.ui +++ b/srx_autosave/gui/main_form.ui @@ -11,7 +11,7 @@ - MainWindow + SRX Autosave @@ -35,7 +35,7 @@ true - Qt::AlignCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -60,7 +60,7 @@ - + Arial @@ -88,7 +88,7 @@ - + Arial @@ -101,17 +101,23 @@ - + Arial - 12 + 8 + + /home/xf05id1/current_user_data/ + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + - + Arial @@ -124,7 +130,7 @@ - + Arial @@ -137,17 +143,23 @@ - + Arial - 12 + 8 + + -1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + - + Arial @@ -160,7 +172,7 @@ - + Arial @@ -173,13 +185,19 @@ - + Arial - 12 + 8 + + 100 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + @@ -196,7 +214,7 @@ - + Arial @@ -204,18 +222,24 @@ - Scan Delay: + Scan Delay [s]: - + Arial - 12 + 8 + + 60 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + @@ -251,12 +275,18 @@ 0 + + false + + + false + - + Arial @@ -269,7 +299,7 @@ - + Arial From f0801edf7dc961dd0e0da2212eb7d3dc588f637e Mon Sep 17 00:00:00 2001 From: Kiss Date: Thu, 9 Apr 2020 14:29:09 -0400 Subject: [PATCH 3/7] Added status bar Added force restart Lock widgets during scan Changed padding around widgets Change main window icon Start browse directory in user home directory --- srx_autosave/__init__.py | 79 +++++++++++++++++++++++++++-------- srx_autosave/api.py | 25 +++++++++-- srx_autosave/gui/main_form.ui | 36 +++++++++++++++- 3 files changed, 117 insertions(+), 23 deletions(-) diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index 59163b6..e0b1426 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -1,8 +1,9 @@ # Import packages import os import sys +from pathlib import Path -from api import * +from api import (get_current_scanid, check_inputs, xrf_loop, loop_sleep) from PyQt5 import QtWidgets from PyQt5 import uic @@ -16,6 +17,12 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) uic.loadUi("gui/main_form.ui", self) + self.label_logo.setProperty("pixmap", "gui/5-ID_TopAlign.png") + self.setProperty("windowIcon", "gui/5-ID_TopAlign.png") + + self.pushButton_stop.setProperty("enabled", False) + self.setContentsMargins(20, 0, 20, 20) + self.pushButton_currentid.released.connect(self.update_scanid) self.pushButton_browse.released.connect(self.get_dir) self.pushButton_start.released.connect(self.start_loop) @@ -29,51 +36,89 @@ def get_dir(self): dialog = QFileDialog() dialog.setFileMode(QFileDialog.DirectoryOnly) - folder = dialog.getExistingDirectory(self, 'Save Location') + folder = dialog.getExistingDirectory(self, 'Save Location', str(Path.home())) if folder != "": if folder[-1] != "/" and folder[-1] != "\\": folder += os.sep self.lineEdit_savelocation.setProperty("text", folder) return - def start_loop(self): - # Check for a thread running the main loop - try: - if self.th.isRunning is True: - return - except AttributeError: - self.th = Tloop(self) - - # Check the scan parameters + def get_scan_parameters(self): self.start_id = int(self.lineEdit_startid.text()) self.wd = self.lineEdit_savelocation.text() self.N = int(self.lineEdit_numscan.text()) self.dt = int(self.lineEdit_delay.text()) - (self.start_id, self.wd, self.N, self.dt) = check_inputs(self.start_id, self.wd, self.N, self.dt) - print(self.start_id, self.wd, self.N, self.dt) + + def set_scan_parameters(self): self.lineEdit_savelocation.setProperty("text", self.wd) self.lineEdit_startid.setProperty("text", str(self.start_id)) self.lineEdit_numscan.setProperty("text", str(self.N)) self.lineEdit_delay.setProperty("text", str(self.dt)) - + + def lock_widgets(self, value): + self.lineEdit_savelocation.setProperty("enabled", value) + self.lineEdit_startid.setProperty("enabled", value) + self.lineEdit_numscan.setProperty("enabled", value) + self.lineEdit_delay.setProperty("enabled", value) + self.pushButton_browse.setProperty("enabled", value) + self.pushButton_currentid.setProperty("enabled", value) + + def start_loop(self): + # Check for a thread running the main loop + try: + if self.th.isRunning is True: + self.th.stop() + except AttributeError: + self.th = Tloop(self) + + # Check the scan parameters + self.get_scan_parameters() + # (self.start_id, self.wd, self.N, self.dt) = check_inputs(self.start_id, self.wd, self.N, self.dt) + tmp = check_inputs(self.start_id, self.wd, self.N, self.dt) + self.start_id = tmp[0] + self.wd = tmp[1] + self.N = tmp[2] + self.dt = tmp[3] + # print(self.start_id, self.wd, self.N, self.dt) + self.set_scan_parameters() + # Change to the proper working directory - os.chdir(self.wd) + try: + os.chdir(self.wd) + except FileNotFoundError as ex: + self.label_status.setProperty("text", str(ex)) + print(ex) + return # Start the thread + self.pushButton_stop.setProperty("enabled", True) + self.pushButton_start.setProperty("text", "Force Restart") + self.lock_widgets(False) self.th.start() return def stop_loop(self): - self.th.stop() + try: + self.th.stop() + self.pushButton_stop.setProperty("enabled", False) + self.pushButton_start.setProperty("text", "Start") + self.lock_widgets(True) + except AttributeError: + pass return def update_progress(self, x): self.progressBar.setProperty("value", x) return + def update_status(self, x): + self.label_status.setProperty("text", x) + return + class Tloop(QThread): signal_update_progressBar = pyqtSignal(float) + signal_update_status = pyqtSignal(str) DT = 0.01 # sleep time def __init__(self, form): @@ -81,6 +126,7 @@ def __init__(self, form): self.form = form self.isRunning = False self.signal_update_progressBar.connect(self.form.update_progress) + self.signal_update_status.connect(self.form.update_status) def __del__(self): self.isRunning = False @@ -145,7 +191,6 @@ def autosave_xrf(start_id, wd="", N=1000, dt=60): while True: xrf_loop(start_id, N) loop_sleep(dt) - except KeyboardInterrupt: print("\n\nExiting SRX AutoSave.") pass diff --git a/srx_autosave/api.py b/srx_autosave/api.py index 4689a82..e99a23d 100644 --- a/srx_autosave/api.py +++ b/srx_autosave/api.py @@ -17,7 +17,11 @@ # Register the data broker -db = Broker.named("temp") +try: + db = Broker.named("srx") +except AttributeError: + db = Broker.named("temp") + print("Using temporary databroker.") # ---------------------------------------------------------------------- @@ -134,13 +138,22 @@ def check_inputs(start_id, wd, N, dt): return (start_id, wd, N, dt) -def xrf_loop(start_id, N): +def xrf_loop(start_id, N, gui=None): num = np.arange(start_id, start_id + N, 1) for i in range(N): # Check if the scan ID exists # We could make a function to check if current scan ID # >= this value. Or same thing but return True/False scanid = int(num[i]) + + if gui is not None: + gui.signal_update_status(f"Making {scanid}...") + + if gui.isRunning is False: + gui.signal_update_status(f"SRX Autosave stopped.") + gui.signal_update_progressBar.emit(0) + return + try: h = db[scanid] except Exception: @@ -183,13 +196,17 @@ def loop_sleep(dt, gui=None): t0 = ttime.monotonic() del_t = 0.0 while del_t < dt: - print(" %02d seconds remaining..." % (dt - del_t), end="\r", flush=True) + str_status = "%02d seconds remaining..." % (dt - del_t) + print(" %s" % str_status, end="\r", flush=True) if gui is not None: gui.signal_update_progressBar.emit(100 * del_t / dt) + gui.signal_update_status.emit(str_status) if gui.isRunning is False: + print('SRX Autosave stopped.') + gui.signal_update_status.emit('SRX Autosave stopped.') + gui.signal_update_progressBar.emit(0) break ttime.sleep(DT) del_t = ttime.monotonic() - t0 print("--------------------------------------------------") return - diff --git a/srx_autosave/gui/main_form.ui b/srx_autosave/gui/main_form.ui index 8765fa2..e7d0da7 100644 --- a/srx_autosave/gui/main_form.ui +++ b/srx_autosave/gui/main_form.ui @@ -6,19 +6,23 @@ 0 0 - 1573 + 1538 1166 SRX Autosave + + + 5-ID_TopAlign.png5-ID_TopAlign.png + - + 256 @@ -87,6 +91,9 @@ + + 0 + @@ -98,6 +105,9 @@ Save Location: + + 0 + @@ -140,6 +150,9 @@ Starting Scan ID: + + 0 + @@ -182,6 +195,9 @@ Number of Scans: + + 0 + @@ -224,6 +240,9 @@ Scan Delay [s]: + + 0 + @@ -264,6 +283,19 @@ + + + + + Arial + 12 + + + + Welcome to SRX Autosave! + + + From 509cd96a0938c5229c7a47b07c1622dee10ec5a3 Mon Sep 17 00:00:00 2001 From: Andy Kiss Date: Thu, 9 Apr 2020 15:12:05 -0400 Subject: [PATCH 4/7] Added try/except for imports of pyXRF and caget. Fixed bug with emitting signals and sending the gui. --- srx_autosave/__init__.py | 2 +- srx_autosave/api.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index e0b1426..6227873 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -140,7 +140,7 @@ def run(self): try: while self.isRunning: - xrf_loop(self.form.start_id, self.form.N) + xrf_loop(self.form.start_id, self.form.N, gui=self) loop_sleep(self.form.dt, gui=self) except KeyboardInterrupt: print("\n\nStopping SRX Autosave loop.") diff --git a/srx_autosave/api.py b/srx_autosave/api.py index e99a23d..ccbba7b 100644 --- a/srx_autosave/api.py +++ b/srx_autosave/api.py @@ -5,7 +5,16 @@ import glob from databroker import Broker -# from pyxrf.api import * +try: + from pyxrf.api import * +except ImportError: + print("Error importing pyXRF. Continuing without import.") + +try: + from epics import caget +except ImportError: + print("Error importing caget. Continuing without import.") + """ SRX Autosave APIs @@ -147,10 +156,10 @@ def xrf_loop(start_id, N, gui=None): scanid = int(num[i]) if gui is not None: - gui.signal_update_status(f"Making {scanid}...") + gui.signal_update_status.emit(f"Making {scanid}...") if gui.isRunning is False: - gui.signal_update_status(f"SRX Autosave stopped.") + gui.signal_update_status.emit(f"SRX Autosave stopped.") gui.signal_update_progressBar.emit(0) return @@ -177,7 +186,8 @@ def xrf_loop(start_id, N, gui=None): try: # db[scanid].stop['time'] make_hdf(scanid, completed_scans_only=True) - except Exception: + except Exception as ex: + print(ex) pass else: print("XRF HDF5 already created.") From 754d3a2c71b05a90bf34dcbda93cb44f849345cc Mon Sep 17 00:00:00 2001 From: Kiss Date: Thu, 9 Apr 2020 16:06:35 -0400 Subject: [PATCH 5/7] Made paths to UI form and SRX icon relative to __init__ file. Accounted for Hi-DPI monitors --- srx_autosave/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index 6227873..b6db097 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -7,18 +7,23 @@ from PyQt5 import QtWidgets from PyQt5 import uic -from PyQt5.QtCore import QThread, pyqtSignal +from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtWidgets import QFileDialog +# For Hi-DPI monitors +QtWidgets.QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) +QtWidgets.QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) + class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - uic.loadUi("gui/main_form.ui", self) + path = Path(__file__).parent + uic.loadUi(path / "gui/main_form.ui", self) - self.label_logo.setProperty("pixmap", "gui/5-ID_TopAlign.png") - self.setProperty("windowIcon", "gui/5-ID_TopAlign.png") + self.label_logo.setProperty("pixmap", path / "gui/5-ID_TopAlign.png") + self.setProperty("windowIcon", path / "gui/5-ID_TopAlign.png") self.pushButton_stop.setProperty("enabled", False) self.setContentsMargins(20, 0, 20, 20) @@ -73,7 +78,6 @@ def start_loop(self): # Check the scan parameters self.get_scan_parameters() - # (self.start_id, self.wd, self.N, self.dt) = check_inputs(self.start_id, self.wd, self.N, self.dt) tmp = check_inputs(self.start_id, self.wd, self.N, self.dt) self.start_id = tmp[0] self.wd = tmp[1] From bd479347676d68fd98210ad02065010bbe99b373 Mon Sep 17 00:00:00 2001 From: Andy Kiss Date: Thu, 9 Apr 2020 16:53:57 -0400 Subject: [PATCH 6/7] Made form work for regular (non-HiDPI) monitors. Added +1 button. --- srx_autosave/__init__.py | 5 +++ srx_autosave/gui/main_form.ui | 84 +++++++++++++++++++++++++++-------- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index b6db097..455ba94 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -29,6 +29,7 @@ def __init__(self, *args, **kwargs): self.setContentsMargins(20, 0, 20, 20) self.pushButton_currentid.released.connect(self.update_scanid) + self.pushButton_plus1.released.connect(self.update_scanid_plus1) self.pushButton_browse.released.connect(self.get_dir) self.pushButton_start.released.connect(self.start_loop) self.pushButton_stop.released.connect(self.stop_loop) @@ -37,6 +38,10 @@ def update_scanid(self): self.lineEdit_startid.setProperty("text", str(get_current_scanid())) return + def update_scanid_plus1(self): + self.lineEdit_startid.setProperty("text", str(get_current_scanid()+1)) + return + def get_dir(self): dialog = QFileDialog() dialog.setFileMode(QFileDialog.DirectoryOnly) diff --git a/srx_autosave/gui/main_form.ui b/srx_autosave/gui/main_form.ui index e7d0da7..843d146 100644 --- a/srx_autosave/gui/main_form.ui +++ b/srx_autosave/gui/main_form.ui @@ -6,8 +6,8 @@ 0 0 - 1538 - 1166 + 556 + 383 @@ -23,12 +23,36 @@ + + + 0 + 0 + + + + + 128 + 128 + + 256 256 + + + 0 + 0 + + + + + 128 + 128 + + @@ -44,9 +68,21 @@ - + + + + 0 + 0 + + + + + 400 + 0 + + Arial @@ -91,9 +127,6 @@ - - 0 - @@ -172,17 +205,33 @@ - - - - Arial - 12 - - - - Get Current Scan ID - - + + + + + + Arial + 12 + + + + Get Current Scan ID + + + + + + + + 12 + + + + +1 + + + + @@ -347,7 +396,6 @@ - From 3af171b3c4f9b3bcc15861590632af7937ef7164 Mon Sep 17 00:00:00 2001 From: Kiss Date: Fri, 10 Apr 2020 13:41:10 -0400 Subject: [PATCH 7/7] Added +1 to enabled/disabled function --- srx_autosave/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/srx_autosave/__init__.py b/srx_autosave/__init__.py index 455ba94..6726034 100644 --- a/srx_autosave/__init__.py +++ b/srx_autosave/__init__.py @@ -72,6 +72,7 @@ def lock_widgets(self, value): self.lineEdit_delay.setProperty("enabled", value) self.pushButton_browse.setProperty("enabled", value) self.pushButton_currentid.setProperty("enabled", value) + self.pushButton_plus1.setProperty("enabled", value) def start_loop(self): # Check for a thread running the main loop