From e7ca6f4e336476f1f3c9358f41369e794a44a28a Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 17:29:55 -0500 Subject: [PATCH 01/28] MNT #48 delete old archive content --- archive/compare_centers.py | 119 --- archive/dirWatch.py | 77 -- archive/docs/dirWatch.html | 61 -- archive/docs/index.html | 176 ---- archive/docs/index.md | 79 -- archive/docs/localConfig.html | 63 -- archive/docs/plot.html | 56 -- archive/docs/prjPySpec.html | 121 --- archive/docs/pvConnect.html | 224 ----- archive/docs/pvwatch.html | 74 -- archive/docs/specplot.html | 46 - archive/docs/specplotsAllScans.html | 56 -- archive/docs/wwwServerTransfers.html | 59 -- archive/motorchanges20110913.sed | 28 - archive/ploticus/livedata.pl | 1157 -------------------------- archive/ploticus/livedata.png | Bin 8541 -> 0 bytes archive/pvlist_2011-09-09.xml | 194 ----- archive/repair_old.py | 63 -- archive/try_reduce.py | 55 -- 19 files changed, 2708 deletions(-) delete mode 100755 archive/compare_centers.py delete mode 100755 archive/dirWatch.py delete mode 100644 archive/docs/dirWatch.html delete mode 100644 archive/docs/index.html delete mode 100644 archive/docs/index.md delete mode 100644 archive/docs/localConfig.html delete mode 100644 archive/docs/plot.html delete mode 100644 archive/docs/prjPySpec.html delete mode 100644 archive/docs/pvConnect.html delete mode 100644 archive/docs/pvwatch.html delete mode 100644 archive/docs/specplot.html delete mode 100644 archive/docs/specplotsAllScans.html delete mode 100644 archive/docs/wwwServerTransfers.html delete mode 100644 archive/motorchanges20110913.sed delete mode 100644 archive/ploticus/livedata.pl delete mode 100644 archive/ploticus/livedata.png delete mode 100644 archive/pvlist_2011-09-09.xml delete mode 100755 archive/repair_old.py delete mode 100755 archive/try_reduce.py diff --git a/archive/compare_centers.py b/archive/compare_centers.py deleted file mode 100755 index 47f8250..0000000 --- a/archive/compare_centers.py +++ /dev/null @@ -1,119 +0,0 @@ - -""" -compute centroid of USAXS fly scan data -""" - -import h5py -import numpy -import os - -import reduceFlyData - - -path = os.path.dirname(__file__) -TESTFILE = os.path.join(path, "testdata", "Blank_0016.h5") -OUTFILE = os.path.join(path, "testdata", "Blank_0016_center_calc.h5") - - -def centroid(x, y): - '''compute centroid of y(x)''' - import scipy.integrate - weight = y*y - top = scipy.integrate.simps(x*weight, x) - bottom = scipy.integrate.simps(weight, x) - center = top/bottom - return center - - -def flyscan_centroid(dataset, cutoff_fraction): - """ - compute the center of the fly scan from data near the peak - - PARAMETERS - - dataset : dict - A dictionary as received from the `reduceFlyData()` object. - Contains reduced USAXS data. - - Expect to find these keys in *dataset*: - - * ar : numpy array of angles - * R: numpy array of reduced intensities - * R_max: maximum value of *R* - * ar_r_peak: value of *ar* at *R_max* - - cutoff_fraction : float - Discard any data with R < cutoff_fraction * max(R) - - RETURNS - - tuple - (center, ar, R): - - * center: computed centroid - * ar: (numpy) array of the *ar* axis near the peak center - * R: (numpy) array of the *R* axis near the peak center - - """ - x = dataset["ar"] - y = dataset["R"] - peak_index = numpy.where(x==dataset["ar_r_peak"])[0][0] - cutoff = dataset["R_max"] * cutoff_fraction - - print "peak index:", peak_index - - # walk down each side from the peak - n = len(x) - - pLo = peak_index - while pLo >= 0 and y[pLo] > cutoff: - pLo -= 1 - - pHi = peak_index + 1 - while pHi < n and y[pHi] > cutoff: - pHi += 1 - - # enforce boundaries - pLo = max(0, pLo+1) - pHi = min(n-1, pHi-1) - - print pLo, pHi - ar = x[pLo:pHi] - R = y[pLo:pHi] - - cen = centroid(ar, R) - return cen, ar, R - - -def main(): - fly = reduceFlyData.UsaxsFlyScan(TESTFILE) - fly.reduce() - full = fly.reduced["full"] - - print full.keys() - print "ar[:5]", full['ar'][:5] - for k in "R_max r_peak ar_r_peak".split(): - print k, full[k] - - cen, ar, R = flyscan_centroid(full, 0.4) - print "centroid:", cen - - with h5py.File(OUTFILE, "w") as h5: - h5.attrs["default"] = "entry" - nxentry = h5.create_group("entry") - nxentry.attrs["NX_class"] = "NXentry" - nxentry.attrs["default"] = "data" - nxdata = nxentry.create_group("data") - nxdata.attrs["NX_class"] = "NXdata" - nxdata.attrs["signal"] = "R" - nxdata.attrs["axes"] = "ar" - ds = nxdata.create_dataset("ar", data=ar) - ds.attrs["units"] = "degrees" - ds = nxdata.create_dataset("R", data=R) - ds.attrs["units"] = "not applicable" - ds = nxdata.create_dataset("cen", data=[cen]) - ds.attrs["units"] = "degrees" - - -if __name__ == "__main__": - main() diff --git a/archive/dirWatch.py b/archive/dirWatch.py deleted file mode 100755 index 1b68272..0000000 --- a/archive/dirWatch.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python -########### SVN repository information ################### -# $Date$ -# $Author$ -# $Revision$ -# $URL$ -# $Id$ -########### SVN repository information ################### - -''' - watch a directory (and all directories beneath) for new versions of SPEC data files - - TODO: this is an incomplete project, it seems -''' - -import os -import string -#import sys -import time -import localConfig # definitions for 15ID - - -class dirWatch: - '''get the mtime from SPEC data files under a directory''' - - def __init__(self, dir): - '''define the interface components''' - self.baseDir = dir - self.db = {} - self.mtime = -1 - self.file = '' - self.discover() - - def discover(self): - ''' - walk the directory tree looking for new treasures - @TODO: this algorithm is not very efficient - It keeps regenerating files even when the source has not changed. - ''' - if not os.path.exists(self.baseDir): - return # cannot find this item - now = time.time() - time_window = now - localConfig.TIME_WINDOWS_SECS - for (dirpath, dirnames, filenames) in os.walk(self.baseDir): - tail = os.path.split(os.path.abspath(dirpath))[-1] - if tail in localConfig.SKIP_DIRS: - continue # weed out unwanted directories - if len(filenames) == 0: - continue # no files in this directory - absdir = os.path.abspath(dirpath) - for item in filenames: - ext = string.lower(os.path.splitext(item)[-1]) - if not ext in localConfig.KEEP_EXTS: - continue # only look at certain files - full = os.path.join(dirpath, item) - mtime = os.path.getmtime(os.path.join(absdir, item)) - if mtime < time_window: - continue # only examine recent files - self.db[full] = mtime - if mtime > self.mtime: - self.mtime = mtime - self.file = full - - -def ts2text(ts): - '''convert a floating-point timestamp into text''' - return time.strftime(localConfig.TIMESTAMP_FORMAT, time.localtime(ts)) - - -if __name__ == '__main__': - first = dirWatch(localConfig.LOCAL_USAXS_DATA_DIR) - ts = ts2text(first.mtime) - keys = first.db.keys() - keys.sort() - #print len(first.db), ts, first.file - for item in keys: - print ts2text(first.db[item]), item diff --git a/archive/docs/dirWatch.html b/archive/docs/dirWatch.html deleted file mode 100644 index 1704d73..0000000 --- a/archive/docs/dirWatch.html +++ /dev/null @@ -1,61 +0,0 @@ - - -Python: module dirWatch - - - - -
 
- 
dirWatch
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/dirWatch.py
-

watch a directory (and all directories beneath) for new versions of SPEC data files

-TODO: this is an incomplete project, it seems

-

- - - - - -
 
-Modules
       
localConfig
-
os
-
string
-
time
-

- - - - - -
 
-Classes
       
-
dirWatch -
-

- - - - - - - -
 
-class dirWatch
   get the mtime from SPEC data files under a directory
 
 Methods defined here:
-
__init__(self, dir)
define the interface components
- -
discover(self)
walk the directory tree looking for new treasures
-@TODO: this algorithm is not very efficient
-    It keeps regenerating files even when the source has not changed.
- -

- - - - - -
 
-Functions
       
ts2text(ts)
convert a floating-point timestamp into text
-
- \ No newline at end of file diff --git a/archive/docs/index.html b/archive/docs/index.html deleted file mode 100644 index 6afdb17..0000000 --- a/archive/docs/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - -documentation for ''livedata'' source code - - - - - - -

documentation for ''livedata'' source code

- - -$Id$ - - -

TERSE EXPLANATION OF MAJOR CODE BLOCKS

- -
-Makefile
-	--- calls starts/stops/restarts manage.csh
-
-manage.csh
-	--- starts/stops/restarts pvwatch.py
-	--- sysAdmins could point to this from /etc/init.d or similar
-
-pvwatch.py
-	--- *** this is the main code of this "package" ***
-	--- uses these resources
-		pvlist.xml
-		pvConnect.py
-		livedata.xsl
-		localConfig.py
-		prjPySpec.py
-		raw-table.xsl
-		specplot.py
-		wwwServerTransfers.py
-	--- and generates files in /data/www/livedata
-		index.html
-		livedata.png
-		raw-report.html
-		report.xml
-		specmacro.txt
-	--- copies these to the XSD WWW server as they are generated
-
-buildSpecPlots.sh
-	--- uses these resources
-		specplotsAllScans.py
-		specplot.py
-	--- and generates files and subdirs in
-		/data/www/livedata/specplots
-	--- rsyncs these to the XSD WWW server as they are generated
-This script could be called from a cron job every 5 minutes or so.  Not any more frequent, though.
-
-dirWatch.py
-	--- will probably go away - ignore it
-
- -

NOTE

-While many of the Python routines have code that executes -when called directly from the command line, don't do this -unless you are CERTAIN of what will happen. Some routines -change the local WWW dir and some even push files to the -XSD WWW server. - - -

LIST OF FILES

-
- -
buildSpecPlots.sh
-
- script to crawl directories for SPEC data files - and build default plots for all scans -
- -
dirWatch.py
-
- code under development -
- -
__init__.py
-
- tells Python this directory is a "package" -
- -
livedata.xsl
-
- XSLT transform to build index.html from XML data -
- -
localConfig.py
-
- global Python variables for this package -
- -
Makefile
-
- starts/stops the manage.csh script -
- -
manage.csh
-
- sysAdmin script to start/stop pvwatch.py -
- -
plot.py
-
- plots last n USAXS scans from one SPEC data file -
- -
prjPySpec.py
-
- reads SPEC data files -
- -
pvConnect.py
-
- EPICS PV connection management -
- -
pvlist.xml
-
- list of EPICS process variables to be monitored/reported -
- -
pvlist.xsl
-
- convenience XSLT transform to view pvlist.xml in a browser -
- -
pvwatch.py
-
- watches PVs, writes reports, makes livedata page content -
- -
raw-table.xsl
-
- XSLT transform to build raw-report.html from XML data -
- -
README
-
- basic documentation for code developers -
- -
specplot.py
-
- default plot of one scan in one SPEC data file -
- -
specplotsAllScans.py
-
- default plots of all scans in one SPEC data file -
- -
www/
-
- soft link to local WWW root directory (/data/www) -
- -
wwwServerTransfers.py
-
- common code to scp files from usaxscontrol2.cars to usaxs.xor -
- -
- - diff --git a/archive/docs/index.md b/archive/docs/index.md deleted file mode 100644 index ffae8e3..0000000 --- a/archive/docs/index.md +++ /dev/null @@ -1,79 +0,0 @@ -# `livedata` source code - -**Contents** -- [`livedata` source code](#livedata-source-code) - - [Major Code Blocks](#major-code-blocks) - - [NOTE](#note) - - [List of files](#list-of-files) - - -## Major Code Blocks - - Makefile - --- calls starts/stops/restarts manage.csh - - manage.csh - --- starts/stops/restarts pvwatch.py - --- sysAdmins could point to this from /etc/init.d or similar - - pvwatch.py - --- *** this is the main code of this "package" *** - --- uses these resources - pvlist.xml - pvConnect.py - livedata.xsl - localConfig.py - prjPySpec.py - raw-table.xsl - specplot.py - wwwServerTransfers.py - --- and generates files in /data/www/livedata - index.html - livedata.png - raw-report.html - report.xml - specmacro.txt - --- copies these to the XSD WWW server as they are generated - - buildSpecPlots.sh - --- uses these resources - specplotsAllScans.py - specplot.py - --- and generates files and subdirs in - /data/www/livedata/specplots - --- rsyncs these to the XSD WWW server as they are generated - This script could be called from a cron job every 5 minutes or so. Not any more frequent, though. - - dirWatch.py - --- will probably go away - ignore it - -### NOTE - -While many of the Python routines have code that executes when called -directly from the command line, don\'t do this unless you are CERTAIN of -what will happen. Some routines change the local WWW dir and some even -push files to the XSD WWW server. - -## List of files - -file | remarks ---- | --- -__init__.py | tells Python this directory is a \"package\" -buildSpecPlots.sh | script to crawl directories for SPEC data files and build default plots for all scans -dirWatch.py | code under development -livedata.xsl | XSLT transform to build index.html from XML data -localConfig.py | global Python variables for this package -Makefile | starts/stops the manage.csh script -manage.csh | sysAdmin script to start/stop pvwatch.py -plot.py | plots last $n$ USAXS scans from one SPEC data file -prjPySpec.py | reads SPEC data files -pvConnect.py | EPICS PV connection management -pvlist.xml | list of EPICS process variables to be monitored/reported -pvlist.xsl | convenience XSLT transform to view pvlist.xml in a browser -pvwatch.py | watches PVs, writes reports, makes livedata page content -raw-table.xsl | XSLT transform to build raw-report.html from XML data -README | basic documentation for code developers -specplot.py | default plot of one scan in one SPEC data file -specplotsAllScans.py | default plots of all scans in one SPEC data file -www/ | soft link to local WWW root directory (/data/www) -wwwServerTransfers.py | common code to scp files from usaxscontrol2.cars to usaxs.xor diff --git a/archive/docs/localConfig.html b/archive/docs/localConfig.html deleted file mode 100644 index 6c638cb..0000000 --- a/archive/docs/localConfig.html +++ /dev/null @@ -1,63 +0,0 @@ - - -Python: module localConfig - - - - -
 
- 
localConfig
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/localConfig.py
-

define 15ID-D USAXS constants for these Python tools

-

- - - - - -
 
-Modules
       
os
-

- - - - - -
 
-Data
       A_keV = 12.3984244
-BASE_DIR = '/data/USAXS_data/'
-FIXED_VF_GAIN = 100000.0
-HOME_DIR = '/home/beams/S15USAXS'
-HTML_INDEX_FILE = 'index.html'
-HTML_RAWREPORT_FILE = 'raw-report.html'
-KEEP_EXTS = ['.dat']
-LINE_ONLY_THRESHOLD = 400
-LIVEDATA_XSL_STYLESHEET = 'livedata.xsl'
-LOCAL_DATA_DIR = '/data'
-LOCAL_PLOTFILE = 'livedata.png'
-LOCAL_SPECPLOTS_DIR = '/data/www/livedata/specplots'
-LOCAL_USAXS_DATA_DIR = '/data/USAXS_data'
-LOCAL_WWW_LIVEDATA_DIR = '/data/www/livedata'
-LOG_INTERVAL_S = 300
-NUM_SCANS_PLOTTED = 5
-PLOTICUS = '/home/beams/S15USAXS/bin/pl'
-PLOTICUS_PREFABS = '/home/beams/S15USAXS/Documents/ploticus/pl241src/prefabs'
-PLOT_FORMAT = 'png'
-RAWTABLE_XSL_STYLESHEET = 'raw-table.xsl'
-REPORT_INTERVAL_S = 10
-SKIP_DIRS = ['.AppleFileInfo']
-SLEEP_INTERVAL_S = 0.10000000000000001
-SOURCECODE_BASE = '/home/beams/S15USAXS/Documents/eclipse/USAXS/livedata'
-SPECMACRO_TXT_FILE = 'specmacro.txt'
-SPEC_FILE = '/data/USAXS_data/2010-03/03_27.dat'
-TEST_PLOTFILE = 'pete.png'
-TEST_PLOTICUS_COMMAND_FILE = 'pete.pl'
-TEST_SPEC_DATA = '/data/USAXS_data/2010-03/03_25.dat'
-TEST_SPEC_SCAN_NUMBER = 1
-TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
-TIME_WINDOWS_SECS = 15552000
-WWW_SPECPLOTS_DIR = 'specplots'
-XML_REPORT_FILE = 'report.xml'
-XSLT_COMMAND = '/usr/bin/xsltproc --novalid %s '
- \ No newline at end of file diff --git a/archive/docs/plot.html b/archive/docs/plot.html deleted file mode 100644 index d3b6455..0000000 --- a/archive/docs/plot.html +++ /dev/null @@ -1,56 +0,0 @@ - - -Python: module plot - - - - -
 
- 
plot
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/plot.py
-

read a SPEC data file and plot the last n USAXS scans for the livedata WWW page
-@note: copies plot file to USAXS site on XSD WWW server

-

- - - - - -
 
-Modules
       
datetime
-localConfig
-math
-
os
-prjPySpec
-shutil
-
tempfile
-time
-wwwServerTransfers
-

- - - - - -
 
-Functions
       
calc_usaxs_data(specScan)
calculate the USAXS R wave from the raw SPEC scan data
-
extract_USAXS_data(specData, scanList)
extract the USAXS R(Q) profiles (ignoring error estimates)
-@param specData: as returned by prjPySpec.specDataFile(specFile)
-@param scanList: integer list of scan numbers
-@return: list of dictionaries with reduced USAXS R(Q)
-
format_as_ploticus_data(usaxs)
build the data portion of the ploticus script
-@return: dictionary of formatted data and R(Q) limits
-
last_n_scans(scans, maxScans)
find the last maxScans scans in the specData
-@param scans: specDataFileScan instance list
-@param maxScans: maximum number of scans to find
-@return: integer list of scan numbers where len() <= maxScans
-
make_ploticus_command_script(specFile, tempDataFile, ploticus_data, usaxs)
build the command script for ploticus
-
ploticus_commands(db, usaxs)
plot USAXS data using ploticus
-
run_ploticus(script, localPlotFile)
use ploticus to generate the localPlotFile image file
-
run_ploticus_command_script(scriptFile)
use ploticus to generate the plot image file
-
update_n_plots(specFile, numScans)
read the SPEC file and grab n scans
-
write_ploticus_command_script(script)
write the command script for ploticus
-
write_ploticus_data(data)
write the data file for ploticus
-
- \ No newline at end of file diff --git a/archive/docs/prjPySpec.html b/archive/docs/prjPySpec.html deleted file mode 100644 index 22dc333..0000000 --- a/archive/docs/prjPySpec.html +++ /dev/null @@ -1,121 +0,0 @@ - - -Python: module prjPySpec - - - - -
 
- 
prjPySpec
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/prjPySpec.py
-

  Purpose:
-  Provides a set of classes to read the contents of a spec data file.

-########### SVN repository information ###################
-# $Date: 2010-06-05 11:39:55 -0500 (Sat, 05 Jun 2010) $
-# $Author: jemian $
-# $Revision: 288 $
-# $HeadURL: https://subversion.xor.aps.anl.gov/small_angle/USAXS/livedata/prjPySpec.py $
-# $Id: prjPySpec.py 288 2010-06-05 16:39:55Z jemian $
-########### SVN repository information ###################

-  author: Pete Jemian, jemian@anl.gov
-  Further comments:
-  Includes the UNICAT extensions which write additional floating point
-  information in the scan headers using #H/#V pairs of labels/values.
-  The user should create a class instance for each spec data file,
-  specifying the file reference (by path reference as needed)
-  and the internal routines will take care of all that is necessary
-  to read and interpret the information.
-  Dependencies:
-    os: operating system module
-    re: regular expression module

-

- - - - - -
 
-Modules
       
os
-
re
-
sys
-

- - - - - -
 
-Classes
       
-
specDataFile -
specDataFileHeader -
specDataFileScan -
-

- - - - - - - -
 
-class specDataFile
   contents of a spec data file
 
 Methods defined here:
-
__init__(self, file)
- -
read(self)
Reads a spec data file
- -
-Data and other attributes defined here:
-
errMsg = ''
- -
fileName = ''
- -
headers = []
- -
parts = ''
- -
readOK = -1
- -
scans = []
- -

- - - - - - - -
 
-class specDataFileHeader
   contents of a spec data file header (#F) section
 
 Methods defined here:
-
__init__(self, buf)
- -
interpret(self)
interpret the supplied buffer with the spec data file header
- -

- - - - - - - -
 
-class specDataFileScan
   contents of a spec data file scan (#S) section
 
 Methods defined here:
-
__init__(self, header, buf)
- -
interpret(self)
interpret the supplied buffer with the spec scan data
- -

- - - - - -
 
-Functions
       
specScanLine_stripKey(line)
return everything after the first space on the line from the spec data file
-
- \ No newline at end of file diff --git a/archive/docs/pvConnect.html b/archive/docs/pvConnect.html deleted file mode 100644 index b0c21d3..0000000 --- a/archive/docs/pvConnect.html +++ /dev/null @@ -1,224 +0,0 @@ - - -Python: module pvConnect - - - - -
 
- 
pvConnect
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/pvConnect.py
-

simplified connections to an EPICS PV using CaChannel

-Provides these classes:

-    CaPollWx
-        Use in WX-based GUIs to call ca.poll() in the background
-        @param interval_s: [float] interval between calls to ca.poll()

-    EpicsPv
-        manage a CaChannel connection with an EPICS PV
-        @param name: [string] EPICS PV to connect

-Provides these utility routines:

-    on_exit(timer)
-        Exit handler to stop the ca.poll()
-        @param timer: CaPollWx object

-    CaPoll()
-        Use in non-GUI scripts to call ca.poll() in the background

-    GetRTYP(pv)
-        Returns the record type of "pv"
-        @param pv:[string]
-        @return: [string] EPICS record type or None if cannot connect

-    testConnect(pv)
-        Tests if a CaChannel connection can be established to "pv"
-        @param pv:[string]
-        @return: True if can connect, otherwise False

-    receiver(value)
-        Example response to an EPICS monitor on the channel
-        @param value: str(epics_args['pv_value'])
-    
-    MonitoredConnection(callback, pv = None)
-        Connect to the EPICS PV and monitor it.
-        Invoke the callback routine specified by the caller.
-        Set the PV name to be reported in the user_args term of the callback

-@version: 
-########### SVN repository information ###################
-# $Date: 2010-07-21 11:12:17 -0500 (Wed, 21 Jul 2010) $
-# $Author: jemian $
-# $Revision: 345 $
-# $URL: https://subversion.xor.aps.anl.gov/small_angle/USAXS/livedata/pvConnect.py $
-# $Id: pvConnect.py 345 2010-07-21 16:12:17Z jemian $
-########### SVN repository information ###################

-

- - - - - -
 
-Modules
       
CaChannel
-
sys
-
time
-
wx
-

- - - - - -
 
-Classes
       
-
CaPollWx -
EpicsPv -
-

- - - - - - - -
 
-class CaPollWx
   Use in WX-based GUIs to call ca.poll() in the background

-Set up a separate thread to trigger periodic calls to the 
-EPICS CaChannel.ca.poll() connection.  Awaiting (a.k.a., 
-outstanding or pending) channel access background 
-activity executes during the poll.  Calls pend_event() 
-with a timeout short enough to poll.  

-The default polling interval is 0.1 second.

-@note: The code will silently do nothing if wx was not imported.
-This routine use the wx.PyTimer() to call ca.poll() frequently 
-during the main WX event loop.

-@warning: Only use this in a routine that has already called 
-wx.App() or an exception will occur.  
-Command line code will need to call ca.poll() using a different 
-method (such as CaPoll() below).
 
 Methods defined here:
-
GetInterval(self)
return the current interval between calls to CaChannel.ca.poll()
- -
SetInterval(self, interval_s)
set the next interval between calls to CaChannel.ca.poll()
- -
__init__(self, interval_s=0.10000000000000001)
@param interval_s: [float] interval between calls to ca.poll()
- -
poll(self)
Poll for changes in Channel
- -
start(self)
start polling
- -
stop(self)
stop polling
- -

- - - - - - - -
 
-class EpicsPv
   manage a connection with an EPICS PV
 
 Methods defined here:
-
GetChan(self)
@return: CaChannel channel
- -
GetEpicsArgs(self)
@return: epics_args from the most recent monitor event
- -
GetPv(self)
@return: PV name
- -
GetUserArgs(self)
@return: user_args from the most recent monitor event
- -
GetUserCallback(self)
return the callback function supplied by the caller
-values will be set by user_callback(value)
-@return: function object
- -
GetValue(self)
@return: value from EPICS from the most recent monitor event
- -
MonitoredConnection(self, callback, pv=None)
Connect to the EPICS PV and monitor it.
-Invoke the callback routine specified by the caller.
-Set the PV name to be reported in the user_args term of the callback
- -
SetMask(self, mask)
Define the mask used when applying a channel monitor.
-The default is: self.mask = CaChannel.ca.DBE_VALUE
-@param mask: as defined in the CaChannel manual
- -
SetPv(self, name)
redefine the PV name only if there is no connection
-@param name: valid EPICS PV name
- -
SetUserArgs(self, user_args)
define the user_args tuple to use when monitoring
-@param user_args: tuple of user data (for use in user_callback function)
- -
SetUserCallback(self, user_callback)
Set the callback function supplied by the caller
-values will be set by user_callback(value)
-@param user_callback: function object
- -
__init__(self, name)
initialize the class and set default values
-@param name: [string] EPICS PV to connect
- -
callback(self, epics_args, user_args)
receive an EPICS callback, copy epics_args and user_args, then call user
- -
connect(self)
initiate the connection with EPICS
- -
connectw(self)
initiate the connection with EPICS, standard wait for the connection
- -
monitor(self)
Initiate a monitor on the EPICS channel, delivering the 
-CaChannel callback to the supplied function.

-@note: Example:
-    ch = EpicsPv(test_pv)
-    ch.connectw()
-    uargs = test_pv, widget.SetLabel
-    ch.SetUserArgs(uargs)
-    ch.SetUserCallback(myCallback)
-    ch.monitor()

-@warning: At this time, there is not an easy way to turn off monitors.
-    Instead, ch.release() the channel (which will set self.chan = None),
-    To re-start a monitor after a ch.release(), connect as usual and start
-    the monitor again, as the first time.
- -
release(self)
release the connection with EPICS
-@note: Release ALL channels before calling on_exit()
- -

- - - - - -
 
-Functions
       
CaPoll()
Use in non-GUI scripts to call ca.poll() in the background
-
GetRTYP(pv)
Returns the record type of "pv"
-@param pv:[string]
-@return: [string] EPICS record type or None if cannot connect
-
on_exit(timer=None)
Exit handler to stop the ca.poll()
-@param timer: CaPollWx object

-Call this to cleanup when program is exiting.
-ONLY call this function during a program's exit handling.
-If ca.task_exit() is not called, then expect to see the errors:
-     FATAL: exception not rethrown
-     Abort
-
receiver(epics_args, user_args)
Example response to an EPICS monitor on the channel
-@param value: str(epics_args['pv_value'])
-
testConnect(pv)
Tests if a CaChannel connection can be established to "pv"
-@param pv:[string]
-@return: True if can connect, otherwise False
-

- - - - - -
 
-Data
       IMPORTED_CACHANNEL = True
-IMPORTED_WX = True
- \ No newline at end of file diff --git a/archive/docs/pvwatch.html b/archive/docs/pvwatch.html deleted file mode 100644 index dc420dc..0000000 --- a/archive/docs/pvwatch.html +++ /dev/null @@ -1,74 +0,0 @@ - - -Python: module pvwatch - - - - -
 
- 
pvwatch
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/pvwatch.py
-

watch the USAXS EPICS process variables and
-write them to a file periodically

-Start this with the shell command
-/APSshare/bin/python ./pvwatch.py >>& log.txt

-

- - - - - -
 
-Modules
       
xml.etree.ElementTree
-datetime
-localConfig
-xml.dom.minidom
-
os
-plot
-prjPySpec
-pvConnect
-
shlex
-shutil
-subprocess
-sys
-
time
-wwwServerTransfers
-

- - - - - -
 
-Functions
       
add_pv(mne, pv, desc, fmt)
Connect to another EPICS process variable, uses pvConnect module
-
buildReport()
build the report
-
getSpecFileName(pv)
construct the name of the file, based on a PV
-
getTime()
return a datetime value
-
insertPI(xmlText, piText)
insert XML Processing Instruction text after first line of XML
-
logException(troublemaker)
write an exception report to the log file
-
logMessage(msg)
write a message with a timestamp and pid to the log file
-
main()
run the main loop
-
makeSimpleTag(tag, value)
create a simple XML tag/value string
-
monitor_receiver(epics_args, user_args)
Response to an EPICS monitor on the channel, uses pvConnect module
-@param value: str(epics_args['pv_value'])
-
report()
write the values out to files
-
shellCommandToFile(command, outFile)
execute a shell command and write its output to a file
-
updatePlotImage()
make a new PNG file with the most recent USAXS scans
-
updateSpecMacroFile()
copy the current SPEC macro file to the WWW page space
-
writeFile(file, contents)
write contents to file
-

- - - - - -
 
-Data
       GLOBAL_MONITOR_COUNTER = 0
-MAINLOOP_COUNTER_TRIGGER = 10000
-PVLIST_FILE = 'pvlist.xml'
-SVN_ID = '$Id: pvwatch.py 383 2010-07-25 02:49:05Z jemian $'
-pvdb = {}
-xref = {}
- \ No newline at end of file diff --git a/archive/docs/specplot.html b/archive/docs/specplot.html deleted file mode 100644 index 53e946b..0000000 --- a/archive/docs/specplot.html +++ /dev/null @@ -1,46 +0,0 @@ - - -Python: module specplot - - - - -
 
- 
specplot
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/specplot.py
-

read a SPEC data file and plot scan n using ploticus
-@note: does not copy any file to XSD WWW server

-

- - - - - -
 
-Modules
       
localConfig
-os
-
prjPySpec
-sys
-
tempfile
-wwwServerTransfers
-

- - - - - -
 
-Functions
       
findScan(sd, n)
return the first scan with scan number "n"
-from the spec data file object or None
-
format_ploticus_data(data)
find x & y min & max
-@return: dictionary
-
makePloticusPlot(scan, plotFile)
plot scan n from the SPEC scan object
-
makePloticusPlotByScanNum(specFile, scan_number, plotFile)
all-in-one function
-
openSpecFile(specFile)
convenience routine so that others
-do not have to import prjPySpec
-
run_ploticus_command_script(scan, dataFile, plotData, plotFile)
execute the ploticus command file using a "prefab" plot style
-
write_ploticus_data_file(data)
find x & y min & max
-@return: dictionary
-
- \ No newline at end of file diff --git a/archive/docs/specplotsAllScans.html b/archive/docs/specplotsAllScans.html deleted file mode 100644 index adf10b3..0000000 --- a/archive/docs/specplotsAllScans.html +++ /dev/null @@ -1,56 +0,0 @@ - - -Python: module specplotsAllScans - - - - -
 
- 
specplotsAllScans
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/specplotsAllScans.py
-

read a SPEC data file and plot all the scans
-@note: also copies files to USAXS site on XSD WWW server using rsync

-

- - - - - -
 
-Modules
       
localConfig
-os
-
shutil
-specplot
-
sys
-time
-
wwwServerTransfers
-

- - - - - -
 
-Functions
       
build_index_html(baseSpecFile, specFile, plotList)
build index.html content
-
datePath(date)
convert the date into a path: yyyy/mm
-
getBaseDir(basename, date)
find the path based on the date in the spec file
-
getBaseName(specFile)
get the base plot name from the spec file name (no path or extension)
-
getTimeFileModified(file)
@return: time (float) file was last modified
-
needToCopySpecDataFile(wwwSpecFile, mtime_specFile)
Determine if spec data file needs to be copied
-Base the decision on the mtime (last modification time of the file)
-@return: True or False
-
needToMakePlot(fullPlotFile, mtime_specFile)
Determine if a plot needs to be (re)made
-Use mtime as the basis
-@return: True or False
-
plotAllSpecFileScans(specFile)
make standard plots for all scans in the specFile
-
timestamp()
current time as yyyy-mm-dd hh:mm:ss
-

- - - - - -
 
-Data
       SVN_ID = '$Id: specplotsAllScans.py 376 2010-07-23 23:20:40Z jemian $'
- \ No newline at end of file diff --git a/archive/docs/wwwServerTransfers.html b/archive/docs/wwwServerTransfers.html deleted file mode 100644 index 597d67a..0000000 --- a/archive/docs/wwwServerTransfers.html +++ /dev/null @@ -1,59 +0,0 @@ - - -Python: module wwwServerTransfers - - - - -
 
- 
wwwServerTransfers
index
/home/beams21/S15USAXS/Documents/eclipse/USAXS/livedata/wwwServerTransfers.py
-

manage file transfers with the USAXS account on the XSD WWW server

-

- - - - - -
 
-Modules
       
os
-shlex
-
shutil
-subprocess
-
sys
-

- - - - - -
 
-Functions
       
execute_command(command)
execute the specified command
-
scpToWebServer(sourceFile, targetFile='')
copy the local source file to the WWW server using scp
-@param sourceFile: file in local file space relative to /data/www/livedata
-@param targetFile: destination file (default is same path as sourceFile)
-
scpToWebServer_Demonstrate(sourceFile, targetFile='')
Demonstrate a copy the local source file to the WWW server using scp BUT DON"T DO IT
-...
-... this is useful for code development only...
-...
-@param sourceFile: file in local file space *relative* to /data/www/livedata
-@param targetFile: destination file (default is same path as sourceFile)
-

- - - - - -
 
-Data
       LIVEDATA_DIR = 'www/livedata'
-LOCAL_DATA_DIR = '/data'
-LOCAL_USAXS_DATA__DIR = '/data/USAXS_data'
-LOCAL_WWW = '/data/www'
-LOCAL_WWW_LIVEDATA = '/data/www/livedata'
-RSYNC = '/usr/bin/rsync'
-SCP = '/usr/bin/scp'
-SERVER_WWW_HOMEDIR = 'usaxs@usaxs.xor.aps.anl.gov:~'
-SERVER_WWW_LIVEDATA = 'usaxs@usaxs.xor.aps.anl.gov:~/www/livedata'
-WWW_SERVER_ROOT = 'usaxs@usaxs.xor.aps.anl.gov'
- \ No newline at end of file diff --git a/archive/motorchanges20110913.sed b/archive/motorchanges20110913.sed deleted file mode 100644 index 12128bc..0000000 --- a/archive/motorchanges20110913.sed +++ /dev/null @@ -1,28 +0,0 @@ - -s|15iddLAX:m58:c2:m4|new:m58:c2:m3|g -s|15iddLAX:m58:c2:m5|new:m58:c2:m4|g -s|15iddLAX:m58:c3:m1|new:m58:c2:m5|g -s|15iddLAX:m58:c3:m2|new:m58:c2:m6|g -s|15iddLAX:m58:c3:m3|new:m58:c2:m7|g -s|15iddLAX:m58:c3:m4|new:m58:c2:m8|g -s|15iddLAX:m58:c2:m3|new:m58:c3:m1|g -s|15iddLAX:m58:c2:m6|new:m58:c3:m2|g -s|15iddLAX:m58:c2:m7|new:m58:c3:m3|g -s|15iddLAX:m58:c2:m8|new:m58:c3:m4|g -s|15iddLAX:m58:c1:m3|new:m58:c0:m1|g -s|15iddLAX:m58:c1:m5|new:m58:c1:m3|g -s|15iddLAX:m58:c0:m2|new:m58:c1:m5|g -s|15iddLAX:m58:c0:m6|new:m58:c0:m2|g -s|15iddLAX:m58:c1:m7|new:m58:c0:m6|g -s|15iddLAX:m58:c1:m4|new:m58:c1:m7|g -s|15iddLAX:m58:c1:m2|new:m58:c1:m4|g -s|15iddLAX:m58:c0:m5|new:m58:c1:m2|g -s|15iddLAX:m58:c1:m6|new:m58:c0:m5|g -s|15iddLAX:m58:c1:m1|new:m58:c1:m6|g -s|15iddLAX:m58:c0:m4|new:m58:c1:m1|g -s|15iddLAX:m58:c0:m1|new:m58:c0:m4|g -s|15iddLAX:m58:c1:m8|new:m58:c0:m7|g -s|15iddLAX:m58:c0:m3|new:m58:c1:m8|g -s|15iddLAX:m58:c0:m7|new:m58:c0:m3|g -s|new:|15iddLAX:|g - diff --git a/archive/ploticus/livedata.pl b/archive/ploticus/livedata.pl deleted file mode 100644 index d65905c..0000000 --- a/archive/ploticus/livedata.pl +++ /dev/null @@ -1,1157 +0,0 @@ - -#proc page - backgroundcolor: rgb(1.0,0.94,0.85) - -#proc annotate - location: 1 0.45 - textdetails: align=L size=6 - text: APS/XSD USAXS data - -#proc annotate - location: 1 0.35 - fromcommand: date - textdetails: align=L size=6 - -#proc getdata - #intrailer - -#proc areadef - rectangle: 1 1 7 7 - areacolor: rgb(0.95,1.0,0.97) - frame: color=rgb(0.2,0.2,0.1) width=3.0 - title: /data/USAXS_data/2013-10/Dale_DOW_1.dat - titledetails: align=C size=24 - xscaletype: log - xrange: 0.00000119582376647000 0.30003791405099999867 - yscaletype: log - yrange: 0.000000000007304324035430000337 0.003289851971200000082390380030 - -#proc xaxis - label: |Q|, 1/A - labeldetails: size=18 - selflocatingstubs: text - 1e-6 1e-6 - 2e-6 - 3e-6 - 4e-6 - 5e-6 - 6e-6 - 7e-6 - 8e-6 - 9e-6 - 1e-5 1e-5 - 2e-5 - 3e-5 - 4e-5 - 5e-5 - 6e-5 - 7e-5 - 8e-5 - 9e-5 - 1e-4 1e-4 - 2e-4 - 3e-4 - 4e-4 - 5e-4 - 6e-4 - 7e-4 - 8e-4 - 9e-4 - 1e-3 1e-3 - 2e-3 - 3e-3 - 4e-3 - 5e-3 - 6e-3 - 7e-3 - 8e-3 - 9e-3 - 1e-2 1e-2 - 2e-2 - 3e-2 - 4e-2 - 5e-2 - 6e-2 - 7e-2 - 8e-2 - 9e-2 - 1e-1 1e-1 - 2e-1 - 3e-1 - 4e-1 - 5e-1 - 6e-1 - 7e-1 - 8e-1 - 9e-1 - 1e0 1e0 - 2e0 - 3e0 - 4e0 - 5e0 - 6e0 - 7e0 - 8e0 - 9e0 - #include $chunk_logtics - stubrange: 1.19582376647e-06 0.300037914051 - grid: yes - grid color=grey(0.8) - -#proc yaxis - label: USAXS intensity, a.u. - labeldetails: size=18 - selflocatingstubs: text - 1e-12 1e-12 - 2e-12 - 3e-12 - 4e-12 - 5e-12 - 6e-12 - 7e-12 - 8e-12 - 9e-12 - 1e-11 1e-11 - 2e-11 - 3e-11 - 4e-11 - 5e-11 - 6e-11 - 7e-11 - 8e-11 - 9e-11 - 1e-10 1e-10 - 2e-10 - 3e-10 - 4e-10 - 5e-10 - 6e-10 - 7e-10 - 8e-10 - 9e-10 - 1e-9 1e-9 - 2e-9 - 3e-9 - 4e-9 - 5e-9 - 6e-9 - 7e-9 - 8e-9 - 9e-9 - 1e-8 1e-8 - 2e-8 - 3e-8 - 4e-8 - 5e-8 - 6e-8 - 7e-8 - 8e-8 - 9e-8 - 1e-7 1e-7 - 2e-7 - 3e-7 - 4e-7 - 5e-7 - 6e-7 - 7e-7 - 8e-7 - 9e-7 - 1e-6 1e-6 - 2e-6 - 3e-6 - 4e-6 - 5e-6 - 6e-6 - 7e-6 - 8e-6 - 9e-6 - 1e-5 1e-5 - 2e-5 - 3e-5 - 4e-5 - 5e-5 - 6e-5 - 7e-5 - 8e-5 - 9e-5 - 1e-4 1e-4 - 2e-4 - 3e-4 - 4e-4 - 5e-4 - 6e-4 - 7e-4 - 8e-4 - 9e-4 - 1e-3 1e-3 - 2e-3 - 3e-3 - 4e-3 - 5e-3 - 6e-3 - 7e-3 - 8e-3 - 9e-3 - 1e-2 1e-2 - 2e-2 - 3e-2 - 4e-2 - 5e-2 - 6e-2 - 7e-2 - 8e-2 - 9e-2 - #include $chunk_logtics - stubrange: 7.30432403543e-12 0.0032898519712 - grid: yes - grid color=grey(0.8) - -#proc lineplot - xfield: 2 - yfield: 3 - linedetails: color=green width=0.5 - pointsymbol: shape=triangle radius=0.025 linecolor=green fillcolor=white - legendlabel: S29: D04 - select: @@dataset == S29 - -#proc lineplot - xfield: 2 - yfield: 3 - linedetails: color=purple width=0.5 - pointsymbol: shape=diamond radius=0.025 linecolor=purple fillcolor=white - legendlabel: S34: D05 - select: @@dataset == S34 - -#proc lineplot - xfield: 2 - yfield: 3 - linedetails: color=blue width=0.5 - pointsymbol: shape=square radius=0.025 linecolor=blue fillcolor=white - legendlabel: S39: D06 - select: @@dataset == S39 - -#proc lineplot - xfield: 2 - yfield: 3 - linedetails: color=black width=0.5 - pointsymbol: shape=downtriangle radius=0.025 linecolor=black fillcolor=white - legendlabel: S44: D07 - select: @@dataset == S44 - -#proc lineplot - xfield: 2 - yfield: 3 - linedetails: color=red width=0.5 - pointsymbol: shape=lefttriangle radius=0.025 linecolor=red fillcolor=white - legendlabel: S49: D08 - select: @@dataset == S49 - -#proc rect - rectangle: 1.2 1.2 3.8 2.2 - color: white - bevelsize: 0.05 - -#proc legend - location: 1.5 2.15 - seglen: 0.2 - format: multiline - textdetails: align=Right - -#proc trailer - fieldnameheader: yes - delim: whitespace - data: - dataset qVec rVec - S29 0.000100000762479 1.5588314918e-05 - S29 9.22279079961e-05 5.55882501134e-05 - S29 8.47540094552e-05 0.000245963733374 - S29 7.75790668559e-05 0.000469467716535 - S29 7.0852558169e-05 0.000725730787662 - S29 6.44250054238e-05 0.000967408866 - S29 5.829640862e-05 0.00118085160053 - S29 5.26162457288e-05 0.00138561045962 - S29 4.70855608086e-05 0.00159884292521 - S29 4.17043538591e-05 0.00179118054681 - S29 3.6771580822e-05 0.00197616737034 - S29 3.19882857556e-05 0.00215521388186 - S29 2.75039466312e-05 0.00231386134872 - S29 2.31690854772e-05 0.00248034907003 - S29 1.89837022944e-05 0.0026507369586 - S29 1.5097275053e-05 0.0027981041224 - S29 1.13603257824e-05 0.00294687150346 - S29 7.77285448271e-06 0.00307522758135 - S29 4.48433912472e-06 0.00317048685369 - S29 1.19582376647e-06 0.00325317459319 - S29 1.79373564984e-06 0.00325000943907 - S29 4.93277303712e-06 0.00323581165276 - S29 8.0718104244e-06 0.00315704777363 - S29 1.15098037531e-05 0.00301907206119 - S29 1.49477970821e-05 0.00285016483378 - S29 1.86847463527e-05 0.00270223016063 - S29 2.25711735941e-05 0.00253886427708 - S29 2.67565567768e-05 0.00235012331387 - S29 3.09419399599e-05 0.00217779233211 - S29 3.54262790846e-05 0.00199678788719 - S29 4.02095741507e-05 0.00180202777498 - S29 4.51423471878e-05 0.00160302462224 - S29 5.02245981956e-05 0.00139120041268 - S29 5.57552831161e-05 0.00116584573135 - S29 6.14354460072e-05 0.00092681667653 - S29 6.74145648401e-05 0.000676569732341 - S29 7.35431616436e-05 0.000425404976778 - S29 8.01201923598e-05 0.000174163667027 - S29 8.69961790174e-05 4.2595456317e-05 - S29 9.41711216166e-05 8.92205471522e-06 - S29 0.000101794498128 1.96811317436e-06 - S29 0.000109716830582 8.62084167832e-07 - S29 0.000117938118977 4.67720022141e-07 - S29 0.000126757319255 3.02090526712e-07 - S29 0.000135875475475 2.1803019266e-07 - S29 0.000145442065607 1.68786432161e-07 - S29 0.000155457089652 1.35208859725e-07 - S29 0.000165920547609 1.12182894191e-07 - S29 0.000176981917449 9.37265988189e-08 - S29 0.000188491721201 8.04445839874e-08 - S29 0.000200599436837 6.95303666105e-08 - S29 0.000213305064356 6.1212852011e-08 - S29 0.000226608603758 5.36477282741e-08 - S29 0.000240510055043 4.77374428076e-08 - S29 0.000255158896182 4.23051332588e-08 - S29 0.000270555127175 3.75879220257e-08 - S29 0.000286698748021 3.33308163753e-08 - S29 0.000303440280751 2.95448824447e-08 - S29 0.000321228159275 2.62813247702e-08 - S29 0.000339763427654 2.36250031493e-08 - S29 0.000359195563856 2.1355070823e-08 - S29 0.000379524567883 1.92262189443e-08 - S29 0.000400899917705 1.76288309169e-08 - S29 0.000423321613322 1.62862143208e-08 - S29 0.000446789654733 1.50282457379e-08 - S29 0.000471304041939 1.38236407216e-08 - S29 0.000497163730881 1.27929970401e-08 - S29 0.000524219243587 1.16571140644e-08 - S29 0.000552620058029 1.09310618566e-08 - S29 0.000582216696235 1.01329927779e-08 - S29 0.000613457592118 9.35012886517e-09 - S29 0.000646193267706 8.63317499764e-09 - S29 0.000680423722998 7.98575500368e-09 - S29 0.000716298435965 7.45408873136e-09 - S29 0.000753966884577 6.93794224577e-09 - S29 0.000793429068834 6.47697279319e-09 - S29 0.000834834466704 6.07595893907e-09 - S29 0.000878183078188 5.68193268843e-09 - S29 0.000923773859226 5.27492599358e-09 - S29 0.000971457331844 4.90998054144e-09 - S29 0.00102138297401 4.62963544842e-09 - S29 0.00107384974167 4.37083927909e-09 - S29 0.00112885763482 4.12263486312e-09 - S29 0.00118640665346 3.88907421278e-09 - S29 0.00124679575352 3.64434880546e-09 - S29 0.00131017441297 3.41915836102e-09 - S29 0.00137654263181 3.22152129843e-09 - S29 0.00144619936597 3.04865421182e-09 - S29 0.00151914461546 2.85157644818e-09 - S29 0.00159567733621 2.66691731679e-09 - S29 0.00167594700617 2.50982801753e-09 - S29 0.00175995362535 2.35148592562e-09 - S29 0.00184814562764 2.20358349133e-09 - S29 0.00194067249102 2.06882707918e-09 - S29 0.00203753421546 1.92519992981e-09 - S29 0.00213917923486 1.80542944361e-09 - S29 0.00224560754921 1.68487594282e-09 - S29 0.00235726759241 1.58231687439e-09 - S29 0.00247445832036 1.46753779305e-09 - S29 0.00259717973306 1.37377761044e-09 - S29 0.00272588026438 1.27435894214e-09 - S29 0.00286070939227 1.17819389761e-09 - S29 0.0030021155506 1.1049540847e-09 - S29 0.00315039769526 1.01884877975e-09 - S29 0.00330585478215 9.44779710637e-10 - S29 0.00346893524513 8.70526540558e-10 - S29 0.0036397885621 8.01438315726e-10 - S29 0.0038188631669 7.41649772719e-10 - S29 0.00400675697132 6.76853254549e-10 - S29 0.00420361945323 6.22866982134e-10 - S29 0.00441004852441 5.70148710091e-10 - S29 0.00462649261863 5.2209526804e-10 - S29 0.00485340016964 4.79309153279e-10 - S29 0.0050912196112 4.38609655242e-10 - S29 0.00534069833293 4.00174775715e-10 - S29 0.00560213529055 3.65846012451e-10 - S29 0.00587627787365 3.35324680232e-10 - S29 0.00616357451582 3.05247111884e-10 - S29 0.00646492208452 2.77335479166e-10 - S29 0.00678076901325 2.51533379847e-10 - S29 0.00711201216934 2.29690773538e-10 - S29 0.00745909998619 2.09911851372e-10 - S29 0.00782307880891 1.88428380086e-10 - S29 0.00820469602665 1.70960752172e-10 - S29 0.00860484850635 1.5333857327e-10 - S29 0.00902428363693 1.3827374449e-10 - S29 0.00946404776303 1.23775095865e-10 - S29 0.00992503775119 1.14399678388e-10 - S29 0.0104082999457 1.01026241563e-10 - S29 0.0109150301686 9.13667706865e-11 - S29 0.0114462747637 8.39059060318e-11 - S29 0.0120033790304 7.48972434858e-11 - S29 0.012587239834 6.80208150029e-11 - S29 0.0131993519511 6.10016967259e-11 - S29 0.0138412101578 5.42756299143e-11 - S29 0.014514010274 5.10455266507e-11 - S29 0.0152195460304 4.65332692422e-11 - S29 0.0159590132458 4.17547648344e-11 - S29 0.0167343551273 3.82565004396e-11 - S29 0.0175473654034 3.48275179613e-11 - S29 0.0183995388459 3.1938977151e-11 - S29 0.0192929681366 2.96956661195e-11 - S29 0.0202297459562 2.7148033045e-11 - S29 0.0212118155058 2.54816200003e-11 - S29 0.0222414189404 2.37721607148e-11 - S29 0.0233207984129 2.20729631793e-11 - S29 0.0244524950293 2.05729633509e-11 - S29 0.0256389004152 1.93578234625e-11 - S29 0.0268827051484 1.80627475407e-11 - S29 0.0281867492807 1.72823930301e-11 - S29 0.0295538728596 1.64217827887e-11 - S29 0.0309872148831 1.5537726442e-11 - S29 0.0324899143437 1.47695436003e-11 - S29 0.0340652597046 1.42994987508e-11 - S29 0.0357168383772 1.39751022217e-11 - S29 0.037448536719 1.30091641664e-11 - S29 0.0392639421222 1.31005639777e-11 - S29 0.0411670904004 1.23627073585e-11 - S29 0.0431624657862 1.19087786957e-11 - S29 0.0452544030191 1.16459775625e-11 - S29 0.0474475357759 1.13735784572e-11 - S29 0.0497469461448 1.11684597086e-11 - S29 0.0521574172358 1.08999652745e-11 - S29 0.0546847784726 1.10170143185e-11 - S29 0.0573342613383 1.07580190181e-11 - S29 0.0601121436202 1.04954030638e-11 - S29 0.0630244041092 1.03536564578e-11 - S29 0.0660774699793 1.03056842717e-11 - S29 0.0692785157335 1.02237278071e-11 - S29 0.0726342673799 9.94597805378e-12 - S29 0.0761524971904 9.65196901696e-12 - S29 0.0798411268276 9.51594688489e-12 - S29 0.0837080778566 9.68475654886e-12 - S29 0.0877623180634 9.7286073254e-12 - S29 0.0920128151035 9.40593501494e-12 - S29 0.0964688354347 9.57460522688e-12 - S29 0.101140542197 9.43074210773e-12 - S29 0.106038397281 9.44293401572e-12 - S29 0.111173310773 9.11477294379e-12 - S29 0.116556640923 8.95188438543e-12 - S29 0.12220064252 8.7662145421e-12 - S29 0.128117719478 8.84945908883e-12 - S29 0.134321022679 8.72360295896e-12 - S29 0.140824748851 8.40880235103e-12 - S29 0.147643094183 8.8046056646e-12 - S29 0.154791300559 8.6594091291e-12 - S29 0.16228550598 8.03274578428e-12 - S29 0.170142445509 8.62954189432e-12 - S29 0.178379600617 8.27928627831e-12 - S29 0.187015348507 8.53704868015e-12 - S29 0.196068961938 8.26742983375e-12 - S29 0.205560609037 8.10098379907e-12 - S29 0.215511651996 8.04035626516e-12 - S29 0.225944198418 8.05869865466e-12 - S29 0.236881549412 8.22711712769e-12 - S29 0.248348199246 8.14765900654e-12 - S29 0.260369685481 8.47302174831e-12 - S29 0.272972737981 8.34173690811e-12 - S29 0.286185726755 8.00219965352e-12 - S29 0.300037914051 7.30432403543e-12 - S34 0.000100000762479 2.05802279105e-05 - S34 9.22279079961e-05 6.07730651176e-05 - S34 8.47540094552e-05 0.000277723507098 - S34 7.75790668559e-05 0.000511155954919 - S34 7.0852558169e-05 0.000756222315298 - S34 6.44250054238e-05 0.000993528411542 - S34 5.82964086202e-05 0.00122223911997 - S34 5.26162457291e-05 0.00142107863631 - S34 4.70855608086e-05 0.00160844745969 - S34 4.17043538591e-05 0.00182579926873 - S34 3.6771580822e-05 0.0020143153422 - S34 3.19882857559e-05 0.00221055554854 - S34 2.75039466312e-05 0.0023772480821 - S34 2.31690854774e-05 0.00253739387882 - S34 1.89837022944e-05 0.00271038023343 - S34 1.5097275053e-05 0.00286204805579 - S34 1.13603257824e-05 0.00301176440824 - S34 7.77285448271e-06 0.00312824568507 - S34 4.48433912472e-06 0.003213115169 - S34 1.19582376673e-06 0.00327692697127 - S34 1.79373564984e-06 0.00328459028006 - S34 4.93277303712e-06 0.003202686975 - S34 8.07181042413e-06 0.00310645998102 - S34 1.15098037531e-05 0.00297066528303 - S34 1.49477970821e-05 0.00281562642254 - S34 1.86847463524e-05 0.00264371529531 - S34 2.25711735938e-05 0.00246916572006 - S34 2.67565567768e-05 0.00230498485613 - S34 3.09419399599e-05 0.00212308645253 - S34 3.54262790843e-05 0.00193348916546 - S34 4.02095741507e-05 0.00174752781602 - S34 4.51423471878e-05 0.00153396402769 - S34 5.02245981956e-05 0.00132399757447 - S34 5.57552831161e-05 0.00109445489315 - S34 6.14354460072e-05 0.000861252115059 - S34 6.74145648401e-05 0.000589330369547 - S34 7.35431616436e-05 0.000345219312086 - S34 8.01201923595e-05 0.000124591611479 - S34 8.69961790174e-05 2.90869203419e-05 - S34 9.41711216166e-05 5.91089545655e-06 - S34 0.000101794498128 1.55563113278e-06 - S34 0.000109716830582 6.93576826196e-07 - S34 0.000117938118977 4.05400299737e-07 - S34 0.000126757319255 2.73391260547e-07 - S34 0.000135875475475 2.00606984095e-07 - S34 0.000145442065607 1.57136195793e-07 - S34 0.000155457089652 1.26577144009e-07 - S34 0.000165920547608 1.05480021673e-07 - S34 0.000176981917449 8.88387852527e-08 - S34 0.000188491721201 7.58527048943e-08 - S34 0.000200599436837 6.65072733679e-08 - S34 0.000213305064356 5.84869803793e-08 - S34 0.000226608603758 5.20399048925e-08 - S34 0.000240510055043 4.61631465599e-08 - S34 0.000255158896182 4.13155707774e-08 - S34 0.000270555127174 3.66397219914e-08 - S34 0.000286698748021 3.25834195062e-08 - S34 0.000303440280751 2.90687993541e-08 - S34 0.000321228159275 2.58747430935e-08 - S34 0.000339763427654 2.32920233488e-08 - S34 0.000359195563856 2.10357620574e-08 - S34 0.000379524567883 1.89940604069e-08 - S34 0.000400899917705 1.74520246423e-08 - S34 0.000423321613322 1.61755224333e-08 - S34 0.000446789654733 1.4880214526e-08 - S34 0.000471304041939 1.37631169226e-08 - S34 0.000497163730881 1.2731988108e-08 - S34 0.000524219243587 1.16806383194e-08 - S34 0.000552620058029 1.09071504803e-08 - S34 0.000582216696235 1.00993002416e-08 - S34 0.000613457592118 9.34949372904e-09 - S34 0.000646193267706 8.64507858305e-09 - S34 0.000680423722998 7.99509464814e-09 - S34 0.000716298435965 7.45676122887e-09 - S34 0.000753966884577 6.97144583394e-09 - S34 0.000793429068834 6.51808162725e-09 - S34 0.000834834466704 6.09686142752e-09 - S34 0.000878183078188 5.71398009136e-09 - S34 0.000923773859225 5.31723574168e-09 - S34 0.000971457331844 4.95376278517e-09 - S34 0.00102138297401 4.67152641386e-09 - S34 0.00107384974167 4.38891619098e-09 - S34 0.00112885763482 4.14977267998e-09 - S34 0.00118640665346 3.91075173339e-09 - S34 0.00124679575352 3.66678443446e-09 - S34 0.00131017441297 3.44149211193e-09 - S34 0.00137654263181 3.24473549859e-09 - S34 0.00144619936597 3.07242136064e-09 - S34 0.00151914461546 2.88115018398e-09 - S34 0.00159567733621 2.69158972807e-09 - S34 0.00167594700617 2.5273056149e-09 - S34 0.00175995362535 2.37628381744e-09 - S34 0.00184814562764 2.2196070534e-09 - S34 0.00194067249102 2.08343833476e-09 - S34 0.00203753421546 1.95288099288e-09 - S34 0.00213917923486 1.81809798925e-09 - S34 0.00224560754921 1.70821210995e-09 - S34 0.00235726759241 1.5916050876e-09 - S34 0.00247445832036 1.47685065139e-09 - S34 0.00259717973306 1.38331359605e-09 - S34 0.00272588026438 1.28651085478e-09 - S34 0.00286070939227 1.1917931252e-09 - S34 0.0030021155506 1.10925746377e-09 - S34 0.00315039769526 1.02782012243e-09 - S34 0.00330585478215 9.56658345311e-10 - S34 0.00346893524513 8.80147156262e-10 - S34 0.0036397885621 8.06632022401e-10 - S34 0.0038188631669 7.49495663145e-10 - S34 0.00400675697132 6.87458097914e-10 - S34 0.00420361945323 6.32779815353e-10 - S34 0.00441004852441 5.78513795063e-10 - S34 0.00462649261863 5.32809472415e-10 - S34 0.00485340016964 4.85347543613e-10 - S34 0.0050912196112 4.44712872539e-10 - S34 0.00534069833293 4.06539492314e-10 - S34 0.00560213529055 3.72536350993e-10 - S34 0.00587627787365 3.39897717635e-10 - S34 0.00616357451582 3.11227296348e-10 - S34 0.00646492208452 2.82748754566e-10 - S34 0.00678076901325 2.57038141698e-10 - S34 0.00711201216934 2.34329697552e-10 - S34 0.00745909998619 2.13181294982e-10 - S34 0.00782307880891 1.9321159391e-10 - S34 0.00820469602665 1.74993984677e-10 - S34 0.00860484850635 1.57841524523e-10 - S34 0.00902428363693 1.42147317277e-10 - S34 0.00946404776303 1.29312130551e-10 - S34 0.00992503775119 1.16240795241e-10 - S34 0.0104082999457 1.05149181602e-10 - S34 0.0109150301686 9.49721447587e-11 - S34 0.0114462747637 8.56848114324e-11 - S34 0.0120033790304 7.68736968472e-11 - S34 0.012587239834 7.08902984509e-11 - S34 0.0131993519511 6.35967657304e-11 - S34 0.0138412101578 5.71489991275e-11 - S34 0.014514010274 5.20471271992e-11 - S34 0.0152195460304 4.74848305672e-11 - S34 0.0159590132458 4.32687711879e-11 - S34 0.0167343551273 3.94383324783e-11 - S34 0.0175473654034 3.61564964689e-11 - S34 0.0183995388459 3.30873456012e-11 - S34 0.0192929681366 3.03553856635e-11 - S34 0.0202297459562 2.81899737147e-11 - S34 0.0212118155058 2.65163612998e-11 - S34 0.0222414189404 2.47807238633e-11 - S34 0.0233207984129 2.25385071464e-11 - S34 0.0244524950293 2.09541846188e-11 - S34 0.0256389004152 1.98849867501e-11 - S34 0.0268827051484 1.82999299804e-11 - S34 0.0281867492807 1.77097359429e-11 - S34 0.0295538728596 1.65453169968e-11 - S34 0.0309872148831 1.55981316075e-11 - S34 0.0324899143437 1.51084728628e-11 - S34 0.0340652597046 1.41706453628e-11 - S34 0.0357168383772 1.36723968953e-11 - S34 0.037448536719 1.34555512826e-11 - S34 0.0392639421222 1.2870595519e-11 - S34 0.0411670904004 1.21584858714e-11 - S34 0.0431624657862 1.19472554489e-11 - S34 0.0452544030191 1.20203562501e-11 - S34 0.0474475357759 1.15178417166e-11 - S34 0.0497469461448 1.1185894321e-11 - S34 0.0521574172358 1.11568874053e-11 - S34 0.0546847784726 1.10237738839e-11 - S34 0.0573342613383 1.08180138321e-11 - S34 0.0601121436202 1.03061223848e-11 - S34 0.0630244041092 1.05891545475e-11 - S34 0.0660774699793 1.00293402379e-11 - S34 0.0692785157335 1.02670612123e-11 - S34 0.0726342673799 1.00672333459e-11 - S34 0.0761524971904 9.61573598157e-12 - S34 0.0798411268276 9.91292682007e-12 - S34 0.0837080778566 9.89552370064e-12 - S34 0.0877623180634 9.61218537391e-12 - S34 0.0920128151035 9.52970323489e-12 - S34 0.0964688354347 9.4029814195e-12 - S34 0.101140542197 9.56446625458e-12 - S34 0.106038397281 9.56668418849e-12 - S34 0.111173310773 9.2778740358e-12 - S34 0.116556640923 9.13014300149e-12 - S34 0.12220064252 9.40328607194e-12 - S34 0.128117719478 9.06152489529e-12 - S34 0.134321022679 8.99624796752e-12 - S34 0.140824748851 8.80858189693e-12 - S34 0.147643094183 8.29665101741e-12 - S34 0.154791300559 8.50362669624e-12 - S34 0.16228550598 8.38249046966e-12 - S34 0.170142445509 7.96453831932e-12 - S34 0.178379600617 8.27947992084e-12 - S34 0.187015348507 8.55139081797e-12 - S34 0.196068961938 8.23116677166e-12 - S34 0.205560609037 8.34465532587e-12 - S34 0.215511651996 7.88680808074e-12 - S34 0.225944198418 7.92787690221e-12 - S34 0.236881549412 8.09751145258e-12 - S34 0.248348199246 8.25402736634e-12 - S34 0.260369685481 7.99898030678e-12 - S34 0.272972737981 8.28857587437e-12 - S34 0.286185726755 8.03726047619e-12 - S34 0.300037914051 7.7343395377e-12 - S39 0.000100000762479 1.39651680843e-05 - S39 9.22279079961e-05 5.40824287867e-05 - S39 8.47540094552e-05 0.000226330780429 - S39 7.75790668559e-05 0.000466817601154 - S39 7.0852558169e-05 0.000713153604065 - S39 6.44250054238e-05 0.000954007452788 - S39 5.82964086202e-05 0.00117250135813 - S39 5.26162457291e-05 0.00138031642448 - S39 4.70855608086e-05 0.00158344904193 - S39 4.17043538591e-05 0.00178481955704 - S39 3.6771580822e-05 0.00197136472103 - S39 3.19882857559e-05 0.00214964037855 - S39 2.75039466312e-05 0.00232976966427 - S39 2.31690854774e-05 0.00249447179099 - S39 1.89837022944e-05 0.00267306867224 - S39 1.5097275053e-05 0.00282284546899 - S39 1.13603257824e-05 0.00297217174012 - S39 7.77285448271e-06 0.0031078737776 - S39 4.48433912472e-06 0.00321108943294 - S39 1.19582376673e-06 0.00328510108003 - S39 1.79373564984e-06 0.0032898519712 - S39 4.93277303712e-06 0.00325569547004 - S39 8.07181042413e-06 0.00314844281932 - S39 1.15098037531e-05 0.00301443792951 - S39 1.49477970821e-05 0.00286677153464 - S39 1.86847463524e-05 0.00269984267825 - S39 2.25711735938e-05 0.0025484171674 - S39 2.67565567768e-05 0.0023666250992 - S39 3.09419399599e-05 0.00220043936065 - S39 3.54262790843e-05 0.00200818388482 - S39 4.02095741507e-05 0.0018181760768 - S39 4.51423471878e-05 0.00161888888889 - S39 5.02245981956e-05 0.00140491912981 - S39 5.57552831161e-05 0.00117654112183 - S39 6.14354460072e-05 0.000934651838746 - S39 6.74145648401e-05 0.000685714647167 - S39 7.35431616436e-05 0.000425128717631 - S39 8.01201923595e-05 0.00017813264714 - S39 8.69961790174e-05 4.58812840703e-05 - S39 9.41711216166e-05 1.01150062353e-05 - S39 0.000101794498128 2.12703492768e-06 - S39 0.000109716830582 8.8140641367e-07 - S39 0.000117938118977 4.71043737073e-07 - S39 0.000126757319255 3.04767195233e-07 - S39 0.000135875475475 2.19497426582e-07 - S39 0.000145442065607 1.69213452058e-07 - S39 0.000155457089652 1.35992144422e-07 - S39 0.000165920547608 1.12027357231e-07 - S39 0.000176981917449 9.42843468185e-08 - S39 0.000188491721201 8.0516343323e-08 - S39 0.000200599436837 6.99140869696e-08 - S39 0.000213305064356 6.12677296047e-08 - S39 0.000226608603758 5.40787852887e-08 - S39 0.000240510055043 4.81969399567e-08 - S39 0.000255158896182 4.27876251229e-08 - S39 0.000270555127174 3.03631320136e-08 - S39 0.000286698748021 3.37144152198e-08 - S39 0.000303440280751 2.97634202599e-08 - S39 0.000321228159275 2.65614760296e-08 - S39 0.000339763427654 2.38755980861e-08 - S39 0.000359195563856 2.15230243669e-08 - S39 0.000379524567883 1.93502397174e-08 - S39 0.000400899917705 1.78224058862e-08 - S39 0.000423321613322 1.64123966109e-08 - S39 0.000446789654733 1.51299789564e-08 - S39 0.000471304041939 1.395087737e-08 - S39 0.000497163730881 1.28724371276e-08 - S39 0.000524219243587 1.17978519564e-08 - S39 0.000552620058029 1.10229387796e-08 - S39 0.000582216696235 1.0218262078e-08 - S39 0.000613457592118 9.43392661452e-09 - S39 0.000646193267706 8.67825615757e-09 - S39 0.000680423722998 8.03496745385e-09 - S39 0.000716298435965 7.46979151581e-09 - S39 0.000753966884577 6.99530161609e-09 - S39 0.000793429068834 6.52832092847e-09 - S39 0.000834834466704 6.12476656741e-09 - S39 0.000878183078188 5.73273689516e-09 - S39 0.000923773859225 5.31256505858e-09 - S39 0.000971457331844 4.97386996278e-09 - S39 0.00102138297401 4.68217270019e-09 - S39 0.00107384974167 4.40741583258e-09 - S39 0.00112885763482 4.14235226432e-09 - S39 0.00118640665346 3.91668195303e-09 - S39 0.00124679575352 3.66687933093e-09 - S39 0.00131017441297 3.43502054574e-09 - S39 0.00137654263181 3.23335393464e-09 - S39 0.00144619936597 3.0600658567e-09 - S39 0.00151914461546 2.87824371491e-09 - S39 0.00159567733621 2.68205769741e-09 - S39 0.00167594700617 2.52237231832e-09 - S39 0.00175995362535 2.3725579397e-09 - S39 0.00184814562764 2.22292530136e-09 - S39 0.00194067249102 2.08058228504e-09 - S39 0.00203753421546 1.94960959937e-09 - S39 0.00213917923486 1.82011038114e-09 - S39 0.00224560754921 1.70270297625e-09 - S39 0.00235726759241 1.59616350534e-09 - S39 0.00247445832036 1.47745041135e-09 - S39 0.00259717973306 1.3739810923e-09 - S39 0.00272588026438 1.28353907646e-09 - S39 0.00286070939227 1.19327191672e-09 - S39 0.0030021155506 1.10810701793e-09 - S39 0.00315039769526 1.02614982194e-09 - S39 0.00330585478215 9.4995808938e-10 - S39 0.00346893524513 8.77831556127e-10 - S39 0.0036397885621 8.10117409418e-10 - S39 0.0038188631669 7.45380597816e-10 - S39 0.00400675697132 6.88392066584e-10 - S39 0.00420361945323 6.30055117464e-10 - S39 0.00441004852441 5.74887411538e-10 - S39 0.00462649261863 5.2876214511e-10 - S39 0.00485340016964 4.82037519969e-10 - S39 0.0050912196112 4.43845050578e-10 - S39 0.00534069833293 4.06220337272e-10 - S39 0.00560213529055 3.69131000341e-10 - S39 0.00587627787365 3.36244326156e-10 - S39 0.00616357451582 3.0755096363e-10 - S39 0.00646492208452 2.78412672347e-10 - S39 0.00678076901325 2.53160175348e-10 - S39 0.00711201216934 2.29848899885e-10 - S39 0.00745909998619 2.08408635965e-10 - S39 0.00782307880891 1.89094781146e-10 - S39 0.00820469602665 1.68692945927e-10 - S39 0.00860484850635 1.53840054372e-10 - S39 0.00902428363693 1.39803537336e-10 - S39 0.00946404776303 1.24785539409e-10 - S39 0.00992503775119 1.13181726706e-10 - S39 0.0104082999457 1.02760713027e-10 - S39 0.0109150301686 9.30362116992e-11 - S39 0.0114462747637 8.33478124016e-11 - S39 0.0120033790304 7.56089124789e-11 - S39 0.012587239834 6.74300717969e-11 - S39 0.0131993519511 6.19106325165e-11 - S39 0.0138412101578 5.61346608337e-11 - S39 0.014514010274 5.08405604155e-11 - S39 0.0152195460304 4.62609820988e-11 - S39 0.0159590132458 4.17533544399e-11 - S39 0.0167343551273 3.79161445723e-11 - S39 0.0175473654034 3.48069924229e-11 - S39 0.0183995388459 3.21495291949e-11 - S39 0.0192929681366 2.94514535102e-11 - S39 0.0202297459562 2.73930386043e-11 - S39 0.0212118155058 2.53911898004e-11 - S39 0.0222414189404 2.34219409945e-11 - S39 0.0233207984129 2.16027830844e-11 - S39 0.0244524950293 2.08241697909e-11 - S39 0.0256389004152 1.9400176343e-11 - S39 0.0268827051484 1.79118662747e-11 - S39 0.0281867492807 1.72861469961e-11 - S39 0.0295538728596 1.63060792046e-11 - S39 0.0309872148831 1.55388367753e-11 - S39 0.0324899143437 1.46711070136e-11 - S39 0.0340652597046 1.38510443282e-11 - S39 0.0357168383772 1.39321596524e-11 - S39 0.037448536719 1.31151777116e-11 - S39 0.0392639421222 1.25792217254e-11 - S39 0.0411670904004 1.21926059898e-11 - S39 0.0431624657862 1.18106205292e-11 - S39 0.0452544030191 1.16996361583e-11 - S39 0.0474475357759 1.16111487461e-11 - S39 0.0497469461448 1.10450587826e-11 - S39 0.0521574172358 1.07293104476e-11 - S39 0.0546847784726 1.08528782109e-11 - S39 0.0573342613383 1.08776804799e-11 - S39 0.0601121436202 1.06559164391e-11 - S39 0.0630244041092 1.04814029497e-11 - S39 0.0660774699793 1.03965971377e-11 - S39 0.0692785157335 1.0332031103e-11 - S39 0.0726342673799 9.98624922726e-12 - S39 0.0761524971904 9.98711286895e-12 - S39 0.0798411268276 9.84350534003e-12 - S39 0.0837080778566 9.56657369394e-12 - S39 0.0877623180634 9.66161486321e-12 - S39 0.0920128151035 9.84869928858e-12 - S39 0.0964688354347 9.44287267411e-12 - S39 0.101140542197 9.79604613056e-12 - S39 0.106038397281 9.36653549839e-12 - S39 0.111173310773 9.15297139009e-12 - S39 0.116556640923 9.01229727656e-12 - S39 0.12220064252 9.06785109506e-12 - S39 0.128117719478 8.81248511737e-12 - S39 0.134321022679 8.75799770175e-12 - S39 0.140824748851 8.73160046821e-12 - S39 0.147643094183 8.85958951341e-12 - S39 0.154791300559 8.62968644344e-12 - S39 0.16228550598 8.35522170462e-12 - S39 0.170142445509 8.37627740195e-12 - S39 0.178379600617 8.5371171797e-12 - S39 0.187015348507 8.38923909982e-12 - S39 0.196068961938 8.16059002224e-12 - S39 0.205560609037 8.25364123226e-12 - S39 0.215511651996 8.17301949983e-12 - S39 0.225944198418 8.04725212819e-12 - S39 0.236881549412 8.34904193097e-12 - S39 0.248348199246 8.27478524571e-12 - S39 0.260369685481 8.26898428034e-12 - S39 0.272972737981 8.3856300624e-12 - S39 0.286185726755 7.95562444225e-12 - S39 0.300037914051 7.80456034234e-12 - S44 0.000100000762479 1.73476593259e-05 - S44 9.22279079961e-05 5.4687202649e-05 - S44 8.47540094552e-05 0.000233277093455 - S44 7.75790668556e-05 0.000467254727757 - S44 7.08525581687e-05 0.00070690114575 - S44 6.44250054235e-05 0.00092951027658 - S44 5.829640862e-05 0.00116735707318 - S44 5.26162457288e-05 0.00137591287149 - S44 4.70855608083e-05 0.00158377932784 - S44 4.17043538589e-05 0.0017824605838 - S44 3.67715808218e-05 0.00196442552545 - S44 3.19882857556e-05 0.00214654921467 - S44 2.75039466309e-05 0.0023152547948 - S44 2.31690854772e-05 0.002483786452 - S44 1.89837022941e-05 0.0026511445356 - S44 1.50972750528e-05 0.00279921284754 - S44 1.13603257824e-05 0.00295984154306 - S44 7.77285448271e-06 0.00308782296227 - S44 4.48433912446e-06 0.00320357287552 - S44 1.19582376647e-06 0.00324535929484 - S44 1.79373564984e-06 0.00328425980694 - S44 4.93277303712e-06 0.0032313681028 - S44 8.0718104244e-06 0.00312063020663 - S44 1.15098037534e-05 0.00298344870988 - S44 1.49477970821e-05 0.00284124087591 - S44 1.86847463527e-05 0.00268546640475 - S44 2.25711735941e-05 0.0025194105051 - S44 2.67565567771e-05 0.00234769669227 - S44 3.09419399601e-05 0.00217116402116 - S44 3.54262790846e-05 0.00198479216803 - S44 4.0209574151e-05 0.00178319096934 - S44 4.51423471881e-05 0.00156937050587 - S44 5.02245981959e-05 0.00136246926036 - S44 5.57552831161e-05 0.00112522990099 - S44 6.14354460072e-05 0.000897279443282 - S44 6.74145648401e-05 0.00064614686531 - S44 7.35431616439e-05 0.000390659673189 - S44 8.01201923598e-05 0.00016165147805 - S44 8.69961790176e-05 4.43068368508e-05 - S44 9.41711216169e-05 9.71855371744e-06 - S44 0.000101794498128 2.22171757314e-06 - S44 0.000109716830582 8.46556838983e-07 - S44 0.000117938118977 4.49540357612e-07 - S44 0.000126757319255 2.88449395065e-07 - S44 0.000135875475475 2.06034406482e-07 - S44 0.000145442065607 1.58673359718e-07 - S44 0.000155457089652 1.2647849632e-07 - S44 0.000165920547609 1.04929194932e-07 - S44 0.000176981917449 8.90850276096e-08 - S44 0.000188491721202 7.60893755848e-08 - S44 0.000200599436837 6.66649755207e-08 - S44 0.000213305064356 5.86172826996e-08 - S44 0.000226608603758 5.18338986483e-08 - S44 0.000240510055043 4.60942241554e-08 - S44 0.000255158896182 4.10101214319e-08 - S44 0.000270555127175 3.65425478094e-08 - S44 0.000286698748021 3.24052072801e-08 - S44 0.000303440280751 2.87782610895e-08 - S44 0.000321228159276 2.56813073018e-08 - S44 0.000339763427654 2.30784766665e-08 - S44 0.000359195563857 2.0783353115e-08 - S44 0.000379524567883 1.88687343002e-08 - S44 0.000400899917705 1.73133272709e-08 - S44 0.000423321613322 1.60224770294e-08 - S44 0.000446789654733 1.47972810471e-08 - S44 0.000471304041939 1.36599795214e-08 - S44 0.000497163730881 1.25216468001e-08 - S44 0.000524219243587 1.1513603076e-08 - S44 0.000552620058029 1.08376625839e-08 - S44 0.000582216696235 1.00285862457e-08 - S44 0.000613457592118 9.26614320576e-09 - S44 0.000646193267706 8.57453218055e-09 - S44 0.000680423722998 7.91191997777e-09 - S44 0.000716298435965 7.33848890459e-09 - S44 0.000753966884577 6.89585643714e-09 - S44 0.000793429068834 6.42863895629e-09 - S44 0.000834834466704 6.03297707998e-09 - S44 0.000878183078188 5.64775448062e-09 - S44 0.000923773859226 5.25370680661e-09 - S44 0.000971457331844 4.90437942848e-09 - S44 0.00102138297401 4.63613392293e-09 - S44 0.00107384974167 4.35293300411e-09 - S44 0.00112885763482 4.12209691474e-09 - S44 0.00118640665346 3.88029195238e-09 - S44 0.00124679575352 3.63772700349e-09 - S44 0.00131017441297 3.40924854033e-09 - S44 0.00137654263181 3.21604046698e-09 - S44 0.00144619936597 3.03833077879e-09 - S44 0.00151914461546 2.84431892682e-09 - S44 0.00159567733621 2.67087753803e-09 - S44 0.00167594700617 2.49481036539e-09 - S44 0.00175995362535 2.35311174287e-09 - S44 0.00184814562764 2.20200147557e-09 - S44 0.00194067249102 2.05040324107e-09 - S44 0.00203753421546 1.93733507652e-09 - S44 0.00213917923486 1.80885707077e-09 - S44 0.00224560754921 1.7006778848e-09 - S44 0.00235726759241 1.5928767573e-09 - S44 0.00247445832036 1.47261504993e-09 - S44 0.00259717973306 1.38297568587e-09 - S44 0.00272588026438 1.2831037185e-09 - S44 0.00286070939227 1.19065300006e-09 - S44 0.0030021155506 1.1082582516e-09 - S44 0.00315039769526 1.03093987009e-09 - S44 0.00330585478215 9.54498150665e-10 - S44 0.00346893524513 8.75931473448e-10 - S44 0.0036397885621 8.10994870496e-10 - S44 0.0038188631669 7.45293540526e-10 - S44 0.00400675697132 6.84638613329e-10 - S44 0.00420361945323 6.3115259278e-10 - S44 0.00441004852441 5.78692334484e-10 - S44 0.00462649261863 5.29056229874e-10 - S44 0.00485340016964 4.82169419796e-10 - S44 0.0050912196112 4.45917609596e-10 - S44 0.00534069833293 4.05288964518e-10 - S44 0.00560213529055 3.7308411215e-10 - S44 0.00587627787365 3.38200969086e-10 - S44 0.00616357451582 3.08736705968e-10 - S44 0.00646492208452 2.80184933916e-10 - S44 0.00678076901325 2.56535233213e-10 - S44 0.00711201216934 2.31920531465e-10 - S44 0.00745909998619 2.10979830529e-10 - S44 0.00782307880891 1.89746357936e-10 - S44 0.00820469602665 1.71977338676e-10 - S44 0.00860484850635 1.54160893308e-10 - S44 0.00902428363693 1.39553777967e-10 - S44 0.00946404776303 1.27073216638e-10 - S44 0.00992503775119 1.14424272881e-10 - S44 0.0104082999457 1.05217134565e-10 - S44 0.0109150301686 9.31819466337e-11 - S44 0.0114462747637 8.42192544945e-11 - S44 0.0120033790304 7.56039726019e-11 - S44 0.012587239834 6.78843226788e-11 - S44 0.0131993519511 6.22667010316e-11 - S44 0.0138412101578 5.57328203968e-11 - S44 0.014514010274 5.17727015292e-11 - S44 0.0152195460304 4.71837294017e-11 - S44 0.0159590132458 4.32373300138e-11 - S44 0.0167343551273 3.92706395596e-11 - S44 0.0175473654034 3.59501105127e-11 - S44 0.0183995388459 3.26632944245e-11 - S44 0.0192929681366 2.96390551181e-11 - S44 0.0202297459562 2.76785545777e-11 - S44 0.0212118155058 2.64208638752e-11 - S44 0.0222414189404 2.39807911987e-11 - S44 0.0233207984129 2.23017142569e-11 - S44 0.0244524950293 2.10025850207e-11 - S44 0.0256389004152 1.98163343111e-11 - S44 0.0268827051484 1.79763250136e-11 - S44 0.0281867492807 1.75209098366e-11 - S44 0.0295538728596 1.65314504318e-11 - S44 0.0309872148831 1.56602710868e-11 - S44 0.0324899143437 1.48187575535e-11 - S44 0.0340652597046 1.45579878372e-11 - S44 0.0357168383772 1.41874970414e-11 - S44 0.037448536719 1.33916571678e-11 - S44 0.0392639421222 1.27576035819e-11 - S44 0.0411670904004 1.23896103077e-11 - S44 0.0431624657862 1.19739391472e-11 - S44 0.0452544030191 1.18266365575e-11 - S44 0.0474475357759 1.14127146148e-11 - S44 0.0497469461448 1.14266567292e-11 - S44 0.0521574172358 1.11246912254e-11 - S44 0.0546847784726 1.07748071102e-11 - S44 0.0573342613383 1.07386549976e-11 - S44 0.0601121436202 1.02802526796e-11 - S44 0.0630244041092 1.03819367339e-11 - S44 0.0660774699793 9.9692285453e-12 - S44 0.0692785157335 1.01529652609e-11 - S44 0.0726342673799 1.01398281091e-11 - S44 0.0761524971904 9.84612960757e-12 - S44 0.0798411268276 9.77994058566e-12 - S44 0.0837080778566 9.83321286478e-12 - S44 0.0877623180634 9.64674907993e-12 - S44 0.0920128151035 9.50652354984e-12 - S44 0.0964688354347 9.74604413204e-12 - S44 0.101140542197 9.4351234366e-12 - S44 0.106038397281 9.66654279308e-12 - S44 0.111173310773 9.3447730453e-12 - S44 0.116556640923 8.85780601334e-12 - S44 0.12220064252 8.93802534897e-12 - S44 0.128117719478 8.85762398177e-12 - S44 0.134321022679 9.04884871011e-12 - S44 0.140824748851 8.97298627544e-12 - S44 0.147643094183 8.86768069714e-12 - S44 0.154791300559 8.50885745458e-12 - S44 0.16228550598 8.32122090276e-12 - S44 0.170142445509 8.61076259464e-12 - S44 0.178379600617 8.39299149931e-12 - S44 0.187015348507 8.21559242513e-12 - S44 0.196068961938 8.25728349747e-12 - S44 0.205560609037 8.33590912677e-12 - S44 0.215511651996 8.20397460494e-12 - S44 0.225944198418 8.09131235063e-12 - S44 0.236881549412 8.24457759668e-12 - S44 0.248348199246 8.41688222679e-12 - S44 0.260369685481 8.35207505127e-12 - S44 0.272972737981 8.27021768751e-12 - S44 0.286185726755 7.98344827586e-12 - S44 0.300037914051 7.64243952188e-12 - S49 0.000100000762479 1.64635783189e-05 - S49 9.22279079961e-05 5.53880494075e-05 - S49 8.47540094552e-05 0.000249467645364 - S49 7.75790668559e-05 0.000512482949355 - S49 7.0852558169e-05 0.000743752036698 - S49 6.44250054238e-05 0.000977030742314 - S49 5.829640862e-05 0.00121052960358 - S49 5.26162457288e-05 0.00141349894305 - S49 4.70855608086e-05 0.00162505937945 - S49 4.17043538591e-05 0.00182341605036 - S49 3.6771580822e-05 0.00200716384322 - S49 3.19882857556e-05 0.00219561787905 - S49 2.75039466309e-05 0.00236278848799 - S49 2.31690854772e-05 0.00253755686647 - S49 1.89837022941e-05 0.00270263039456 - S49 1.5097275053e-05 0.00227810007252 - S49 1.13603257824e-05 0.00298576561463 - S49 7.77285448271e-06 0.00310899431214 - S49 4.48433912472e-06 0.00320101739826 - S49 1.19582376647e-06 0.00324660410132 - S49 1.79373564984e-06 0.00326687154886 - S49 4.93277303712e-06 0.00318871860207 - S49 8.0718104244e-06 0.00308911584184 - S49 1.15098037531e-05 0.00295597971369 - S49 1.49477970821e-05 0.00280948331916 - S49 1.86847463527e-05 0.00266525996794 - S49 2.25711735941e-05 0.00248996370917 - S49 2.67565567768e-05 0.00230232761857 - S49 3.09419399599e-05 0.00213876274473 - S49 3.54262790846e-05 0.00195129553136 - S49 4.02095741507e-05 0.0017665723727 - S49 4.51423471878e-05 0.00157219512807 - S49 5.02245981959e-05 0.00134983883308 - S49 5.57552831161e-05 0.00112741646516 - S49 6.14354460072e-05 0.000891961374467 - S49 6.74145648401e-05 0.000640532136319 - S49 7.35431616436e-05 0.00037961883998 - S49 8.01201923598e-05 0.000154535092005 - S49 8.69961790174e-05 4.01970369256e-05 - S49 9.41711216166e-05 8.51218533748e-06 - S49 0.000101794498128 1.8687480487e-06 - S49 0.000109716830582 8.31039831865e-07 - S49 0.000117938118977 4.41603282626e-07 - S49 0.000126757319255 2.8785794673e-07 - S49 0.000135875475475 2.09147380022e-07 - S49 0.000145442065607 1.61248716472e-07 - S49 0.000155457089652 1.30646733832e-07 - S49 0.000165920547609 1.0732597984e-07 - S49 0.000176981917449 9.11396994474e-08 - S49 0.000188491721201 7.81498425079e-08 - S49 0.000200599436837 6.83060177655e-08 - S49 0.000213305064356 5.94805909654e-08 - S49 0.000226608603758 5.29702846575e-08 - S49 0.000240510055043 4.68938627076e-08 - S49 0.000255158896182 4.17996825119e-08 - S49 0.000270555127175 3.6997550245e-08 - S49 0.000286698748021 3.30200147886e-08 - S49 0.000303440280751 2.92954960819e-08 - S49 0.000321228159275 2.60754135724e-08 - S49 0.000339763427654 2.33483812072e-08 - S49 0.000359195563856 2.1060920431e-08 - S49 0.000379524567883 1.9061477355e-08 - S49 0.000400899917705 1.74392273142e-08 - S49 0.000423321613322 1.61417421324e-08 - S49 0.000446789654733 1.49065759524e-08 - S49 0.000471304041939 1.37244348762e-08 - S49 0.000497163730881 1.25398631833e-08 - S49 0.000524219243587 1.15868608026e-08 - S49 0.000552620058029 1.08482820002e-08 - S49 0.000582216696235 1.0077436883e-08 - S49 0.000613457592118 9.28458426348e-09 - S49 0.000646193267706 8.58830079383e-09 - S49 0.000680423722998 7.9578884118e-09 - S49 0.000716298435965 7.38930743624e-09 - S49 0.000753966884577 6.88518231187e-09 - S49 0.000793429068834 6.45320597373e-09 - S49 0.000834834466704 6.03612553342e-09 - S49 0.000878183078188 5.649011423e-09 - S49 0.000923773859226 5.25875522817e-09 - S49 0.000971457331844 4.89237401981e-09 - S49 0.00102138297401 4.61980631053e-09 - S49 0.00107384974167 4.34136132196e-09 - S49 0.00112885763482 4.10154541934e-09 - S49 0.00118640665346 3.8756686497e-09 - S49 0.00124679575352 3.62466420941e-09 - S49 0.00131017441297 3.40360013019e-09 - S49 0.00137654263181 3.20332956299e-09 - S49 0.00144619936597 3.03304352165e-09 - S49 0.00151914461546 2.83798019086e-09 - S49 0.00159567733621 2.65174682028e-09 - S49 0.00167594700617 2.49240424988e-09 - S49 0.00175995362535 2.33762667865e-09 - S49 0.00184814562764 2.19207528541e-09 \ No newline at end of file diff --git a/archive/ploticus/livedata.png b/archive/ploticus/livedata.png deleted file mode 100644 index d000113f01c27aabcb918fe08c4ddea883a1cc5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8541 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 diff --git a/archive/pvlist_2011-09-09.xml b/archive/pvlist_2011-09-09.xml deleted file mode 100644 index a8e9bd0..0000000 --- a/archive/pvlist_2011-09-09.xml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - one-word mnemonic reference used in python and xslt code - (mne should be unique for each EPICS_PV) - - - EPICS process variable name (must be used in only one EPICS_PV) - - - useful text informative to others - - - (optional, default="%s") PVs will be formatted with this string - - - (optional, default="false") this PV is ignored if value is not "false" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archive/repair_old.py b/archive/repair_old.py deleted file mode 100755 index de7e3a8..0000000 --- a/archive/repair_old.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python - -'''repair miscalculated fly scan data reduction from April 2014''' - - -import os -import glob -import localConfig -import reduceFlyData -import stat -import shutil - - -MCA_CLOCK_FREQUENCY = localConfig.MCA_CLOCK_FREQUENCY -ARCHIVE_SUBDIR_NAME = 'archive' -V_f_gain = localConfig.FIXED_VF_GAIN -FULL_REDUCED_DATASET = -1 -REDUCED_FLY_SCAN_BINS = localConfig.REDUCED_FLY_SCAN_BINS - - -def copy_from_archive(archive_path, hfile): - target_path = os.path.abspath(os.path.join(archive_path, '..')) - source_file = os.path.abspath(os.path.join(archive_path, hfile)) - target_file = os.path.abspath(os.path.join(target_path, hfile)) - shutil.copy2(source_file, target_file) - mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH - os.chmod(target_file, mode) - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(): - owd = os.getcwd() - testpath = os.path.join('testdata', '2014-04', '*_fly') - for path in sorted(glob.glob(testpath)): - os.chdir(path) - for hfile in sorted(glob.glob('S*.h5')): - print path, hfile - copy_from_archive(ARCHIVE_SUBDIR_NAME, hfile) - ufs = reduceFlyData.UsaxsFlyScan(hfile) - #ufs.make_archive() - #ufs.read_reduced() - ufs.reduce() - ufs.rebin(REDUCED_FLY_SCAN_BINS) - ufs.save(hfile, 'full') - ufs.save(hfile, REDUCED_FLY_SCAN_BINS) - os.chdir(owd) - - - - -if __name__ == '__main__': - main() - - -########### SVN repository information ################### -# $Date$ -# $Author$ -# $Revision: 1011 $ -# $URL$ -# $Id$ -########### SVN repository information ################### diff --git a/archive/try_reduce.py b/archive/try_reduce.py deleted file mode 100755 index 661576b..0000000 --- a/archive/try_reduce.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python - -'''developer testing of reduceFlyScanData''' - - -import os -import glob -import localConfig -import reduceFlyData - -MCA_CLOCK_FREQUENCY = localConfig.MCA_CLOCK_FREQUENCY -ARCHIVE_SUBDIR_NAME = 'archive' -V_f_gain = localConfig.FIXED_VF_GAIN -FULL_REDUCED_DATASET = -1 -REDUCED_FLY_SCAN_BINS = localConfig.REDUCED_FLY_SCAN_BINS - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(): - #reduction_test() - testpath = os.path.join('testdata', '2014-04', '04_14_Winans_fly', '*.h5') - #testpath = os.path.join('testdata', '*.h5') - - # clear the test output files - for hfile in glob.glob(os.path.join('/tmp', 'reduced_*.h5')): - os.remove(hfile) - - counter = 0 - for hdf_file_name in sorted(glob.glob(testpath)): - print hdf_file_name - ufs = reduceFlyData.UsaxsFlyScan(hdf_file_name) - counter += 1 - hfile = os.path.join('/tmp', 'reduced_%04d.h5' % counter) - - #ufs.make_archive() - #ufs.read_reduced() - ufs.reduce() - ufs.rebin(REDUCED_FLY_SCAN_BINS) - ufs.save(hfile, 'full') - ufs.save(hfile, REDUCED_FLY_SCAN_BINS) - - -if __name__ == '__main__': - main() - - -########### SVN repository information ################### -# $Date$ -# $Author$ -# $Revision$ -# $URL$ -# $Id$ -########### SVN repository information ################### From 9c3d6bceaa7a7e1e6bb683e8f5bd3f894201ba94 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 17:34:00 -0500 Subject: [PATCH 02/28] MNT #48 archive all old content --- .gitignore | 5 +++++ .project | 16 ---------------- Makefile => archive/python2-version/Makefile | 0 .../python2-version/StatsReg.py | 0 .../python2-version/__init__.py | 0 .../python2-version/buildSpecPlots.sh | 0 calc.py => archive/python2-version/calc.py | 0 .../python2-version/checkup.py | 0 .../developer_test_reduceAreaDetector.py | 0 .../developer_test_reduceFlyData.py | 0 .../python2-version/developer_test_scanplots.py | 0 .../python2-version/developer_test_specplot.py | 0 .../developer_test_specplotsAllScans.py | 0 .../python2-version/handle_2d.py | 0 .../python2-version/livedata.xsl | 0 .../python2-version/localConfig.py | 0 .../python2-version/manage.csh | 0 .../python2-version/plot_mpl.py | 0 .../python2-version/pvlist.xml | 0 .../python2-version/pvlist.xsl | 0 .../python2-version/pvwatch.py | 0 .../python2-version/radialprofile.py | 0 .../python2-version/raw-table.xsl | 0 .../python2-version/recent_spec_data_files.py | 0 .../python2-version/reduceAreaDetector.py | 0 .../python2-version/reduceFlyData.py | 0 .../python2-version/scanpager.php | 0 .../python2-version/scanplots.py | 0 scp.py => archive/python2-version/scp.py | 0 .../python2-version/scp_over_paramiko.py | 0 .../python2-version/specplot.py | 0 .../python2-version/specplotsAllScans.py | 0 .../python2-version/usaxstv.xsl | 0 ustep.py => archive/python2-version/ustep.py | 0 .../python2-version/wwwServerTransfers.py | 0 .../python2-version/xmlSupport.py | 0 .../testdata}/03_06_JanTest.dat | 0 .../testdata}/03_18_GlassyCarbon.dat | 0 {testdata => archive/testdata}/03_19_LLNL.dat | 0 {testdata => archive/testdata}/03_30_Martin.dat | 0 {testdata => archive/testdata}/11_03_Vinod.dat | 0 .../testdata}/2014-06-12/saxs/GdC_infl_191.hdf5 | Bin .../testdata}/2014-06-12/saxs/GdC_infl_191.tif | Bin .../testdata}/2014-06-12/saxs/LSCF_190.hdf5 | Bin .../testdata}/2014-06-12/saxs/LSCF_190.tif | Bin .../2014-06-12/waxs/LSM_YSZ_infl_227.hdf5 | Bin .../2014-06-12/waxs/LSM_YSZ_infl_227.tif | Bin .../testdata}/2014-06-12/waxs/LSM_infl_228.hdf5 | Bin .../testdata}/2014-06-12/waxs/LSM_infl_228.tif | Bin ...-02 flyScan test reduction calculations.xlsx | Bin .../2016-02-27/02_27_AlCe_saxs/A_AlCe_3433.hdf | Bin .../2016-02-27/02_27_AlCe_waxs/A_AlCe_2849.hdf | Bin {testdata => archive/testdata}/Blank_0016.h5 | Bin .../testdata}/Blank_0016_center_calc.h5 | Bin {testdata => archive/testdata}/CeCoIn5 | 0 .../testdata}/S109_SWY2_Cs_75C.h5 | Bin .../testdata}/S217_E7_600C_87min.h5 | Bin .../testdata}/S463_PB_GRI_7_Nat_75C.h5 | Bin .../testdata}/S555_PB_GRI_9_Nat_175C.h5 | Bin .../testdata}/S563_PB_GRI_9_Nat_200C.h5 | Bin .../testdata}/S571_Heater_Blank.h5 | Bin .../testdata}/S6_r1SOTy2_0235.h5 | Bin .../testdata}/S96_SWY2_Cs_50C.h5 | Bin .../testdata}/fly/08_14_NIST_TRIP.dat | 0 .../08_14_NIST_TRIP_fly/S48_Fullfield_350C.h5 | Bin .../S49_BrightRegion_350C.h5 | Bin .../08_14_NIST_TRIP_fly/S50_DarkRegion_350C.h5 | Bin .../testdata}/fly/10_09_Prisk.dat | 0 .../testdata}/fly/10_09_Prisk2D1.dat | 0 .../10_09_Prisk2D1_fly/S14_GC_Adam_2dUSAXS.h5 | Bin .../10_09_Prisk2D1_fly/S21_GC_Adam_2dUSAXS.h5 | Bin .../10_09_Prisk2D1_fly/S28_Air_Blank_2dUSAXS.h5 | Bin .../fly/10_09_Prisk2D1_fly/S49_Glass_Blank.h5 | Bin .../fly/10_09_Prisk2D1_fly/S56_Glass_Blank.h5 | Bin .../fly/10_09_Prisk2D1_fly/S63_HR6229_start.h5 | Bin .../10_09_Prisk2D1_fly/S7_Air_Blank_2dUSAXS.h5 | Bin .../fly/10_09_Prisk_fly/S5_Glass_Blank.h5 | Bin .../testdata}/flyscan_modes/S10_test.h5 | Bin .../testdata}/flyscan_modes/S11_test.h5 | Bin .../testdata}/flyscan_modes/S12_test.h5 | Bin .../testdata}/flyscan_modes/S13_FS_ArrayData.h5 | Bin .../flyscan_modes/S14_FS_ArrayData_bad.h5 | Bin .../testdata}/flyscan_modes/S16_FS_TrajPnts.h5 | Bin .../flyscan_modes/S17_FS_TrajPnts_bad.h5 | Bin .../testdata}/flyscan_modes/S18_FS_Fixed.h5 | Bin .../testdata}/flyscan_modes/S19_FS_Fixed_bad.h5 | Bin .../testdata}/flyscan_modes/S1_PSO_Dir_Neg.h5 | Bin .../testdata}/flyscan_modes/S2_PSO_Dir_Both.h5 | Bin .../testdata}/flyscan_modes/S39_Blank.h5 | Bin .../flyscan_modes/S3_PSO_Fixed_Both.h5 | Bin .../testdata}/flyscan_modes/S44_GC_Adam.h5 | Bin .../flyscan_modes/S48_FS_60s_2k_2e5.h5 | Bin .../testdata}/flyscan_modes/S4_PSO_Fixed_Pos.h5 | Bin {testdata => archive/testdata}/scanlog.xml | 0 {testdata => archive/testdata}/scanlog.xsl | 0 {testdata => archive/testdata}/test_calc.h5 | Bin 96 files changed, 5 insertions(+), 16 deletions(-) delete mode 100755 .project rename Makefile => archive/python2-version/Makefile (100%) rename StatsReg.py => archive/python2-version/StatsReg.py (100%) rename __init__.py => archive/python2-version/__init__.py (100%) rename buildSpecPlots.sh => archive/python2-version/buildSpecPlots.sh (100%) rename calc.py => archive/python2-version/calc.py (100%) rename checkup.py => archive/python2-version/checkup.py (100%) rename developer_test_reduceAreaDetector.py => archive/python2-version/developer_test_reduceAreaDetector.py (100%) rename developer_test_reduceFlyData.py => archive/python2-version/developer_test_reduceFlyData.py (100%) rename developer_test_scanplots.py => archive/python2-version/developer_test_scanplots.py (100%) rename developer_test_specplot.py => archive/python2-version/developer_test_specplot.py (100%) rename developer_test_specplotsAllScans.py => archive/python2-version/developer_test_specplotsAllScans.py (100%) rename handle_2d.py => archive/python2-version/handle_2d.py (100%) rename livedata.xsl => archive/python2-version/livedata.xsl (100%) rename localConfig.py => archive/python2-version/localConfig.py (100%) rename manage.csh => archive/python2-version/manage.csh (100%) rename plot_mpl.py => archive/python2-version/plot_mpl.py (100%) rename pvlist.xml => archive/python2-version/pvlist.xml (100%) rename pvlist.xsl => archive/python2-version/pvlist.xsl (100%) rename pvwatch.py => archive/python2-version/pvwatch.py (100%) rename radialprofile.py => archive/python2-version/radialprofile.py (100%) rename raw-table.xsl => archive/python2-version/raw-table.xsl (100%) rename recent_spec_data_files.py => archive/python2-version/recent_spec_data_files.py (100%) rename reduceAreaDetector.py => archive/python2-version/reduceAreaDetector.py (100%) rename reduceFlyData.py => archive/python2-version/reduceFlyData.py (100%) rename scanpager.php => archive/python2-version/scanpager.php (100%) rename scanplots.py => archive/python2-version/scanplots.py (100%) rename scp.py => archive/python2-version/scp.py (100%) rename scp_over_paramiko.py => archive/python2-version/scp_over_paramiko.py (100%) rename specplot.py => archive/python2-version/specplot.py (100%) rename specplotsAllScans.py => archive/python2-version/specplotsAllScans.py (100%) rename usaxstv.xsl => archive/python2-version/usaxstv.xsl (100%) rename ustep.py => archive/python2-version/ustep.py (100%) rename wwwServerTransfers.py => archive/python2-version/wwwServerTransfers.py (100%) rename xmlSupport.py => archive/python2-version/xmlSupport.py (100%) rename {testdata => archive/testdata}/03_06_JanTest.dat (100%) rename {testdata => archive/testdata}/03_18_GlassyCarbon.dat (100%) rename {testdata => archive/testdata}/03_19_LLNL.dat (100%) rename {testdata => archive/testdata}/03_30_Martin.dat (100%) rename {testdata => archive/testdata}/11_03_Vinod.dat (100%) rename {testdata => archive/testdata}/2014-06-12/saxs/GdC_infl_191.hdf5 (100%) rename {testdata => archive/testdata}/2014-06-12/saxs/GdC_infl_191.tif (100%) rename {testdata => archive/testdata}/2014-06-12/saxs/LSCF_190.hdf5 (100%) rename {testdata => archive/testdata}/2014-06-12/saxs/LSCF_190.tif (100%) rename {testdata => archive/testdata}/2014-06-12/waxs/LSM_YSZ_infl_227.hdf5 (100%) rename {testdata => archive/testdata}/2014-06-12/waxs/LSM_YSZ_infl_227.tif (100%) rename {testdata => archive/testdata}/2014-06-12/waxs/LSM_infl_228.hdf5 (100%) rename {testdata => archive/testdata}/2014-06-12/waxs/LSM_infl_228.tif (100%) rename {testdata => archive/testdata}/2015-04-02 flyScan test reduction calculations.xlsx (100%) rename {testdata => archive/testdata}/2016-02-27/02_27_AlCe_saxs/A_AlCe_3433.hdf (100%) rename {testdata => archive/testdata}/2016-02-27/02_27_AlCe_waxs/A_AlCe_2849.hdf (100%) rename {testdata => archive/testdata}/Blank_0016.h5 (100%) rename {testdata => archive/testdata}/Blank_0016_center_calc.h5 (100%) rename {testdata => archive/testdata}/CeCoIn5 (100%) rename {testdata => archive/testdata}/S109_SWY2_Cs_75C.h5 (100%) rename {testdata => archive/testdata}/S217_E7_600C_87min.h5 (100%) rename {testdata => archive/testdata}/S463_PB_GRI_7_Nat_75C.h5 (100%) rename {testdata => archive/testdata}/S555_PB_GRI_9_Nat_175C.h5 (100%) rename {testdata => archive/testdata}/S563_PB_GRI_9_Nat_200C.h5 (100%) rename {testdata => archive/testdata}/S571_Heater_Blank.h5 (100%) rename {testdata => archive/testdata}/S6_r1SOTy2_0235.h5 (100%) rename {testdata => archive/testdata}/S96_SWY2_Cs_50C.h5 (100%) rename {testdata => archive/testdata}/fly/08_14_NIST_TRIP.dat (100%) rename {testdata => archive/testdata}/fly/08_14_NIST_TRIP_fly/S48_Fullfield_350C.h5 (100%) rename {testdata => archive/testdata}/fly/08_14_NIST_TRIP_fly/S49_BrightRegion_350C.h5 (100%) rename {testdata => archive/testdata}/fly/08_14_NIST_TRIP_fly/S50_DarkRegion_350C.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk.dat (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1.dat (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S14_GC_Adam_2dUSAXS.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S21_GC_Adam_2dUSAXS.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S28_Air_Blank_2dUSAXS.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S49_Glass_Blank.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S56_Glass_Blank.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S63_HR6229_start.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk2D1_fly/S7_Air_Blank_2dUSAXS.h5 (100%) rename {testdata => archive/testdata}/fly/10_09_Prisk_fly/S5_Glass_Blank.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S10_test.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S11_test.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S12_test.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S13_FS_ArrayData.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S14_FS_ArrayData_bad.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S16_FS_TrajPnts.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S17_FS_TrajPnts_bad.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S18_FS_Fixed.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S19_FS_Fixed_bad.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S1_PSO_Dir_Neg.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S2_PSO_Dir_Both.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S39_Blank.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S3_PSO_Fixed_Both.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S44_GC_Adam.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S48_FS_60s_2k_2e5.h5 (100%) rename {testdata => archive/testdata}/flyscan_modes/S4_PSO_Fixed_Pos.h5 (100%) rename {testdata => archive/testdata}/scanlog.xml (100%) rename {testdata => archive/testdata}/scanlog.xsl (100%) rename {testdata => archive/testdata}/test_calc.h5 (100%) diff --git a/.gitignore b/.gitignore index ad07344..893d7d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ *.pyc + +# APS IT .loglogin + +# Eclipse configuration .pydevproject +.project # Microsoft VisualStudio Code editor .vscode*/ diff --git a/.project b/.project deleted file mode 100755 index 4e1eec6..0000000 --- a/.project +++ /dev/null @@ -1,16 +0,0 @@ - - - USAXS-livedata - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/Makefile b/archive/python2-version/Makefile similarity index 100% rename from Makefile rename to archive/python2-version/Makefile diff --git a/StatsReg.py b/archive/python2-version/StatsReg.py similarity index 100% rename from StatsReg.py rename to archive/python2-version/StatsReg.py diff --git a/__init__.py b/archive/python2-version/__init__.py similarity index 100% rename from __init__.py rename to archive/python2-version/__init__.py diff --git a/buildSpecPlots.sh b/archive/python2-version/buildSpecPlots.sh similarity index 100% rename from buildSpecPlots.sh rename to archive/python2-version/buildSpecPlots.sh diff --git a/calc.py b/archive/python2-version/calc.py similarity index 100% rename from calc.py rename to archive/python2-version/calc.py diff --git a/checkup.py b/archive/python2-version/checkup.py similarity index 100% rename from checkup.py rename to archive/python2-version/checkup.py diff --git a/developer_test_reduceAreaDetector.py b/archive/python2-version/developer_test_reduceAreaDetector.py similarity index 100% rename from developer_test_reduceAreaDetector.py rename to archive/python2-version/developer_test_reduceAreaDetector.py diff --git a/developer_test_reduceFlyData.py b/archive/python2-version/developer_test_reduceFlyData.py similarity index 100% rename from developer_test_reduceFlyData.py rename to archive/python2-version/developer_test_reduceFlyData.py diff --git a/developer_test_scanplots.py b/archive/python2-version/developer_test_scanplots.py similarity index 100% rename from developer_test_scanplots.py rename to archive/python2-version/developer_test_scanplots.py diff --git a/developer_test_specplot.py b/archive/python2-version/developer_test_specplot.py similarity index 100% rename from developer_test_specplot.py rename to archive/python2-version/developer_test_specplot.py diff --git a/developer_test_specplotsAllScans.py b/archive/python2-version/developer_test_specplotsAllScans.py similarity index 100% rename from developer_test_specplotsAllScans.py rename to archive/python2-version/developer_test_specplotsAllScans.py diff --git a/handle_2d.py b/archive/python2-version/handle_2d.py similarity index 100% rename from handle_2d.py rename to archive/python2-version/handle_2d.py diff --git a/livedata.xsl b/archive/python2-version/livedata.xsl similarity index 100% rename from livedata.xsl rename to archive/python2-version/livedata.xsl diff --git a/localConfig.py b/archive/python2-version/localConfig.py similarity index 100% rename from localConfig.py rename to archive/python2-version/localConfig.py diff --git a/manage.csh b/archive/python2-version/manage.csh similarity index 100% rename from manage.csh rename to archive/python2-version/manage.csh diff --git a/plot_mpl.py b/archive/python2-version/plot_mpl.py similarity index 100% rename from plot_mpl.py rename to archive/python2-version/plot_mpl.py diff --git a/pvlist.xml b/archive/python2-version/pvlist.xml similarity index 100% rename from pvlist.xml rename to archive/python2-version/pvlist.xml diff --git a/pvlist.xsl b/archive/python2-version/pvlist.xsl similarity index 100% rename from pvlist.xsl rename to archive/python2-version/pvlist.xsl diff --git a/pvwatch.py b/archive/python2-version/pvwatch.py similarity index 100% rename from pvwatch.py rename to archive/python2-version/pvwatch.py diff --git a/radialprofile.py b/archive/python2-version/radialprofile.py similarity index 100% rename from radialprofile.py rename to archive/python2-version/radialprofile.py diff --git a/raw-table.xsl b/archive/python2-version/raw-table.xsl similarity index 100% rename from raw-table.xsl rename to archive/python2-version/raw-table.xsl diff --git a/recent_spec_data_files.py b/archive/python2-version/recent_spec_data_files.py similarity index 100% rename from recent_spec_data_files.py rename to archive/python2-version/recent_spec_data_files.py diff --git a/reduceAreaDetector.py b/archive/python2-version/reduceAreaDetector.py similarity index 100% rename from reduceAreaDetector.py rename to archive/python2-version/reduceAreaDetector.py diff --git a/reduceFlyData.py b/archive/python2-version/reduceFlyData.py similarity index 100% rename from reduceFlyData.py rename to archive/python2-version/reduceFlyData.py diff --git a/scanpager.php b/archive/python2-version/scanpager.php similarity index 100% rename from scanpager.php rename to archive/python2-version/scanpager.php diff --git a/scanplots.py b/archive/python2-version/scanplots.py similarity index 100% rename from scanplots.py rename to archive/python2-version/scanplots.py diff --git a/scp.py b/archive/python2-version/scp.py similarity index 100% rename from scp.py rename to archive/python2-version/scp.py diff --git a/scp_over_paramiko.py b/archive/python2-version/scp_over_paramiko.py similarity index 100% rename from scp_over_paramiko.py rename to archive/python2-version/scp_over_paramiko.py diff --git a/specplot.py b/archive/python2-version/specplot.py similarity index 100% rename from specplot.py rename to archive/python2-version/specplot.py diff --git a/specplotsAllScans.py b/archive/python2-version/specplotsAllScans.py similarity index 100% rename from specplotsAllScans.py rename to archive/python2-version/specplotsAllScans.py diff --git a/usaxstv.xsl b/archive/python2-version/usaxstv.xsl similarity index 100% rename from usaxstv.xsl rename to archive/python2-version/usaxstv.xsl diff --git a/ustep.py b/archive/python2-version/ustep.py similarity index 100% rename from ustep.py rename to archive/python2-version/ustep.py diff --git a/wwwServerTransfers.py b/archive/python2-version/wwwServerTransfers.py similarity index 100% rename from wwwServerTransfers.py rename to archive/python2-version/wwwServerTransfers.py diff --git a/xmlSupport.py b/archive/python2-version/xmlSupport.py similarity index 100% rename from xmlSupport.py rename to archive/python2-version/xmlSupport.py diff --git a/testdata/03_06_JanTest.dat b/archive/testdata/03_06_JanTest.dat similarity index 100% rename from testdata/03_06_JanTest.dat rename to archive/testdata/03_06_JanTest.dat diff --git a/testdata/03_18_GlassyCarbon.dat b/archive/testdata/03_18_GlassyCarbon.dat similarity index 100% rename from testdata/03_18_GlassyCarbon.dat rename to archive/testdata/03_18_GlassyCarbon.dat diff --git a/testdata/03_19_LLNL.dat b/archive/testdata/03_19_LLNL.dat similarity index 100% rename from testdata/03_19_LLNL.dat rename to archive/testdata/03_19_LLNL.dat diff --git a/testdata/03_30_Martin.dat b/archive/testdata/03_30_Martin.dat similarity index 100% rename from testdata/03_30_Martin.dat rename to archive/testdata/03_30_Martin.dat diff --git a/testdata/11_03_Vinod.dat b/archive/testdata/11_03_Vinod.dat similarity index 100% rename from testdata/11_03_Vinod.dat rename to archive/testdata/11_03_Vinod.dat diff --git a/testdata/2014-06-12/saxs/GdC_infl_191.hdf5 b/archive/testdata/2014-06-12/saxs/GdC_infl_191.hdf5 similarity index 100% rename from testdata/2014-06-12/saxs/GdC_infl_191.hdf5 rename to archive/testdata/2014-06-12/saxs/GdC_infl_191.hdf5 diff --git a/testdata/2014-06-12/saxs/GdC_infl_191.tif b/archive/testdata/2014-06-12/saxs/GdC_infl_191.tif similarity index 100% rename from testdata/2014-06-12/saxs/GdC_infl_191.tif rename to archive/testdata/2014-06-12/saxs/GdC_infl_191.tif diff --git a/testdata/2014-06-12/saxs/LSCF_190.hdf5 b/archive/testdata/2014-06-12/saxs/LSCF_190.hdf5 similarity index 100% rename from testdata/2014-06-12/saxs/LSCF_190.hdf5 rename to archive/testdata/2014-06-12/saxs/LSCF_190.hdf5 diff --git a/testdata/2014-06-12/saxs/LSCF_190.tif b/archive/testdata/2014-06-12/saxs/LSCF_190.tif similarity index 100% rename from testdata/2014-06-12/saxs/LSCF_190.tif rename to archive/testdata/2014-06-12/saxs/LSCF_190.tif diff --git a/testdata/2014-06-12/waxs/LSM_YSZ_infl_227.hdf5 b/archive/testdata/2014-06-12/waxs/LSM_YSZ_infl_227.hdf5 similarity index 100% rename from testdata/2014-06-12/waxs/LSM_YSZ_infl_227.hdf5 rename to archive/testdata/2014-06-12/waxs/LSM_YSZ_infl_227.hdf5 diff --git a/testdata/2014-06-12/waxs/LSM_YSZ_infl_227.tif b/archive/testdata/2014-06-12/waxs/LSM_YSZ_infl_227.tif similarity index 100% rename from testdata/2014-06-12/waxs/LSM_YSZ_infl_227.tif rename to archive/testdata/2014-06-12/waxs/LSM_YSZ_infl_227.tif diff --git a/testdata/2014-06-12/waxs/LSM_infl_228.hdf5 b/archive/testdata/2014-06-12/waxs/LSM_infl_228.hdf5 similarity index 100% rename from testdata/2014-06-12/waxs/LSM_infl_228.hdf5 rename to archive/testdata/2014-06-12/waxs/LSM_infl_228.hdf5 diff --git a/testdata/2014-06-12/waxs/LSM_infl_228.tif b/archive/testdata/2014-06-12/waxs/LSM_infl_228.tif similarity index 100% rename from testdata/2014-06-12/waxs/LSM_infl_228.tif rename to archive/testdata/2014-06-12/waxs/LSM_infl_228.tif diff --git a/testdata/2015-04-02 flyScan test reduction calculations.xlsx b/archive/testdata/2015-04-02 flyScan test reduction calculations.xlsx similarity index 100% rename from testdata/2015-04-02 flyScan test reduction calculations.xlsx rename to archive/testdata/2015-04-02 flyScan test reduction calculations.xlsx diff --git a/testdata/2016-02-27/02_27_AlCe_saxs/A_AlCe_3433.hdf b/archive/testdata/2016-02-27/02_27_AlCe_saxs/A_AlCe_3433.hdf similarity index 100% rename from testdata/2016-02-27/02_27_AlCe_saxs/A_AlCe_3433.hdf rename to archive/testdata/2016-02-27/02_27_AlCe_saxs/A_AlCe_3433.hdf diff --git a/testdata/2016-02-27/02_27_AlCe_waxs/A_AlCe_2849.hdf b/archive/testdata/2016-02-27/02_27_AlCe_waxs/A_AlCe_2849.hdf similarity index 100% rename from testdata/2016-02-27/02_27_AlCe_waxs/A_AlCe_2849.hdf rename to archive/testdata/2016-02-27/02_27_AlCe_waxs/A_AlCe_2849.hdf diff --git a/testdata/Blank_0016.h5 b/archive/testdata/Blank_0016.h5 similarity index 100% rename from testdata/Blank_0016.h5 rename to archive/testdata/Blank_0016.h5 diff --git a/testdata/Blank_0016_center_calc.h5 b/archive/testdata/Blank_0016_center_calc.h5 similarity index 100% rename from testdata/Blank_0016_center_calc.h5 rename to archive/testdata/Blank_0016_center_calc.h5 diff --git a/testdata/CeCoIn5 b/archive/testdata/CeCoIn5 similarity index 100% rename from testdata/CeCoIn5 rename to archive/testdata/CeCoIn5 diff --git a/testdata/S109_SWY2_Cs_75C.h5 b/archive/testdata/S109_SWY2_Cs_75C.h5 similarity index 100% rename from testdata/S109_SWY2_Cs_75C.h5 rename to archive/testdata/S109_SWY2_Cs_75C.h5 diff --git a/testdata/S217_E7_600C_87min.h5 b/archive/testdata/S217_E7_600C_87min.h5 similarity index 100% rename from testdata/S217_E7_600C_87min.h5 rename to archive/testdata/S217_E7_600C_87min.h5 diff --git a/testdata/S463_PB_GRI_7_Nat_75C.h5 b/archive/testdata/S463_PB_GRI_7_Nat_75C.h5 similarity index 100% rename from testdata/S463_PB_GRI_7_Nat_75C.h5 rename to archive/testdata/S463_PB_GRI_7_Nat_75C.h5 diff --git a/testdata/S555_PB_GRI_9_Nat_175C.h5 b/archive/testdata/S555_PB_GRI_9_Nat_175C.h5 similarity index 100% rename from testdata/S555_PB_GRI_9_Nat_175C.h5 rename to archive/testdata/S555_PB_GRI_9_Nat_175C.h5 diff --git a/testdata/S563_PB_GRI_9_Nat_200C.h5 b/archive/testdata/S563_PB_GRI_9_Nat_200C.h5 similarity index 100% rename from testdata/S563_PB_GRI_9_Nat_200C.h5 rename to archive/testdata/S563_PB_GRI_9_Nat_200C.h5 diff --git a/testdata/S571_Heater_Blank.h5 b/archive/testdata/S571_Heater_Blank.h5 similarity index 100% rename from testdata/S571_Heater_Blank.h5 rename to archive/testdata/S571_Heater_Blank.h5 diff --git a/testdata/S6_r1SOTy2_0235.h5 b/archive/testdata/S6_r1SOTy2_0235.h5 similarity index 100% rename from testdata/S6_r1SOTy2_0235.h5 rename to archive/testdata/S6_r1SOTy2_0235.h5 diff --git a/testdata/S96_SWY2_Cs_50C.h5 b/archive/testdata/S96_SWY2_Cs_50C.h5 similarity index 100% rename from testdata/S96_SWY2_Cs_50C.h5 rename to archive/testdata/S96_SWY2_Cs_50C.h5 diff --git a/testdata/fly/08_14_NIST_TRIP.dat b/archive/testdata/fly/08_14_NIST_TRIP.dat similarity index 100% rename from testdata/fly/08_14_NIST_TRIP.dat rename to archive/testdata/fly/08_14_NIST_TRIP.dat diff --git a/testdata/fly/08_14_NIST_TRIP_fly/S48_Fullfield_350C.h5 b/archive/testdata/fly/08_14_NIST_TRIP_fly/S48_Fullfield_350C.h5 similarity index 100% rename from testdata/fly/08_14_NIST_TRIP_fly/S48_Fullfield_350C.h5 rename to archive/testdata/fly/08_14_NIST_TRIP_fly/S48_Fullfield_350C.h5 diff --git a/testdata/fly/08_14_NIST_TRIP_fly/S49_BrightRegion_350C.h5 b/archive/testdata/fly/08_14_NIST_TRIP_fly/S49_BrightRegion_350C.h5 similarity index 100% rename from testdata/fly/08_14_NIST_TRIP_fly/S49_BrightRegion_350C.h5 rename to archive/testdata/fly/08_14_NIST_TRIP_fly/S49_BrightRegion_350C.h5 diff --git a/testdata/fly/08_14_NIST_TRIP_fly/S50_DarkRegion_350C.h5 b/archive/testdata/fly/08_14_NIST_TRIP_fly/S50_DarkRegion_350C.h5 similarity index 100% rename from testdata/fly/08_14_NIST_TRIP_fly/S50_DarkRegion_350C.h5 rename to archive/testdata/fly/08_14_NIST_TRIP_fly/S50_DarkRegion_350C.h5 diff --git a/testdata/fly/10_09_Prisk.dat b/archive/testdata/fly/10_09_Prisk.dat similarity index 100% rename from testdata/fly/10_09_Prisk.dat rename to archive/testdata/fly/10_09_Prisk.dat diff --git a/testdata/fly/10_09_Prisk2D1.dat b/archive/testdata/fly/10_09_Prisk2D1.dat similarity index 100% rename from testdata/fly/10_09_Prisk2D1.dat rename to archive/testdata/fly/10_09_Prisk2D1.dat diff --git a/testdata/fly/10_09_Prisk2D1_fly/S14_GC_Adam_2dUSAXS.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S14_GC_Adam_2dUSAXS.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S14_GC_Adam_2dUSAXS.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S14_GC_Adam_2dUSAXS.h5 diff --git a/testdata/fly/10_09_Prisk2D1_fly/S21_GC_Adam_2dUSAXS.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S21_GC_Adam_2dUSAXS.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S21_GC_Adam_2dUSAXS.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S21_GC_Adam_2dUSAXS.h5 diff --git a/testdata/fly/10_09_Prisk2D1_fly/S28_Air_Blank_2dUSAXS.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S28_Air_Blank_2dUSAXS.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S28_Air_Blank_2dUSAXS.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S28_Air_Blank_2dUSAXS.h5 diff --git a/testdata/fly/10_09_Prisk2D1_fly/S49_Glass_Blank.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S49_Glass_Blank.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S49_Glass_Blank.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S49_Glass_Blank.h5 diff --git a/testdata/fly/10_09_Prisk2D1_fly/S56_Glass_Blank.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S56_Glass_Blank.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S56_Glass_Blank.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S56_Glass_Blank.h5 diff --git a/testdata/fly/10_09_Prisk2D1_fly/S63_HR6229_start.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S63_HR6229_start.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S63_HR6229_start.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S63_HR6229_start.h5 diff --git a/testdata/fly/10_09_Prisk2D1_fly/S7_Air_Blank_2dUSAXS.h5 b/archive/testdata/fly/10_09_Prisk2D1_fly/S7_Air_Blank_2dUSAXS.h5 similarity index 100% rename from testdata/fly/10_09_Prisk2D1_fly/S7_Air_Blank_2dUSAXS.h5 rename to archive/testdata/fly/10_09_Prisk2D1_fly/S7_Air_Blank_2dUSAXS.h5 diff --git a/testdata/fly/10_09_Prisk_fly/S5_Glass_Blank.h5 b/archive/testdata/fly/10_09_Prisk_fly/S5_Glass_Blank.h5 similarity index 100% rename from testdata/fly/10_09_Prisk_fly/S5_Glass_Blank.h5 rename to archive/testdata/fly/10_09_Prisk_fly/S5_Glass_Blank.h5 diff --git a/testdata/flyscan_modes/S10_test.h5 b/archive/testdata/flyscan_modes/S10_test.h5 similarity index 100% rename from testdata/flyscan_modes/S10_test.h5 rename to archive/testdata/flyscan_modes/S10_test.h5 diff --git a/testdata/flyscan_modes/S11_test.h5 b/archive/testdata/flyscan_modes/S11_test.h5 similarity index 100% rename from testdata/flyscan_modes/S11_test.h5 rename to archive/testdata/flyscan_modes/S11_test.h5 diff --git a/testdata/flyscan_modes/S12_test.h5 b/archive/testdata/flyscan_modes/S12_test.h5 similarity index 100% rename from testdata/flyscan_modes/S12_test.h5 rename to archive/testdata/flyscan_modes/S12_test.h5 diff --git a/testdata/flyscan_modes/S13_FS_ArrayData.h5 b/archive/testdata/flyscan_modes/S13_FS_ArrayData.h5 similarity index 100% rename from testdata/flyscan_modes/S13_FS_ArrayData.h5 rename to archive/testdata/flyscan_modes/S13_FS_ArrayData.h5 diff --git a/testdata/flyscan_modes/S14_FS_ArrayData_bad.h5 b/archive/testdata/flyscan_modes/S14_FS_ArrayData_bad.h5 similarity index 100% rename from testdata/flyscan_modes/S14_FS_ArrayData_bad.h5 rename to archive/testdata/flyscan_modes/S14_FS_ArrayData_bad.h5 diff --git a/testdata/flyscan_modes/S16_FS_TrajPnts.h5 b/archive/testdata/flyscan_modes/S16_FS_TrajPnts.h5 similarity index 100% rename from testdata/flyscan_modes/S16_FS_TrajPnts.h5 rename to archive/testdata/flyscan_modes/S16_FS_TrajPnts.h5 diff --git a/testdata/flyscan_modes/S17_FS_TrajPnts_bad.h5 b/archive/testdata/flyscan_modes/S17_FS_TrajPnts_bad.h5 similarity index 100% rename from testdata/flyscan_modes/S17_FS_TrajPnts_bad.h5 rename to archive/testdata/flyscan_modes/S17_FS_TrajPnts_bad.h5 diff --git a/testdata/flyscan_modes/S18_FS_Fixed.h5 b/archive/testdata/flyscan_modes/S18_FS_Fixed.h5 similarity index 100% rename from testdata/flyscan_modes/S18_FS_Fixed.h5 rename to archive/testdata/flyscan_modes/S18_FS_Fixed.h5 diff --git a/testdata/flyscan_modes/S19_FS_Fixed_bad.h5 b/archive/testdata/flyscan_modes/S19_FS_Fixed_bad.h5 similarity index 100% rename from testdata/flyscan_modes/S19_FS_Fixed_bad.h5 rename to archive/testdata/flyscan_modes/S19_FS_Fixed_bad.h5 diff --git a/testdata/flyscan_modes/S1_PSO_Dir_Neg.h5 b/archive/testdata/flyscan_modes/S1_PSO_Dir_Neg.h5 similarity index 100% rename from testdata/flyscan_modes/S1_PSO_Dir_Neg.h5 rename to archive/testdata/flyscan_modes/S1_PSO_Dir_Neg.h5 diff --git a/testdata/flyscan_modes/S2_PSO_Dir_Both.h5 b/archive/testdata/flyscan_modes/S2_PSO_Dir_Both.h5 similarity index 100% rename from testdata/flyscan_modes/S2_PSO_Dir_Both.h5 rename to archive/testdata/flyscan_modes/S2_PSO_Dir_Both.h5 diff --git a/testdata/flyscan_modes/S39_Blank.h5 b/archive/testdata/flyscan_modes/S39_Blank.h5 similarity index 100% rename from testdata/flyscan_modes/S39_Blank.h5 rename to archive/testdata/flyscan_modes/S39_Blank.h5 diff --git a/testdata/flyscan_modes/S3_PSO_Fixed_Both.h5 b/archive/testdata/flyscan_modes/S3_PSO_Fixed_Both.h5 similarity index 100% rename from testdata/flyscan_modes/S3_PSO_Fixed_Both.h5 rename to archive/testdata/flyscan_modes/S3_PSO_Fixed_Both.h5 diff --git a/testdata/flyscan_modes/S44_GC_Adam.h5 b/archive/testdata/flyscan_modes/S44_GC_Adam.h5 similarity index 100% rename from testdata/flyscan_modes/S44_GC_Adam.h5 rename to archive/testdata/flyscan_modes/S44_GC_Adam.h5 diff --git a/testdata/flyscan_modes/S48_FS_60s_2k_2e5.h5 b/archive/testdata/flyscan_modes/S48_FS_60s_2k_2e5.h5 similarity index 100% rename from testdata/flyscan_modes/S48_FS_60s_2k_2e5.h5 rename to archive/testdata/flyscan_modes/S48_FS_60s_2k_2e5.h5 diff --git a/testdata/flyscan_modes/S4_PSO_Fixed_Pos.h5 b/archive/testdata/flyscan_modes/S4_PSO_Fixed_Pos.h5 similarity index 100% rename from testdata/flyscan_modes/S4_PSO_Fixed_Pos.h5 rename to archive/testdata/flyscan_modes/S4_PSO_Fixed_Pos.h5 diff --git a/testdata/scanlog.xml b/archive/testdata/scanlog.xml similarity index 100% rename from testdata/scanlog.xml rename to archive/testdata/scanlog.xml diff --git a/testdata/scanlog.xsl b/archive/testdata/scanlog.xsl similarity index 100% rename from testdata/scanlog.xsl rename to archive/testdata/scanlog.xsl diff --git a/testdata/test_calc.h5 b/archive/testdata/test_calc.h5 similarity index 100% rename from testdata/test_calc.h5 rename to archive/testdata/test_calc.h5 From f9e396b4100b5c7796970dbf63023ff9915d9dc7 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 17:35:04 -0500 Subject: [PATCH 03/28] MNT #48 start with calc module --- calc.py | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 calc.py diff --git a/calc.py b/calc.py new file mode 100644 index 0000000..a3055de --- /dev/null +++ b/calc.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python + +''' +Calculate R(Q) from arrays of measured data using numpy +''' + + +import datetime +import h5py +import logging +import math +import numpy +import os +import spec2nexus.eznx +import spec2nexus.spec + + +logger = logging.getLogger(__name__) + +# TEST_FILE_FLYSCAN = os.path.join('testdata', 'S217_E7_600C_87min.h5') +#TEST_FILE_FLYSCAN = os.path.join('testdata', 'Blank_0016.h5') +TEST_FILE_FLYSCAN = os.path.join('testdata', 'S6_r1SOTy2_0235.h5') +# TEST_FILE_UASCAN = os.path.join('testdata', '03_18_GlassyCarbon.dat') +# TEST_UASCAN_SCAN_NUMBER = 522 +# TEST_FILE_UASCAN = "/share1/USAXS_data/2021-09/09_18_test/09_18_test.dat" +# TEST_UASCAN_SCAN_NUMBER = 11 +TEST_FILE_UASCAN = "/share1/USAXS_data/2022-11/11_03_24keVTest/11_03_24keVTest.dat" +TEST_UASCAN_SCAN_NUMBER = 248 +TEST_FILE_OUTPUT = os.path.join('testdata', 'test_calc.h5') + +CUTOFF = 0.4 # when calculating the center, look at data above CUTOFF*R_max +ZINGER_THRESHOLD = 2 + +class FileNotFound(RuntimeError): pass + + +def calc_R_Q(wavelength, ar, seconds, pd, pd_bkg, pd_gain, I0, + I0_bkg=None, I0_gain=None, ar_center=None, + V_f_gain=None): + ''' + Calculate :math:`R(Q)` + + :param float wavelength: :math:`lambda`, (:math:`\A`) + :param float ar_center: center of rocking curve along AR axis + :param numpy.ndarray([float]) ar: array of crystal analyzer angles + :param numpy.ndarray([float]) seconds: array of counting time for each point + :param numpy.ndarray([float]) pd: array of photodiode counts + :param numpy.ndarray([float]) pd_bkg: array of photodiode amplifier backgrounds + :param numpy.ndarray([float]) pd_gain: array of photodiode amplifier gains + :param numpy.ndarray([float]) I0: array of incident monitor counts + :param numpy.ndarray([float]) I0_bkg: array of I0 backgrounds + :param numpy.ndarray([float]) I0_amp_gain: array of I0 amplifier gains + :param numpy.ndarray([float]) V_f_gain: array of voltage-frequency converter gains + :returns dictionary: Q, R + :param numpy.ndarray([float]) qVec: :math:`Q` + :param numpy.ndarray([float]) rVec: :math:`R = I/I_o` + ''' + r = amplifier_corrections(pd, seconds, pd_bkg, pd_gain) + r0 = amplifier_corrections(I0, seconds, I0_bkg, I0_gain) + + rVec = r / r0 + if V_f_gain is not None: # but why? + rVec /= V_f_gain + rVec = numpy.ma.masked_less_equal(rVec, 0) + + ar_r_peak = ar[numpy.argmax(rVec)] # ar value at peak R + rMax = rVec.max() + if ar_center is None: # compute ar_center from rVec and ar + ar_center = centroid(ar, rVec) # centroid of central peak + + d2r = math.pi / 180 + qVec = (4 * math.pi / wavelength) * numpy.sin(d2r*(ar_center - ar)/2) + + # trim off masked points + r0 = remove_masked_data(r0, rVec.mask) + r = remove_masked_data(r, rVec.mask) + ar = remove_masked_data(ar, rVec.mask) + qVec = remove_masked_data(qVec, rVec.mask) + rVec = remove_masked_data(rVec, rVec.mask) + + result = dict(Q=qVec, R=rVec, + ar=ar, r=r, r0=r0, + ar_0=ar_center, ar_r_peak=ar_r_peak, r_peak=rMax) + return result + + +def amplifier_corrections(signal, seconds, dark, gain): + ''' + correct for amplifier dark current and gain + + :math:`v = (s - t*d) / g` + ''' + #v = (signal - seconds*dark) / gain + v = numpy.array(signal, dtype=float) + if dark is not None: # compatibility with older USAXS data + v = numpy.ma.masked_less_equal(v - seconds*dark, 0) + if gain is not None: # compatibility with older USAXS data + gain = numpy.ma.masked_less_equal(gain, 0) + v /= gain + return v + + +def centroid(x, y): + '''compute centroid of y(x)''' + import scipy.integrate + + def zinger_test(u, v): + m = max(v) + p = numpy.where(v==m)[0][0] + top = (v[p-1] + v[p] + v[p+1])/3 + bot = (v[p-1] + v[p+1])/2 + v_test = top/bot + logger.debug("zinger test: %f", v_test) + return v_test + + a = remove_masked_data(x, y.mask) + b = remove_masked_data(y, y.mask) + + while zinger_test(a, b) > ZINGER_THRESHOLD: + R_max = max(b) + peak_index = numpy.where(b==R_max)[0][0] + # delete or mask x[peak_index], and y[peak_index] + logger.debug("removing zinger at ar = %f", a[peak_index]) + a = numpy.delete(a, peak_index) + b = numpy.delete(b, peak_index) + + # gather the data nearest the peak (above the CUTOFF) + R_max = max(b) + cutoff = R_max * CUTOFF + peak_index = numpy.where(b==R_max)[0][0] + n = len(a) + + # walk down each side from the peak + pLo = peak_index + while pLo >= 0 and b[pLo] > cutoff: + pLo -= 1 + + pHi = peak_index + 1 + while pHi < n and b[pHi] > cutoff: + pHi += 1 + + # enforce boundaries + pLo = max(0, pLo+1) # the lowest ar above the cutoff + pHi = min(n-1, pHi) # the highest ar (+1) above the cutoff + + if pHi - pLo == 0: + emsg = "not enough data to find peak center - not expected" + logger.debug(emsg) + raise KeyError(emsg) + elif pHi - pLo == 1: + # trivial answer + emsg = "peak is 1 point, picking peak position as center" + logger.debug(emsg) + return x[peak_index] + + a = a[pLo:pHi] + b = b[pLo:pHi] + + weight = b*b + top = scipy.integrate.simps(a*weight, a) + bottom = scipy.integrate.simps(weight, a) + center = top/bottom + + emsg = "computed peak center: " + str(center) + logger.debug(emsg) + return center + + +def remove_masked_data(data, mask): + '''remove all masked data, convenience routine''' + arr = numpy.ma.masked_array(data=data, mask=mask) + return arr.compressed() + + +def test_flyScan(filename): + '''test data reduction from a flyScan (in an HDF5 file)''' + if not os.path.exists(filename): + raise FileNotFound(filename) + + import reduceFlyData + fs = reduceFlyData.UsaxsFlyScan(filename) + # compute the R(Q) profile + fs.reduce() + usaxs = fs.reduced + return usaxs + + +def test_uascan(filename): + '''test data reduction from an uascan (in a SPEC file)''' + if not os.path.exists(filename): + raise FileNotFound(filename) + + # open the SPEC data file + sdf_object = spec2nexus.spec.SpecDataFile(filename) + sds = sdf_object.getScan(TEST_UASCAN_SCAN_NUMBER) + sds.interpret() + return reduce_uascan(sds) + + +def reduce_uascan(sds): + '''data reduction of an uascan (in a SPEC file) + + :params obj sds: spec2nexus.spec.SpecDataFileScan object + :returns: dictionary of title and R(Q) + ''' + # get the raw data from the data file + created_by_bluesky = sds.header.comments[0].startswith("Bluesky ") + if not created_by_bluesky: # SPEC created this data file + wavelength = float(sds.metadata['DCM_lambda']) + ar = numpy.array(sds.data['ar']) + seconds = numpy.array(sds.data['seconds']) + pd = numpy.array(sds.data['pd_counts']) + I0 = numpy.array(sds.data['I0']) + I0_amplifier_gain = numpy.array(sds.metadata['I0AmpGain']) + pd_range = numpy.array(sds.data['pd_range'], dtype=int) + ar_center = float(sds.metadata['arCenter']) + + # gain & dark are stored as 1-offset, pad here with 0-offset to simplify list handling + gain = [0, ] + map(lambda _: sds.metadata["UPD2gain" + str(_)], range(1,6)) + dark = [0, ] + map(lambda _: sds.metadata["UPD2bkg" + str(_)], range(1,6)) + + # create numpy arrays to match the ar & pd data + pd_gain = map(lambda _: gain[_], pd_range) + pd_dark = map(lambda _: dark[_], pd_range) + else: # Bluesky created this data file + # BS plan writes a NeXus file with the raw data. + # rebuild the full HDF5 data file name + filename = os.path.join(sds.MD["hdf5_path"], sds.MD["hdf5_file"]) + if not os.path.exists(filename): + raise FileNotFoundError("Could not find uascan data file: %s", filename) + + with h5py.File(filename, "r") as root: + # get all data from the HDF5 file + entry = root["/entry"] + baseline = entry["instrument/bluesky/streams/baseline"] + primary = entry["instrument/bluesky/streams/primary"] + + # Must copy from h5py into local data to keep once h5py file is closed. + # The .value property does this. + wavelength = entry["instrument/monochromator/wavelength"].value + ar = primary["a_stage_r/value"].value + pps = sds.MD.get("scaler_pulses_per_second", 1e-7) + seconds = pps * primary["seconds/value"].value # convert from counts + pd = primary["PD_USAXS/value"].value + I0 = primary["I0_USAXS/value"].value + I0_amplifier_gain = primary["I0_autorange_controls_gain/value"].value + ar_center = baseline["terms_USAXS_center_AR/value_start"].value + + pd_gain = primary["upd_autorange_controls_gain/value"].value + + pd_range = primary["upd_autorange_controls_reqrange/value"] + bkg = [] + for ch in range(5): + addr = "upd_autorange_controls_ranges_gain%d_background" % ch + bkg.append(baseline[addr+"/value_start"].value) + pd_dark = [bkg[i] for i in pd_range] + + # compute the R(Q) profile + usaxs = calc_R_Q( + wavelength, ar, seconds, pd, pd_dark, pd_gain, I0, + I0_gain=I0_amplifier_gain, ar_center=ar_center + ) + return usaxs + + +def iso8601_datetime(): + '''return current date & time as modified ISO8601=compliant string''' + t = datetime.datetime.now() + # standard ISO8601 uses 'T', blank space instead is now allowed + s = str(t).split('.')[0] + return s + + +def bin_xref(x, bins): + ''' + Return an array of arrays. + Outer array is in bins, inner array contains indices of x in each bin, + + :param ndarray x: values to be mapped + :param ndarray bins: new bin boundaries + ''' + indices = numpy.digitize(x, bins) + xref_dict = {} + for i, v in enumerate(indices): + if 0 < v < len(bins): + if str(v) not in xref_dict: + xref_dict[str(v)] = [] + xref_dict[str(v)].append(i) + key_list = map(int, xref_dict.keys()) + xref = [xref_dict[str(key)] for key in sorted(key_list)] + return numpy.array(xref) + + +def developer_main(): + path = os.path.dirname(__file__) + hdf5FileName = os.path.abspath(os.path.join(path, TEST_FILE_FLYSCAN)) + fs = test_flyScan(hdf5FileName) + + specFileName = os.path.abspath(os.path.join(path, TEST_FILE_UASCAN)) + ua = test_uascan(specFileName) + + # - - - - - - - - - - - - - - - - - - - - - - - - - - + + # write results to a NeXus file + + nx = spec2nexus.eznx.makeFile(os.path.abspath(os.path.join(path, TEST_FILE_OUTPUT)), + signal='flyScan', + timestamp=str(datetime.datetime.now()), + writer='USAXS livedata.calc and spec2nexus.eznx', + purpose='testing common USAXS calculation code on different scan file types') + + nxentry = spec2nexus.eznx.makeGroup(nx, 'flyScan', 'NXentry', signal='data') + nxentry.create_dataset('title', data=hdf5FileName) + nxdata = spec2nexus.eznx.makeGroup(nxentry, 'data', 'NXdata', signal='R', axes='Q') + for k, v in sorted(fs['full'].items()): + spec2nexus.eznx.makeDataset(nxdata, k, v) + + nxentry = spec2nexus.eznx.makeGroup(nx, 'uascan', 'NXentry', signal='data') + nxentry.create_dataset('title', data=specFileName) + nxdata = spec2nexus.eznx.makeGroup(nxentry, 'data', 'NXdata', signal='R', axes='Q') + for k, v in sorted(ua.items()): + spec2nexus.eznx.makeDataset(nxdata, k, v) + + nx.close() + + +if __name__ == '__main__': + developer_main() +print("\n", __name__) From 6bd3b901dff2e01f0dc1386fe7e752e7dde081f6 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 17:36:15 -0500 Subject: [PATCH 04/28] STY #48 apply black code style --- calc.py | 189 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 111 insertions(+), 78 deletions(-) diff --git a/calc.py b/calc.py index a3055de..a85c5ea 100644 --- a/calc.py +++ b/calc.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -''' +""" Calculate R(Q) from arrays of measured data using numpy -''' +""" import datetime @@ -18,26 +18,38 @@ logger = logging.getLogger(__name__) # TEST_FILE_FLYSCAN = os.path.join('testdata', 'S217_E7_600C_87min.h5') -#TEST_FILE_FLYSCAN = os.path.join('testdata', 'Blank_0016.h5') -TEST_FILE_FLYSCAN = os.path.join('testdata', 'S6_r1SOTy2_0235.h5') +# TEST_FILE_FLYSCAN = os.path.join('testdata', 'Blank_0016.h5') +TEST_FILE_FLYSCAN = os.path.join("testdata", "S6_r1SOTy2_0235.h5") # TEST_FILE_UASCAN = os.path.join('testdata', '03_18_GlassyCarbon.dat') # TEST_UASCAN_SCAN_NUMBER = 522 # TEST_FILE_UASCAN = "/share1/USAXS_data/2021-09/09_18_test/09_18_test.dat" # TEST_UASCAN_SCAN_NUMBER = 11 TEST_FILE_UASCAN = "/share1/USAXS_data/2022-11/11_03_24keVTest/11_03_24keVTest.dat" TEST_UASCAN_SCAN_NUMBER = 248 -TEST_FILE_OUTPUT = os.path.join('testdata', 'test_calc.h5') +TEST_FILE_OUTPUT = os.path.join("testdata", "test_calc.h5") -CUTOFF = 0.4 # when calculating the center, look at data above CUTOFF*R_max +CUTOFF = 0.4 # when calculating the center, look at data above CUTOFF*R_max ZINGER_THRESHOLD = 2 -class FileNotFound(RuntimeError): pass - -def calc_R_Q(wavelength, ar, seconds, pd, pd_bkg, pd_gain, I0, - I0_bkg=None, I0_gain=None, ar_center=None, - V_f_gain=None): - ''' +class FileNotFound(RuntimeError): + pass + + +def calc_R_Q( + wavelength, + ar, + seconds, + pd, + pd_bkg, + pd_gain, + I0, + I0_bkg=None, + I0_gain=None, + ar_center=None, + V_f_gain=None, +): + """ Calculate :math:`R(Q)` :param float wavelength: :math:`lambda`, (:math:`\A`) @@ -54,62 +66,69 @@ def calc_R_Q(wavelength, ar, seconds, pd, pd_bkg, pd_gain, I0, :returns dictionary: Q, R :param numpy.ndarray([float]) qVec: :math:`Q` :param numpy.ndarray([float]) rVec: :math:`R = I/I_o` - ''' - r = amplifier_corrections(pd, seconds, pd_bkg, pd_gain) + """ + r = amplifier_corrections(pd, seconds, pd_bkg, pd_gain) r0 = amplifier_corrections(I0, seconds, I0_bkg, I0_gain) rVec = r / r0 - if V_f_gain is not None: # but why? + if V_f_gain is not None: # but why? rVec /= V_f_gain rVec = numpy.ma.masked_less_equal(rVec, 0) ar_r_peak = ar[numpy.argmax(rVec)] # ar value at peak R rMax = rVec.max() - if ar_center is None: # compute ar_center from rVec and ar - ar_center = centroid(ar, rVec) # centroid of central peak + if ar_center is None: # compute ar_center from rVec and ar + ar_center = centroid(ar, rVec) # centroid of central peak d2r = math.pi / 180 - qVec = (4 * math.pi / wavelength) * numpy.sin(d2r*(ar_center - ar)/2) + qVec = (4 * math.pi / wavelength) * numpy.sin(d2r * (ar_center - ar) / 2) # trim off masked points - r0 = remove_masked_data(r0, rVec.mask) - r = remove_masked_data(r, rVec.mask) - ar = remove_masked_data(ar, rVec.mask) + r0 = remove_masked_data(r0, rVec.mask) + r = remove_masked_data(r, rVec.mask) + ar = remove_masked_data(ar, rVec.mask) qVec = remove_masked_data(qVec, rVec.mask) rVec = remove_masked_data(rVec, rVec.mask) - result = dict(Q=qVec, R=rVec, - ar=ar, r=r, r0=r0, - ar_0=ar_center, ar_r_peak=ar_r_peak, r_peak=rMax) + result = dict( + Q=qVec, + R=rVec, + ar=ar, + r=r, + r0=r0, + ar_0=ar_center, + ar_r_peak=ar_r_peak, + r_peak=rMax, + ) return result def amplifier_corrections(signal, seconds, dark, gain): - ''' + """ correct for amplifier dark current and gain :math:`v = (s - t*d) / g` - ''' - #v = (signal - seconds*dark) / gain + """ + # v = (signal - seconds*dark) / gain v = numpy.array(signal, dtype=float) - if dark is not None: # compatibility with older USAXS data - v = numpy.ma.masked_less_equal(v - seconds*dark, 0) - if gain is not None: # compatibility with older USAXS data + if dark is not None: # compatibility with older USAXS data + v = numpy.ma.masked_less_equal(v - seconds * dark, 0) + if gain is not None: # compatibility with older USAXS data gain = numpy.ma.masked_less_equal(gain, 0) v /= gain return v def centroid(x, y): - '''compute centroid of y(x)''' + """compute centroid of y(x)""" import scipy.integrate def zinger_test(u, v): m = max(v) - p = numpy.where(v==m)[0][0] - top = (v[p-1] + v[p] + v[p+1])/3 - bot = (v[p-1] + v[p+1])/2 - v_test = top/bot + p = numpy.where(v == m)[0][0] + top = (v[p - 1] + v[p] + v[p + 1]) / 3 + bot = (v[p - 1] + v[p + 1]) / 2 + v_test = top / bot logger.debug("zinger test: %f", v_test) return v_test @@ -118,7 +137,7 @@ def zinger_test(u, v): while zinger_test(a, b) > ZINGER_THRESHOLD: R_max = max(b) - peak_index = numpy.where(b==R_max)[0][0] + peak_index = numpy.where(b == R_max)[0][0] # delete or mask x[peak_index], and y[peak_index] logger.debug("removing zinger at ar = %f", a[peak_index]) a = numpy.delete(a, peak_index) @@ -127,7 +146,7 @@ def zinger_test(u, v): # gather the data nearest the peak (above the CUTOFF) R_max = max(b) cutoff = R_max * CUTOFF - peak_index = numpy.where(b==R_max)[0][0] + peak_index = numpy.where(b == R_max)[0][0] n = len(a) # walk down each side from the peak @@ -140,8 +159,8 @@ def zinger_test(u, v): pHi += 1 # enforce boundaries - pLo = max(0, pLo+1) # the lowest ar above the cutoff - pHi = min(n-1, pHi) # the highest ar (+1) above the cutoff + pLo = max(0, pLo + 1) # the lowest ar above the cutoff + pHi = min(n - 1, pHi) # the highest ar (+1) above the cutoff if pHi - pLo == 0: emsg = "not enough data to find peak center - not expected" @@ -156,10 +175,10 @@ def zinger_test(u, v): a = a[pLo:pHi] b = b[pLo:pHi] - weight = b*b - top = scipy.integrate.simps(a*weight, a) + weight = b * b + top = scipy.integrate.simps(a * weight, a) bottom = scipy.integrate.simps(weight, a) - center = top/bottom + center = top / bottom emsg = "computed peak center: " + str(center) logger.debug(emsg) @@ -167,17 +186,18 @@ def zinger_test(u, v): def remove_masked_data(data, mask): - '''remove all masked data, convenience routine''' + """remove all masked data, convenience routine""" arr = numpy.ma.masked_array(data=data, mask=mask) return arr.compressed() def test_flyScan(filename): - '''test data reduction from a flyScan (in an HDF5 file)''' + """test data reduction from a flyScan (in an HDF5 file)""" if not os.path.exists(filename): raise FileNotFound(filename) import reduceFlyData + fs = reduceFlyData.UsaxsFlyScan(filename) # compute the R(Q) profile fs.reduce() @@ -186,7 +206,7 @@ def test_flyScan(filename): def test_uascan(filename): - '''test data reduction from an uascan (in a SPEC file)''' + """test data reduction from an uascan (in a SPEC file)""" if not os.path.exists(filename): raise FileNotFound(filename) @@ -198,26 +218,30 @@ def test_uascan(filename): def reduce_uascan(sds): - '''data reduction of an uascan (in a SPEC file) + """data reduction of an uascan (in a SPEC file) :params obj sds: spec2nexus.spec.SpecDataFileScan object :returns: dictionary of title and R(Q) - ''' + """ # get the raw data from the data file created_by_bluesky = sds.header.comments[0].startswith("Bluesky ") if not created_by_bluesky: # SPEC created this data file - wavelength = float(sds.metadata['DCM_lambda']) - ar = numpy.array(sds.data['ar']) - seconds = numpy.array(sds.data['seconds']) - pd = numpy.array(sds.data['pd_counts']) - I0 = numpy.array(sds.data['I0']) - I0_amplifier_gain = numpy.array(sds.metadata['I0AmpGain']) - pd_range = numpy.array(sds.data['pd_range'], dtype=int) - ar_center = float(sds.metadata['arCenter']) + wavelength = float(sds.metadata["DCM_lambda"]) + ar = numpy.array(sds.data["ar"]) + seconds = numpy.array(sds.data["seconds"]) + pd = numpy.array(sds.data["pd_counts"]) + I0 = numpy.array(sds.data["I0"]) + I0_amplifier_gain = numpy.array(sds.metadata["I0AmpGain"]) + pd_range = numpy.array(sds.data["pd_range"], dtype=int) + ar_center = float(sds.metadata["arCenter"]) # gain & dark are stored as 1-offset, pad here with 0-offset to simplify list handling - gain = [0, ] + map(lambda _: sds.metadata["UPD2gain" + str(_)], range(1,6)) - dark = [0, ] + map(lambda _: sds.metadata["UPD2bkg" + str(_)], range(1,6)) + gain = [ + 0, + ] + map(lambda _: sds.metadata["UPD2gain" + str(_)], range(1, 6)) + dark = [ + 0, + ] + map(lambda _: sds.metadata["UPD2bkg" + str(_)], range(1, 6)) # create numpy arrays to match the ar & pd data pd_gain = map(lambda _: gain[_], pd_range) @@ -225,7 +249,7 @@ def reduce_uascan(sds): else: # Bluesky created this data file # BS plan writes a NeXus file with the raw data. # rebuild the full HDF5 data file name - filename = os.path.join(sds.MD["hdf5_path"], sds.MD["hdf5_file"]) + filename = os.path.join(sds.MD["hdf5_path"], sds.MD["hdf5_file"]) if not os.path.exists(filename): raise FileNotFoundError("Could not find uascan data file: %s", filename) @@ -252,33 +276,40 @@ def reduce_uascan(sds): bkg = [] for ch in range(5): addr = "upd_autorange_controls_ranges_gain%d_background" % ch - bkg.append(baseline[addr+"/value_start"].value) + bkg.append(baseline[addr + "/value_start"].value) pd_dark = [bkg[i] for i in pd_range] # compute the R(Q) profile usaxs = calc_R_Q( - wavelength, ar, seconds, pd, pd_dark, pd_gain, I0, - I0_gain=I0_amplifier_gain, ar_center=ar_center + wavelength, + ar, + seconds, + pd, + pd_dark, + pd_gain, + I0, + I0_gain=I0_amplifier_gain, + ar_center=ar_center, ) return usaxs def iso8601_datetime(): - '''return current date & time as modified ISO8601=compliant string''' + """return current date & time as modified ISO8601=compliant string""" t = datetime.datetime.now() # standard ISO8601 uses 'T', blank space instead is now allowed - s = str(t).split('.')[0] + s = str(t).split(".")[0] return s def bin_xref(x, bins): - ''' + """ Return an array of arrays. Outer array is in bins, inner array contains indices of x in each bin, :param ndarray x: values to be mapped :param ndarray bins: new bin boundaries - ''' + """ indices = numpy.digitize(x, bins) xref_dict = {} for i, v in enumerate(indices): @@ -303,27 +334,29 @@ def developer_main(): # write results to a NeXus file - nx = spec2nexus.eznx.makeFile(os.path.abspath(os.path.join(path, TEST_FILE_OUTPUT)), - signal='flyScan', - timestamp=str(datetime.datetime.now()), - writer='USAXS livedata.calc and spec2nexus.eznx', - purpose='testing common USAXS calculation code on different scan file types') + nx = spec2nexus.eznx.makeFile( + os.path.abspath(os.path.join(path, TEST_FILE_OUTPUT)), + signal="flyScan", + timestamp=str(datetime.datetime.now()), + writer="USAXS livedata.calc and spec2nexus.eznx", + purpose="testing common USAXS calculation code on different scan file types", + ) - nxentry = spec2nexus.eznx.makeGroup(nx, 'flyScan', 'NXentry', signal='data') - nxentry.create_dataset('title', data=hdf5FileName) - nxdata = spec2nexus.eznx.makeGroup(nxentry, 'data', 'NXdata', signal='R', axes='Q') - for k, v in sorted(fs['full'].items()): + nxentry = spec2nexus.eznx.makeGroup(nx, "flyScan", "NXentry", signal="data") + nxentry.create_dataset("title", data=hdf5FileName) + nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") + for k, v in sorted(fs["full"].items()): spec2nexus.eznx.makeDataset(nxdata, k, v) - nxentry = spec2nexus.eznx.makeGroup(nx, 'uascan', 'NXentry', signal='data') - nxentry.create_dataset('title', data=specFileName) - nxdata = spec2nexus.eznx.makeGroup(nxentry, 'data', 'NXdata', signal='R', axes='Q') + nxentry = spec2nexus.eznx.makeGroup(nx, "uascan", "NXentry", signal="data") + nxentry.create_dataset("title", data=specFileName) + nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") for k, v in sorted(ua.items()): spec2nexus.eznx.makeDataset(nxdata, k, v) nx.close() -if __name__ == '__main__': +if __name__ == "__main__": developer_main() print("\n", __name__) From e767d479c0e4d075ecd5ae9ec02f3ba91a31834a Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 17:39:36 -0500 Subject: [PATCH 05/28] STY #48 PEP8 compliance per flake8 --- .flake8 | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f7a1afc --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +[flake8] +exclude = + .git, + __pycache__, + archive, + +# E501 line too long +# many long lines in python code +# most in documentation +# some in test cases +max-line-length = 115 +# TODO: re-enable E501 error +ignore: E226,E402,E501,E741,F401,F403,W503,W504,W605 From e9488e4d0d9823e4ac400782fd0a06b76c0ad244 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:09:34 -0500 Subject: [PATCH 06/28] MNT #48 another archive --- archive/python2-version/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 archive/python2-version/README.md diff --git a/archive/python2-version/README.md b/archive/python2-version/README.md new file mode 100644 index 0000000..ca18551 --- /dev/null +++ b/archive/python2-version/README.md @@ -0,0 +1,15 @@ +# README for USAXS `livedata` + +Manage the display of live USAXS data at: https://usaxslive.xray.aps.anl.gov/ + +The main code here is *pvwatch.py*. It monitors EPICS PVs +(described in *pvlist.xml*) and writes files in the local +WWW server directory. It can be started and stopped by +the `Makefile` (for manual control) or the `manage.csh` script +for automated startup in a cron task, `/etc/init.d`, or similar. + +Another code module, *buildSpecPlots.sh*, crawls a set of +subdirectories, looking for data files and plotting +all scans using defaults (last column *vs.* first column). + +NOTE: [v1 code documentation](archive/docs/index.md) From df11c816c8105ea908101cf94d4f8ceb4ef2308b Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:09:42 -0500 Subject: [PATCH 07/28] DOC #48 --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index ca18551..7582802 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,3 @@ # README for USAXS `livedata` Manage the display of live USAXS data at: https://usaxslive.xray.aps.anl.gov/ - -The main code here is *pvwatch.py*. It monitors EPICS PVs -(described in *pvlist.xml*) and writes files in the local -WWW server directory. It can be started and stopped by -the `Makefile` (for manual control) or the `manage.csh` script -for automated startup in a cron task, `/etc/init.d`, or similar. - -Another code module, *buildSpecPlots.sh*, crawls a set of -subdirectories, looking for data files and plotting -all scans using defaults (last column *vs.* first column). - -NOTE: [v1 code documentation](archive/docs/index.md) From 15a998939997376430e7e96ee69272fd51740e82 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:09:55 -0500 Subject: [PATCH 08/28] PKG #48 use a custom environment --- environment.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 environment.yml diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..1ca7bd8 --- /dev/null +++ b/environment.yml @@ -0,0 +1,23 @@ +name: livedata + +# micromamba create -f ./environment.yml + +channels: + - conda-forge + - defaults + +dependencies: + - python >=3.8, <3.11 + - black + - databroker + - flake8 + - h5py >=3 + - hdf5plugin + - ipython + - lxml + - ophyd + - pandas + - pint + - pip + - pyRestTable + - spec2nexus From 2cdc10e3f77b2993f3fb0d61c3a6fdcc91eb3092 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:12:15 -0500 Subject: [PATCH 09/28] MNT #48 remove diagnostic --- calc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/calc.py b/calc.py index a85c5ea..38b27cf 100644 --- a/calc.py +++ b/calc.py @@ -359,4 +359,3 @@ def developer_main(): if __name__ == "__main__": developer_main() -print("\n", __name__) From d38d84982e9b00921f6cf0f2c47fc9aa113af385 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:29:17 -0500 Subject: [PATCH 10/28] DOC #48 test data folder --- data/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/README.md diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000..bed5f89 --- /dev/null +++ b/data/README.md @@ -0,0 +1 @@ +# Test data \ No newline at end of file From 3db2eb2cd34d3860eb3bae42781a46005d31daaa Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:29:44 -0500 Subject: [PATCH 11/28] ENH #48 calc - work-in-progress --- calc.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/calc.py b/calc.py index 38b27cf..6e6544a 100644 --- a/calc.py +++ b/calc.py @@ -11,22 +11,26 @@ import math import numpy import os +import pathlib import spec2nexus.eznx import spec2nexus.spec logger = logging.getLogger(__name__) -# TEST_FILE_FLYSCAN = os.path.join('testdata', 'S217_E7_600C_87min.h5') -# TEST_FILE_FLYSCAN = os.path.join('testdata', 'Blank_0016.h5') -TEST_FILE_FLYSCAN = os.path.join("testdata", "S6_r1SOTy2_0235.h5") -# TEST_FILE_UASCAN = os.path.join('testdata', '03_18_GlassyCarbon.dat') +USAXS_DATA = pathlib.Path("/share1/USAXS_data") +TESTDATA = pathlib.Path(__file__).parent / "data" + +# TEST_FILE_FLYSCAN = TESTDATA / 'S217_E7_600C_87min.h5' +TEST_FILE_FLYSCAN = TESTDATA / "Blank_0016.h5" +# TEST_FILE_FLYSCAN = TESTDATA / "S6_r1SOTy2_0235.h5" +# TEST_FILE_UASCAN = TESTDATA / '03_18_GlassyCarbon.dat' # TEST_UASCAN_SCAN_NUMBER = 522 -# TEST_FILE_UASCAN = "/share1/USAXS_data/2021-09/09_18_test/09_18_test.dat" +# TEST_FILE_UASCAN = USAXS_DATA / "2021-09/09_18_test/09_18_test.dat" # TEST_UASCAN_SCAN_NUMBER = 11 -TEST_FILE_UASCAN = "/share1/USAXS_data/2022-11/11_03_24keVTest/11_03_24keVTest.dat" +TEST_FILE_UASCAN = USAXS_DATA / "2022-11/11_03_24keVTest/11_03_24keVTest.dat" TEST_UASCAN_SCAN_NUMBER = 248 -TEST_FILE_OUTPUT = os.path.join("testdata", "test_calc.h5") +TEST_FILE_OUTPUT = TESTDATA / "test_calc.h5" CUTOFF = 0.4 # when calculating the center, look at data above CUTOFF*R_max ZINGER_THRESHOLD = 2 @@ -193,7 +197,7 @@ def remove_masked_data(data, mask): def test_flyScan(filename): """test data reduction from a flyScan (in an HDF5 file)""" - if not os.path.exists(filename): + if not filename.exists(): raise FileNotFound(filename) import reduceFlyData @@ -207,7 +211,7 @@ def test_flyScan(filename): def test_uascan(filename): """test data reduction from an uascan (in a SPEC file)""" - if not os.path.exists(filename): + if not filename.exists(): raise FileNotFound(filename) # open the SPEC data file @@ -249,8 +253,8 @@ def reduce_uascan(sds): else: # Bluesky created this data file # BS plan writes a NeXus file with the raw data. # rebuild the full HDF5 data file name - filename = os.path.join(sds.MD["hdf5_path"], sds.MD["hdf5_file"]) - if not os.path.exists(filename): + filename = pathlib.Path(sds.MD["hdf5_path"]) / sds.MD["hdf5_file"] + if not filename.exists(): raise FileNotFoundError("Could not find uascan data file: %s", filename) with h5py.File(filename, "r") as root: @@ -323,19 +327,18 @@ def bin_xref(x, bins): def developer_main(): - path = os.path.dirname(__file__) - hdf5FileName = os.path.abspath(os.path.join(path, TEST_FILE_FLYSCAN)) - fs = test_flyScan(hdf5FileName) + hdf5FileName = TEST_FILE_FLYSCAN + # fs = test_flyScan(hdf5FileName) - specFileName = os.path.abspath(os.path.join(path, TEST_FILE_UASCAN)) - ua = test_uascan(specFileName) + specFileName = TEST_FILE_UASCAN + ua = test_uascan(TEST_FILE_UASCAN) # - - - - - - - - - - - - - - - - - - - - - - - - - - # write results to a NeXus file nx = spec2nexus.eznx.makeFile( - os.path.abspath(os.path.join(path, TEST_FILE_OUTPUT)), + str(TEST_FILE_OUTPUT), signal="flyScan", timestamp=str(datetime.datetime.now()), writer="USAXS livedata.calc and spec2nexus.eznx", @@ -343,13 +346,13 @@ def developer_main(): ) nxentry = spec2nexus.eznx.makeGroup(nx, "flyScan", "NXentry", signal="data") - nxentry.create_dataset("title", data=hdf5FileName) + nxentry.create_dataset("title", data=str(hdf5FileName)) nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") for k, v in sorted(fs["full"].items()): spec2nexus.eznx.makeDataset(nxdata, k, v) nxentry = spec2nexus.eznx.makeGroup(nx, "uascan", "NXentry", signal="data") - nxentry.create_dataset("title", data=specFileName) + nxentry.create_dataset("title", data=str(specFileName)) nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") for k, v in sorted(ua.items()): spec2nexus.eznx.makeDataset(nxdata, k, v) From 873dfb2b00306e535e2ac86e1c3e6079b1416920 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 18:47:52 -0500 Subject: [PATCH 12/28] MNT #48 refactor per h5py update --- calc.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/calc.py b/calc.py index 6e6544a..7cc8807 100644 --- a/calc.py +++ b/calc.py @@ -264,23 +264,22 @@ def reduce_uascan(sds): primary = entry["instrument/bluesky/streams/primary"] # Must copy from h5py into local data to keep once h5py file is closed. - # The .value property does this. - wavelength = entry["instrument/monochromator/wavelength"].value - ar = primary["a_stage_r/value"].value + wavelength = entry["instrument/monochromator/wavelength"][()] + ar = primary["a_stage_r/value"][()] pps = sds.MD.get("scaler_pulses_per_second", 1e-7) - seconds = pps * primary["seconds/value"].value # convert from counts - pd = primary["PD_USAXS/value"].value - I0 = primary["I0_USAXS/value"].value - I0_amplifier_gain = primary["I0_autorange_controls_gain/value"].value - ar_center = baseline["terms_USAXS_center_AR/value_start"].value + seconds = pps * primary["seconds/value"][()] # convert from counts + pd = primary["PD_USAXS/value"][()] + I0 = primary["I0_USAXS/value"][()] + I0_amplifier_gain = primary["I0_autorange_controls_gain/value"][()] + ar_center = baseline["terms_USAXS_center_AR/value_start"][()] - pd_gain = primary["upd_autorange_controls_gain/value"].value + pd_gain = primary["upd_autorange_controls_gain/value"][()] pd_range = primary["upd_autorange_controls_reqrange/value"] bkg = [] for ch in range(5): addr = "upd_autorange_controls_ranges_gain%d_background" % ch - bkg.append(baseline[addr + "/value_start"].value) + bkg.append(baseline[addr + "/value_start"][()]) pd_dark = [bkg[i] for i in pd_range] # compute the R(Q) profile From 9ee8452c8ced65d776c25da22bf6f461e6109088 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 19:03:57 -0500 Subject: [PATCH 13/28] PKG #48 add diagnostic tools --- environment.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/environment.yml b/environment.yml index 1ca7bd8..dc62aa3 100644 --- a/environment.yml +++ b/environment.yml @@ -19,5 +19,7 @@ dependencies: - pandas - pint - pip + - punx - pyRestTable + - pytest - spec2nexus From a8f78cbcdfb21c058d41cca9c64f85b2277ee247 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 19:04:11 -0500 Subject: [PATCH 14/28] TST #48 test data --- data/Blank_0016.h5 | Bin 0 -> 922220 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 data/Blank_0016.h5 diff --git a/data/Blank_0016.h5 b/data/Blank_0016.h5 new file mode 100755 index 0000000000000000000000000000000000000000..29f493f4835e8a1c3fef042ec05e2dcfef0cdea5 GIT binary patch literal 922220 zcmeF)30zHE!$0s-q(nr6O6nX$D$P-;>@?3ah30uqgGvKMLLs6;B28pYR74>pQyG$} zD9TV7A{6@n&N=&Boc9jTeeQkU&;Nbjdp;lMyVu@puf6x$YdU+#T4Q}fK3*|i3jM>w zLm5utXH2rbhR9FQ_%V!GtT1c28#%#2x`uQwCv$=W>Cu;okn{P;^r^#`GptGNZ4C@4 zh{;NyCAk`fD#o1RVa@o*|5G_&Y@lmRKVV1ls9B}7o-Ajq%V zk>!=WT#u$0qpr}m!%GpQ@KAjGgS=fs+=IQz?TB*FOFo71kGU_*lFQn!IrMmBVW!U; zO(Dqico^w@(0(xGR?_xX-r*7cp&@^m9`_IF(W~ZYiZ!`k zfas>2du^dVXqQ zvA-PjzmOh14c7kA)Be7HqZs?=9}*E69u@0aPrs+fEH4qB?jf$h zp53K4$p;oeFL4tBbZcCKFTk?so0ib@(Y71b3K)m;^pTy^J#yL-jD zMn$;CL@3M)at{e~Ra8`(CGV%k*chvMX0%iE^!!4Dy%jvX-Gd{P$W#^N{lj7-{X#<& z!oout?Wjjkly^j6tU^Sjdw672m;xhl*NCXFu+Z>Gg$Vbh-iAT3`q&rwFnU{0|B%B7 zlrH8cDkLx@G&+RQcG2fB+8@X|9$mDr`reV=o@iwC7;TvX(zK)3N@mmc(`I6VgP6%u z808()WU-(q$tx+!t9}o&mMhC^$SWcqYkwH+0m8c0c2q`QQr#n|8Z%AwXX-IhCQl`W zIm)6?m~ni9VyX0&f|e#nbXiPdO^`~kg(b-_E5BLk8aY^7 zMg@D&4}tNalCq$&CP*i7Ya11!7wZ}1J(VrK6kB*2TX;GdW}OG5=dOt+ z#n;!|J=nv`-PX$^P}hn=k!D+O23uH$Ei6lhS?60f#1|cSXs{eP|A%}bw`aiG6eGq= zw)EuL!U}9*dR?b~Sm!;^dp;wq#Q6SRFIf5IfKp_M;UcqikdXkR5`7O+(6s#YVYaH2 zKP)e^ep-t{navhfXA861M^<@f?I&3d{ro)xL%cDx&}3VW)laZyYq2IswMkj!mz8d0 zxO;$-rgcxbjAppWj1*w!;<3!AWoP028;J^OjTEtKq$Z}cc; zZ0nh`g)P{^mSmVcpHQ_JpI<)?OYb0W`j|HYzXE>#*!&!%*s_J~$S|wj{rNa7!{{fT ze!}h9)^lJBGi%ZJnG48CR(s4!i1oXWeS3^{R#VrOF_gf%PT!MaOf6&!FJcQXCd2Ic z=1k82_3c^shuB4Wharv&TY9c+VK=rgx>m-AeS03{d{+O$%1`F;k=tVx4#f2&M3K9ZALu{vl2JB%`zcVP5DD)AULDIiY{px4VQnn=+o1mA{dUcFNwL^~0Xk zZ<5g{j7EzUfUNyu&59x?I7nIXmy%)jc2PxLSy9t7DAY61)hFD0NtAbpXDmHxG#Q^o z3~Pdv5$T6{%gAX~f25?q+;SW_Uxbu(oQ&;SxX0-Exrc;!2SvpHkQRM1fi0ZK7G6$< ze>GhUVS~KGmE= zW(#Nj3+Y-R-7ReEZDk8*v4ykAFl&6vDrby#13xY_y<$+Ts6%$QVig~#MTNS&3yKmM=I0Xs`8eGXi) z;~|eSj2w8e`ZM`}O^Q_4+DfoI++kA7lHh9^lt^h67g^=;wwNi*;SF zg=}aV$<>(kp7%HFy|f+k3_FtXSNJl+#$?z!iW$yfLdnQwPJH5IM(7UWVocPM;iopt zurV1fH(-VtJBygr+RQMOOpoeArbmWFPBBL^#$=e>jeqzLMwC$#{FI%n4S!Ml$?nGIKr|q)_C3jwelqjoB|OF{AzEZ`OSb_I&t$ z-{If8kHKjE$xI)Ez5L#;aWn3VMELuLxMPU>NB2X9(~po$XDs7rSuGYL@L#@ut6lS( z!i@TJyY}195qz4)jQ{&v`!AdSXU6}M9ALF;tbR?JJT88+9I+1b=erIR3iB=lt}l?c z_*wN`k+s4f$f}R*?ZaQKkE7|^CF>ErP5PeRzjt4lF~%U%8^<^%R!heS{PM+tJ8Sej zVAUhmID;TFbOjlcInMa)`uNcfe^KT*V;HOIP}HaXoSqvQUP0~$l}wjPkQ2WFt3K3{^?&Db?tp7i4HmhA@&#&+8(7!j%V2lZPn7P5cCj09x#Ba4jE699aL1yS5wL|LU zjTmy1|I<#xKl}R^bKtkyA!)Yy>L23i?}a;Ju51kvtDgVac8FDf*xQG{T7MYjm@Lf9 zYtetM{?wAcOIDEi%BVjazkQuBCxiI|v6eBgr%+-xFvHLPsZf8hWd6@somGD*!p!}Z zW?rnK#5gj`!GG!~SoMef_w-+_KR=GEnfE?_tN#4@^)`w!^N1VC1OKD`=ZzgR;;(Pz zKb3p`r2GF%4zTKvBe_FV(!EMQ?;-2=XXGzW{2)&9p%ftk)4a(jQZ{`}bg(4Ud` zp5DLryL2Rd_sG74*~I*MP5G_sL>X$#w9|tUVZ%Wz7+E}FDr+BK5*9a z{;c~v9OM-v4!j>j|FH6nCH)sUeOdrt^y|x9WSEnalM#i7Tz?ox*lhax%vj7R`uBLU zk)TJuHc`*{`*DtCT%RW2TVcHp^M}`|8KW1n5fEi;=Fg7s87uwr_4C8)zw$IV;}Lp9 zSHwT&llJtI5EqbjY{S#`xuI2iQaLGV1mlT#%xA^MBtCEE669?@MqXjg=GgW6r!Ue7 zouEk?8Iank+~WhIT~E)_g|~tfyCtAZLi*F83p7G#PQb?wX!UgD*9N%!rr-5)xJKID zARqP%J`9{F^>SIoE!Nn^bj4qy|5sAxemL#Jz zI87sBc16W$!1Z5-371sUh%af|YWZN(PI>d-Q#4}gP2aT_PSOa4S=q+IuzA60C)X+( zA#J@beRm~|Sa`wZ$h`_0armkE7G79kFUqZQf<~B7weH#;rx820Ke_2&j`O4S>QeME z8gVoJ&C2*P8sWTa?(OKKG~!HhYl{C78e!+1=52GBM%Wjho~v*O=PjxDG3P-VA$Lhh z@alf#U;2pRb)__7_@h*JoqfoEw>#syN@&EnVONt@71M~wZ6o4^i)aLAOlw1K0n!r; zQlGE~`;(KpA!Rr6FXnPqS00V?Mznl0eQ+k3M$8@Orf9hWt)wwc}`naU+je zZVZjERiE?vWfaOolErQ52pZuvoxjE*j7DtamWW&uOe3n!Kdgug!10MESVsENh*I;x z!G&HlVwR`mDg}2M!LuWw^sO_ExO;S|a{fXZu|!+FO?v^2I5bopamkKGjMKPQrfp3l zBnQ5^>^4WfMV!!lZA>F_^r!L27|@6#m#x-X>(Yp~zQa~{Xw!(nmd6KOHE4uz)Pmba zYBa(ozPMaWiAHR9c{H{~4&{&YVexWl8j&^HPIKH;8c|EDTbC|LBZg)sJH8d85sJk} zJ?9Fe-ZmHqEggsSyT&SKjiwPRv-2bKxKSS{fqfe|X@tJOfneXSR6_7d^q%RTsKj)? za_bxKsD$ppiL+gLsKi6DIfA#lsKmV)k;RJbR3aqh%l?RmRKiiewLQ0)N-Us#;yKVr zCEk~PeXzTMN@VVd7LBW;5^e406E)9JiB->pF1J=t38TiP$9<1diP)nTD0fP!M8mNP z-)V(ZBEhDmc+pNO!I`>tS!5QK7#Kc$SJ*}>QS82;(P|Bqs5J>_7?(sPE*}{%J`qbL zu2Ay0wZo}In#tL-MFCVoJ#?S)8xJb6#W6xid=Zt%5f_x2VMir)C^(5vFryM5Ha)H$sN)jKV=ATH~ zCP|ck))bFUktD?ZMeEJ?U*w-~$MNRrUn)i0c+EJ+yb8))U5BuU(o ztZBC9mLvjs+=ETtN)TL@$H#rVFG1vUnaD(U4k|2~1t4>QDEp3$#RD6D>k)t}7N6Mxk{Ye2_;l7j_*)R2w}<8y27mwj(Urt*5K3Nbl|R^)ac!m>knH_$u;Y&s@)k>Pz)b zH_Q!xFKuWiba*ancNnRV$(Lq<=Q zsTvZTvZ=!Nt{W18)sdMZk@^H{IXx|kc@N?1gHPuy;`XM+-}D9qYZ+l5Rc5y{X(SuZ0+G2S}D z^UL9(zTxh{zr1Q3NxmWE9TVmqj;EAyWs-4y(%;9Q@s3Y;6kcefznDXAn%u5yxVKl7 zr?;1@PgD@@)>A0#^Oe=;r-Xi*D;TpD-jE?av^IaqQXq>r=%=F0JWxh8=Zi4=Z%=hOvMli<5Q6c_#+LWHJ zzn>0cdt^HFXV|}wvzh0KxnGg7VPwJkqvt_>Om7k+J=S~Y;~D$KOwY?Z!ZX}IjP+6< z;{BQZWIhV?bN>F7^GUzd{oChr8+ks-3*w9e{QJ)*;~qVENc6G)-*G;vjC5Fk-{7yG zPZ}e=zyEwPAF*OTpQD-Oo-FWxrhd|&0Q>FpxsyDf%;&@qmGSxQ_UhlOpY(c5{)DAW z!6fUs3TY$8=2(>)0V+A;_hH`W=AsZ}G`Cs5A3uy-+^GNi@tC~>1_tDHLo&N5?AH;k z$z&9De?L7|tK3Ll_h6;RejViZQ~A#={X6Q_RP5g0*-lC^();`C74wk?_IkxfVGQBO z6Z6OQN`z4l$5H2!#hIX}j-sdzF)pb5UOg-M!+uaf{KxA$|6V;~4A00zoQ`~C)w30* z%rfN2DuBPd(axOD`euJFUfP(otFN4_u9eba_N&?U%S=W;jL<*z`j;$WO#Geo zmHvpyf17*9;J;e0YW`d5731m{c^>Guf?4%S-Qkx5Bqyk( zzYnuseeVY!+x~uhRzIjd|M%nlUt9Wj)GPW=@xQZP(F^2%qCaK+HYW=qd7@d@%^LsX z$N}kOHy}!TR)Iz$y96V$CkK0Yw4I!4p z@LUGHDE#@~`_fM2CI?6v{N2~p=+~%O=T{q(tadcao>_qZ==Z}(P1m(%cu=Bsx!+DB zxSn>ZOb@=jvEcGtI9_M&lR5AOEqq`$j8c6os0yzsomNzYs`r{La|*@VswBjM8rs#RRDg6IBI z4!HeI^SEz#U_vlOju?O*)el1Z;f_tucE5x7hnwB%h29hSxL?7$ZE{*&(8gnbIQ@Z- z!pgO!Z7}s)ea8cM*&#rp1+MN4S$G?IwB5~agcqWUo3BFA&IiJm;h4x+#|!XLz=|Db zVfLhVZKq*y*A1zYkXvqI@CkT#!JXq}F!u4#&>_fgkZZOdmW?@(Qvz=`Ds~mX<1^%D z?}o3>?OKxyJ8jlK$bqgo{R&&5fcK-6P0(tK-lO%ffM0gjI=JQ5){NEAr!%EH8TNX7 zHCYa$Bfss9gB6ZxBV*uwx8lG^xci;WjWC$KHbXTShE#g(@Q0o7nEF2U>iLh-voFesg?|HPjUG9c=-3WeiI;f#aVzd^Vs<^9Y{@ z!}H{Nbf8sxmH!-g;>4Ke^ao!AW-9ur!p%ifUMj+UcYA|pLXL;oeKX)#b*Z>%@WQK| z!>7P|Nh32P;le3lljsl1gdM(rKm=yC*y;$ukH#HM<6vQ9w(l7Da(BWIFD%<$u#FoU zSBlLV4l8%}-=@F^@s>-!;sKe3nmS?wFlo#4+J1PWXjwoX+%>2?;SEfz48PP3d#8Pg zdjYTW3MqHO53#Ww?NG&CX;T|~I^ocqhtNezt)m4hgnUfB1FsJkler0-{Z3xK4sShv z<8uYpcQtX;!;NC0+w0)y`*#)3!c}KPFPw&!-Nb^Eu-wkE>jYG5_m84K_@liqk>dyy z_?nk?0QRiEDX*4!5W7F2cTahLltD*hI#;9Z%-nI96BD^5b>=Xx2CNH{7f8Z!QL2Gs-bdrfL z2!lx*Cyfb)HCv`c`@$|M_*_|?UhG$G@;#1wav5Or18X4Ww>de zNtONpRN3sFm9kJIAhT&Y44!^*(p30lV6`6==6c^fDh{W$RelzM^>^e5A=rILHDw%( zywp_B2S-J6jpBu|98G2;;PY~|G)^dgRrT^WJUBHj@dno*Ts<&C_ap2Mc(|+&8o#qX z_8P_=^nUpY#vGeSeGVgpBJH0-fws`p$8fhWf7L@Md)nbe3#`7bD{==$&%0yL1kdq~ z3%>>fg!*$Y!%Jo<7canvn`?T{!sshSLT8}$P4zh^;q(=;t|#D=eSXPhFrUg@cnEeJ zb*L+aIny0FilL_Q2#x~S+3Y`Q7Yxr_p}qqqYns_+!@QJ+fJ|s0JCw8$a%-nX zR}JY8vh~l1)YF2cTCFJ!E~q&&Lk{lvGJ`4&-)Z^@O@(v%0!LC| zZLZpviBSFEg`NrUc8++f0Mx&!d2K8_-9SAx3J!aBy@VTvoG{272H&hoNf^R|auSy} z_zXg|9(T)+^zgzV)jk*#?JM>g=I-DA?Iql^Xxo!c==;j^d^^lMJy6gJ^Hf|`-iOTx zH@G)L&$M)%o6tS-8hLJK;3kJU!U13cA&gmp%>; z=dBq$0;MQ7t{s43x}AA@q0x!!A%$?s(KXt;Vf`w>QMqtVQuy6$c&mTUo=kW`wK!-a zlwBLImIm{5r+r-w^GgfP(;pCgH}=DtL>N~sX0Z%94hZruh4;okZCV0TEZ1xfhT&e;+z_#TcgJWRSp2&P&nBC!SI0D+o$-Lx%^7+@(zv98iiRqT| zpJ8fv>Gk(;!MgU~H}D0|$5Gu-c*^s=T~L+pgnkDU9dv7JrAvJgb03a6Y&otOn%v*C zw+SA-Ri<+dmRvo0s~(EyF)K_5YT%_EPt#7pjA&=^3fMjG`2I4OB-gER2&Q;& zUfKt1$A7Xdg6RR*9_@yAvXXssp+wz;H`#Ex#QNw=Sklz~bpw3FBf4@Ojj&+9f z4HDlu!Wb)~0DI`s^X{<~q#7j6H-kR$?-~qYW~HIdJa|wd@rX9`y_G}LfRhHZHmSmU zRWTGrXseCX|fJqn z&R$VI0xm7L5aFaRe>}A8Yb}lVJSY3%Cz#YJp#2_>q%6&S4F&d|9(W1weSNv06IxNf z9eV<&$bTI32%a`*_G^KFRIjLJ2zsNwLpm2mjo6MDTX}9w9ouIDTt>yM`U&)&)D|lG<<2zFrDb^)p2%j0(y6eK~j^u1D zxST`b>TH-?SvRB%J03y~gPt>>iw$MVRCwB0`@AF+H1v8a1~RbGXYtWo`+TwaR;`#NZ=V8cP&x|wB zeR9X%Dww&)vbG#%G!-=;h9_&Ezbb|1NfRi=&}pi&Kt8;BQe7$+`a8(Z%7)j5^BQi3 zA@^?EuZOuEX&!6f3KfOWl~D5DnPrJ^{!`7ASlH%Oupts!1boU0h5W-c^8#Rmu3w=y z%vD;s&kb(qSa)y{l=fMEWImLtcPq1j5+Vx6%;8$Emt{t<^3>*|^WZ~S>BHJ^`1#WP z>QH>6ScwYk-5H%P52e1I-97^vpZvUeDy(UjSSJY^S1PR#gZZUOQ9@9|OVXboCY&Bv zGzzMmKW{MtawIR);ea6{B;~*0!S43ON5wzFl1)my?_lSVUH#o~|HZ*4U9h9o;A%T` z;YvLI2wpQN+}Q%__E)aD1*_(r3b+orgb!FE}7#ulP_u=+so6KyEsny^6nJfK@k(PL6^6<Tnpg;MwbMr_ql~ zE0lhQ^#}Ys-$SB%)5O>Ccu(og7jR1Du{BR&nC$`WHW++<%iDYK_PF4}+t5^BZT=1D z>fSS=0j>|-dZG?$M9uWBfyZKwk3R|3R?Ms^hn<_Y1{{Vh$9e=yA$PahnL@aj2=Unk z_n*k(+YZa^4$H^Pk)N58Cv2@czLt%8diB6OF-b;;W8vCwMe$D~O3q$!^o z3OypU&iK>AL6;VK!G-7N^}E1|xI-CE@Y;4>8GGm^wcwm3q*1pxnZQFE&%V}!n`7Em z=|H2l9uW%>P`tv6Hq9Zc5rI@b-Kyht1mFn0cro zS`->N?71ZXCyh#!9|Na1nXci1L3??gaKfjXD%8K?MUmJI3pRd)JiDGheG5J60yMgz z$H-s8*!0{_|BpRTriDO_L6qoa?I0K8MT{)|u z(Oh%$V^A+lX6r$C{nY1&C6Ise$tioFR6(GBE)<_T=1>;=^dbLk20VOenra%9E66(Qi+6#A#GtH@Z}@kgMKjAXZmvwD8r*R*%?ZnQ(3eCe$Jed zX#?dFhBuhe=O;7|89@2WZK?z;J!9dg3GaS;w_O#!wq1Kw9@0+of0c$CRo2Q)fi`E} zEt~`+oh;T&fS-qLD<21&tM9apg8j+Ect=1@hsi1w7_F?lXaFzZ#E7aSzlUFVr1tj0 z$|3G+&*6eE58kxH=&!p49>GH#3)Jqy6z*>hO)!BkE9wg59WS|s{z6ZVz_z0`a5ew% z>s3&e+wH|Mc=N-7VTWM#{g>innbulTQ1GLYwj{ik zG+bL0mNZD-jOi4-vFI?b+6RHz3XP4Is;ea@|IS@BCVF} zGT7*wwdw#Y6Eg@ahHq`2F5C@g@1(4)eL9?Y20o*hc{?r<}ajMsv!dSbW z?fQ`8_2x(&DA*)zp$;#8I5brmPIZzRl!YoE(;B7Vn=9>wlF;VXj2KZkZ=}1a04()i zGm#JayxH|?1hm{%a*_fS-FB@Rz>8tGh*XF7@Z&63S}%0ancm$6ciOfce*$AdQxhM+ zH5rnIcc9Kd{%evq>*1rvsafY>a%Y=UHJosNy7&n=zQXmkD$=w59Ce3!aBCK?K_6jE3M@2l*1 z8US6k@^ABkr}speyFlIr4c`_(+k<0{*ud?(=ee1|$Fe?S4d5ix*wZ?2UVmJGI@FL2 z5mtts9ad*$;k1dgKq+|R$`b)ec=XMtQz9^Hm6FGJNF>*CkA{Iybq;XBYefgFhVUYt z(AkmiKEZCq`I+zF``4RPx?%mN>YLBtH-o!jZBXjwV?O!|co(wo?YjxxH=WhL3L~p> zS}(vTUf1Xvcr#IOY!&>#UsY5Fbx)bkIRMAU+_+H$YjkxzcfoDr_k75KFQ2|$y&3kG zDNaa-gG*hCQ=pP;oa%CT4l0`VYc_^SI5UzeP&%+ze_&D;F3v6tv33r5R zBD_A^z(vg;6V0IM``BRy(C*U9)jBZ9#B`)OTpgLSP6?*ZyTdCBy$({=O@+OO#CRm& zFx@FB6JU(97{@p${%t6Z@#3NQ?GM8c<~x=Z@)a*2J~(OA*$?l}>{|Q=R&zz&d;w=_ zy*2EB%?*oAK7@|@%jKG()!o6JH=v!d(wNKe!$;4-h;d7QA^{`{sv zys+7K^jO<_I3;S6TrZT(UL4Z}d-#RUKZb@6PL940$Go<1xCO-wuIFBb>dkXHE`)U14uook*otRqzISIhWRhr9xFnz)~#b?;KoJjbEd#W3orSI!{(dwHVHwy)1@cJ zKqsD>_nZ^Dm54_CeQhoFXCa&ZZKH|OA)-7tGx-JNZ4 z=GBX@Hp2?f6y=X@%MK9*y?xCH+ov-E9j^ree5aJ z7ZN)72wv#xE^3D1O^0{hfDenDx75Q-zy7snA#L2U#FLOO`}vY%@WESMuLF=*bhSef z6dr!uAP+{DURTYA`NM8a&VXFp=f5S9_|Mz^e{%Li1owSVF%hymZaKSq1L!B96;J8##_l zDL7U9K&S*XmDM$y06$vSO7X*mt7Z)Gz+KhxcR671o2o+tcp*AiY4crn!ISDw8G=7cHeAfs_Q7i1vygxBmoX>d+U1KLl))~o!s7i< zCapQF5L&pu)7S}3PJHCZf`Un%^&8->$}<~M;XS(*b}QgP6WIx|aM-4rdrM#~ReMJO zyzshUktclfzE5m16rVoxo;{={SY=tjrk;hihHxm;8;)Gnw_Fh}9ab=V z23!?7rV znNx9HF#UeA^kZnUTKRShoLAY9*aU@q&7>Qk%i8kBbFf@!M9e98fRm6YhxZIU&L4pF zEkV9TaKvF(-aI(5R`Wm>T+jK*4}Z`~TLDEfE2U%M$2sB`mcaZ@U$+3b zZtac%4>);ZMdl)SchgB(dnoX<=$tv!=81JQfT!N8^k~E0UCk?IK~;*ofC79u>Q>=& z=vO7DMul8M!F3`~;8vFHIM^Xu(l!#F9m)&jgrD^j`v>sCeA8R~6@75ymv=nfFmlnF zjHj?>IM;+n@IbF$?j8F4vE|h3(89U1;35pVJ|tBG@gEVDR6zBj3nth?A>8X6q%xJ`vt zZdN*?^swFJl5tQp?!g#dc=Fn^C{8G(OL;JWKO7)2S z^ANg@625pFz6=?fdJVc9xSo6gZmwF{b{Z~DmzZ+`ZV=nG^$`5z#Pzxu`VuxqyI{%r zCA;!O;kCefq21iC~o-|P<$NPT?b4lhdBD=vg? zj#&w|aAy7K%ck&wXM>;~oItB~nFG^4Y%WoOZOxXiWntyX*J@K?RaR7-I81hXTq6j} z_!Nin!KayC23+vy^Muu3@rM%*bHrmPkKfz3UATJT#`)9qI^pJoo6B3_$hn@Unqgu_ z%ZKZ5nXj_KCAhLG%(n)L$7kkMz>9_3Zy$z~8LLO`g@)f8w0FbtV!UBF@Zpj@`591j zP~zqqsI`ANM-n^{U8fKWKREO(UIM-646N~oVUu1RbB76UYaThlw+CWI*g|;&A$e1{ z_3&2vc`)V4=U7eX)To=M3?@Rx8S`!*6Gf z9qxuR4=Wcvg=^MjZhHtv88)ZihDVzR60gE=C62HB);c~}`IGD7zDmMbkwS*-F!cXQSy*=Ui z!DORFFyP)JX*+oFzUW9Zc=-LS=kwr~iE0-$VUmGhfimRxxt%BjW5c7ICc`w>kIG_j z{N#nB#>0DucD9d%dk4-|azKNDi|aq*4~9hTIpF>l4t9mBzJS>^q9dL_9*O+s7C8D7 zPjMr3o@WzT56j=i>ej;gfn>f)Xk{DJau|N;R@+koSJbox?1E`${FSremDXFIHo#Yc z5@%M!tq#V?i78k+uE`GI z52U0#X?pk;N*Nqae*x3)N6&cz=Vpn%Xo2T~wr_2O*|q~jJ=E6HcvS+UYcW)P{ zxeYB%l8vvz9lNeKo`+4FJ}o!}H3Tudf}#_jd6dG6?IoT0aDJ+h|2C+8r|xA2%!`o> zTmvt9FL=2e_GAS4MMKM8muDexO1Qkc4=m(=@W2^zpYd{VfJq@&u9-sv;jsq#FlC>@ zsW~uAe5RrbRP*F6l!27+Gs2VM7cJX0qR_7L+$VlGvt_Iw4?L2g(LA&l@tWuAeS}2r zE8F6x7d+i~O=dCF zDW8#S2Pe&PZ!v`~rjaw}!Qsz5k~HAh0lC|XFfrk#lr*&Rv|B1k5AQmBK^SsP>k=3P zTVH;5fOYNC(%9ERtX)vv*a#1oI)fJN!3zN4A zG={+&N7nH8(WSP`b%oB4IF`sovh&RpPhW#4L?Y|Y{I@KiZ#1Am%lwWZ-yvkj8tpk4iP}z4M zw#Jr-+=ND{p<0*W`*Gu3Yhejr`icrj4SBr#5R{lcrnU&W$_cgR!d*)~3}nKbmxqMY z;P5nErDV9n@Stfd6d3o;BMj17$1d}QvH2r1T;O%f=0XS9crUTi94gGBT+xHu7c6|J z2~R7n?^c5Pl{tgb@Lp^DNGg=trXf55-nf6AHU=hSYsqoJ+vk$iz7*lQjdi|uALLcd z(SHeL-F;1;z_=@8tXg1>&j#BYP-0rA{YALrGy~?Cck8FZ#;{>JiLk=tomv!p)KDoGM4#U^f3hc>wB@SsLTGhfl-CBn z@zeQY1lPrA_UOREXMB&;U|m7w6*(AMtXVY$&UlhpBnBgm>N3W`rmI($@xZfQrJh5D zkkieqA6jkiR_cLW64n!*!r`md4c>>6r+2hA!6TawvdnGJ|$#)Lg1;Z-YYtmk@xa{1Q zh485Jh8s4Jpep7Y!^)nLC39h7)dyl0e7{t5>`WLhD)4X$jNZ~-A_nyyq=t`!bDwZ( z@xV*l?1m2&;5na9u~$C87q6o>b;DVaM)N!1(wJV+`;hV`;9(=2alLxyC1^0>sq-1Q zH|??baadqec7H!yZRV7{2j&OevE2se?VZM#0W02`UtA5%w2TuIpmCgtS|t3GfBIzr zEG(G4%N=fxSZ(VFdALf4TR}6kJ;x1To<^v*7Sun_FQ@_&AFMt*1Exnc1ykXHRc}Nl zK=Cgf=lG!Ljs1bcVe1lxgA;peTPH*3 zFtq?tSWQWIKNdP{&Q2Hs^+i&+zUJe&QVi1j;O1*B<6gq49XeT$VPsXb=v|njw{pid zSRCmtUI&lO9iMv=-pt-Ab`(wsdzD=RH=hy^%!Bs9V>WJqK`{?_(%|zO%aW2{vrqS@ zXsD~G7#afABn_W>!Bf-Z9T&j|8qXVSV4hi|mNDeDx_LkcO3mgJSA%{1!`8{d=%l;- zGUXGwgyD|zL(nFE-1s7R_o8FS4tP;y$>mLu-+SSdHLy5OFg_8+ zR_?kL31f-|X9U7cj?&BBVOgEjtp)Jn!#*iXSe3asS|3I|6eN~tS1r}dzO@a=F z@e^WT$)Q2Z5cnw7G{p-x)cKuU2pq2xGqXI?HJtKai?@2^gd~Mc^6#Ky|r^ItO(lAnGWk`Zk&`1LniCa zj)C6VXRSh@?~<{;UU247*@T5K?6uHlYuF`my~q%nDBD+RK{N5amz81ujm!6>;gC+% za|tNoytH2s=81C*8wGX4oW^j#qo&J6KJLVI{k(8m4?O0gGNS|TPB^P@5AyeltKEQy z_84effKxZj(K-di3%+R|h1$0_=#)SO!EWuHaGQg$RwksT@oB7u{v}PS%i)Bb9tu(L znoF&8Ae`CulIjjy_}eEefWe(bd=@a>b{3}|yk(U6K^?YLR(Hum^PIAlsqjQgWW5-y z2pU$w4__F%6pnz;oKiBrC z>;)5hOb#!E`)WR|wuUt=;m(H8NVZa03)b(eAE8Xwu<+h=c=f&M{z=fWxH?V%zP9*m zIuZ)DQbdP#pr0_g_3S+yl^k~TC4983CGIiQo;J#$8SY!k!*>ef&=H#~5=H$3Dl=CBA} zHOm}h1Nju1FB-!3&+Q3XusW|wMH!CbU-*1FoJ-X0m;^7OL|D~k zsQ)TRdksB2U#T_$Uba518v##T<3HyIr`E{oxzuzHh7NL z(eMu3e$t`50dkEKqt!xPN>Tc8xPDoGe<_?EKE`b~G`rV*X)9FQvq?S;Qft0su7F09 zRq_Z8h zuq^Ms(h0bAurhH!%wM;-VK?N^xgeMYTeO=QSgJim~tR=7+kc} z4fcC`R5`$}nKK7X;fCnba|n2ib9|f{d^laUN(Me^;pvxz@3{9X3Bfrfo4`wWJ8XeT8|)3sOt=kSug^Sk8NRDp(0&Gv?`j)g4*j|1=Iw+0 zd|F|7Q1a#2otf}ZMoIk|c>5h^Uji)VoGcyz=adcC_oIjVN&}qXmrB75TWI}Wx7-LG z&sM&x1+}(zeo}({t?jtmM4%Z0{^KYDC}3MqQ_t6_PIwSFADVl1f=2K_G` zqI$z5!=cd&Ay*~;CrjAH*YQLTifS%xP={&q4M%4}xks(plVSJl^7skxq@tH>Qrx7db9Dh6SQxWK4S?NrxR&< zu<*)+1?rG*<00Bi7*jv|6%C&E7ds{luY0|Y9R;KJubfMPxe9lOeb|iq&>b%>zJS*% zPOWK$n|7I-+=4`UE>Ar?5xW0kHM}w>cI8p%)Xp`x7^*wieBKU;)r-nDLSx^l0juD& zm;0%)@CfDIonUw>w|}h%6hBg>wEz~&&FM9Ra>L{D2$&=tYoP|C)6@oMz&42^B@%G$ z?dP@v&>*kv3peZt-cs~s6Ml!Qaa+8FU!TqHdjinP~&18`?OxZ2^ z)&|a-SCV1~bw9lonF9^VKbI=N@qTCKOoahl#1#=ZHZjqO51u_A+sOgv)hmVfXJEXa zQ2ga3JaqeRN*kO|dx7sZG*t@QQV+Sr?uwpJOR@ z9Se#cPlQt*^4sylk0rC~hQriPqN<-a;<#%n_VmC#z5M*`Fe6$jz8Magedug}dCn`W zYv823iZ$4X|z87+MYNkY5~p3_eu!sw{y4Bd2lZ!kMM{rWx?U z@VlEPfcLh}Tyh@Hb4p%a0oQHauzf$&(6T+a3pVRqJCg~!HVj=) zh3_W4coYZYAFX{A3VCaueDs90@;980Q2)@0(dMu}PellWMWQO&S6mhH%h*nqhVdnR z)5Kxo6Ehipn00xf+;FI8L!J34jXuuGmFtF!xNgWihUWw;r{9K^YZpwZhbJ8lPO66b z0ae0B;LXaoF@>;A`Tg)5*m-&KKpJ!p8E`sI}$B$S+ z$<*n2dT_R_)wUVUQhni3qMNf zK4^!fy97@+!wQR?I~(8z+JpErFn{#*g=Nr{vUGMa)aboAemmTG>1p?RIFPXaLJ~Z~ zIeljooNu%s!XGMXYZ*Jk$~$)^+CV4HslEDedHJ*&b-4A>qYZM9E7Zi93N;q`%Lu~l z8QT5aFk{%o+QGHB|53=f?lpYm)o0TIIcMdF+=UBAzJ1gHO&h=FpMmP}XS~Yb+HcC! ziebL-qRwsbsoCtJ_3-%TiylcZT8~#U3Z{#3-1md?!}ez`hO=gkG`FVDcNQC_53BCB zR?ddvS@VNsq2RhCswAWox!xQPwOigKkAO3T)RYI-p!`_KJ??=?OPbT4z~TrM^*ivT zzFvDh+}q5RRt@Kf#VH?wUnm7F1<)WqAt4)vB=FMG;EfL^=M!OV^Bk{mnDhTAy6x}F?|AR;U!uRt+mxua!>~O1e z)dh_ba=906agSj(pO(F0(M?a$% zOcl6mZ4a}ER!U9auLr~gm*D5#X7#f$xKJud0v~WiZJ#*70r#|Wo#aHO!X3SD+fDEa_YsNLuwa_btq^i1KY#WR&L7>L zii6UDs~oqXY;uyWH{>>-zv}>*+81iCLe=K9)l2Z3$`-dWWRNDjED6aHDuemqyfQ~I z2b`7_9-)KBU6x5m;9#eb&{im}zlz6|dC1)^9q<{_YZPbqLWcV8<|e2`pD|Yf7ye_W zDulG`vH}m`w{p4*aq!MYoLwk9-dY>#1t*UtXWGMwtwWV2@SM0-uNIW-Iys{R2VPeG z7Kd_uD|CD?opa#mAxIfxDNYNYQ)a3V!Q8uHx<5luSM9P|&O(Mu1~*1wtIt`VZm4BH z64C&hX*r_G;YCxb`}y#%N^Wu*+B(=5eY|0Y3O>UjO8S(;m_IRgzf8cg9l(ySm;Yq=rrgWwH=Il zte3)O0k+$#%YK2k?n^NCK~0&Cxh5#5M^RS+P5M^jp2G61Tb3D6yS!TT9+dr)Nf-h{ z90htjAm4_0x*a?dB5!L1ng=r(g_bjyZ}UL%w*lu^p{klT6%{;s zqpR~z5c(6I_2^aDZCbBB3G*n$DLz0EHl6pa&^hp6a5c2!jW}Hl*(e^ZWx*(V(}D-E zM)dvFa9DhGklqJcg+{%*4o4E^Jxw8F=o*hUq?gYcJPUiPHg8Elt?xfh^1+xFX~XO= zf_nDWeki**#zzPX<$ZcL0@2@W)p$f~~*$)PJ-6FpM?@p>`nL`q~S_vIEyExdP0tK!=Fq4As{I=Kl z;Zs_YP!8C`QgMLp-~O=ukBMN}IU1p#0m!pDrBySKG3n3Q5jaG+(%A{`-3iyNhvKa7 zMoM5+PQFG`G16=hDQd{ov)`mo_(`26Nw}Ic(ENHrIg_EW~3fkjRGd ziWJN^-qFnuT{TXqa=;tyyfw7&R&0+55lo6?&-vkxc=>{wc?NneXNP}+%u-)|c0jv; zI;%RU{o-uzOITm$B=ZEOWt_=Pgq^2f(nLVnJFOnRaL6-k#1Z;$PRp6WgTEFZYr_QB zWMXC5uQ6>U0pA3DYUG8Y+b&$}kh9>KKMk}Aj{dmkhvRgmJGBn)IZ5694rQ!K#y`Lw zRS)qtD7-J?ZZ$mfKz{5w{PZR5WEPzHlp1g!s(GI2y!~(cpTW)-(93*f#-~#U#5V=+bFIZc0jGLN ztl?g!yZ9BDe5uO%9ORIEd|w*cofW7TgzC2X^M~Q1I37lNc)Bd{3^81)%eVUJgZ?VV z`0fnc)-8Gd32weE=?+c<)=e9bTX<4y9po2 zu|9Huz1yx5rn3h#x%?@AjHyhKy9$!!GJ#WOB zTkllXAgf53)FkZpQsf_kS*p%#El@4)3`G@;QhvHw2t{;XO=Q5G7v^2D@aegtieN}Y zo0#ban>RSZtl{e!6^AP@X_Q*?96a{cO+*@UhkMftLXE@hYaH;J&($7UC>kO6j1bz( z)ZN+e!ubpxvzUS`ms6!bLdxVH^li}M?DEVTIHDF_^9-I!thtv7j~t4(h=cZB#G)av zA(fHP15UX%b=$yus&wi4(3gDI<~%ej2o;ur+suVqg7A;>jk?2-_h@e@9Tds#Q6qv~ zrH+(8JaIfK`5jX*S3M}|Bh+~H<9r*mbtfaM{lPm_612mCUm4-5s!nW;a*F( z;04n3C+_gOzlnhj{QCI~l|Ecyt*AW*n-6Q=kcKSdAsmA6frf4e2VB%|^rnS_E1gFO z;V!91=eh^#hN#yb-(j0`GW!SElf%%^0teXyZK~j%#&NPDm=t~LX$E|0fB1YX47rd$ z69lU`J8!!};*n@BD@g4y_eK{6zx}48`fuFrxgZIRLUV8NLFPUeW_Gxc|M4j`4Bh@D z^Vc2EQ|8^d0<+s*8h?ZS0%3Fgu)ghw=R3%dxJOU{xdL+|^I?qV^np}Jc+4O_g?#=c7|zIjLJ4*^KFk$^`6RE7@WAqR+e~JdMq0*74u9vS z#_zZxZe*}gEWo#)#Dm7*HO+;MF37wfZC4K+vyOdw38~Er&S%3UmgdR_u;@*|@i4d` z)pOqqZic-1V+XI_mA5m5b7xF@Em2llmcJV3f4vIRP4s&n2rA9+Kv-PC_ zShz>2=nSd!YwnxFPQ87T+Aw@^_S9L}M`a!KA5_lq?%;u5c}JO9;2zO6GjixzsGPs! zg8UX!wKNZdGnz%mphD|)uP&%e__VSPa!Cd5ynxrPE=WCwB^Ik*_hGDfOj#(r-`23~ z0k!wX^4da^A8Y3NP}OlM`5b&~8{Bmo{v9YJ6oAV)j{hBkMD6cwXkfcoMdDv)tjF}r zzFUE>lTFsf;V-uX9Q{yO&rGceP6?fLDTjrtwaK}VDypY82_`LjPuzjSRIf>W;MIZX z6ArLcd`Ha~S|9ywdlCMLPP{D(DXiXS2|m;i zLs-8<$^}WmA!tArD%T9{7_GHl!#bi-(*iivIOCWKtyCg?BH>%##@l`{L?G=|yrKiJiYl^*7~1v|75;ESJ}IHgpMoAK zW!WF#=*@+UmVYW$CRW0q(+6Xo!e@GPp=mHUDBU{>Zs=A!_`|SfXA>v*`d#HkQ%J6m zB%=kldCB<{;69iAOsC*W(x-de&}-!5>;X9RC9RhjsuQnQ{BT6SCiOXe3cBAg3HksJ zCplZRz`;k%N|lh_Uz__Wyu{A6mkMcagp5YQChMCOesFPxFwPMY6L4O;3U7t=$Z5iH z7Y0Uom{vMJBMeW-=)K{B1iP1`=;6bONh2cYH9^L=aUJt!q3-7-tXd>*7=-l0Q<3js z@IyVF3K($GfGr;e-dy^c47YDEj1l|f6o}fH}7ubsY6=pF2Kf;gM*{cI`7?$PWafJfV>udr|5e093~s` zOJqVG9oCLmxHX+*8U(#b^H!bV{zode%;B*dPdaUQgj6C^5xSO!^NT|747*oHU`E-n z90NQz`>B~2e&fHc@#7lK_R0{HC=48a4}&7K3@c#C@t4DSP}DQ)N;0&u z<{G#IUwjwVyb0Z>yIZb7CZj*{hHy)`y5c+xFd!F{hUD+QKjMeGmX{8&!`D3Op;R#Q z_u%@j-M{O>Z#O;My?pir5q!d@9Jp?a z*T368Fae*vUO6!U8@1AWn_y{Ke@7YoFp$EY10%8)?Gqq3Wp~AGxJw{QP8_osHaF(uSZXY}%m&mtegY$WP*KP)Wzq_3C z5whI9G2Q|*4gJ_EVb=nqP66B&mWfP(9?Z-25isH2$saf2v#6cp_K{0?(PZEvcZCP%BLw|bmp|hFKhhX0LHuXP*$hAcmx*iXB=pUaamJ8 z-$1z?C6*#cOd%+l4u#)l=|w?0+Q)8w@W+&3>~+W|u3TUY|13_wRfn~I7>8wGg?{&v z02E(1Ky(O(7fdix!=FL2$A4Sl_$-b}EJ9(HLlZq67!54|#=4!@l5sg{9qmVMa*khaz&f*mGYaCM}D`*-(i?OOcn zGsQ&~;9S@g?I`R}IK12r^S^#=e*g(Z~Z^SgUmD(iPZXra`3& zou>QViNl@d&;C3xbInDJ2_{^Lnk9h@(}Ed4%y3@g^fbT22o=)30l4^6rnm{dir+CV zgRB&Cze{fA_OgU57LQ!)nHGvf}X21z7AdZ!QfVrpYYu!7s-ScpQXTdrrILke*%Z z*7jBOD|W9)XJO@FR>VhmUWcBt1!dn%D2e#AXQ3mSR$0JB_RxhfjKmO9*|fr zeZva=@Okc_1O3k_k1N6Vsb;#O@YmJ7W^Sk)c2tTUcI|cK6Tte~ZKhQd^z$#ChJ1xV z@4hT`!_1{*{W{pnHdOx{9y^nC{2?4s9F2;Gbcb?Q{NVDNRqg9AZGS_l5tJcgI&cAQ zCM-IihHLq1oxJd}$ng`bQ1);{Fd6)4;WWHyjQ1@ThJ>bJQDH;q5Ih+t^6?#9jyl3u z4(Vf)eV)KK;rCkaLn>QJ<`8)N5V@r*JeeBt%p59pMg7!*cM9lb<>4WAwooB>lqt7` z1J;C>Qqn*&bES*Fjc`2iy3q?zjB>7h1nN}pk+s8|gp{*Y@N9TlKmoiIe5oQC&bm1- zg+qQ#x}#oDj7Uu1266>`ysHcMzxwm$EM!Z_ofn1X)IF?6;BOjV6?#Z{H_wFt<~r%7 ztr+6|E(&e@0$0WUo9lu@KW+Bc!qRW7qR-&tnTxvVux5+UGZG#ml1lJ_qnpbm*C2iK z@os(i(d_H2DpW3KC6a)yW&HOf*0^@P07Pd zxX5fD9|OB$9)u@dpu9Xqg-8-Xw9zHwoEGq@cEx343KyUF&^vsa#_1|q0 z*fJ#Wb>j-=|E-Tr6L5bKVSXQcp;#F47FLM&U3&pVyuH*iA@64Du^9L)TZX_Nwm2G( zIKbP=JEewj`3pW@c(Gn!=^B)qJ@!H$+Hj^jtHM^@ zSRrwEFatwDLUI`a@JJ5uPnmoehP`y+c86 zuw7=H$pQ{e*%WF)^Ez8uIq3LmqFWGdzWHRz4u99FFHu0h5-H#9%UEX*$s(PBAGkyB z4Z&wY<1FvsQdDJTDNK@}I`$Yob{@{Z2Psc*@dv`rixY*8aH97xzcEyMGL(NF{wUjb zObUKqFUdR(=`MX_VS=vJUiXM00Z$z1x;E-o|D(R+Fs4*wsT=xLyf&?cJ~u17i(t^K zsB9X%y?MMa;@|jw0+TniedZTr15f=oHlqWF4pwL?!Sfmv0) z&glMa0(ogwG}WQ(idg1p=pdNBcmifs_?~8ly#f8VN#LSucK5m#&O^vN^EX(KRd}@r z@>Ne{*TO4}_os^>XWkp$H0W`~$UXv|ZE`K}f_`HJ)7FscKG_i+I6e?&q6p{W{St*C zL(Oa_2fV1%MMMpQ8TllCX(B%!`E5T7@0qBke}GD*WF7BdHiN=WDNJ`+;du-XRxxSc zgZn<0`UXJF&gSgvaMVk?%@8JcUS3v%jWYcmDn*zA#zyv0L;NE5&`_Hp&qxa zfprvJhG%0iE=jw&9i|fP`&tQ!?uu;Y!P0|$REaP?XNEHrh9C40b%oy7yv~|I^;^rA zHQ=1in7Is;=#_WkgRIl6H(8)ZnB6T>D4Z#9cjF@DvbpyS>hCfn^uTCNspMK%=HHi6 z1dEBrQ&OSjW2@vlu;iRcf+q}`ZM$dnZ@n9@v zw^b2;=In{4AwI`^djQT++$3#)3MBcri=o|!3Q-0W@YfBz3k^T@Z+pSoZ*N_#;jys8 zbK20TY{66>@|*Au2tsq`K@~RGGPY7d_HTQF@A021sJp^E5+)!`)@OoVcx3R^^;&2` z|7WlehN+dENrASu!`Wf5^{OM8JM`dib})yciQ^p_u%rB~fD9yaq>bc-&kH-JnP8at zwjwdq54fMcs*K}VP22bkiHhhibU^#`&CE(TS#o|o7fODiRDJ;Y>7)~aVTk0%2`5OR zJRxihJ$a1%&cU<}#rNV+@^})>QAo#Qpi2kmUf)Uma~5%;``G7sSZE;3ISg4<^UdDF z2jS6~rSLPw*U?9?sy>q?2F@jSX!}Cp5SvIlc=3i~gDxbxIKHU}>8Tp{g`rIP{;P+e z!eVO-1sq>mso7LQ{JR%AKMB_wLYe#EH}_SAI(R*?&Atd~Fw@4TK(*<&6=9J1=a&&T z=p${&iDj;T;A@@2uWsK?i0f6(;-F6il`&937STsIMw`c zE2N{7UwQ?i8)=;NRYPZL)FyuAHh$N|bT|0kmln{UvlD#OQX+FGYzWGtcn zVJIVh=qeR-9C~cAt$_O0^uF~J6mM9v>4$QQWj6J&sOP)YGx(ozuz4zs3k)|7hu(Z! zy6!Mbz5k*)Y#L-as{xbBTO_4n&&W6a6Oc#J_7Ee~tkR_>gaq2pe=W(Q9$#{v8G!>q z8T~ELY*VzR9G+7Y%XtiyE1Y!$jWP%V)3-E}7Il0&@lr`L@96Hp*k=aATi&XBPDGJLDb%53(q* z`oi$G_G(*b7gBRo2ilxs`YR9ln+G!lp{dC3>4UJ)EMkZhR-E;BUY9}q>HI+U6~3HJ z&g_6o5%pq~Ffv1dY)&4W;k(m*qoY*&iYaP~6_{Suk87 zx^~m6R#{0^_(C$H&+ zR3}1D)WXUz%lM~oe`elJB79+{WgY^h-*OfBp0;<0)ujIaSsN`Of%I)kpfx zf{z19n6@QQcjiwie}~~ReD1xFP@v~g4OF-J*j@mCe^UIB01I#KKXeN&iO4EBL8J99 zTSFM}?Co6@NTbD6bP5Kxb+mIpSE}!`6wsft_dhG;&r+kZYMHnzk$Ln zOm=zD>Z-TjeaLrCIyMk8dYe9SfUiGI7GHt6X6-df&~=->O$Zi0cr(BTm$}}J?SoDC zgubu;hdkV~Gxr6$_wp>aL)TlSs}=A<|D&~R7+bou8Vh^qGnRef!>@(&w(t%W+tg)v zIBf2-99)+ZY1Ox%sb-+|Jo)VwS>YUljNFEl_`GlH0+_x?B;>`7L+gOq5aNK%pXzI%Pj=g z=b&+Qr0Nj7{LY=L0TT6BZ$E=S2P}J1U`5rnoZGO+t;^d5wlBw>H-V=($_}1`u}fmJ zV$ea0;T0#;64ef-g73qY&TpN<_asiw(oe#OGm69Aa5`{1;|<(ep)t>cNN#W(rd5El0bo5m)BQBke6f*9pZlnPdDuoG7!*pAToMW(Q zCsmaW##$K7>Pzw8f z60g(JaCZjMj&(Q>zy{;gi8^?vuR*5}eil?|N`!J(kDj^(k6$)<=m_a{SI7*Yx7rt% zv+%?56T?EVAfjEC4ceNtJ|=}x@mwTpf;di=0qZfi_W5g53p_Tc%3TI?=Ewpv;XKXJ zk9Xk>krZK1SfF4RZVu^%;zrdW_v`^-NvLJI5_|+Q1oQOK!0%^XaqbA9Z@!)B_#NI= z`S!X8s=EXbR>L0~Zs+shZhgbOc=(~oe$XHOT6SQ$20x3uH_(AUZr)6hgXI;sKk&i$ zPjd&DAVFWg1_AVpt_xq_N50!VS@!`d(QIxsz*{BEyw73c$9qO8aP@{oR4CkM7xm@_ zJpY1b)({Fx&N8S#?qxPP5t!PPYs(JzTeP}&;g3xNXAijeb0@+Kb~~+SU4RstO0UHsP54{~7d%Hg`-KYfRm-k! z@?sqzd4BI3OpaKl>4F47=h-S@i~a7gC(uZWL?{*>AF&emfhlrqGS-ko?}WS-JVFzt zC=D5|s3`G36V5A&bkN?QR&MtM?$=dc>1i1FG4{VcX!3houm(QUtU8(xhiiLS??d&| zXQ%_9^UJ?~>|pT$mgURPRV!>v79PEFzKs{2WVR}2fZKun5C0xVJ_t$EB-Mzj#d@DVX4LRWsbjUc#cCfVokZ(ODQTn`!_Z@8lXnUccH1t95mrX(}G zFGE8^2#;Rs9be$V@$$@;et^?YbweBAWEa!rXV5oTfHN7=mL$&Kg5+Ad<&LoJyq&*3 ztcdGYQiA_6M3S9^d2dtOS)fbApC}@@Y4}Qg=@{b5u?C7^*!YpFxe<0*4hBDmoUcYt zr$F{PiM0@T#Azze2_~1!7#YA(Us;;7P^)sf_9WCZoOEP`8X973#4t7V!~3P9aG}$A z7(Vx4W^IDv`^)N{!}$Dcs}#s3GPN%hDkb|CIKeBOmgfv$b=|{hCD?maAoL{MNTE8+ z3Pa4~UK7EwtR~H+BdGH(rhoqkZM5ItY=l~m6sev=Umm)QWT-SCA`k+JQ=U{hLUyZb z%KC8XXi&EztZkdQA_(`+W`AaeWFf_tgiyqtdU}By>uvjL>^?wk!P=R(@NYk@Z4s25 zFPlh&S1g`h4Ti5ytbTBS#exx?4a1>5E`0v=sY28o zENHFU9)JlYG1hhPtIqT80yvS$A^8Aq-1_n`0A622g$T2{v~4fLIYyG!GccoMAMXiB z_rNfm9;O%kp4jC?zxZuMb{cA)6ie=f+m|W@Ieo9t>(XTkwITzM^NW z;l*LyC{36@*gPx+9ZVyR9ffn&sW)ig&5t`3TZged_vxQ+kiM=!sS`HPZG~09&gg`e zEEvXpi!2Jt*43zZLbc0kA!hJLCs*xxn8a~@Qw#=*y*+st{`nJTMGmbU(~{RYaJ_E) zX&ZwA>&3sCq2O#WUkSV-|L00N+@4Mi34@m1x1Tw~$?vs;hOpzH%I;Zs&4i6h2$E;W zo@IrMpaM*!9zxBoc8eU zJsvR~NWiYGA`3eg+VoC9>9u>-^e~a_iR&&q)=Tw?1E!#S(#!B3$kS37TlLRRYY9(a zWOZ0-419RiHp3e}Eq?IO0&YK~d8iHt*#^_ZVL0PV5+^k9RgI^B_hr`aZm{8esm%v} zhSo7cUM(>Ccb$C+l)s&BoDSbHyi*T@R%uew&d{1{=eQxHy+C^4EG*Q%x+@6%X$U8o zVW;D2D*+rg6?i^>@ZbC{xi<)%DnniBpxfUptpcc%KymUu)U&T8_lLEPH4`?lH)db8 z796Nfk3S75r~Pb?!Z*8lr)l7c#Y0qEteD?bI8D&&T0v|C~-c{6jjqT?QX(@nk%NY>!^b-hsK3w?DhUVCrl?BREG+&Z-O# zwN{j#gjY@%tFypC_2mfy*uU&`bDjy;kNzC>Ae4V%ky#6mKWGxlhyJ}zwQ(@ug82ns zXtSC&WCd@OaF}Vpe*f8N3D~K)>&OL@SM=5>prhxu`vxQKzs89lWAIQjqenAzaE@Jj z0iQnCa!Q5tE(S9pa5T5T%n>TcDSXg_-n^XZ@~}!rrH&WMy(kqv@K1ibN4pF-4`r4# zQ!q*<*QXn*z7+cY8X6GpU3?6;Ihe~Mq47`mgC4NTi#6aXREsD6stTq56PFi(t(wmt z9)u@)um2)~(fKZViwAJOzpE}Df~T4AZNMLe=-3`@-~eBF-b~Pe9g5hyjL`L3nA#s& z^dXsV?53gYSF!LO=*qg5T?yZwUax%&^+hE=MncQA`ek=WYa2~+72XZXU{!^G1XuZm zVbaw*GORF|!14kioFNI;pWpv4|9`U_fWymSjy15d)X5_kzD-E+jfL#D_XT;w4d>Ul z%%PiFQOJ4Nxby9nD5Ul>4q}HsMJ#?KQ0y+V=MoL#&R>0}5Af8lQLB2`d_CKs0A5fk zQjdqzj+3a-X8 zH>Xk~F36rZd58kul+ydWMv1W+S;h!F{&L5l3BIuuWPAp(KthYlVff0W!d9e; zj4k~2=H`kf=zGCf#q-A6~Z;NVS0?!EDl+&~NbECkbe;Q{lx4Sr;u2ki*;<&1b9RSjRo#qx=c> z=1+_?z=FS#&V_J*&5h^*bXrJ$;0N!U{XSw1&1@gNya-p9gU^UV^VzIs4%nf%r@0TR z&!-P9lOewLdKrC$JKyhpu7_8dW=sp9lD7YsIM{ec%ftt6<(m&%z}>7DmoLCsi!&Xf z@Z+a_O6>64*8bPTkh4gae{mo3*u=;5L6{bkLRAaN)02F1A@#4msTf$7df}26l&~kL zGKEq48=PuzNatvnFyz;Wn`MF7qS6-#pw0$Y;VdcELj#q``(Slrj&&8xe4$wX7&83F z&Ke1YA9cde zRFm0qC}I~U{19p%An*)>yLXE!ouESP{a<=eEd7QdKS~boy3hKH&U}A#}H>CxLpnC|r{5&DW%Hju@^!R!Mtf1hT~F9P&&Lj^e#Fna9FlXiHSk}|6l zQfktrr@^`;t%MNhGtd&{097__-ntA0rz}0Cp;Stk-4W=%JFZU&Xa0DouJ3UvWW*|p zjr=o1^>8CxWxh>T2(uPViQZC}kU4X#quMpQW9LxwnY@PQgdq_YDt1p4SEv zgi!vJKKh=QH_}s%i@&)PdQBEN2jQs$U;3(HGUL9GCy+$P zS|kek)@+Zt!Cm>LJ4WzsXOoB$!3TeYP+y>&i7vI3bk#M;uE3;*IBkkJr8(A%#pNy`_u4 zxD*n(HWUUSmxOv(H7pYw)_(#u$kWCn;X-ztog0i=uU<5S{L}0%itxST%QaqTr3(_&~f?>&?VLsNY(1i)K5CAHSjknE7~MM#?S zCR6O+cGehbcG#lx;3g65SMHja+r~T^kyh`6oFro(XW zvsp=Bfkh`sQ)J;^i;<0EaI|tlgBr@PAI{s@;!t#h+XX z*-FRU2jGO(iTCpz)sr_@!HTf$t*|;>h!Z*=Y+u&d%FAJCw?Z`r8UG09;2g!uzseCtQsD(aG8G$ zt%4OhBH*?E6bqfiQ*?)jG%n+l{m$J`#(R|=Ywf&E_|dAcn-Zy#D*Tg-yU}Ze4!Z~^)U;0o1?l_4QfXvT^E8+ z&t+0GL09GeqTMAfg%~H%^Aqq}Y2Ei$s7>r0_yWGwFlJ7I^!O|J{*X61U%?8_T%{d2 z4^K^HTZzC~I?HutxYBbm=+7b^M{h;>9eOn7JZOg%L-gz=ut%aWD;Z`DIUNgxl#fmF ztl`QXujA@a*rz(@6fCq7;AVx263yv<7q}D#zeX`m!LwTaQ62Dxdon>O{7>eydkXYS z)R+o{bgr|yHgK}$d80b)wtFZf3SXGNPGN=CNxKAl^O#p#_I6Y7y*p872Nc|SCsYc* zdX?Nufz_ie%YiVHnf#&+v|)D3SBHyjq=cf7j;qOl6&8~f7XO_?+;x6KGzFVgf9Q6= z9Y6it63Fy-aw8dTR;MZiLhqcLQP$8sFL>|*eE+h9{S;IyrLtgwgQ>AOe`YbhB0W6` zeLn0z-VRkLsI6bZ+n-NmB|%!H;1PfLmS^jL6?DP>`#2A`c}xR^;VCZ75+=BzYdf(! zgE&@}M?V4G`Do=^;kUd;4#n^^n?XVXyzC}b;|qy16(-E#v1pflYA{NmmG>m1y{>ki z0p2;iY_~JbrO*~ubNefNCRh6CJtWoXsVss;B@Dgs(EWVSls6QmIIwdS)_v`uRE7)H z&)E3kz36gYIvA)h`QPRg=3ldv{1}X*d8O6}jY$kN3t&Skw@xffuA#r;0bd&P>Knmv z8=ET%aHp+R=Q!;4x~xSF#f*v0uYbpOn_-1vxDhreQTNZMoBTPj^Q|05B-E^WM&kl6 zhA8~jgUOV?W@R9O;->*_Xu$ZTh77v6ljkf=aw)KC7~CCz+;LrwRnS=An&u;zM8hi- z2D5o+sINmUOP1M7(6LmtQ3AG-q^BK%qURW0iJ;H*QkB^W?7!;C+6_mjug;ahxNG*6 zsqm^DyJ%C?gE+6%Q_v`7$OyO4TkNe8-llQI~KO`RG*`~q;~77exSX~^7Q&I94+`tZRQ4{cdEEA@Hf2y}|SmOu^{J}`?d ze?@%Xp6MBYTa%*>Rgn4aI`JcT`GY|EZRq~yj)((PJhtzh7Q9WAuO|+Pl^v$o;GK_J z?ga2dk^bJ)7cPa$7q{+qz#n~G^d(T6@Jvb)Ocd-qBcj&dnj=qtRKgmgCriQAxdlayXDoIYWb`vAr#lx*CD z2evsBOd+3QVdPnuVtQqO7ao>B!nPm!_tBVbj37Ud9LyeuZ=26f)G*~&!}yFv6roPBjLV^6nwN8aj-7@wJywleZS*0?2oJYd>F<{kt`F#^oRDpXFnoe6fcr@ z!<$Ai3}x``J@rE=P)7E|(E#{Fn~T>HmR3s$s6lQA2SGvjRJ~e&9+GJB@oj!UJ!M&b zY!othnH+u#PkVDS=l&b(5mQG(!@qdpRKG8TMKtcxtpD ze`W~R@yq47PG}}}(7OZ3gtzIg>4!zkAhhBpAqxM?j|D5kV$OZ)@ zbte7}pq_ZSS}+M4m7hAb!oL&#BF|x)&-Jxp*^~0ukVN~Ibp|Y+@;Pt|MwLg`*}#j>s;*st+0_K} z!cex);xz;88XYso+fz%aDHp{NF@|mk9n8@sddzuZ$Ul^?id>= zModd|0e%o>@)UwK0bqC@i^kSm-UhoS%`L1EZ2f_aY#f;<&XF zbiSY4stuidL--|N(TrCl8|-flnf=>^$5ZDiPr{(bQ-638xZ&3+!f&KdFX4y4d?(`5)*p{8a7^cySdtd?OZA^2VwH(lhc(jHBON2Aq?kL zym$+eBwY8mfv+2i3eUr{S%e=?LXE>tga@Dx|1#fZJD0-svfGzNU~|5RXB`yUTuaV| zYB8;q;jrAe{G$V8x%BFXCS)}1pcR7}Hk-UG(Ar8uVYdzSuVR$mSNMA6$F(LXY3%Ns z4-dQVi;RLa$BUDlVXmda6CD`ABJ*4lUJ>JZ#STky4p#5AqW?5HQ9lV|l+HJ_K&^{G z4MmXCwypjid>DA5#vNX}om!y}xokOK$Uy4@Rk@sSz}q&B7*dCwh?!}@eZl)Xpabe_ ze|LNV^On9EK7gkemzBNY3mJMLW0>2n$|Mgf@7()!1cqBIeBKA2s~XkMH*+bp^NeM7 zL!TuV|5B(Q&1#SY3D^2h`9cQDEDBS|-yHN+5q@KEE8~HK-@L*o;G@H_dQ0z7A5^|R z+6M(5ZLE~TBX1Soq(D>3>=1wGZix%+j4f97T(8@UR5+59i9o?gbyIZ2qSDWy> zVd(~gFoTDQs1jamdGs_L?lY9p3W64GGwYV{=f2zoRcJuq{vSU)lB3;E1LwvSZmczO zDJbVEkPpGOua~kaA#ISWLH;XT)G9)1{kqbrLB{%#*V zxb_x#$#W-e5Z*UAzONFF-LUjYhjVGq=L4a751ElAO#J22q5=)6=0y1*->P#4HB3q- zC0VV<>t20oKLF!qO}Z+eyz^nXe(PK`hZBWW=agYcO?mbSs76}%ixM`v z4_#WWL%p&2E2j@0^cUJHhm0G}YAKN9LVcniyy<*s!W911z9XmvkB{$p@W3GQ@M?0H zTy>COu@=Yo?Tu0oJmX>?S_-Ex^E4*HJ2#jA-h|J4-kmo2w_UZsO&->DraV0Yo3xU? zkiw9q zD#~yFF!bh5hw0f}Dl!HeKx}05-VuzFq ztrz~jL0_%>(&!t!B>UvrJ4pNfu}3~!YAXtigznM};ZBf`bRzl^Okv$0F9y$BsXSnY z?*{HX*r`I?HyDZ^gOcMXW9y-uVSGe36e`^d4ui3uZhBvX)3v{^tHTb?iT+e%4omQbY1`eAOwec*$V8GjSlB+9KR4||VUa~y%P zUN7fKU@X(VikS+W*EQ>)b~wG&srnplu*gxz!4>(+9yfT^R3=^z)?2h(l7h)nCi~f; zUEfaIpI7ML*s?;$;ZGthnI>3qbKhDn^mJ&>iGXFzu~)9cRX?+Rny~n$RJjOrDmrM+ z0G|^PQ*M?cZ#n)b8HO*1m-MTl`+qCD58>+@+Yf``t|x_@6|9pwGNuB9bCquL!umpo z161&rL|Wle8SY=HK80R*HIsIz6oy>UvrB{-TG?CPkniSZm=VmY(>@>zx z2M-a$QnYn}-yi_6lh!1XxlkTI2 zlAhxZ%P(=AEE~Fdp~qsrP#Lr$Oo&N@gZIMby`e(5zlss$9=Mr#2Ci89{yYqi^542Z z2*v4R9!|bMUL((3eh&x#mdO^t%M;y^kudi0@{l7W5Mbccf}BogY)?U}D-J~rP-!S* z`DZcuo8bZBVfaFW*7*(OKc@981F~OuFcS#3pO11`KuI}XgR@X`?q&=REPdGZmJEJ6 ze|&xJIr8FI2zTfI6y0|`Reu-sOVj3~2Yi$q36Hc>{(%&e@C z85OBiMpl&7ppa2!*?ynjU+>q&b-!cXdi!ys$8Gsyww9^_%x zfm#nbsKw#Mw$OP77=2gq`=5N&)A@zgPH6h!aaB1S<#5Wl0|j0Ph4{e0k{=gNLx=tx zZ3UCMF5eZ9c+&lV*JQ8|1PteAWmh^W773q204XPGOKgc16hn4(@olQwOdJ zMSK;5Q_Y6O3~W^g_61KSZ; z#(1xL52W<6xwZZP=U*e1u2hwBEct(s6)e!y1* zp1qMJxE&61UTB}n!}&~#`FwyOV}H0yVc^=6hB)ZMdD-?7>=KcsGKR~fzxQRK@o0qt zCp2*L>{-r5-ObzYIshwue^EV$^L^P#8PKJ|TsRPh|Jzk#0}JEc98-gY$CXY2SQH{+ zu>~5}ejXmnLA+3eIlPBU$E2qU;EOB$t~a51c+j*fl(rUfHiVC6-;GK^K~YC*X2`I# z<@0@3*hlLNK5@Aig3Mdh7`?BTA>;9HtdNPlr$7=B~7!#!Bsztm~F zGnt9LviIPxR`?=6>BwU^RJIs-3q}nc|8x-w?s>#_0`5A!>UK(y|{M(P=$&1`h68cU6L6aw2&oNR%G$-^{>$a{EgR z-=Up$oN^R1e-x^Tw&j6b+{NgfjWE<>G?KwM6TH2X8!;2%dg_|X~-MQ50yT` zvhxmlmGH|${?keD&D+m*KCrPo+{Fw^nM+-ghtBz3KD%MtEjPc_R6K8$+J6vw4#fLD zhfX|uyfff@N!rEh&>@J!(HimvMq8;u|CFVZe6a0>r3N+h9C$AKBL&C#!Y23za#Y(h zJ%H~|l&*$B>CCy23-D2lM6(tQ+h+4v1PV~a+@^Nagss93ho#u9~hv^HaSu^3Q?Xh)%uq39& z+6I<8^idy$e9<$xe9&NIU5y$>Sy6r;xr03Wd&U1X^t(F2ng=^q+Y3WsplKmt4?kZE z=+=Ou)@tYW!MDFQ=jb2>{gdnCiRcSUPSd}K(Qp68=fkV2kz7&GU^`QW6Vzvk<<*5P zniN@LkmK$J@(vjE=T+)d0`@5?Z0v1t(#s;Y7)D)7qKSpUI!#yHpwQci2?N+LOl=_v zGozW_?}TinEHX3k$VbZb_dDRcz~YuNxMcLvB@VtXf7syx%jMh;oPgBrqVWgdmh=68 znBmr6S9Ry({;!8@dEHROD|4+J?l2>tNQB!*;_|#;IQ7!sQ?Tu!k%kNuevy&D21gEk z{jzu)b!3HExEDTJlXtIzTB4^alAxD|%|CDG(_kZK3e#wfeGWqjNreiI|GzKB`sd#* z#LM{CeV?J8afW3Lyl7>Za1VMc&b_$;@6ks7HG})FF$v1U0R5XrT(HGv*?&0}`5@mc zw-3fJ7c@VGx1*bO$v3>O>`ohtA<^fo`A;MV) z1-6IoN&VlI4sL(A_>_U$0uIP2a4A6U^QT$&z%_9PhSg}C&$SCQ1F%5Lbh#E%`W~K2 zfm=7|hWsFP)4LXPC{yoSa|8~5+MTl->TeWAuSB5^@Je|0Lq~}WlUkU`yeXFgvtAf- z`N7BJ+y73(Z2}G5@^Inl*J3VM*8V$mITGi8a@wK~<{uo8dkIslt_Buv?_f<`i@-v)XY8 zCh>l&V1YN!8(7STVcy*eHoM>_k%g=$@Ik29(Re6#@%T3n*!;;hzz8N6*s@E*@4beP zcfqeZqUtlD$omVkpW5N?#s`ii&?iT0ITk($`4r{`t52O|Hh@1VpQTDbXFn!>1{hj& z^3hZXUT+M$A=V15|E3oe!la2jv1q7kl=8?4)^Pgs>p)IPtuzsscY~5;8%(Br7(O11 z^^hXB@)o8ZsY2t4_~<0}g~4CCmyg*)h2*M|V^HVe2!|j%es1k54GeujF+6eu`8Rh) z?G-#j-I|jP-J0VmZ@^n)>St}>U#;OP0uBs1G4jI8abN5~sM?b*+oHLz>MChRT@ zs_bd>hPe#~*Nx%Dd4CxhxLfhDGb`+kXudyx9r5$Fqq7Sdk2O#}fk8bfl5y~Mpryq{ zSlPQh)DQ|ZWf$#-yOj957+|qT%;MBFjGNy#_Ez|sn@6?~=58K683ierPPjQjdKH~; zEx6z#maz}c+jN`lJ7MKl8B%wyFfYs(=@&f=d(r|N2YMR8M&FxORvUq&!aNECrcLiVSzcCl>Mt ze*59|9ra@7HfU+Gb+!<`4%BIjf*xg;9y-GK-r(z6(D;j=@jf_OWhS!a|L+^{tp4!D zc_qASeg#D@ncvHXrGs5|L2&rFw1hRBqVZW!hRgS}pYlMNs}%ujSCDU`ij)T6XjRnO zQ&{%L;ORYR#=m^Y2htFc;wJF>!Oh`AkaODnE(`p4I!$}d2ld6_)p7^Cv8Cf-34F)b zcrq5s7bdT}K#EwCJY9I~>5`5pl-?8gdn=sInz}XSjXqjIS+WUU>Gf*Oh4-xS`2kSm z-E9h6cxv_XT>`3(tB8{!$JO62)-NL;Jq$7&gnLYvhiYN+6=SDln7(pn!3P?&)CQQq zuFtQiWS|jKSr`kPvAwZ%&I|EccQ~R03e$JemOutYgP<5#pi;8p3r%2&=+!}}LB8}2|wlVvV1*t-}5zJP*cf!&0sm{|L zm@mQF+7@U=)X^2d(wvMF5pY52T&g|PT;D#d4&S%m5#onwIh2l+aM<23|LaB6WxDHQ z&!LvrZT>WvpAvl54^lXq-7$kMZHz6lkg@8)1}ogc#49!Lj(X7QXVnS4d#i4i!2LWQ zt7BkoY}YquD9`nTP6xIWc!~?dSZP+nEifxJ$^C~L`m|5WF^y2FOQ|3e{_?YZeGNL- zUl=?CKgQ_K9f3zbvTWgkU4gY6|6CCtL8|+D;D^_b4wXYMxfzu>Sk}y;?GCdX$;b7f z4Kz zM>hPbfZAdbmGRKI%jeofXylrC%m6Y^JfIhcO|sE#+hB;Qe(ab7`lh8Ry*JQL+i6=4 z{Kog@RUj<#T=%zx3qmXh6yXPnEu&oU_~qWa|1KauPX=rDz}#z$i%(!Sw|C}kc)z?> z+YOdV56=$X@Tsnh8xM8y;VUO4}whEa2dg znL#eO*s|LF?-cqfk9Q*rA*+rT>A$H;oer^-AvGAcT@Cl zJJivu%BwAKXe7=d9~vn%bcexJkN1k_;MC>f994Kw#$_uReov=$UbDq|$nb6HgHQP1 ziB!V{?%B{pIN-DJ)dTwQbsaW@z4TEr;&3aO`};PyM?d1gmftT+cWTinp3{~|6fm>oo0tifhVLEtZ~0+>E^{w7(15v_%ZZSik*mtU1O&N z9AVYR1q%%*KIa<84~hK^uPNY$_vGS`71nR*=2zuzs$SX~GhDdG{;nw@|J4reQ7=P2TSu1m#*Eg;+wLL#wF@aJF8sj1!if z5q`aR7WJ|lCcg-HR5ALp@IL;EtesW8#ToVm>aS5cR!r0c|ExuE>B(fGH!u!XCD6W21aon ze4Y*WOqu6jhqwJtC!B#Z75n|=p=wyaH9L$NI-@>sj{V`IO2H1u-TQ;47M$8aLlKALc_@MYNYdZYpdZW`9Ubh^HHi6ErTzlc5jGw#IaA%+BVL7IAbN~6ADYFnLpaVCkb8qmEoewx$xc4 z>)YF*zecDJcI(pJkiCi~qy$R79qNvTo*@x?9bo?ejjKAetvgZ03u&1=*Vm4tzihTW z+6O~>1wt#KDPPT-I4F_EP2&bB51b%$U|Xk|pAd|4lzKu14~%^t`(pV2>lP>W)Injr zG`+jfVQ01fCFpZ8`;j3uIBVH027kVqSfzu9;x+kx7{DqojRrWx$Q=ycn^=U$6MyXY>GOoAjr&EZh00)iThc|!%gj7CT#G+smlhl zx|k>VeDyZ?b*xY>ANJ{86%B=VO?Y-`)jTuA*mu%Lsu3VGbv0Y$4# z9u~o1zP88+s8hk@bPjr&3uqDWHx;b_H!MvpU;n3#_+pmo>4x3Leg!3v^JM(hXy~%! zV{idB9u(tIgWKl|f03ZPz)Jaw7V1?cTR<<|mbjt%1janBppAufbBEtL!eE1luw$^d zBu{NG%%i!zv8IXZI-*(q8D1t@Tq~d{H81d!li3J*tDU-8>tEbxRXlzI0dxjmzWqhhTq3A z6jTirTg*5T;C+eA0ylVAyj(#C&Q-)U3&Q7>3Z|4Wl)LQvpgQh1f0}iyfiJq(|0KYz z6g`*RA*GJbq7J-s|A&_lEc?wpM+sE~d0mIpP+z2f|Ez&_6yawQVSC_2hdVrz$*HCb z7xkFRgf*HGKvwljQs`=_f0 zbfL7ny%)A!dQ`fmguip`sr>K>O0Y#wJb{17!Zfjv;>XQBj!??*sgyc&HY`>n!^AF! zQ!9$NuBJ)b9(a^X*1Z(Q)@b@hL$i3^AbS{`*bt@)=Zf}4al?rB#?k*2kXJtIM0LT+ zrY+&ckSRDaC<2mKyL|27OYRYOW!T^I)Q$`4vYHw%9Km_Lsa0=>&p!WF5)~O>fBt*A^4br)udNL!A{z;o3f53-2xUI(h0kpcutQ!Q2e;yDx1HXLX zT$P1g9?t|?K5iG!w{U%@1Cb4%o)Xz{72f$=(qjrQMy{qEfFi~_Y#CsN z$gsfpVa)$Rz|Tf_ICn579dZg$T3>s_IxKYWVmz^rMUlQ~1G_bn@3Ya&r-+~Nd4W^E8LjOjR8oU)i)6E0B ztTh>y_T#?wxX!UI7)-g-zX+xzCp->=Ss9aAf&sYlrn*dcN*ixocj&3fy% z2wrcQ3if*kC78?Xvf)*|G@SsL7 zJmfCSKHP5M0UM$oN$EmPLltU4=;N8ty|EARJ)E2M8D5XM`*g+^zye)+R z{)jHq852ZZtzlbkfCu)<=A}U6a%Mv>*ng(*uRe_4PLZ|`9xUFaLJ89(hrad;AU_74 zzg7YJV)A##Le*pW6&GN9;yFE4c*k+z`)fj-`fB0?<1Kegjt}w=j^UI%q)&QH^o+_k3qU1=;CD^XueLx?o*tir3 zK@$cZUJ5uU5s}ch7xx=Vi)qTCQE<$qoA5)zzHjGYPrJX43X~9xdcq0Qf=+PG@uCm! z`sm*RKW$+e%7v2K=@hTQ%C@R|rvE=yP-@E5h`wi1aa7YYCUbBWf;>_#h zVdT`vA~WDqf&*hBW=mMVcEdC{QMr|_jkunw88*o3x_;7 zD9RIX4aWK>#hAhqq_=5O@E1i%{x--Tu=)Fr;$Ya`)}DC=rjxYeW#IhFD?yAfHqX*+ zj1%!*y<*k?qkrhBCPT+P#``>Bur|wfU6^zxe2O1hA1!NHH8-=6!-f_eWQ7*___ z(ggNL!B;c^?`&b?ws30&`2OCpzbr8Bes$y&Gv4(bEUyswu|9jFw|XhK@Tz)>RJlI2`Z|l^_@8Hb53Ht(C9@->|?0C ze*8}uwA$IOZw0l6H}Vg|8oO`QyP#drC5v%Jy#6O#Q`!LKv>sC@!{%1wQy%bq?&EB2 z`0QED@4YaX+DmSQ0dZ*m?Rpm!?_Pgh052VGr3;3Ofx222&@SCS>>#}8ccyVW{6PDA zbz}$Dy@b@kXYkk;K8Hm3a!XT+3p|y`)uIkdy?Bhl3JOBFBG)7Cs8wWcxyo`v;r{C9B}EW9%BSuxa&?xjj57GKrU{ zktgd01K6Q{=GCa*+i-vWP1~I&n9tFckpas>{qwxxm$lFN2LJ2yvrq_HJz;5$B889 zX?R)04Jy`_?9_mH68GkLp!wbXJ&Rj#-|nYpMH@7sIUkn?7l&zFuEJYxC)AB$d!qhs z323eMcbW!HU(l-`q(wfMF^sK%`R^$#ZbFG+OTlx{Y@gG#0_+LnD`$ZQzr(#JX%Kh1 zDTiOd{^-+xQ{d6`H%~mFzUvnk9hmzoM}QB`ZnE_+QzO3955;ssrm{_?d?>TeXDSfR zO{d&5gR^r^N2Opt|JW}&XyUf>)>kUzZ2AebioHrEWkJbZ#r3LapHNT-7$DxKmlHp#^9r(;~MFv#vC6*tW1VjXSX zAQSynB5!=5C41t1VR)70aoczEn1C8{f`bXCA*eE4E9`01!uuz3h z$12%oR>{P?Ej;2Npol?gO2GGC#AVV%dt%jd|%3&*6>Hjp?jc4H^hvflaN z`7D`Wy?peIGyLZG`YRV4=eo1_c7{wiT32lGh8diEECR5R=gaP{KV*VQcbq2>zVRox z_d~AeQMSR~WJ2>)KYbL8c(P~pFysg-82vd-Cd5<2+7ck;U`dH8{6G?l`7=c(T9kjC zPlwVR%ksMLaIo>V!qCS@B_G;o*Au_SOfZ6jBd?uUZzzgRSt<2j7 zv7YX`Y5KwB1Ft0a!PCNLSw0SsiKAx}=K^6szICevl$?B@)!$DhZmjuuhQqWPC*?yh zcO{GVdmov2na=Yr26A2<3OxclE`3)X`%ESn&x*_^z$KQ-Yyv*H6rw-<3D;|ScRm@4 zrc5VlKq-!J>G@tVaWCKQ{e2iJ9(+a*?)LvVvD8B*d@n|M<-w*gk7Xms|18ya<0F|U zdq=le029V5olW7dzj;H{u;;@koziYHaYa|R{0yAaq~O>F?e71*T-ik?CdK!6*ueaj z2ZD_7xrw!RZ6}$~$Tx4ahqf7awy{7dCMLc54#e+ZYos$YAAHxy0cR&@{=I4^6Qv`8 zLKk60mZA|i7+zmF}5pNn^<1K|lNwXfpvR?q&{&&^oh3T71{&_JpFt`wxG zvhyB%k3120?05vce&w9tA?SalVe#ubQ+@RFRW&d6J=>+xH3x1hn; z?uH{!g=zTGXcNwZ_{kR!6O07flpvXO-}BcS%wLc(%N=NCs#!un1wSv1sn=w}?9Jfk zyYM|rpuHN*A}s&>euX#_^0|}(JF=SRjzOK9X0Ef1IQ~oCku-RQoBxy+WV>S3F#i(y zx};~{eRw1zG)f1)CAp6;Hjs()JK|NdApgT&!zBdHwtgzvPs0Suu_ zu3mnD>-A?^$c1j^jbg`P*0n;*)#vy&ves25j~3FDg&S4?4^r*;t)^hh9|F0R0JzZao2gK;w# za;f0-aNp75ax&5De|^vt8YY=}P(cfZ^OVI;$i#YG?oCrDca?*M3Nng6Pb?}!JR2(EKTW%}BoIm@der6$02$eR+LS312)^gD2uiEVQ zOfs?enBmoEsI(SJBLkoR9Si$%ANgbFWts?BvRC4&G^E|SYqmcF`5~RhIt21PH*S`I z&8jt$pVF}&wj8@12seJNPKrRMy1}E}X=EbXW-KuPCYJ9R7KD;FeMQ?-kq4V7+^#@w zrLv;EP^Tqov^j;0zw?dR;{~geYjk;F=dB$+P08phwne79L)UK-^_=kS%*w#ad+5K| zX6BuslrRGwD`cZR!S?(v@^9D+l0Ezseu94|j2vO$uSvq!y=Hi9ARqe*3q2gn7Nsq} zgF3SqG;0BCe{FuIfhDJN>xvWcJYM%?6DY}L?YWsiCZb3_8joPm_6jy5xLV8pZ8@Gy ze4)FamkpWQ^z3zEz)v#YLLAniIA>oPyy-6!eGINW_L2L28|ymj%eN%BPr@lg1r|!| z<{!U>^Sk={*=7zL9w}q^9SIUY{|<5G3W@Hi(s1-H*q-n9x+VhknSy)C8mf6u2yKJWKl_fCg(F{! zOk6$xZ3*DhTQYn6?YU@P)d&9f8PG=E@dVpnt>{E?)TkbXxb@>&VObJ`wJ)!swzL2W*oF z8+maJd4cn3q&1)v{zq`vhO>>XFW zx#~|QrWlO3=RmsouuvVi;`wo9){jgiIVd?N!^e+b_7aeV*G6gF7hk`-=l(6YZDwXw z7An$wKl$Yfnb>?h`5+YXx9nOJhdu|7D}VGs-`kjf!yo=W$``n{QZ3D69d%KVP358LVxr4IKvqj*YuE=8t!u8kSw@_ zxbV9#X9Qohs3oajUqt z<|SNlG6g0{?YOM^|Kn3L%VUnnU+v?*vGBY9;=&=ga**A5zyW!vOJ^_$5(g8sgyF2@ zpOOz3aJ(E#=F3pt@w@vTc*?@(-AjAy?@sY@J3vJ#{J|@XepgUYaUS`3DSFu)-cPwM zPYHiShI&0bhvQ_X7wE&R+P4GqcBpG*)LTg_9~-}mad749 zcA3L)ovvtM&;s#TVP10Mf9pBU3&Tq45w;KJh@&&lUS5K(xj7D8kfQ1yef??ZlKSu* z7TzE@zNI8c2tNC0{H5g-^1tF%Sr0hL)fB-F^EGbu)}6$-rrVKi z;KcG#yDhL?Mk2fL1nPn~-{5hWP`#JwuMzryCt|9pFm6f3g@6HemhnH2Bkz3(E02PA zSe~{^!o7@(qaO|b-ya=W_J!#&kz2^{*W2%muMNnAAfFnuBi!&V-o*&_X7SRO>!ZK$ zPunns|Gx76Uem)qwV?QO7Nn~<`a%Pieg2v;sf+qB7vgstW)4al$-pBXqkR237{}=B zKi42Ft$j5=jC?BP_f8x8tzarSS4dJCm|=o}FKctFv`}v*Gf!H;XNfWEo0{lD?geJ& zLj5k^quTJ^9^av94b*ePYnS4ow^k(0VQBn4`S!pu^ur4!9D(rQ*_LDh7_d0W{$3sX z>U&JLT;T>;k%}1xUU0rxrG`AAeZS8fDrA0A+B}NmYOto~z+evJf12>nbh6%*Dw*)i z9!|RrzcNXVAA%(I>-+l%)P=|{_p9)(rbrRqt&(6B4IX);f^pscjm-&Gy;V_Tgxqr3 z&QFxdMBQP!TPE;emRs?P62`~yb5jNs<5nLy3MYokCPo!;o>%uSM!_xb;#c;=tW?_7 zE(OF_x9?wXNTc&-YB!|oKmX;$5!7|dp=MjySZe%a3oI}CkXRs(epOt{(*QQpH|osE zkqIF_MN$$xpkFm54RVR(O`@j-u31dHAoTr3>L(OJ+QpmfFd!K4Seyhu@ zQQqjM`b6K2hxZSln0xpt_nbnG7W# zUlVK;!TR5SP3Sy4dEbX`D=g@D;wltIUavFVZU77RC@;eBcKnjoYn9D z!;d_EqCYAg#(cAsm4RD_`v*Vqp%0cexaJ3M=hy7!fu#bwpVaTgd0h2Ww}mFLFFR@A zv^1UhLtf11QMDm$sGoY-Vv3A$XV3T;3tMBJX-UK8!H()~682p~{d>LPh*N(UC){q; zK3dCzK0fiO>RBi?dhhNgH}+NgUB|Pail*!#b=b0f!EbaA#?j=#vj`YSslO}=pNx%3 zwe7|@jCk9*L(}%<+f1+{^y1TUF6=M9_YWGwjAwGI|2UC{W+Pcs;DW^mVMWN^Lo4@% z1MBjz)X_lLVX3dl2OR~@YQAR2y0Wu6Y7g%&o|M}P#jB2p6tLm@*IC%~;0LiUtJAFL z3+|*0-GbZiY1K-@Z#%~0x>=B)KU_I?8M@W1NOM5GT-TKvX7mj%ozKnT=SjhUH74ZY zH7A)2_|-S_mkPY1)O7FrF6_(r29E|q6{_Je0eG(FQ+U%(tY7kTJ_ne*E52qMOut~F zUC4;}+}PHm2M0@vEPgYfe>71Zx&?2wyf`flMwl`vrT4wqYMyJWIX~J1q;(@xou+CvsnI zMW567^NTI46R~HdfmRaT>Ung?4-dsJYQXcCJd?(@Anu)Vt0N$nLu0oHWY=h&Xr{&E zuP@h};T?vYZ47X|(4FZq4c1rUK2}3`I=h)^h8p)lOU&rw;IliO>(bCgN@l8y3iaf4 zaL*;UHd*(S6~1`+GQ80Jfb?~ZMC_i8 zthx@XNygf|Fkg1-r-oG$afT(w$_6Uj5*(p~Wg)k0vR6oi$cJzJMj^ABAcmb|544Gh^%w2 z`y*f;-87Fd{Px{&>$^Yrxwbpg7hqW4i#EFdAFDM#dia|}+aul|5vc9c14E+kBSa0>Twl0Y!Pk0}T^m11gsU_Efee_FBM_qmU$)bX4E`Vydz)V! z41heYHrMySoe4v4YDX~s>~eJGP_FZ$+VU`dE+gFU9<((LC^`&JeKP3l{Z1kbmo_N8 zVL{?eem3|l>>p9_jYJGRQaODR+Vz}snfpp2a!2j`;^4okW5H6ejF&RJ{R@c@-nbX) z2FpYkt~0=_t7k724Uq`_kCC>zkbY~t-ozk@5Sza&9SLbJ$}kDTUuGSXO#>uCB0}Tk zdC33yZX69H@xMNk(~me%YvDTzomq-UzV(p^8&}8s*I}+0g$W6U3VYB!|BP`|epYS) z&ue}+U-?8LXi4AJ?!nNsn&iV!Cf!N8w-?`ES$yRM&&8S>vB02%_xj3uNO-^A$7hbi zH+3=)YE@aplPR3T_i zc{lxa3yDy9ACzJXFWpd$r+`Xyy&)ORB;tp(il-th9t<$=e@`O5evehU0_*o&-op+P z&PgtmzatTuNzdOJL9v_Uq(5&-#1*o!RSawqwd5CrjuA(O-!BlqODADpT*isXdarmsjYYKO@9prnknk_b*Ar?Y^u77hta0ha4KX=R?cR>;}v$Yl^oDl%kdYJXnwU z@PBm3557J=9?b#!td@oGNmL!MrDY(UkC@N=bP}9f`PJM*m3xz8*QW__-G2St`Nk4FzWx_OigLz$mHGrzApD z{seE%Q25N_rK8V1?Bo(@* zd=kO^N?5@b?)us1xbcWYr1zdkNP)8+3DvUjNL*@f*F($`qw|a#w0uoTyB#Lnv|@eu zfJC^fhLF@C>A5@q*F5CyzP|-v^MIQG5LS+cQX5~*$^SkUNdj7VHHW{?#5&duRXYzi zOKDappjDt&VcLBXaWdh!sT`zw&Ari`f%BXyy6+Ap#wZlG!^IfR?g!~4f~ju)+)?;Y zD}Ht;jfD5lSh(T~d!{*8Sz+~4iYuk5xDJEDKf16fjMH{31@&D~tNjKP);@BW1k1vI zCqGTbe0QGvcM9@Tr5n!NBN0Y(N!igbFW|(C5LESVmutL>b@+}U;4J*C#8A7GL?YO# z+U61NE<;0VS-$db7W%$d$g{m)(L2#@*NAssj=JC*%22ET`Gz%~$WI)?M? zfo%-4U6nUc-v>B?k3)~5Rf)-H65(f&)*TAH8kaBeLZ3R8opn(pB8_(<*BGw01jx)p zl8C~)Ee%od_1RtxL1?*@_O?C(by}6IW)9t0&OTiTClSnACZf0C?WgofB5+gHZ|zkW zet%u=SxZPm5%qK_6u;+}$gTwV{zQn41QcdVE`A$=`KNbSuz_lfvodSJBtmHUpl1?% zk|&if1y`m=`xFZL2rQ@4qbjE!hXq6%mum$ejKNTsiBNn z_k9tEa~mh+;T_F9%I+%||Fe_@F0g`;MSlx4km;Ye?}K$xXc8yS>quPP^s0 zKwUR_i7n9XfqCiu%Ory9qSbzR=$*&;u*-`?1oMXNc7fV;4ebphcjez`D{f4KRuLuobEv)!k6X$$wD!Oe@8kll89!F3%-uf>2Kr{ zD##>YHJa*@!1MCU$;qxHB1LJX_yGJ{S@f*M z1@jQFtLYrv?=0B7?u>anGx;tF3X0e^N<#Od$f|cv825UcJR4Y`G!eVvi2S_CcPRmW z_`3asI6P95EB3|#eW&g8=2_VM^yi1a7qG69V(#67$3Ol!BMkrOPVRYWkMDoV>^Tj; z8_`D1ohK0o5;^6gVcnJfVSeb}rxX6{9Qv%=jzY%pu8(ZPv>ob;L)ob?cu~23gao;} zN?ogMah&x-bB1sj|8+cWgSZVnHW>)_CUshILAwX^?Pb>Jvv1GH=|E=5;PhcD5^>q0 zam^2Y{!?ti0%e+x6&6~eKdXscQG>5Ceboogl87-gwn#5HQG4dy4)`Y1kSgyC&f~|l ztP=cTb=bbg0(E;@?6xbUwr{DRh3UC$AJffIXKv8{mVveX`BWcHW1kZ?&twnxT8-`5 zFhf393FS?KA(U<8{jmSq3$7+p)ZMt#jFvEoC1ZWj1ncNa^jHk^Y~yPcg!`Y`7Ctv7 z5sU%4p~jGQX7Kd%Db%w+>>{C%=H&7mH=JDzuC6$VKEJHWTMyb<$4UJ-fxc;g<)=TK zSXWA9fn}LHl?#m!R~Zq*N8w8QqoDrdB;xF@i+enwsQj0bZSc6jymFQy`m@5v@8sZx z{9XDT2B<&(I9o5k-R^IXZR%qjCim1P!7u9UBKu*oNmcwCJrcpUvikQ7wBI&nyr7Hx zZ|PGK4LiGvsQFM3&&X! ztUCsc7_a{r)Fk13N&UBaL3?-ZecK_Wn>CTGfjaO?^OQUs*XpwEIEL}++jPDFFY9-> zZ>XcL1gg2+fm1rCFNnie;Y=2<)JVk5T`t<@P@>-9!0b`Xiz^*_1Wa1QZV`$}i?&s& zBLA-xXX?QO(Qda90&$&|eAo{z2ve@^g7pR4o<3ATUSECUp$sV(*93Z$ah)DxUCxk8 z$J3t*x}Q!bB`YD{`Z_$5gnNU#PcROM4cAx43dEtVb z`bO0e)T2yiZ+)0QD*bCj9_KrnWa0-O_@=zr1v`Be_db-vdTa=|rUbtUTl97xM!t&v zBR+QyiUUk){+N*4v~mBA>Zy$$V~NZ zWe;Qx8sUC&5OwBiu)G#z?5scjT+ta0{d!+S0hhgwawG}jcrGUb#2|nE@<4+Cjz=peYXTY1Fho!CBR_}w_6I`! zGXVnZaQP{lV<8{n;mWZ`1Pt=nKHRexb^O`|c4t`YclsCwyrKNv?G7*McJ4;JDD)34 zE2}4?K0RA*J_RrEefsi?gn5h8p1uZOj2!>V0+Z&ZmLKsTZtn6dDZvND!!upnsDliO zKQ2I}IFYWkJ?LMU3v1)xYv+b^A-LDu(Z6mt_CebpoIDP9Z1EQQ$%Q(t(l_r52X435 zF~XjYX4i5!(O3QOQjmkgDg5)T9GK^hb9pwf*h=oiUv{i(zKrE47&dz+l?*>TB+FH> zAzzae+O;5!)VTE!E8??KXxam=MEt$91xkmQE~c=cJ_TxfNWyiq*0ER2sIQ}6&CLFn zD&+MP6V`E9jc5>T*S{Lg2I-}{M)P-}ZqZjODno&Cv6!x%SSPenpY5UIz$>0rM%*`H z7(06ldix~b3+{cY6@7%$_(bzSIvjb8ZM4#ctglAag*aKp=GD->nE9+tKp^|F;C zRSNFBsa)`y9{r5ye7PCC9Vk;XwGI1nN80K@`1HALDJ%SAs-OF4E7lX!n|MWN{rbIs z2OXZLV_|g;-U#qjTH1p93NixhH{l5bi(h0Ymqn^Br$ygQ=XPBa)>>#E9iYK^d6#aw z!L|cA)l~3;<6fsEYUJ|;Q4Ue)Qe6MyITiAz+gEEN_}cb)g;b zZ{zzX_bIS1a?OxG2(9JUYnwKChyuz{S#y|o->i6ggNN9G;u8e#z3Pu-h1Bc6{ybdg zA+}t)aa;jfY~~bxSmPmT{S7E>;o!?4qs3Jod_JL4S_IT)zVUqzr0y`-Te8A~&%Md9 zI11US18?^%^AH0GKkFT!!~U*ctN088PiqavTd;)ss01JEnSHHM`HzQ4p=meQg5_-1 zjst)3^$j0SH&~I(?oA148x*_}7kP;P*!vE!Dvs{`V@D}c6zf&&*a1-x3l~ALp(v|XAy0k=|Gp?$M&=cwAsHO zY2%ExL1Dz|?Oz*=9cHBe;EQ%tf=AyMZyWG^psrhnqdok}jc;z>G8kL`THSvJ+O!R) zr#ho;vE;^??Kcg^-wRj%;EQ(MQ%^N{@hdF;Yq3&R`wI17g`Nl^EW5}(uGcwW6&5LMJ z8SRuVrdQX0XfVF``HzXM&^pIHfG0jM7=Kx?bX|Y64LamE{rDQj!QbOhD%z77?j0Un zH5kp$tUQ#D_S2siG;50X+3$Z_f9ib${{OC<6?>!YvL5}wXXrKJg@pXHy81Q#khMw+%Hmq9e8?U^H^~SNn7cpq_8oT`T z^&19bwMOgyn2vVPjWrJ((cTTN@%NV34aUWu|Gn29?Xe!?zj^ku!B}y1uWN(R*8g(T z#T%~~jQO6Ec1=e6;K9kfio{OKj9L31gR#xH18th4t+!~zZ>L{17~@}?`D|~rJF3;o zd;gNbIH^{7<&(#xjqQ(h1$b> zUVF)4{A%Z}mqw$Fcs+60cP|=@E7EpcpNTdgE;7Lx?UysMzu5YM!PsiE^Vkk(*KKI< z*0awWj2HWF3m%L%c-8v-*Pk;OcXzAcl!10$yU&sVZE8^G=kqTbjKB5#t5y@Vr#!lj zKXJie?6l_gfe^IS*ExRo*0WfTf7;>PzQ{MQ-1zj)GM-12_C$Skxo*0mm132m0cq3E?w z7>piH+Xgj5dpoyc=&AEq|GcmHhN7MHOQQzwoWuN_egE(AXcNO9eEic{EZ^LMGxN~) z>A7=>JKE|a8V=ui#(=-yUA2)P+FvS~ZajAy)Aeh+bqLxML(I{ioiZ5z`>RXU$!Is$ z^gan_>*gMh$vH-R!dd*a?HNYmG141f$*D-utOHj$?mu{lnhl&=&mu z$A7**hWZR!-hUR_hL@MT=!AB6z%PMYjv9;;CcnO~E!wY#)^dC52=Wc>q_?~3=A5m!Y!-HCVu^;PA#KwqBw0~DgNUMOh+Q3aq^Y>xDbHCg6#%LqH>U-?y zUW4(mZ!bR41MLrA?t1p}9)t1b!5^O)jrLE^gXg~4jd7{?`k`rP7l)79zRs9d>3X)}MDzH4H=Bcm2>iZ|^V|b1sc~+Jv@G_~rR`3k}9@-|p=_ z2kkp6zp3DYc7?-+^INuKz4bqw+!pQF?^@S>a+|?8*}?UxzG%0PA2<5yR_rI%f`7c`x#v=(&)Sr*myUV`aZfH9!{L{N|v%%QC?GyVtpnbiN)xKdj;4IUw*vJLbSIZZ#vHd?Sf^s-rcbQ$Ia;}UOs4z z#x{x1K92p$nTY)Z(R!bH|BDaTV>vuts+Wwm(y_Yze_MzBdhA=-3(>B7@WVzAv;z|- zKe1yi)`N_gcRHfI^UJPV&#W;RFTEahZvfhj?ogU_#5OhWtjTTj8gd>l7t z{BeIi+RNv+ed~rcE`HCo?W?i>kFI&KJ=!)CKHl-vD%5?l)9ij|=M3H+eRU<~^TTy5 z%xK};@9zGz!eI0rz3*%u+VILRPH{nd=0Y#uEz7Z;O=xzrHCod<=Jn^68I1EktsdMP z?MtSbU%b5(^V?+#exU-#tu8(5|FFb>>pzJ{W}yAD;g3Pp(M~w~(esZl#&PaO%ihh= zw#<3^xf6@9-;DS)Fc@u4ul@%wFT^-?@bnyo_VU*uGjA=xaevV3A7`U2>~cP!BHF{# zE3RBUAM^3cp^qD)Z9nUD%Y*Z<{69396oB@bCZ8X8aW0OpcdLFe1nqO3mU&*!!}{^k zq_7mUn`R$M`F#%NE3n_{MQHcF9(LFR?EuehFBHzk_VZozd+pJFDe{H`#5$AXF7B_B-wnJ#TmvcQnc^PpAu6W?WPNN-!2@B{o}%C!rP(kb@b!2=f)U} zS3CxJg`$mE?Y7{J(FUXIe_MV&5^Y-hRY6~k!g^aVd}RjOsHh6rszCU;AvrcHwp1S|khA6y#!*i#aqV4hci+hd? zH5eED?zJWm?d`^wb6*&Oar^wGk%Q27Z{X7TgGlTrckZr{fcD(br62w<*nsarJ-vGx z+IRE2Cs#(h=dBvvs|Oj3j)g5htA{rFz18b>N8miHf1h3*(Z2jazi*xxXfQ4ty=g@l z+Qb6^t=}Ag^>9m*izCq%y2K3mvcCcUug8ZUr=flCode^4?}zRGgBL%akJkJ`+p#Wa z2TuI-t%AOIeBZ~$W@z7xZh7!XIQC})o^lREyEK0Dr04tKI63v@7Y3qzEv~!g)!tZN z?*BR7g!VH}hlAgS;eE^FW53No>k;`^OF-*nxHV>3D3;^>`YUUpZ8^N)+_qi@;(bSxkzIzMea&-K&yRxf{^1435Hs55@OF3jYJL;YVGuA30WGq#5n6xyg=q0&yHYUFUo*+M_{E zu@%uKebon61Yo;e`O4fhIzV7qfzV$O0{Rf}T%0@f+dWS22`C|Rq@@>n- zXx|_A-Bee!)4=zIjXnlr!l)-4nxTEC_rku1JL34aW!=2aXnnifI{Rz~9M`^|`BgaD zDVvA6ywx7#SZ}iLD715e-yHNsJA<+5?eqyLXj^?ZH22rG2IH&Ea~I4;8#?LwvTA7G zC|I&QzYXfOC~HwYwBS-WV`pp9&Rr(8McaDNGtsBLQI98%1olAd*6h0H%dNr1;E;CLSKMYZ;5_gDEj>`Z;^M@IzD?1A=NgG)zV zs)yrJ+m#cM+;4aOJNzSejgTEn)mpqq6J#vfyTAC!hRZR^)#?|B;Vcj0O$ z&PLnNd*;NdXb*ZdN?KJL`?+d=#CoEwf7X5Iwpy64D(0Ro(awJ3$L2>pupb*VIIIFx zh5evxOOh+Fz0yqqJM3!K_VZ$XmxgKk1v0lQ8K1j~FTtD@ zpP8JIot&Pg&_X>VcvxN|e<5Ta;^?QXKPo^C8m~(% zP!*sqy<`Q9C)wCxi>-vwAje!WuS#}}#0{z-BF8IMtVokMFrG>k-VC4)yl@HtcZP}c z>kUH37U^4ErANjT=3cH2jfo?RUoYHsg0ggYcs)ZYkGy&pM)~B|yWYraDLmaLy4hOS ztGeh~M^&^wxGzK3aLOZ3*S?fbzOMa9*?Fa=`$f8XS5yv&?e!A#;iEi>UzV{k*zA7>w6&rK2-I##lP+6b)Fcd@LI!jLdqL zfb6!IPq3*C&AW_#FOud{UcWcQM&%w#OUyb2x?j}qd6m)cG4*-%hJQX!X{Ag3txd|q ziWY^tGV9?m$|t|S8%}v2t-qs#2P^Lg&;lIhrIqjmh#E3H*u8L`g&tR9{uW#^FN$lJ!*dw ztV_MAM?%N??eVzF=UErWC_U$KavnE+{8Ex~{31OHpsagUfx8SRW%3@E9T1EI8`pi# zvl|oPJco6epTm>de%Dw6pA7Bm-e;%ppV8*y*Z;RzCBBtm3Cqy6@!I7~VtyIpwEc3K zUtyfKUti{zJ5t+^x&3QR+4Jg$zmJj8-vQJM3cWXdOh zyqZXPtsk!@(Hay-KW+V?U%eTuOTAH#S1A}gne~P|3$VrgeLCf}zTPN3PaLw@e$%hs zjL@atxRa2vdCp|qm054db01sOn{3MaX!VAU)~via$fMuSY7H(LlQa`NAi$L zX1yVEFk94{X_WWT>J6P8v+_QZ0ol8~|Z&A{EV=baD#d~zn`k#|2o zi}J~zAI(NyOX2B0S5RqNeg*7w z{z7fPQZsGEK^g5T53et?Kc7qaeyXL6aY#p{vm@y~;l zja_0nji2@HbOq&+*G^YbKKbo*74ljNPxq~x&p0AS+4Gq^rYG?#+GOjgp3kht%kcTk zjXcXh!Y=MrlnISJYsTT+bY&8>9{FXA!<_GN$|tWraNZ3x4#nFWc~6x@t|)vo!cg{m zm5=&7=(pDug^FKaT|YWDwEq3dCRi3;-U1rOM{n<(XCvjYzP)q4O~}XV?TSIl{5c7a zJ<4WH{pI~)K3-Tx{pEa*Q9gO~m-DWn0V-a9_&VRC%pDR39k9d7i`ZxLdl77y9 z`fk%*-_4Tr-9mZf>ARKk$=7!q^4d?|y}IkWoyO7n{<)Cy$kTTR<&&@PPUJ1AuQFe> z-TSRab%l-aWOH7-$T$#x$ zpUV9L9xinY4;?&^`nf8nl{5f^hiqv%spHL=Ug;COrkbW@q$j6kXL%*3d1YssCYs~3 z(=(@eWyPED!8UuH3h485OxHCzA%0*;Owh2XkeH~TK2fnz=oX!vYNo8p+B@5&rm`B~ zzB21BI#`wM((xMii}NRE`GwIdDVf3Z^u5obvoNypyJ}|zLR3)H(D+zu-Bp3%y>duTO`e zy=VZuSUPT+@EvBG3b+}=yxt|46En@`ENIS-^ZKD&r!&6{v=DfByW@vj@f~KpnJv!O z#lpwyFHhEn?=ag|!0i|=&KF?eYcKeB5O7BU`;^KTXyNk}{QU&nNx=RL7uU(pLfBdG z?;_v;0SA`OXDM)3!9PgA-2~jdbUur~Jp_L?cv_AITlSP4Bf~;@Eduj?fG6w4cbE+o za2Uh9-@y7u`(m#$Mb4#ZK}rqm1= zE2JAI;8+32F{~OFR60e4@cL#F(v25zynqw5dBZp_iG_Nbpa!^~DBvW9`8dbps?^tE zA<^c{RN|5>q?;(KEfer^0k0^Bw+lPJQb@N-z^etE&v04!4PfUV z6Vj~_@LB<{W4J6{KCZ79(mgKV4FWD;*o)cXxma%YO>lDx}{g;Ozn~WY~+@68tRxsn~6h+pitWkDHx*huK{M z-pz1*W=rsAB&UU@=iqIj;;=_ZzgNKf1iYW&y3CfwA8txcJ0PSxDBwc^KFn}EW=rt) z^HFCTjtJ?G3iz0Sk273)zlZgvpAX44oDkBT6!0klpJv#JSg!M}hwA5xGX8%H(VMS-RK$J$9PirJ3d(hvQQrC~ zmK6&4`*Zs%Dd#?(RHdYo>c~ar=bx_a7sLFVhHLxfGQaOeX!`{+zjo2ue)mZbaQlTl z$5fE*KdpI=!u2CXl=0k?^HrvN^49A(ucAQldd;8P+io59SzX!_8Mk;jtJ3&A`Z_G< zsU}%h5_y^Lx$}HhFIL`vwhr4G({i2~xL!JK{d?hvRlB z9dOJ;^~05czyIs?r6qwSX6haO9`lV`+J5}KW4Nj9m&69lz|E|qRbTA?(7w$6lW1y- zcIHBP%j#FOzduV<Hh>IkYSJFm#99?l=X1#HzeDcR#56b&! z^@g-+US4G_AP$e%e$%hsyst~WA@}1vj%4jaX1yWzR<`JG$h~IS^Hc5qMqO`|d&@Xv zv;C%Dz4=s^dgF;9DIgAFaP3b5vg5M!2uv_a6W0_v0S*{{E&e z^@dE#cpMw!KA%^59Z}|U<;^S0ZuiQH02%ufCgpxkJxk=*?TS7!>uV75m9-ur^Eypv zzU8-zrj+;5+6B26%+p1(E6D9WhkEhvxm?@MugTT(vx^{*B3 zmV6IRFMaRnuCF(ZV_EI(;rlVNMj^9*X+!zs>)RH2?WgbGy6fAH#?g9x+fyES?Y9Hv zldo?_RE@7vQpKJTwB?ZOJYlZf86)*iiWsHlym~EN;rXS^# z->y2*@GA3I9%+6bY5VW*xaiWZ)b|7aG>+Eyo8+Aenfj7k9iG+hzkv6fm9_gXFXf>QJ0-I?2mCVV% z>xhhY6@o>QIUndn`Q*>{LTTv5cBS;2Mb(Zx{lGQOGJlt==>Is8xmDT!Pox8DN5rdW zlYQSrS_fZ03d4OFdiJI~^7_d>luy2%;mBL^zSvfJ2I-NLsBmKTj zY=SA<)X~q!w`+SJax>Q_Wg7Xm6!~IvkXNr1Q`)3hA0OW?9g@f$ICE?7RaZh6a%6XZBgX(23x9k0M$f{HWc!*mog2*F4Gy{2b{L###{3rs5gJwC)Sb=K+Y584}FOPto|wXY@%{ypj`$G^m|n?Fim`vv zN8imHb)8-}Z$Ss;{KZK|>c>7vx=kdT(x%#HGmNn< z_kU#=a+ms(l~S`RLNOzVeothCR&!4=qH(JF_F>sFHC#syfnc;TpbKE42z-D?HE z{4wLh_I9?>!f%J156<%*v-emM?8JZt zEWgP{FWLb58Sg$ITaSwwJ!)Lt)pv+-J1l#w#Vc)CI?hJzgCno(>bdIc>I1q%eRe*8 zStGflrkk;Q(v(jg^fW3UL$MSnA1`1wS3lp~$gigy^;P`x1}YZfjd>50Gs-99gOTh! zJ$kXh!R~5|2m3M1{i&boqd7YdFqxG1ZlDFLoFK!it!bEvqipi&&V*h47xJREjw&6!tuQRioeB1FwCrR z1jF2pQt(A)<8l?8&urih1@~eWdje24W_i8kmYua2ew3`Ml*~ejN z`F*+fhinSYXX1FKw9T4vn7%%S9-iu#f_IONI-FbOQsjbnM^Hicd*0*=gN4{^%>uIh*(IyFxJ<4WH`S>_z z!VAl&znpJ8<&#%`Id41-Q1SYs-+Ma&y3}8Ff0jVwX#IF)maOjt8|j-^s=j)SSHZgI zOTJUb>oaNdydQ|WZs~w&6v&yAQ!w#0NG-o_k$eYWzNzSol0sieFy8JulhbNLz2Fe zCF`4MBYm^%s_zJ0^i{XtY)Sg&NY;0Xjr5&rSAAo3(O2Dmb0z6Zp3BG_f2Z3>-x+q* zH&GXT)%PtkCFwg$vc9uzr0*OJeOd6K{NICdMIW5?<1ns_UtU7vX#IWBQpzJw-({3fzP`(m*M9mg)?MEfG>+EmyOQ$A({~l+ldtb; zv_&TUG!~$CFJ^U!F~Qb$LDwU z{K4`VLhE@>Wr$>tU0naPBp|^nu_ZCTyMJl>8JVBYf3^MYR#Pq!`6)|K;)?$_4#;R% zTd}Az*ORtUKKboxJC(#<>;1=cX;)rYM&7OpabJeMJ1CF5esd?~ldtbCh(SU zbGqnTA4|yf-HrP)^xZ>w$@NKW$1f=^2pQoAmx*< z?;+&1pT4i?qHjGcA=mdX?#s~k2<4Hd?@`JpU*BWMYd?M8)J0$Q^Sa|Sj@I9oo}fJP z^gT)W=4Y&- z?RSN|oQ~V2WNp9W%r8Gd+fQNB|I?aw5QdzWjHw0xjJNx#QCM z|5cCry`6Mj^d-6HE(vY(5+0tFJM7+0C+8luL zJCweb-$@r|8LXSMT#|Le7F?C>34E5{ac9f37XRY40QkS7TJ{AK=T(rEGi~w zf;rX4FDT1A*~d315r2S_O^Xy~{N&f?2S^)JZ=|h|P2|l;C`Ri;+JJf!M>(RcdLtT< zjofdLjr<-d$*+oc{G>!5@K8=$KFNDY>W#d|qTa~+q+}!g2H8sI?VXwFqnzj0S5Q$o zIVShJ>W%Z36AxeVd_cW5R0G^E{_R(QM(jM#PwDygg~kGpmw=lHxGBRt-=*_nJbV?$ zWd&*9TfO|2l9S?s971D(XxVM1&FkEgtm|)>T`o040C*b}Ji}4`+bSG6m zJwQk|P{0ua9#jS|_NQuEpTR=9NC6KK@KAu}SB^ce!4EZ|Ir%ZW#)EFoRC zfO7;qg<EfDZR0WV^>+<35lda;mxiGY_1cp1Z`#KV_%(|&Y1?Xz4+w?e=x1-y#kQsTjR zX@9y}NS80*#{|5F;ZowE;w|b=*9z&^33$DLA7{9nc=&D*(iI4JqkuOttQ`+u*es;m zBH*n8-o|jb@nHSwb|HPCfOiOZC&T5$!*7?6ZnuE<2zW2U+VSv%eL}kZ0zM$%gAA7& z57wU^64Da!?TC;w`d8$z!>(}QvA5p*-`c|jP+DqTxbkUczXCB8IG>(t{ zJec!1QXYBwI#E7(>#sbY&d6JGeO<5TRu6R7*9A+<*G=y?t4K>vznn<-Too*Q^4}9O zvg2`wDhM9Mp$~)J2ekbzkuUR7#d@$1WeZbjxRJdW-(j@I{^9+XF3zgdg&$?rF7BX7xmQ?KWe zHFeRK%%i!!p13cg{gOH(v;C5>+ZOG&9`f2x-#WVJtDg4|t!3!jfbz&|zYQs${Pt@= z-je$2HScMxi@xgqxe=C@&wK8+`zQ0Bs?fI*4Hg{t)t-N3FuzNI+I|JhFR7=tA27db zEw%l;ncv;^+J2GDFTazvpHf!*Xr)WLQjd#G zX&kMe_cWtC^7_r@Hqy6+UG?p#i@xe{v85z^TS?Z}+eZ4fwyVAYy6CGO7u!hEx2!Po^eO*Lu%jUD}m;TpOt* z$kTTq<&&>(1oGNX-#NPLJBY^7dVL2|9(npkQa<_m4nbb~>AP4LebwXdP#Q<;^^KxD z^7M_SeDd`jhP);9)oWd8wJ!Rq$KT;tTE4F2)t{}?Jk+{URdB6Lg9SGRX|L}XnO|PC zwqFMGD;TZqSIGR1kJt9Q$kv}6!nFPFGC!{Y+I~uD<$cOnR~muYk-4rEL;2*ltC2Kp z_F7lkpi8?_&wEDEI9lItj+U(N7#rz3)~@<)*F|6TxHwLdzOj<^jiY?>+ph_EOOA_r ztvl?~MPK!}IG)DQ`t}=7dF0J|5-6X1ea*;gKYfqsqOZDto6AyFz8REH zzP^)@x1_#$-Iu~>8TX}Gm<5^lrP-8Ee!I${p|aO~ z>6^N=EA_ZIg~rkPesij1eRFN3?=-vWdsP>G)#KuHN&3!^tnW-4={w7=`d-&XU-h^+ zTavzWB&lVSa@} zwf*9l-}#Z+e)-HVCRW?;2Aj`Z3fK0dlH1`!#=K`f=3C~xX94At->w$Y_}XjUb4QnU zr5+a-(KuQ^?^!Ha-z7HEcd1?V{Y@8r)#KtaN%}6AtnUgN>ASL2ef3%oc%X~E>Tz)u zmY0u%r~Q+(#~oK3Ldn*5vbLX*`NidG`(-dcpHxSu&G=Z` z?+Wv)`+>Hf!Y1!i#<;i|vm$}cI z`mVRDzBP5xS3NF1E=k`FlJzaHk-i)4s&5@#^i_|Gn&M?+lt)m;A%8TT#wFxxWkTlP~v`R(ce4X?fKTLN@xSL$)`AdRE-{pKOb`X08C zzDMk;Z?G=j8nlJq?$S>NL}()Wa2^$piWU-h_nQj)%>BAyRUe3~ai+&%lrI5arfV~CWn&EQd;nzk;*H*yo1l*os?RfY>2O(WY0s9Epm*H~b z;iot`n3OIt{ zGULI1cWRK3Zm@tO1w4e|GUMT=^ru6GbWsA17Vt2J%Zvx*E$UB)3+YD)I7YxD87?Ot zzN3V6qXj%hz+)NKj)yOd6Vk;BI8MMOhRcly>uAOc>Ei{QAYe1Y<;25pf{-pzz)1p5 zW>`BOelSr;H%Y)L0#0SP+<35#G)+jKF5nCSPiDBBcy!7X(q#!aTfjLCYsaG#OcBye z6>zSAr!ib^JXn7^T}VGez%vCri{WzO;XhkQH%GvE0-npTc0BxHo{(<7fENgOA;aay zgY~D2g!GFAyhOlD87?Ot!SGprHPuNCk*0k3};e!n9A$A$D81Y98CjSs`$sfd4* zkbbj(w+MLa!|?kT@oy8-Zx?W(fOjyg8UM~j{5ys8y9B&jz*&+$t!BoGERyro9*bs9b~e*)rNY=OcZ-%>S#*d6lulUiIO@WkrhO*O&b} zX|*tRyu4&=eFBR-~>DjHgnCYOgD4i(gmWp5AB>I<_c& z)u9%0nKP2(vtlzWx0&^jg!GK$PtB+719;$Z-k!*uP=>AtDUbEzC(jp|bJ{{zW$s!$ zzFgNUEI{S!T9?Mv%`4cVtG^@oKVZ$VY<+9$qHiOWzGQALLtip?FRFKrs(P0_EyIi_ zajS>zK&##nAFeN%``bd_FaM-eb4ElueyKkEJ@!k zlJyO+k-mYY>Z{j%%LQHZRrk+bvAq1gWyuM4A5hLZq4GS`0le7#L;eBn`-d3j=W|Tk zFQ57KJ*(|^pWO#t*`w{}!}u}|Y5OHHza=NN{R)|%_jzr+EmJBaeg(|0iCldo?i^4d?|JG$tr z?w^OyI9jjoP|71u-zdr_U*BltwV%Gf>7uW?e;!8TXuZC}DUUpTM^HZb`oOI|g}6>Z{kfQnlBVC7t5W4b|@jzb@guV0HgI7E8<5 zm2SMi)U8x$JXX`8uPin89++}|IFKGLDlP}ZYcJXCxzc}U>cUjx7fcfRVqwRN` ztpi=#qV0E^`Q`4^_ESnx{-=!l(s6hhna{&xDWCjy6-UESyk7J7b#-_jTbFjF9v4kC zj@I{^<0+54elwo($=5f*uKL!|MPK!}XqKe!1j+g)+DP9dyXxCm7k$;^VzMNCCrZ|L zl8y9Dv8%qVbkSGcKc`C4H%+p>={C|g!>;;v)J0!)|2$cezL}Es&9afc*`?~M*L_QX zF8Zqb=Nv3Azi%nH!sg=-b>HFuf$YAZ@RIgCK7;ug-_Z8k&-`NE)An;^em7p!_Up_1 z^8Takm&g1H-qQ9v&-?(eP`H6-6?eVCD-%xYQKYZ(N{e#&c)Ko z=%43N9(n5y^C_SFb%zDWYd?KQ=%TN>e_lxAXnp^@i1NtOcQNIYukRA%Evc_w^V(Ql z^i}uIOR=^&aU9F%Ix7T`nqVDZ#C5@x?{pKpk`mVN-zWH|5 zH(eKf$#Y(>PkM@8gt5p1vC> zpL~4_kk@|t&e2`pjWmwd>${2a$kTT-<&&@P7UZ>`zKeC&cPovf_4;n3Jo5D2PWj~P zTZp_R_0{XXWwq}5?!eOW`xfup|Lnen{NF^f`P^XX6jptCeJwoGWeS1AhdF1JOjPl9X^*HjDrK|1V=RBs1zUp<^6Eu$2>wA*& z$kX=}<&&pxuCp@7Ig7j{_5G*w*MItdvCrwEYXdA9|NpCG#hTAs3oEd7Y0EM;sR3ng zW&PeL;U`;UNRUt0eqljT(IK&+v4aN3hJ=Rpj*99qJVg1HpE#QoSH;=1Qz64o_WC&( z*by~0@9LTP~Ugxl+XRm=+aPe7G(eI9y#@{P8s+Y)L!@{KOI11QF zz|IW2F>CpK$qD{J*_oy^@@^E_TohyZtZCU(c3cI_`D(G_YTgOSDcR=CkQ8#tT}bC4 zV4j{ICi5|}k>{&qE6E$4V#>+_GQO)f?$3{jwWuCD!LL`6H=4YD3go$;dgJwqA8W{W znB}_hd=p;sJ36VkQ-b1sf>OJ57}{$%`Zf~Mk#$t{=A{O>--O|^c>Nh~Qz2b50XG+L zi*k57G2WI!x>f@A7I5owcsn!RHbT0#0&XYZ_GR&Q4h)L-4NB#_9fWip1?(eWUxs=8 zwA6>>_##sD@e|T@60pC3J2PA=FRu*f(?v)ZAmBg&cV)OlePD6mx&;a8x(T?ufP0j~ z%j;QBAziS5jRFo~xYYb&yWqO@64HeVI84C38RqSuw>OhHGo4llU!|h;5z>VVxUYcw zFlc*DvI~QXlF+@u0*(~$5Qa<5FY@}a{0+d)rU95oP1Z-kh z-HvI$o*86Li%(B5XDSZkg>>-(P7tt};ZpO9{TZ(h6NGe$0!|Vz9lY>ZS-iYHOcc^h z5-=UKRmW1rtsGt+hv`DP83LXu;92GH2C{ZMTSzxYz zzzZ2x&!dWdA2T#GEH;6LZjq2~v4EEdcxm~(RDxwfy5$01A>fq^i~WxBd!Av**pg|E zR|)A>3piiEk39@8=6H>eZmodV33xrjZp`xWEk3y`PT0+INNykf_dh)te=*&%vQ61JO3!tWo#*Bd-(mK!fR8ZTh*`?p zZ_t2{h@fno|B@PE&P4W1b1c2lI4Yz+Cg9@&KEZG?{fhXL%%<$*G#q?R3h7P>__Tn} zFx-GyA>WxM{25&gwUTM{IV+?;Ctwb(S;yt;4;7J2#&Z|WSBdh;TMy#Am1&_Jc+sh~ z{d1QKx~vBour$1!Rd8R1u2m_I_3L~*U)3m|d|eeq_15(j-F2-lN!J>Zb#=6nu1?5n z{rc^r{{G#Yy68*hCA@t&<33-<-S0qpLEI`RhMXyFQ~p10HMmbI2W~!XSULrk{3PPz z=Gso%FOK=;_15JK}Ejcdg_1^R?UG!DA-+DBTkN(`6w~P9e zN8Wsv+>6;l--gI*KYj1$qAzK)To-chBBTA1dwQAew=w0DKmK|lZ%KXs>3a!}I+6lY_S3i8W#vw@_`F*7|20*Qi>+xKt=G2=<&mdvTgoS2 z-*(7bQeVBs#hSY7+a62H$HjvBtV6dfXK1bWRDnR|ex;*!{~yQvGCFJfEn$93x@-F# zXXBl*E=xc)EBT?^qX3-WZ7EPmCp#XIF)nt%{L35{J5oOR?aGJ7xp=?DpL^JTT&$x@ zyCQ2d{C>ih#?ktIlRUzesc$FBCtqKGa`xwSa*Fp(>PkMZx_lVZ(IzZeDd`T zL|*&p+e#OG)$^XNG>+Em8$@~J>D!I+$=A0#@|M(BuX#^L-SzE(rRDRU{AO$(VVNdq z&3hamhWUAaWJy4yCw}Cz=T`*-wdW~Im|yN7ZNF=5_{_hnJwI|_e0SSx^SLs=D;>4{ zF0x`?{93!b1uR?nm9+CGW8TvfgDrDh45obY+m(@43VY3a0(5UzAvBKG_nW;akGy^} zl=8{fH_Wd32J50Pc_zi@wY_N^t=G2?<&mdvIOUVCZ(rmsIWFoouMO8lU-i7FAC05+ z`u3+h^7I`*`Q+<65P9vV?_k~aji7O~Uf)5KN1nceDW80OBazpB`i{_D-yt-P*6TZz z^2pOSit@?VHyU~Er*Ew8`VOOUv|iuglt-SvBPgGIePfW>HIl~BdVNPx z9(nqXrhM}C9fQ2~(>Glgebw`;u{4g>>pPC}$kR8L^2ygX4tY!JtJk{HR9*B{ult*@ zw0vFZc4xM(Vp%rPT34zDG3>cklC$=DOkL&|SykJwFI(Ta5v=VO$^0&cX#2%5zrs*$ zzq+ivH~51cS_evgcr(BAZ(9OX(&--@k+H5c9bqKZeN!drnmSJ6M-ctp9*u3Hrf1!xRuH;7`^V=V!J^wH=za`zZ z{k+*cYJYWYKO^&7;;8LcGROKHkuff2Vpe6&d$K5>{BbdxhSXm3p6$A~s~j3f>+ct* zNY;0%jr7g6tG@em*LRvEeWy#-cZQAhooQEnkLj-OEJ^y#maOj_8|j;eyd~Fr^m<-; zPIrChVrgZJzw;=My!E{Klu!Qny8wAh>Z{kh_JS_@s=xQS5KGJFwdeb>`QJm$YpcQy zHgC*tu03zO&HOT2YWv+~ev#hVeo1Wp8yBwam%;qb_t*Bj$fjGzf6#ufi&hPZA2Qn2 zBFvA>dF^7#C%;`Sp>eaY}guIp#`9`mU0! z?`j+An{QWruj;PvW0Le;BU#_IHqv)psru?Q@42pvzHV4X{+{Q0+~@P2<3ri};GyO{ zm7y-1CfxtUl7L1}{7}X`fQ)n6eg!OH+EKdp1edce9Q3-GaO&?-!Nz0Nec@)g4`QRnK>}()d}g z>o&?GZyjJe<&)oT3z4^^u6m7!zv-f{`u&3)SXw?FdOgMlILk0o`tvOsBK(LN8XKRJ znQ2Z_9$rAduvqdn3J430h&88~GZV=oFvC+#Q_Lymw8ZQr<$QLMIoqV1hT&lmu~FHX z$rGpWg$u~{j}+2ffX$I3J4qVwb#5ZU;7 zmR*R7>>V1LYKqTHkHw#XF~??M9?}whyDHvTzT)QkBqgO3s}CIW`oJ--4;=IQ(9b6%H6xr}ie4YD zM3{Y8b^!7CNr^tp#^QerS57fM-{OA0DnI2E^Ybh2=cn>hPBFhu#r-;|{FGD7&%d~z zzsgTJ)z7DR!4MZK7{Rn)jEG@N0l7afAUn??UXP|H{`464=LKcwIaK@8W89w?nE7+4 z_NT|VKQB1*=TPlWkL6?}6e};m>hdydDKGcu1!U(r1crOF;!;d13NIi#&!Ng+ImG#S z0hvFCDu3k=_vZy<{v4|Ol|x|zBSS;{ibS?xR%kW06dJX+94Cdr(poU4BZXIEOX1bW zSrF8Iq{M2B=`3-v94G0ps1}UZBPCa3OlOIYbiWi^YW>=W*c})6ux7 zusSZhehtIek&R>JJD>{2(JMAS$&{98#t}1XQY`7YdB;fnOFPdSOj+l7MJVe$7rdn8v5@;TICp(=)ST<5SYJ z%=9(XsY%KBU;nd`vNL1TGjKwqyqk*ii1f7dQVs)NR2ln}Fe*4HIet=_IV&qx`HeYp ziuzfOhx6e{IIjH08}Sd%F=Zz7O3x)0IIQop7AMc}Ak8 zoDsg!k;(Dt5aFj-QfiZz8YNL414y{k;=v(ILU|3n=8Q1v55)l;pHY zs4Mw-j9Btx6TJS3$9cU|A2*Yq|Hx8!)yGrVZ+DP0>f`u37M6IbkCQnc`}qv+AKkSJ z&5jzY-PFe^E#o8U)R$NLsk5s-o|5JlWyyhxCnFQ9AkNKb0aeGRnBsGCQdyF|IXLHz zjmF6FpXmst{zId~d`meDc-*gyc?30c7XOo^G9q&%C zGEj{D9CjCpWd3Qv<}}(BaEu&uZm-~MONYX5qxk=~VA=Se`CkP77lHpp;C~VLUj+Ua zf&WF||6BxQJjXkWrIz^|?;Pcm_dFmsU3oUF%;AbZ$1Cf1-MNr#6&)I~1Eu_)9Q{c^ z{)2MtDKt+Gl`VV9P89)HWw;Tu0zaA3=49awKCUFw-`T4sq%U5YqThArZtV5Bvhtsj zmZ97*@ccXSgv>e#*jd0X4409A<(@Q-}BHScnIlh379-D zQE%k=7}@xBC_ny=x+%ew>Kl}qoxm1G>L_lO&$^a9Wv8Bi>oZ&?FT0^_Af)5|*vxC`48^M`q)_s#(pPh}@iz+D9##4vA{Jb(P&yPM$OUBEpA+>>Fky!_rhSnxLr zI7GmF9gMeYoMNw3V4WshcYbIBg%_wB2j{Ww1D~gGCw?=?ePAa z>qPkYnhJ3l!4ohWBjAw&9>uU&pK#%auFZ@V{Kp7*tboTc%=>3QAK$L+eFEG2_fE_Qr>+5A0$4+fW&XT>$8yD!-<`(il@on~dZ(B&aDh1*3(5TAnB~UAcbFY7 z;CP0Wxe2bzGE5hxaG#G4{JNb2{d|H#l_&L~xQ3r*R#wFL^*Q|nzQb&yfRh;J<6|-Y zK{;9GkQ7RsETo$#;7I~jas%MSj;H0MjyGp|rBCp}w*qqFCwY+v518pJuk3U$yi6+d zQ`H+9Us}%8coXkWamlXOmR=0udb5BhGtB2#896CgW(;m7JMP3RJ|Vz|=lpC@{L9J@ z@6WS^>*WY|ih!pw%=5#aj*~TWvhjIlKOY58Qw)@qk526TbY{7k!FQOQDd1TQ^ZLf) zJDVNn`Qs6p!;bTLEk3`YNAlQlv3?GV3W!)67XUHFJYL^r+EEW=O6ipnkOrlAm0U0Z_CsG_m>NJ1;czk zR|f=b!NN3K1)gmJ-Y(!mhMO~6c6n4mL*E_DpPQY0huK{M z-pz2?~o}&UjCSVTh z@*QUN`+bjAuUDYgbSRbs_3t9R(I9ke(Kl$!%00PA#*nJr{+`k zf!^oy^$s$oShK#vc}U-1lrKkBzOtwB_x$QrsGylIqyWxWnexeB->E__QsQ^b{^|Rk zt~9Q$D3hX;k+N1nL}FaAVnrHi2T*EKr3xK&(Upu-Jbq-1=I?nPpU*~SUIAFXXr@c~ z;J<^N@2H$SzJR4K#g`n~&-|7w()NpFeh!=YOr~_gK<4MOQQOa(`9*Hf_PfaZlK2lZ zm3~>cR6{wDKTNwEWh5_gSVp_5hO9E%6{)W>|IZh%Z`En(i}zc+-?ZKP@d3KDD>6Uh zJdS-_0x7(qH;Jg8u{7 z%*yUJgLTnY-EX>JK4s`zlk!;q{Rdv}$XIR*eU*F1()G1nyA9VxS8`v%3E=vo_j`Rs)5;s@`KZQpJO>!L5wl`!d>X9m*rG-I5ugE%a5^8pU?|Pv^0Z z+AojLU0<>m%jdJ_*RX+u4>0tu0v&`+na4VS16#;DzgBx*=f(UA)@%C(GQZpn+J2GD zFJ_~*p93q$m6t6A5=x@L`~2uj+J3j0pU(^0ewUbE+;iG~(L;Op27F3gkgt^E0oyK; zn>5@4v)TMxxw(S-)f9Z3ozF{D@D(zd#m)Oe1>Y`SeU+2{`}{u_0i}L;0k0RitBTk6 zx;vHnmBicO4(0rQhVu?8_%_3RPbt`oMdElvHE8`uHcId zFWIHwzRW++UE$}y`<@i1oWG$QhHHHle3#*j5eoKVcz;_3M>3q(MZvBNN7hqz_&SpJ z8KwMp`F-t51%t{=zmbLTOB zc0cR0Rykjn;iMtT`TgvAaZf7O^JeD@dMM}l{|hbjSFpF@265FC?4!`a?cbCWcbPsn zZYn2u{~%*rCTsjM@0Z9uoh`;?1C43%j!nOLSFG;iGP!q>p)Z-M%hcD4^2uL6Y=XS@ zn^!05u5VKs$4CEv0DOMljPl5vSCeM}wrIaCkk@|trt7Y6OBzS(^=(CY-B9*dF1Kaj`GRZw>|QfrSC;He<^>xQhAQ!f&OHsQ}+5~axlFd z^-}i8r|jo-WbKdlqaAQx#(3M2^2pQEhw{nSQ+fBK^!b#0JyUeildP$8J;}R5GV~? z-;(DqW%sx9n4ZL|Xp^lcd3M9wXAti5bw}96>Jb}90KAGap|Pi|Lso`dw!Y!8wRn9L zlP+O?*S2Z<6)?a3h1!06odtGl`-%DZw_zFWsv8zcW*u9pz?P|}k@CscGX!}{_LIv050pQTS*welGFXMo=AAde{>9DpRQS#rs$>=8sU;$;0 zdjlz-{B{*Vh0tprvqhJ7rGD>Z5RIGl{p4WDdPdqv&mp?%xm_1M)$40RCFvO@Suc)!#!+|;K99*i&E^4*G>@qQ_u0Io;HdU|!hvJ}x1{6Res!5&;7M)2d^Q|%TW< zqI~l8A5VG98b`Fh*G5_|FK;~V1H{o!TYvC+W4rhG&*@TM$h%)WehIiQqu()89(m*W z1j;AB-X$V$$?@D7=!n3Eh4TA@fBJpJ3%ck^zWcy+O~QRXuE#yCjGr;AeDF};SF8+| zNT_hTpK3{{91uQz*y+F-+I~sw^o_aNetp?#*IC+rLN@+&P)55-#!|`b&nHqo`R!^F z<>mdk10BEd_bk%z2SK2`aoY>i@_L(s`{mWo3izL$Xw6{NLu{|XjVTFmTM1hNaV6g` zR$ig^q7G%x!!JFnw3t6~ULtUzLiN;O+IrZb~j6w!Pjw))anvp(%XYs|ECo zY66LWHG^&kn?lp2E#X>D6L`y^IqZF>8Jw%w974A>g_UQT!VRb95E);> zQU7fPuE*QJj74qWuMw@`#zuLIWiRL3>XPjqs(w)a1I20HwT`I$${r~PJp2Q&WB1} zPC(D`Z^0i=9EB14eu8bA`&YO$W*WSW=Zw$155wl}g*r*eaPx-|FniND@cVu$Y$#X- zyC$SU#H`^kH>4jN`z91dK^Vk*9R&xvkA$zX#>4Y_hQgD-hePuEp0M~p5cuu$gQOb) zaIbGy@SoEYUKr6EmiqUDq=??&mERM>V!Fb){4VhQt`6`u_&~h{9iV#qcJSj$KWKEo z4+cEw1Pk8_fIl<4f>TBp*ihRaM$hy^ZQ8+mciKV##{QeLE#cE&TERzOwSo!vT7mIu zTR6P34fNjM29`v(hj!CD!0>&j!!p$4{kAaf)0Pl)vlYD9t_8fdsU^6rY6|r~Z3gG} zG=+tun?RUP3pkB+!Tg{ZBy4O6i+o!^wJj~-qdhI*_qMH}VRQ?q*rWxty4?)k$2yk2 zq$y13)&wH&H-=TG8^Mt)jlp-Q0rrF$Am>aYc;k!4@Y83F;b}a#;#4E}Ii)GQU)>A* z3!1_cflXk0P!qTs*#x4{=Rsj3i2kuLj9k_Tdj7Wo#G(D~-wj~)-3BmtbA9Oja|5V6 zv>yEVRs%TuVFP&WVtwe3$Cf_d02=l(z{+Y3VTNNv7@gJ#R#$Hb%OebMeP$y#Iotq8 z*Vl(10uAu`i}fLBYkfHTSp&#eULVf>cI$yx)Azf9oTo!6CCpD z!^L}b;Q5~QU`Cy~aJxr6SQAhWQp4&&yD|0PNSAt00l5yD>VbQBJ-Gf&1Na<$GDGS? zl5<^nJF*_6{agpW!?AVwmvx};R!_+Ky$-xLu{PX7e$UReq1O>l@LK8#FQ(Ln&+YxK zR5|AfyLNfPlvSQEEwMJ#+~^5Qde?^GJ3V1t<67`R&)VP^UK>u{@qqTuwcumyjXirH z|FiD!37(trvj-&J_W;+wJm9tZwV)&7q*1jY=RFT-ky{%o-td5@&@bq352%(?8*2UG z0pZgMDhCA=N!&jerfFqXGW0xE3zv=<+ zopgt!r`=)OyB^SVw>v~;yTQ%P?%=o09eQnYhkZxfAtu2M1{`#U$!FZ*n~m-;=D0ij zwaXo9Y{$Qw9+3Q`J7gVphx<>tL%TKZ(D-`~nD`%e*n8Il=A*xBrCMOjcZYZJ_^DJk z$T;N=zo1?7ng_f;+zkRS?E{k=oLKG-FN|`7*9N=6j=pZtCejU_AL$08Lfv4{FgN(C zp)0h({low_Xja=5PRw_Q>p^aijy?r$uJFl3H#pVJ4RXg}niMx!lI0Gw!`+avI`{Da>e>k6CCf~R2 z+@KNWf74YLsEBzgz_iox8fUAz!mHoZgkhK$)8{qe)o*J;@B1}j!CNlS0QnZ;wZFpj zzvJ=thg=~4lbSI2qzk-u)CE>utqHf6I75xEYr@ByU0}jN7f3ws0t2_ZK$G`fAmM0D zaL#jvCYZ;{S8BrHr(B@jJ{OqvqzmLC*PxGU!e=L3-~r~>^JY!BhsU zfes(lgcs1>#cRKa=_{Odfl$Ov@qC9%Sa!VD*2i7o;%*nXy~73iqjfpug7w4&mg4^9 z6)w;>*BPG2yncxL$CtanSIN%sVxbG?CK1TI>R8iO%rmI!rgx8Tu@6 zf#JwoKgt<;;x)(2bb;l8PSAP23oPsJ4C974!xFS_4|j%D9h~4M`hA{@} zipR5qoMB(2GdSXYi-FGYSCkXD499YJc7lUpsM#zRcpJ;`NrW?mAxDcGXXxGwkHtE} z^`XwtJKY&p4|awJy_{jz7-x7E%YO~ejYxBWTZlvOnpKg1!+2--x~DU=#_Obxb%x<1 zoMDu|GZ-*0SMWNmF#Tg4o!~#=&M=`VrZ+ml_1?~Kr;{`Et?2~gkYiOtCz#p98QOb0 z!L0x+3m#AD>I^>BonTKpCvf+2hPP@t!NS^3kkuOVNotopS^p9aq0j_Hi`oM4~76Xb?C!@WlM*VPGL$NYHu zIl<)an0F6nc%mix^ucs>F%Ouwu8T9wL(Z|kID)ZzUc_J{&Iwxm`}GK9Kp3Bmgz@F__Kx+%tF5i7bo}{&!7Fy5k7Zw zg5DU{pWmthRVrZoaR2gsM_66S2{u%Af{u7T2>pVpIzdPsXYj*2jA>Y=d}k*(bJG!K z+;fDdkn6(Fj_~qNj*x+izz1y^f8@&}Hv4)@pK zv3bwcfEDOh?cEyC>xLs_BIo;G)qoFPafH7=afEiy*MOYMHQ<>K9pUm-M_B%04fub& z-38Q_)z&upMLh9;9zl>W2ni{XMmhzNkg`xg36YkNRzguir9>1IX+=P#Boqv!8>9q5 zBm|^EBqY!8mc76Cea|@G_|7@sd(Ix?-gm7v=QXc+&AHb7hrRjHx%dyB8$ak&a=476 zF1VO;EIFiKhr0vGA^XOpaP??%Sj6`GtIffC>3zvz=E|hd%KOB><$=#@Yz#aI_vWOq z2&T63F!qp`_9lf_w9`DEaKdM>&6Ml~*h5f%JhidCWQ(cl0&=2N#V%Mr0}{}3NK6w zr4}TG4d&m*)+;lTLS{#sPvjV`jPkj1YEpP|UQ(Ficw<{qD7XNAv1DiCS9XiQI(1G` z$n=%?>7*w=%VxvsT+6%u=8gV3DU9El9E#3Q3b|ZAjjNGAB!@4SCxw1{lfy1^cYm7{ zx^7Gk-FLzV!_l>J$0!9aOiv0iF+K8OQn-t)-sYBhHx^2NloaZYfrs6Zqw!^4 z31hZTO9~$t-;RxMhs45ow%`6BDdhMrDKva1Dg5N!(9e>>0(SR%K8~Mf+#hqi;Fv1@ z&Fpq(XVW-*jr0HCwIVr8$6vAc@y6d&Iw~_kEMt>Gk3~u0LGi!u-Es4umz%F)IsZj0 zoal=q{&I`so8kPySKqwh@5REQ*ONjeF%kU&E8E4 zbKF<$9}B5rKLYO*=M9}=VaJH1kbY<^JjPCkUa@fSwWRO>40m|<6g!`=oznHDk>VPX z6!N|t3$@HoF$}+hW1;<<_-GRg-_yd2ZzP4DBg}*Mc6K|kbF*_SOvCF?m{0YMg|uw! zcrPjR8W;lJxF{w+w^)YJTxFF>@)TyoV@i)EFAAmSLT;8?mc`Z zb&Q1q#$*;(!#=SvtgGCZm*D>BOR>55bB$x+wx+Q#@SRxr<>{nQ5B9tG{P88c8{3A>3}U<7KPh}%NB-G+8&5fn zS<@gXjKRnK&0=9zJGy!@780M0g{OPK*GP|fEEcXl6AKN*xe?X@Rbyd^`)0V9E#@c7 zCxx1PbTGG17r8fo;^VP!4Bz{VdylO%FUCR<-1m^Ln*67SwS@7hou7C z8EovHCupUKx_>nmsz2wzO^f>K$Gh(x$IU(Wta^rN=ku|!&$C}~u-n+W;%~|BM1B{E z>DiL8Fxt36c>GA6d{-(K7ShtZsMyXh96)^_gG@+g{L*O^|-o;;T1N^*QC1& ziD7>Y8i;b9m7aRyrcx|q5l8<9v|2wFmKKeL=f#?q&GO=3#s2adiD5neyX)#74HCnW zV&cY2eX&jUet0#p!8cd)+4ddx{=(}iiOkcq-3|aBL};?#5U2K%ASA7{?WRz zP}KMi?B!%Hi+P{HSl)e4`Rd_Wy!)Qc-?F#Od8+5fVP0hJaK{d||K_(|{lsw6eQTUO zP(%)mpIado9mDwnZ53%3w{osm;p{@8g*NcTI>A4&}Gz;(4mV)*9XSeTFdLgtOljt90%$<-treUM!&eBHrT zCfxKXNAJn8aG!TA`A!sf-F%55mz+)!b6$8CsH>#3vG7Nw#PC@LI>W~)Iw@o98TuH* z@0AA=!xuT!4bC#d*zjgzxR{3??x80fOcd{;Qi)+)JgoPMA@?mDkLHSnYUZ5Gl^BNH zBY*N#`wqOMPYg$JSl_&A1rozrF`pC5r+ihHqpzbl`RFBJ zF=m0YFS~=_`2DHGusfChi_3fNqUXxyWTGd&Td~noyd`mUPoBi^G(Rtz`wjk=yRYPa zuY700_xjw4VH+FeadAd$75ST6i0v30FlU6Rvg_x)t7c4QdD;q3+7Jt0!0}4XSon#r z%dTU$S?@9@hLP10!(i`6iaVv}NonLz9;%h0%|8;uSA1{Jm>6EoiU)jcO*F>b*E7%= zoQoVcFDHgInG(YS;~o%St2+|IQpcz4>@#*cZW7tgkYs)1vphdJ<#yoF#IX3z#4rVi zU#5tKI>y}e{-T`sxsR^c+{pg~d}k2%1lZaeb6D=T(at=vboPAs6@ApW+5B{q*HXq* zWjBE~-noz%M&R)#{%@LhK~9^}!jEuI6~jt?2477K%lRGS*(b(TXEy=H&hTHklo*=b zofuxqk{G(j(Nm<^+vzB+obkEkZ$ABKUzyJj#T;RNl8x4B5<@@7i=G`2TZdR;c*y-| zHX3@C)Aeui+4ewUXo9zLDdgI_m9VBglNde^iU0J0uj1KBC10?1g{ez|L%cK1PX$9U zaSn0Ld?O*W5JwAG-@cR(9`k;XdcPZYA10}_)QRDHev69tl$dg|KQlQoG>A!IIgsb~$n7sec{a3h{aXlgY z!T&=y)QCBAjOl0m0T_}}CWd=)H5JFN{|2x70=T(q?jh&<@X(&m{`nI_^&<(Pu=_o1 zt~o&$a4ljZ<&}g`^$eWTO za-aX>lMlt%2<}|?=$6l%0|}uN{)Zgl!@GX`RmW?xcxJ*;nV*hl5<(BpcX-wt-?{m0 zBzIL#CxkojR8KDMx|tBNTuTV8VBChQNselKUt<4&{O^Tr8V!y(ln`p1N(h+?&@Vf2 zVor^Va>i7K`D-?QriGXITP@D{#!M2^CHB8Dx1BhzdsY>{34bJniFkX?xTn0I;<`3m zc}^yTp~u|gWdPd~GAD$Y`x3%$e08LuJ>t1(?8}D}!k26f6z?*o`+d%j!~G4P*IZw}9v{A?$=P_j z=zWy8cE2TrlyDVLc^KBm zC4|}DMVz1K?-Fcr>_v4|$=p}*yo%4F#?EleOpy>qv3J?C@?v`9DBjG??|wQir`?$l z8sfMTt{%oor{f9Xv>ZRkPKDd$cdu*X8o(C$dc^Yw{)!JX_`cKg4DM_Enh*}+e;p1R zi*=SfMEO1*>H3a@uoRA|p5JNA4gP;Tp$BA22zAUW%3lZfU0~SZI{Nzrc~jzYH#_E4 zhqJT#8X4KhpAddonGlA%enAX-&CkkLE%Eoto)C_)-v-_X_^!t096T)%`;U3x`Ys`S z!1v4Y8)>YMabFZl2$Odugm>L9W#@VEbfn$wJ8@WD9j@XRt|EN3HZDEP`C**J?|M2q z^Giaw*R!pA5<(g_>WMKm9{)_s#y0lgTx?#7SVDNnyCe9x-+g={&9ZS`To=tPn2RRA zp%cE&!TzTl57@-+2Cn-klY~1uN3m$TNHxGAp#k`V@Mb6b}C>K5D_&Yu{bpL}n5AIJ0^)uoD zmcE&vCz7`*rde+ywcro_DbR@_bh);u-}^L>|R%fD7{eAk!L;V^wb zL+#->@A(8VHBC=@p4aEQC!Ks?{*&@{?QYtgl@Jce>#z;#9dA2+mJj~l<|m#u8sK2? zvH0+;@u~T^eltEiBiDDs^o{FR%&SaKZN%3X#v-mqr#2UcL*h+k%#+5lN_O=nn6W$!G4#eDV7-U){x%vQ~UZ9O>J8RD8Jq zi@0!?J@Lc4;zETqNn!e!xbT>`H_zkui1=an$-Lgv=TIa{a~C_)z%Q_|R5fH^EwCll|7t`0$u`>wgyi z&zkXUN`3XHe*BrUybm}KA6A~EiB<9=*FUe14DwmINXiJ zI{rQy?5%&Y8`WX657mTx>|3E`F2{!y;#~wwGyIki?^rmeT!;^MFM-Xt#EjOJ*Ub45 z@AB}&7=7hLe7FY})0ws-^F1o>b~C${aY?$^cF8SSKzBSPT#~$Bl^goPJWr15Z=LQ+dc6i4<4_vh4+41tt4*~<@IGT zjC(I3B*B#ypNkHu1$l|r!>)VxwOoJ5&i9@_1atDoY6o9c)nRJ)SMR}fC;dkZd&Id! z+~?S?PHUUgb~C=BnjKFgX^c5P5jWy$YD{0e{W2^eETg+G@R?Oy$8p_;e(%KbnrZTD z?2LEmCF0Mx7_2G9Q5B9AY)_NZtD~)bzr}}=a$XyMhhRO8t5W(#ZrXV2clJFy$ai{t zevZo|{1)QlbH4tB>lJuM<9VyGseX+QpS@$9<0t-Ld}xomCOhTD*m^Y82%pp6&fzRE{#c_|71Q?So_Q;myzth`R7r>pFDeN}CW^()VZs=+<%?Aa9` z`n+XLl%v%2_5w_Kv*_1P==ZMc;q3U8_;5jtlg;lVuIA=V8%1N@U&Y1RZF(9k8=|=7 zW&vN%iEk6$n$y_=@qMcvN4Z|AryTY?vv_joQ4Pc!Z|;YE)EiFgi@gwCp5GfErWlij zes6lVoX;Dya1h=ORZ-?sk1_ojE5aa4P- z*gRhYdsGMgU|qOTE@^xt&PuJ)U*)JDJs&XUc9_=T=OmtgH}Bm6)+>1_MMpj5ac?)e z_?c$J*O{;4%jr*^N{A=Im4?rW?Rh26czj9sRFd+NVw5<)up z8N=O=W+jhV|`hEhvKaUy;L`U7Te#mJJg(hFm08Gm3W!EH9oY) z^-(;n!qm zJsr-Pa26HsQ_txk>_3a&r_Ddj&rmt7!0#cPc7)?aHq*jaW>b7PJ5X(~HAo&`Ztr`P zjdJFnqM3%YQ^34gj;D89tK_~XEi5;GiZQim^dhW%*T;u$IG+N?pSZ{*o{aK;&Yb>u zx_-kxus&NbMe+VcTm8hj(cH6Qd78d|V&lBA0p6o>@s+W?eq@WkS@J!rwY|GJ&#j3M zud!QEY&}=NV%~7E6zZ!#%TE#>R~vK9{JG-!4wjMT^@8m%Zr1aE3?CoKNngJE;^f^W z))T%lnZHE}|#T`s1`> zSKmH#Rs9?J@$6kTDyjYTFhub@BF2MD#op3-W$ror4nJ(Io39@=w7)zWA6|v~W5?$> z7%mU*@SUhOQnH^$jz*}P&1~-w|7h`~V0#KqMzfn+{5{2XvsFSk0pq)_Ps?$c@8iQ0 z@_J3ofACpAjFFGZFDHZ-*+_rk&*Pc7djd^dpSW&D)k z`=(sCn@+pNPWQYe9tz9(DLOe0M@Ia0VShIrm4*GlocOSd%@W2J(A!$GJ;R(*V%x&+ z4(Dd->@#`oUe-6Bc&5yyM}7z4{xxHdn!kdN)ZUeR#CNd!T6|Q4IS>1*U1!1JuWYsG zV1GCxJ}fn+2^)Lq^S!U)!#lA4_L=>g@wssI(q27D%~v*d5sv!fa-caE`K!tAXx9(V zX4}}gY^Q5te&i%1hLkeqSopC z9lna-Bo1%sXk#f(;`#pBxMJ)agttGw^2tMG_Fi=U`+aM=8r>q!4m7!t-PbDl&BeIJ z>Ff{K%fxPD@5X#+?*-@1$?8Jf-;HB)7VW|{g#BcG*0T2s>^ot4iSLSIV1cpr2lmk7 z9z_QYaa7j$G~$oeiMir048!cVZ>{55hRXr}W#Ikki20q|kD_7r7QQRT_C;^~92cg` z%VUli@-g}aeFCTL-?Nsny@}25#WX<<&cIN5ot_U{8OJYhJk8c>x|jujA>;ZR(+nT8 zO=Ha94|CC<7)N5?{l!`8KLjvrzp*Qi}$Q}`U(D{Z2gFzX`Zza zXHB?z^0$8h4YS$Lxg8tBVZ9`lS#TB-Lm_<4g!^kYe=u%<`8D8gLT3%f;|IQ>I6DYO zUg!F1sv6$j87TgZ^3hv7aDMcr^^>;SzIv-C-^COrs)?he`xCT&2(Ak7w1;bu=lR65A4eIyPXT{Uw$rh_4QEZ^jB0TyACu|e zl;<(`jo3*C^I(3~;VFyjhdf*8{)lm%%&UyEX6{?yvl06}rqaEbFT+&Q_!rIV$JR~f zm%T57hf^@U2k%z*!-uL_<7-}t^LH-k#r;F(mWs2M<8hgI&&zLuc~y;1#ZGr~&++}O zI2ytFIiK0Q``!H~#tnoqm)u1By!SRSf}yLd5taw9Ihh12il=F~#_NWQA^8)1(06Y*cr*gky!?*4Y?8**0L z_`UeLOwVu0!+?HzAblp3gk4`bdpRy-=c}1#U+|L|j%U_dYhJQ0@H1%uEQ|T%s|>rP zVO!$*+B^0-wEh6RHN|pL9CgH7;AULdyFveZorb=)X7jhz`#<4G`K6rWq0&5cK!e#L zF2q#9v$KxNIJ?WZ5*On_5zou9IZ17{V7H)nPmi$olAo;NoalLEBa1#!_F7yxYJNF9 zulDYCoRs!_o|s->^HKAs$$L?KY&`CN`6Vvo!o_ayyE!L_Wu52m!MYX~w~O&n&+DgG z6P^|9>JWQP&vVgQ9r1L<=TI?rhVub)8nQhjo&EWnYSpvjavN!Ex$8f?AM1H4^S%>9 zw8rkZ8W-aEY}j9nG&TT+1e$%9kCkFM#BY@Q&&_EkmOB#l6t#Z0*lyC;EZQhOfzH@( zF9(N=Y0Y*HerGzqj_Ln2(AKl}+1m?4_Q5d1G|2nG&gEXSw(v2Uwo2goVZQV8*P6}m z**wqJD9`i5Is7g86T^>ry&u3PyfbmIR1WvBKTO`HIkw%4A9;DbuR4aQHk<3&n)U{s zj33*bJe+>KXpNin*Slr?=-5jw^EiA{$X(y+IR)3tDe`^9xHmcA|V$ z!bjHWawUe>+1!x&*52uAFZ*Ns9y0DAzm?_X2|OpgVSkgtcL0C6*rI=nfEK$sV~;U#ut{OBK+2DZV&gFUW%vP z=H7f>{baU&inpC-b&MV9Jlotsr{ltE&&T$&&cV16zI<@})dA;s+|qwm&uYne%O1Dx zKRy;0PKvFN=daUIX?QQU;6G!0sPd})n0FuJ_)AB{R{G67zBS-Y;^#R&n>*hRbH6ij z;cGg6T|CXi(&6=6x!IFV|7dD|%x+WTJ{Lm)T)olVJ$@$9LryWAF}Dnz)u8v0uzbw_ z`#9e2Sp~lD;Af@$k14Egsr9>YIN97t<53MH@%hbTG*QC-Ma&!I=?m|(oBx)e0juCwv%-NmR~#4hB5cycxE5_xhH9= zKzw*x%%6Jp2;1`;!E`@e!S|Hs1I2i}t~DqhJ>c&sUrlgz55GUFw;aZQB#ya!l#_?? z>1n?8Ep3)E|Aw*IFU5tw$IFczH=_M0k9FbfZ)|<@-r*y)aal$f`>39qhY#`Z?F|E) zx$&|)8P8qRnEQ@6sbhR6<2FAjqnkXe#$&pA_JiUo<~qZ_ z^0&RSo)zIUeh9n!X@8#c06FP(A}$`UwpU zfa~fj`bkmy*0SncJO$Z!o9aNzx`gaF2OXJ?X<4f1lnp<0^5SK|b~fyme0{53QGpm9Y`#)T*0i*jEKFI(iYEB-ps<}2#y zSH5p|WIPb}&pze}V;}dfQ?j+9p~eUX9M-t_U3;kmg&(PSkkzT^4I`Zm)q0OA93M5c$?w7 zTWU4&oV|_bE5vi1zH(Ldz3%-)ezKNUt2uBc?&)w$YHDw7Px^t}<-~al9L^M5L%xdO z_uCFQsv=iKXqn&6c)Lf9PrTpSYV6S$)D|0!g1%+W?zicZ?Va{XH=9|9@e%c@;`Ujq z&Am?#NnzZV_pnvtRzA-0y+y3E|B4H*8Pmu8gLqHA4Q^blHa=R53mG#x11@lz3f6~c z^lNr%J!`ELOA4Iad%OEb?PGEAy=UEwpI_IS*j26Iqi7xbhD_=W#wNxLYejR`o2Yh9 z8~=+KN1TcaEsd`MdvCrj9f}M0$X`pxU1HxG(_i(C4?DtEmd@2qIk`A3ws|y=L##{9 ze@w0u@URH(PhoB0{1@AG%zec8ndYbV-8fooljLhW+o#gl_`FKiF9R=x2ePUUt*bNiU1ap5Z%=JQ>X?&_DM!!P8hTwJKP@K#@ZWM5p! z;Wy3?GFXT3RN!n}_=f+ITja}_lyk+~I4-RH)Hh1CxX@h8<5pW2>dR4aeWif!T|9sH zrFC+L=cVk4R?#mV>>TLZ<_EponD50<^BLpC+@+z~7x#OvdwF(qHx8G@g~np2+r)b5 z`Yhkg>h^hDg8rrB<|8i0@Y zkBE1?y&fJ$wYHwQFH_lXm|}_c*J)_AI1anOywv`D3QSAimM>#7q=V@ZeG9IgFn4ci zKQd5F(c2JwH^svy@w|@fjO?wakt^oCelRX>y71R*BfGPMIUrJ8Lph>&;2_t zeEgbx!Ic$XJ;k5c!XCohui!6KSkE@M6)V5OHm4x226&g9?V5U9 zjE-(kWiKG7OL2SCoFn{v^@{x$ju*(mxGFU3IwqI7arI{{`Qqn}nrbhF+BGj7UhcHF ztO zaoHfXK3rao#8kGB+A5$v__-|hkKk*??sPWaVeck>7Rkp?_AAf96%%{6GU^A8A!;<~ zL4ECZniqQ_&VI744Wj$Bu7|3bH(}1nc2$~umH&n8elD*)==K-9wXtXDSC&2ai`;9? za8$w7;c~FR`5qrn7;{BjS>&;C4qV@-53rvj(SE2VE#R_Sg8C_rYq>oyFVExuXMW$M z@A_<&7sF`w&eH66FcpNkd?|RS4C)Wclq4|5fj@`X-qo4_}(I)Y?|5mNin$nZ@v? zI$zFiERFoo*G&Ayr&IUU;g^ddci;_2Z;Pn|-oJpUF^=|Ey|u4s#BMYBeWn<#^njtJ z-;(6(d3=3f%oTbb3DXtx=Xt)L%|9PdzkDFSPo}l z*oNbD8M}AN;e+a^c|%<3KOMw-6o;qibEJGPrniUXYQDNTBlcx?scH5Pi?vl2vB^`! zVc8UPP|-fsn9aC(wXPmc6KCjW7P}Mhuocz;_-+4)*!0`n@ZFwLuPCmL#B-wpjj5BW zxIV3qS>QW9%f@IP0ue0AO2h7vy)usQPWHKY{=H*_3?8~2%akX|0hL|X;yRrsj8IWA;nE4^F~#`h@p|GJkgb&?XVHO%km z{e9|a`pUTQn|N+x`#c{}PxvMuuIV8q`)_U6%h}9f{5;nW^B>RmgInXmmvZ=tyv`~q zKeRGJAM1gixA=JTC%>VwUmo}GiSuDT3TD=0(ps~PT`31wX+Em0A2!$rcy^B-mdd;* z+0E?v+%5K5&G7fAJ-qdzC*LoYfm@!k%FP;h8n8cyX8xr6E)QFuVJ={7q@N~Y`VQ}( z(M(UZoDo0$*t;qIrR-szJW)-?%SU_9HskC& zxEG5v$@?rg=p3((^3ap#hky2W9F2Q|pBwNj@oW@6F5)fHL=Jm?&spdC;DG0wzkq^l>c=2>Y{F{ z!`wsMb4$R${=?>sWv3midv?Wzhm6fD-gEfNCXW~S&kkQ|=WKXgEq8r(*k{*gUr%YU z)_RZEvie%p2l0ciZ@jNX|9_a1NEZq4Re>XecZJ291Fu==Z1>W*P?(Kv@|TRm!u<7I zqYtqCtY`1a#kYL#`Bu)ntBRw~VS56$ziA^~P5kha9S0rR$t#x0@{zkNUe)oJ=C-DR z%I4Oi_bd3zZQKAn-^bP?IGgC%5%Hg253_UJb^8cfO&MWc78f?k)otFrB*t^}weJVt z-R8Y6-ecwPZ`@?@K1UxP-K}RO(yeEs%#Ckle+**=Sg)G@g1M(X8*F|THisD(;A_oq zd(Ud%aIq_D_+@buRG3LCyG_7U71ZP@0 z*yB80elpW)!3XpaTppvpX>ixVQ(=B~tAqSBkcl4Jcvh+n&C=0dc8%~_KDgqW$s*9gY!r7P=c1;64PitGr+w?uXx@$E8CqasB8E~(AeGL>FeF&5mxbj z@m*Xf3gapH*a+7~eWE)|Thz*va-M~am%JM&uB`I^q;>u?eh$d@n|P=#4_B?v&+tEA zo`>V{M|oU9vy&h3T}8{$elfK?9+8iF{GN_0{%YeT4)3WW`~Z^78At z(;|D>XxwrdGOvUfhpL-2)!{JiO+9lXTp!~9G4ae1!>*fZGn2kvG(IfVTMKxW{~P&> z_+#%9dpQbOU-tX|Ii{ziIeM=A@A%w1INrYz7fPo0-30GBn3CJl{7f9+>S=zb$?T&qrX7t94a?sm4br;^N;HxiRdFg3~dTPPvnNsv(e8A!IxpCq1 zR`yP6qntT8#rL`S@9_p;vQI1tk6n^mv0ui7EDNpxMtxY6{sU+s+)(+~U? zY^NWKcNRQb@3G&|_aC8|zH;z$7yJ52xNt5Zw$ETJ_n12Q!gv_lO^gdK;-Y~b{WmVx z(Eh>Pw2G(gF!tx~Fu#rS%iW*WJv}D(DfLAo@5tjJvD6;xccG5pm)HS5wk(rdxjoRL=RW0`}2+=s;~25!Cr^k#lXh`k98r{Jb8d@J$XQ{7i?L`Qg9#s6*`9H*CJ=8mBI zHF)2oHU^00Veuv$hzl>2u+Hcc@icl&9!md3L;Chd8~t8TMefCSK<*>%?zT>h*ZY#( zSFm@gdq19WpIiMzYfmfjX2J89>f}!6TJ!t{Mi-A;GcM-!?E}*?&*Id{2KYOu?X-Hu z8rUxzvxG+4izoWC%@wrV2i6Jrc}Xo*#O)Ruj{a=0!3p{)=UYOLeL+t4@|#y3oLXpa zMf)Xb=(^q%L{)8bJX=X9In@Q8?k*u97~)xsNufiIDy+g<#YplKW%kw?lC=Tit{Ws7m8^u-L+t& z23t?^u>yzD9`BlSRp*WR{vP8;eeF9^950INvhzgw%&3-gh^qn2(OTL?tt{93$K<*7 z=Q1B$*C(QQ;LB$0;`idh3_5uW_8h6=LjL}AAkQgli^JcyzBWBBJSE2k^`i0CmcMay z3cp={m(S<@CX4^qy4epEwf@e~N95-P8tJO9Ul)51W7EQsZMl9kke=J?&9t*Z-M)s0 zO_^y(TqT;QQ}xs}1~0CvSC$vLd`oSvf1UOR$A#Of>OVLsI?j6fs_PVTt?rK83G+U` z^WpK)w0_4B&&N2N!Pg&O#f9{=u?uIXaaxHMlC1&X$@!|>@{GGWxXe$l_0-d} z@>GG}y<)yo3`^A9QbZy(+_(AF%t&;LSu z#xK_|D)CzbhyAmPT}_@Y=Dj*;;rS;x`%4e12~TPqEfU`*aeksF&ZdLndhK@oV1V(> zaF&Go-}&z(u3~tYW?TXH>u~xFe~-}50DZnZ-Dbx1OY`+`@xG$Y-uHYOeP%{+@>F%5l^iH=MS&W3M^By36@aJp6&*Jf3fdEBbAFwOAYSok)+_#B{f@ znQ*@Pl)fjQY2s6UMHJVhEY zId8n(Z(VYk7r!a#c?!)%-%@qxX5kK;xPKkTFNp1J^-`*qbx~|l%oF7Db6UDeXN!yK z&Fbzi;}){>g_sWECYiQs;ykPVet^c>h&}AL7vO*RIkBd9FZKjE$u7Q%v~+BNK4RWF zxvUQJLEJ~bfp&B5uJ@nPm!jX`C-MJ%q<5U|!^;KZ3*_{hgct{^%jmb8gXWE)`4ad` zrqR{nJ$OWY(A!A7+-+<>*gEOq&(PrzV;`WS-gL0_KKnp@t;0QfI4-{8dmkM&g>i|R zuj9PgUOS_hYV(y2|5Me=uVQP7(|p(9k;hcW?f;T#TMj;u->Bdi{yeNOHo70m?=iL`|L4Wr3_nxF{HXoUK^O|Dh06Rc$9c&IX?m7#J)D0@qm$(B zas8pIxs~0QlA|ngRm1&AHZRM;7`D#ApH}@=(tEn7>!^O-fb~4RXVjy1>otGjAOhv4b}SM=wKBaD6C+_did%l&e?enBh^<#D;Z zwPWL)yheM+sP~-1>#zF3F8nW`n`UBKfYYj<`W>&jKWCArvvl?}Ty?ESE!g@XPJRyi zzEn?_ahO4zooVYDzopb>^!vtD=MU6P$>VyZTDdMK_seH-9OdBqn4Iku=UL~~Fh}cj zI(4wm`@hs^MYex%ybSM<#p(_I#%dy$F>l)+q*S*v#QpFgIMd0EdOGDYdqx7I&^0-d#tS)aW zX=x$equvm$X*1c&BaZVp-KG}a!$AVBv+%c7U#NIFF5JFfZ_^WYd)5WV!|=XFuHV4L z8TSRnmzn(u>}QsTob*;-?Ip-v9(B>&TKXiuzqOX!C9cl)ewpZD7LDJ8<6H6kB>oM% zaGKWdlg9ogM^CDQXzUU3trhzLSkuDu!C!IV7d3uH&W^!wSw1@Awg>xr=&yyowE~Yb z;pw5zeXM4GfH$iDo8~k%XPUg0rkf75b_C9;>SmbvGvu!@y`G$^XQ-!Z=8oHAot2w; zuD@ny9A7z&{oc7LE{DR>h>i~YZLP6J_rb-he0_zRJMftz!FR*2e*1&%0X17m-qWkM zE3|rr4rbD3F&NsJn+?yWjQyJJx%inNhkfP0!!&%#bz3;g@x39Gm; zy2IEk>|Mm$PWB(r&!*wwir!J|g4#1?gEb-gR;v#~4cs2*=UzAu%3EGqeD^4x<>Xu3 zM7*^Y_sg`A;&YtR`h@xRY6tbbWPMsrR>@7LiTX=T-({beW6U#R&7R~pKY18NAGK-v zH9j8~Lv7r?Kzr$pDeC$A-j~=G7ko(YJSgxJSw9ZM0a8{{rrh(0Cj3qkg(mexAoe zU%YM=TOF9%dY6+P*N8D%cLTpA7W%E$`q2cAKg7TErZrA2mx1>tn}4g*U<%tK`TrQkvFZKpqz1dl~ zU%=_*{j`anoO1FrOc~fdG8s4aHE~gFH2#_zy)J*JY4I4I8|vSQa=+Brp2oD$`&#bt z-Lr`<@P3HBigI0s&j)DWAvS&%TWvgVr`G|ny(_j4J#Wiyl)L=$Q%`=Ts@3e%?Zu5r zXMXvg?O$)F6XR##EjRr8@DLN*8^7yk@-hhC{`&NF=O6HXP~KmGX9!#ajNcMzip}Wz zqNQ3GkF!enihl1c3d0MYwZBedN8}#Aui@gCPsJSR74N0wHC^x>FRtIzNFzKQ!Ra`> zN9+C>IUY|V&!~@la?t5E>)$^0BM+~MXX|)+)~ox_!!R61*e7hcrQ`hYMeELUcsy#( zk_0`SCJ)0pnx9=_ZU<9w^*PTPFo~b&TYQN8r)9e%KBv)7?i>8!YlC_9&0m*F-ga6) zBc7}m`SG_XkM9oWX7UjAs3rVGe^0Bj7#qO8QZ8y(lUM3#b<8~$@4LbHj`+Tl?Sk}j zVVfG4`?NIh9KMUG&o#94JwA%-U6nTY9hHsyJnKwvWpF#vy8N6RM*EwSuos@{9)>~s zcOkz1!b!Xs8;WNZ9^RDKCHl+5o~7m|rCe;G>oH=kMo-PzI)dBG;yDiAY`*X3caD7a zmy3eV!I&>_HWTjc@MoGJ4%{@8gY@z?L~KzWzDc954?UnGj=V|LW zZWhSr_%k?Ji_fvX1J!Bk6V^&L@?P|tfps|hKp0Q@jpWp=p1CYR{hig<>8hq)(N#Wk zsgKrt_7Z2&&2Z7kV}HRSmb`NPt9{OoboC(K*WmAWy({{b`9lmdXuG^I@3I?x^S_U` zEO;m&2Vbyx<$(HtuOSR+Y5NI%DEfPz!*=;DQez2pSfAg4ba2I(^ueE8eh8%Qbzoa?c@motCvci^~296mw*!WX$&M@vQZ8uV*Z{Ykq&NkC) z2KxIJuRX7doBf(%*eljU=HDg%P38AX_1KU0KZLO<96cR{#q;;~_G@_i?N^+`QeUlo zE%z<2=<8zJii3Cgi1bzCv^t`L%5Xj@o_lGiEE_M2b3Oh`(@srsM8EI1#AOA%AAqx= zd@O_~ll*lzw=~`Ca6Y@;x0O6rH!k}7qwj9@+dBN~a6eH@uduUpi+J%}OmEsD-eUAJ zf=+tU_BcEz%j12Q{6@jwau~9!onfAJVxy5>(jNXj^c(ezl*jCY#4t!r{-JJ1u=l-j zgE#tf0QEN#uF0@g#rJvGHsk9EzUJX-$q_!yZ@|an>ZraR_ZqwXal9Khr*QrZ{s!S= z+y?tObLZ*Hwb>n^SG_5Q+3cK=!{RgW2ImZ1%@^OFH>`ndya7uYTG@xI^-O*b_JMCa?rYM^3%H!^o9QFtqBVD% zeM&q%J+220_>_LdaFL%4c$rUsC-4%j2d`N}7sBv|mCSKD~7nP4~uedhZKf(dS_N3&xr-#&Gx< z9cPidMBII;FTJG(+x&$Sy1!c39`RPQ5UF0It z%*W=;GUr-qe-0`4^KraGeB~$Gzsg@N@$NHcpX+GNevPJzoUo@ef3RoWVVwW5zgK|A zpJ1B;*DHMYm_Xlotbgp}RXf@AkK4?N>R~&qb>$(3i)f#peV6xQ+;40RdMiM0ZD5P? zaF)$~{`XGn?FC%_DzC@nx>_Pm*?o_nqjkzh|Sz%8_*MG}lIkh$$#w=&`8RL4Hn_CP8 z%xete!ST4(|1WsAUoPV4f3WK~Tx6x=_VOA1o%7DPifVMSew!0#=V)e~XSd;e6wYUg zWdPql$U`f++6nJAb2rHM68&o?UUuuBeb|@*`x-UegYPH))TejLk^0G_hkt;_Ic&T} zyQ$$=yZ6@GwZnBI*nXkIp>n=O>_5}haT?eLXKDHEFOKVSl26QiX{NJ2U&j2V$9!w> z*+d)@@v;ZUmDrkP&Q?7qs+V`ze~Zm%k2V%JmD2L%*~e$-1Ai&UTGtEOf3w+5eize1 z0WoJ5Q)axSV&kWjev@Or#{qvfdsMDqc~~67)nY;UoUA9G!QF@Z*gv5!@&73vd*Z3s z3VSO$p3i4CI^F%dJvPqz;r1!D8GTd5j9rVf(rRSpdix=|m`5x1aJGoQ?7!;a@YlzE z19R$%qdl81nwvr%%E9+Jp4W>r`ZLn#&&;ao-B;i&$xaWtenWh%bNllJd=00!Ddv>G zSH_d@>{n~>MZekIBljQCV)S=@XM0}~4^OML@7U;$=bWCMT1|)g-yg8N|1NLw^3%>V z+#SbTCG&rEj`s9Z;p)a#u4}%PXeavn3zcc*1P<%F--V07e(sz0-D}mE_e@9rl=nE(1 zroH&1Kf~^;zJ8Xk=)2-|*Uuc0gI}zv(Q~h3_j)U>?S;e#VtJL0A2mhrG!7h%dw_Ui1|K(!9+n4HUoH-ZO*eJ1I6!+Tb z>^}NFo`u&W+ImurXNdTMp}}Id^`1_yi|8Fc+ZPPOQyu4b*zIE87_q10>!{qe)7!qo z&8929)qeDCOAj6F1rq5f`sP_Eo;$^R-wNwCeVh~TwhQWelbV|t7v5&8Exx~l^9=sy z7+al2W*XbU*pvLe2~!$&d$3j1`=8kTl#kV7TzA;_#UA}1hSJ97S!Au#1EO3`!PA3V z?2k`dFXVTrxW0igD}OJU-^|#5p@f~S7)q20z z!*-Y2jQ*^(3_sV^!y5Hc#r|x)~O5KdsU!JgDH+ z!;&}jJw5q^IZ=LU;HAnCYYKZkUyYQ3xC=IT) z=EHLWFVTK%F?~gA#!^R}L9idT9?DZK@0-Fo0Je*C^ZTh=Ttg0W zz}eWmGI-mKulM=uioX}-eMq$Ki|rVmhtbPq{;ueCU9QunJanCEuS1jB)ppq(@bOcI z@44b^v)%r292@kTEI&2jE!q(5U()EVlxA^Z(f2(#I zN8``n=!kJ$VOdYhSH(ESm_{_WNDO)L*Gr!rZGK<(tE>;(ahXC+tDn|)--r7T{d&K( znV(B?F&c(FjxDa2(dE|f?Zf}lV?4|CwKYzRm5jZn&Z6)2@;I1^r!DH~yHE6<^K`h* zzC>KFuyGZqRp6VdAE(3P@j3QZxY?~{R?1@`am~Y7wLRt^)Q51>QH@6XjVd^~il^pm z+$rAG`dy6O4`5j?2lK=g{T|zi&%^W+{e7dHu&m-|g#50=>t*wz->a*M_is74pC)2* z&=fZd)Z0O}E3h~9Z+mYVt3p%Him^3~e9M1hTz<*VU*2uMBnQTiH@=aza3W5l-<6~9 zvGiddwMl zT&uU?c_uwPN}sdE5%qzJKlt`w@q$-aeenR+?DMC&lhM`t$pUk7YD;6ppWqf1RD^@4H6-dz@V3?elQe;_$8Sj+C42)m$$g zjRIe81?rF^(cWE3hB^o?1dZ#o=|Q_q`9= zD|miot6cKYir*tg?XSHXB~Q)wdgk3D;yt!s|MGm6xfOQXZ|G;y_ue8l%fi~<^DV|q zP=^OUWaAv}jlZ4EdIwzNYbRd2im$FXFS(9>b8JWBQQ!H5CJVrMC(a*}vz+)l`WI&`la?Xzw={!KfUh^o?r4l#aHSX_eE*$R5VVF)`P7G3?=o7Q{p}( zmIk!5+55X_X)#|jKep#Fra3(6VeZTKH*ikzyaQ~>bpM9&wPM{nqyOZg`Eqwpa|f8(>WIcOx><&2#Yl z!hcP5(Oo=uo0myEyBZSwHE z>$g1XgZBy%k9e3#E9pMi0dkHLp2Wp9aui|lz@u$QvlKr1uk3OQeiY1jiAIH7qEVQ18cES@@UOmGKN;+ zZI9!R@w3UCyXmdmIy!eB6T`Dx>|5WKD;k;Ny2N{QXZ_wX#eRjYGY5>5tLAe4&>VZB zKO8W8$M*Ic^eE>ya9hvXvK@vR#@%2m0k$8^8;_fe8?7($__RDeiV+w;+= zfB)O_om~4DLjPs&j{jt@<%?Zkc)4xoZf!etyk*wEX&?Q2?YEBT-iPc)pRe2hVW-PKcK&O;Pd;At>BpOPZ2r>!&3JG7 zPxfAReJg^0F8<4ZMSMoF;Ji4{Cl+C zw)y`|FH4vH|E-ta;`#5?%d7v``|sCFR6zfs7ylp7>ZEJi)*YL7_+R&zC};o9Z{Po7 z=l^}5>i3VG|Ghqy>ObM?@A`kPJO56qw_E<{E>ZveuYKxYkN+7HSxM!6^elaj%Kz## zRYJ++Q1WKv(0~5t{c82bTefcX`CFqy`0wslSs4CzK3;SEA9?*h=i?S5(Hiuhas4}9 zkN=0A*Z;BeU-R+5vrZ29$KHRxj^Btt|0^s1T>jtquN04j@Sw@@|%Qa~;w3Tb@1oYzX%>4Izi)h1UMMR}1qks*7CLXcxi==hqzN9eW+m zSfdMgUci{*nrTVL9%MfJP6!VU3E>ylO%>t2h`1PJtoH2Sun-EHdvPef+@JOQ4V$YpO+$DLN4uXF zKko9Hm&I|sZ3s`8U&4ag)3~$lOM3pU9NcAGeSGhi&)M?vs$-esS?{8po$zi!lMrgL z-OQ2KG1|NSa{H!nIUWt+in$x)eO|2)x;mGki|F|c*R4GJ-TU452~g;P30pqho=0N$xKjztR0U=e*{vrOkVt z@Aqt!F?F10IyWpUKIfZd;B~#M6yMHgOW?~npJSb8jiYzQJi}if*KavGnb*kqSb6z( zUCq08#@2No+3D)IU``I_;>B=TQY`Gg>?rHLh@)U(c)h<-G=%P+|K zQPZ)&nCSjn$3-@eIsf3O&&Fxzf9$*7ZT{bd^jX)b9)O$uHw)SqI9GGr>scn}9iBCC zj*bhie=x6#>-CQ4JkHU}vnQSBId5}L&*lo}ERLIej5IH;>p{+I+*c|^_Fr_K?b*YwlkdA_=WHJMJfGuvO6PZShmhU*LC0b5B1~;OTkkrP>kG~Woo6^_ zbdIn*;Jng(UFR>IyL;Bc^-|}^);G?RJii0J9&9!6>`~{^o;P-`>3)FoRbyUr?&+xL zc*c>^QPVNfQ7eZY=6o<0o?OSwOXEHob6ZZDabD=%9On-mP1tMc+{ls3@v$SaU(6B3 zT*x_!D~kC+$5C?!J2!B|vv7CHzDEL&(9kC@_(_N0eh@_r`vLJs7=@+D~_EeIJiTht$|9pfU`n zyn=S&vEQLV=v}lXdIe33>Q8INkA=M#dlxjI>^+Ili0CBhdSg$8kd%|7Z>XDzEM*G80u$6L(cs#cQ_4es~8DLgi&LngS*S=t%kN;m^k*RA15g;Ly;Qk7<35PbO zd=7ga>Ubx)uO?cXx;AJ`m;}`!8We(`VHk|2jbm4aFO2mAS^&aAL&yh7;S7D2K+n?V zU+gs4AFvN#m&bP8vDo3!>*&V7HT@n(xePi9jf=l~xpo2jC~U zIF5Z0O~$p=XkxS#+7b2sH(dV;&#?aj$L>e@KDPcmL7i{=K%110QeKT zA9@5n!Y0at(P;Q{5W8t?o`Kw3o4TX1h$rK>Iq_6|wukuaFoP zLr+)$-ZK!^LRRPi?I1n4{_*JVJmZYUK23i`(Ry$mCPO~h3Qf3Xzk6Xg#Dlp|2|7bx z=ng-_kN8v>HI5(S1O+_>{Yyc85ws5Ehi8oYg=@vBKaV!0E<5(0*b}f1a=kI-Pn2_0 z&Vrp6{T@w$wua@jsfE6xjpLTVeoI{^G$zES&ao51d+O_=g)CEF6SW`5*Prl^6F$<; zSS6x90h&EfACt2E6h-yp9PMs1u6?e+z6)uw52Ml1$LJy{Ddcwc(5A8Rgd*BS*hh5YiK$pU9@W;5<=qWH4_M(pa28_!hbQReDar7R9 zU=EQeheE@me^ci@VXzBPR~TErzo1iKCpZW5Z~`h%U@JtT{YZ3Cz_0b#S79ya%NiIK zuqUGSC7t0ZWP!plI15pIb&fs2zIVYAu+QPpo_34T#_$d*gL7&K^TGIK0Ozs==7Diq z2A9COY^9H7_-gKW-FV-D(J%>YXPwt?1j+-^Y20UA^m9L~gK^LRwsEZ>YJcvhG%%k! zuML4Qkb|;5Ij5}H#%w(FgS?cD(G*nwo1;~WlqFj^D3U>8B_gKe^-Cm<7cQ#1o= ze(C=R?1*R&7zHUQ&qf`mJGS#k0EYwZ9HT3CBh+|xf|-GKb+KvQLc_gLVKd6&`M})v>4hD{T7|Qf?WnXDH;zo7MArl8gvWTMX{4$zu}$~ z*x9fnp?w0{5IYmLYP<4i1&D%uhkJ^nX(`u&dolmJcMo;RDCa=m(xwHre#S?mq4tpo z{SmE>o}`U=c?*qAo$<^IPbhzdrh#^4Q0LZxYx>$7y-N8y7~@cs|Hh7hZBD(!PKf;j zRHJ-}au9YH_#Up~uRcVDBoGUJp^bgVr2TR1-S8ddr>HrzFQB%+%Kg@-$JWm{sO_Iq z9}(Mef1_L)4GN6!T3e@|#_%HQK6M`9(ZA3R++PzrB;2F?2W8uw!On*sq`V2+G47+* ze};Y3k3nC+RCpAyKcU8cD7ph4zzNz;jLJEM51&-7-=c0Mb^`39*uAkY)7F@G2iJWy zx(yu9^%)JuVjOw{oX;`tdxe^J+9R>Az(!aE`@y-{hxSc)4Ekk!&w^`Yymw;%4*$S2 zcn&APyfTg-!1X^2ui-SD0{!s1F*P6TLm%&gc{Udw;G6Mt?#}4|%!g6%1@vtmZH%XJ zy#Uo=JUG`ET*T8$*P&Te!GymGd zW$JpO=GZcLOSvuTy37M}ZVz?FuoD=oRg`;J56-OxB#-uAKhv?#fw6Tx`N#18^`jk% zSd>M7<$iq}0IgvcOKo7xk?270t^az>~j$qeBJE6VN zgHQ|G^*n^yULV_H8z9R$D;1>R_J&r zk39)3jaEeaKy&P>sB>tE8gK9K48^eLp^oMH4?)wRuB$$%v(W{pargphv6rER(F15w zG%srId4A}Z<2v77sC!lQOmMEo+O^crUf3Jabm$iJ33<5)bv~7;8;F|UMbK$b6uU0k z7uDZ<;C1Kv0*-Nib+iKXp}q&Y6dqFF9W@`!i;ieSsE%#>ZBQQc!`L*2+Sta_@w_h^ zD2F`=O%9>xJ1XS@*n2@A8=_sI5%v_c4!A#E7w6&{G(abSIkz4i0A;Z^p<}@O zK8>b^HQ@T2ckU1UGT*afPei9fKQOObpiSW<_D`s5Vq5dpF^%ass7g5}>bS!oA!U6w z_SWSF*I)!12HJx6c->eghSg98I>2kc(4s#$qZC#_nfjVQ?m^%9@3ijigb3Q-%$Swx?|g~*Ub~> znhab6=W6~K^9tY`6G2q^ac<7z6Me)+Z=miO`_>2NTL&V5eb`rHxI()usO#mLIv@QE zffBtLyWA5F)%Ta!wuufk;4tNOw6{<9=Rcrre8Yl$di?|Sm%um~lh}{2cpl_yJoVT2mh~V4Jr|!2Tc7CKh&laNNvLllrvi2CkVq zr_m=c8ao5m_FxyoHm=vP9d|YL3s7_4HE|!_36vc>4rRwI2*$`cJEt{Vn~41mGEuGy zYq7Uce*rarT|0fAM_E7JCta}31^X~2ZK%@^4P~D z2HSCtP_9o|Ukk%d$^$7MLf?aH(TcKjHon`jU7HZJmEM%^Q#OuXpGw)ec($abt^{T0 zWIv;^U!dm7ZL}k0*X=5{xm=m@W^8>lrrv8j%~NytN3=F|;Zg64hHdUU*U)Hn$`7zz zbMwGC)}ihZwtL(-mZ$8VeFnxpGv)75bHX*$hh^CRLJn-t0An452FJm%%;`Tk8_jQH zn2)xuwQJA;+x}gvPS{&e$83u2dg@z5Y-42KujnTw_qs;T&%8JY(Xl;mT)$PQvG6|Q zJs#3x8w-7R54c9|sl3?k<0$A!%9o)Fw&!09>>=25Q1d1=wtl!S&cS>#e*LkRV!OsS z(R7quC-={6G(Yu^QP;?`$^GQG_T~ECU{0PH?$N5$yWZwNUF^N6KDjp5{fz48VKARu zFU#4me?^^R8f#)dMvrjgfvD`~0Z>8XMziTwdan=YsQ}PB{~5u6oWj z#6E>yjl{b#Kcm#7ZR?2tyH`JV2$Tf4+vs1rZ-&53$hiW{VVo+xuueOZs zx|u)5Y@7GO66)Ni#w{alE>pLjy13Y%c@A!&Y%GS{Izv0p z-Z<2!p_~CWQ#Y47_l5Z|1$D0&kM5{@RX@Gv8SoDb19QI$S_93BZbZ#v*JK?SN844x zHdlVao{IgBdUGKe<(Ft#bTXO%J%PH{JXaE;MIiyUb1aTs2u+2$Zsz`0D2V+%Ov85X zS>F~dkG^Fr`*gm$Q1iPq>e$942%Uu%LfvyepxaUR_X6}P_cud_Qm%&@clT5l)Uj(q z1?;2fC)zqjB+BNEKCMJu+aYLJ)O%bbY`^Uo5SYXL+swD?TyI?%AQlL zu*bqh%8p~3a%iVO8)K3f`){s0-UD&~-8v7a7tTWc0!OgK9Zyg$+y6f2HhHOWhw(%%RIW@NPaP5rOdDQ*lI`-h2^VUv5U2e)FvE5t0qievk zV;N<0a6jb1HrMZh=R*z3p5LLd{{`3IXOMa8`LEyMust{Ofa4ek&kOfQH*Cj$3i=b9 zI_GbmdxqF1E_P1Te%oW~i#eGedpLEMu&wuuIS8JM?q~DKy_N>sHFoR**b&i>lxt%f z_acsX3N?UW`0O;RZ7)zb|VyT-1m`Qci)p5JlZ z+|phG#v^zx=H6{Ehho#lm>VPGZGIS|`qUej5nx`pCv0!bZa_uuIV9M&cdb{T=C3~Z zT)d3lft}!4X}p}rbJz@j(8f3$j|r&jZtPmq)>s9{;25?!kb-*W=s7qS%t_B|`+9>K zC(9#I=WJZlQC9bsq)JZ2*{qo_UUAp1Wq2YoPA2fvD?J z3G~Nz8*{57#Nzrq^az+E)_e9i#%20=fZBgTR6mb`XUd<@m-;_Y=hX~`fVsQ~^h5uB zRvN!H;Qg*mCs+sL!F@OZtp?8D{`AK+X$uXF?(snG`8uo#{m`9!o&egu%Nld&Ia_2V>#6(+mb--vQ@aA1;9FZ2XLy_G{WrMU7=kxC_ojKee6D8R!G%@?6?` zUl%Y|1Ht=UQ}bar^oBg(zMTmtxu+d^0NR4>_0{vhF>AqjC=SNk_&n$5HRHGnjDu(Q zd)nuqemv?r8`r9+zP1OSS6jgS)E}&y2F}448jlgs2)rjMv2Kdm&Uo*Ij8G4bLpN|P zv0xqOpX<~E+)rIm^T7Oa?#^p8I7S2TtSABQp<|#=UN?RZ@O>zH2nvDMjZY?M1)fi? zx$$yL$KDKH(=T&z8-0{S8xoVJ*v8m+m-ZUiZa5SJ=Tj3L(|9xk*SrJhe{I+Sv7rgL zp2n#XbOifV&ACYM5{y-TSO&gRZp07Q-I&{dOk%W%pRHZPeoz*!)6PDMKtrew`ga*} zfqo2ws9+!6VJWnM@{k5rg1MIu>|+2V0`D_E2ca;yj>haK@O^j|{(0UV!YALAzNdZx zG>38U9k_PpigOaIQ}Nk|A83YDQ3)Bx}81df>l%r&nY!!F=lGD9peX2XQ%Dcat05Rmed`Ifbxn+QDlqouiTxXoKbV)-&5M}O862wx*v=g3 z4Q0S}&kg!xtO~$e^2BG8etQPgpg(uBCn$Z+$mD#=-G^0@t)yfQU7ah~R@R_pn z9}3yQT<|{Q<5>2iU%xTu8_eDP7#ns&2v`eyxIPH=e*OFr%s*r4d?JDIao)x}9hh4$ z=x+;ppZlG!enbT0)EC@?#;7V7FLO8v7?a760^ZPv@vR0k;4OVwcI}*p`(`{u0sVPJ zd;M_VxUTwQPF8`+@Hh8wLgRpIvzqqK(K!u(ypRR-%lqBqu1j|A)AtXk_vHs;Wgh8& za2(8w7*LpY*-+Od1GsmB@B52u*1Hy+;S)X>+w#yDLV`YbgsgCp_WjU?0q)b@Tri&2 z<%F)Fzv-a}REM5mpYCHks=sP<GbJ8;;As7Sq`#WOQ0yRJMe-gMiO2aVjxeTwtT&e^Oz}yK1$*DJf?@?ps z^?cx&krIqeGp_YUpQ6s+{n-VQLu435z4Nrsv|zrNdn;iZ*WBCDQT_5><7&)YJNplw z%X{WgnQ{nl-SpQvyB1MVe%}7m2wgEDB1$-!+dB3oV+D9L%4_U|v^%Q;?ss`zr|M zP?pOD)d%759WA_`H z8FjAqpBkKJgFx9mfvAaN5 zZ1-VmSWdYKxCdi^?NU;<-w5Ek{_kv~t}wQ1Y3ysD@zHRo^EMWfDIWpjY}q+x#LkCi z32>Bhw!roBUWe}}KLqz@74YYV)!^FHg4bSyFJOE9GA<=4XG7D1@%Wyyd89wa(Y$R& zozG+EPzrtm$E$-rK)vr9YVP|CaIMYbE7)Veb+j+@unzVGaNn47?vG@Yjbkcs-u4#< zb^Y|yePXO0vxoKL&p;dFvza>gv9SpO#ke*I8e-eOXK#792FAhpJm(%`ZtV2wXRdXC zw%B2zGQ^<#3hd_+>RFf^+x(FW^kKd_w)uFP>(8hwj;+7G6B!fpLEp>=*Ui`)-@VvH z!9L2uNo?O~%+YROZe678`uBkQT;B;jz_{y^`@wTnzugD3u$`-8xK81PvT<9pBn2N z*#Cg%b2-Wj(Kg_|*O&9Ced>E+aQ`L;$M78SjD1KvnxMX$c{b+<&qA;1qx&^FwmE+s z+jj!j#(74i?7rF$`jH7Hf;n^>bsw5Pk5JFALf{^K26hwE4`tcowed(-+FlWj{I<9R}xUKa`(kd!ro# zbjUO&V}N)&~3iKtIOP9Q2$jOT9VeKA6fi&qVXkoNf%Ev0Zb0eGKj; z_e59JHH?paoBKRBJX_oY=EDZAX+K9jf1JO2*Lkf3pG}@WnXylRYw9_l9gczP`xqE;yK4MqJo{hTJ=1)z^p0(q#J?l@Sb>RSLCxMjIyAO?paWXef2l_Ln z?t9N!<8=%4*>=u7XF&f#UFWx`@zdYyls)I1oA;Ot#xWsG1!J`ejReNacr4}~+hu~V z)E@%R%l6=Udk(qA=BCdjZR28_7L*&HlfgCeT1mJB`jrt}Yv)lMeudBAJpQ3S_m*pv z4y_ID8`n4*_$={0`?~|i#QFBdc0YDP`+~WpkM2#^@E)XxXW-gL4cK1$1@gf=mArJr?j3W%bMYhfu8HqsLs0X>HT5iM1P37*yas(SM$KUv zln2`<=3akh*@wLbGJ$jTxmp<=4CZY?bP2oy^{-&%Z#n<-o1ovT+eXb+%b+pfqL`9IH#h{@r+kfSP#CB8Se*R`&#H~Fvf-9 z5S)Zz&6ga2qpdYSj6?h6S z;V^x;S8if|#kTKI*aN`#qnc<2cm$pmp0n;teR3}wOMf2y8}+R4cNsntmQyxv`r#S4 zn|kNqIQn%JZa{P}xAvjyXwwqgb96Sg{<^QTf#-zln589(68dz<371Imk+z z1n4?&p02Msa1YES<7N(cmib)rUe6@Qajrf?jJ-MZJ$wY$-Zga&QQ$q8Q^#p*JD-0~ zu-*SZQtk%!agRFZ>KY%zc0U}02;g2a-&{A(Q_s@N*mvP~>*YPUXeLiocY+tUqYiUlHx8}RCG?&(58}mHS0{q#| zwR(*GCwSdG@B98Q=qT{IXVV|(RLbs2eR6Ei==PMIzvuE%>=B@U=I0gaZlSg@#;&=~ zH~Y8_?xQoP_qIaKNzXm&%=cfg|3DuEbUU{5F%GlgCpZtuVG3A(jq5%SieWoP_niJ- zLfyaa1#=}AxbOWL%717175gptF6(?;!{L<8SKlo>Z)Z_>5mQdkG(mitEk3WED;85m3ROpJ%uccSLyW^DcQom$_Icnx-8`wqAc z{Xp5h;XS@fnOp8V&jaUa{LHOwlt02&Y-4YoeHokE*cZ@N;Ct#7bUtc-&e7lHyN{-! z#=|`k8yw?j>dc=#sQ&5yQtW4_b>3$jJU<+F9BM4gg$>xo&vVK(HAjqtaoK|HGkyhX zew)9`(be!9_7>Ei&t2;_*sizv>-y-!M$|Rm1b46xp@-33sN=f7^w*eQNA-CHs^9Jp z+mA$zg?V7#uK#3g_p|$UA8OoOcklD(E%&_n=(sDvIekXWSM%H0`8;?0O{nWK79zu8 z_=~#1=r;5f>bQ>OesW(F&nM4LW8gXM?;Fe`een$YhIU8apylBQ zcmh5LPNBE5DsEJNTT?1p|I{I8SXU(ZeXed|* z=FMX2R-v|aj>(}FxVL^oJHRV!*Jv)f7@L_ovqqtxDEo6}N7S|HifvxEMcp&LSCm0-Kv`_}#R9Y+WzRSF zi+SYOyUD0SwPv2bqY znEA08jMW{qI{F+nXU3sRz;^Ba(s8 z`iOE_@GNu9s|9o*dW>>AbRKG7?jzq5@}aKx4AfY?LxWK3(qo&$El|gD%ogY@bUd2K zHeejg&v;M*+QVVmn7__(K3WWYfeuEUljlTjbRDe3HYVorE3^@+pXP|;#sug6nQ~UN z7K8(H(46`i^~`9A`Yg~l*LW-HnmC5*Fb8$d#6^8BI*-|?=Z0(C8+Bj0R~>gLx)gQI znxXE`25^nCW2A!B*xoY&9fNvay9doVbD}AF5p14o#*R^tAnBP57&#D6GW7Koc zGiM^WcIN&tbRX(?GtrZ%zBvC5Xb2btncypRQBmhP5*)iCIuZR9?T1c79sdw&E^GQ4Foq{@saT<+IMw_6{ z#X8rfKY9WkjynHZsMpQsVgYrnZ&4nO>c2h=LGPmOZ`aLrbpOmo&5_M$E%Yv03N^pO zf@7Ei1JHt~`_!{GEWh*gXAbl71a^9U4_XG>+;R_8rj2{^2>3bg{j@Qk&9hn9-MDu@ z<@%J(!OqxEz%yVt*HWSW&LAPz(qb1xOTr;+eKM~c$MF(ldnOrs-yf4;55ZoCx-Wef ziA34^z0WziW*M>1KojgQXbChwx*T<#)}y|^xDKUJ+x11)qI=M|5C+U=W9U0|Rn+sn zaiBa2EkfD2+3#HR4QehnL|dS)(Nxr&>xG&>l~A9xNze_j4BL3=^JsKBYOWqYE1|v7 zfoNTH32Hw+&pZdsjW%c^aBWkAZJMI1P;=OQ+Ymex+%x8^>hs@y?XxdSK#QQRZ6?&c zRR(>F=0?pkW8_&mAI*=NukK&>ctup-eU>aoorC*m9BK>)pxx2EXlv9sg$CE{D|N=h zu@-~5vjc69n%5)HztIwC7u5dxpgtolyH9>b7oo19Yjp*Ug8qr>e=D>D`DXUw+% zj#Y;`-ye#i8_|4d47iAG4w~Puuydg1Wo&p%*|oLL6sR$b2(G`m{Rg;)At4^Tpqw1_ zOlTZX{kRO~qi3M?^H5{$T)Ut?8(N|VQFBKBCZp!7xzYo5&aRpJ!M$$z2CA?BqTx~3 z)fjILXh(DtR6s9`eL4BM-QOI(4A-p)b%uMz3XjU zBA~{}c&hsAvwZ>T+2q*6Q2R8;C!t+Y*TLtSd)2eA4C?u`5H;6=*LE>w&y48kc61o( zy85ikgX+8e4M)wZ;PrFv6Dj|Qu0W@wGf{KLI69X*sPoN=ZCp~K(^1#NXIxLzv5KQU zJM>Xs%c4HVoYOJX^Ct%y8BGVfuuG$rQ2jK<6VZIAF?Wrcq2`7D_C`CQ<$d7f57Ju~#HC2IWJp~lU1aUbeW zQFIG>8NSChhK}oAv5&myUes6*LOYzxzrdIuh1}G6-n$NE(a~sQ)OB<{^~*l;qs}!MdKdMa9fY=r zDcC-b?cZ1yMxDzX)V+`pH4Y_EWBwG3$zI4oU42wPY-8-!qQ>DIYA!{?ZipHK_nK=~ z0bPS;N9Us@(MG6y)b;Q_+nD2((J`oNq;EY@&(&X0*XcX#>u4C%xcNM^jXCRn(I?j+ z3pyTc4x_L=d*-6KQFBtggl0lpq1{pU>_*f%>6iU9M$LtIsQc7&!S&O(MQA8AA{c*j z?{_dxj;&9PQO`8+ z`RRJML(TPYsJUaldCvTbniK9v$Mwu~4Hu*O;+%r>d=TZ7l=q_v(3xlxFt=x+K9dWe zozNC&MbxpKx4El7?!8T@Yp@IT*=6}QIDh-jOI@&kj%nVQQ||Y!*v_LfYK}Os%BXAN z8EZZ`r_rEaz0o14Ib_W1p~k#8>Kc{Db`1xhh0t+mDb%+8&^V~)&3$+d=FA}KI-~yX z!d&z$a1NfMV^Q<{GrR!zco)<*9neE)NHjEh3DwWK*w1-yK8DtzJO}NI`i`(2^w0H7 ziEcrigLzsT_3Uu$g^(1xHEOP!KRr#<>3aGs)sJS_h0&&HB{VH+ zK5PZgl}_j~)VQ=jo1w0oF?T%Iwkqm;T5=VUiKXlHMh)@a;WEz&xO+H zzpxDk!_U+i>!RpzG&6b?bq{%l8E<`SfI7F0=xQi{?RvT<-rog{kD7?-dbA8`Y$`%^ zY-8d+)F02Ub*S;Rt{s{lH8%D)2Q_E@fihrB%_;Y*_bi3CPz`cX=X&`Z?2hh(@Ypp` z_vJuTKi@$Ha6MC??n}>3*G_+WpgB?F>vMQFS__P|arI2?f(D_FP}>#9PK^3o^_qKN z9O`-+TYdJdG4?kh6t?T_Ud)G@zthlBP%}_B5UoMkz8j;ivwf97$D>8jbm$dSA1Yw` z9Q65?9W}R#p%1}0G(pYVW~k@H&**+Q39gCjW&Au_je9+?ubk*$)R_9d^b6`U%e-`6 z1yN&bZe$D8MMqOou7^6$RuCCGYoN{?7T2N(=xZ|6oOf+TfI04(XGP5i^*ZXjUE}g- z8q~4%-#u6pH71rPf!Cdr>)|-=%@wFIH=kYq?&x6D9EyWo4()`-L?45(aQ){|_lYyB z16qKxaTU)m_uO36d@w$aWv=!{&3X5O`^K}&^(%&E43zt#=3i9m@}tYp%&7TnoZSK@eycf)crFRd|&bRBt93qKvit7yXOjE59N0h?&lvVJ6~hlfbv7G`EO9>(mQP9 z{1r7fW|2!5W0eAqSt4_ewYKsy%(x)_F-%*P-o7$ zHc!EId;)dB_!&q2@;r2}y@u6bzc0acbnnLEy7_HPjqMl+etx59z>mEX}f1#n;+4wXd7^SJD|o-b?)Y^d(OROdt>f-^)u?Z*bH?~n?oy5_u+16 z3eL@QPX9)O{(0^cM=yZwjD0-nnxoFev0W4ME-mU@TVNY2&xn?&vGH7qivEP!-gs2S zzQP`?gm$Cc9Wr9O{^#L2Gz9&b4S6kdJsIjdNGfy{_&n2p*V;UBuervK`wWi23~;Yc zfxYlIiI4UMv`bod|W!UF&*a9?wThf;rI<-3RW2T&VlMGCB$# zf#+9r>K>x@v}3>~nIh3j_}oVRP^ysWzm#>0KT33YC+iS6Q{=2b1|haCxZ zUY_4e!M$%zOa}W&53cJRv@w*&HkO_XY0#-)4(ONXvwe?-YS^ylcz6J%+V_X${$E$%}g$*zroSSRp`q-y2*$Cd35j7sG(Mn*<+M%9d&eu3kz~0He za2`H;`l3g{zKy^4*k3UyiMtU1NW46$V#h_@Bd(2YU4LWcJiTsw-KVa- z`ymB&#?-!ipS54lmi1^_Xoc-wGzPE1{50nN?2sAt44a0Uf38(J)O9@v6~MK&ZVLGP zD}%lP$2T7OG6Rf@^VaW6U>uC4<2%2_5E0uvG^fmsdSF}^p`N?WuNhhwHD}C+FxUqI zwy`j#=Jr!40G^YL&`MAT+x#`R&Bd}%iaLEUZ_Rt#I$zf-J^B)!gKIyUx@xF>&BiW; zo`m<{Js06HjE5Jn9c-gtZP3l&+ND5^MHkfje6G4a#zWg2cfOIaC!=e@bu5jVbJJ0C zt~j=Pw=?*Ra6g;JJ<%~x0^1xhPWiD_^XMbEp2p6-Y<`x(u8HbX5$r0cb1~+gEyhm& zTxZ91%;u0Ddj#s-zkur#j=B}7?alvUXh?J?+62r)=j8d3274%49()c~M%@d0pe=aL z8V~&qkG&gpe#XH#+o$y%Q0LGR9SzRcytnUW*!nsTTu<-62T`fdf*yx=;M$t^#=I-o zpX=5gd^Xyj&mZ&A`g+*L*)_1=9xxR;Qt!Amz_Y+uIbJkuW2|l5jZahR+M#v8xEhnW zkP6$m=%ahBEO=I|N1baV?0RT!@c$Ax?nTbGoY?-XSDf+(+T6f)++CEf$Qdg_jvyy z>WuX;aDV4RjfKxY_o;dB8aua4P@3{4)U|XU`1{9K*qb3fw4|;fx)Lse=U!t7qJB8) z8R|M4w>RM0w*cRxUEiW$EL8n-Uyeun!E^8&@QgPfT*ph`nPL4za311w$NlI!n9G$> z_wpa$-ZodXi$Q+qOdDFAZ3!x`q8}x#|!1{d~aowGx`D||3k8^U&!H@@B_o86GzJr-B&NX-) z)?;6Rq~LjQ07`)IbPlz^eXQDt?_tK%z2Y;|`d;uD^kWHpgHDhSdZkkOn2P(X z4r-q_!RxueKC_{}!VX9YuIC774)*QdbF9>~wa(|1YuFfV09mmUp>yCfIA{0sQE(ld z*GaIw@iqUP^In(_#%nk12jkoY-hyMg$3KGW=X(FhSTRxC9Rv5QdHDju!9AD@72qzo z=1sv|(7#mRo|y^h;9p1!=8k?YhooRUc0ml-2lU902>Z zAFr8P#^7hD1jfmAHGWT_FF401kQ*Aqb(jW8z_ZFcvdy31I<|#-Fb3?`pRc2%!{9a4 z1?RpG>VR`I&s8T7Lxj0OAc2FBd|qHpf0 z8&Dilf;rs;bv&O}{=V=C_8M>>WrdeuoAeNeHpx+ax&``g+}1;7a9{iGkPz(;@1Y9( z1g^Dh_4N`&f#EO_7Qjo!_FW+(w(EW#QbSkR3P<5CZN8%EpoL|~3(nL0(I3}iFIeX~ zn(N0P4%`ICGuF=0xQgqv8nS`+Zh!(XAEtx(>YQC~*L@f0^GMLg`{10Xg7@Ztb1(?> zvou_TvS8go$OQVl0=h#%_yWetcpiX|Fa?~uF|qA-u%FSe3W8&L8{0fIHnH%t7Frd2 zw@Qnaf{&0DY-j(&pf>1_ajpi>p&qmZ=VhF}1K&Z6kNI>OhQknW{LA3FnMa4g*tG?7 zAwKFFX~#m1zd2}(8o+08{_j~^{j-})Z6c%Mv#Bs@-fRQ&(RFdobHNz7#!tb2_wZ+A_me(l!tQ{Ufq`Hj?O_s_8*!l{ zRG?jUG#>bKm+N5+_31Tznm=b@9i#{6a}1n=XOMm7geR~>13$>Z~P*sJq*)IajK-2J}9b#M;a@LhsX5@5}W&e(&;~!za^m?q}g$@+abwpK~Y= zvF7`)a~I}pD$brP&G}z}e5=CR*5f>DO}u*Xv(ixFH;*~)BA3sRUr))Ws3C$bR3j!~ zI9KsM7T*(^wIcJqL4VKxd;iXlKMfeSXd%wjIy@5wl203W2HfH~LLa#vkS9e#1VyO1bsTp&rW;UR~I?! zuXFDN)(SssFqd9w`27Ykh{gA)buzYLd7ee#_ewp;zpSi*-_^w$wEd2K7MeVVE1`JC z!L^X=Uw9OPJi_Ob1IT6Oy|pcNea`o0#Eo&X*XC#YTAU-**(2rn8NV3Uh@aoH-I$n8 zP0xA}>x$4n1-Z!g77s&1qSKO)v#iG*^ayLynKhb%zkB!|$j;z*P?m)LthL{{RfYBT zJ94(5{!M$oljSw|x9BsXXV8}D81lrw0q+07U(3Ax{mpXnD>Rf~zOA8Oc%EA@6Lvvg zcoU9z!6EFsXe;y@x*CQ<1Q<&U4?q~8=7&UJ=BH)kPVKo9)G|CI0X}72o#4CP%ls}iME0&Pz}mMUa&45B!M;% z8A8Dg*7rRefnQ+@41`rs13E!TXae;hE0lo@Pz(w}Jg5L^Aw498M34!hKuU-R@gWG_ zlTR<;EZl{oa1G|ccGwD2U;>cp|Eq6@&V(M&6MhcR799*tp&v8^wn$J-Xb+Xad&)y2 zs14Q?2J6y5DM$>NfNdAVmI_J*Y~`TuAqhl)_qZPx4Gke72wo$f1N?*j4Q~V3<{kD6 zu-y~*5GdQ%9kA_P_yf)YM?laicnBxp8XSZ3a2O84{s6ntT>)03J7F7W+jc8#gk`V^ z*1`r@2ipVXRp<&>2}=Xmb|I{RB`_c6!9th~6Jch6ai~oFe>xI-1Pl)_43(ZR1O~!j z;5hl8-7lb)`v>Z+JHg(4o{@Q_&pl{%xR#nZa9^)f#6J!12WUO$K%3afS$nirN`3}`RInfg`x**y zU!J7o8hVHJZE1T19zX~fLEG}lh$mFQPKgfTzE9i}1v?dXC+sG)jRDz`;3Is5TU>v_ z`PU3~!G35ArQri-;$o-(sX2#BqEn$d`2F1P6Oq4YHuNUf%g1Ld%HOb4Vkf7*KAMp7 z3EM;Gcsw7WJNA06J&eoq3Hum2nDP$r`@8*q>a=Jt2nCNhldo~#eOL&qD33$Cpdr!Z zXc4px&*696>vv0kpl%;$xZla@_urnS{6`GNLt9YxdsS0_-xqqBGv4nsoymPMX_Jw< zVbC7@ewNyh0sO81g@Py~eG}P|{^jQ-Vz0Gsx2KE;;9A&>7;S_dXbT8`PnVyOI z9R+^hlz%Vz8~D9O{_Weru#Pqz(5`giE>u^0S1_qWkXXh`aR z2j7PlQ@0iFV7G_P*sIWq=v36d{qFzS?TY&U?Y>3goJS9!qu?fXV{{fAgF%$jp{HRL zcCm=$6YB4$cSZQ`efc7FGr;%spQ)co{b=kh*d?)jAN1$F3E;c`G#CICAQ$8V+xspq zzWe>jeZE8bv;XDr|Go42JNMtHKLmAPHk5}Wu!*(}(FTwWnnN2H1IZvaq=85fo&Fv} zYU~y0@9-H8@jRb_Zh~5Hg>qwb7TOw34nJdOMoU61>{IX~b~ogSTuRzx#FY3wv; zD2R)F1Kx-IukVG}zC-*8&)^mHtI+9i7P|x59*$yfK<7b!xJ|h#x(cm==7Vf7gK~Ow zE?NX_jQ#;Ju#=&=;1MK&?(pzC&ODfn{R;gH?qdIoK7%3HXVA~!HSax$9)knm{jI25 zhpvUCup4qvw-TKUgP|Ga321kyh&>6d1>GPV<@{)6h=Of>PG|-}lsn?fgO2SuSVbbz){7K%Y(NCnxzcfVMW3FgqpdNeisg`EM- z1qmS}M1eGrA9BNI+UQ?bbT>SKNR&fEa(F@6cgF{C5pKd&=nS?$3bP>rtP9ws&?B%F zmP2k>4TWI`Y7zw@LZ76aUjRRw#DEJQRdu=`_1P>`UN9)2n z?1E@9*o~b7jRnWCGoi)7Yhj=z=$HO)eCmJ2TVZVjH;0yTkgg=+uN8f<@0{nS$ zAN(Fr$9{$V8lJ%?@LD|TAD~y^AY20Hd(GC|CwdVH|XU>0q7X)P~F8&qotLzskW9xBx|ADh!6< zun7i%{mg<9FcQ{)bZ=7!AFl57=fXGy;DC|I|LpLlbZwwIM&~LnW}k2H^dr!Ru9_4nz-V6*MPQgdC6=NP|xO$$-s0%hxefFy7_Q1-fg+@zcW)emDA z8?I6p7fk^9;5KD{PW5Nmc;Gy2W1opZpKnl(f+mFU5C`s4_YM5{^?S;ZAsU>d{1QHa zbF$6v5Rz*V!S?=q>(9JzAS`uP(6?ZlztCU7F(1GpI0v@b1Al^T_;pXvJFuVi@EZ2Q zU9hh$;63Z$Dfn}>eHz09pkMxcZ6EsVSoXUO{2h-zPlBB=4KBfX@aOJhFcOZy2^br& z7o%SDXLj%X1@u$@9AhGQ{W^G`bF&ZEaVqHl2yp)TJrs69cUTMiU>qEW@o*5<1XO?Y z{Sb5o$F%Kc@OJ?_zuXg6+M3H`vC0?dud=fJcLQphi*PQ$b=02I$2Im8M0zD4*;8!>e-fKJCSQdTW50}Af```#{fgJ(ce$E8!{iy96+xu~~S1&Nhze z7<*t9tbvuV4ZOAyHh|-(US9|Hy#O3{ZNT=rwtm@eKFkH{7Q(WC?KS&#Ovms(`&tM~ z!S=S90`tJ}^lduKf;r%vRp&Ajrot$241Myx6|fp?Ga35B67ZV7*`L?+aRS(n?d;n& z+QVT6*hU}6fnyDT;QK~nPXhZN3zofiVxVk&aKGcR9n1EPZ$HDp`<$wTi#6&y=@Aan=Y>yK@Ef_>S>`c8qeKK2UK1&`-7$Iu7w?*O*55AQXu_Tw1( zYrSP@4+B9z9M600-)r{YEnpkNfna~mTfE1)+HYsj5AU}h$FTe}SZ6<7zw|6dt8T!y?AVo|dq4}I#=~~5abD1$5?~+3U%%|X zBoqZ>W!Y<`!T!W}=(E?fgMHSYQedC@TO5pIG4Q@JfwJm#+j`CU*-qc|#j(5|Jf`F5 zkJt54^wDzg_?GS0zO2(O4Emry`l#BM*DQNa5wMM8c}+XGUZ3^Pdhc~SvCeqvzhmf+ z>tVZKKdiTn?Y&q39mBP=F4!N(^P2bBPTy3^1;KvugJW6e{Oq?7cwNm2)>-zt{@IV} zSdQx$UMmFpsoJmiIiBML`{_MJ!27%&jCHot=iuw+k^STW#~1s_2ez>se2?u4K{n7I zZLj5q;6Aj2<7*q|<`}lq56d~gYxb>rujARqwuQhp(l6A1zk}Wzj^`}okMQ5W4Gcp4 zjKtT7yn}tu`#eOB!aILtp5fr<4oXliAC31J^a@&%x?jP6hgtxCV5gw|BX&o0MGOYS zJ_6^lccLR=@-BfkL1&?9WAV<*wd!b5bOf}@TpWHLN4>s=x&`QYbRW7E4q$ghx4~iTnrIPp2pS$L zW2Z&+Cp>m`?1)emJ1UwTU60yV3G5y!)W(j1e&fCo__zc;44tt@qW#e+Xi0GX#;zpo zyTKRY^dn{G@d5i0`a606lF&9Y*Sex*&^(Y4yBXRRx?rb5S94DsG&c;UE+T4Q#^)ls z79D|leI<;-Zc5!2bR_sWqb2BYaLzxW&gT{D{1vWneFk-vAT;%vQQ1v>bTl^f!TyW3 zPvCFt_vi=ch3&sP?t;_U+tDcy1A72E9ZFzVLz_Vd@bfUK&~$JC`yjcu3(`{7_aoSC z(4*)%NJ#y9%CXQa;I*&R-vZ0dZ8`Qw7=ZmJ`WBjD_f_ESraGh&i(Q)tq+x{y<8#qka@2IZ@j#&^*0e-LXV9I`{d?sjw9Ugs& zKW88pFqVDLCNKaZ(bn&zp9xc792BQ+7wUIae~*0)eE{p>D)*N~ z6GK~w!gc%c-<0jse|!E3^}Dc3V_)OE3x$0P%}Y5Xw%;G@_pSR~!w;yp%@^#S(S}eR z{EqI$=ss8nAGp2;9S6I?K3>3J?0V48ddelxD$o`dQtpT*hXybO{2uYDXfenNB`6m_ z{oAR2zrbD2YX8lA0eTVULpkcF!b1(N2q^))xZD#N}D?Wj`>Y&+x3POfosRHy>BjDpl%*I2!_Bg*g#!V zv{rytsQ+H?=OD|2pYP8?xh(4EZT8mOy*`q|T@5Ee#q&Cinhd4R;=XI67SJC$pG9<)6882~?zs$CRDK_0I6 zMB9S>Rt3kNK)vP0P#pYRf$H4-47%euzIFb6ibSY>w&Pk|)H&$K4`7?u5EX3e|Bt!_ z#&0)u#%2VZg=OIXi;6(KpFi+3uo2NPZ~^-}`rnAgp=`YTeED^F2AkkF>irCbpJ_1m z<)AdwfXrO0kLH1N@FQf0R?re$vjUI?Dh4ze8WysGpJy0_-bCNRH@F3Umcq|F90k{M z4SE=M!!O`xAMe9mxCVcLpPdMeKhLlq2I~Bb#5ULuYr)S^Y=<4thxYBzLC^?VKp`j% zwV@OAf|RtYi57$$pxp*KLKkQc9bhEwrlXUjq@N=JMVIlMjv@=HCpb0dE{7?@3Jcpn2@Ut8V&{^PTO8%hy0G_~M zcnUWmBG>-+`3~%;*xqAoKTy5{yI>#8fyFQwhQJJ%1lwR8tb_&72?l_lMKPWm!}1vc zbR1>d+Q&wi3p?RfK#!tJ;0)NGWjO@r1Gej_FJaKL=xx{ykHLA}0q3+Fyfzk=g1*$? zo<69bgONI*|IMIHKu4lop%s*eIFK4{G6z53;^$soz+yNF58w=(hdppLz*F=w6z85F zp)?c*t>!l13V7+@1TqS+#o8z4z|_p8v4>{O4ZsJTKpzHLFIA8e>$g#UcrUeq8q!d@oUZ zQB~01Ky(#lMJLfn1dF_)mdGyhh%CZa)DevZ&!`t~L2ldx-)nS2@O@1$1>etfQ%n)J z#Ck#dM#1+^{URQV+v1&gBwmZ8#x6b;5+y_#Lt$wJk%>YiN30MtL@%SPk7wvsv0oe(djz&q z3vx40ik+e&LNMP&F;grT)5TJ;&A|7IT^95~@rRgUAG_a+Tmrkh#dg7*#C^Qrd&fe= zFryu$14R$P^L}Em7%C3O(EEL9Il;JAqMfKNd_^PCRxoz47%SF@al#?O#c$##fuC)} zK&?5e^hZIwjtXKKL+!-sx>z8{&k=D|tQOcJp4S9x+9fs#o_UDJ0w2x@^08S=5cFFs zh)Y+&8rzE?QBPD6_%mE<%`eR@yhTEhMr0BB1m7E1SkRtXOjdu!xr-VigD7C`%S&?# z9}!K&6V=o|iu8u`y|^u6=>DRV@5%d3#MFJL^s3-{>T>8lwa6yoi=-m4NGlSFB!cg& zOD;_z@TZa}Bbp2RM*Tz|u|s`JNuvu(Fs7J@E3%6?B9rhE#B{9sGv{+@R4KmWC+lDh zSHxDaO<=o*;Cuh}34TrkJtP?aOeD~_Sc14r(miW>Bz-6@2(DQ>F~+Bqg0){(8#y8V ztf_*iE2<0Db4JgrNm+X>QC09PuP829mrcA>zsgdcVTW}RbK=oc&n}7GhNV(+FiY@# zjpfBMFUU5LMh7*F^@O_xA#YDmPMvf5pHbdY) z_tOkZq?W+Wdcn`xOc4tO&x(q9f-(3{O?MZyL~lV1h)1AkC71^d70EUGGM?IF2=ue=H;Tl%P9(AlzDE=KsvTQ=AE$>%Anxm) z@5jUk#_&Cza9eFl#B;%zbZWz=Y+}3axlS#x?ntLdfxb~iVRK(W+E8FKP*f7$qMaCIAoiR+C5%px&JrkbW_{Qt_H6`p zK~3Qwu`Da4&hRU@V4ZGK;>G%J=ssMG62y3e7%!FzzBiTcGv)hNFNytPwrD6Oip^qy zm?L_LdSbZn6MQdgSHbs~elHw?xmF3rMu=PDq|r&zMdBwxAGDd^duH)@p`g8_Ah+L% zgW{MtAm$75N}lgX$@LaN--qU&`B(=&UKO#`_l5LVu}<(kzSQCqLCq0cVnVE9h$w~& zYTGA>FSUjZ=A@ok8#eBVPv&}nlsabqx2A2klzhYy$;D0GuMw>2gdkp<#1O$+Pl+R9 zshBFT$y!(=G4d7kB|mcoezz6G7vJ!eyoZ@Ka!Fi!ikX7>SQGR25xWF=$2N9b2=?t> zK_5TCGvatgbQFUGYoIQ+ilKrr%=Lqy?_@!4X=l#LVv@iY)`P!Y1Z%${E{oHGS|Znn z1pPLMc4EGmC8!VP$FJ!EoBc&`QBU*}#3Mi?6O5%TSg^jdVwk`d@ug<%f||rGwz`X? z0^7ulwd4>j1#6`*<5?5_P*dcqoVjMruHp(?) z3yVwoH<)Tt=8r3y315MqJjWinWRKEbSFlFfs)~miUqRYL)EBpOUq;$o;B!GyNMIkI zh$ZW_CM>&AVLhREvsOT34*#FC-Ry5l~UqYOtco*U`?gP2l-Y? z+Eg?UnFW6J7W7Rm@R?lWTReeXA5mR+2);j^xX_kYL>Qapb!`{Vb>CRpPM|M!-$EKF zu#rVD7te`b9)UkK1U8tHH3I(NrzP%M}yCm!yCaoCR`SlevTPoxqJ z1%C7r1QI`kRmuq6XP!Nl!(szOw z%@X)c+a0k&JQF7bKSOX|5NFoI^X~+8{9f!8CDfi#uwORmp8hd)9ZfiN{YtQwVS@d` zxGQ42V1E2!e)bbF$RY4KijMJ(Zp3v0$N z+I>U@ky79{`w9QDOLGg_Pw76BG^Ze*)B-h?P4IKe)DAVnxN8C%tn+(uLy%))v`*l2 z96jGBWgNAGzx#E6-aMx^mkZ(?Uu@O&MM12X8t_sFZ z5{xB=;o=98NcNKn;?H>2K~0eh*2f+`t!rwvmLM;jd7TC8q}DkT>k57zrHqul%y{aK zYjVL}r8dYRd$P75_TUYV-FjLbj=w>j)*t@<12pN68J@Z z?9?^u`6S&UsG&51Iq5@fu}2b!Mxwc(ZmWp4g86&}`*Vn3KF))VB9*8lI9plAN7?j~ z5{KBrQ)Cur)lSZ+KlU+aU|L1j}O}fwTBO!0py~( zpjOE(@uMGWAZA<>S7L(C)FQbh$HxTzpO@Yh*r0u_plzAxA;>v3+EeTjy9GZV#MwZ+ zdJFQ6z0u-F@v|5vmJ8;^7VYeP&ZcvMId=%=VolSJVRC`-FJ$n#&%VDmXKaiV*_4*kdi6U5uv>_E`H(@vFcd zb^Ttf5ajZ$bb;U;yDnU@BECIL8$QtIk+>|r7t|)}8!PaM{;dUT!I$2Gyc`nb1?7H` zV4j0wj<_m_CAlWQ#G2=B@{Rp~kDpEZWAlt0FgNS$X|BlyIU@GtccP$X)(GPCQs6gh zAQt%ji@BdGWzLOarMMsliDQDkJm+lSK1}QsAk~@5-?y$LC92A`ddvS*d7Q019!CIN4 zjo2po3;G-vgT;Q)TdWh*Qix#QVPb^XELw`Sf;d$Y3xuzrA2mWghKp8Wkk}=t7knUI z%?0%pAh5SVuwLx2t}fz$z#q;Cu3L!Zf}Gj~mFR#Z3FD8A6%L@-B5(M_xy~OEZhp zx=ttkNlKrrg69>aZi2as2>j|H@*2%4B_FJZaa=Fq$oXTnQPX#&$nuHB{Ng4|t^;>R;7 zJ`(4Tx-Kl`=dg(_>`zM}YUg{a&n2R}yGkNxwa!%HhQoAjra1DSmK|U-(aL z6GLj@oUXCQ-Xhl2#%IBL$jLD&dv3dw7)CXHuyaUlC8W$#OZrBNPppUfA^yjuMWy&a z9?0!kU9&FYYKQAfu&bh+etc|rW9v|sLoTvfvO^i72 zxQ-$M1-Z1OoClm6oE6k6bv9EFPn7jfH9A9jKoAT1vNqyJ8?Qs`)BS=xO&9nvTj1A1 zLHl|^f9hwVASYu4cDjn4qQBT6W{Cx&w-_M?h+cwyk1iJz1pY7|@meI_iY0;=%@eF` znIP`0ccWll=BMt*3F5I>u!hxweZ(1aQEV2R6`VV7L|nxywqT!dUrb8exM!`zp86qA z*pDUUnrG}~{GkrWPX_5xDf^hd*yY^Vr)%o+xRf)6J;*siOu45<=tnKbmoh(4C!8Vl zp>8;D*zeTf3u#j+HOzU%J$vW9l>JG6&Kzo!7_u(bM~zbt+_P`%2y9Sm#27!m+()d1 z^PSfc*1 zucrm}mI_{5SU-E0xXu;$&)jzf`+#bxlsE3)VeJkhggPpNWd+wwHPXxvx~sL zmV#&WAuh~=k3Ecbm5vtJpD7r(La-L%Jy=i++_P@h!I%)iGwwSI>@k0Us3Gt@hhR;4 z1kdq>_16=ODJNKa84)U~3Sw7F3>4KxcTrXlE859(ML~S(2-d{fz7x#LJe347C?r@H zz6=vR#aPio5GQII9 z12M*@MFKnQg=D74 zwnng5rW++@3q-J(Xq5fTy4MNz4L0YAePXGg|1LodFn3GwooHh2sqGP>v|zo&g1+RM z7}OSBL|ws{&Vu;h3-vToxboRn*E5B$z(zyCb84%Ds3K@9A&Q#&yiz|=N?@OSFt;T- z3g)L)$(1YT*v1F)zDf)f`vtYQRuKR70$;Za#uKMef|wF}^14#6hWSQkONjwGK#*7R z!g$)4lU(=-Vod!J2i8vxh#j?3Ok@|9a2HiYWH}c9FI7jL8oxp#_;KLZBVN&)HYn~~Xf4cA&_=_!kWDe}l z75Fn>U>85@2v;6x{$!`kqg?0CHZH4 zjRosvKi3!d(LiJu#KcFiX2y9K)QgrGI>ZkIeB_zT-zM)5bOHARbW# zc6eqJ)ClpaC{hb-5`X#<8)DHy5NGNpn*m?(kF~OfKv7ZL+6ioS6vPWVuDCG|F`}-T3F0zIFm{w+&k@g1K`pix#Gmp0qP`d{@R2_F zLoUcwUO^vfh}tF2JZ~kKud`s!`HF#p`t2dA2CfCwTr1Q*nfwaMd__xQ*`d^MkI*%_k}K!`ku)Vu?Sj zhc%NYYK9yWzfr=KzhGUnR$_)-@yVM`+ z$FJ0abrACef;eRm=|xuIVeW|w_xR0od?9|>MI*sl>Wcz`m}3tc%#}*uAAN~+B|(kR zpP1$s#24R*IkCllW5K=+6~wQ*7$gD(wMEQ$4I>|I#C#DZmJ0d?iHV|zm?Q=W`i&QU zqMM*kTQN-F5AAgY^RvJ3zosB}HbK3zE@DSpR#927X6mh?h$D#G8|9vu68m_9JWvOxWGQ^M_IR@VB9Fdx;hEgUtLhA5ns^GI*1Ex z`0rW=aiLEVkxj5xe9a)D3)X@%2XoRlm%t7=pdWSR%7abUN>(a|15nW$S;atJSXm~#pNUUWjt%bE_uRV#9F%EypZAw8K=R+nb{fH0q69?vb zC1qZ~ZUvE5WD%tVYod+0nUg;ErNof)i5&2ZIcVelOTV(~o|>W_3kv3Q#ep-8m_LwW zgSxySXg?xei(kYALHu4ySub;8hx}kSw;7Iey=k5BPG^z&d}`H9p;vUKfnzIg0<}oPOl-p1=+{XI{o$ z6^y?qeJEJ>V=1{}E^^2E*pK*6?P4FB^mmOx$+c@d{g{)yk{@b;JH#e+$Xjhe4)Tb)B9~|>Y=YQU7t~=1 zL2j|bJ|{-_kWWx2-hz3k6UMPF>WsYN8*{k)#Wrgo4)kY@>>kgkgfUcXUd zSyZ^`-gOoipH$AkWk} zv8N{E3UbSOu#HdDGIohA`VPHu06Y$UA2WYoZ?T zmFJ8}D5weY!+b#9<7-qwU8R+BR&lOk=bB)TaYk^aQKOt=X9Z`&CUHQl7uy7JI3!LB z#iFc&c=|4#(x z0ZLwp3vnhUv@t(>j5@<7#$m%{hnnJ=`SFK3+bQsa9N-_ZAO`qJURfu;u%D?r$`;Ay zD7D}%QVYgq5Y!d5!#*WW*tsNi?Fn+jp7}`-3w+!l)(Q69Qt`dmBUYJvuBV6vg1+-a zxY%dzS??0DTAUIq#dd-1U4rKhu|*JX#_kZzM_=MX zoE4nQTvLD8qXyV7=LL3{j~sGdf7CT+1^3hrXAiYRjgfoCVvBvs8t{i__)Z>K4}Rhk zZP=$CXk$&Rj~eEl=f?##Pu;Rt@SVCK*7!i(awbtX?3b6iX8m0AjB|zBqc+P4&Wo&~ ziQxRKAb1^MPY_S~QQNr%e&rI41?LoVaNgnzX9{N?J~JolV&4;2YV(R99$W)4;S8WI zcVb>tO%k3rZZxBe7$h#G1GedsqDMk!x~W9bX{#lAZ)cz!^z7nzG1#x6EkJLd-KItMw&c!s@eQu?!2Y;paY zpw6&M{J6$8@x%@>#7}DUgrJU2iDLra*pt)-`Nv0m;=Fg&B6%d<#E-S|jMow3!Ffe4 zsR7Qg@`BfwvVzwiSDlp6HTA;lR~5k?p#CgTSdf!?g0qX-!9IPMgSui~>WbH7S6%Up zYiyEBYJ*%64{||X*^|@)*VGhw;hGp-7yMn~Cv(kui77E4kHi)`)Wm5)eBTP%s8{Nc zvxwLcNBWQ-#{X!p=}SBP$uT*gW{EZS@dtnLgENLW;|F;p$IMBc(VzJkOD@SRb;2G% zIroVhb7$C@SdGQxJ%vW5nUUEgei359$xUvtZd;DWOwZIvP zulUBka-Hquf_|)>+^}YH%bHzh5;e&lA(pQGuJPoLnxijm#D;of9@a(tn3wgr<|a%9 z_24VMGX_7&H)HUPnnJ0CT!Q}ep)RnG-|P>3X77*#{3PD^M<0Bpo~U`G_O(_FJ=*2;5oP*AW2+UbWs*rJW|jk&SU z>p5p4wZQ8e=P}PX5Al_I&gpP-Pa9_@HF-%y2x^OdjE67JXZ#_LtdSfrj=T_S)fGM^W73y0L-d98Cn&w7M7of)F*`0~$bFJ`2@ zuE9ZJp=$rH?5NA{c7{gUarqVR3%|m8b_%2?_M-fyU-WVLHB8s>^;_eeekJ|FUO-@I zt1dyE!h%AA8KAHK6+eeZj`(AI|Gxa5|6B6wp*6bVI6~K7Bfldf?R>rbj*7JR_47Mg zcK^J8{=WQP|6B5#UMqCvcZ{yTMt;Xe+WC6<9T#cu>*sg8?EZ6p<42yS&ObO8|5NAl zoxeT56SUgIk*B8Kqwq`7_;MZOrrp{2k3U_n`y))hb%TE&@Q+SkTt%427v}zTKi=>1 zwQaX~A#>jT^Pk3y753Nm{(0d4td_5_ufEfg{_%SKzyG>8G1A`G-&d0&L;vU3#lLG` zJ^owvm8aI|IyWcl+O@B2{Y;(yUaq`*`uAU@>-g5{+95QsR!C?NZ$o&m#FyG@bnW=h zk6it$hXh2v6ViQm|DK&ff`TL8W-+c+NY~&nroT}z>(-%xP7`549Rq#CLc;tzI6HJ~RX_kZ~eNZ8`bp<)dQz>{rfEO3hdzD zDbzXR7jcP9(UwqCSKSePDTr&trXKXfuX*Y^Z9mc6x${$b~jhQ4h7($JsUe>dc6FITNr+0q5_{n6MLe*OOZOQZkh`R~TRd|poW3;u)pI`U>uD|bp{##!6ytGEw>)vc#e~tGwb0Y10y}vKajkNdm-`C8O-T!lc zUx;Kp8X6Ir^Xe z^fUdU^Gl%r;;V=0cgy{$NL^W2fxz9`QC#0Tu&)nFij}TY?8^@X^k1?3;Y

z13cslFfhRsv`_q&l_|H^)+kWl?=<{!WG-|+q6OaH~-AHH0#*O4D)=t=21wl0DG z0WJMowYPO|8yIZU-?D9yf6cbF4(bvZVCxSMIH(UL~(P90#G`_e|s7%gkG zoKpw5w7j{mV4hbrTFJD#w6bZhV%l9=)wEYL?Jliu+H08hKWk0%yp~ffqXYh=;pX10 zw(0jL9boRm|D`n znXzZot+^RzG~9LH!rAWTYx)}vH){ErcBA1&-Ta;Hen#gTb!+Ktw~P)j8e!Dd%K3bN z(Qu=-0Ml+X!l*6K**@QBgi))tvpw9Xt&JIP)UB=a-p^>bQCpDdXVk5oxi=bN)UUm> zJ>00RgSj_qbu{-ztzdI+)CzIl&o`R5lk?us=zOCQMs1y)&pR1iXVk5Wv)wW}!RSe& zwoqf&=sKfrVP>4s2}UDa?OmPECm4+|ny;I)eSpz$qxrf!+dCOOY1G!k^f!9asI8}I zHyUBo>g8;oZ`7@~^WM*BxKUdlXL~23CyiQt&3L1U`cgMjMTGKA&zh!f3WJ&UQbe>x@1zYK?V1A7S*Q(Zu7-IHSvqJ~5hayz_Yv zqbH3fpWtjSWHi9&5TmP%UNGwRowI*7qm_(?8J%u4+-QW+_!FJu@)>PrbcoSaM$a1k zU^M+C=eTl4I~bjAbequ#qlqV*^%(UtI>zWGqgRZ^p5pAE%c!5x5k_|zjWC*cs_P2}%7@c7BfYG}~)6a1BuVggL=zOC` zjlM8yo9XOd%V-~?^NpS~`oU=DfbB*pWde-OzqcImc?WHwZ$Y?dAt&R3FI>G2F zqX&&f7=2+h=_1RGuN+3p8Es*-o6!kIR~QX9dco)`qlp(g?PW1q#%MF6-Hc8!y2NO> z(Q8Iu7)|=U(_Sv46^!~B?PYYb(KSX78@*xlgVB^rj6I`Oj0PC(V|1#~bw-aEy$?b}~B3=n|vhMz0usV>ICkr=2`TD;W(i+Q;Zrqg#xgHhSM^?3GSCnT(b*8ep`a z(Wyqa7(H$DfzkM@j2)xpjQSbvV|2RF4MvX`y>B$;YNwqHMoSrOVzi6ViAGl#J!tfX z(Wq;jc07z0Gupst7o+2hE-|{#=nbQvjHX@dv{%$cUMn4#hx6WxN zozd(@y^WSPTF0oL(Jn>@7#(kPmeJ)#cNjfv^qkRKMxPt~Y&5}or$6b9<}g~+XnCV` zjrtqyWVD~r(MD$(U21fj(E~<*HhRDI-t6=v zmC;N_3mPqFw2o1Kqn(WQH#))SJfmxj?l$_9(F;cJ8GU6m))uE9$&F?{bbez#SMpqf# zY4nKEi$;Gn`r2sBZB9Q@8qI99kkPV6>ltljw2RS!Mkg4ZXLOCxoko8$dd28HqwkEy z+3xfswb5)wy^WSL+R$igqg{;-H9FDgBBL9O?lpSKXoS&+Mn4&ix5Md2TBA9P`WUTb zw7$_GqdklcH#*hmBBPs)9x!^w=uM-~j7Hh%oIkNqPouewmM~h~XcMEsMtd6_WpuXD z%*Z)P>P3MhhD)XSAMCf1}-v4l_E%=pv(=j2B&y0RH zns~R<51Y~4MoSp2VYIo?5TkvJjxjpN=nA8|jQ(WwveElS-x-ay$Hc*CHlyA~D;jNJ zw6)RhMu!`nYIKRw%|;IxJ!|wAqfd=Sb(nfE>SZ*)QTzYCUQY0!%Rxe=NGAqm92+j(m+O<;X6b+nrxz`kmOoW})f{QG zz8Pvxq}?az;wzfost&IgT}wvo2~^^$*c>Et2zZ1tpz4-T1LRT{S3 z?RaTv&(67gic0gnDU>y@^yjSG_h*rM)qCFBL)yCF@Wm;m*$aK1n@~Epc)T$&rK5b~ zTc3XLvf^d8FMlP?eZOeN$I|$@ip;(%P4uC~+Z)n+x$jT9B3<8ljr%$2gk>wIp5Xps zk^DbOcf5-6VxRQqOZT(wmaZ8->gZPKn`k{xZIDjwx9{R=sXf~LW6PuqCQV+kNE$7Z zefV6ddymamW=Q>)e(XI(IxkJaDHEit&Ukx{m4?o^_1#G6if+?>9xAQdzSjML(ht6I zKKGTrj@x=^FKMUok7sx1`Lzol!=$G@XZGwOZP_W?`4DOJlY>)sl)4qnUbCIF?`Weq=Rakm?5T1ao6-5tHTwDOL4K24=PyF5G8 zSeo`-)@_ZXUKNkKqiqLA{l1~Jd7Mep8%S4P%Jj0H)Hll1C3U1Ft4>H#OIpBx{H^Lz zpHhpnSCclpv-?~XX|`$YyH%FPoUx{JMQPcBGm4j!PXEPz)sm)czwK~IY3FlOpA?gN z$Nzb7QE6z(+QCJnzg92wsh~8~HS0<~Y5njUDRN8g4Wy-j1|;axC9QNqM1>+?HY1ly0-B5F>gu7cVF25s?=vp(YoiQZe8{bIVGK# z<96bs(z|ckc0MQ#Dvf=})wgmL4A~iLcaad5((>q&XY3 z?OjuP_ZMH^3er@^k`E~%{pDGsf(4{0L)+)hD$Q{6MaT5g#g#+5CXp^rGA|;QG-1?V z-oD>2KY}V{c`Ciu@OGKo(q@ZiF1jGS{Vw;DpQO`sO&aEqcJ(>ZdZTn;mVLvPN{2TX zWSb>jb!YMGu~NtS_00xI>lbjV*I61|d80=w>BElWj@Ofx4v9EWL0Z|-*S)Cp`r77& zb4aHI722L!T4mDg=CP&Li^`K;@AI;fc{h)7N4l@+xsB(fFIu$!{-88rjdG4HQlDMD zFE5pzTHGSaROy{pSLzOyUhm$fQYUHZ`G+4hmma^DpiWh(UyAKdi%JJ|PLVH*)XO`; ztfbPZLHBch+N*sMd-aXG(noGH?wpnuOZ;%p9;s)s=l-jt=NF|IGEJJd`j#w%q%B&N zc-&sPb#~Rx^`#Xq-?>vlntep4L7Ao1JCs_JK>2NPX5w&%etPbAYF6yRFhoN#oMD#O)AY;yKUpw4&}dD z`NEf_DX-nmut(Z)N4=)=r2*qcEgmGT)#yf)K-isN91?@?*m=R-nPN=wwaFn^SEL67TY+Dhx(jajdL8-sNS@ zxSwp)9%<^;m7Y$Ow*6e)r<3&gPkv)6NsoSa{eeyDHKfSnM>`dlLnj*?k=963e9Z#s z=T?25cat7Yntwo5X}LysuX#up1f*L3Vu#N8Se5JlDE)ZlLhK`zkJ4W z(ref6S4knA)91k3+uOC@Q@vfeMVfs52>;>ItkrfWYb1SRPnSHKG;zWEi63oKzN0xB z?vZ*-shw`Lbg_N>s)o|+_m7s$B#k}2-r*-(wJ(~M%(`28Yj)pJBcwC8-Y-^9nl8c6 zFn4L_iRforKE>jOfDZ&nk!oBh@+dmtP6GhGfb8q z%Q?12HEFYzHwq+^cHJ}X(Ya09XBR_f%#`LWs)S0@jIR35Ce3o?q|dF5Ue?Ls`}QrC zhV3a>I6%6<)}~fQX~7BmYTekN*PS>YN-U7RUGw^suheIqUO1%-$1FX2dA;iR(A<*K zq(y>zAFVCT++pOBkL$dwZj0xo*ey-}>6T9i>7I%e2WOTh&9-Xp)wN#M_Qt7_j+XlL z)rWghx2wN|zg(kzT5Hb671BOG9~t5+y%ko;mRx${JNNKItG%q9l@{dcBRw@EQLEh2 zyS*OtxVp;A>M(A1w$ajxtAE~KLYn^0<*W}@dRe`zEiN=mTBv;Hw3Vd+M{?bHy~4}- zVa$}0Yoy0(tQ^}w+AikO7g43%e_obr?Q$>c>AAU|8cCzpczomIGR5oO%Rwuo@dKx| zs4Zf-miJ8;)zAt2mY0wcaTO+w7zv5>B@9I z>6b3lI@;~2QcC*d`zP}+E>OMrrheC1dbDJ4oVe2W*0bXa=IdM2=B7QHQCfLlnuc3vcv%M}6fmcI_vq7mB!=y)x}KOE+)teWdp= z#ihfz)HjAIKbd1y%p@(mB&FMoA=(#*j`U0{ZMt+oyDo#Ze`?(S>GUA^GrUcl0@5AN zKHZu;Q11iQ>`VWAfR~kEb&cb-q-%;a30mFX%bGSOcKg`UuIoOX@bBkko%tb3&CPwa zE;qkniKW{HK(P3BzEq=ViSAJ9v65#gTWd{4c9jTwJpDd{%P zDY3@&(ChhjN3Sd0y{tpC7BtTx4end9Y@cp=pZ6-~o5NjI@5v+ll1t0^<_m5drtv2( z7hDpmeHS=><%2G& zYo9O7J?Dpxs>_p2e~BuM+jL=&y@QvPWT#vCZtcCS{()5{Y-y+SFV3Y~PlEJ1kaMV& zM;f*Ksbzs}^}5sf^!=G_RF74!oH*WEufKiU#f&QbHAdfYg#uLvx0AJP9iaDHO=72- z)=KZw);5iPu%*t)n{5j|_t&{pGC}H0(*C#dx2WN#*VVf}{?^^s%i1)+QDc4!FKfi7 zfIEkq>-DJJcZ(i3(|+9EdRlU6w^YYMN;cK|$7{<6^lqYarbUOuvm1L^=Soz(wX>0z zRkPRiHq4LGP zb@cvn$>hczctEn!^ z&TMnJs+W~NcI{SoswmD4cU62)S?BA#6=kAHs~?{k6u*-8^P{G#lU3Ay|5zzWnhILa zrMqNpeYP@MpWow6zgc?!U#DP`k9Nf~^`$CrOX)l~ zJFE8dl3v!hu$5zeE8%6Wn-*o?jpBO!4o~vvdND6+|Ly`IKl^xDG2CNk+ErBV-s;Z@hguiVIo0psirV>A7wdNKES698 zICVks(|Pnhq}GIj%W~^=Dl|M`MlPMh{U(I>%Bk1&C_mD7cX4zp}bl#OZ0M=-Rp~kYNgkH4|=mReLC&i9aXxYPowuC8+t!mms)X5 zJ7`4DRC>MsSS(BFl&Z%WvFayEA$ukJg`7#I^ZB<}O-gCeXYK%SKd+ue^;e*)D24?Y~O1YVM1p^_IK4zI$xtsdD_NIb-Sl z%h9_P_s3BFvxhz(8(r^LZ+AOeGMe6}_+%LJAd2jaZC_x9o9g1(f!$?4+pPFe0^hv& zXtPpw92Yw4gUz}arVlmW+pMQ?$6dMg)@B_V88_eXH#Td`+wBXozqVP8&OPw{@ukf= zcx3;a)-P<G}0uEw?O`ZDZ}&3aj(PWQOK+N_R)e=IxYmd*OD-rU{$jI=r+&FranDlbVwEj6>vinHWmjynS)p-Gwg0%mW^GzqqEgNE zHY<6>o~7rlwONG{#klZbwap4&eCKGcRhqBh+KU5L*sQmC6GuP1O!w#WUP!RiW_9oN zczg5jZC2)fwzKmW*{rTJUhe*7fz29yKX!53e4ADCVf?yH=PKSF?g^&O)_hZnmN-7s zX6@SjfHoed4(X-ezn>Dfg%ly5j*sMj-7amu(oY{Q^YHp^>Qt}Dw%*sM6oCRoRY*{rakr@Oo#VzXw{sPH81 zV8vr(@xeI<+N}0%reCVlU-`}dYuyfgZC2ckX`2n{t^0?CpU>=Rvpn()?!C6V;<@p1 zkG)-OR!ooM8?J`htdm|l*5B_e|9{P5yAq)Wie3ug?PP}gQ#pV~I7b+x@|+tjo< zKmVw1v+5^F*3-MH&8mIv(5dv5ZPtW|37UMWV6&QsB^dXxoXv`!r(c|(%Gj)59`x?H z#L{|tq^~ugl=j!L9TOUruvs~tm(QQKnBum?KL3KZ&6;P@7RMfs1a%z046Xow_vssl!Oslpdi|i~8df6+J z`lXKcThRSc!?ujCPoz}*n+%&Y zKDo_WU-q|YrIOmLqfxzQmrZ1|vYk(U_(6Qt(TG?+ljGW~S;>BQ=@Z*#UCTdg@bMTL z|31Nw{iA6g{n~P$M-=%LwBv5qkDk`p<7Kxld*f+6@3b_{>*t&!VxzhaypM9Rz&={q^E4o|#a+ckjf19V3Jy*42nKyb`hg;67S7?o= z6|#0`UiammR>iaP8bw{~X_Y?|bo1t1Ppj>SG$;4Y@U*JUe3^RIWKV1HB)18{<2|i| zm$J9<8Kw5Ty)#B1>S_HDE#%?7{+?FT^g}{w_4KqVyZ1;uHO$k>@_x*^>LH%i)b9>l z`5feFC7j(i?}-3UtIU{#6YBVSTDGNgcP4A%X=Ml+IDBJ0Pb=%__v5S8^t2L>Id2~6U$TY8wM?Y z5yjK$?|pSc*$*C867Omk$G!5fqI1rc;s~t z>(-T@GHk!-VP)m-hi5#jw=16P%YWR%vSz-2S?{oi)nq}X*P;77tQ`I8R2sX>!%CYY z@$fC1J*@E_j;;6BdRVW+(k6Ve+`}sQaEiyXMIKfaeZ2H!j)&FWE%S?e(>$!wXERj1 z{+)-lbL*k*$47fu1zRk4-!#<2%6Rc`+L`@5thNi@jG5Qd!-};pdZt>T9@ep09wF5` zdRXf=Y{~A?#>1-7yzGv9eje7w!bu*iY2sn!DLm$A$9f*thLrQ7`c?C=J}2&WJ7EQ_ zD=0AjZo7x`^H(1aYsS50n-UfDuo7Lgc3jBiVGW=BEJ^#EvYIK{2RmZk)_=Geb zR`TKj$x|frum(OZ5;8l!hgEG_AR*`^vvCA zzHGsUm=D~oF#pA=Hs5r&o_(J2`q5Q)YqgJ+ui|-ktHttt?`EEKw{m>U{_gIN?pC|S zReYlFcegr*9k1DWm%DXgZ0;Q^H@jO_jzsByS>tZK{+u#S(q-;e;JO?2+AnaofTV6bmnhq-y6)Ed+eMqzs^)Ih)L(D6m3Oy% ze@V9BP$_roX{V_V=X$$a_p863-8rATRW0`Vaiy}mTL-?Yz4m)AckAe)ZfOssmH(|4 zzV4IE-3lm@bVJGb?$*AWmv_dD;cm@)RVC5ekLj)Vn?`*)`69hFcSNCx8;{dlg@*Z* zp7kKTRqIOeO&9N{w|0*!8W8PXdMk?ge@RliIlo*#KeuX0jL3%hQJn4d>Xa|fZ$J9{ zr!ORV`PCZ~Fh~2vjOxIx^Mk%qklhO}{zTe{|hR%+GZuU+k>E zLZ)v2@pER*|1YOTBmKH_|IkpEGym299}`m@AFOoRWDESmUfjr^2jlmH>ld_l>|_l7 zSN4*rUx)h%B6~aSx&Hs1SdsQZgW3f1yV?IMJB)SNiK^@PZZ119BJKG1aQ+JU{}nsY zBJC74|L;y**^KR0+czjUAgI;vzmGm~Y}aqpwbc8IAFGYLa>eU^?f*R@My|L=kDMUi zm(%>ezIOe*vBDYoRc)Em>wjm!71hNn{<@#*{NA)Ce2c)h2z-mcw+MWTz_$qe-xGmh5o;fhsA@Sn-}vdp)AW`j@x2KX z&t9=Ryu*LIf3v^cF?ec>Xh}2L9q;znc+lctDM#f_Z?+x@D&IIM$dgSO5t;dnP|!{@732}k9|quyn_UEJ~6ZD_}l zBa1t>ZuzA{!aT(trSFtVu<>*;hyBLvR^7W7bHqC1H?(Q`VvZKu8;*FYLar>ab zt$iGI>g67ND4vgFNwy=~a;`7x*uB*~7_G-S?rh)B5Fi3`n*zXY<(k z9p7CaFn!;;e2)1)#EMt7aX!cO*j-k=ijvPU^IXXqqwD8&6nRy z-QngqRpQOPtd5!mu7*UnXLW@6uiV~jau!F^u2bemxu4mQ_;ZbQ&+VBVd(V~WRd8x1 z$CB`OQ}#Z{=-8TR(t^2VGdfB}c$fKUW(LRN<{L6qdgkR=(J1Dp6ji(&sbhMLDZJ3; zShcc5?wT(>9S>{NynbmSu_2G!^UoZ|ML$Ft)2P3UrN&(VgJ`dk~l zWY0zKJ*~aNPnKHsaA9w++&4 zUq6%@arH*L(a+rMzL&DMKN%X$zGr`?0*#}_vp+dFY}({e3G9b=SIX)gH?e(Pzl*IN z)l6c~*W}&S_EVGE%ZBwVyYgx>`!9`B_FtYkg}qC{$$47^r?h`gP^;j8>3E%H zA8VS+esFWbRjXp>w*TP&vi#Z2x$VzJq>ELqRUZ56MuWz`Pn_32%St%t-JZO5_hb3W z%rBD9-XN^SykE!WvzJNsICrhv`RoxNJ0G}GBEQ|oHlx^>$@%S1n&k>=@gTo_MYNCE z>y|5EpK|$9!*;U^*k|t=d3@1}0`>tvZfW1u;Q_Lp(auMS^Z$bRjpXObUc7q(ApS@`^Y|HAgZu}j>@zq7DCZpmmVW+pFU&mQu* zPR8IO_Iq{94LEkFh~2GT|AEuJyzMzVJu5z>x3|4^$&e`%e)hJ9y{R3xJ8w~YoYH+A zaYq%khcv6-qQ|YG_C9NN6^&{4vDYk@$a~WaAA5qro@EBT@UfRJkiTrFn#Jt)1#dF; zTT#s3FUpkTD`OP5cRx2a_glZ>_PMi9=4`#IxV=%*)tjEBDq)W}c6#I7&=U42VKq{; zJ5jg6nHKeM;^`WC}W+E3IO_`~p*T*Ro$2+k zZ66&Jt)utl()KgzB1}ifEWF*U+-|ySHZ!JF=;)edX4}+q&N> zYj1GCv9e(Da`x=+LK}Z7Th3mu*`uB3L(AFc?yPpqv8bH=X1cw@x11?wpWWlT+dHF` zx0f!G>X#D*%iHf?+7tGyReAfP)3Ih{oLt^MY|r2ttqzp8CunkR zf_>)EUkCN9U%{TDcG)7&hgPts%CoUq?=2PVMSJFbn&eIedyCS0A8koh(Y|xsz*_Ap zRkT0*ZTkO1(RIhw{Jn7_S|n*Jln`kMMa5fLmG<6y@4ffB?e6W~+wB%g(bg0VN=BlT z(b7;-2$653lKk%Puk$+RJkR@iKj+;0`JD4S=kxh&KOA}y!(5PDEx(A{{Vi8VMlOQ$ ziLLDuc2y9jX+95>R56*}_oUlX75<--?cQHi#RQL@66*_97!f*kPOVpir&Lycw5%G) zL$>@c?A35vd;LQxsv20fpQ7b=s$s|QJdt-v4M$%yRTUgm$MO!(X*qLs{46;rH8{Ywz!o9mcOyo9fJMa|ydxrF|{PxGH= zFQGVx7Pn4F15%|Vb}=mtystQ$Y#5_~Qa7<*lzI(3@xG-sHLU^D{z%d(K1~#+a<`PI zX<|N`=do~vCcL5!)D%@~BBg&h;P^XDl**moF}OnuH^sV>5-w=L{EcDHnNTfU-`_OJ zR;7g}uFvmJz12dIbEs?Ac5N&jD;sJ(r;WuGO_qDX+Hjw6TI#OQhVq%;*FTMG<9b+P zVhW26LS{$E`$TonLT8QWQq)1p_aY@fLmf2lRq7Y@)w@NN z`Y_s87Z&kmr^C~9QQqhp6L~`yEpn>KN$tA0VBTVo^F|lp6vA}Xf-bBsgvmYIs)yUJ zt<}CA(8F>ui;#ed9_p7b-c+~HgZ+=1M)-YHiUpc&rE0 z-RqM|C-m?qtTkx;iXMu0iSDxF)`zm^uJET)`fzu>oG!1X4~^DT-70&12*i$b9f;CL zK~(gQ@?3pPj(R_nZO})S*>$}K&-KyA!k=RDNgw36E{|Uu3?Oy8aWz-i00OJ;r-oT9o z1GFk0sZZcAgspM#6SsqgxMM?%GCpUBvzusx>ehy^<0C7-5(D-bc5u8zI(IXXRhJ5mw&TJ=PvILUBvq1k!Dl9gMM!Z6&`c+884p7oFttjZq-h+}?8A80#O9OV7VDMseRR z;h{NWDD5rY?agWevaEac)_o>O+4``n{)`EvO_n^ZEKKlN-RufyhzTB4o-}$&Ho=>| zPqgG36No3b+G=&1;60cd1V5T!xJNW?Zi6W#2u`z|yG)^{cKCUTf+@7E`i^H9nW8ql zN;J&R6c%qp$ex*|VA+tB+p8|v0GL+-l+S_@ug=-KUlRwT^~DmG_s8nyVJh8+q=T!c=X-nj9vw5Mg-U>y-eSY%0te`sB5vQPNg&2<~|IV6Np@7qWRzJ`R zeY;KImTiT5t8D8DHC7n>y+^8|#|oUzGM%qyt)Le1qH80wHAVvH#}&k^!Q>{kC0N-S z)n4oxEmqdZ3!eSCF~S-F;7@eLZEM^-?rO{2Zw*(QvlVVXtg%YqmULsb z!FpZG6QaB}Sgh>{8W*!cam~?B`A2O~*=?V0sbYiDpHlXsx;9V=b@updZG$zj$#JmIwWI@>)ki@ ziNY4AyE{$`YuV!H)>y($OItkTy7y1W!xnnCwM#|9ZDD(4N7TVoTMz}N;*Zg7L3W}w zsg&75-=0Ir;EpX`H;Y!gblO7Jzt$^ez!uHl$d>ujw&?p@9@e~Ii#`5*k4HAxL1*pV z>3`epV1KcFX1|CXJlxN>=*rlkY*O!TH0(fg>-GsU z6ypdxEO#?`Qq%0Pwx1X|m}dw2sg4%G3On2q4HdI%wnN}adP`N89S)w}7`ZZJhiECf z`=yU|IO2IKsbJ9#{?u1Ji<|5bQ>qAE9((+KXFgsnW{<8x-qLNy?6KgcbJ_o_J$^j; zdUr(69@Y*oSkBwoLy>JVy583wwa?dy?v1sF4m1Dv9HKq`GiCp{wa^|#ZTDG|YwR)X zy32S|yFJdoXX{Jswa1?Y4`0@|_E2q zKyxg1RD$jRwtUAc!(|R2GleH7H#uPMe^r+iyBuH;_2JErAqViV?>JNW(E+y=cl9~{ zcEIM@g`@+_j@Ts^Dltt^A{XZvZ58uW8?@w)=P&z zIXXhBChhONKu5gjTwBUXbi@OjY$1QDBf9&Kx#-<+#KGGB(PNE{NN%$U7JTf8^~_xg z8wVW0)3?0u`?Mn-k!DW3{ppAx_Hh3fOitJ$Q8DnC+X>J2+FQ4YIe}o3!`X7o32vUp zW}D7AVGpEcTMV4gI}yNn-@ys&SC*~10-Uh2aI^n)f)lvP<9(+nPH?R7IR5jx6Z}QY z_pmlPVRxvqtjJ?0d_K?Rt~B6;7rOVKnawyss_e9G#4jhTMaHZZZgR#Iwa`~xJkD5G zDfE0{pED9;tiFlJIYV$*R!;wdGvvQ#TqhVgqxqb}$#y4aaK={rTn=)^@i@ald4e-a z4b9$$(43)tv3kqBQfJ)nykN`J#K4Od7}M%GWK7FQYs>@xbw+(_T>>*s3mheb5L;wTkO6U zD|)VY%Ukz{Z0`!qx=Ib109SZ^-1e|9!4bG@Xn2eDRta+{blAM=_zw})CevHo$zpnz~^mha*^%`4pKZLpXrWD zn-B}d0(Y1?bY4-Zbw`uaJ|@i$ca-^4v&>$(qqc?jjQe|cY{)9-iT>%1BwH=coJ}5R z?b{?%%j<#1c`pK9dre0}aG2%bAfL znCZ6@YR~k*r^o?^{|Y>?-_z-pc%27+WbrxLJ@5b@>ldMd*B+4Ne82p5+5;h*H>`>* zdf<(l`d$}iPs~lUdDroKVpZJk-QR50GHY$#1(b5y_7yj+k z_V$E-LCeL;7*FtRY072G_QW5hKOCOLp4j+{Q1R-PC+Y<4+!Z@LA={dvTRP;4z&{+8 zytAGV-Ss&md4*BW-aGx5-3x@lcIzNvFKnbfT=$=p7aTH%Ea?qP6P#$Q!BW z%J-cd^oGd2O@SLfd1Ktd{qu`uZ^V3`fAC_H50KeZ+`{35%vd5{37-$jUqtL8@A1J6 zQfyP~K_A4wFPia{^?}gu=^OTnJ`iKoST;K610}n$iA$P37%|N=RWb5`o>8ikg0&Cc zch+t`=Hi2W5?l(>zCIvvG^{%u=7SAvxn_sreNg-G@x?>wJ~;mUOqT@J2Rv#VV^UXr zu=kp5{LxY$6!+HOJ6Y=kx!dG4TIFij?}N{VDv3Vh zJ_zI0I~Mc72g9qAL8R|K&^`ZCFpsW?TawUrb`AVe8D|8)mfEJNbf3@A?gKZ(k@~J)Wf-;)}MFf-MoTz94gc5-v;ig}5Bg!vV4{8o4A_ znXdT4t>gI9<0ZZ@^*DaWrN$TkU6kE@xyct<6xp(u9lrQjp!Arf+ZWgF83if#`{JAq zp*4Km7biOVDR(~jLXUcW!>{j*c9qGE$CiB|Wm-uO*x-k)fj1xCX7@vn++6Gmw;zt^ z`u1a&AAW_NX-zudhw4CExjq>`yx|@_FQC9EWp2$nob|(6<$_4Ph9CTc3%{*1@`FKY zhqkV@AM!q1o-cOs!@<&szC~X@^!JN>yA@T-{uC>x@6R-zVlAR`&->bqY_3zCY%9o*xvk z^vB&pCaU5mkxk6<&M?09PsN&p$T|K)$xwUVrNV2<(`m z3AzSAZ0=goL%#qVUnn~36%hcb^WVY*lLFw(oT1v46@YD?$yLGh01Q^lmWUSzK#i$I zezYn8v+WzKvu+1qr_6Jq^Y;VLUMcFk`ZNHkxmzq6UkBir1!dfOJOF(j4}VE~48RBF z;_I{X0dTN6^|@>%0J1vyovuuQAeD5UJj@Y@wm)5G=lKF*`m1ufQ6vzHilIW05`o}I zQ228Gcp$bUFY9n35a-zMYmTV~B6OJcu3Rq=Z#z=Agjxh5y{M>H%_$H>^VtJJK7p_r zcHy57Wt40Zg^%L{G5Y0X`{j&42t*bH2GRnd^vv<0_O(E`OyrbFR0g7ONNFEyV<29> zwmtc&Ef7;H+h)4D0#Ov9D_;335WSf)8?(j&LGij181Nwwn;qw)&F2GAcXvkQ+zO-K zbIn|eDF~DEQj&rkLAa7i%VFjZfAx6`?v7=#@a zYNOY)f*@T>T`QpnVIWVprJ^_pnhMDrwKs!cWPk3@?K=#+zlbsI9YHAU54rlZI|%>2 zZZ;ek2ttL-h3iujjC!7O`j6QljEggE-S9IAOz(5|bFT$KY?$rM0hVA)2|0?L;tIy= z?s^sjpZ$2A7j$f|35Z{Zf%aFsh%bQ`*l3Gxmk+gWhTd3%C1_qnU;^V@>KTQ&Lkc~>y5Z6EMk|2i0Iqm*X3@nF2IkjV4= z7!0v}H@HiFF!=u5a&YobFxdONhIVcafxGkIZd1+>EJbN=$Q2Ah`J0)jVX+X{pSwX7 zlny~Wk$%DIWC%>Mdr8G-Lm+TSC-}2w2o@C%{gyQeK}wCw_DH)B%mnduc6o##jiAEA z8ytc!=H`VVAVzGit*r=w!gIa?=Ee|^dzQs* z+Cxx(rAf2pX$TVhY_{_DhafKGue1N#5Tt(2w|w;}1m2rfzsW9y;LjEgLF(TS7;CId zE-{BfZo9x)E3QyT)dih-EEI~1DZAfD9tZ{V((bLfM?x{|kTAAUDHOt|OfULg2u1mS z6`GSep*Z_D@w<*Wqdv5F_@QGcR$19L%lm}FR-h-WDl8O}IcjNp6GO3%CrkcvRwx=I z%cFVoLNO6@-9Gy|L#{64{kED=MqI)7UuJVCBd=j`XWOGtu&9``XZMEUVQ<(Uo;RUT z?tDVXe;Bx)$;L{a1>f3*M{gOUNkWz86TAC2Scx{|)oltfJstOzQ*{?)kWq64Cr!)e(#W#cJ>Laj=^X&5Ey$IBt zC=nd&iokP)nbDrt5s;VXIQ95#1a4|cpKhOxz_VJTiI$%cP#|RQY5Erd0`rgcci1AK z;v5ryhbIzVlUg^L_eA1nqnqTqN!i9`-y{jzm4bu+J-vNX*InZ5T6- zgycTI+)s9qIG~d%yyzJTb`FEHn?fV;{oyz(Z$c!zhmZS;XGMZ^+bvWnFA`(VQ$_V| zL_#_Hx`%6RBx=JtjpJG)k-X{O`^%k?D5$sC-uyBW517AA430&jA)`Zk@na;UU(DF> zEkt6*#&w6nS|kF7%$%)QqY&2JWs$%gh1l_qh05Jg5UMmdJa8xqcP6touak?yYmvCP z!^%;3wpXFhOd|>`C?a?>SD7?J8?7BBJ3IQkG>r4}(z&*8+ zN6LzVS@fO#-FZ>4d{<+zxg-jQNBSG`PK=oK(?{hUYUUGR-U+-YjQ`-yEY6f5uMcqHi=Z z(#HI+MMUGv<~FmxglM?;^URo$qcO<8z}0d!8XwmjrUfgaVO|$z8rc{P6T7p1@9sx~ z`CPBm#hz%aBOc1C9*Ty3tTUO9ad3jgl%g2-EgDx$?-RZ)M`M%YU&ZPTF&GOwxY>y% z1_U0DqFo#@_%+tUIlLnV8Ik(B*#a?WC}wuh+#LhvwRu(6eK9Ce;#=2wCb{ftY!?tE%i5U){jAmN08=I zlNiW+*y)yG6@$UFOQw1bF?hLm>rQ^R7|A$x3@k`vzO?KZ*o$3wYM&c}#0kQJ%+(k$<(AK{701A8U&H<9 z+P+dE>=Y>_58-5G-OF)%4EmftoVgMi5jfseB>P^2vNwttU-e-*`;{woIK0)z(t zKQUjMy>r>CW$0Bzhyk&f2;lUj) zE)*P#u#9_+ERnIeUc$k*7#EAIpdgLu*-#3JzkVWuH37Q|P1 z?l%f!!PPeRm{!8z&64~xwJH|3`h9;#)yJaXd_%8)Q!K7U8-%&G#o~9F<%;zqh99$o zTE@>}5#inytodVaTxn`;_C^qI9Rl8O+R%g4i}`}t~)Ce2LnRAx|&=ZmO03-I!bZayCvDuL?sUA z9?|#Ps>b1d+>1wUS`1v`u@iv?ad1%RjfpXfgU0h4SJQ3cK+1hcqC3Tbw}t|hH;_^&N%qP-&y zrzz1ABTwQm(Q;Pv*Yh}3UY++N=)V7nf7_;bq((f+k!6p^h~dy@qwVpif3$}u zf-fG)Dpfp1!tv-N!MU=_J|EG2lBdML$1HCMix zrV@|+N21dns>VaSUjy@6@tECI{#4j7o-uEi4{4ak!%J4$F5EUA#M5p*mCo^yST!^p z^Nh#Fr4>$&fOweRw=F&s7LSE@*;@i)8209JG|H0VVG$5yKb{`X_#G(d?jXnGw#sK7 zb$UD;>a*%%3*xbTURJE-Mm$Q$W1b6@@pyKenkiWykKKNbNiI$CNP2H*c)cwiV+Xwd zOg@Uo>R5BguI_j!7M*@>@iHFL0-KlfhvKnsntye4JRVXd(St(M@gOmsO}6?RkI`cH z?bqhx@l{Yi_i2sd8Q9*vQ=f(t_8?PyBU`@dKY6I()Z3$q%pZ!ajHvu^v ze)g$C3DCFuTlQKs0kZ~*ox%qbkf?sV)kT^ix3oaGbvyx9Trr2&D zd;eZmO+fDsje}pc67b&^M@c2a1l-^VX3a29fc!w}y|=arIJ&!B?TAYPW-3Q+CU_-4 z;H3EH;eZ5WrRJ?kh9@A!PT@;@YywylIBQ1;2~fYaPg6EC0Zc7%4^k-!m@}8*oX$(Y zA3J%u)7KI(o?Cc~UYdaWT>_}IZU?L98@{h0V zNyL94ZqJ+!B*J%Si1=DE5jh`SWt5L4;$_Cc@*0IiaMbSpvr9P$b%`&ZeEVb`xzuqhxB`}qX6 zONS@owU6MtlGsGl`Q1w0oy_35ZGI~~GZEg3gKj&hiQxa{eJwLT5ym?^>)47CF?sJ@ zKA|iTzq`AwH`OF!NIBzgd}AWy1*HQw-c5vtZcAtUgG6*q%m2spG!a&>YJVs7CE|~O z)DY`nBGm6T5i`aT;n!*^#`QiCmjwi~X|suN9oaAa`^N)!lejyetX3+TZIusZIjN!s@OZ{?jXanHtzM<-6XQvY_|*HH zx9^kSlC#A;@pBS>G~8b~{38ihkFf>~{7ypE`R_eZYf0edkN79PiGc30rmg+#1Z;X# z{X3eAfTeZL_YMmXpx*V_YGgM7-!;#UCGRI-Ygg#;6OsgQc+lKF9VLL*|3)}nfdF>y zn=Y4>31CUPb7buz0Y03)gLPU22)$c2b}}Tu`NX-#ofZT*8Q%W((vE=IKvSj^R|1TN zeMb};xFUZZ_@LV!t9 zcy2{60gd~226+z6 zPvv`($%qd-DwlaI8Mb*3nQau4@j9{kwya7rW1sX_WwTl`o+z|bjc6ys=)v4VjZrc* zvaj(cSti42>x7`CLo#M4H7m#5lcAh0)U?$v8F!4O^*@FrL&4%lcSm$GV*j~|UP($u zczKyoXht$F->-Esq9jAFI>Y2xelk=?Rrl{GO2+<%FE9TqONMblwE62AhW<8=hqrDg zc!6-4ttc0^>vGqR^7>X>i>M}*;mQ1djGsz{7o_h{noo|n@q+a(RXU` zLoys14k%8{F?ef9`gQ$GhSFdfq5e-Y3Lj{MU)hiXp5#!?6xI}6E!eph$eDtKSfvbm zz7%AZ&aCL}N`XT9o{P%+QqWVe=zHXF3OXZw!$ps#V0S{EJ-0#%bQQx7F)OElE+YJ5 z*t0v+hAH^-?bGszMGAK2d=q?Sp90o}X~Ac1DL9fcu>8O`1(D-V z@7xVZ!C*kQ!JX(7SP#B`QJ=)nOA$I;laT@r-e{*PN($~U@x)Z*r(oOU$B^=(6oejG zuUlT80@Xw}mWtXG3^+>`RNhHJu%kXtbsGbB>j$^G$0@is_pP9@Ck1iT;9D)P8Gcez z%I=S(z$iv4;>o)d1by(9?)#L2N9M*4hQ6nOIpEu|cZ(@l_wY2~%ik18PUy8YiYY<&mx|)4SPL9h(U4BXp6*K&OSx1= z9;5fM_o-Ag^t%nkolC{PhXLoL-t?aQ_BtE6WD9STZ|9dKElXoK{7E_@YxyQQX zZz|4jDiV3gl!lqZmmaUOr{PfPKJ|k;(!hF1vDQd14aZ!mzY|2#P$+nM&n@vZ$p1cl zXj(cAUv6FF;gL^+{Z^l;3#Zf2+tfe|J)Z_`zo0!gHPUeDX9ww>ej2_m7fkb-r(rt; z1vKr_@FrG4I>9v!*Q41E-1SL=)^GBupYEA=7r6jY}{WO>u zAI&LzlE(NSI5SW8roqR)-%NZk4ax@bM9* zdpd0;4ZmOQo9thojyvVj4!c>?k)n40nHy(13S!^wxWktYX^|^n+MSNlJbo_kZFv?P#j%!j!CY>tM(ObW@>v3H=;&k=N`3EWTWODmkM*qos>w;&}@qYhrrtz-~o~LHNp8c7Q z{q|RyD*vQIDN@*ldt(M-{Qdt#vSmQ(aJk>7?HRb6)%9FoAOl57qg(Fp$-t|XFVd3Y z8HgR&D4#E#0pSgjqHOXR;PdeP5qdfU<7*?=KcCNlX{o=Gg=PkR>Ys1wHOPS3f2RB@ z78$7aZMC><&yer%Pn2-afZWUU>}!4*SRipG@P}n!dXdG97@L8*`Dkv|lnm7PM>oU~ zGjPvuyZm2T27=$AAf$lN-pKv+X9;z+a8sZ7YH-+b}wTqb^{Rz6SE$OLEQ@mgMkOep>^3@J0u#0`7i z{fhRP2xp0H?sdy#oHH(vwfD;e-=;{?QYZu8Lv%1RHWOF(=dIhHl8Lj5-#FWdjPkZP zvvF=F9u(@opD)NnA#>=Bw9-tRS}f8$P?L$k(9zL{w=;3z>R}z*woDufD6IJRI1{EU z%3rQN&qS{)KNkivky|0NdvYujTyMx+anqS#XVLg7_B9hG7wxOM7BaD+>b;)lpG;U@ z9Gc+Tn1ui-RsCkREc6AOxoN|dg?6cDb1Z^cNbvv8T`!V_k0vdm76-H7_3IcP^N}nF z*t-5#eZaIth&MK&l474JNhK162W-x7y#eqt7u*hi*yGP1B$ewfTm%|glL9f#U4 zXEE|(Ii?H*>~U zf0c!^Vd{>TN3x(a60pl+iovH(xn1Zp!(M#r#kW7Quu=Hyoxrx`zMC?(!l)Z_ch)mW+*UvkNU|BA2 z?AuMmPf3r!#=S%Yw=0+DA0VRXk+g1{1QC%2ik`00M6eND|LPni;#xra`jhfRxExan z6Hz3hV`9;X4Mb>UO+8sqA;Q&y?aKHCBEoF^KRi(V&>PeR}4!IFwD>^gB@5QjYap3{8 z9}&zK!?yemB%*>f=Gli(BG$XI{vC`Y;!*6*>rY~dnD%RJZ%QO$)byKnMG6ro4Vw)v zXAqGcFVdSyB7)9z_--tfh`7b^U4e8W{)Q+mx?CY5v`Rz8@){AF>vn9^yFtXQlAIG4 z%ZaF~9+^_QNrcDj^!lT9M0kg^P!Bc|p%!U#bx$)9GXi5f`R@@S%sjbou#$h_(k##oziF{^_Xc&khrjm~|**dW;CC;%!`$ zlSJ^={oOM*O@!e1^7Y|Q41G=3s{>z&DBFKz%j~7lzQ>> zt`d<^EMf9|9SPc9f}GEpNMJp!eCjz12|^OrC!TL1L99vjcP}Ri*m5l41vd%KI_^oY z_(>S9TKL;9L_&u8tItDwNQ`~(7R^yH5}3c0h)sx-@b*Hf@B70fgc7)peUc$zV0g}b z?idMEPNxMHPLS}&j8$n_iG=O%^!xvvA;C;+@6_h=B(Up5IC7|wz)N(n;?*SKIp@2c zUAiR1t}WIcFeD)) zojghCl4kGm^CjWlREcGDAPKTp*&NeDN$?{nPSGPtVEb}rsKzw-ZU&mHkra zaS93FPP_Z|XOQrl>#O24iG;$_rV&4>BycX=k=>L>!cCj4*1T6quxz$kFJ45#M(H{M zg%T1H7g)yhc0_auZHZ?MVxNJ5BbyzZ?pB)rtHZ|?oh@JFJQHoL%>2N-;1S|Xvb!~4#j zRT92_T3b?DmyLz5o9j%OvTuvs=@^qaiv-))`^vSZNw3)b22$XONjvd>28!O&Hzb2ch1erUA0GwgV} zaewsA#{SE*dYl2-*t|c0MIj^`qUzg@J49sT{>A&lnK9Y8Vx7LwoWS7wA|!DpIU7}P zQZu(_WMlR26OPlQZ1guD+w4xwM#J`@fLwYuOg7yz>%5YUxPcdUeqPIl*CW~Vy(QUD z*fBArSHWm!8qJHVX889;VxYc08%qO&N$+oGRl zJkwe~1!l4#e5n^Yv)N$L{Af}+sN>;QJGENLB`hFTc(fr$T(B+PkEgX z85Ej$kSTN}V6R)yBIDNf6LrV+$;e7191Jlg zqwvp~K&v?!J?+JDYu04=?+&+DaUi4BG4*be3mKax2Q#`o$XFP09OCpPhY% z=4^)>jSODGO*j8*Cc~N>?R}<=q3`ysJo5n=E{m*|=3rn-m%*Bj4NOyXP1gKfY}YZl5G0&fYV4`wV0J_f*f@&yq0~(mi#1j*QJ6 z&otToBjbfe-4UxrGE{vMGV52!xEiD8!2FMl*q56cEH+Yb|4v(2Ju?M2QbJoacUgi8B;jqwL|Lo}*xE$^L~OsuZ}i zL}_YkQV@1fO5~<41&JC8UhGB`JdY?l=4M6#bw^adQ!5JgxZRgLXitI9NPta>GefSe zaLZ?R3haHX9R0wKCq;<_vfhT^UUJCZ4k@yUPxj7GuDd+JYRE>jTSStm{_qyV!6>({PRU=w@S zz_FYHi#a!u=Qk!qw5k_F5%@uFDdXe6Kzi(pg{9fr`E4G41OCtP3+!Mz{@T));GoQuW|Uz zsgD%2*_G*5ePQ7E6VL9Pr+{)XrzZ6$1+r(_sVhqq+-u`pbo)!ehrPrXqwA>{d8x8b zb2AmH2c>?tu~E^UZhY#x-BOUn=8zVcxwQNQH`0DKR*Vir1cZUi^xpV*Ly$ zEg+tX3|6I%1p*brDy_c$=~S#OabH;=Qqe)yIvYTx;%d03!!J6Ok*^P47krh9ynY|P zf9>N`DA;+t zRGp$iIePu>S0AWgvmO6v{)Gy5|4oNKe5ZmU8vVuRClyhn+WS|RsAyt~93%Xt;<)e@ z4(<&(NQvIiTDUm}j|&{%OS0$S&1VHd>$V((^b$(cxN}h3q^ms4pMxKKSB;&8bMV#F zZ+1~62cC1U7nAnq;Jk8IG@nEcCLS6jlu75neL9Ls;aCp*xir@GoXA1qy^dh3Q#t4} zm~s1|oP!+IyKfRMVdW# z+`Bgy)%75Uk#F4;<@zKCe22W2*?MwNG%5L`>O~HsCe$3Y2XYwq|0jn3y~)8)desB! zTgLc{uT`FWp96uJj_q$h=3w&V--WoZIS9Mbs3-m-2UiI5lD)rjFy+;d7_gE9yY7u< zJOAZifcs;`gN-y$AJ~PsvC!bB8S#K?D-Fq#CsSIt)9_{FYL5di4c@)==^Q(0xD*@q zv}rdDzuf=C*^AL&!J^pAagYY%zSABpk~F+g*mBuPmIj_jWvV;mX=t4h@Vl=_Lz{pj zzt?Q={S3SNc+@77doN3E2nrX{eaDFMnf9Lw3>WQ>hj-Jb56X zrD#h7-}9BxuZ}cSpZfUWsv8Za#Az>GZyL@jFZnYC(6HKbVDU}}qrWEPmwO})MNad+ zd*Wzt@}js8B++p0oZD1t8VzEO!GmXrG;G)IxcHkwLw&TSTs55ry#SFS$EyrKDpk^i zi)lzHzWrmMlm_XMxPiy!$oT zM>N>6-V6NJMZ?a~^d}Y1X&}x_<~YBip|q^+z1R>9>uyL?PmR*B%S>{yaFT`xYEN6O zXK3(QVEVV~GYyHuGwpA`(a;&@yLNSfhF-IqE!Im6y_Ks!_xzV}y6bQ*1QIsB%RG_`u@?u{8_DH@En2NWSTUDz&d{rM2Dv!PlW$mkE*Bq} z20eq-a^dPC#-yy33*kp;{OtO<5WKRza=rI?4EiQ!czT zx~7=ibK&H4^6N`)hW@V4{sjTKxU@16>KU4gwU|9?icz`vEnl~eJ)U8wgcdtOVECcW z`0PbG1OIXOS{Nx8_u>;)F6HFHl4m(UI6oI#H%xebFUUpYOU0Rv>$xyv=NYAx=VHeR zA8nWFT$oJgsVFrta8n*Ma5m-QU;CrtnY+0-?^(6A^KC9PFS0FMeV>c?*u%ylpK>vH zgI_^!E*D=;_vat}FBhIe4iU`1bFp!yZ*=NUE|z6>wzRFIGoF{#;LG1chabDkLNFU0 zOpgP!47bry(-ACwl$#E|^q|=70(98b2AR$a(-F`bS=%Q{N8JfhQq2K6?ls;X%{olS z+P#GupCfd}ISI{;dUAA#+;}!{RFMvetKJ8A&(N{mSY>JH933H+s;XmZ4EfUpzWZ8q z6s9maUDu~$bNAz;DJFEJl3v7kS<;c=en`{6jtBuv-r|+ zrU0c;(IIEFoYE0ZhlMf!tBM3V=BDfEloWRmeYw4(T^=5nDNXI9? zZM`pB=#1xjT()<#(=lli-Cz5NjxJX-zH3kEpnh3=pb{P+(#tc$rnA6%s4?V+a_{H?^WI!|ESHB{{JLx%ig_qB=VE-7 zI1kLrqFgQK^T10;HoBvp2ZemqosHTIdp#~T4F-AWtoydF-ZT&A-c7jIS>>Vqhq7#) zeI6#l1Vigw@*uOr`%JxO9-8&eCpY-zq2oo7PGfK$eu!nz??f=l53Kerv3U@$*>&?? zQXcYoUIurh<)Nm2|HH?`JV@-`m-URw;J5H{v@b6Yi%(?B2MY2aDW~~+^m-mn5AE-q zD$m3BvyXW`SLY#~SL(x$hCB#;a*|(Z%EMK?K-P_Ic~G6VvS7Sw2G`6E%J6pOq4@Af z!k*`O82lGxCh;l{x!#-VB;+68TJudzdT{$0%|6}O7<7#^2a7Kd^id0lWNg9Mm@faneAxcwH z5h|gg$Z9A=MxuT9?sjLAeo|UO!zzhnWF-x&LBIE}^Ev0d?{_@U_tw4V{l3o$3@9yJ zmyO!)LSgkLGQaZ3tk;{fvHA3W(|&nmL$m74MRnh7*jl_eGdCa`3BDx?t9NCiq|0*H zXJ0n1pF(VOL^dpox{q9n%0`j-+CTi*Y*5au8LT;x4ZWRx$KNMpBVfB%!l<*^=+Hen z-r!<3E-c@!x#DUzuK6kF`QFIJH^URWqq*7S`Do3uEE>75^J#qvI~(!~ovp2cZ1CsR zb`OcON#E|3M7^8YIJ13I4ob7hbCo|!0?Ud2iafWZ>TEP*+kfXgAoD`Lt3PVYhD$9& z)cY(ON&)+xYP}@5G&vl#dy@@nnH??gLpF{*a(6oWB^zV%uEvPIXCv_GKjW4k*_b^g z$5UaD*i$$-e%?qn#48i9Wo!<5548L`HZBK;Z4WPCspp_;O<-4}W)3pv(@cl8b8s)y z_Ul~z92~M*Y~emT2bVK1_n$J!!J8;=TcLRl_Uyf^*k+Z3i0RleepwD)F5F{ivmyuI zH)}-hS(5`xgSCz~9CJWjt&msiltaGPyB-;E%fVbd>nEn3IY^jQBKGsk!O)ZrjkKLP zh|U)MD&L)hMn8+~KSPO~ksa&i9n8TbvrS|>Kn}c{c*Xwld+ zyd31jrcS(DNb;HcE_YCtgC(Er3YV1R;6`Bn+Jj{|c(-bjZ$V`azL{P7`T2ehnj02r z&TYtn_LE*|@Ka*v&C_PuiySDvN6=OF36eKMsx2i!5% zwjKRBc(QK)p;-etSiYj)Ecjmz7GJ5`#~PChpPqsFpH*^EJ|_8qnR+h#j~rQcKrEn^v~x)xbmo=;yP;z^x(E;`sJM3Wbh`RA%8c`wUF zoJH;r%8Fe4Tczsvc}*^PZZy@=YJDyW1Iy*doOAK_;S^5QmRtmQPt~8~m5T~_?KI!* zxwx6O_!=uP7ftE@8GRwS@Q!qvj__PCUyPZPdMKCheopk_NG^1Ke7HOJcrNl4XLuZr z&xL`PRd`i$E;eLYjGKBP7iW%X&D(V)7m2*@59QZ$;r6`jhf;1XJcX$gFIq0hOPCsY z>|9{hqQt*~T>PG)UAkG4i|B+&F^rqJ2pT)9_)lpr_PK3|bFLuwb=Z}(np}KSH@^Jm zVJ>Vso_4rA&INVVts-V~E*`&bnfSLY7s4=`Z|-k%N#DU6mx7PE_%!3`Vx@1nSVYsw z-_e%~R$I}9TfcL${)AXz%HLc}x0(27|L8oJls${AQ_jQWit$m#YIzv?A^j7tkq5`| z&7TRBf)H#Rxi>>ltWA$m8&aynT zDl3%7uE;~WS6O%KnmnX5&!^h0&x6cnQ%jz69sMNB{ zc%Bb;>y$J4ZTYya;_k`r$cMwgN#TNz`B18voLbVAk2{tVd+hu2(bRXmwc&R@sviYy z^8HKVTopu(97RFodyh9ZV<|9~rUoh~QxK6l-lS$61wO8i7hIe`!RWTmIFE@GXlZPk zpr=8>hNe;N@260}9Z>rzm_|Wt=pmPAT?%d}S$$ohN5Lna>jR~k6uekg-`y~af}@v2 z9+^fIWL+5;2{fVLtG?iy*?bDZv>xdHHm4xf+xu$y0t#-`dT&ZyM8Q4#hIO9S6zB>| zlV;gaQ2JqxO1CWq%!lLaZ>^-@@Y;jTNvlb`^L^)Rv8Q0}t_L!GM+z1{u1xv5fr7%V z8!5$36x7uD4<)%$z|mIB+PZ~;$p)+93_U0?E7PET_oTqP^`z<@9}3*JoGVVRF;!od6S)mk^>u=q)y;U2}=pZJk;_^(qAiMrx}*U!#z3&)Y`bxk15D zU)iZE*%VkNkFyWSBYwn7HrY`rM0dwAV={vREBDn#A6XQ5Ts8hv%B5h}PtpM`puoic z&fdL+6tF~xRaQv|Z;eJ@wd533QtKyo-=v^NIV!HIg!t{$o}c?41#yOV^`h@mP@5Cx z?tG7eRd$9AM%5H7fB)vfzgh~MV}zHQA5ft1Dz#ZuPl5KY6M^TN2;ZFKg~3lLn9;(u zU)fCX-TNt9yOn|?tJW>;ZzFZGrdq4ModVI;(B<5>6f}4(qa}Wzz{PrjOTZ@z_BVN~ zwEIedOkHqDyPE>VCriHm=pp!w`NeMPr{F{4caHEE1vz#`-RB1=;7`mtvUivQ&v%<0 z97hOmIZmENqp1imyi_nok%~jx^w+;trlQnv(71d&6{q$XZ=|SEkv54XJ~@dB|B++c zcW6?Ps>^qV78QEyt>X=JsF=Ke=FrG=D#jlQD}JL-#S0s~l5#^TMEoTR^f^@AEhs*h zIG2iL#$4~+rc|P%GA?|*ITbY;uPaR#P?5LQWT)C9Dij!BSNANT;`kTSkSEKia8Uc) zSY%7Z;eL~oSu3eHnDFi3iPcnO&~3PZ_EZGfPBLg8e zFZ$(5g_V<9#mg;JDBr!8UhYA~jHy}zwigvUtdvzQ`Vv1QnVcwpDk_7TF8J=GB5|GQ z=9*wCRMKpWO+%LycS+AOgctINXhfH5hti9a(gqw`!p3Te&Xe86N%kQr(E-7DrQE=?X}NQac5ok zf-x7U_-Zw{vpbF8KclVr#TCNG*NiJw*GPOR=2yfwsAzskd!Cz3#kKl@9q00>*t%hf z#StnMyv1ItgBet!0Ft(?wKN*bVc>NvRNa*82XD zQ?VrAvfIaE!tWKY)aSRUxV77GLiHUg`P_9k%FC(H%)Zyktfb<@Big)-8iM!wwExc3 zQE}|Y`HUlvs3^RhE!)#bMRIcaG@mC_d|Rz>chhr{_fwyO6)&g=o%gievW<$sE;Ic~JYPuy2A`O+5 zQ>|#on!hJ);u0GCj;uBwzl?_Dj*QWYw&cD?=p2QWGjJ1~s+ZWxvNHjm?K?;NEO#YmOy&K`LvBBjb&>J$`YLhR|)dm%NOp;cMB4 z*KJ8O9CmGa^g4xx4Sm-}cbum|A;a+ayNfikU*N=w&dW3$y(V1yIh}^+hw`+p>jdwT z48xu*8hoAI2l{gfZjrrOe<(B<=^Q*dOec7mKc78{O~byt*_ui`8p^!=)~gnfbLWJ6 zlSDLBgk&dbNon|*!9G8uhz861yp~zF2o7EB(0Qdaxc5-)7nBiu71=>cE6BXfH)>Z_ z5q!^OMXsx*A#*!3*yRB^-nyN=t)7PJ)Hm8YnrN6%t<@Itl!iI(J3ky~rs0p#y~Rgc zX*gtxx`bCWyxQEtyzqvGp5)6l*WVHUcfu`coisduAMr}?ndJ33tLbJJ4H36{RquVL z!F%J8)cQUeXh{}ZTYu8f=cgOc`G?d?*$Qdj5DoKkvz)VUj1wQuqvM3j7Q0twbeOn(T;6R- z$Bw(7PL5a+KdIiTldb8P#kPAj%Z85Dk@TO7?dVvLvdi5OblhGb9`Ig6$K)?ZKZH4u z_?T7GPOYaS#6A7e)lGEtcYbzdyU_8%<-W&lH#*dgFQGNK)A25F;ga{Bbe!rfQy=i5 zBUtyng}Of-AL}XCX78lqtir$=n_xN`DBrDJL+G%fCLi3jkB(#g_xg^7)1iBoU2^3B z9o1Wx+~-D;JRJ_`mmj9XazuBCj zW3@W}%a*frlqLVL2umgYibt0urO}~rwdH*N6*~5gi)GxpMu&p>$1zWD5ZtaB@w&6= zc$m8M`nY^L;=dX^m_wsufY#x#ib)5Bc~aSrL&v(#;fXPPI{x$Y+mSA$<4G6gr$|gk z@{LE2AIa#5b^h?{%S}2|M!xJ+xlM-^r9;#B4jsmG9!{_?r{n$c0;j-AI_^_ao}8+o z->{;U4o*a)qyH;9 z(Gz;Fbn*=y%~})GDDUWS4cg>W-bu&U9!B@u&vdL2Pp=#MjgFsLk}l&OI{1{ZZ5#SY z-4r<~hy9|%rO9LD;s6~{@6WB24AaRvQFrCD5jx&fC0-dA&A`PtSGJxK18!ua!J2Ul zIM}7Jc28j7z4`dd=O;3-w5F;;q`^SyaHZ|@sSIqd7(YCu&45%bYs{<}3@m>Vy=A=t z19i&OPy1&v5JAzezGlP#{c`V@QWFOLHO}ySKc9i&E%fmdEE!m=mZG@WihIhDnLGZpKLeRvKNc74 zWFYCREBi$-12qN9cPi~+z}WRx;G$3l?g!QL1NJlUyd%!;;z0&R->sQa8pXiWAL7Md zjxex0YGsc0F$Q=`4{UNg!GQJRjcX2{W*{J-;S@8Gfr--5%FmLC-B}->jy=yHx{N2k zEWXIV??nxcyDl^EvZAHyS~>#{HsrskzRtjjG9&drSq!x3Mx2|M$AHV$6E40~2CnSb z=5v9;AbOaC1!Zi4Z)4boJ{|+VW{J&=gbXY+uJiN~GjO4)r0%?oftfSTB$eGHy!^FD z=`UeGE4=ve+&c_d1RCw~D`$X9Gy*PHGGJ0F-&tM5fa|382SarX^wj=7zObG_`r${% zgfx-+>&BGiK4qYCaA@t*W(ExVzfVrM`4U7XIuG$R|&2l`B`43+t+%wpo@#t4ps5fiHag{??1VdCiR zLf!KDOl*Sfsu2q&GFNK|S6DHzx^!u9+!7`hafE@lmoaf{B$hj9%fyY_Im?!Vi6zf< zCLLYFMAP~omL(2MY*4ejF}R)y9lb~EZJn499BNt}=gLHaGLhuk!bHn6ZM6{(CX$dU?<8Bhql-o8J_cAfN;nkg{FeZMb zC#Ft4z=VH*gytQ|gzfx&qw)?jA?p!x-o`Sa#80>}_c#*|+`l%4onqqer%!H@1SS*% zPfq`HhKV;NV)K>f2yWf+DTx=Du=@RT{)0EG7ok zHedRbORoRc&R;-f!X)$BuNVdsYrAVR%Gpd5_R3F==QB}}w?n*D$i(j6+WEO+CMw_m z=53?z*$8IxmbDQzEsyj@)pd5WNse*~LXv%26DuP>6)=_pXiN7$+ zw*LVW%T#q7(7;4p2krX#$4o?6s4Z%F#>A>`KGTd|kbHI1><_guu~{N0DQjoqf~Y7~ z?Hv=ZCyP$|b~54jIQud8GZWLhuII--S771a+2~y3u`IkD6MpiDG7IdIC|>P&7Tox0`nu{YoOyir zW$0uUw9QrD{x^k%iNyyOPSR%K!GYiR1E;glzcrsP*Joklj)Sx|xNb+Yh+o-C0DR z{PdncPZoAgyL6z$hXwUD1LYe2EI97D>leC{Mf5a3t*Q)W!5}|szuq1e(gYzN4u`T} zC#+;P?q}f`?P8(nAr}55*N#q#X5pD%bbfmb3%v$6l9tD@kk_zCe(fX+Df>2A^v1Jr z>3c_y(-{`|{XRI%Im?2%&0$2{W#uDJ7UmqC{OnR0nOBou*;~QFz_*6KTdG;;>#jN_yHD`B zAM333i12s3J1C}+g|=~XD_fti@G`=9|B7Z7JcQqOQd(IUU7eIa<~510=&Nx^2Me_) z=lrRA&%*Lqi!?1ikvhrMjk)@jg~|P|jQ@1AP;Yq3V0#}6cTL9zR{UgPl6rXe+yNG5 zJp8Zn!Y~UfcMZJnA0a%o&T{n`!$#cv6=TYk*bsPc8f7w`4gRjp8`9L+c;f2%^4BCb z)O0r8^q<0peZ{(0)zjESZ`8rraylESH#aEX&}ZYIqNnPpS!{SMJ>b31h>a}g8@*3V z*eKla_{j=0HpFIz3LHx|ZvIX_tg(mKbUby-rj ziVb0rxBh&4HqpILbGz=yhLKlq{g_Q`gk4L>IN-v@)tdF3S8i-P>{&Z@lLs4@G-7zg zUThRue9AKRV?)dF`m^*MY_jf>;xi_Q4b`YS^A7E1V{@wA+K#yyH;1#qc`(D{ z?g2LT{z`OS5XDCK{MDBzN7$IO-(7F=F*X*KmH#+>f{hZxRIUEgB%kX=@xe)KJezHA z^E`!(E453PuTN!TQ%ZVLNg5k{yS7g^zskm)pld_<8EhO*gt2B88!zkpv(MzR(P8t; zXOO~%-cyxb`x$J~=aG5qEt}wfjql~jXG2!8dd)*2v17dB@M;MgK2=ITi{u0kitQb< zTWmy@Dmr#y4@pL!}xvc6{$2f1-|zKYsJ-&qw4uKrbY` ziH-6slcWz%**LqP?c4SiHpUs*QlGzM!ys;bzH2)h2{y`~YTvREA8_>8noc&-elomE zKeO>*=xn_8H{$1r*)LHK$vZ4fHvb13yV~Xdv3|3$>XEiyPc+KBhc^U_5j~~rX z(&b>(ZNsoJ`W&*qdi(Fwh8&D$Bs@|u;=rZgdiO~a4s4=xd`9MTkgjlT@<~e$EZh?{ zN31w_78M?F(wc+hkgb14Y&fJZ)#l}?6&$R6Za8lAY7XhIofMy7&p~mm@;b%!9Q-s; z^+?{t!Q~LW!f~z~cr#YJr*7fEw(Y3>#BCfr)qHsJiZ=(V+?FY6ZRbEQv!XdOfP=HW z;Xm|(IVjw*!jBfh!Sv6ICe97z;PzE_HNk#z45n>cc!-1Moio1{MRQQwKu=(a=8_#48jx7^ketnLEofDre-hGiASFfJkeVGG~ zXMg+-UnB80{_FU6gM+f=+a$?396Zn-T|bG!LB>JFC0TS1mTn6AIGfFZnLz(T0gvFF zwcKKvkOL$Al8Op3iPMfkbCz>(eCYe5mSPU{g9P@$r5w1%wkY+K5u6;3Oo_Y4LCmA` z;VLy8RA}pHT&v^YpZToOv+6krn!;OA*hJ!fEUmPAM&gZFbG@#GgNQyaxmO#p8#t){ zv7Lk0)$^W4zvEz^@t3D#KXULfWu;;I7Y;7|oOf$>H{ma`Fh|-;_!#&2!P=jM*SqQK zp8nw=C2w%Tu3-*l3X}|fjgYu{ZpS5$;X*l5&say93#qNebpCiQrfuo2hM81P-ig^tlE ze&1Ry(K}D#oOR^l<<9TDGdFQD!Ff$`u?rW64NvdcyoC$uN8zeWwsb72tv z?upKJE@D^oyq5%UQ8DY!YNuc>#u@+n@*#waF`E_NoDAink~_&zH-ZaayV*k7Audi# zT9xd2n2Udw`vhNNxmZYN=$$#v#rw#RX=%;#@=|6d{(Fl$eb1qCaYa;~<;djX zSdSpLlfy;UNJCeWfJ@#VVxD3o;zC1uKcY&?#fnfCeb-GcY;|j9E8XT|Qk_K)>kb#U z65pt-ui%pB)a~J)tGKXVYhiHyJ{Ny{8fTb4BJr?lcQ-V0!M4=>d+;e2`3kk|Q(Cyt zo%2k+_$3#S4EuAw?F5fhoh>78iTw}TcGEk#$SfJDcKkwk)S4F6^^J?%v6S%3y#)V@ zxl-$&TsW3#tZVzj#YyUDqxfM`2dfTjHc{Zgo8@0yKZb|s*Nv&s$~1|LtqEw$uf?0T-2{~{h{7{0xuvXlqiefOo(&nfoVKwIy_-axyr-JT&iwO1`oZI@K>=}WW3+K2py%uH4{-X=$OqSBc%9vrqS z=-A!k!N4akrmvdd&EILnt>fY34rhaadcsqD@a}1kdFY$P8?1lE!zE|Kwlgnyu%Gm3 zJYMl&9V0sP`whYGYQ3xIJr9Zzrn~ok;=x&`=KicM9+WBUqix@LNS@RXlhMz^hnQ8B zn}750r1PJj>JSeZd(_re|0CB`BMzS)%}3;GifA5NQ?Z)9qG>=<=u z!@96tIgZclqVmyf37 zv3ZpFe1s?#9@=5ahu5AR>H3TK_#nG5=Cw5+vH`ohxy$*eb~sSCeI*~IY|~l#Yxr1h z5mEEnfsgkeSC!;%;N#;v+F*b)A36(fq|9>TWBTja!SCJqa2{NLf#tCSS;1fNX)@>T`e9#02b6S$f{ijCAJIlw+)jjOs3w&%F6YoF&G9SxK zUkCQ3^C78HmfyO;$C(eKJWl5j95Wl%Y@qOQy*Kj2WCkDNzrD(>Y(C;2IknUHe0UyA z9vfE3hu#3=$Py_ZRZ*N}BSm~X{E_Z>zl0CNv*C=Zclda>dWoxl1t0#F-yP>w^Pw4- zck#!4J~n=Fn0@yVA092A6jPf>ymsfTy`J%*Y+^2-^MVg9@9F8@Ha_ViPsq9RhL0aN zS16{w=i_&fVyX8hK7M_Eb$4zTAFF)oG=F_3d{;k`Rrd4Ae($gJtH1g1d1BNNI7E&a zr^2j8_}FsepTp=e0yux6g+Epjz%%#2JKA^w_!axuk?H~%g{I%K*AQS7r$+R9VQ1W2q`EpWFJU}dNOzIm$z&<-AV_}^LqV!YKGpEwHO*K#0+yGekv zeTyPax(YCK%SYz1RRG?qPmX4u0xY=EK5vY#0Pi2K^=b7NkUoCx7vewxN^N$q&h8dq z$GI65JNF6TptRL&`F;U@mMMPFI3&Qe`%AmNMhj55q-%9mtN@u>#+`Y`N&Y83wZxnj z;Pgpr-K|L^4>L`k`B?$cKRunIazTK7&l2VDE(!4Er@_LqbmC{iwxO&W0)(2GPC1ez zKwC)uv8@#1r>S`10)_zL$78M4I0Bd)e}DE1Ux2sohUeE75?m{*X0W6JJhWQlmv~cv zx>eC%0&fe@H7c-Xb(sKBXI1-W-XnQ_i3(S!5#aDLs`Z;X0UXa`=? ziuXU?clx;is~4C@1hxt=l_*lIeJwzX&^_4btpEyF=X)r365d8$rGNe`fKVx7_QP)i zocOOpp|Dqg$xCg_uly8XLUIfzdO(2aa@ry9za;O0lJhG@6(H8Wvfog#06Ow=p3=Ai z*zYJ3eVtGMUr(lD)1(3fiZ64ErW9c4)QOxN?E-vi-Tyj%Mgejk?~2$vvjD2cce`$$ zQvl|-$P-IV3gB;Zd05}90M=3E_mvhDAWfp&*}bR$k5oe3nwJ*9_554ayS4>raXC)D z+dc&r;6l!aBTsi1!14tB)7^aqn2^Qf6zng+m$Bb(W*sWP zhbQq`DTj&w8Nrp&M@gRj*UE!V6reMFy84!Q;)nX1yXs5@%3_~)(yWN6rjz3CzNbet+_{!xI<@58+s{uDrOq58U- z;R2$o9-L67AVkBM=t;$6g-9)0{Y|7Ign@d!4sU`GZiadp^hrYOP_hclogzf!(}^dp zYYX8?yY>0<3?Y^zTQO5-3c=1iE=rywL~Kv4YJ!Oni|H9fC(ML6P`gijbb%1(j_3^^ zUQEW-h%+LW3GuVR^z;EcA+p%#Z-=iEqGDIGL#VwFvu917xp%z~DgCw^L!5-*_WD%r z+AIVXAG{dsF2w4`r#V4hLelS>rV->PgvQCdhk*e?xc(=49vCddbou=SL3@NKJ<-(~ z6h@8(DUbAQ12O(4fbm}xe3(;)Za!mi55EFhWotW24{5|9}F8(P*e%;;3RRcmO zscs2z`b&6c?cjTjDnxEk7j`KYBG`R+&Y^LI_<7^9&q=jHOwB8NlRBB4$9dn(np#Mn z*I#+f(J91Ai|yNP>J?)DqW|=(4GVE3M!xi!Q6ZeCZJ@rNR|wa6$9#X77h-a)%kHsO zg-BGZDW76ph%C*I;YP~~@xEWY&w6Dc+-gmW9oG~Rooe@O-s=jnQ*PxDy0H*iqiYk7 zyA(pgn>O{*mO^;lpYw~hjT~e8<`(%Bg5H$CyYF9!&);7je-&7Wn_YjYy}Jw1`?gC< zDYOvDUHmrPh(c7)UfFLMSxEY?Sw0R&3elAGNyGP8A;LZ=%{X|n5U1P59!^du#Mh(` zGxL%QVK&U4AwN&-c>C^sm{y1{w|*(TzgmbY>(f7nt`{Qc>)*vwatg`s#E2?0N+JI2 zZe=+z3h^pv)eC=4AqK}V{bB@#SP-bX@QSFAywB_KHi4`V8>&7(tS&Buqr#r_j?zL> z?>_{?cMCBv>57(i6|pnJzG=~Y;-9JZ%Joqp6sme{!kP-?Ry(h`ApFJYpIwg{JJ zDyuEm6=8(2a*@*v5%k;V$NTGxptrqo;J{1~yb8>-5@w0;cDqAR#vBnw|JC;38jE16 z!;2_25#i9PK}mzD2s=lq8oVv$R{I80z!tB@0*lQx}I(2yCwd)dfRxZ2vYu06B9YfLz6vw-A%&RsbR<9TcnN> zjtKLdLWu505?+A(44BHLITB5;7QF(kR0DE_q7pV-ZeQ2`dMm624A%&YRv$@)P?P z+rA)m`a%1E-%Al%_*!nqUx`RxhPQ8Cy9k#Sy}MY}Awt;{`PVn^2u}AK5035>k@bnC zhI2lN@a)^y0f#RlqN~+C6w)QaUBlIL&vlc}!QKzY1U(`g?Q9#U?-PMFcOJF(hX`l> zdvI{dZxK!|s}5Z{AVRx|Zko@K2vz#`UmpKU;^#eeqmB@~{Z{r?juK;Om2%0aF=8}D zSqUa6iNPK!t688ThW;1v1ds7zJZcRVWI2ZXw2k$p5bVT_A?F zkJHyVi^Q1n(?8s4i5O{5e`!Z86(jNN)b4B>F&Y`AUH5FoaJl(yO4kZ8D&2Uyr>qjg zPVq;F-5N2j20lEq%U+D;!NndI*NO4R-_rT!dNKYp_;3H4jba4Wx8EA)EJp9p4U0vt zVyv_I^3cysjQnZO($8!aRibz3?z6-;)SijV!XJm`%N)K3{MR|kA-{1xV>)uN54>l*Oxc6lyEVU zF3wGnMTp57u-^4I2gRt@t7@1KCB~Kc239tQ#fUm-erH#V7$RP8x3A_A1|fpoE9Tj->z$2f|%%@Fb_o~i7{gOXcjG541?K4!wqK%E+=*>{5vm( zS3{$@*+ntvEX5?BOJWRN8>Mvi3bA8ZExMU5#`iHENu3#D^e_t(re=z9kJZe#&nA4_ zsQ(+8E5?`!8L{+ya!j{0ctRE9U3pITXa?b9Zu>_omKYh^{)`Xe5WYG-dtT*e z#z|l@YE^l>ssu&*uSS%pN$~ZQ^7_7s5;)i8ZZy@9;NC0GBY{&SnC`N)K1)l2*}@gB zPqZb7y%aX6I-OikscNv-li>T@jSa^QB;b!8{!?T~{PNqJdS**-OZKg9p0Nba#0w&W zOe8qBgzb=PD#55HTh=t2NzgxI$==D9WYaUsa5orFB^ct5RRr34;%vP+*kI*%3X2&F$lX>D{*l`J3O-(-vPD=1mw$AL^X$hX!JK)Yw?Scw1YJ67K7T(a!4xC@=!F+Z{`nWz9ZHiBz3gt8^s)p;EFL)jyh`dI zbnfJ(8N|-+u_G}zBv6wDO}m{%`2M)g=WmV#A?bxRM7*A?C+6>nr%K3sTc*vbqD!E? zQg@;XOG19{a*Q`|Bxu-Lx#uEJf(;}4pEU_25H8?_O)ZoVU63Od-eL*dqFZ!xr4m$% z>KAs%B}l38jWjBjAa~{I_Pr$%xZGNGSXe57ygGYn{~ZaehOW+DRxUyN7t`g(?@6F& z8hEUtN`gC1wjY&iCCL1E^SE=J1mcS?R$hH5!Jg+-(--v;%-E{2+MtQlQ>Iz`t|t;O z?fSm)pGmOgad2W^vjnHRlN@YX3C>2I3s1F?JSMa{*1VSBfc2!a>Kz0xo1q^b?+Bip zob$OKB-pF-%jv^Mg8$5MHs)Ul?{3Ym(OsloRtH}Gubb3i692DauLOt0vUHbz33m9L z+;;t^1btJ~*1q{o>OFdg&%8ki{3plc92%A&InHZb=|2hVoJLa>M@jK(%8?+~F;bil zE#91|D8=pfHGAGEOA$Sl%{Lz}#f~eUIx!QZ2h^nf5QO)0t! zcgNAEN@4cQ{%-d)DQ0f@XR%yYie&Tj+N2p$gbXWRXwsLWcB^lko}m=t+O&+lv!yWU zVZD?aNwH`LZPREIDO!06oi3(Q%-Z85$TpKA((GT(CkrX4saeHK7E19*Z}v$1A}Q8J zXC7!=BE@WzkqP?Cq&WA$<7wz}DPH}^tG;DNuG_S|Q-%~3<`d1ft(KxE#3rAyR*IoG zzZLxsQfQApsR^KkgFz12(jvZ2bkDpRrxKoP1kM>-Nc{zIv#D06q_yT|Mc%8aZcTL$RS*cY@g}Vu182=aOGX+r-M@bbF1uF9wo&) z%QEE)howZP`k7aIj1*-#St)kcnD-qyFU914i8T){NXhRfud8TMY_W0)p3aoQ ztLWvFgKR09ZCE|kTq*jt6#kwekV1C-&%8(>xu2Jid|xERr(w5AGo@1GZ`f6NSWfWO zEu%lWDFu{v3g?takxO%Wf2>rBwxk4yCwEA_tW$n$S}w)Tqc6_J-;-if@laT66^Sz= z;+Rz};f-f4KUYWU_WTyzcMql5*jUN4Z6LU)r|nK}lER@);{5H26qgzo2CRE7#f8&q zdHF3;bR{_|541`#Evh4X%PT2j-?sS*+NI!y*g7h`m10WPzwH6*SSmHIKQ8eS+#((7V(5cB|Daa6bAo1tuF*4kH9}%`gQHK9oo7GE|Wn_Qt ze{I_1Ww2q*ejYtRhB)r{p~vbnaL-0r@%W**#gkL0bk{ zl-DY@t_&~Y_Uu#ClfnDz*tTwP%(Lzn00Z!_n}pr5$7`lPW8C5JTXUYp2Z zaXWEf#e5mo{77}rHJ8CfZuWB6Qik={c3tqZlEI6-#;A0$jO^p%M$K3%L+A4%Zk&w_ ziMqk#U)jpYV<}0-ij^`bAELYDt&(B6Y?;-_8W|=uM6M2SkU`OBTU@!L44b+--p<-6 zgOZ+hbfS|CUlRtcI$cQo#ph!ASTL%5w+yxlZ}aEumBCeG>%Npw z8In|!0=mLwIGzw2zxjX+rIW21Wrt+=?JU}$6HRby_}P8@hzuO_32jfzH+3b7XvQ^udW2 zWXN0k@?mtE4A-9NxVK#<_&m2#UYjlhyJ>DGH$#ReI|h3uWs>9<+=7; zlJ8!rwj)J`1R{?oq{%Rlu<@1_Q-*hcA|9Mz%dpMl#JG=K8J6%4N4g4RsMPWuS1csB zDVjf+DJJ74b{C(O%8>mq|8bw3@bh%7wqLOfd&^#)ttye>xzXOamjB66mH(~hMwtx3 zRr@+eSID4l`Bpi+lJFbl;n7@8>cO(Qe)WAB{C+Ko5&DEg<ieX0q4K*a_Ft`|6!&k$GT;)R*Z9GfBs zCH7Mr*>Hu0o{5TnI&$3Z+UoOgx*W5gc)hgKmxDBfrWDMSW6m#?K>gWrTnxS$l4c~w zwPb3hf{7f4e`6$yQGEd4pz+St-ZX1%v8`SIKer?CZIo*T})GoE+}wAcxV>Yp)v| z<%nAw8n(9|I=?CQqkrp3Sjgn*Drd>Wa|UR zNS=06Vm=<1qtYR@)BhB~Z~n2<&*SANIrDbCOOhNfd+*s*C(E%>$#DD1b8-l_4{?i9 zNxqleXIrMpG1boEKKrs9nZM3v&rFv?$#-v7W`-O+qwA|QGUZsCa!W5YTaGV@<}}4T zIeDMy{H-S`a(vz>Uob$ELx26l<&jJ|(XTpm@EcnWofyv35S|>RuMY&j6%br{AI{uf zNO;(IXIzU|4&TIe23uuh{?d@0^+kmL5yQp}x5zO%Cba6d9KDOe7OlP`$C0Q1=KgnA z4v*f28*J{$!I-0bU0x+esLH9yR<(pr>%3xNogC9Q2Bpq_B*%)j##By&9G8adfqNq;Jb>6IB-Gn?gTHnMZhdm%@%?F91~ZE_q4zi5;7n&9S9c}TlMj**LnEgA3R z@RGWPPVJQApP#@q{gWIAjmxz(zsjM>yKH&o8{sM5AbN6-9OfIsK3?i0^`9AgX3|eN z;?*{6x%69(Vzh0XG$_aUy2S@B4a96T@ByrJVo`-HMg45~; z*L$6cFgRcP%n{cj-2NLt{p&{JaASW?a4*8liZ1{0o<&gkYBlPzcM(>E9BZ8BNBlO` zJk0Yifi+2~nwQJR>(mh4^r*&()eP|KzWaXZR;YB!F z`Xt}|KoOMo$BunD5WGN5=o>K8d4G!5~<{CwvfyQl~MZay+f3dG>`}t z8Wcsp^ZV<*?)}_z-p~7ap7W{CJ@>p%4dfUP&EK3S`RROD49l&-rOmz?Kk{qfsNd$A zbg2e8e{xI5U8#Z1sT9X^S8H%9_WaWi?tt3k)< z_N)o@HPD_>ar4S^ax|B8S-h%2nV0QW*6SLKnEsGo-CTptwF>nutu-i(-1Q{#9pQK4 zoWYTH!h6$l?^7RZuqCvnVaDeg#H~(0R{53q$GUnudPv-|WqY1|Cp@gTzP+ulhSZ5j z)$AXr!QJYG#m5F~kag8BV#*)V-l}m!=|~MK{tDt3jn*KlM33`Wo&t5*+Xcaj6o~(6 zw)Bppps}N>@|ZFOf~k_qscID5w_DwETZ00_m+lLfYErNypUSD%qF}eqwS8ezC|LDV zVdL*<6zE9b?mVSOfxo3=si8gvdxaCVYYa$#rV$mM#uOOzImf>>q2L<-azwm21sd~H zvXtjiNS&?^O;;=^NQiClx3{LCINnpf)|P_KldGG8=Tq>?ueI&xLJG`x4r^vFqF{4G z*pKXgbQ_!k@{LITO6sQ<7 zVxqTE@OX7WvQj7ob~c8Tt2-#@4_`NP(M}3k=W74(`ECl1xCj!XqA8@Vgu*VRSQ1~_ zmvJqQg0hC(mre;31h;)(^D2>o_>9rPJxLVu9LChN%=4%(K0<-<)^vsCX%w)F zk8Eo_PJv0G(kDAA6Jr)P#v%v?Z4)3V=s5(g@FoG@*RcA}zt zG_`KZQgZI0-otjL;-EsUW4H?yQ8HTf1UD*}LbVABfRRr^3rG zK8Bq^#mMtVg}YA@KBgc2GwnYr4%g-%7oVYG;vr4MpC@^}>@hXUr9x+q)skoVRQPl^ zrXIRPC3P1+56rtl#qqJBxy@Ip*znGCN7fB0&Q2=|bu6ZmI_)86KbKOmXhv>#-W@6` z%@!xPR}vmo{VfOYQAz!ykJ=?wRI<-)*X#{7RHTgE9i>F0V&n19w-g2yZ+Zy=i ztx+F%PDNwcGoRvDR2+Dlzz=Am;^({DA*wCJPphMC*lkoa2>F(=?}`2&%QnsGpkjiL z$-~A^H9`Sr~AJj#l2L}6ZYE&4iFyh{8v0-kctz> zR=5j)6F*)roN{1<=zcR_+j5i&i#ZO1AuSkRH(%q)sN;Di(Rd_o_g$DW? z7cotZhNxAxEin^l=oqOpFr7rhgwuK#TC{28eOH|Y=cm%}{dw&;PhA?CMAqZvXV5Tt z>S}xHOd8z64bR3H(O~0$ceC_y|mexhAZRaXHRjV zVWN)xr>84u(Er4*&s<4xPB7|rSw+L51%C7Yt)>CLH&jr4Xb=wBtc&xbq31>WY)gL{ zx-23szXZ@oU4*CGZU&P4Uug(~x6lw4Hg~<=HX2%bO(!>Qr(wU|7{&8DXmB{nGhDNi zh7;oL(W;Ro|BS9Ll4u&1L~OcnESB&x?m^VDcp4@t4D9=tKtu9~VFlv=4V5`dv<@WG zAdOmCy5KP3#l$soAeDv{n{9(19HYUdN;`GW2^unLyX!5pXb4%(^zF%}!I5!HuKY9& zkuTlbcAcXk)JCamb`A}xF+$TXc{FUYPCZ+Ck=&<^xeT&92g5DG_#cUZ=sZ z@${t9TQtnd4vB~;r9qOuw8#7o4cEg>u6?PXVcyee$8O)Fk$HUG+}%|)WL3{?oL57` z!#HpEZ&Vu87a#j{mqCMbWnFbFn+DM?s|R*G8V*e=ZTl%8`MNDyL>1FuTHGl-C?k42 zzkg`SV-h#!Vz}H>f=}nhVL?5KANNF@{(^>>8G1`R8);~uo@!TrLxagDQ%Zdc4RLOt z={av{$T|4DXJb1J3yi<6(f>#z^G_D<+dF9(>wooH$yXXW4PDMg_Rui<)6{!5KWNBy zG5GhZj|R`?pd{u`8l1cv%#IEb-z+&iX5}yqH6A-OH2%>@UHiXi(}~c z83E7J9oIbnQ(2)+Cv{$BY_+N6 zn7rot3mrN{YYiV>o=%6u3+KVmnRGC&-Hos?qT}-B80BBH=&&eGZDO0zv47_0*C*!C zp}Fg(lCK3F`exKHU28gYhi3JEu%%2N7ee<#~TNAOd+Z(bA~t)093LifvTFgX5H>9$)w}b`P}IJ z*>r?#W^z@|M(7aEFr?N8r zLlyb_l$=~m4IQTvi}q#ENM4bhx`9kOD%9S5x8Tsx{5x?#j!$@aB{F{@q+`ztes+n3 z4!_Sa21&I9_qJyryr0k!;P>&B(K9-J1>gJqtAUPtv+cYdy`-ajZA@L!YdVgl>^KtN zO!)KC4e@9r@wR2h>A$BVESO#0|ACIw&-YDapXeB06d}Iyg^m!Dj@{xQaEaDa|8i8Vqc@t~7U>CIgR2(NRZj28`9|3noov zV8W-Zid{Mktl0EfUO1frH?_4^g)_1=X5?_9=&MpR;3N_a*xcQ41Jon$lvo(vbnpc?r(Oiy3&6qL}h; zDFfDbGrlw}W1ufYG5w({1Jf5DSbNr;fzz#@y`wxCaQV6}$zwGGwqrSsCO!--sW%T* z@MGXq?|Fl_>lqlUtf0;eVBi5ges)1117&4B`x3U0I0vHp{I)Uh;nC0)n@|R(t@tl# zLO28CGpsXub}}Gb_+I!Zl7XPXYc{uI7?`vxzAj@g0|U03E`{$S`Tty(@0Q5G@^w2z zCP@rf_MbKydx(Ll`<~zJIKn_oifxoIje&i4D>vOtXCS=NBk@=!1J`DZ6^CXsu<~lV z%Zk&4ho%p`Cg&Kaj#7CxE{E_tx}mKzkAXurb<<@R83-tiK6$&4fwpl@bF!}zTtDo8 zN8MmxyYiNyHN^zi0L9Fz=iLK6tVf_kBSPuLuY#z(Ro*X+R;dmx` zC9kiRt1_W({drf81`~;~+rtiNGO^#<>rA*d6XP{+cKJ+Y0+h)8i*%WAla$!an!$wb zM}Gqi1197jCM_8dAH!CUy0F;r zJ0Er(NoOJ?exy7$lZm{B{wpEbOnfLV=2K=^ejcy;3;!8_~0jXWw7oyNJ>PBNJA>~wo}n9am0EwNQRj|qj< zlerN>Ce}MHNDYz@y;J7It*vFki`}2&{)Fg$?fjR;&zLB!RgJcNPIP}4WNiM5iC^g_ zmGzsLc)mT?K)Zzrqv=N?RNgZ2cb(^ZxppQlRQ<{t>>zx%riXp&WTGr#WY5R1Oq?*; zSkcnMgvRMBx-Wh(5fM;Febmo{%=GIC!7rkBQU3|XZzi-Ox~Wwogh!KCy5*xx_@z9k zxT(N`L_00Ea4ZWpXFYc0j%Q(fzEbvSRTlZ(#ct2gV4>*Eo?S;ZSy;7grg5@13oS~I z)e@$%U>=`nwnvu**Hxx*yJoO3&aUx$r~wNPpJf*Y8?!JVTbaDcl!diIwe0n?S#S$` z^Vnw|3;WhqE%mY@*KG>kyW6r5U2^K7%X}8DY$AV59auQ_qx6T96ASqo&o?+MWg%tL zq#yRnSWr6{L9=mX;p3#nDob}3?$rjro$JZMa(BhWvsbgQ#8NCX^`)GlB`e+^Msshp_M`w(w1X^osIE5BNnzpk z0})^62nz?K`!DFGvGB4lG*2&`h0@0V9!<|AyiFgp*3V{v9Z@8id71^sZJDPH&#`c% zye7jqhlLA2%&ScDSr9ybuVH?P;2CMZH0KHnwv8|--;R*sCVu=dD2+;y!hQiZzc<` z7Vg~U$6=v!x%bilJ_{=q&w2%kSY*D{E;~fZg01U)Iz0 ze`UU6*%uag33fU6x(OfqTsBa@voNn)=E?12k^2AA80k+IjNUklpAE6__=>%E(=hSD zzFSJ||46@zIelN{*~sr#oG_rs#^%qzHvd&(qdqgTWt=J-b%(-FP1IoHW7L-zT}?K6 z{`RNM(q@D6GDBuLl?}PeWzLSeY{U(2_~J5yjd7=Q1l|U0lq!cl-Du3lS9OtEm?;}C zmeZ5=%x1%B&5G&C^VraM6aMXl6&uc3#(n2)**G=spxM>=Y-sa{>t?^KRE9m-810}w9`DU2`@z?8r}(ll zf{ahI*0G^@rf-Se1~yI~QI#y)#0H)EBGo5|oWETY9lVv?fB8^$^maBD=7zMS>|nz! z{%_E!ooujLeKZRrNxuiS{3?%OW9knxb=F=sc63&*f3lAa6{Vj~TNByX^=mlkTM`?z z&7zH?huGxz$=aP0kFs%gZt-oy<7`OVxijoC*f6WS$#*@;M!l*Yz`=HD3y zm~3Qd&rkV}!$!9Gh8s8dY+T@M8>Wiba7gt(UMFS4aPq=s9gm0(#$&Di)RFdU`Q8&7 zh@PwVTs3{k#^;9Io%PfZ+P<7iN$;_N{-Zg;M!T=s_zC0NEQ8a+=S9@yVlqH#l%`H?o-@@5I5< z@F4oxr5tz}t8`Q@<6yGK2RwG=;OUXCPrkTw@Nuuo~YpI(+QN1`b^H7C9Gf;$ZEf2Nvug4niu!R<~~DAZMn3(a3fVE-pB3JUyI) z*OvyGoOTghgSYsBQ5@9uU2IL-!@-I9FXvp2<6wV;|7~Uh2h;5hHn$$&Kx3J`^S@*c z9xlA#Kl2FThr6uEIgJCe<4biz(m7(6WY~YrEo$&DJ43%+DZn%(8VKHTQuss|htJ}CGc^N@pL;qJ@}RFbz_*=8Dp==IJoq>asi zk5kmeF?bvGG<)A1kBsTsL2fuc>$6c)BV1D$C5^g<*tlvx2=zPIJ zyr-l@{WS+cvFGCKn>n!YY>o|V<6t!9#`UA`iC-RekGF_(eT%a_dO2X5&gzaG;NZae+(Q=!IT&l6<0AM&^m8|I>i$b~uPl0yCC9}= z<-x5p6}Xr@KHt203>R+>rzv}k<-%qKeN5XpE^g0TGBs{I7avvBUB|0%ajkkzR-q~v zS^sjs*s7EJSZhP18eA+DZ5arf$i$@G8>y^@dz* z{dN9WgfW-wQ#u+vI*W^cSH$v3{#gW$?^zQ1f07lQ#sO3hweh+Cug$9r=z zrsuAlh7ZX%aQlrCUoLw8Wd<+vQhVi(QK!dY5hF zLT;ORmP;TPbEsNxTQ_s@VM^la#4TI|to466X)70rxSLS1jZ5Y$(ri|4=VDyii?JU< zxww*cy+36K7Y3UPsqY3Y3UkeTQ za8dKbX(=m~OX@dj4h6(7TsshOC&t)yV52-z{Scct!ZIN zTr95D+%2C%^f_J7Sa^uUZM+k-=m^PczsKKKsa(8V5mytJM&eJIlR5D?7w*>?9WjDbr1jPH|D)YI~0J9~avvw@eK>LwF^Vc7M(i zp0|FD$v;o>nCZKGK@JzA%HEbQa*2+E@9p;Hll&R#eiJWp(LAr=WaT9;EDWsMyb8JK z+GXz9b%l$tcE;1pt6a!$?us_QPH}`aj!%C-TE#`) z^wz4Phg@9FCPkPjT%_LiD}GDmLbys*e3(x3ToE}slgWio^!~M67Qubdg0q#wh56Of zE97~^AM37syvFBZ^Qlcm%Z0=b2BN(mL|iDgw*?=Qka1U9y2C_9+It$G5Z7|SzqE!I z{+J8uUdzeKbzHRFy9u+uNN$SK!bWb=v(mpM-6 z&l|Zor>JGUuZatH&6~bco4NRE@;#f@!iDCvpYH?PxMW>#jnBWgTrj%qo36em<2`Xt zlFJ9u&T-emPaRzBRy0=6{6xlE<)x9ipSh&2)yuI@zi{z;=&AYME-uVmPlZkHA>T71 zbtd&2(cN$Dyv;udkNm)z(Oxc0zRcNlqn``wLhe-epIqoy&-niJ7Z)$axV-ysi0Bbw zLrUgyk$m&ZgvMbmeqN3APWnsmS}=-djuPHxzM3YK<6)N<`(}g!4}Dqjp6ZG`MBn?Z z{9r5(&(3*&@K+*7rTACF<9Q(4V|}=)!h@x}y^5O}4=y&(JiF9+*s#j^)|m-BY%w{m zvp|yv+nl13=1Dx<+jC}3sx}YzPX%b2P2piSf2jNMR37?2#J2C%;bDwS*gH*+2T%7t zUCwkK3hal2!t{Cgw&NXL)qn@L>gzTS40$-*x11kf%tP)VFLHDi53vOXHpQl-zsleV ztIc`HQ$3pT!ighJCJc}REapR$T*|3=J&cnjDd++S_;342p zOPTg6g8QcfpXJ5FE#1t49o{@>3!gYo^x@&RM5%=4OLQ1nXtv#thw}@Ad1~u};WfKnu##I*8fjm4?qkC)#;$f8n>+twsqF)p9>4U92yxU24 z3<}}lNJb4yDU{$;eGvE{jNq%S&ayNsb4{@?b1%=~s#4VM5AKQcXM$H-78sha~V|HF4z|^+X;N^HT27 z4)Aa?lyx;MiAVO;9DheMg@=xL!B1I-cnE#LSKoDn@T+IPW6Dv&|C_4U{4^dubSw{x zInKk-q;}QmCwREK=-gvz2El2&u4G>p!S~GkvQah<$z2ogJUzuj?MC$%$)|bX_3BTZ zbC$Hvbc=61NAw>$)^+Rx@t<~cvTZI8cd9(h-{tYpTVCIDs(|qQD(;EXCE{Nt8_Ac; zJS0t4Y0kUC!|BG+@oranm_AF(ci}P2)yj##g?c883gU~?j!Spl<>BI<uyzlViMiet!TMy9)iO^Ho9>M9}cpK zgFF&Hpm}?VfQRjur`K-~5uI921&o*Qpz_V?4^_$oy>v{?&RXK5cI9l{$2{zeb4h;m zgp3F4`{~I~d3fLJcgLchhefC6eQj$X_cuAYoO!{+*vg|d%U%(`zLR(DZzSWxgWh$s ziH9gwd4+#74=1$WWR7p;0VXa-=xsb~5`R7u`Hn}{ojzjdxAV}Z-6L23fwYfVy*2G4 z52-ahP4hc>$l%mPeg4b?V_UD@rLR2viu3PS-OYo}lYe5lZ#)dRbP1}y^ALV_dh?E6 z9=txCSJUn1;TXp^;K=|FZ#6ftkNo0cW%bRa_Cq`rr#Llr{w98Mo&MjY;s4`x4_fn= zhoj~kw=tt+y!RdTq{#7+IORgbZUsIHclBSLsmOysgB6DRWVwa|B}NRyAbF}aHmYVmRQl})nM zWIoLA%zD)^g^ylAjOWE^eDn-zHLcO*!&!gQ(XrF{c%i;~1!D#u)rB*SV`uU)_LjAt znIXCU=ZZzE5g+psc5gaAi;wBwa<6%q@;;#g}Ou9?k8Qfc7isJVPJA5aw= zTkx?VN&oB{OFop2vVzjjIMNId zD@eO<6-PSU_)x$6v+?pu61S(qW4$Mz>|@>dQr(M>iWhOo;?;y7owOx~*YIH&JWj{K zmyaV!|1^5n^0CV;a^~%Ie5mI6cy9OSL+Dz5dU^mKZ%mJTf4Py5liE*qo(|*#AJ6|= z8N`R(o|hF0!F)7ipWDye%7_1?$AJkUe9V4*bdz-`A6Fkz;=YFQG41H?lAGateAF)b zxn(Dx%+vU7(b>(1MpwrB=aGb$R_DY2MH628lNNgH;Uh%RNkwrliSzl;AUlo^mxPt0 z2lnyt=y<;2{QZ0^{1&t2MrL#o_0F7PHF_PwRQ zrWNy1*R3euSi*Z?;an=iv6Nj zKj0%@q+_R6&4+Hyu}O~}5?`*#QOlwbU+?lV^q`UcT5tP|XAplLxqnf@eqfj?c$}+UO@D0nu6E(dFYJJ~9rxNM9l0qjOyL$+0p%;*R#+7S$5p z3dVmt{+RgZ##2YPIx;>6-OI*3aT%8h(% z8evy^5oTQ{L07ktAcvHZi16{T&b;x@P2%Jz}4@3aD`ri zkX}Aw{rx;l`uTV|X~x&i0pkDq+qZ82;-h@`-=wG^KIEvAqpbgs>;DZT_7C&1UV5>p z<}dNxV&(S-M){!fotG?;7of&&d9~aa0i44%+=Pk(%%5M}cVe6X(tRrQRpSNtAh-DP z1Qh|URXPH^GYDE$yPL4fYTsWt|h0+3yf3OXhUpmuZj^fGM$4!rTD zM@w$lVS?|jK^P)C5B26FS6dIDsgcUqb{Ljd!=-XU%?1z2&x@tUfk0NM?I z|2#7iKs@GhOztcJg5)QU2AT?>eq(2up*e}qyBOU$TY#;0W!@EY1&Cg;Z7)F1v<-r_3j}1{VOFq?g8<3H*QUI4B=L7# z`CRNIz|y=UFQb+Spd9YeY7YVE6~9M^odvk$a&C!Wxd5GV+PPV-0{mnhR$l8Sz}K~} z@^n@TV4FW}*?SL?_YU{5Wvc|Jy|BGw&uRgRMxxpr)(FtEFT7vQSAa=PKE|@O0&M&@ zC;IFz;{s3sC&+ z{)ID90?6K~#czrcV7bnY=vlD>q{gw+zU>v@hj>agHC_O%?wHY|2?B&LYQt715Q9ltv!kO;9}3BiWC8^oY+&5p1ep9GhGKV1fDLW>t#T#AuYsRt)RhXLXDc~y={C{NiT@*_ zoaE{I`sjj60bY8lSS#KWV7>gtq5As*^7ly8TvR1Mzmm_JsD}dVSN_xQL=m8Vj?i3% zCP0MVPLz4e1o(KyQ(gZNY1dm3+4ER{>Pt?Y%sK(~g%2cWKO?@gXtLPcK=4Vv z{+|1S_*`So`{9?w-%WWR9yJQ!H)g*4F#)vYGhoh1`FOTLnA*wkc_pFC8m{9e>MS zO(h`&9|rt7#tSidMZFJ2MTmu*frue=oUtzmX6_d71uRvxIoI|4X-lsSp~o3$Oe%6XMgcTf1v#3(5W*W7ZYT6+-&+3L$*# z-Uv+G2_MyCLPk~!k#BNV@42TCy4%uzlz9m;jOKSoyoE@=RM5S_M+l|W94)K0LcFzD z?LTgv5UZxTR=2Jfg3*4{;^77%j_=&V&)z6R|FM$2p@Bj~XslYgI7o=Vw}B?w!GxFk zH)h>ig?Pu5t`LTh_Q|G)FNX?o>GQzz_#Hy5Yqob?9U;UMi_WJeyM)+FQ``S7}%bLU=5Chh#>J{~+o@(A^%uR9{d*taw9SRNH(!IenL_+v!xB!{={$AySn zbIgc!LWryZpVYieAwIhl4ac4oV%gL^XT44dG3YK_Zg!gZ;O^xK3TK5-H2*W$a!!ck ztkz-51tIR-bJshcD}-UZqknWh@kM1-xyMBz_Bc}XO)d)&^v$(g?url-OM=$76bUhH ziGnWm8o`xi_vic#Aufma{fxOKMEqT-dzBDBI!$*rFC*t28UHEX5u*E8`p~;_Ay)pH ze27&kgykfMnHTQ~vE@NebHW25cHLW8?N=>?*{3O`)-}Y}7TFYaD#0I|-~N>*#B~3~ zv!x8;>#oF;w^%~-4S6f4afC<<;9TFz6XM>7+6_wuLTrxTYd&2hgj+|A_8+kjR_X=Q zUrU8}A=PoNsTJbq568n79+P&#f138z5ggwFeSDvhah4F=Zq-2i{Cw;gjTgj6Eo(OR zyc7b_OP4-w6ypBYhNX9!2(JIs0<)Thqz--0xt*=V&+_{}t$ZuQ$>GxV=I@1gly~f% z(gz_z@2Q;V>=43Y=3)=YC*oJOj#SX>j7J5Rdy?=7)Eayq5}hxPK$M7z9?C z{UGBZ{<_}yJ~F?GmtVIg964>oxH z6~g8I?7tSHLWDbLxlE83LGq^h<_`rCg7!`@YETqmR+BNKYMcm;U;1Kl#*2`doZ`G+ zMT7}a!g(9jMEG_=YVV{Wf=*plz>J9^l=P|Qk7|mL-{RH(UP}a(9p1ZylSLSa(EM9G zRfIH7?ww2>5oj)N_eJW7Q2DegaLo)6l9sT7Y-ftdyu-Sr$%Y~fnm(u=G7_Q1zfrkm zmI#XV!h;-B5prUZlx~=dP^va^H+_x>n~%E3@0uqf>$SJ6^R^VhbL(;+TWb+sB=?6; zu@&L!o4uF**opAbkN350z6fmo+x3ElBJ7rb+EU^ug7(+bhqIkT`08)GGU{p!lXpCe@|D6uxIOm!w)@0sK3IQ ze#uLO!|Lx|9`PnTrZ?Z-;Ugl?K^|Q4S}VeIh450_bt3SnL(iu9lk*P|lSem*us@Kx z@8d=h%ttzV9|ek#Tr`yVAc(Y|*|nq~ScEMbxavo?iEz>2!0#Q~$@TS9pNL}NbHsw_!XnkfqB@5;tN`9A_ zMiC)JRefs=O@!8H*R_ER5l*I_*y_#_!CJpD%Z?*L#!UPAnLH6Jw-(u{2}Jlr*Lg4` zBtBHR>Ge@8g7a*QX^@Jrs;}!MyH*4X=R3kXkIA^`NqL%INAh?X`s?^J5jHh!b&P8u zcsP6Wx4sYo3753IUy-~Tnr}P379ruT{g&BpM5MlFvBlIDqTf0@ow02qoU4Ch(EnD1 zfJIka-@hmP>^yYr=?4)WIwZeieH7t)T7E!zC-J?>i|&hGM7T6$RhZc&LUm*L&cq%O z`94^+HvIelKUd%2-%EVw*|&d1zX%SWhO6y=5`A9G(U>(T!j2z@)3knz;H5pHJ!V*h zMS55HeIp|1y^G9l|0hE2lI?pN5r%f2uQ0jLg0Evs87(5DnE{7||2MxjWDB+YB)V%r4iyohinb^w+2B4aG3*SRE@c z7Nb=6Z}dYGG0u$b%qTGv z_7bDbA&FV)Ek^aVVY5OXG1;F~cK7UBG2*^_^h;ky;vMqUPVy)GSgQBN1c;IPLg#Df zCc@*Y#LHh#1!6eCtalWSfi zMiQT1V|Y!Bh4Ws@bZ>|;OK;n6ty?6{uHY5wC1QmCdR8&6OicEruCbN7Lwswk`QmrE z7%@5T&-Pc6xHrusd+v$R;Mf_``9KUEYyE`x)nXWJue;S;BSu=~Q145s7@VHMP0#3J zM31oF)-uJ=ui2a`VvDiy*M4xhV({}dbs2myJ|xc3tPzT_X|G_`eKBdbwRK&&l=yq* zfomnT1V>h$%8kcjGQa0|=1QFytCS?dXC-JNBnYN zZP}Iq;-5v|cW(YA#?1^v_f11$%y$0j9Pmesi;F!s_>YLe*tk4v-9It5#yxLZD<>iE zv;AA)t02KDRbky4MG35ynZ$dKlc2WUXu<075`2He()Lo3AolgI39Hm3u<58X^VE>w zSFPJ-kBJi8iU_V)IZ1*k1v+{wwIwifNVw%bMFNIlf|vU=2}*5rC%WrO(Co7Phud@s z=G(9BanqL|r>%3GyMcu47i+S_-ADqHdX+Qovq<~;f>C!<3Euo^IJweX0?RoRdyhF1 z=s3Cm@t7yUY2{-ro|Y1%#YQ);vL^j{D-C+tO2~8RHCEpC5=ffdkF8lCfd_hj`Z!2n zl;w47?IH<$ayMD6TP(o~OdnjoRDydU=bAS-OVIv?)v|H9gzV2z^e50&fj>3~@9dRejIUzCy?D~EoiyZOf&{tO0wU>&B(KDpbDV<`jGk|QDNL3i?pf>n+Cvh^ ztCT%>dPG9r=Ud_R@~8yoi<`|0jXzowDN5GZKhWyG+NOmq7Q&SfN^u1iKr4r)cF#pf*;zPPae;uh7_)hL8>x6 z;7r%gfz5X$*pPRyJG6rMLG^=N6e;O+(Ae5`-+kgQX2_A0DiW_Msq6Sd3G_ZB#r;Q- zz;<@%v^<&wPqxi}Q^X)X-}!@D%922TdT#Z7js#WyW>4rm36B4~sUQ>(eqKyi|3pN1 ztB(-Al1Lzs-1B)Sli=_9&%eJsl7Kf!liUA9f=q+CH~v1A;8nrG8%hllOb=Yh)qEkr z7w5Tu^j;DCX4<}{uO*n#-6ynuBf+U%()CU)6677u8ROO_0bMDu&gY#3XW#171hx}? zKQz#Hbdd4&BfTZ|lLWWh7wDyYmOz&KAtB?d1R-BeeLvq##usJ5@uF`;zm~RzcYcum z7o1R{^pWv*)p~$GKzuXQ{I~9x1W$FdW;G8JiNGCRf_t=$`6~=rD&a3 z?GZ6Sif#qT<9(V^h#Na2)3l^e&mhx(lchNI!(XvzsuaCF#lz(~QVeb$Kars)#SY!% zWwIInZ#U*l<4h^m^%o9*G?XGly876Fu@ntkwC0R4m15`l@7)iX6u8jax6pNy?js zV;jilnXi3vH%bwnrO;gxD1}r0#+$StDbAm6JMbu2is;qnB3rjf$@}R0lD}`4BK1sW znfwkZu0E~n*NTwB(!(v#be9y9-~4#%5Glp>;HRmpqKPgw?ao{FNMX*HJ0o_ll)N8! zXUeg7DRNyyEbcKy&1DWqc~ z^wiP_pGu5>GmjI#HhPWPosc4|Y5I(nnNnN|wFuaJQi_qrL-+QaBIgq)nI1naMNU^I zwcxB2T5j??E6+<|_jk(zL5>vTzR4TD&LjN%{A%7)AjOGZU2lcUQtW4+J2UkP$GuO)=XBbEt2WQ3oN@+IRR z6W)HO8yVC|5m?BY?mUuZ`B94B?5|JVJIS~@&amC~ zh3GML;K9KzqQklzp9?)w{F!=Hweq_Z0nU>REBxcJvd`7gX!XSyNWq77?*8aQ9Dlt=DBt8ot843dG(yDXd}a-wXf$G z*^z!ekRpWhWhe@d-w?D=hAT{U-6TgDl(|-a@||Qjwx)ToW{C{r% zlcD3=%SV~kG2NW+lqG}CTaVVD zY{JKcb{zRn29+EW?)5V=BzjNYB047{bpapfe7+#V%jY9~DtUyTz+Yc13S^M{SM<;4 zk_=e&+d95bhMs7})Vv}YUN!Q*(XYwSx!v|)+YK3}_xc+v6w7cR$oi{EDd8ubQ|ED8 zh6}64)<>1guqIQX_iUvMcK@%&a!-Z@pH2Tq(Ur$j)pg;NR7z4*N`z8`%rZXLJkJ?I zrb>tksSF`enKOqHnTqNarIBdl&i4{&B1LH+Arkub_t*LDbM{$luV+1L-*eB|d##k$ z_3iL^X1Czqt#&-lQ7w|b(++Rt4qNkk?a+_eE*bKm9Y-B4gtD93A&@O8apzGxR*K%U zeB0WN3(r>M@;q(F-3ZygGSAzQu!uL_x~(0&yUL`(sO|7DpZ~Fd-i~VxTUwi##NNo2 zO&{6qNIUwZYe`2td5@yATvYBRek846cuBK%_kRarb2iD=Yq->((jC+&u&>N zc^u=MisZinF)Fqcpa7bI)>z+zQ`~qIS)O0Fr&6Zm|$fTlMW7~~ySyVi(^VlwyOK{97 zWm@M`QQkbAdg45>U+}>G8%0#)`2X_hy+DOzS=R9tm#ElXVsXQujEbdt>*t4;6Z<_D z9jT}!{_k1N+f_}4YMx5{q8cj1Lc(LUu2Eq`;RYPIK}FCHyU>eu@NgN%{`*R;^54v@?$Dak2EgX z_muE=?mW&vr@}nCJcrgs#R;SCh5x8j9MDkDS7A`mr_yZf&!U36CS0h9L&eIcJ7*Xj zgzu~OE}Y**CF@|ysw0X2^$9uxtq3Fp)AE>Yl`tP>x zM=GvdU7Z~|Kt;vM{*{7OKHeoT|$99E9GeTyGA(HN}h)G7X=Lx6=^sS?w#Hj6$B>(h{@Uvpk8f5mL51%om!Lsk-Wep1&h7|sd9JZp7_f}0FwKg=AJb5)R zU`NJDb*}IR3V`VP_N#_G{*sWW~@>uaR5D zjHO}mx&p@1lQi5bUAoHR6b&0|ejPoXM8gdcn?Fy^(D3x}`XZhb8aU^?cOi|~<1MOr zB7=sL0+y-|&e1S0`j^e0Y#P+gtU9BaN5dJR8}B0uXgK!g@y=U?G(?Cy4gD&n;g7fX z1?3VNA}@XkIb2G^!N!r@|6QiRHzgu?xPpeZsi;DQY8pg8%o_;4O2ZGS#dgsMM}x_Vn$!aiXlPA) zGgR9|LyCUB|My449}C~`C_EToy3MOa4Mb!&SBm{VVaq*tCY= z4;pM}E$WUVG-$7ocFG&2LH@E-=BqIh2RwgozZkzS=UzqblNJTKxh#whmS`{a#4N{*%rEI*aL$*isW6$4`!TDi=Rp zMu&os#jVAHbgUG~i?tD=lX{RT`?FWkaj8)5P`3yjy*&|mVr%J8Hwt;}yN-_N+d}%) zVsyl;*xT}5oZP3j&sRl~j#cwkOCFb|ll|hy{xxl;YBAx6{?Udf2Ovmmc2k!4trDJmL?x7lWIzFvvHX7BWgD)joV}~{!mX^QF z;&kaKSC&5hT%V5L5`r%T4CqjqXjF4HqQfd{ed`4iI@CJ#f_ykhi46l-VqP z#F`FQ6A77zwsfrcNU`R1pd<9mrYvhGI>J`c#&ezN5ZJ4J?42te`ks5&%DB@Z^2Y7e zK~FleT2|e;<3-2S?|HZ8`O*=DKDNakIzF5qTbi?v4xt|QzPJ17;O#s9esd5V^z`o$ zhl1${e=IM5?;stEytH{1gwk=DvU>oim7WF6;C z--I+ebfr(cU}VrSWFciOnnlO6(bLcP;n!Q<B$>XHfk>?@&Tr&F`FLK)F(WZxE9PVi}d;`h9g4ySTau~k>-c*1oY_pYJim;B*x z*RIhamL0}3eS?mqes>MCn{;^1$)@Bs&@ub0Z|1{oITe?dLTSd^DZ=kb;mZnt=~yF|))_lX zN9w9YGVFh3+_|UoCFe76A$_S#I4=WAz4@I_`51T_D0_MJ5(c^jo@N9rWuT{af6;vb z20S;3JX^Mc0j4Wsx%Wy29?N7MyD7}Ty-?qO3)V2m^D(MiL>YK8D;IoqJp*6cng$>nM?P!sqx@rxD%+gmzjb#)mCkD1HM z(r1u$ngWOt?eYFjeU>v!l@rZofckzq0e zwhXNOTX09)fkEcWzWwK%7?^3b(Hta2sOsB#H|x4FAl6i%n(fZOT-;HgFP;qW|BAV$ z@6EuUvD!6xz6>P#^Irb8hk*dOGggNF4D8;hE?gKu?3Wds83|(G?B>-A%t9E@`fVv& za)`)@zHx9Ol!3|ugAVH>4E&T`=vr};)fo)3UuAIV zp>qr@W~%bFWiznczDYtdj{%+BQ*KcOM1J&{x{g8y(gxeNC|qD5w9%^hbO{4Ibb%v% zr3^?f7P8PTXW)^Qi(YOe15L-?+YeVWz#CF?%Djew=w%OHUcN?f*Az0Fy}& z%0TCqkB^m}lHy;hB}3AlGoY+}L*q*u1G7g`C5&kdWK`eVQpzCytJkudVUhF4mgT!~ z3E#R^W^Q#daHZk;Nuh3n!}k+12YVQJ=U4HS`iA&rN|L$dJ@IS$Bs;N>fm&Xfu@C(O zPrVyz7+~PCXja~Z&kTq}u|%ea7#Km&ZP)J%*jxJ@xjoE)P5fQQRigyQrV#tkF$Ri0 zCWNpi2tF^Tt}0A1@FI-8Fm0N|(?$N1UuPI#Yg}1x@sEKs5;ZTYd6?+kA@m;~FB2>F zzOUZL$Aosm%9f{#nMgfyV^Vr46Y63OwxT*<_=xc-XE!c27S zAO6I%hKU*4(jH$?Ca%aAd~aRPgy4ZJ2`OHCCRnA}yCdoTn#R>%zF3!oXI}Dh(y>5tC!O4&29zW@CBBu8@nTlA$bJDE7}tN)pmD-*We z%%%VBVxl{m61vipiLllkzazYuh)KR%)Z@d%-rFr+dV82SC*x>XwvP$_<`9#G0ZiC? z2?qoQG4cGfX$>Qo2{~Q9Wh#f5_^IoZo*&9Y4oy+TA%y2TTOfn|k** zGqF%Z_XxX%iDHWXR;?#Y3^8r@EN<<#SQ+$15gTH+OBG>l-GtTn-DhzGGtezpn<{ z`k1)3Q7k*Jp9%Sth4bePFrmz>&I#4+XqgO@{0v;?nPLHSPZPx(GqGfeE|j}}-x z%7hc+xz5Qkg7a;5^7jcQ%qj{eU8k6MA1#&iWSWW38-i7~&oW^ceC%J*KPGOOn|$Y+ z&jMdw*HjoU3u3DBn|t|KXy!EwwOYbL`?oFc?kr_t6_pjRX*mlKCn21*f`w;e$9iX1 zvhaC+6g5bMgy%EO3Itj=#}jVbc7xkEJdP(NDty?&-6z`_YbbTMbyqUEkAPV8kNN={vE= zlm(vnYtN6Gv#`bbL&%^d3*|iHYA!Y`vhQwS{c}4OQtKL}G#pvrJs4n7wUdSZ3x6|L zyRslB5&HY|E*3_++G8d?SlC|0CmrC$!h%CvhhF)x5WY%@WwD3cA0hto{yrA=9~+^a& zX%q8^pEgoV31yO_sIS@>cTJof!E3q>>kG<_>sSZS1f;bk=oXBKRdw5(x4X=CHF z=4-@X(H1GH|FK{>`TpRQn=B|EJr=&<77Mg6B_9(l7FY| zvpkFVeHAshoWsHu+1EnrI$6jp|I>A@iv@{2vJV&auyE5ds5SNt3pKh;gA?yq*t=d@ zKBSM}-hLtLQ$HEU)@^G%2Z-OqSPwfs6aObACs=)D;h&jDz?1I;PZN`aIwM4{k9lsx zC^<&&d%bO(@Zk7GO2uztkIv|Gu|F)Fuka1Y`^&=ff)Ul_b1Y>1wMjRc$3`l*VMRO- z8!I2nRy>@~#$KH|vp);iX#CIYulhnZtj_&x4&!6P*S0qMzeQ|ldK^FceK8w?Tit?W z`PtYcV|CDfDH~1C9TLiyv9TdKxT;TpjnlV^UvCg(L+4YkxW@`M+2^DceqM-;l5bbK zU#?`MyHe45l`tDe*VEYcB5XWrirts7nhl;g3x4_AKqa7Ct_^SXB$*`#M$Kg(`qe9upyzEV|!vF8>igB$p!A*E zU8T+jZNEa^fCih?RhoWFK2YI=#aFexT5Ncqp1V-4%?3+TQ{Y)~JTY55wlQN}wcRc6e_8f!Bn@P6Jb zX-4E&uCxQWe(bcddHnv&TF^y?AfNoC{7m$o9~G%~)1Kikiy6Mv`% zzocal{ckp=3Z7%*o!D9jrz|#(2RLpi$Y#Sti7oIZhmBVQS-cW?Y%rz^mhQ(RGUbs>6S`8Z$dsFmAYuVVnvNllTI`Mz{+N$UqM2|(sua^G^j~n0E@!e$Ol&MOS zRXrPF2fDSh8i;@MX>DD%2ye9o_lq^M@#)275x+ZZaO+NYR^MggtN(-YANL4$JlYGgZPt|-@CDsjobH2^JZTX z9z|roHSQ+!Q&q2O+AAWjR+cV1Ls%ncrUo)bssjqkbsP{h?rr8Eqq#$L;TAI9 zceO$GTRE^h`j=KL%K^{KW4+IE9LV?0Z&8uwpgH$}W2^!P|E2zzXjkN5T%+)+h!O`U z7C#E{RpyZQU*3k-RmeC8qqh7}j&;}EA4zAqVB%P(f!IJ-{mDD8t7qs}uYH={< zYH>b7n}cLO`@U8k4#e|b?pUtJ!42P@GIxCw~Gg#-8QK2Quf=#BgM zHO+_v-x>d^9%BwZHXjI+G3CHV>z8e~83*s*P3yFnbFfeRw3dJ+2S0;z&D^axNYjz= zueRnucSa{~+=hb%l3kq!b{veXpV*XXPukvvNpBrEu=ITLTh@sKW>Wa^V>>y>x2fTO z=FCCE=78%fT{+kmf8EE&jRQee)TSG|I0#8(PyKc0ka}eQy|eJ-pyli8j)L7B^u#Uh z8So;!F-ja!_2EG0O3La4UxJ5(m@V6nL+Ti6<%{p-z*ew-ICvii^#|6tJ@Ds1zr2UD zB!C0Ht+JkOfy6Jte3Ml{9MpaYy*zP%gWJ;wyo^H#Ph=kL(w>) z6w1Nh+L5uiFv7>Gd-GZ09PIijEF~U6#xuj?7;=<@2YM&7A0FdCY;(@XrI8%0+q}cX zBZ`Br{|?`&iRR#-rk?h646#qKgkcd&@L!l4S{O&JD@n)<#gp+$Qoij-;K12pvgPb4 z4n)?QRKH5(;O1hvlC7sXa7wJIJbs1)sd*2Y+Rk!dI=p{yZ3+iNE!QOiQ#nxonihC3 zjRViO3!g5@;2?I1=yvx^4mv+Ruc=bIEAa zB@Qf5cRHe!1BDph*U!s1m|xkTzqXu1-t$*C1Xd6{Pw8qss3haxaD=t2n(#5$_Ndnt z4*qWC0_e&CoE5wX%b`ji! zc#IalB7CzhbllZL{GmD!cKtO6{yPTBdERo6$S53ee#e3Lz6CpK-VATy319su zmpgsnkb0a6Io16f7_y#jo%zUt%+_Y6{Q!wqnXRX5e|xHdGc&V36I=$`!E0EU}K*O zWonFrfq8>BY$iBR-tMAU{+ol~cc+@CCQ01K9`d&R!-3Z+QK5=y4!(FNbo}{C;_q}# zmE9}{vH{sCm2)Jnk88*M{YUsKSDxs=!^Q7mdQtU!F2c0#w$3i#Vol7xKTZp|aFKm! zP{YSXNyDPzd5gISN}uI*UBbmi**kI9`MJ>kqqdZH85atNk5#)1kn8i}cHUghMSz&~ z%Ec?VxTMkB=Ox6&$<`e$w^wqJx_wvOGGQ*%$0#@Vh;T6;`}6Vr)m)gF1PlnR<)Y&5 zm9>GQT%=pod$z3O;`rgDn`^|l_*^Kp=FkQ%oV8QZpNn&Wrw@h2H*z6gbN^a|Bp10} ziOviuE~cE;32xrR#k-l9o|w&CJfmH^)hWY;nrcm{{8lcmRz_S*l;z@Po?z`;IWC?? z9B)^X=OW^6-Bg+a7sl+nI|dZFc(^?)Sx`9v9{J z1a}F53oj35xIcx9k0N_6Ju={e>A`%n)`*MRODkl?1RBJ9QUMFuDu;GHdjUUyw<030Qvg^D(vD;Rg z{>y<2bG~k_wG$VI*V})s+R25{ysDKvE?fj|>9O)~C4OJPhg9?Dd!M;zB3SYGIT&7hgoq-|F9DKTE zU@yUOCVw5pp9>y&Yw_a!Tv#3cr1Cp}3xJ$%cA<5lM4kfwg@`bMhXzfP6He^+2>&D>!9~5`g3D)*av}UY>hPyy zT&x;>ZAXdZ!hgXg%7rK{9vpHpn2P4&@s%*AohP_Zk&HZA7t2MHRZz{ccrL_>O-BMw zaxr&uxyiExE+oSr*GeXmejog#W0Sb}q5re?^=U3v{0lPCJj;cBZpTPYGLauDSNk)C z3&ACakJ+Sg!ON`JS(8reRBbn0lu2-_d1tWq92fjo`W;%c2rn;W+~y($u4D*K&^s|i2K zub%pIg~Uber>Cwpg#XQJ*EH60VQXs|C48OW$Ost@yFvK+UuHPxKN64r*On;Vl(9XrVFV<888W)x)d~TJ|3GPnE|KnkDQTjIb zfiH`TnY6}Nt!!dv*E>EbE}1tJmYODXaIt#<^7}ivNTx+j8g`L*n|csf-c4|wTlix^ z55djW?d0CqgwLK0GSA*{F;Kkb-R5^hUu;sv>G#B+$7W+b_i_<49p`WUf%r30!1roD z7k~Fx1@nI*e%fJ`d|-f!eO@z-^g$9Q*7qkAzHqT2BFQ9kh|F77UR6K8lKITYWUb?O zF6OheiyM9r|M+6N$Oz$Wh*Rg$pCr!r9ZPvNO5(q%)RMlI)v+y~bkum9uX z+R2iqf;=5KcD}+aY<>rV!dl*RF6cl?!moJEg&pY7(a|j8>wu0>@!ZU!4wAoPWw-Yd z(*JE)%VYiy7~Qpauz6Vr)MNNxoE7MRRq>~f-{fJOsBzG~UZ?|+g@R=w zt2&@n?leDAxC1I?4M*OJbijW~af%e<#(UWpY2|A>aLG1CWzo70)c4p91+DLZv_5V! z#X2xKY?GoQ-T__j=p*?O9hl{5J2btq0|u6AFwy>h!owsc^pr0xSJ*$xccKcsk9t^<1{p4W@XcYvgJGC!%%fpC#`!vl&PkV`MA zvQ+ATxS-xAj%g;UzkTGE&Sv7Q3)SSP+c_)^w9dNId5)|vj*J#H$b#M zS3`HvY-pDy8H=3C1|=%$P1W)oxIRhIPQ9OlelgC$o5HzJ9`l)6+nfuX7>k^@?s-rd z;GWqsorix@fDwNZJ685Q%*u9Oog!o^B1sh*P{_z)llr*w1%wz>b?hR5^=MKDu+K|j{%2B$YpSH@gCnVj z5-8@I-Lc^zBKKc44=4G29nRT4d#Oe8*NuABoQQaWM>Yv>#ao}_jEs7@U!N%DnElT) zQF(8%R_>mJZsK>$8D^>-Tt9~<*^d=;rZ! zid^+u-NJ1Jl;Pah`FnK>D1V&7SAF@GPwDpZ91d8SPZ3M@o9Eb_OKFq#`ujp8hw{&) z?))d^EQ(&26V-Q^=<`v#)US|Akx93=Nz6S%5s;JSELfOGsd}=}^OZ|HW#;*f`s?>1 zDO#T!Yt3xJDE8-L10n; zvFt*ou}X7fPUp)A!~~_wj;-v6wyN>xL&C4&)&Ehrt-KSasSD<69mxMA=I`X+Wy0^_i=Cb)Z*#a zci~8ze^;B-@2eZGCTm4*lYKJ-b3aLaWlGiSTISt)9FWsa|5bGpW6yk5o@Uk|xo}DI zik<%N7`5AubOII_99^VoUl=`^nDVitf#=evrn1!SMD{JF%!e4MBr`FZqD z9>S_!Pb@f;hiCHEH!DYT@z+Q2OzqKJNOhdC;7;aXM_cia)hBasV_e9kdSMQFZA_QX zFUm&Mz11QO4%v9u7v_8Pc@`}A%yucpXW{EA=KqIGg$O92O6xjb9Bp2l;lN zR?dcV&?zj^cFWGh;D3)j0vBiEU4MFcr(FgvuPr#@_Bb60C(Sh*RMYYJVOr9LvNWvk zowW_wmWH|y9%QXzNhY*XBpRD`#mbib0Hf>COInv_Bcx`wKv-dsw?gJb*CJB*UC zGJcE3*@m;Yepy^m#PKX51+Vv?`hEsIa}QgV1e^im_=nilGNkSQWV`LX(~$n~>Ybb2 zX~Z~x6-gLPLeIAwU0LUn@NKAM$lfLi{|OWg)N>Q@-w9{_|@Xm0@CC0*fwEM#XTN_ z6#xDNxp=6&QT&(6j6+s`vb>XD9L79;960?W79Uo|&RlDZ#ktKdJml%t4|}BnA)f#tg8=qLK0Ei1o zn&ctq4-c!2g6G3D-`ssspt=VQIIWGs%M*WxDU3*5NM3O{IX)6^rA?E4Hb!D`$(P8o zrsF8mDt(@lb{tk7sy~j^Wx9p4EbPkCA+KgHu6%$FSnr+t&BXkKw_@ z1jY8zQP?h?tNc}V6rXeKkA5*Z3Mr?-scG$_SX7YsY)w-Hl;)EJ*2xiAr%+_G>re!# zUopbpy(j{~y_cJkKOVu!xJ#$JgN~rom~wTW+Ywx(?>O{q=Mn5*usJ4h$q|e+my{^I z3y1Q&ZAQhZ;Uus74GXv3;YdjE7!#HY2SZ>}#FhEsFsv<-^LZVH&+lUo2V{rgl5a@i z!|*UXlc}AqR|&(T(+MRNOTsX~eXiH^K9sz#Jy-N8B@|=o*UxNB3`HDg*>#^Ip)li* z$=D+qLd6!t^OQ7tb7qi1!b5o9wL#;O(joj>yzGqQ$U*46c$N`y>mc59ogyb44q|iKLjR_h zA*9ah#bN)@5cEsMmx&dIU}18`Jpum^tS;4j(EK_WCxUaV*Te>+q`Lj!4$ojHo!!O9 z&mTr5b|HwB0&*9XGSBe82+Kp>2Lmp+~w3c#|d6yBwRf%v`Tc`{FG!2h40 zW;h7}xb{)lBF`@X^X=}=Tzk16FIrx-R(9^k3Xf*@hphdGFI}JOXt*B%yq88S#P_3V zQP-QkpZ-WOP!`oF@yD!k+kJs5f0(Xc+I4fUKlTQBXn5)R!(F)7|Jf>k=#)RV-}-zX z7*>9?k8S&KVkYz*%W)sJd=PHhkgyMj)@5JbMA?V?9%%|atM}n+?8B{hAMQm*pQ_f^ z5BQX2U~5D_L6^?}vTz-?vOk`=PE`uEO3eZh=~TrKbGi%9;z1&wEKD#=H^JYy0{5xHo$E{*I^J_C|i>(Fti^Z*cBuczB9> zBRf)aZ??QQUR1Tesw8dh!`Xm;gRIF3-BHtmm`hCjWjSy1k&4{}j zoom@qn1^vo3?r9%viy{T{*az$cxO5HqR=-WTT&)Ac;idrl*6p_d0_eJu(k z)jhCpvAgCe9}nCPGG#nl=t1)CAI!{cbcb8rcxZNrJAPjry~MM}9d#+St3PgV#~O{F zH*6){u{+41DS4|qT&OjUDB6X?ePjLQxw{}}Bklh1$Sy1&xcz4$Y!@C!>)Ib*vA?>! znCX?1d(Cvg_M#Ho!h5QVAg2LM@ zU2wp;nekK91+UJQ7TsUyf-`rXN!{4$g26u_%wNx(v2fRbw?wToqHI~5oIqz%Z@gu# z{7Prkw{ovf-`$C2cDqNrzwE>($=MyZ-|vJUJ^P=Ss59Em=dAnjcqcRu%tZPB-id_& zwvMe)is1zU_!phnGY=_IJcY`L08cN*y6^(Zu@8 zSx3Yjiq*NK?+67ik#|EI9g!Zz3^>DdAa!FWu<)A$^j-3sLgE~-z{f$sx6J{q48Mid z90$}&MDh-0I*@#FUxR~69bo+Z__I(62M8{k@>t^GfWT@?tqvClDCFMSVXf`}f9KSq z?oamE+!nO8Yn}rpjkQBu%I&c`UAZ#xi9HS$o@3wZut#}yQ^TGU_6U&*{>cBz9>3Tp zCHU^x<88d1XRDt*dc8k3xVYKlbdqV&W72N>=gwgF*x_o+LYKzBb|{QCJG^VR9X@Uw zKFHi?k2{;5or}F{hrJVhDn(g#xK*v`_MRk_`)oupfUOE%|NQ3qD(z z=D*btIA8!LdzRLCw{P_P{Q)a{3kwbTWM>Vr`kKm~ z2UZw4J3PJUjuqAm^@uB0T45@S?q5#Y0%uwO3Tmv7;d3PSG1=Rg~=KHL%fZ1P)dbqI#?$w{1 z+PT;Qdf)T3jTc%VJ0RnML$x`)9EYDxTr`LK>L0nk9-5G@ioI@6I+!-{$`lbp_dyQnIWW4#%o`VDe4|li-nQHF| z6YNa>*59pa0^XsT1qt&_5G}F%cZ`GyQom@gRCr*5YfP`dxB86n&1AltT(L1sXyp+) zvBp?cef1!7#u)L_HMbA?8H05tbII>AV^ptRlsyz-j1TM#Nqcu=Qg1rj@=%a5UMXq5 z(9ST%86W34eGOwc?3#bMZLKkEg{myGUKl~TWc1-gy%BgnoosiAF+zbN4nD~>!pT2J zdkk+F;o#{k8TTS1Y?-&JP*2GSwHqXMY}Pfx)RC%7au!B#Vg5S8`_B;k-7|3(Ck)B& zMYGtQ=Y}w~e<%=KVTihU;w{$(4M{!6oy}_NjPN>fz$d-ikksGg>kig2#5uP>6N$ry zfJeV%_(4OQcjGaixM+xkw)&1y3q$b8oP4KEOTV*T_4;!L$1#@p0C^S$^h;M0!5@646s*DJMG+K19V=yvDKTLPYU?)?@grv zHZ@7)Zq7Hrjrt8x++%>WpA~()p$2I3@L!YYV1SF-+p-g-3{V;L*7raw1r?jedtBdB za7a!nW#>~0eBEN?tKU%YFw5$S{5cAG>W2E)*HDlXxghcB6AF?|_+H%4q2S9Cx5D$r z6y&}vQKsCdV5<1>oFX}%zJ9oM^$7}g(Y#UyACk0vZ|ryrNqb4k{!OgS6u7m=(7o^J z!^Em-c4a8=Vo3_cb0<*wR?5hx9I$0A* zC#XtNABFpt^|wX>%f#MREjXYL@6QTf7hTqe)tKw6qIdc*bO{T6#nQ)YoaSdnyFM01 zM?Sf@S|9T?1RZ|P=;5G(Np12nePl;erAt-mqjqoS1oxjFvQ0`yci+(?`@F7iJ3Xm~ zd9I7~zEtU9PfkIl=Mz1wxEXs&KR^!+^Ov$#r05}z?b*5htR6P-aO%3t^bmPTR?$OE z53>GuL;}Wiu_R;cb-8bDYnuByvp1JHmmz)l?)VY6^ zJ#`Q%a!%hvT?ZW|h3v!Mw6Vr@zj-)U8+{V4=Ka^S@kKgqVCNrgM7%t|HP=rYu8wg< zkzcgI)G;b@zM~B@sn3UMhPA*g<0OrJ)`HV;&77aGHXf{P*)nFR1+)4)pB)ctf6?&TQXYMtFo+7uXP73 zLxrLgCAFX+w)$VdBP|phZr~MOqJ=drTx}o54m5RU1O~s`fg9&l(-$*$VDfzHr*Fwx z&}t(8^o+HzI^@Scm+Bpm)Hrs=R9Fl8nGer5R_=iPR<32ijU7;Ab@8nq(M11!mWk{q zO-xGMeRt%ACgeVyH#5Jb2?zU+rCTC3aV(rysq3XC2KRH-GrnsgNxx5$>&elF|H{vK zQJUC0ET(WLS`!hf|18x1YLL3%nKOHjYLf5Dru`Wy8YtM)_)4Nd1O3}}6`GZ3ATjg% zm0(K^Sbc14`=O!%o9Bt8W2cGS%$L>6G&FE}_IJZNe+`^k`AtsBSp%~NZmUgf)4&w1 z^b*?C@hD2H#o?Yh8XAflbn?~l+4pH))FmB*v>_(nDSH4%yXoURVHjWrjC)zoqH z*8LT(J!=o7WYH;_?wyO|R!|JwfftM4i zaGfZgO_fuF(C~21@gY?tTzFXdahV!6cbYtt9#DmTc0&615>>REx~tWxpoY)IWqnHu zRneCCL&P~m70Ld87hG;o#fvA}?mMZf_%60;a#~gurzeLvw@p;xGJnS7wSg*DoS8^+ z8&rY5UPfHsdR4sK@1lEBTNT?X_;i{@RN+?9q`dl~3LMMsIP))7#m!2yDD73Mu$TUE zGfjEaa0g);k9(6H~CT$`8*g!znLzSyLU zh-^Q#SGSaK=;{H>2TIBiI&ZXLYrZme25vpIf44HhE9*aZmJ-T7@J8k_mB37SdMGni z851i$@x8EB1`l7{oVJlNE}#2lX>P3y)<{yU{V8SGtzLa!Yo#*IR%Y?bhA5*WKgZlu zMj6lKJZg_ODM9T>?@@6bWt8)^YEFMwg4o8luI5xF)O@A?vw5h5kj=LLI2B4*>h<}x z?R_OQKh-oE(^tZGr@OYtw=03?d7SYAbtUYMI&L*=tORed>d31Jr2p+Me9=~d^|xTj z`!!0aJ7*GEbY(m0ax?!5#VVoutkUhE2qirF_2kF714>Z-jfFpalyK%?PND{TJCago zUu=D_9rL^BtL|!UMc^n%aO-(-!pCGgSvTVA9I$#jn%mROYz``- z+H$lpeYYa16`)S`3 zl9W48!Q$pX&RbRdJXW3<&Zvsz#xJ8oc~mixzA&3^OBrLO2Rb4tRq!&O#C`SsDmXQM zJX!6VGA@bj-zlT5iUoHJoIlX3BKIK+1DymF9H>5Y?V61Wkw3?Ut-M+VJNBKRI{Kdq zQhkgHow=cc+8*D3tnE_8*h?Rn=3`Y*_?p-|a+5MP?ti*2-=~DX1DRX75|y!g=hE2D zbISP8Ylw8VMj4ITY-{8GDx){qsh6Ts8LcMt^yW5{@sqUme;2Zp(RE*l@G%M%tRTk@ z{~A`ptKF~4ebOH-BW4YI6tRl&=Viw8N_hXH zUz{hmGM1(1y%6qDM4S29Ppw^wNamlc>`hfd7LU3&TbW8|I+<24cvlHmXmaR(BrD;O zM1K|Caz(5Nmwj}@Ux_$R)1u9>LIGcYIhb@&TZzc8xh=N0R{{UIf1$Uzp@@1?MYfvh ziulzu;@#VJMf_+w>~)P>2^|=8T21@@SsK7_mGckC{D@ z(Lp@&C?R@3o9eJUp8xr{>2bO|N{a1^cMX-t&WOp+Wi9gftZMs8>^=o#*5}s$U?z_f z0!-oe!{yMy(yP1KQXX%%nAsRzl*h=V3YoLw@@Umi)O@F%kmujCTdr0PJ=D#!O-kiZ zWYG2Z^OJH|`|YjO!+beR*B*BB*dvD{=QlriM9N_=qm5;+wH#_Nt2=#rB8S{Y>Qeu8 z%i^zCsRx2I^0;4>)R31ehXNQsQTt8~2Y2fvTsb3$$^;hIj?qzb9uRT0W7*y^cQoT)DDD9;52v=V`K75ltg%^-Gp`4vW0Lr6q@(|1oOxq{yPy z@ao|hE?LwW2#T{Pu3aG3tses!skCc~~HW zS+=xM&LE2~Q_cs(_{!q7`O=vwPZ>OOD>i%hl??Hn?+iyygA9I*xT-V2Ba5WC?EP!@ zve-c9ef1)XEK)u_{vX?v3?B9IT~g_jLG%5YCndLJaOWOVagj*J|7 z{B>J~$j`bz;8lVQexa5zRn(Nh7YVyE=e1?j8_D1rye)ffA%mN& zGspLe$`H@%n0ri*WpIl5ifKltG(Jo4{1Qp@v)6tsocK%{3n|XslhcqU_QeR!__;{q zdEFz+HBD03B){S?CnAF!XB7^*ACbWynJ+G$q?5tDPy4sccx2G$>a&Ro!d^+PXgkG^ z(kN0|YWCJu8jl<>NVVve#&o&${jvelcxrE{+(%7m;{Nvaj4?j9M;f_^yf=)!QYdsND3X&&8r%MR;rYjnC||X^ zNTX;hm7e%eOUbk5EgaXWLO9*W-lUmvrvw8cX38SM;tYx>ESig`?q)_TmU+Ky|DV!wxU5X2m#5c1cBs~*Jq?3CVi3B}QcBi?djT9a% z5|Xi&lR`cr1Imp@Qphmk5Mq!miP6Vf@6Zwa@~^d~b|y=r2idx`d0i5nZE{LRIi;|< zGk!FQR|+?eHI%<@l0@PFuB_?}NjxnRMHxITiBHKP&mI{`qB)*vbsm+(T_h8p0Rt%% z5X)q>Blsqs%$y#Tki?|8jLYlL7`Kf<%i_yd|-@YsR(Vh$OKuJacc_DM|dsI}`7DP6DYV{^M6T zB!TPiIzE+lNMO15+OJFkw;C_gv!YiCTo|AKoV_D~`gfD1!`@5a*>my6S2ZMwylqM? z-D48i$sJRrTPuP0N{l(vG9*zX$2hM@R01~_uCaK0l)yWq=k{v+lfdbnH`TgY2ze$i zPUO9kKv5=-fVd_J;yqx;BYIZ}tm6_YgwF;N$X9pq?l(gT%ncK`bYZ^)?v+2UqaZ`* z^`Z;y0*E0`2RcQNz((r3ldP$27bc)vJiv`;RxxkzAb)gFga zf#TTz?w!`X6)|G}zy=Spwm7T-OtY^fqBj+sdAdcv4yeoj@VZ*e0F^= zOZa7RJYOw;F;7VXFG){dt-B?LqGKPj+4My*vpPbZ>$Dh7=LtSpcNRmMTRwN!&Wa&j zOuxyxpD3Q$H^=brh8Uj9*cIueCXU9Ak9&VHi{Y(Q{!;?QA}BAk=8wsuc%;yc@@ z=UCey}=eOI0IqIY4>-++I<<**>*{H&_&V zFHd{;+!8_YegR6mN>Oyv;B_9UBjj-(zgl1}f{Kp<>BaMfF?!1KP17S09A04Ycc>6S z%H*2A$<(4KUA5XI{zMr0c11sJViP9Lg|8&VeiKGdFWGoqTVbpbxEW%fAc9)Ge)sgR ziD2V$%HuPfBG{*=TER3Yf^J`poI96=@%jZ}$?+Kihroj^hUbOx#@LFH370S)nvv1i z;VXhS>LS8y*+h}#{`~BL8^X9KFLSq(T?CK)*<#;QER2^$+HR1438AoUR7;1g5NaG; z;mm6h!g_`hYUOA_d^wS@gF-?3lMsa2p3KsCG0#9 z7$B%2f-{ZOy|fy_#D6VM9v-|VjARvoHNgNOZ25eP=bV!;s!{Q^{qq$@D9=R?2n2_7@HMZrWp)f`*xj6nfB7#Sqo73k> zLfF2j@up#)FiL8iPVlx8#`>gZVRCzgvCP9cs`RuVaSq=}jiL+z{9Smws)s1m7y3N3 z)&wxKcGh+Du^`T=YD0p>Ti~f0e)D6Kj?#Zt6oROg z@n|K&T>x|Zn44Qh1W;y>#z#e22qp46~=XHWO z|KE&~;er6>?Jy5C(-y+1E($}%GlHo3`L%~avmk0Oum1S`OaR3q`k9q`1u#tM)c3nb z1n`%q`$<6?0o=HHiLo-BALo5srOPY$@y4Cg)yB$Xhp*wFHKeiT41qeINu`2yH=$IbfsxFD9Rdi1t&38Lp) z?)T+R0_a}u7Rr530MC9S1xtJuz_QzG!8;H06Z_mH$9EL)<3$6B6@~->OsaotaY9xA z4dm=@_Q&$$9(eTCgIfS~((7iNqxtcm8&53;@uNFcexmUtA6^_2f37jWkNo`Rr+D}C z-GC1-33 zcyYJon`E19KD;KI-+6+}kNf;W@{g7BW1D9q3pJ|%R`jbbSC{Z1x4lYAhzK7>M+XK( zw(+6Og;$>G+#f#%oSFSBH^W#^E{pojp@gk-A)l-`^e0a(7%aoD@ z(Qk9$l`EMShllUh7&!3ZBv&fecn~kjyx6N^UCf8(pB_v8R^dawn`O;>CwOt@AK96c zlNSrW#Auu&_^Hhu^?xYOi^1ZCH@mlaFd$M?k6xG;%?`=!Npa`JwxNS@7Eyc{`MJf2 zQi%`cS_Uin3A_QXt~GG^$nxRoX}Y3!a=e%jH@%yao)_zg+?p7|jaPE1XH(jF@JY7e2n(T)mfcAq zu?QZ#I4!YycP9`2bGR5-tHOh32EXs`|IUrWb*0y1EqSok?dYB*ZyuccleB3)#Dk>u ziQCGj2|Gf3F0+U6pr=fA@`3^nifd%@COziC`iITwIg{Kt&S*aM_zsZZtk`e)NhQH$D>AbhUNhMu{(9f7rg^ z#%~j=!|O+Quu&t&{X`ZwdLG_$R3Q*>)}jEn;TSjm3p&DVpU91rSz^!Z z*ym&|BqlEPu55`%pXR*ZdpyX`1 zj$RKBp6&NeVK3mqCbm}fs7|qT*$MpOhTWKL;k~d#kPSHWef$~-s^E9oo(|TZGKLS zjLVp%BIME(r(V%?ZcaS@GkN#B`?bW&Zl@qV(G9M6U=S0?xtUmhy4$S@5Z5^e@fp6m9 z#5%Ka;UsfZ&R1qm{ML7II`}m^a{YJo@&z4E{P5s%c`booj>YW_ju#wgWB$dfrIj56 z^AgQNf;dp-uj`rk0yZ=-*_7zyvs7-*7g3|H3yckCheQ7>^FuM=;}$#?9Ij#2;LyB(?8f+TbBtb z-z!Fi1u>(L<*fMs`ONuh>l6bEp3>{)eDnY78`hC5d5oyO%*!+Lh_JA-p+rZR8Kr+a zF3Epq#*mo84I3I3{CCFe-)CiR9O->!Xa={)FCOw=5I> zeJXzF(I;k{RXDkaHjojA#@(6uxL9yPO$G}Ov*NH*g-7`mBO0@u&kChwLZ+uZ{y`_1 zP^*@v>XJ1xra0LB8th?2VJcyBJ5ffI`)RaPbetaB3e+~G_A%mo0;!_6gaLtHSNM@X zBZ^3O%kX3~VtIX%jPX-Oq!V*^Wv$7G{LORh2YeaOu3}~?`X>Xn*9DrK8)C%e+Mp?0 zG9#|G7pZiJG2+paOHMLGhN54grSCF-G2p&DvRsua17@~}nQ7l(#N81_NrgWcP{Cm_ zVAlf%v@Ya$QFELL->ZeopPFXGP>z-v3IzS6$7B z!J@5H3>J)N!u-r+n1vA~{B4g+sWaf6t05cT0vXWx^dqO8Ul_40c^Hw82^EK`Zq95m zpbiWBH`Z|m9GtvTtrpLS^`<;Cr<3WC!Qr7_odP4~$GA7vbuwb?Ykil3Dn^tK`aFDV$XVP>Y8 zjhY^{ghh)w_cGvLx0LH*0gTvS|KjcYO?rIosBBAXN{?xS%qyRZ=n-sJ{B;k}VV1@V zpCnCMY}suQIATDLU#H#jxnC3h*<8GCkivlHNV8?f#kwPLK?wjOXuIr=6*&M%gvnDzm*eI&pjG@PW zH?LGttk7Xj-Dx(XH98Dpv;r;tndKdEPF!sHlw>K3XdW7E8c*aSG|6YGJ<@!K}%TLtnbyjJSHcsRC z*fm-lcQ&t)@1sSxx91sZ1?Z6N!D5J#G(Ga?-DnKxp+jvS@0*s-Xi<{9WwvZjgFREt zI@Aa0@WlGoT&@Nkeh<6YnNHv)cp@d(>Ip4AoH$(TxJ->YgK6P=>!@*XKl#!AyR?{0 z{XOuM8x7XDB}WC((;~~FJkz@mw8Z~XHo3G8w78i%@i@JN4(n6b=e`iQXkPpAdE1%} zcksQPt`VfeA(Mb3#ObkEXw|JTy^{uWU*@tZt>S&4m=IF?& zNK4Fyyd&pIP@pd=cP8G@8coI z$FkJOYQIyiqKq0j=9G5-3ZlXFJ1N1bjMS)JzOpp3n;MBHlyZ$1HSU=z5UFmXA@a$c z$#~&G_*Z6S!Bv6=xtvEv9}3c9<{*2*iA2J#Ck4m)qG*uPhjXpfi3$(>mmAyMONm}n zlBfFn33}s9wKIk^c-!F7H&a5czn^CAD>Z6ljNA2G<{b@sC{T@h5_0X& zJ`AR^(V)|e=BT|h4eEJTzZ)2&#EUwiMpp-@Fv{dHd7hpMcRGd6KbfJ%4L{o18KlBD zhjM=%^P$8OOc{2+&rzW}EvclH&|mb~<WA>M z3@W^`f05pru&ZOA+78o`R9HCE;Pm?#CAuur^AA6xL|1yV*qk^j^w^c@-AGA;uRA4K zR31~~?_<(h3ksCDX`0q(HcN@ACpM#cd#I7SAbQL40u|@r; zVEZ7}8c2yD=2nTfIw(;{*=%B#M2Rn*?(H8nrNpmKwA*qCy>sl1&lg2d;`Yg;6aIM)1vleOa{l!4@c77(3y8dJ7z#b%nam zQDV5}(!nM|FZmdYJxo_AaQxn-_H9uL{Hf!8TZerc=rqPC&!iH3HS9SLHBjIu54+id zw-iXV&UB{PmjXSQX@bQFeGg~VmYg!D#9K^55vBPQc)dNK*#71=^sR9x<$a+*r+Y6V zq9?W?E-`6oui7?TCtca|`zZykeA@Xjw2=atLWw!& zUX<>dqQF8wy<4;V6!`R4s+0i@1^!BqkUgJ4fp>2w%4RoigO>QfMmPlpkw@U^g%NE^ z^h=nPc=eZp$QzcW?)Z}eOB9mW4Ff39RNma1_v1E**z;fK%-9B>Mn$uTUJ4we(BOBH zphBvr?dv{OuHVo*lQAZNEK9!_oS0r!2)6aDM zM#dCavA9kmRcwRsMb)w7L)+lR6{WSqYa5*Y`!t5(+YtMuvc+#}8|p6?6gz8CpzW<_ zlun_*>b!1_Fe3gQ_ID5;G%0XSXrAq@GX=iM9!i=f{3tq1wNbWAfgwUPACK5?0as+T znAFK_kSIOz;*tz;{k%Zv&fraurnLO$wR;m9y0`9oJlg>C{=S#N=eJ?!rmR%^)Ha-< zv{0aZy9ME`nMnrufu$X$#&q)RXBf(MqBUe zDja>)_Omo?75=MZ49Y6pgzf`%c{<-WK!M81#r6CK@Y2LRJr=Y9fgy^;hSZzzK}`LS z1uF$=*m1UAyRr?o3oI)x_cq~bx#5KJ>kT*{a5*dT!5V0)EzmL2ZoD!XmE7ze+ zCil+ktLvchncnJ<+9uScl%9Q>wE_P

JL*+JH%Z>1T=sTi`XJ@l3~h6OLthG1O~s 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�dQD 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 literal 0 HcmV?d00001 From 0c9bb39c354482377774a75ca7bb91fd1831edff Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 19:04:39 -0500 Subject: [PATCH 15/28] MNT #48 disable fly scan rocessing for now --- calc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calc.py b/calc.py index 7cc8807..3e11335 100644 --- a/calc.py +++ b/calc.py @@ -347,8 +347,8 @@ def developer_main(): nxentry = spec2nexus.eznx.makeGroup(nx, "flyScan", "NXentry", signal="data") nxentry.create_dataset("title", data=str(hdf5FileName)) nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") - for k, v in sorted(fs["full"].items()): - spec2nexus.eznx.makeDataset(nxdata, k, v) + # for k, v in sorted(fs["full"].items()): + # spec2nexus.eznx.makeDataset(nxdata, k, v) nxentry = spec2nexus.eznx.makeGroup(nx, "uascan", "NXentry", signal="data") nxentry.create_dataset("title", data=str(specFileName)) From 7f21091c5bd90de63fd70e36fdd16f30de8dc1f5 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 19:04:56 -0500 Subject: [PATCH 16/28] TST #48 ignore test data output --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 893d7d9..3b63987 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# test data +data/test_calc.h5 + +# Python 2 *.pyc # APS IT From b9e4013dae7830250724441b3af23f0afce1238b Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 19:10:15 -0500 Subject: [PATCH 17/28] MNT #48 refactor iso8601 timestamp, simpler now --- calc.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/calc.py b/calc.py index 3e11335..eadc716 100644 --- a/calc.py +++ b/calc.py @@ -298,11 +298,12 @@ def reduce_uascan(sds): def iso8601_datetime(): - """return current date & time as modified ISO8601=compliant string""" - t = datetime.datetime.now() - # standard ISO8601 uses 'T', blank space instead is now allowed - s = str(t).split(".")[0] - return s + """ + Return current date & time as modified ISO8601=compliant string. + + Standard ISO8601 uses 'T', blank space instead is now allowed. + """ + return datetime.datetime.now().isoformat(sep=" ") def bin_xref(x, bins): From 0e7912aeed74d452c8543ec837443f1dc47317e6 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Thu, 3 Nov 2022 19:12:20 -0500 Subject: [PATCH 18/28] MNT #48 remove unused import --- calc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/calc.py b/calc.py index eadc716..0269354 100644 --- a/calc.py +++ b/calc.py @@ -10,7 +10,6 @@ import logging import math import numpy -import os import pathlib import spec2nexus.eznx import spec2nexus.spec From def090525379745405dabf46c1014549fa256634 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 14:27:11 -0600 Subject: [PATCH 19/28] DAT #48 restore some test data --- {archive/testdata => data}/03_18_GlassyCarbon.dat | 0 {archive/testdata => data}/S217_E7_600C_87min.h5 | Bin {archive/testdata => data}/S6_r1SOTy2_0235.h5 | Bin 3 files changed, 0 insertions(+), 0 deletions(-) rename {archive/testdata => data}/03_18_GlassyCarbon.dat (100%) rename {archive/testdata => data}/S217_E7_600C_87min.h5 (100%) rename {archive/testdata => data}/S6_r1SOTy2_0235.h5 (100%) diff --git a/archive/testdata/03_18_GlassyCarbon.dat b/data/03_18_GlassyCarbon.dat similarity index 100% rename from archive/testdata/03_18_GlassyCarbon.dat rename to data/03_18_GlassyCarbon.dat diff --git a/archive/testdata/S217_E7_600C_87min.h5 b/data/S217_E7_600C_87min.h5 similarity index 100% rename from archive/testdata/S217_E7_600C_87min.h5 rename to data/S217_E7_600C_87min.h5 diff --git a/archive/testdata/S6_r1SOTy2_0235.h5 b/data/S6_r1SOTy2_0235.h5 similarity index 100% rename from archive/testdata/S6_r1SOTy2_0235.h5 rename to data/S6_r1SOTy2_0235.h5 From 2e6018ea320d8e8c6a73c24e4852c1274c1604d4 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 14:30:30 -0600 Subject: [PATCH 20/28] TST #48 begin a test suite --- calc.py | 83 ++-------------------------------- tests/test_calc.py | 108 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 tests/test_calc.py diff --git a/calc.py b/calc.py index 0269354..5096194 100644 --- a/calc.py +++ b/calc.py @@ -20,17 +20,6 @@ USAXS_DATA = pathlib.Path("/share1/USAXS_data") TESTDATA = pathlib.Path(__file__).parent / "data" -# TEST_FILE_FLYSCAN = TESTDATA / 'S217_E7_600C_87min.h5' -TEST_FILE_FLYSCAN = TESTDATA / "Blank_0016.h5" -# TEST_FILE_FLYSCAN = TESTDATA / "S6_r1SOTy2_0235.h5" -# TEST_FILE_UASCAN = TESTDATA / '03_18_GlassyCarbon.dat' -# TEST_UASCAN_SCAN_NUMBER = 522 -# TEST_FILE_UASCAN = USAXS_DATA / "2021-09/09_18_test/09_18_test.dat" -# TEST_UASCAN_SCAN_NUMBER = 11 -TEST_FILE_UASCAN = USAXS_DATA / "2022-11/11_03_24keVTest/11_03_24keVTest.dat" -TEST_UASCAN_SCAN_NUMBER = 248 -TEST_FILE_OUTPUT = TESTDATA / "test_calc.h5" - CUTOFF = 0.4 # when calculating the center, look at data above CUTOFF*R_max ZINGER_THRESHOLD = 2 @@ -194,32 +183,6 @@ def remove_masked_data(data, mask): return arr.compressed() -def test_flyScan(filename): - """test data reduction from a flyScan (in an HDF5 file)""" - if not filename.exists(): - raise FileNotFound(filename) - - import reduceFlyData - - fs = reduceFlyData.UsaxsFlyScan(filename) - # compute the R(Q) profile - fs.reduce() - usaxs = fs.reduced - return usaxs - - -def test_uascan(filename): - """test data reduction from an uascan (in a SPEC file)""" - if not filename.exists(): - raise FileNotFound(filename) - - # open the SPEC data file - sdf_object = spec2nexus.spec.SpecDataFile(filename) - sds = sdf_object.getScan(TEST_UASCAN_SCAN_NUMBER) - sds.interpret() - return reduce_uascan(sds) - - def reduce_uascan(sds): """data reduction of an uascan (in a SPEC file) @@ -241,14 +204,14 @@ def reduce_uascan(sds): # gain & dark are stored as 1-offset, pad here with 0-offset to simplify list handling gain = [ 0, - ] + map(lambda _: sds.metadata["UPD2gain" + str(_)], range(1, 6)) + ] + list(map(lambda _: sds.metadata["UPD2gain" + str(_)], range(1, 6))) dark = [ 0, - ] + map(lambda _: sds.metadata["UPD2bkg" + str(_)], range(1, 6)) + ] + list(map(lambda _: sds.metadata["UPD2bkg" + str(_)], range(1, 6))) # create numpy arrays to match the ar & pd data - pd_gain = map(lambda _: gain[_], pd_range) - pd_dark = map(lambda _: dark[_], pd_range) + pd_gain = list(map(lambda _: gain[_], pd_range)) + pd_dark = list(map(lambda _: dark[_], pd_range)) else: # Bluesky created this data file # BS plan writes a NeXus file with the raw data. # rebuild the full HDF5 data file name @@ -323,41 +286,3 @@ def bin_xref(x, bins): key_list = map(int, xref_dict.keys()) xref = [xref_dict[str(key)] for key in sorted(key_list)] return numpy.array(xref) - - -def developer_main(): - hdf5FileName = TEST_FILE_FLYSCAN - # fs = test_flyScan(hdf5FileName) - - specFileName = TEST_FILE_UASCAN - ua = test_uascan(TEST_FILE_UASCAN) - - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - # write results to a NeXus file - - nx = spec2nexus.eznx.makeFile( - str(TEST_FILE_OUTPUT), - signal="flyScan", - timestamp=str(datetime.datetime.now()), - writer="USAXS livedata.calc and spec2nexus.eznx", - purpose="testing common USAXS calculation code on different scan file types", - ) - - nxentry = spec2nexus.eznx.makeGroup(nx, "flyScan", "NXentry", signal="data") - nxentry.create_dataset("title", data=str(hdf5FileName)) - nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") - # for k, v in sorted(fs["full"].items()): - # spec2nexus.eznx.makeDataset(nxdata, k, v) - - nxentry = spec2nexus.eznx.makeGroup(nx, "uascan", "NXentry", signal="data") - nxentry.create_dataset("title", data=str(specFileName)) - nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") - for k, v in sorted(ua.items()): - spec2nexus.eznx.makeDataset(nxdata, k, v) - - nx.close() - - -if __name__ == "__main__": - developer_main() diff --git a/tests/test_calc.py b/tests/test_calc.py new file mode 100644 index 0000000..f869ac2 --- /dev/null +++ b/tests/test_calc.py @@ -0,0 +1,108 @@ +import pathlib +import sys +sys.path.append(str(pathlib.Path(__file__).parent.parent)) + +import pytest +from spec2nexus.spec import SpecDataFile +from calc import reduce_uascan + + +USAXS_DATA = pathlib.Path("/share1/USAXS_data") +TESTDATA = pathlib.Path(__file__).parent.parent / "data" + +TEST_FILE_OUTPUT = TESTDATA / "test_calc.h5" + + +@pytest.mark.parametrize( + "filename", + [ + TESTDATA / "S217_E7_600C_87min.h5", + TESTDATA / "Blank_0016.h5", + TESTDATA / "S6_r1SOTy2_0235.h5", + ] +) +def test_flyScan(filename): + """test data reduction from a flyScan (in an HDF5 file)""" + if not filename.exists(): + raise FileNotFoundError(filename) + assert filename.exists() + + # TODO: refactor as test(s) + # import reduceFlyData + + # fs = reduceFlyData.UsaxsFlyScan(filename) + # # compute the R(Q) profile + # fs.reduce() + # usaxs = fs.reduced + # return usaxs + assert True + + +@pytest.mark.parametrize( + "filename, scan_number", + [ + [TESTDATA / '03_18_GlassyCarbon.dat', 522], + [USAXS_DATA / "2021-09/09_18_test/09_18_test.dat", 11], + [USAXS_DATA / "2022-11/11_03_24keVTest/11_03_24keVTest.dat", 248], + ] +) +def test_uascan(filename, scan_number): + """test data reduction from an uascan (in a SPEC file)""" + if not filename.exists(): + raise FileNotFoundError(filename) + + # open the SPEC data file + sdf_object = SpecDataFile(filename) + assert isinstance(sdf_object, SpecDataFile) + + sds = sdf_object.getScan(scan_number) + assert sds is not None + # TODO: anything to test _before_ interpret? + + sds.interpret() + # TODO: anything to test now? + + uascan = reduce_uascan(sds) + assert isinstance(uascan, dict) # data is reduceable + for k in "Q R ar r r0 ar_0 ar_r_peak r_peak".split(): + assert k in uascan + + # same lengths + n = len(uascan["Q"]) + for k in "R ar".split(): + assert len(uascan[k]) == n + + +# TODO: refactor as test(s) +# def developer_main(): +# hdf5FileName = TEST_FILE_FLYSCAN +# # fs = test_flyScan(hdf5FileName) + +# specFileName = TEST_FILE_UASCAN +# ua = test_uascan(TEST_FILE_UASCAN) + +# # - - - - - - - - - - - - - - - - - - - - - - - - - - + +# # write results to a NeXus file + +# nx = spec2nexus.eznx.makeFile( +# str(TEST_FILE_OUTPUT), +# signal="flyScan", +# timestamp=str(datetime.datetime.now()), +# writer="USAXS livedata.calc and spec2nexus.eznx", +# purpose="testing common USAXS calculation code on different scan file types", +# ) + +# nxentry = spec2nexus.eznx.makeGroup(nx, "flyScan", "NXentry", signal="data") +# nxentry.create_dataset("title", data=str(hdf5FileName)) +# nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") +# # for k, v in sorted(fs["full"].items()): +# # spec2nexus.eznx.makeDataset(nxdata, k, v) + +# nxentry = spec2nexus.eznx.makeGroup(nx, "uascan", "NXentry", signal="data") +# nxentry.create_dataset("title", data=str(specFileName)) +# nxdata = spec2nexus.eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") +# for k, v in sorted(ua.items()): +# spec2nexus.eznx.makeDataset(nxdata, k, v) + +# nx.close() From 8c908230a0658acf7eea21f377dfd911a08111b9 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 14:47:52 -0600 Subject: [PATCH 21/28] TST #48 enable more tests --- tests/test_calc.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_calc.py b/tests/test_calc.py index f869ac2..0e1f7fc 100644 --- a/tests/test_calc.py +++ b/tests/test_calc.py @@ -28,14 +28,15 @@ def test_flyScan(filename): assert filename.exists() # TODO: refactor as test(s) - # import reduceFlyData - - # fs = reduceFlyData.UsaxsFlyScan(filename) - # # compute the R(Q) profile - # fs.reduce() - # usaxs = fs.reduced - # return usaxs - assert True + import reduceFlyData + + fs = reduceFlyData.UsaxsFlyScan(filename) + # compute the R(Q) profile + fs.reduce() + usaxs = fs.reduced + assert usaxs is not None + assert isinstance(usaxs, dict) + assert "full" in usaxs @pytest.mark.parametrize( From 25fe167af0a30e72bbf51f2b5548eac669299cf5 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 14:48:03 -0600 Subject: [PATCH 22/28] ENV #48 more requirements --- environment.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/environment.yml b/environment.yml index dc62aa3..2893834 100644 --- a/environment.yml +++ b/environment.yml @@ -17,9 +17,12 @@ dependencies: - lxml - ophyd - pandas + - paramiko - pint - pip - punx - pyRestTable - pytest + - scipy + - scp - spec2nexus From 1df421b5f9f0d72a133644eb266f23e596841b9e Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 15:19:31 -0600 Subject: [PATCH 23/28] MNT #48 restore more of old code --- localConfig.py | 88 +++++ plot_mpl.py | 179 +++++++++ pvwatch.py | 465 ++++++++++++++++++++++ radialprofile.py | 270 +++++++++++++ reduceAreaDetector.py | 491 ++++++++++++++++++++++++ reduceFlyData.py | 867 ++++++++++++++++++++++++++++++++++++++++++ scanplots.py | 530 ++++++++++++++++++++++++++ tests/test_calc.py | 14 +- ustep.py | 150 ++++++++ wwwServerTransfers.py | 206 ++++++++++ xmlSupport.py | 202 ++++++++++ 11 files changed, 3459 insertions(+), 3 deletions(-) create mode 100755 localConfig.py create mode 100755 plot_mpl.py create mode 100755 pvwatch.py create mode 100644 radialprofile.py create mode 100644 reduceAreaDetector.py create mode 100755 reduceFlyData.py create mode 100755 scanplots.py create mode 100755 ustep.py create mode 100755 wwwServerTransfers.py create mode 100755 xmlSupport.py diff --git a/localConfig.py b/localConfig.py new file mode 100755 index 0000000..59a1626 --- /dev/null +++ b/localConfig.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +''' +define 9-ID-C USAXS constants for these Python tools +''' + +import os + + +# general use +HOME_DIR = os.environ.get('HOME', '') +BASE_DIR = "/share1/USAXS_data/" +LOCAL_DATA_DIR = "/share1" +LOCAL_USAXS_DATA_DIR = os.path.join(LOCAL_DATA_DIR, "/USAXS_data") +LOCAL_WWW_LIVEDATA_DIR = os.path.join(LOCAL_DATA_DIR, "local_livedata") + +PLOT_FORMAT = "png" +TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" + + +# dirWatch.py +SKIP_DIRS = ['.AppleFileInfo'] +KEEP_EXTS = ['.dat'] +TIME_WINDOWS_SECS = 60*60*24*180 + + +# plotAllSpecFileScans.py +LOCAL_SPECPLOTS_DIR = os.path.join(LOCAL_WWW_LIVEDATA_DIR, "specplots") +WWW_SPECPLOTS_DIR = "specplots" +SPEC_FILE = os.path.join(LOCAL_USAXS_DATA_DIR, "2010-03/03_27.dat") +MTIME_CACHE_FILE = os.path.join(LOCAL_SPECPLOTS_DIR, 'mtime_cache.txt') + +# plot.py +A_keV = 12.3984244 +FIXED_VF_GAIN = 1e5 +LOCAL_PLOTFILE = "livedata" + os.extsep + PLOT_FORMAT + + +# pvwatch.py +SOURCECODE_BASE = os.path.join(HOME_DIR, "Documents/eclipse/USAXS/livedata") +LOG_INTERVAL_S = 60*5 +NUM_SCANS_PLOTTED = 9 +REPORT_INTERVAL_S = 10 +SLEEP_INTERVAL_S = 0.1 +XML_REPORT_FILE = "report.xml" +HTML_INDEX_FILE = "index.html" +HTML_RAWREPORT_FILE = "raw-report.html" +HTML_USAXSTV_FILE = "usaxstv.html" +LIVEDATA_PHP_PAGER = "scanpager.php" +LIVEDATA_XSL_STYLESHEET = "livedata.xsl" +RAWTABLE_XSL_STYLESHEET = "raw-table.xsl" +USAXSTV_XSL_STYLESHEET = "usaxstv.xsl" +#XSLT_COMMAND = "/usr/bin/xsltproc --novalid %s " +SPECMACRO_TXT_FILE = "specmacro.txt" + + + +# specplot.py +# TEST_SPEC_DATA = os.path.join(LOCAL_USAXS_DATA_DIR, "2011-06/06_22_setup2.dat") +# TEST_SPEC_DATA = os.path.join("testdata", "03_19_LLNL.dat") +# TEST_SPEC_DATA = os.path.join("testdata", "03_19_LLNL-problem.dat") +# TEST_SPEC_DATA = os.path.join("testdata", "11_03_Vinod.dat") +# TEST_SPEC_DATA = '/share1/USAXS_data/2013-10/10_26_Course.dat' +# TEST_SPEC_DATA = '/share1/USAXS_data/2014-02/02_18_artune.dat' +# TEST_SPEC_DATA = '/share1/USAXS_data/2014-04/04_09_Prakash_A5.dat' +# TEST_SPEC_DATA = '/share1/USAXS_data/2014-06/06_19_Tony.dat' +# TEST_SPEC_DATA = '/share1/USAXS_data/2014-08/08_13_setup.dat' +# TEST_SPEC_DATA = '/share1/USAXS_data/2015-01/02_08_Samples.dat' +# TEST_SPEC_DATA = '/share1/USAXS_data/2016-06/06_08_DoyoonKim.dat' +TEST_SPEC_DATA = '/share1/USAXS_data/2018-01/01_22_TestHDF5.dat' + +TEST_SPEC_SCAN_NUMBER = 15 +TEST_PLOTFILE = "pete.png" +LINE_ONLY_THRESHOLD = 400 + + +# FlyScan +REDUCED_FLY_SCAN_BINS = 250 # sufficient to make a good plot +MCA_CLOCK_FREQUENCY = 50e6 # 50 MHz clock (not stored in older FlyScan files) +FLY_SCAN_Q_MIN = 1.01e-6 # absolute minimum Q for rebinning +FLY_SCAN_UATERM = 1.2 # for defining Q bins + + +# Area Detector images +REDUCED_AD_IMAGE_BINS = 250 # sufficient to make a good plot + + +HDF5_PATH_TO_IMAGE_DATA = '/entry/data/data' diff --git a/plot_mpl.py b/plot_mpl.py new file mode 100755 index 0000000..2d9ee6d --- /dev/null +++ b/plot_mpl.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +'''use MatPlotLib for the USAXS livedata and generic SPEC scan plots''' + + +import datetime +import logging +import matplotlib +matplotlib.use('Agg') +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +import numpy as np + + +logger = logging.getLogger(__name__) + +BISQUE_RGB = (255./255, 228./255, 196./255) # 255 228 196 bisque +MINTCREAM_RGB = (245./255, 255./255, 250./255) # 245 255 250 MintCream + +SYMBOL_LIST = ("^", "D", "s", "v", "d", "<", ">") +COLOR_LIST = "orange salmon lime green blue purple violet chocolate gray black".split() # red is NOT in the list + +CHART_FILE = 'livedata.png' + + +class PlotException(Exception): + """one of the plot traces has raised an excpetion""" + + +# MatPlotLib has several interfaces for plotting +# Since this module runs as part of a background job generating lots of plots, +# the standard plt code is not the right model. It warns after 20 plots +# and will eventually run out of memory. Here's the fix used in this module: +# http://stackoverflow.com/questions/16334588/create-a-figure-that-is-reference-counted/16337909#16337909 + +class Plottable_USAXS_Dataset(object): + '''data model for the plots below''' + Q = None + I = None + label = None + + +def livedata_plot(datasets, plotfile, title=None): + ''' + generate the USAXS livedata plot + + :param [Plottable_USAXS_Dataset] datasets: USAXS data to be plotted, newest data last + :param str plotfile: file name to write plot image + ''' + fig = matplotlib.figure.Figure(figsize=(7.5, 8), dpi=300) + fig.clf() + + ax = fig.add_subplot('111', axisbg=MINTCREAM_RGB) + ax.set_xscale('log') + ax.set_yscale('log') + ax.set_xlabel(r'$|\vec{Q}|, 1/\AA$') + ax.set_ylabel(r'$R(|\vec{Q}|)$, Raw Intensity, a.u.') + ax.grid(True) + + timestamp_str = 'APS/XSD USAXS: ' + str(datetime.datetime.now()) + fig.suptitle(timestamp_str, fontsize=10) + if title is not None: + ax.set_title(title, fontsize=12) + + legend_handlers = {} # to configure legend for one symbol per dataset + faults = [] + for i, ds in enumerate(datasets): + try: + if i < len(datasets)-1: + color = COLOR_LIST[i % len(COLOR_LIST)] + symbol = SYMBOL_LIST[i % len(SYMBOL_LIST)] + else: + color = 'red' + symbol = 'o' + if ds.label.find('(fly)') >= 0: + label = ds.label[:ds.label.find('(fly)')] + '(USAXS)' + elif ds.label.find('(SAXS)') >= 0: + label = ds.label[:ds.label.find('(SAXS)')] + '(SAXS)' + else: + label = ds.label + p = label.find(" ") + if label.startswith("S") and p > 0: + label = label[p:].strip() # remove the scan number + pl, = ax.plot(ds.Q, ds.I, symbol, label=label, mfc='w', mec=color, ms=3, mew=1) + legend_handlers[pl] = matplotlib.legend_handler.HandlerLine2D(numpoints=1) + except Exception as exc: + faults.append((i, ds, exc)) + + # ax.legend(loc='lower left', fontsize=9, handler_map=legend_handlers) # the old way + ax.legend(fontsize=8, handler_map=legend_handlers) + # fig.tight_layout() # does not look good, crowds the title + FigureCanvas(fig).print_figure(plotfile, bbox_inches='tight', facecolor=BISQUE_RGB) + if len(faults) > 0: + fault_text = "\n".join(["{}".format(f) for f in faults]) + raise PlotException(fault_text) + + +def spec_plot(x, y, + plotfile, + title=None, subtitle=None, + xtitle=None, ytitle=None, + xlog=False, ylog=False, + timestamp_str=None): + ''' + generate a plot of a scan (as if data from a scan in a SPEC file) + + :param [float] x: horizontal axis data + :param [float] y: vertical axis data + :param str plotfile: file name to write plot image + :param str xtitle: horizontal axis label (default: not shown) + :param str ytitle: vertical axis label (default: not shown) + :param str title: title for plot (default: date time) + :param str subtitle: subtitle for plot (default: not shown) + :param bool xlog: should X axis be log (default: False=linear) + :param bool ylog: should Y axis be log (default: False=linear) + :param str timestamp_str: date to use on plot (default: now) + ''' + fig = matplotlib.figure.Figure(figsize=(9, 5)) + fig.clf() + + ax = fig.add_subplot('111') + if xlog: + ax.set_xscale('log') + if ylog: + ax.set_yscale('log') + if not xlog and not ylog: + ax.ticklabel_format(useOffset=False) + if xtitle is not None: + ax.set_xlabel(xtitle) + if ytitle is not None: + ax.set_ylabel(ytitle) + + if subtitle is not None: + ax.set_title(subtitle, fontsize=9) + + if timestamp_str is None: + timestamp_str = str(datetime.datetime.now()) + if title is None: + title = timestamp_str + else: + fig.text(0.02, 0., timestamp_str, + fontsize=8, color='gray', + ha='left', va='bottom', alpha=0.5) + fig.suptitle(title, fontsize=10) + + ax.plot(x, y, 'o-') + + FigureCanvas(fig).print_figure(plotfile, bbox_inches='tight') + + +def main(): + '''demo of this code''' + x = np.arange(0.105, 2*np.pi, 0.01) + ds1 = Plottable_USAXS_Dataset() + ds1.Q = x + ds1.I = np.sin(x**2) * np.exp(-x) + 1.0e-5 + ds1.label = 'sin(x^2) exp(-x)' + + ds2 = Plottable_USAXS_Dataset() + ds2.Q = x + ds2.I = ds1.I**2 + 1.0e-5 + ds2.label = '$[\sin(x^2)\cdot\exp(-x)]^2$' + + ds3 = Plottable_USAXS_Dataset() + ds3.Q = x + ds3.I = np.sin(5*x) / (5*x) + 1.0e-5 + ds3.label = 'sin(5x)/(5x)' + + ds4 = Plottable_USAXS_Dataset() + ds4.Q = x + ds4.I = ds3.I**2 + 1.0e-5 + ds4.label = r'$[\sin(5x)/(5x)]^2$' + + livedata_plot([ds2, ds4], CHART_FILE) + + +#************************************************************************** + +if __name__ == "__main__": + main() diff --git a/pvwatch.py b/pvwatch.py new file mode 100755 index 0000000..88bc121 --- /dev/null +++ b/pvwatch.py @@ -0,0 +1,465 @@ +#!/APSshare/anaconda/x86_64/bin/python + +''' +watch the USAXS EPICS process variables and periodically write them to a file + +Start this with the shell command:: + + /APSshare/anaconda/x86_64/bin/python ./pvwatch.py >>& log.txt + +''' + + +import datetime # date/time stamps +import epics # manages EPICS (PyEpics) connections for Python 2.6+ +import logging +import numpy +import os +os.environ['HDF5_DISABLE_VERSION_CHECK'] = '2' +import os.path # testing if a file exists +import shutil # file copies +import time # provides sleep() +import traceback +from xml.dom import minidom +from xml.etree import ElementTree + +import localConfig # definitions for 9-ID +import scanplots +import wwwServerTransfers + + +LOGGER_FORMAT = "%(asctime)s.%(msecs)03d (%(levelname)s,%(process)d,%(name)s,%(lineno)d) %(message)s" +logging.basicConfig(level=logging.INFO, format=LOGGER_FORMAT, datefmt='%Y-%m-%d %H:%M:%S',) +logger = logging.getLogger(__name__) + +def logMessage(msg): + '''write a message with a timestamp and pid to the log file''' + logger.info(msg) + +try: + # better reporting of SEGFAULT + # http://faulthandler.readthedocs.org + import faulthandler + faulthandler.enable() + logger.debug("faulthandler: module enabled") +except ImportError: + logger.warning("faulthandler: module not imported") + + +GLOBAL_MONITOR_COUNTER = 0 +pvdb = {} # EPICS data will go here, cache of last known good values +xref = {} # cross-reference between mnemonics and PV names: {mne:pvname} +PVLIST_FILE = "pvlist.xml" +MAINLOOP_COUNTER_TRIGGER = 10000 # print a log message periodically +USAXS_DATA = None + + +'''value for expected EPICS PV is None''' +class NoneEpicsValue(Exception): pass + +'''pv not in pvdb''' +class PvNotRegistered(Exception): pass + + +def getSpecFileName(pv): + '''construct the name of the file, based on a PV''' + dir_pv = xref['spec_dir'] + userDir = pvdb[dir_pv]['value'] + rawName = pvdb[pv]['value'] + if userDir is None: + raise NoneEpicsValue('"None" received for spec_dir PV: {}'.format(dir_pv)) + if rawName is None: + raise NoneEpicsValue('"None" received for spec file PV: {}'.format(pv)) + specFile = os.path.join(userDir, rawName) + return specFile + + +def updateSpecMacroFile(): + '''copy the current SPEC macro file to the WWW page space''' + + if len(pvdb[xref['spec_macro_file']]['value'].strip()) == 0: + # SPEC file name PV is empty + return + specFile = getSpecFileName(xref['spec_macro_file']) + if not os.path.exists(specFile): + # Look in parent directory per 2020-09 changes + otherFile = os.path.join( + # os.path.dirname(os.path.dirname(specFile)), + localConfig.LOCAL_WWW_LIVEDATA_DIR, + os.path.basename(specFile) + ) + if not os.path.exists(otherFile): + logger.debug( + "Cannot find macro file: %s or %s", + specFile, + otherFile) + return + specFile = otherFile + if not os.path.isfile(specFile): + logger.debug(specFile + " is not a file") + return + localDir = localConfig.LOCAL_WWW_LIVEDATA_DIR + macroFile = localConfig.SPECMACRO_TXT_FILE + wwwFile = os.path.join(localDir, macroFile) + + updateFile = False + if os.path.exists(wwwFile): + spec_mtime = os.stat(specFile).st_mtime + www_mtime = os.stat(wwwFile).st_mtime + if spec_mtime > www_mtime: + updateFile = True # only if file is newer + else: + updateFile = True + if updateFile: + shutil.copy2(specFile, wwwFile) + wwwServerTransfers.nfsCpToWebServer(specFile, macroFile) + + +def updatePlotImage(): + '''make a new PNG file with the most recent USAXS scans''' + + specFile = getSpecFileName(xref['spec_data_file']) + if not os.path.exists(specFile): + logger.info(specFile + " does not exist") + return + if not os.path.isfile(specFile): + logger.info(specFile + " is not a file") + return + spec_mtime = os.stat(specFile).st_mtime + + plotFile = localConfig.LOCAL_PLOTFILE + plotFile = os.path.join(localConfig.LOCAL_WWW_LIVEDATA_DIR, plotFile) + makePlot = not os.path.exists(plotFile) # no plot yet, let's make one! + if os.path.exists(plotFile): + plot_mtime = os.stat(plotFile).st_mtime + makePlot = spec_mtime > plot_mtime # plot only if new data + + if makePlot: + logger.debug("updating the plots and gathering scan data for XML file") + scanplots.main(n=localConfig.NUM_SCANS_PLOTTED, cp=True) + + +def writeFile(filename, contents): + '''write contents to file''' + with open(filename, 'w') as f: + f.write(contents) + + +def xslt_transformation(xslt_file, src_xml_file, result_xml_file): + '''transform an XML file using an XSLT''' + # see: http://lxml.de/xpathxslt.html#xslt + from lxml import etree as lxml_etree # in THIS routine, use lxml's etree + src_doc = lxml_etree.parse(src_xml_file) + xslt_doc = lxml_etree.parse(xslt_file) + transform = lxml_etree.XSLT(xslt_doc) + result_doc = transform(src_doc) + buf = lxml_etree.tostring(result_doc, pretty_print=True) + writeFile(result_xml_file, buf) + + +def textArray(arr): + '''convert an ndarray to a text array''' + if isinstance(arr, numpy.ndarray): + return [str(_) for _ in arr] + return arr + + +def buildReport(): + '''build the report''' + t = datetime.datetime.now() + yyyymmdd = t.strftime("%Y-%m-%d") + hhmmss = t.strftime("%H:%M:%S") + + root = ElementTree.Element("usaxs_pvs") + root.set("version", "1") + node = ElementTree.SubElement(root, "written_by") + node.text = __file__ + node = ElementTree.SubElement(root, "datetime") + node.text = yyyymmdd + " " + hhmmss + + sorted_id_list = sorted(xref) + fields = ("name", "id", "description", "timestamp", + "counter", "units", "value", "raw_value", "format") + + for mne in sorted_id_list: + pv = xref[mne] + entry = pvdb[pv] + + node = ElementTree.SubElement(root, "pv") + node.set("id", mne) + node.set("name", pv) + + for item in fields: + subnode = ElementTree.SubElement(node, item) + subnode.text = str(entry[item]) + + global USAXS_DATA + if USAXS_DATA is not None and USAXS_DATA.get('usaxs', None) is not None: + try: + specfile = USAXS_DATA['file'] + node = ElementTree.SubElement(root, "usaxs_scans") + node.set("file", specfile) + for scan in USAXS_DATA['usaxs']: + scannode = ElementTree.SubElement(node, "scan") # FIXME: ? scan or node ? + for item in ('scan', 'key', 'label'): + scannode.set(item, str(scan[item])) + scannode.set('specfile', specfile) + ElementTree.SubElement(scannode, "title").text = scan['title'] + # write the scan data to the XML file + vec = ElementTree.SubElement(scannode, "Q") + vec.set('units', '1/A') + vec.text = ' '.join(textArray(scan['qVec'])) + vec = ElementTree.SubElement(scannode, "R") + vec.set('units', 'arbitrary') + vec.text = ' '.join(textArray(scan['rVec'])) + except Exception as e: + logger.info('caught Exception while writing USAXS scan data to XML file') + logger.info(' file: %s' % specfile) + logger.info(e) + + # final steps + # ProcessingInstruction for 2nd line of XML + # Cannot place this with ElementTree where it is needed + # use minidom + doc = minidom.parseString(ElementTree.tostring(root)) + # + # insert XML Processing Instruction text after first line of XML + pi = doc.createProcessingInstruction('xml-stylesheet', + 'type="text/xsl" href="raw-table.xsl"') + root = doc.firstChild + doc.insertBefore(pi, root) + xmlText = doc.toxml() # all on one line, looks bad, who cares? + #xmlText = doc.toprettyxml(indent = " ") # toprettyxml() adds extra unwanted whitespace + return xmlText + + +def report(): + '''write the values out to files''' + xmlText = buildReport() + + # WWW directory for livedata (absolute path) + localDir = localConfig.LOCAL_WWW_LIVEDATA_DIR + + #--- write the XML with the raw data from EPICS + raw_xml = localConfig.XML_REPORT_FILE + abs_raw_xml = os.path.join(localDir, raw_xml) + writeFile(abs_raw_xml, xmlText) + + wwwServerTransfers.nfsCpToWebServer(abs_raw_xml, raw_xml) + + #--- xslt transforms from XML to HTML + + # make the index.html file + index_html = localConfig.HTML_INDEX_FILE # short name + abs_index_html = os.path.join(localDir, index_html) # absolute path + xslt_transformation(localConfig.LIVEDATA_XSL_STYLESHEET, abs_raw_xml, abs_index_html) + wwwServerTransfers.nfsCpToWebServer(abs_index_html, index_html) # copy to XSD + + # display the raw data (but pre-convert it in an html page) + raw_html = localConfig.HTML_RAWREPORT_FILE + abs_raw_html = os.path.join(localDir, raw_html) + xslt_transformation(localConfig.RAWTABLE_XSL_STYLESHEET, abs_raw_xml, abs_raw_html) + wwwServerTransfers.nfsCpToWebServer(abs_raw_html, raw_html) + + # also copy the raw table XSLT + xslFile = localConfig.RAWTABLE_XSL_STYLESHEET + wwwServerTransfers.nfsCpToWebServer(xslFile, xslFile) + + # also copy the php pager software + phpFile = localConfig.LIVEDATA_PHP_PAGER + wwwServerTransfers.nfsCpToWebServer(phpFile, phpFile) + + # make the usaxstv.html file + usaxstv_html = localConfig.HTML_USAXSTV_FILE # short name + abs_usaxstv_html = os.path.join(localDir, usaxstv_html) # absolute path + xslt_transformation(localConfig.USAXSTV_XSL_STYLESHEET, abs_raw_xml, abs_usaxstv_html) + wwwServerTransfers.nfsCpToWebServer(abs_usaxstv_html, usaxstv_html) # copy to XSD + + +def update_pvdb(pv, raw_value): + if pv not in pvdb: + raise PvNotRegistered('!!!ERROR!!! %s was not found in pvdb!' % pv) + entry = pvdb[pv] + #ch = entry['ch'] + entry['timestamp'] = datetime.datetime.now() + entry['counter'] += 1 + entry['raw_value'] = raw_value + entry['value'] = entry['format'] % raw_value + + +def EPICS_monitor_receiver(*args, **kws): + '''Response to an EPICS (PyEpics) monitor on the channel''' + global GLOBAL_MONITOR_COUNTER + pv = kws['pvname'] + if pv not in pvdb: + raise PvNotRegistered('!!!ERROR!!! %s was not found in pvdb!' % pv) + if pvdb[pv]["waveform_char"]: + v = kws['char_value'] + logger.debug("CA monitor waveform string value: {} = {}".format(pv, v)) + else: + v = kws['value'] + update_pvdb(pv, v) # cache the last known good value + GLOBAL_MONITOR_COUNTER += 1 + + +def add_pv(mne, pv, desc, fmt, waveform_char=False): + '''Connect to another EPICS (PyEpics) process variable''' + if pv in pvdb: + raise Exception("%s already defined by id=%s" % (pv, pvdb[pv]['id'])) + ch = epics.PV(pv) + #ch.connect() + entry = { + 'name': pv, # EPICS PV name + 'id': mne, # symbolic name used in the python code + 'description': desc, # text description for humans + 'timestamp': None, # client time last monitor was received + 'counter': 0, # number of monitor events received + 'units': "", # engineering units + 'ch': ch, # EPICS PV channel + 'format': fmt, # format for display + 'value': None, # formatted value + 'raw_value': None, # unformatted value + 'waveform_char': waveform_char, # PV is a waveform of CHAR + } + pvdb[pv] = entry + xref[mne] = pv # mne is local mnemonic, define actual PV in pvlist.xml + ch.add_callback(EPICS_monitor_receiver) # start callbacks now + cv = ch.get_ctrlvars() + unit_renames = { # handle some non SI unit names + # old new + 'millime': 'mm', + 'millira': 'mr', + 'degrees': 'deg', + 'Volts': 'V', + 'VDC': 'V', + 'eng': '', + } + if cv is not None and 'units' in cv: + units = cv['units'] + if units in unit_renames: + units = unit_renames[units] + entry['units'] = units + if waveform_char: + v = ch.get(as_string=True) + else: + v = ch.get() + update_pvdb(pv, v) # initialize the cache + + +def initiate_PV_connections(): + '''create connections to all defined PVs''' + if not os.path.exists(PVLIST_FILE): + logger.info('could not find file: ' + PVLIST_FILE) + return + try: + tree = ElementTree.parse(PVLIST_FILE) + except Exception: + logger.info('could not parse file: ' + PVLIST_FILE) + return + + for key in tree.findall(".//EPICS_PV"): + if key.get("_ignore_", "false").lower() == "false": + mne = key.get("mne") + pv = key.get("PV") + desc = key.get("description") + fmt = key.get("display_format", "%s") # default format + waveform_char = key.get("waveform_char", "false").lower() == "true" + try: + add_pv(mne, pv, desc, fmt, waveform_char=waveform_char) + except Exception: + msg = "%s: problem connecting: %s" % (PVLIST_FILE, ElementTree.tostring(key)) + logger.warn(msg) + + +def main_event_loop_checks(mainLoopCount, nextReport, nextLog, delta_report, delta_log): + '''check events for the main event loop''' + global GLOBAL_MONITOR_COUNTER + global MAINLOOP_COUNTER_TRIGGER + dt = datetime.datetime.now() + epics.ca.poll() + + if mainLoopCount == 0: + logger.info(" %s times through main loop" % MAINLOOP_COUNTER_TRIGGER) + + if dt >= nextReport: + nextReport = dt + delta_report + + try: + # https://github.com/APS-USAXS/livedata/issues/6 + logger.debug(pvdb["9idcLAX:USAXS:sampleTitle"]["value"]) + t0 = time.time() + report() # write contents of pvdb to a file + logger.debug("report() completed in %.3f s" % (time.time() - t0)) + except Exception as exc: + msg = "problem with {}(): traceback={}".format("report", exc) + logger.warn(msg) + logger.warn(traceback.format_exc()) + + try: + updateSpecMacroFile() # copy the spec macro file + except Exception as exc: + msg = "problem with {}(): traceback={}".format("updateSpecMacroFile", exc) + logger.warn(msg) + logger.warn(traceback.format_exc()) + + try: + updatePlotImage() # update the plot + except Exception as exc: + msg = "problem with {}(): traceback={}".format("updatePlotImage", exc) + logger.warn(msg) + logger.warn(traceback.format_exc()) + + if dt >= nextLog: + nextLog = dt + delta_log + msg = "checkpoint, %d EPICS monitor events received" % GLOBAL_MONITOR_COUNTER + logger.info(msg) + GLOBAL_MONITOR_COUNTER = 0 # reset + + return nextReport, nextLog + + +def main(): + ''' + run the main event loop + ''' + global GLOBAL_MONITOR_COUNTER + test_pv = 'S:SRcurrentAI' + epics.caget(test_pv) + ch = epics.PV(test_pv) + epics.ca.poll() + connected = ch.connect(timeout=5.0) + if not connected: + logger.info('Did not connect PV: ' + str(ch)) + logger.info('program will exit') + return + + logger.info("starting pvwatch.py") + initiate_PV_connections() + + logger.info("Connected %d EPICS PVs" % len(pvdb)) + + nextReport = datetime.datetime.now() + nextLog = nextReport + delta_report = datetime.timedelta(seconds=localConfig.REPORT_INTERVAL_S) + delta_log = datetime.timedelta(seconds=localConfig.LOG_INTERVAL_S) + + # !!!!!!!!!!!!!!!!!!!!!!! # + # run the main event loop # + # !!!!!!!!!!!!!!!!!!!!!!! # + mainLoopCount = 0 + while True: + mainLoopCount = (mainLoopCount + 1) % MAINLOOP_COUNTER_TRIGGER + nextReport, nextLog = main_event_loop_checks(mainLoopCount, + nextReport, nextLog, delta_report, delta_log) + time.sleep(localConfig.SLEEP_INTERVAL_S) + + # # this exit handling will never be called + # for pv in pvdb: + # ch = pvdb[pv]['ch'] + # if ch != None: + # ch.disconnect() + # print "script is done" + + +if __name__ == '__main__': + main() diff --git a/radialprofile.py b/radialprofile.py new file mode 100644 index 0000000..76e37a0 --- /dev/null +++ b/radialprofile.py @@ -0,0 +1,270 @@ +import numpy as np + +def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return_nr=False, + binsize=0.5, weights=None, steps=False, interpnan=False, left=None, right=None, + mask=None ): + """ + Calculate the azimuthally averaged radial profile. + + image - The 2D image + center - The [x,y] pixel coordinates used as the center. The default is + None, which then uses the center of the image (including + fractional pixels). + stddev - if specified, return the azimuthal standard deviation instead of the average + returnradii - if specified, return (radii_array,radial_profile) + return_nr - if specified, return number of pixels per radius *and* radius + binsize - size of the averaging bin. Can lead to strange results if + non-binsize factors are used to specify the center and the binsize is + too large + weights - can do a weighted average instead of a simple average if this keyword parameter + is set. weights.shape must = image.shape. weighted stddev is undefined, so don't + set weights and stddev. + steps - if specified, will return a double-length bin array and radial + profile so you can plot a step-form radial profile (which more accurately + represents what's going on) + interpnan - Interpolate over NAN values, i.e. bins where there is no data? + left,right - passed to interpnan; they set the extrapolated values + mask - can supply a mask (boolean array same size as image with True for OK and False for not) + to average over only select data. + + If a bin contains NO DATA, it will have a NAN value because of the + divide-by-sum-of-weights component. I think this is a useful way to denote + lack of data, but users let me know if an alternative is prefered... + + """ + # Calculate the indices from the image + y, x = np.indices(image.shape) + + if center is None: + center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + + r = np.hypot(x - center[0], y - center[1]) + + if weights is None: + weights = np.ones(image.shape) + elif stddev: + raise ValueError("Weighted standard deviation is not defined.") + + if mask is None: + mask = np.ones(image.shape,dtype='bool') + # obsolete elif len(mask.shape) > 1: + # obsolete mask = mask.ravel() + + # the 'bins' as initially defined are lower/upper bounds for each bin + # so that values will be in [lower,upper) + nbins = int(np.round(r.max() / binsize)+1) + maxbin = nbins * binsize + bins = np.linspace(0,maxbin,nbins+1) + # but we're probably more interested in the bin centers than their left or right sides... + bin_centers = (bins[1:]+bins[:-1])/2.0 + + # how many per bin (i.e., histogram)? + # there are never any in bin 0, because the lowest index returned by digitize is 1 + #nr = np.bincount(whichbin)[1:] + nr = np.histogram(r, bins, weights=mask.astype('int'))[0] + + # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or xrange(1,nbins+1) ) + # radial_prof.shape = bin_centers.shape + if stddev: + # Find out which radial bin each point in the map belongs to + whichbin = np.digitize(r.flat,bins) + # This method is still very slow; is there a trick to do this with histograms? + radial_prof = np.array( + [ + image.flat[mask.flat*(whichbin==b)].std() + for b in xrange(1,nbins+1) + ] + ) + else: + radial_prof = ( + np.histogram(r, bins, weights=(image*weights*mask))[0] + / + np.histogram(r, bins, weights=(weights*mask))[0] + ) + + if interpnan: + radial_prof = np.interp( + bin_centers, + bin_centers[radial_prof==radial_prof], # FIXME: tests identical numbers + radial_prof[radial_prof==radial_prof], # FIXME: tests identical numbers + left=left, + right=right + ) + + if steps: + xarr = np.array(zip(bins[:-1],bins[1:])).ravel() + yarr = np.array(zip(radial_prof,radial_prof)).ravel() + return xarr,yarr + elif returnradii: + return bin_centers,radial_prof + elif return_nr: + return nr,bin_centers,radial_prof + else: + return radial_prof + +def azimuthalAverageBins(image,azbins,symmetric=None, center=None, **kwargs): + """ Compute the azimuthal average over a limited range of angles + kwargs are passed to azimuthalAverage """ + y, x = np.indices(image.shape) + if center is None: + center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + # r = np.hypot(x - center[0], y - center[1]) + theta = np.arctan2(x - center[0], y - center[1]) + theta[theta < 0] += 2*np.pi + theta_deg = theta*180.0/np.pi + + if isinstance(azbins,np.ndarray): + pass + elif isinstance(azbins,int): + if symmetric == 2: + azbins = np.linspace(0,90,azbins) + theta_deg = theta_deg % 90 + elif symmetric == 1: + azbins = np.linspace(0,180,azbins) + theta_deg = theta_deg % 180 + elif azbins == 1: + return azbins,azimuthalAverage(image,center=center,returnradii=True,**kwargs) + else: + azbins = np.linspace(0,359.9999999999999,azbins) + else: + raise ValueError("azbins must be an ndarray or an integer") + + azavlist = [] + for blow,bhigh in zip(azbins[:-1],azbins[1:]): + mask = (theta_deg > (blow % 360)) * (theta_deg < (bhigh % 360)) + rr,zz = azimuthalAverage(image,center=center,mask=mask,returnradii=True,**kwargs) + azavlist.append(zz) + + return azbins,rr,azavlist + +def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=False, + binsize=1.0, weights=None, steps=False, interpnan=False, left=None, right=None, + mask=None, symmetric=None ): + """ + Calculate the radially averaged azimuthal profile. + (this code has not been optimized; it could be speed boosted by ~20x) + + image - The 2D image + center - The [x,y] pixel coordinates used as the center. The default is + None, which then uses the center of the image (including + fractional pixels). + stddev - if specified, return the radial standard deviation instead of the average + returnAz - if specified, return (azimuthArray,azimuthal_profile) + return_naz - if specified, return number of pixels per azimuth *and* azimuth + binsize - size of the averaging bin. Can lead to strange results if + non-binsize factors are used to specify the center and the binsize is + too large + weights - can do a weighted average instead of a simple average if this keyword parameter + is set. weights.shape must = image.shape. weighted stddev is undefined, so don't + set weights and stddev. + steps - if specified, will return a double-length bin array and azimuthal + profile so you can plot a step-form azimuthal profile (which more accurately + represents what's going on) + interpnan - Interpolate over NAN values, i.e. bins where there is no data? + left,right - passed to interpnan; they set the extrapolated values + mask - can supply a mask (boolean array same size as image with True for OK and False for not) + to average over only select data. + + If a bin contains NO DATA, it will have a NAN value because of the + divide-by-sum-of-weights component. I think this is a useful way to denote + lack of data, but users let me know if an alternative is prefered... + + """ + # Calculate the indices from the image + y, x = np.indices(image.shape) + + if center is None: + center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + + # r = np.hypot(x - center[0], y - center[1]) + theta = np.arctan2(x - center[0], y - center[1]) + theta[theta < 0] += 2*np.pi + theta_deg = theta*180.0/np.pi + maxangle = 360 + + if weights is None: + weights = np.ones(image.shape) + elif stddev: + raise ValueError("Weighted standard deviation is not defined.") + + if mask is None: + # mask is only used in a flat context + mask = np.ones(image.shape,dtype='bool').ravel() + elif len(mask.shape) > 1: + mask = mask.ravel() + + # allow for symmetries + if symmetric == 2: + theta_deg = theta_deg % 90 + maxangle = 90 + elif symmetric == 1: + theta_deg = theta_deg % 180 + maxangle = 180 + + # the 'bins' as initially defined are lower/upper bounds for each bin + # so that values will be in [lower,upper) + nbins = int(np.round(maxangle / binsize)) + maxbin = nbins * binsize + bins = np.linspace(0,maxbin,nbins+1) + # but we're probably more interested in the bin centers than their left or right sides... + bin_centers = (bins[1:]+bins[:-1])/2.0 + + # Find out which azimuthal bin each point in the map belongs to + whichbin = np.digitize(theta_deg.flat,bins) + + # how many per bin (i.e., histogram)? + # there are never any in bin 0, because the lowest index returned by digitize is 1 + nr = np.bincount(whichbin)[1:] + + # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or xrange(1,nbins+1) ) + # azimuthal_prof.shape = bin_centers.shape + if stddev: + azimuthal_prof = np.array([image.flat[mask*(whichbin==b)].std() for b in xrange(1,nbins+1)]) + else: + azimuthal_prof = np.array([(image*weights).flat[mask*(whichbin==b)].sum() / weights.flat[mask*(whichbin==b)].sum() for b in xrange(1,nbins+1)]) + + #import pdb; pdb.set_trace() + + if interpnan: + azimuthal_prof = np.interp(bin_centers, + bin_centers[azimuthal_prof==azimuthal_prof], + azimuthal_prof[azimuthal_prof==azimuthal_prof], + left=left,right=right) + + if steps: + xarr = np.array(zip(bins[:-1],bins[1:])).ravel() + yarr = np.array(zip(azimuthal_prof,azimuthal_prof)).ravel() + return xarr,yarr + elif returnAz: + return bin_centers,azimuthal_prof + elif return_naz: + return nr,bin_centers,azimuthal_prof + else: + return azimuthal_prof + +def radialAverageBins(image,radbins, corners=True, center=None, **kwargs): + """ Compute the radial average over a limited range of radii """ + y, x = np.indices(image.shape) + if center is None: + center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + r = np.hypot(x - center[0], y - center[1]) + + if isinstance(radbins,np.ndarray): + pass + elif isinstance(radbins,int): + if radbins == 1: + return radbins,radialAverage(image,center=center,returnAz=True,**kwargs) + elif corners: + radbins = np.linspace(0,r.max(),radbins) + else: + radbins = np.linspace(0,np.max(np.abs(np.array([x-center[0],y-center[1]]))),radbins) + else: + raise ValueError("radbins must be an ndarray or an integer") + + radavlist = [] + for blow,bhigh in zip(radbins[:-1],radbins[1:]): + mask = (rblow) + az,zz = radialAverage(image,center=center,mask=mask,returnAz=True,**kwargs) + radavlist.append(zz) + + return radbins,az,radavlist diff --git a/reduceAreaDetector.py b/reduceAreaDetector.py new file mode 100644 index 0000000..875a806 --- /dev/null +++ b/reduceAreaDetector.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python + +'''reduceAreaDetector: reduce the raw data from Area Detector images to R(Q)''' + +import h5py +import logging +import math +import numpy +import os + +from spec2nexus import eznx +import calc +import localConfig +import pvwatch +from radialprofile import azimuthalAverage + +logger = logging.getLogger(__name__) + +# TODO: list +# [x] copy fly scan data reduction code for SAXS&WAXS +# [x] identify good set of test data (from Feb 2016) +# [x] integrate canned routine into reduction code +# [x] create developer code to test reduction code +# [x] write results to temporary HDF5 file +# [~] resolve problem with wrong-looking std dev calc +# [ ] construct and apply image mask +# [x] refactor developer code for regular use +# [x] write results back to original HDF5 file +# [x] integrate with routine data reduction code +# [x] plot on livedata page +# [x] resolve problem with h5py, cannot append reduced data to existing file + + +DEFAULT_BIN_COUNT = localConfig.REDUCED_AD_IMAGE_BINS +PIXEL_SIZE_TOLERANCE = 1.0e-6 +ESD_FACTOR = 0.01 # estimate dr = ESD_FACTOR * r if r.std() = 0 : 1% errors + +# define the locations of the source data in the HDF5 file +# there are various possible layouts +AD_HDF5_ADDRESS_MAP = { + 'local_name' : '/entry/data/local_name', + 'Dexela N2315' : { + 'image' : '/entry/data/data', + 'wavelength' : '/entry/Metadata/wavelength', + 'SDD' : '/entry/Metadata/SDD', + 'x_image_center_pixels' : '/entry/instrument/detector/beam_center_x', + 'y_image_center_pixels' : '/entry/instrument/detector/beam_center_y', + 'x_pixel_size_mm' : '/entry/instrument/detector/x_pixel_size', + 'y_pixel_size_mm' : '/entry/instrument/detector/y_pixel_size', + 'I0_counts' : '/entry/Metadata/I0_cts_gated', + 'I0_gain' : '/entry/Metadata/I0_gain', + # need to consider a detector-dependent mask + }, + 'Pilatus 100K' : { + 'image' : '/entry/data/data', + 'wavelength' : '/entry/Metadata/wavelength', + 'SDD' : '/entry/Metadata/SDD', + # image is transposed, consider that here + 'y_image_center_pixels' : '/entry/instrument/detector/beam_center_x', + 'x_image_center_pixels' : '/entry/instrument/detector/beam_center_y', + 'x_pixel_size_mm' : '/entry/instrument/detector/x_pixel_size', + 'y_pixel_size_mm' : '/entry/instrument/detector/y_pixel_size', + 'I0_counts' : '/entry/Metadata/I0_cts_gated', + 'I0_gain' : '/entry/Metadata/I0_gain', + # need to consider a detector-dependent mask + }, + 'Pilatus 300Kw' : { + 'image' : '/entry/data/data', + 'wavelength' : '/entry/Metadata/dcm_wavelength', + # TODO: Now? 'wavelength' : '/entry/Metadata/wavelength', + 'SDD' : '/entry/Metadata/SDD', + # image is transposed, consider that here + 'y_image_center_pixels' : '/entry/instrument/detector/beam_center_x', + 'x_image_center_pixels' : '/entry/instrument/detector/beam_center_y', + 'x_pixel_size_mm' : '/entry/instrument/detector/x_pixel_size', + 'y_pixel_size_mm' : '/entry/instrument/detector/y_pixel_size', + 'I0_counts' : '/entry/Metadata/I0_cts_gated', + 'I0_gain' : '/entry/Metadata/I0_gain', + # need to consider a detector-dependent mask + }, +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +class AD_ScatteringImage(object): + + def __init__(self, hdf5_file_name): + if not os.path.exists(hdf5_file_name): + raise IOError('file not found: ' + hdf5_file_name) + self.hdf5_file_name = hdf5_file_name + self.image = None + self.reduced = {} + + self.units = dict( + x = 'mm', + Q = '1/A', + R = 'none', + dR = 'none', + ) + + def read_image_data(self): + ''' + read image data from the HDF5 file, return as instance of :class:`Image` + ''' + with h5py.File(self.hdf5_file_name) as fp: + self.image = Image(fp) + self.image.read_image_data() + + return self.image + + def has_reduced(self, key = 'full'): + ''' + check if the reduced dataset is available + + :param str|int key: name of reduced dataset (default = 'full') + ''' + key = str(key) + if key not in self.reduced: + return False + return 'Q' in self.reduced[key] and 'R' in self.reduced[key] + + def reduce(self): + '''convert raw image data to R(Q), also get other terms''' + if self.image is None: # TODO: make this conditional on need to reduce image data + self.read_image_data() + + if abs(self.image.xsize - self.image.ysize) > PIXEL_SIZE_TOLERANCE: + raise ValueError('X & Y pixels have different sizes, not prepared for this') + + # TODO: construct the image mask + + # radial averaging (average around the azimuth at constant radius, repeat at all radii) + with numpy.errstate(invalid='ignore'): + radii, rAvg = azimuthalAverage(self.image.image, + center=(self.image.x0, self.image.y0), + returnradii=True) + + # standard deviation results do not look right, skip that in full data reduction + # with numpy.errstate(invalid='ignore'): + # with warnings.catch_warnings(): + # # sift out this RuntimeWarning warning from numpy + # warnings.filterwarnings('ignore', r'Degrees of freedom <= 0 for slice') + # rAvgDev = azimuthalAverage(self.image.image, + # center=(self.image.x0, self.image.y0), + # stddev=True) + + radii *= self.image.xsize + Q = (4*math.pi / self.image.wavelength) * numpy.sin(0.5*numpy.arctan2(radii, self.image.SDD)) + # scale_factor = 1 /self.image.I0_gain / self.image.I0 # TODO: verify the equation + scale_factor = 1 / self.image.I0 # TODO: verify the equation + rAvg = rAvg * scale_factor + + # remove NaNs and non-positives from output data + rAvg = numpy.ma.masked_less_equal(rAvg, 0) # mask the non-positives + rAvg = numpy.ma.masked_invalid(rAvg) # mask the NaNs + Q = calc.remove_masked_data(Q, rAvg.mask) + radii = calc.remove_masked_data(radii, rAvg.mask) + # rAvgDev = calc.remove_masked_data(rAvgDev, rAvg.mask) + rAvg = calc.remove_masked_data(rAvg, rAvg.mask) # always remove the masked array last + + full = dict(Q=Q, R=rAvg, x=radii) + self.reduced = dict(full = full) # reset the entire dictionary with new "full" reduction + + def rebin(self, bin_count=250): + ''' + generate R(Q) with a bin_count bins + + save in ``self.reduced[str(bin_count)]`` dict + ''' + if not self.has_reduced(): + self.reduce() + if 'full' not in self.reduced: + raise IndexError('no data reduction: ' + self.hdf5_file_name) + #return + + bin_count_full = len(self.reduced['full']['Q']) + bin_count = min(bin_count, bin_count_full) + s = str(bin_count) + + Q_full = self.reduced['full']['Q'] + R_full = self.reduced['full']['R'] + + # lowest non-zero Q value > 0 or minimum acceptable Q + Qmin = Q_full.min() + Qmax = 1.0001 * Q_full.max() + + # pick the Q binning + Q_bins = numpy.linspace(Qmin, Qmax, bin_count) + + qVec, rVec, drVec = [], [], [] + for xref in calc.bin_xref(Q_full, Q_bins): + if len(xref) > 0: + q = Q_full[xref] + r = R_full[xref] + # average Q & R in log-log space, won't matter to WAXS data at higher Q + if q.size > 0: + qVec.append( numpy.exp(numpy.mean(numpy.log(q))) ) + rVec.append( numpy.exp(numpy.mean(numpy.log(r))) ) + dr = r.std() + if dr == 0.0: + drVec.append( ESD_FACTOR * rVec[-1] ) + else: + drVec.append( r.std() ) + + reduced = dict( + Q = numpy.array(qVec), + R = numpy.array(rVec), + dR = numpy.array(drVec), + ) + self.reduced[s] = reduced + return reduced + + def read_reduced(self): + ''' + read any and all reduced data from the HDF5 file, return in a dictionary + + dictionary = { + 'full': dict(Q, R) + '250': dict(Q, R, dR) + '50': dict(Q, R, dR) + } + ''' + fields = self.units.keys() + reduced = {} + with h5py.File(self.hdf5_file_name, 'r') as hdf: + entry = hdf['/entry'] + for key in entry.keys(): + if key.startswith('areaDetector_reduced_'): + nxdata = entry[key] + nxname = key[len('areaDetector_reduced_'):] + d = {} + for dsname in fields: + if dsname in nxdata: + value = nxdata[dsname] + if value.size == 1: + d[dsname] = float(value[0]) + else: + d[dsname] = numpy.array(value) + reduced[nxname] = d + + self.reduced = reduced + return reduced + + def save(self, hfile = None, key = None): + ''' + save the reduced data group to an HDF5 file, return filename or None if not written + + :param str hfile: output HDF5 file name (default: input HDF5 file) + :param str key: name of reduced data set (default: nothing will be saved) + + By default, save to the input HDF5 file. + To override this, specify the output HDF5 file name when calling this method. + + * If the file exists, this will not overwrite any input data. + * Full, reduced :math:`R(Q)` goes into NXdata group:: + + /entry/areaDetector_reduced_full + + * any previous full reduced :math:`R(Q)` will be replaced. + + * It may replace the rebinned, reduced :math:`R(Q)` + if a NXdata group of the same number of bins exists. + * Rebinned, reduced :math:`R(Q)` goes into NXdata group:: + + /entry/areaDetector_reduced_ + + where ```` is the number of bins, such as (for 500 bins):: + + /entry/areaDetector_reduced_500 + + :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXentry.html + :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXdata.html + ''' + key = str(key) + if key not in self.reduced: + return + nxname = 'areaDetector_reduced_' + key + hfile = hfile or self.hdf5_file_name + ds = self.reduced[key] + try: + hdf = h5py.File(hfile, 'a') + except IOError as _exc: + # FIXME: some h5py problem in /_hl/files.py, line 101 + # this fails: fid = h5f.open(name, h5f.ACC_RDWR, fapl=fapl) + # with IOError that is improperly caught on next and then: + # fid = h5f.create(name, h5f.ACC_EXCL, fapl=fapl, fcpl=fcpl) fails with IOError + # since the second call has "name" with all lower case + # + # real problem is that these HDF5 files have the wrong uid/gid, as set by the Pilatus computer + # TODO: fix each Pilatus and this problem will go away + # TODO: change uid/gid on all the acquired HDF5 files (*.h5, *.hdf) under usaxscontrol:/share1/USAXS_data/2* + # Files should be owned by usaxs:usaxs (1810:2026), but are owned by tomo2:usaxs (500:2026) as seen by usaxs@usaxscontrol + # not enough to change the "umask" on the det@dec1122 computer, what else will fix this? + pvwatch.logMessage( "Problem writing reduced data back to file: " + hfile ) + return + if 'default' not in hdf.attrs: + hdf.attrs['default'] = 'entry' + nxentry = eznx.openGroup(hdf, 'entry', 'NXentry') + if 'default' not in nxentry.attrs: + nxentry.attrs['default'] = nxname + nxdata = eznx.openGroup(nxentry, + nxname, + 'NXdata', + signal='R', + axes='Q', + Q_indices=0, + timestamp=calc.iso8601_datetime(), + ) + for key in sorted(ds.keys()): + try: + _ds = eznx.write_dataset(nxdata, key, ds[key]) + if key in self.units: + eznx.addAttributes(_ds, units=self.units[key]) + except RuntimeError as e: + pass # TODO: reporting + hdf.close() + return hfile + + +class Image(object): + + def __init__(self, fp): + self.fp = fp + + self.filename = None + self.image = None + self.wavelength = None + self.SDD = None + self.x0 = None + self.y0 = None + self.xsize = None + self.ysize = None + self.I0 = None + self.I0_gain = None + self.hdf5_addr_map = None + + def read_image_data(self): + ''' + get the image from the HDF5 file + + determine if SAXS or WAXS based on detector name as coded into the h5addr + ''' + detector_name_h5addr = AD_HDF5_ADDRESS_MAP['local_name'] + detector_name = str(self.fp[detector_name_h5addr].value[0]) + self.hdf5_addr_map = h5addr = AD_HDF5_ADDRESS_MAP[detector_name] + self.filename = self.fp.filename + + def read_keyed_dataset(key): + dataset = self.fp[h5addr[key]] + if dataset.shape == (1, ): # historical representation of scalar value + value = dataset[0] + elif dataset.shape == (): # scalar representation + value = dataset.value + else: # array of some sort + value = numpy.array(dataset) + return value + + self.image = read_keyed_dataset('image') + self.wavelength = read_keyed_dataset('wavelength') + self.SDD = read_keyed_dataset('SDD') + self.x0 = read_keyed_dataset('x_image_center_pixels') + self.y0 = read_keyed_dataset('y_image_center_pixels') + self.xsize = read_keyed_dataset('x_pixel_size_mm') + self.ysize = read_keyed_dataset('y_pixel_size_mm') + self.I0 = read_keyed_dataset('I0_counts') + self.I0_gain = read_keyed_dataset('I0_gain') + # later, scale each image by metadata I0_cts_gated and I0_gain + # TODO: get image mask specifications + return + + +def get_user_options(): + '''parse the command line for the user options''' + import argparse + parser = argparse.ArgumentParser(prog='reduceAreaDetector', description=__doc__) + parser.add_argument('hdf5_file', + action='store', + help="NeXus/HDF5 data file name") + msg = 'how many bins in output R(Q)?' + msg += ' (default = %d)' % DEFAULT_BIN_COUNT + parser.add_argument('-n', + '--num_bins', + dest='num_bins', + type=int, + default=DEFAULT_BIN_COUNT, + help=msg) + msg = 'output file name?' + msg += ' (default = input HDF5 file)' + parser.add_argument('-o', + '--output_file', + dest='output_file', + type=str, + default='', + help=msg) + parser.add_argument('-V', + '--version', + action='version', + version='$Id$') + + parser.add_argument('--recompute-full', + dest='recompute_full', + action='store_true', + default=False, + help='(re)compute full R(Q): implies --recompute-rebinning') + + parser.add_argument('--recompute-rebinned', + dest='recompute_rebinned', + action='store_true', + default=False, + help='(re)compute rebinned R(Q)') + + return parser.parse_args() + + +def reduce_area_detector_data(hdf5_file, + num_bins, + recompute_full=False, + recompute_rebinned=False, + output_filename=None): + ''' + reduce areaDetector image data to R(Q) + + :param str hdf5_file: name of HDF5 file with AD image data + :param int num_bins: number of bins in rebinned data set + :param bool recompute_full: set True to force recompute, + even if reduced data already in data file (default: False) + :param bool recompute_rebinned: set True to force recompute, + even if reduced data already in data file (default: False) + :param str output_filename: name of file to write reduced data + if None, use hdf5_file (default: None) + ''' + needs_calc = {} + pvwatch.logMessage( "Area Detector data file: " + hdf5_file ) + scan = AD_ScatteringImage(hdf5_file) # initialize the object + + s_num_bins = str(num_bins) + output_filename = output_filename or hdf5_file + + pvwatch.logMessage( ' checking for previously-saved R(Q)' ) + scan.read_reduced() + + needs_calc['full'] = not scan.has_reduced('full') + if recompute_full: + needs_calc['full'] = True + needs_calc[s_num_bins] = not scan.has_reduced(s_num_bins) + if recompute_rebinned: + needs_calc[s_num_bins] = True + + if needs_calc['full']: + pvwatch.logMessage(' reducing Area Detector image to R(Q)') + scan.reduce() + pvwatch.logMessage( ' saving reduced R(Q) to ' + output_filename) + scan.save(hdf5_file, 'full') + needs_calc[s_num_bins] = True + + if needs_calc[s_num_bins]: + msg = ' rebinning R(Q) (from %d) to %d points' + msg = msg % (scan.reduced['full']['Q'].size, num_bins) + pvwatch.logMessage( msg ) + scan.rebin(num_bins) + pvwatch.logMessage( ' saving rebinned R(Q) to ' + output_filename ) + scan.save(hdf5_file, s_num_bins) + + return scan + + +def command_line_interface(): + '''standard command-line interface''' + cmd_args = get_user_options() + + if len(cmd_args.output_file) > 0: + output_filename = cmd_args.output_file + else: + output_filename = cmd_args.hdf5_file + + scan = reduce_area_detector_data(cmd_args.hdf5_file, + cmd_args.num_bins, + recompute_full=cmd_args.recompute_full, + recompute_rebinned=cmd_args.recompute_rebinned, + output_filename=output_filename) + return scan + + +if __name__ == '__main__': + # # for developer use only + # import sys + # # sys.argv.append("/share1/USAXS_data/2018-01/01_30_Settle_waxs/Adam_0184.hdf") + # sys.argv.append("/tmp/Adam_0184.hdf") + command_line_interface() diff --git a/reduceFlyData.py b/reduceFlyData.py new file mode 100755 index 0000000..ecf416e --- /dev/null +++ b/reduceFlyData.py @@ -0,0 +1,867 @@ +#!/usr/bin/env python + +'''reduceFlyScanData: reduce the raw data from USAXS Fly Scans to R(Q)''' + +import h5py +import logging +import math +import numpy +import os +import scipy.interpolate +import shutil +import stat +from spec2nexus import eznx +import calc +import localConfig +import pvwatch +import ustep +import sys + + +logger = logging.getLogger(__name__) +ARCHIVE_SUBDIR_NAME = 'archive' +DEFAULT_BIN_COUNT = localConfig.REDUCED_FLY_SCAN_BINS +FIXED_VF_GAIN = localConfig.FIXED_VF_GAIN +MCA_CLOCK_FREQUENCY = localConfig.MCA_CLOCK_FREQUENCY +Q_MIN = localConfig.FLY_SCAN_Q_MIN +UATERM = localConfig.FLY_SCAN_UATERM +ESD_FACTOR = 0.01 # estimate dr = ESD_FACTOR * r if r.std() = 0 + +# mbbi PV should return strings but instead returns index number +# these are the values and a cross-reference to the name strings +AR_MODE_FIXED = 0 # fixed pulses (version 1) +AR_MODE_ARRAY = 1 # use PulsePositions +AR_MODE_TRAJECTORY = 2 # use trajectory points (a.k.a. waypoints) +MODENAME_XREF = { # these are the strings the PV *should* return + AR_MODE_FIXED: 'Fixed', + AR_MODE_ARRAY: 'Array', + AR_MODE_TRAJECTORY: 'TrajPts', + } + +# raised when HDF5 file exists but length of raw data is zero +class NoFlyScanData(IndexError): pass + + +def decode_h5py_byte_string(value): + """ + Convert (arrays of) byte-strings to (list of) unicode strings. + + Due to limitations of HDF5, all strings are saved as byte-strings or arrays + of byte-stings, so they must be converted back to unicode. All other typed + objects pass unchanged. + + Zero-dimenstional arrays are replaced with None. + """ + if (isinstance(value, numpy.ndarray) and value.dtype.kind in ['O', 'S']): + if value.size > 0: + return value.astype('U').tolist() + else: + return None + elif isinstance(value, (bytes, numpy.bytes_)): + return value.decode(sys.stdout.encoding or "utf8") + else: + return value + + +class UsaxsFlyScan(object): + ''' + reduce the raw data for one USAXS Fly Scan + + Goals + ===== + + * reduce the raw USAXS data to standard I(Q) + * read raw data + * reconstruct AR + * reconstruct R(ar) + * compute Q from AR + * rebin data to desired size and Q range + + Primary Input Data + ================== + + The primary input data for this class is an HDF file produced by + *saveFlyData.py*. The principal contents are: + + * several channels from a multi-channel scaler (MCS) + * arrays of when the photodiode amplifier range changed + * various metadata about detector, instrument, user, and sample + + A USAXS Fly Scan is generated by creating an array of + waypoints for the AR stage to reach at regular time intervals. + The waypoint array is generated from SPEC and forms the + primary motion profile for the scan. Other motors, AY and DY, + are slaved to this profile and moved accordingly. + + The progress of AR motion is measured as a stream of pulses + from the Aerotech Ensemble motor controller. A pulse is emitted + for a specified increment of AR encoder position. The increment + (:math:\Delta E_{ar}`) is a parameter specified in the metadata. + The pulses are used to advance the channel pointer of the MCS. + + Each MCS channel, :math:`i`, corresponds to one specific AR encoder position. + The AR encoder position array is reconstructed: + + .. math:: AR[i] = AR_{start} - i \times\ \Delta E_{ar} + + where :math:`AR_{start}` is the starting AR position. + + .. note:: Note the *minus* sign in the previous equation! + + The various input signals to the MCS are: + + * clock (:math:`p_t`) + * monitor pulses (:math:`p_m`) + * detector pulses (:math:`p_d`) + + The measurement time spent in any single channel, :math:`i`, of the MCS is calculated: + + .. math:: t[i] = p_t[i] f_{clock} + + where :math`f_{clock}` is the clock frequency (also recorded in the metadata). + + The count rate for the monitor is calculated: + + .. math:: M[i] = p_m[i] / t[i] + + The count rate for the detector is calculated: + + .. math:: D[i] = p_d[i] / t[i] + + The detector signal is modified by the detector amplifier gain, :math:`A`, + which is selected by the amplifier range, :math:`r`, (an integer number from 0 to 4) + and also by the inherent background of the selected amplifier range, :math:`B`. + + .. caution: Describe how :math:`r` is obtained. Also describe data masking. + + .. note:: Actually, the monitor also has a gain and background which is + patterned on the same terms for the detector signal + + The ratio intensity, :math:`R`, is calculated as the ratio of the two corrected count rates + (where subscripts :math:`d` and :math:`m` refer to detector and monitor, respectively): + + .. math:: R[i] = { ( A_d[r[i]] * ( D[i] - B_d[r[i]] ) \over ( A_m[r[i]] * ( M[i] - B_m[r[i]] ) } + + Data from the HDF5 file + ======================= + + ===================== ====================================================== =========================================================== + term HDF address path description + ===================== ====================================================== =========================================================== + :math:`n` /entry/flyScan/AR_pulses number of data points in MCA arrays + :math:`p_t` /entry/flyScan/mca1 array of clock pulses + :math:`p_m` /entry/flyScan/mca2 array of monitor pulses + :math:`p_d` /entry/flyScan/mca3 array of detector pulses + :math:`f_{clock}` /entry/flyScan/mca_clock_frequency MCA clock frequency + :math:`AR_{start}` /entry/flyScan/AR_start AR starting position + :math:`\Delta E_{ar}` /entry/flyScan/AR_increment AR channel increment + changes_mcsChan /entry/flyScan/changes_AMP_mcsChan array of MCS channels for range change events + changes_ampGain /entry/flyScan/changes_AMP_ampGain array of AMPlifier range change events (provides :math:`r`) + changes_ampReqGain /entry/flyScan/changes_AMP_ampReqGain array of requested AMPlifier range change events + :math:`A[0]` /entry/metadata/upd_gain0 amplifier gain for range 0 + :math:`A[1]` /entry/metadata/upd_gain1 amplifier gain for range 1 + :math:`A[2]` /entry/metadata/upd_gain2 amplifier gain for range 2 + :math:`A[3]` /entry/metadata/upd_gain3 amplifier gain for range 3 + :math:`A[4]` /entry/metadata/upd_gain4 amplifier gain for range 4 + :math:`B[0]` /entry/metadata/upd_bkg0 amplifier background count rate for range 0 + :math:`B[1]` /entry/metadata/upd_bkg1 amplifier background count rate for range 1 + :math:`B[2]` /entry/metadata/upd_bkg2 amplifier background count rate for range 2 + :math:`B[3]` /entry/metadata/upd_bkg3 amplifier background count rate for range 3 + :math:`B[4]` /entry/metadata/upd_bkg4 amplifier background count rate for range 4 + ===================== ====================================================== =========================================================== + + example:: + + hfile = 'some/HDF5/testfile.h5' + ufs = UsaxsFlyScan(hfile) + ufs.make_archive() + #ufs.read_reduced() + ufs.reduce() + ufs.rebin(250) + ufs.save(hfile, 'full') + ufs.save(hfile, 250) + + ''' + + def __init__(self, hdf5_file_name): + if not os.path.exists(hdf5_file_name): + raise IOError('file not found: ' + hdf5_file_name) + + self.units = dict( + ar = 'degrees', + upd_ranges = '', + Q = '1/A', + R = 'none', + dR = 'none', + R_max = 'none', + AR_R_peak = 'degrees', + ar_r_peak = 'degrees', + r_peak = 'none', + ar_0 = 'degrees', + fwhm = 'degrees', + ) + self.min_step_factor = 1.5 + self.uaterm = UATERM + self.bin_count = DEFAULT_BIN_COUNT + + self.hdf5_file_name = hdf5_file_name + self.reduced = {} + + def has_reduced(self, key = 'full'): + ''' + check if the reduced dataset is available + + :param str|int key: name of reduced dataset (default = 'full') + ''' + if key not in self.reduced: + return False + return 'Q' in self.reduced[key] and 'R' in self.reduced[key] + + def reduce(self): + '''convert raw Fly Scan data to R(Q), also get other terms''' + if not os.path.exists(self.hdf5_file_name): + raise IOError('file not found: ' + self.hdf5_file_name) + with h5py.File(self.hdf5_file_name, 'r') as hdf: + + pname = self.get_program_name(hdf) + config_version = self.get_config_version(pname) + mode_number = self.get_mode_number(hdf, config_version) + # _mode_name = self.get_mode_name(mode_number) + + raw = hdf['entry/flyScan'] + + wavelength = float(hdf['/entry/instrument/monochromator/wavelength'][0]) + # ar_center = float(hdf['/entry/metadata/AR_center'][0]) + + raw_clock_pulses = raw['mca1'] + raw_I0 = raw['mca2'] + raw_upd = raw['mca3'] + if len(raw_clock_pulses) == 0: + raise NoFlyScanData('no data found in file: ' + str(self.hdf5_file_name)) + + # unused + # AR_start = float(raw['AR_start'][0]) + # AR_increment = float(raw['AR_increment'][0]) + + # compute the AR values from the MCA waveforms + raw_ar = self.get_raw_ar(hdf, mode_number) + + # V_f_gain = FIXED_VF_GAIN # unused + pulse_frequency = raw['mca_clock_frequency'][0] or MCA_CLOCK_FREQUENCY + channel_time_s = raw_clock_pulses / pulse_frequency + + amp_name = self.get_USAXS_PD_amplifier_name(hdf) + upd_ranges = self.get_ranges(hdf, amp_name) + upd_ranges = self.apply_upd_range_change_time_mask(hdf, upd_ranges, channel_time_s) + gains = self.get_gain(hdf, amp_name) + bkg = self.get_bkg(hdf, amp_name) + + upd_gain = get_channels_from_signals_and_ranges(gains, upd_ranges) + upd_dark = get_channels_from_signals_and_ranges(bkg, upd_ranges) + + I0_amp_gain = float(hdf['/entry/metadata/I0AmpGain'][0]) + + if mode_number in (AR_MODE_ARRAY, AR_MODE_TRAJECTORY): + # consequence of Aerotech HLe providing no useful data in 1st channel + # upd_ranges = upd_ranges[1:] + upd_gain = upd_gain[1:] + upd_dark = upd_dark[1:] + # truncate in case PSO oscillations were corrected + n = len(raw_upd) + # upd_ranges = upd_ranges[:n] + upd_gain = upd_gain[:n] + upd_dark = upd_dark[:n] + + # ensure arrays have equal lengths + list_of_arrays = [raw_upd, channel_time_s, upd_dark, upd_gain, raw_ar, raw_I0] + min_n = min(map(len, list_of_arrays)) + max_n = max(map(len, list_of_arrays)) + if min_n != max_n: # truncate arrays to shortest length + n = min(min_n, max_n) + # pvwatch.logMessage( " truncating all arrays to " + str(n) + " points" ) + raw_upd = raw_upd[:n] + channel_time_s = channel_time_s[:n] + upd_dark = upd_dark[:n] + upd_gain = upd_gain[:n] + raw_ar = raw_ar[:n] + raw_I0 = raw_I0[:n] + + full = calc.calc_R_Q(wavelength, # wavelength + raw_ar, # ar + channel_time_s, # seconds + raw_upd, # pd + upd_dark, # pd_bkg + upd_gain, # pd_gain + raw_I0, # I0 + I0_gain=I0_amp_gain, + # ar_center=ar_center, + ar_center=None, # compute center from R(ar) + ) + + if len(full['R']) == 0: + return + full['R_max'] = full['R'].max() + + self.reduced = dict(full = full) + + def PSO_oscillation_correction(self, hdf, mode, num_AR): + ''' + compute array new AR values from 10Hz encoder sampling + + :param h5py.File hdf: HDF5 file object with the fly scan data + :param int mode: one of these [AR_MODE_ARRAY, AR_MODE_TRAJECTORY] + :param int num_AR: length of new AR array + :return numpy.array: array of computed ar values + + The Aerotech HLe and the AR encoder together are sensitive to vibrations + of the USAXS table and environment. The sensitivity appears as jitter in + the PSO pulse train and renders the reported array of AR positions wrong. + The best fix for this, to date, is to re-compute the AR values based + on AR encoder values sampled at 10Hz from EPICS. Here's how: + + * get the AR encoder v. PSO pulse data from the 10 Hz arrays + * interpolate AR(PSO) at each desired PSO + + Good luck. + ''' + if mode not in (AR_MODE_ARRAY, AR_MODE_TRAJECTORY): + raise ValueError('PSO_oscillation_correction: wrong mode = ' + str(mode)) + + # TODO: find better way to report this information + # TODO: show scan identification, this is too generic + logger.warning(" possible vibrations during scan, re-generating AR from 10Hz sampling array") + pso, ar = self.getAR_10Hz_Array(hdf) + + linear_interpolation_func = scipy.interpolate.interp1d(pso, ar) + new_ar = linear_interpolation_func(range(num_AR)) + return new_ar + + def rebin(self, bin_count = None): + '''generate R(Q) with a bin_count bins, save in ``self.reduced[str(bin_count)]`` dict''' + if not self.has_reduced(): + self.reduce() + + bin_count = bin_count or self.bin_count + s = str(bin_count) + + Q_full = self.reduced['full']['Q'] + R_full = self.reduced['full']['R'] + + # lowest non-zero Q value > 0 or minimum acceptable Q + if 'full' not in self.reduced or len(self.reduced['full']['Q']) == 0: + return + Qmin = max(Q_MIN, Q_full[numpy.where(Q_full > 0)].min() ) + Qmax = 1.0001 * Q_full.max() + + # pick smallest Q step size from input data, scale by a factor + minStep = self.min_step_factor * numpy.min(Q_full[1:] - Q_full[:-1]) + # compute bin edges from ustep + Q_bins = numpy.array(ustep.ustep(Qmin, 0.0, Qmax, bin_count+1, self.uaterm, minStep).series) + qVec, rVec, drVec = [], [], [] + for xref in calc.bin_xref(Q_full, Q_bins): + if len(xref) > 0: + q = Q_full[xref] + r = R_full[xref] + if r.min() <= 0: # TODO: fix this in reduce() + r = numpy.ma.masked_less_equal(r, 0) + q = calc.remove_masked_data(q, r.mask) + r = calc.remove_masked_data(r, r.mask) + if q.size > 0: + qVec.append( numpy.exp(numpy.mean(numpy.log(q))) ) + rVec.append( numpy.exp(numpy.mean(numpy.log(r))) ) + dr = r.std() + if dr == 0.0: + drVec.append( ESD_FACTOR * rVec[-1] ) + else: + drVec.append( r.std() ) + + reduced = dict( + Q = numpy.array(qVec), + R = numpy.array(rVec), + dR = numpy.array(drVec), + ) + self.reduced[s] = reduced + return reduced + + def read_reduced(self): + ''' + read any and all reduced data from the HDF5 file, return in a dictionary + + dictionary = { + 'full': dict(Q, R, R_max, ar, fwhm, centroid) + '250': dict(Q, R, dR) + '5000': dict(Q, R, dR) + } + ''' + fields = self.units.keys() + reduced = {} + with h5py.File(self.hdf5_file_name, 'r') as hdf: + entry = hdf['/entry'] + for key in entry.keys(): + if key.startswith('flyScan_reduced_'): + nxdata = entry[key] + nxname = key[len('flyScan_reduced_'):] + d = {} + for dsname in fields: + if dsname in nxdata: + value = nxdata[dsname] + if value.size == 1: + d[dsname] = float(value[0]) + else: + d[dsname] = numpy.array(value) + reduced[nxname] = d + + self.reduced = reduced + return reduced + + def save(self, hfile = None, key = None): + ''' + save the reduced data group to an HDF5 file, return filename or None if not written + + :param str hfile: output HDF5 file name (default: input HDF5 file) + :param str key: name of reduced data set (default: nothing will be saved) + + By default, save to the input HDF5 file. + To override this, specify the output HDF5 file name when calling this method. + + * If the file exists, this will not overwrite any input data. + * Full, reduced :math:`R(Q)` goes into NXdata group:: + + /entry/flyScan_reduced_full + + * any previous full reduced :math:`R(Q)` will be replaced. + + * It may replace the rebinned, reduced :math:`R(Q)` + if a NXdata group of the same number of bins exists. + * Rebinned, reduced :math:`R(Q)` goes into NXdata group:: + + /entry/flyScan_reduced_ + + where ```` is the number of bins, such as (for 500 bins):: + + /entry/flyScan_reduced_500 + + :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXentry.html + :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXdata.html + ''' + # TODO: save with NXprocess/NXdata structure + # TODO: link that NXdata up to NXentry level + # TODO: change /NXentry@default to point to best NXdata reduced + # TODO: What about NXcanSAS? + key = str(key) + if key not in self.reduced: + return + nxname = 'flyScan_reduced_' + key + hfile = hfile or self.hdf5_file_name + ds = self.reduced[key] + with h5py.File(hfile, 'a') as hdf: + if 'default' not in hdf.attrs: + hdf.attrs['default'] = 'entry' + nxentry = eznx.openGroup(hdf, 'entry', 'NXentry') + if 'default' not in nxentry.attrs: + nxentry.attrs['default'] = nxname + nxdata = eznx.openGroup(nxentry, + nxname, + 'NXdata', + signal='R', + axes='Q', + Q_indices=0, + timestamp=calc.iso8601_datetime(), + ) + for key in sorted(ds.keys()): + try: + _ds = eznx.write_dataset(nxdata, key, ds[key]) + if key in self.units: + eznx.addAttributes(_ds, units=self.units[key]) + except RuntimeError as e: + pass # TODO: reporting + + return hfile + + def get_program_name(self, hdf): + if 'program_name' not in hdf['/entry']: + raise KeyError('no /entry/program_name in file: ' + self.hdf5_file_name) + return hdf['/entry/program_name'] + + def get_config_version(self, pname): + if 'config_version' in pname.attrs: + config_version = pname.attrs['config_version'] + else: + config_version = '1' + return config_version + + def get_mode_number(self, hdf, config_version): + if config_version in ('1', '1.0'): + mode_number = 0 + elif config_version in ('1.1', '1.2'): + mode_number = hdf['/entry/flyScan/AR_PulseMode'][0] + else: + hdf.close() + raise ValueError( + "Unexpected /entry/program_name/@config_version = " + config_version + ) + return mode_number + + def get_mode_name(self, mode_number): + if mode_number in MODENAME_XREF: + # just in case this is useful + _mode_name = MODENAME_XREF[mode_number] + else: + raise ValueError( + 'Unexpected /entry/flyScan/AR_PulseMode value = ' + str(mode_number) + ) + return _mode_name + + def get_raw_ar(self, hdf, mode_number): + raw = hdf['entry/flyScan'] + raw_num_points = int(raw['AR_pulses'][0]) + AR_start = float(raw['AR_start'][0]) + + if mode_number == AR_MODE_FIXED: # often more than ten thousand points + AR_increment = float(raw['AR_increment'][0]) + raw_ar = AR_start - numpy.arange(raw_num_points) * AR_increment + PSO_oscillations_found = False + + elif mode_number in (AR_MODE_ARRAY, AR_MODE_TRAJECTORY): + keymap = {AR_MODE_ARRAY: '/entry/flyScan/AR_PulsePositions', # a few thousand points + AR_MODE_TRAJECTORY: '/entry/flyScan/AR_waypoints'} # a few hundred points + raw_ar = numpy.array(hdf[keymap[mode_number]]) + raw_ar = raw_ar - raw_ar[0] + AR_start + if len(raw_ar) > raw_num_points: + raw_ar = raw_ar[:raw_num_points] # truncate unused bins, if needed + + # note: Aerotech HLe system does not report any data for first channel. + # Shift data to have mean AR value for each point, + # not the end of the AR value, when the system advanced to next point. + raw_ar = (raw_ar[1:] + raw_ar[:-1])/2 # midpoint of each interval + raw_clock_pulses = raw['mca1'] + PSO_oscillations_found = len(raw_clock_pulses) != len(raw_ar) + + if PSO_oscillations_found: + # Aerotech's Position Synchronized Output (PSO) feature + raw_ar = self.PSO_oscillation_correction(hdf, mode_number, len(raw_clock_pulses)) + + return raw_ar + + def getAR_10Hz_Array(self, hdf): + ''' + return arrays of AR encoder v. PSO pulse + + :param h5py.File hdf: HDF5 file object with the fly scan data + :return (numpy.array,numpy.array): (pso, ar) arrays + + read the AR encoder positions sampled in EPICS at 10Hz + these are recorded with the corresponding PSO pulse number + ''' + ch_angle = hdf['/entry/flyScan/changes_AR_angle'] + ch_PSOpulse = hdf['/entry/flyScan/changes_AR_PSOpulse'] + + # for every PSO channel, make a list of all ar values + # use a temporary dictionary for this + channel = {} + n = len(ch_PSOpulse[()]) + for index in range(n): + x = ch_PSOpulse[index] + y = ch_angle[index] + if x not in channel: + channel[x] = [] + if len(channel)>1 and x == 0.0: + break # all useful channels received, ignore remaining buffer + channel[x].append(y) + + pso = sorted(channel.keys()) + if pso[0] != 0.0: + raise ValueError('1st PSO pulse in 10Hz array is not zero') + # first PSO channel 0 may be repeated, keep the last one + channel[0.0] = channel[0.0][-1] + # last PSO channel may be repeated, keep the first one + index = pso[-1] # index of the last channel + channel[index] = channel[index][0] + + # average all the other channels, x:PSO, y:AR + for x, y in channel.items(): + if isinstance(y, list): # list items have not been handled yet + if len(y) == 1: + channel[x] = y[0] + else: + channel[x] = numpy.array(y).mean() + + ar = list(map(lambda xx: channel[xx], pso)) # map() is faster than this: [channel[_] for _ in pso] + + return numpy.array(pso), numpy.array(ar) + + def get_USAXS_PD_amplifier_name(self, hdf): + '''return the name of the chosen USAXS photodiode amplifier''' + base = '/entry/flyScan/upd_flyScan_amplifier' + amp_index = hdf[base][0] + labels = {0: base+'_ZNAM', 1: base+'_ONAM'} + return decode_h5py_byte_string(hdf[labels[amp_index]][0]) + + def get_range_changes(self, hdf, ampName): + '''get the arrays of range change information for the named amplifier''' + def get_key(key): + return list(map(int, hdf[f"{base}{key}"][()])) + #num_channels = hdf['/entry/flyScan/AR_pulses'][0] # planned length + ampName = decode_h5py_byte_string(ampName) + base = f'/entry/flyScan/changes_{ampName}_' + arr_channel = get_key('mcsChan') + arr_requested = get_key('ampReqGain') + arr_actual = get_key('ampGain') + return arr_channel, arr_requested, arr_actual + + def get_ranges(self, hdf, identifier): + ''' + return a numpy masked array of detector range changes + + mask is applied during a range change when requested != actual + mask is applied for first and last point + + :param obj hdf: opened HDF5 file instance + :param str identifier: amplifier name, as stored in the HDF5 file + ''' + + def assign_range_value(start, end, range_value): + '''short-hand assignment''' + num_values = end - start + if num_values >= 0: # define the valid range + ranges[start:end] = numpy.zeros((num_values,)) + range_value + + changes = self.get_range_changes(hdf, identifier) + #num_channels = hdf['/entry/flyScan/AR_pulses'][0] # planned length + num_channels = len(hdf['/entry/flyScan/mca1']) # actual length + ranges = numpy.arange(int(num_channels)) + + mask_value = -1 + last = None + for chan, requested, actual in zip(*changes): + if last is not None: + if chan < last['chan']: # end of Fly Scan range change data + break + if requested == actual: + # last range change complete -- mark the range change as invalid + assign_range_value(last['chan'], chan, mask_value) + else: + # next range change starting -- mark the range for these channels + assign_range_value(last['chan']+1, chan, last['actual']) + if chan < num_channels: + ranges[chan] = mask_value + last = dict(chan=chan, requested=requested, actual=actual) + + if last is not None: # mark the final range + assign_range_value(last['chan']+1, num_channels, last['actual']) + + ranges[-1] = mask_value # assume this, for good measure + + return numpy.ma.masked_less_equal(ranges, mask_value) + + def get_gain(self, hdf, amplifier): + ''' + get gains for named amplifier from the HDF5 file + + :param obj hdf: opened HDF5 file instance + :param str amplifier: amplifier name, as stored in the HDF5 file + ''' + base = '/entry/metadata/' + amplifier + '_gain' + gain = list(map(lambda x: hdf[base+str(x)][0], range(5))) + return gain + + + def get_bkg(self, hdf, amplifier): + ''' + get backgrounds for named amplifier from the HDF5 file + + :param obj hdf: opened HDF5 file instance + :param str amplifier: amplifier name, as stored in the HDF5 file + ''' + base = '/entry/metadata/' + amplifier + '_bkg' + bkg = list(map(lambda x: hdf[base+str(x)][0], range(5))) + return bkg + + def apply_upd_range_change_time_mask(self, hdf, upd_ranges, channel_time_s): + ''' + apply mask for specified time after a range change + + :param obj hdf: open FlyScan data file (h5py File object) + :param obj upd_ranges: photodiode amplifier range (numpy masked ndarray) + :param obj channel_time_s: measurement time in each channel, s (numpy ndarray) + ''' + # :param [float] mask_times: elapsed time after after range change + # in which data should be masked + # mask is applied to pd_ranges + base = '/entry/metadata/upd_amp_change_mask_time' + mask_times = list(map(lambda r: float(hdf[base + str(r)][0]), range(5))) + amp_name = self.get_USAXS_PD_amplifier_name(hdf) + changes = self.get_range_changes(hdf, amp_name) + num_channels = len(upd_ranges) + + # modify the masks on upd_ranges at start of each range change + length = len(channel_time_s) + for channel, requested, actual in zip(*changes): + i = int(channel) + if i == 0: + continue + if requested != actual: + if i < num_channels: + upd_ranges[i] = numpy.ma.masked # mask this point + continue + timer = mask_times[int(actual)] + while timer > 0 and i < length: + upd_ranges[i] = numpy.ma.masked # mask this point + timer = max(0, timer - channel_time_s[i]) # decrement the time of this channel + i += 1 + + return upd_ranges + + def mean_sigma(self, x, w): + ''' + return mean and standard deviation of weighted x array + + :param numpy.array x: array to be averaged + :param numpy.array w: array of weights + ''' + xw = x * w + sumX = numpy.sum(xw) + sumXX = numpy.sum(xw * xw) + sumWt = numpy.sum(w) + centroid = sumX / sumWt + try: + sigma = math.sqrt( (sumXX - (sumX*sumX)/sumWt) / (sumWt - 1) ) + except Exception: + sigma = 0.0 + return centroid, sigma + + def make_archive(self): + ''' + archive the original data before writing new items to it + + This method checks for the presence of such a file in + a subdirectory below the HDF5 file. If found there, + this method does nothing. If not found, this method creates + the subdirectory (if necessary) and copies the HDF5 to + that subdirectory and makes the HDF5 file there to be read-only. + ''' + result = None + path, hfile = os.path.split(self.hdf5_file_name) + archive_dir = os.path.join(path, ARCHIVE_SUBDIR_NAME) + archive_file = os.path.join(archive_dir, hfile) + if not os.path.exists(archive_dir): + os.mkdir(archive_dir) + if not os.path.exists(archive_file): + shutil.copy2(self.hdf5_file_name, archive_file) # copy hfile to archive_file + mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH + os.chmod(archive_file, mode) # make archive_file read-only to all + result = archive_file + return result + + +def get_channels_from_signals_and_ranges(signal, ranges): + '''convert signal and upd amplifier ranges to value for channel''' + channels = numpy.array([0,] + signal)[ranges.data+1] + channels = numpy.ma.masked_less_equal(channels, 0) + return channels + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +def get_user_options(): + '''parse the command line for the user options''' + import argparse + parser = argparse.ArgumentParser(prog='reduceUsaxsFlyScan', description=__doc__) + parser.add_argument('hdf5_file', + action='store', + help="NeXus/HDF5 data file name") + msg = 'how many bins in output R(Q)?' + msg += ' (default = %d)' % DEFAULT_BIN_COUNT + parser.add_argument('-n', + '--num_bins', + dest='num_bins', + type=int, + default=DEFAULT_BIN_COUNT, + help=msg) + msg = 'output file name?' + msg += ' (default = input HDF5 file)' + parser.add_argument('-o', + '--output_file', + dest='output_file', + type=str, + default='', + help=msg) + parser.add_argument('-V', + '--version', + action='version', + version='$Id$') + + parser.add_argument('--recompute-full', + dest='recompute_full', + action='store_true', + default=False, + help='(re)compute full R(Q): implies --recompute-rebinning') + + parser.add_argument('--recompute-rebinned', + dest='recompute_rebinned', + action='store_true', + default=False, + help='(re)compute rebinned R(Q)') + + msg = 'do NOT archive the original file before saving R(Q)' + parser.add_argument('--no-archive', + dest='no_archive', + action='store_true', + default=False, + help=msg) + + return parser.parse_args() + + +def command_line_interface(): + '''standard command-line interface''' + cmd_args = get_user_options() + + if len(cmd_args.output_file) > 0: + output_filename = cmd_args.output_file + else: + output_filename = cmd_args.hdf5_file + s_num_bins = str(cmd_args.num_bins) + + needs_calc = {} + pvwatch.logMessage( "Reading USAXS FlyScan data file: " + cmd_args.hdf5_file ) + scan = UsaxsFlyScan(cmd_args.hdf5_file) + + # 2015-06-08,prj: no need for archives now + #if cmd_args.no_archive: + # print ' skipping check for archived original file' + #else: + # afile = scan.make_archive() + # if afile is not None: + # print ' archived original file to ' + afile + + pvwatch.logMessage( ' checking for previously-saved R(Q)' ) + scan.read_reduced() + needs_calc['full'] = not scan.has_reduced('full') + if cmd_args.recompute_full: + needs_calc['full'] = True + needs_calc[s_num_bins] = not scan.has_reduced(s_num_bins) + if cmd_args.recompute_rebinned: + needs_calc[s_num_bins] = True + # needs_calc['250'] = True # FIXME: developer only + + if needs_calc['full']: + pvwatch.logMessage(' reducing FlyScan to R(Q)') + scan.reduce() + if 'full' not in scan.reduced: + pvwatch.logMessage( ' no reduced R(Q) when checking for previously-saved ' + output_filename) + return + pvwatch.logMessage( ' saving reduced R(Q) to ' + output_filename) + scan.save(cmd_args.hdf5_file, 'full') + needs_calc[s_num_bins] = True + if needs_calc[s_num_bins]: + pvwatch.logMessage( ' rebinning R(Q) (from %d) to %d points' % (scan.reduced['full']['Q'].size, cmd_args.num_bins) ) + scan.rebin(cmd_args.num_bins) + pvwatch.logMessage( ' saving rebinned R(Q) to ' + output_filename ) + scan.save(cmd_args.hdf5_file, s_num_bins) + return scan + + +if __name__ == '__main__': + command_line_interface() diff --git a/scanplots.py b/scanplots.py new file mode 100755 index 0000000..c17ff84 --- /dev/null +++ b/scanplots.py @@ -0,0 +1,530 @@ +#!/usr/bin/env python + +'''make plots of the last *n* scans in the scanlog''' + + +import datetime +import logging +import numpy +import os + +import calc +import localConfig +import plot_mpl +import reduceAreaDetector +import reduceFlyData +import wwwServerTransfers +import xmlSupport + + +logger = logging.getLogger(__name__) +# logger.setLevel(logging.DEBUG) +SCANLOG = '/share1/local_livedata/scanlog.xml' +NUMBER_SCANS_TO_PLOT = 5 +scan_cache = None +spec_file_cache = None +scan_macro_list = "uascan sbuascan FlyScan sbFlyScan pinSAXS SAXS WAXS Flyscan".split() + + +class ScanCache(object): + '''cache of scans already parsed''' + # singleton class + + def __init__(self): + self.db = {} + + def add(self, scan): + key = scan.safe_id + if key in self.db: + raise KeyError(key + ' already in ScanCache') + self.db[key] = scan + + def get(self, key): + if key in self.db: + return self.db[key] + + def get_keys(self): + return self.db.keys() + + def delete(self, key): + if key in self.db: + del self.db[key] + + +class SpecFileObject(object): + '''contents and metadata about a SPEC data file on disk''' + + def __init__(self, specfile): + if os.path.exists(specfile): + from spec2nexus.spec import SpecDataFile + self.filename = specfile + stat = os.stat(specfile) + self.size = stat.st_size + self.mtime = stat.st_mtime + self.sdf_object = SpecDataFile(specfile) + + +class SpecFileCache(object): + '''cache of SPEC data files already parsed''' + # singleton class + + def __init__(self): + self.db = {} # index by file name + + def _add_(self, specfile): + if specfile in self.db: + raise KeyError(specfile + ' already in SpecFileCache') + self.db[specfile] = SpecFileObject(specfile) + return self.db[specfile] + + def get(self, specfile): + if specfile in self.db: + # check if file is unchanged + cached = self.db[specfile] + stat = os.stat(specfile) + size = stat.st_size + mtime = stat.st_mtime + if mtime == cached.mtime and size == cached.size: + return cached.sdf_object + else: + # reload if file has changed + self._del_(specfile) + + return self._add_(specfile).sdf_object + + def get_keys(self): + return self.db.keys() + + def _del_(self, key): + if key in self.db: + del self.db[key] + + +class Scan(object): + '''details about a scan that could be plotted''' + + def __init__(self): + self.title = None + self.scan_type = None + self.data_file = None + self.scan_number = None + self.scan_id = None + self.spec_scan = None + self.safe_id = None + self.Q = None + self.I = None + + def __str__(self, *args, **kwargs): + return self.scan_type + ": " + self.title + + def setFileParms(self, title, data_file, scan_type, scan_number, scan_id): + self.title = title + self.scan_type = scan_type + self.data_file = data_file + self.scan_number = int(scan_number) + self.scan_id = scan_id + self.safe_id = self.makeSafeId() + + def makeSafeId(self): + '''for use as HDF5 dataset name in HDF5 data file''' + if self.spec_scan is None: + self.getData() + + if self.spec_scan is None: + logger.debug("No scan %d in file %s" % (self.scan_number, self.data_file)) + return None + + epoch = datetime.datetime.strptime(self.spec_scan.date, '%c') + fmt = '%Y_%m_%d__%H_%M_%S' + scan_date_time_stamp = datetime.datetime.strftime(epoch, fmt) + + self.safe_id = scan_date_time_stamp + '__%06d' % self.scan_number + return self.safe_id + + def getSpecScan(self): + global scan_cache + global spec_file_cache + if self.spec_scan is None: + if self.safe_id is not None: + # try to get scan from the scan cache + self.spec_scan = scan_cache.get(self.safe_id) + if self.spec_scan is None: + # to get scan from the file cache + spec = spec_file_cache.get(self.data_file) + self.spec_scan = spec.getScan(str(self.scan_number)) + return self.spec_scan + + def getData(self): + if self.scan_type in scan_macro_list: + if self.spec_scan is None: + self.spec_scan = self.getSpecScan() + + +def plottable_scan_node(scan_node): + ''' + Determine if the scan_node (XML) is plottable + + :param obj scan_node: scan_node entry (instance of XML _Element node) + :return obj: instance of Scan or None + ''' + global scan_cache + global spec_file_cache + scan = None + + filename = scan_node.find('file').text.strip() + if not os.path.exists(filename): + return scan + scan_type = scan_node.attrib['type'] + + # TODO: #23 similar code blocks could be combined + if scan_type in ('uascan', 'sbuascan'): + if scan_node.attrib['state'] in ('scanning', 'complete'): + if os.path.exists(filename): + scan = Scan() + scan.setFileParms( + scan_node.find('title').text.strip(), + filename, + scan_type, + scan_node.attrib['number'], + scan_node.attrib['id'], + ) + scan.getData() + + elif scan_type in ('FlyScan', 'sbFlyScan', 'Flyscan'): + if scan_node.attrib['state'] in ('complete', ): + # specfiledir = os.path.dirname(filename) + scan = Scan() + scan.setFileParms( + scan_node.find('title').text.strip(), + filename, + scan_type, + scan_node.attrib['number'], + scan_node.attrib['id'], + ) + if scan.spec_scan is None: + return None # reached a dead end here + scan.getData() + + # get the HDF5 file name from the SPEC file (no search needed) + spec = spec_file_cache.get(filename) + spec_scan = spec.getScan(str(scan_node.attrib['number'])) + if not spec_scan.__interpreted__: + try: + spec_scan.interpret() + except Exception as exc: + logger.error( + "[%s,S%d,%s] %s", + scan.data_file, + scan.scan_number, + scan.scan_type, + str(exc), + ) + return + + hdf5_file = get_Hdf5_Data_file_Name(spec_scan) + if hdf5_file is None or not os.path.exists(hdf5_file): + scan = None # bail out, no HDF5 file found + else: + # actual data file + scan_node.data_file = hdf5_file + + elif scan_type in ('pinSAXS', 'SAXS', 'WAXS',): + if scan_node.attrib['state'] in ('complete', ): + # specfiledir = os.path.dirname(filename) + scan = Scan() + scan.setFileParms( + scan_node.find('title').text.strip(), + filename, + scan_type, + scan_node.attrib['number'], + scan_node.attrib['id'], + ) + if scan.spec_scan is None: + return None # reached a dead end here + scan.getData() + if not scan.spec_scan.__interpreted__: + try: + scan.spec_scan.interpret() + except Exception as exc: + logger.error( + "[%s,S%d,%s] %s", + scan.data_file, + scan.scan_number, + scan.scan_type, + str(exc), + ) + return + + hdf5_file = get_Hdf5_Data_file_Name(scan.spec_scan) + if hdf5_file is not None and os.path.exists(hdf5_file): + # actual data file + scan_node.data_file = hdf5_file + else: + scan = None # bail out, no HDF5 file found + + if scan is not None: + scan_cache.add(scan) + + return scan + + +def last_n_scans(xml_log_file, number_scans): + '''get list of last *n* plottable scan objects, chronological, most recent last''' + xml_doc = xmlSupport.openScanLogFile(xml_log_file) + if xml_doc is None: + return [] + + scans = [] + node_list = xml_doc.findall('scan') or [] + for scan_node in reversed(node_list): + msg = scan_node.tag + for k in "state type id".split(): + msg += " - " + scan_node.attrib[k] + logger.debug(msg) + scan_object = plottable_scan_node(scan_node) + if scan_object is not None: + scans.append(scan_object) + if len(scans) == number_scans: + break + return list(reversed(scans)) + + +def get_Hdf5_Data_file_Name(scan): + """ + read the name of the HDF5 data file from the SPEC data file scan + """ + scan_command_parts = scan.scanCmd.strip().split() + if hasattr(scan, "MD"): + # HDF5 data file (Flyscan, or area detector) written from Bluesky plan + #MD hdf5_file = blank_0755.h5 + #MD hdf5_path = /share1/USAXS_data/2019-05/05_02_test_usaxs + fname = scan.MD.get("hdf5_file") + if fname is None: + return "no spec data file" # missing in an early version + path = scan.MD["hdf5_path"] + + elif len(scan_command_parts) > 6 and scan_command_parts[0] in ("SAXS", "WAXS"): + # EPICS area detector data file written from SPEC macros + #S 10 WAXS ./04_02_Cheng_INL_waxs/LaB6_0002.hdf 0 0 1 20 1 + #S 15 SAXS ./04_02_Cheng_INL_saxs/BlankHeater_0003.hdf 0 0 1 20 1 + fname = scan_command_parts[1] + path = os.path.dirname(scan.header.parent.fileName) + + elif len(scan_command_parts) > 3 and scan_command_parts[0] in ("FlyScan", ): + # FlyScan HDF5 data file written from SPEC macros + #C Tue Apr 02 20:49:39 2019. FlyScan file name = ./04_02_Cheng_INL_usaxs/2104H_1020C_47min_0013.h5. + key_string = 'FlyScan file name = ' + fname = None + for comment in scan.comments: + index = comment.find(key_string) + if index > 0: + fname = comment[index + len(key_string):-1] + break + if fname is None: + raise ValueError("HDF5 file name not found") + path = os.path.dirname(scan.header.parent.fileName) + + else: + return + + hdf5_file = os.path.abspath(os.path.join(path, fname)) + return hdf5_file + + +def get_USAXS_FlyScan_Data(scan_obj): + scan = scan_obj.spec_scan + hdf5_file = get_Hdf5_Data_file_Name(scan) + + try: + fly = reduceFlyData.UsaxsFlyScan(hdf5_file) # checks if file exists + #fly.make_archive() + fly.reduce() # open the file in this step + fly.save(hdf5_file, 'full') + if 'full' not in fly.reduced: + return None + fly.rebin(localConfig.REDUCED_FLY_SCAN_BINS) + fly.save(hdf5_file, str(localConfig.REDUCED_FLY_SCAN_BINS)) + except IOError: + return None # file may not be available yet for reading if fly scan is still going + except KeyError as exc: + logger.info('HDF5 file:' + hdf5_file) + raise KeyError(exc) + except reduceFlyData.NoFlyScanData as _exc: + logger.info(str(_exc)) + return None # HDF5 file exists but length of raw data is zero + + fname = os.path.splitext(os.path.split(hdf5_file)[-1])[0] + title = 'S%s %s (%s)' % (str(scan.scanNum), fname, 'fly') + numbins_str = str(localConfig.REDUCED_FLY_SCAN_BINS) + if numbins_str not in fly.reduced: + return None + rebinned = fly.reduced[numbins_str] + entry = dict(qVec=rebinned['Q'], rVec=rebinned['R'], title=title) + return entry + + +def get_AreaDetector_Data(scan_obj): + scan = scan_obj.spec_scan + scanMacro = scan.scanCmd.strip().split()[0].split("(")[0] + hdf5_file = get_Hdf5_Data_file_Name(scan) + bins = dict(SAXS=250, WAXS=800)[scanMacro] + scan_name = os.path.splitext(os.path.split(hdf5_file)[-1])[0] + + ad = reduceAreaDetector.reduce_area_detector_data(hdf5_file, bins) + title = 'S%s %s (%s)' % (str(scan.scanNum), scan_name, scanMacro) + rebinned = ad.reduced.get(str(bins)) + if rebinned is None: + raise KeyError("No rebinned %s data of length %d" % (scanMacro, bins)) + rebinned = ad.reduced[str(bins)] + entry = dict(qVec=rebinned['Q'], rVec=rebinned['R'], title=title) + return entry + + +def get_USAXS_uascan_ScanData(scan, ar_center=None): + created_by_bluesky = scan.header.comments[0].startswith("Bluesky ") + if created_by_bluesky: + title = scan.MD["title"] + else: + title = scan.spec_scan.comments[0] + + usaxs = calc.reduce_uascan(scan.spec_scan) + usaxs['qVec'] = usaxs.pop('Q') + usaxs['rVec'] = usaxs.pop('R') + usaxs['title'] = "S{} {}".format(scan.scan_number, title) + return usaxs + + +def format_as_mpl_data_one(scan): + '''prepare one USAXS scan for plotting with MatPlotLib''' + try: + Q = map(float, scan['qVec']) + I = map(float, scan['rVec']) + except TypeError as _e: + if scan is None: return None + Q = scan['qVec'] + I = scan['rVec'] + Q = numpy.ma.masked_less_equal(numpy.abs(Q), 0) + I = numpy.ma.masked_less_equal(I, 0) + mask = numpy.ma.mask_or(Q.mask, I.mask) + + mpl_ds = plot_mpl.Plottable_USAXS_Dataset() + mpl_ds.Q = numpy.ma.masked_array(data=Q, mask=mask).compressed() + mpl_ds.I = numpy.ma.masked_array(data=I, mask=mask).compressed() + mpl_ds.label = scan['title'] + + if len(mpl_ds.Q) > 0 and len(mpl_ds.I) > 0: + return mpl_ds + + +def get_USAXS_data(cache): + mpl_datasets = [] + for key, scan_obj in sorted(cache.db.items()): + # scan_obj = cache.get(key) + if scan_obj is None: + continue + scanMacro = scan_obj.spec_scan.scanCmd.strip().split()[0].split("(")[0] + if scanMacro in scan_macro_list: + if scanMacro in ("uascan", "sbuascan"): + try: + scan_obj.spec_scan.interpret() + except Exception as exc: + logger.error( + "[%s,S%d,%s] %s", + scan_obj.spec_scan.data_file, + scan_obj.spec_scan.scan_number, + scan_obj.spec_scan.scan_type, + str(exc), + ) + return + entry = get_USAXS_uascan_ScanData(scan_obj) + elif scanMacro in ("FlyScan", "sbFlyScan", "Flyscan"): + entry = get_USAXS_FlyScan_Data(scan_obj) + elif scanMacro in ("SAXS", "WAXS"): + entry = get_AreaDetector_Data(scan_obj) + mpl_ds = format_as_mpl_data_one(entry) + if mpl_ds is None: continue + if len(mpl_ds.Q) > 0 and len(mpl_ds.I) > 0: + mpl_datasets.append(mpl_ds) + logger.debug("{} = {}".format(key, scanMacro)) + return mpl_datasets + + +def save_temporary_test_data(mpl_datasets): + '''save temporary test data sets''' + from spec2nexus import eznx + hdf5_file = os.path.join(localConfig.LOCAL_WWW_LIVEDATA_DIR, 'testdata.h5') + f = eznx.makeFile(hdf5_file) + for i, ds in enumerate(mpl_datasets): + nxentry = eznx.makeGroup(f, 'entry_' + str(i), 'NXentry') + eznx.makeDataset(nxentry, "title", ds.label) + nxdata = eznx.makeGroup(nxentry, 'data', 'NXdata', signal='R', axes='Q') + eznx.makeDataset(nxdata, "Q", ds.Q, units='1/A') + eznx.makeDataset(nxdata, "R", ds.I, units='a.u.') + f.close() + + +def main(n = None, cp=False): + global scan_cache + global spec_file_cache + + scan_cache = ScanCache() + spec_file_cache = SpecFileCache() + if n is None: + n = NUMBER_SCANS_TO_PLOT + scan_list = last_n_scans(SCANLOG, n) # updates scan_cache & spec_file_cache + logger.debug("scan list: " + ", ".join(map(str, scan_list))) + + local_plot = os.path.join( + localConfig.LOCAL_WWW_LIVEDATA_DIR, + localConfig.LOCAL_PLOTFILE) + + mpl_datasets = get_USAXS_data(scan_cache) + if len(mpl_datasets): + # save_temporary_test_data(mpl_datasets) + try: + plot_mpl.livedata_plot(mpl_datasets, local_plot) + except plot_mpl.PlotException as exc: + logger.info("{}".format(exc)) + if cp: + www_plot = localConfig.LOCAL_PLOTFILE + wwwServerTransfers.nfsCpToWebServer(local_plot, www_plot) + + +def pr20(n = None, cp=False): + """ + test code + + make SAXS & WAXS data from BS show on livedata page + + supporting issue https://github.com/APS-USAXS/livedata/issues/14 + and PR https://github.com/APS-USAXS/livedata/pull/20 + + import logging + import os + import scanplots + + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + scanplots.pr20(100) + """ + global scan_cache + global spec_file_cache + + scan_cache = ScanCache() + spec_file_cache = SpecFileCache() + if n is None: + n = NUMBER_SCANS_TO_PLOT + last_n_scans(SCANLOG, n) # updates scan_cache & spec_file_cache + + mpl_datasets = get_USAXS_data(scan_cache) + + logger.debug("scan list: ") + for i, s in enumerate(mpl_datasets): + logger.debug("%d %d %s" % (i+1, len(s.Q), s.label)) + + +if __name__ == "__main__": + #last_n_scans(SCANLOG, NUMBER_SCANS_TO_PLOT) + main() diff --git a/tests/test_calc.py b/tests/test_calc.py index 0e1f7fc..b21bcc3 100644 --- a/tests/test_calc.py +++ b/tests/test_calc.py @@ -2,9 +2,10 @@ import sys sys.path.append(str(pathlib.Path(__file__).parent.parent)) -import pytest -from spec2nexus.spec import SpecDataFile from calc import reduce_uascan +from spec2nexus.spec import SpecDataFile +import h5py +import pytest USAXS_DATA = pathlib.Path("/share1/USAXS_data") @@ -27,10 +28,17 @@ def test_flyScan(filename): raise FileNotFoundError(filename) assert filename.exists() - # TODO: refactor as test(s) import reduceFlyData fs = reduceFlyData.UsaxsFlyScan(filename) + with h5py.File(filename, "r") as root: + amp_name = fs.get_USAXS_PD_amplifier_name(root) + assert amp_name == "DDPCA300" + assert isinstance(amp_name, str) + changes = fs.get_range_changes(root, amp_name) + arr_channel, arr_requested, arr_actual = changes + assert len(arr_channel) == len(arr_requested) + assert len(arr_channel) == len(arr_actual) # compute the R(Q) profile fs.reduce() usaxs = fs.reduced diff --git a/ustep.py b/ustep.py new file mode 100755 index 0000000..18fe7ee --- /dev/null +++ b/ustep.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +''' +Step-Size Algorithm for Bonse-Hart Ultra-Small-Angle Scattering Instruments + +:see: http://usaxs.xray.aps.anl.gov/docs/ustep/index.html +''' + +import logging +logger = logging.getLogger(__name__) + +class ustep(object): + ''' + find the series of positions for the USAXS + + :param float start: required first position of list + :param float center: position to take minStep + :param float finish: required ending position of list + :param float numPts: length of the list + :param float exponent: :math:`\eta`, exponential factor + :param float minStep: smallest allowed step size + :param float factor: :math:`k`, multiplying factor (computed internally) + :param [float] series: computed list of positions + ''' + + def __init__(self, start, center, finish, numPts, exponent, minStep): + self.start = start + self.center = center + self.finish = finish + self.numPts = numPts + self.exponent = exponent + self.minStep = minStep + self.sign = {True: 1, False: -1}[start < finish] + self.series = [] + self.factor = self.find_factor() + + def find_factor(self): + ''' + Determine the factor that will make a series with the specified parameters. + + This method improves on find_factor_simplistic() by + choosing next choice for factor from recent history. + ''' + + def assess(factor): + self.make_series(factor) + span_diff = abs(self.series[0] - self.series[-1]) - span_target + return span_diff + + span_target = abs(self.finish - self.start) + span_precision = abs(self.minStep) * 0.2 + factor = abs(self.finish-self.start) / (self.numPts -1) + span_diff = assess(factor) + f = [factor, factor] + d = [span_diff, span_diff] + + # first make certain that d[0] < 0 and d[1] > 0, expand f[0] and f[1] + for _ in range(100): + if d[0] * d[1] < 0: + break # now, d[0] and d[1] have opposite sign + factor *= {True: 2, False: 0.5}[span_diff < 0] + span_diff = assess(factor) + key = {True: 1, False: 0}[span_diff > d[1]] + f[key] = factor + d[key] = span_diff + + # now: d[0] < 0 and d[1] > 0, squeeze f[0] & f[1] to converge + for _ in range(100): + if (d[1] - d[0]) > span_target: + factor = (f[0] + f[1])/2 # bracket by bisection when not close + else: + factor = f[0] - d[0] * (f[1]-f[0])/(d[1]-d[0]) # linear interpolation when close + span_diff = assess(factor) + if abs(span_diff) <= span_precision: + break + key = {True: 0, False: 1}[span_diff < 0] + f[key] = factor + d[key] = span_diff + + return factor + + def find_factor_simplistic(self): + ''' + Determine the factor that will make a series with the specified parameters. + + Choose the factor that will minimize :math:`| x_n - finish |` subject to: + + .. math:: + + x_1 = start + x_n <= finish + + This routine CAN FAIL if :math:`(finish - start)/minStep >= numPts` + + This search technique picks a new factor based on the fit of the present choice. + It converges but not quickly. + ''' + logger.debug('\t'.join('factor diff'.split())) + span_target = abs(self.finish - self.start) + span_precision = abs(self.minStep) * 0.2 + factor = abs(self.finish-self.start) / (self.numPts -1) + fStep = factor + larger = 3.0 + smaller = 0.5 + for _ in range(100): + self.make_series(factor) + span = abs(self.series[0] - self.series[-1]) + span_diff = span - span_target + logger.debug('\t'.join(map(str,[factor, span_diff]))) + if abs(span_diff) <= span_precision: + break + if span_diff < 0: + fStep = abs(fStep) * larger + else: + fStep = -abs(fStep) * smaller + factor += fStep + return factor + + def make_series(self, factor): + '''create self.series with the given factor''' + x = self.start + series = [x, ] + for _ in range(self.numPts - 1): + x += self.sign * self.uascanStepFunc(x, factor) + series.append(x) + self.series = series + + def uascanStepFunc(self, x, factor): + '''Calculate the next step size with the given parameters''' + if abs(x - self.center) > 1e100: + step = 1e100 + else: + step = factor * pow( abs(x - self.center), self.exponent ) + self.minStep + return step + + +def main(): + start = 10.0 + center = 9.5 + finish = 7 + numPts = 100 + exponent = 1.2 + minStep = 0.0001 + u = ustep(start, center, finish, numPts, exponent, minStep) + print(f"{u.factor=}") + print(f"{u.series=}") + + +if __name__ == '__main__': + main() diff --git a/wwwServerTransfers.py b/wwwServerTransfers.py new file mode 100755 index 0000000..68f6da8 --- /dev/null +++ b/wwwServerTransfers.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python + +''' +manage file transfers with the USAXS account on the XSD WWW server +''' + + +import datetime +import logging +import os +import paramiko +from scp import SCPClient, SCPException +import shlex +import shutil +import socket +import subprocess +import sys + +import pvwatch + + +logger = logging.getLogger(__name__) + +# general use +#WWW_SERVER = 'www-i.xray.aps.anl.gov' +WWW_SERVER = 'joule.xray.aps.anl.gov' +WWW_SERVER_USER = 'webusaxs' +WWW_SERVER_ROOT = WWW_SERVER_USER + '@' + WWW_SERVER +WWW_SERVER_NFS_ROOT = "/net/joule/export/joule/WEBUSAXS/" +#LIVEDATA_DIR = "www/livedata" +LIVEDATA_DIR = "www_live" +SERVER_WWW_HOMEDIR = WWW_SERVER_ROOT + ":~" +SERVER_WWW_LIVEDATA = os.path.join(SERVER_WWW_HOMEDIR, LIVEDATA_DIR) +SERVER_WWW_LIVEDATA_NFS = os.path.join(WWW_SERVER_NFS_ROOT, LIVEDATA_DIR) + +LOCAL_DATA_DIR = "/share1" +LOCAL_WWW = os.path.join(LOCAL_DATA_DIR, 'local_livedata') +LOCAL_WWW_LIVEDATA = os.path.join(LOCAL_DATA_DIR, LIVEDATA_DIR) + +LOCAL_USAXS_DATA__DIR = LOCAL_DATA_DIR + "/USAXS_data" + +RSYNC = "/usr/bin/rsync" +SCP = "/usr/bin/scp" +SCP_TIMEOUT_S = 30 +RETRY_COUNT = 3 + + +class WwwServerScpException(Exception): pass + + +def nfsCpToWebServer(sourceFile, targetFile = "", demo = False): + ''' + Copy the local source file to the WWW server using NFS. + + @param sourceFile: file in local file space relative to /share1/local_livedata + @param targetFile: destination file (default is same path as sourceFile) + @param demo: If True, don't do the copy, just print the command + @return: a tuple (stdoutdata, stderrdata) -or- None (if demo=False) + ''' + if not os.path.exists(sourceFile): + raise Exception("Local file not found: " + sourceFile) + if len(targetFile) == 0: + targetFile = sourceFile + destinationName = os.path.join(SERVER_WWW_LIVEDATA_NFS, targetFile) + msg = "%s %s %s" % ("cp -f", sourceFile, destinationName) + logger.debug(msg) + + if demo: + return + + try: + shutil.copyfile(sourceFile, destinationName) + except OSError as exc: + msg = "OSError - could not %s: (%s)" % (msg, str(exc)) + logger.debug(msg) + raise OSError(msg) + + +def scpToWebServer(sourceFile, targetFile = "", demo = False): + ''' + Copy the local source file to the WWW server using scp. + + @param sourceFile: file in local file space relative to /share1/local_livedata + @param targetFile: destination file (default is same path as sourceFile) + @param demo: If True, don't do the copy, just print the command + @return: a tuple (stdoutdata, stderrdata) -or- None (if demo=False) + ''' + if not os.path.exists(sourceFile): + raise Exception("Local file not found: " + sourceFile) + if len(targetFile) == 0: + targetFile = sourceFile + destinationName = os.path.join(SERVER_WWW_LIVEDATA, targetFile) + if demo: + print("%s -p %s %s" % (SCP, sourceFile, destinationName)) + return None + + with createSSHClient(WWW_SERVER, user=WWW_SERVER_USER) as ssh: + + report = None + #report = report_scp_progress # debugging (from scp.report_scp_progress) + scp = SCPClient(ssh.get_transport(), progress=report) + + for _retry in range(RETRY_COUNT): + try: + scp.put(sourceFile, remote_path=LIVEDATA_DIR) + if _retry > 0: # only report after some retries, otherwise return quietly + msg = "scp was successful after %d tries" % (_retry+1) + logger.info(msg) + # ssh.close() + return + except (SCPException, paramiko.SSHException, socket.error) as exc: + msg = 'scp attempt %d: %s' % ((_retry+1), str(exc)) + logger.info(msg) + + msg = 'tried %d times: scp %s %s' % (RETRY_COUNT, sourceFile, targetFile) + raise WwwServerScpException(msg) + + +def scpToWebServer_Demonstrate(sourceFile, targetFile = ""): + ''' + Demonstrate a copy from the local source file to the WWW server using scp BUT DO NOT DO IT + ... + ... this is useful for code development only... + ... + + @param sourceFile: file in local file space *relative* to /share1/local_livedata + @param targetFile: destination file (default is same path as sourceFile) + @return: None + ''' + return scpToWebServer(sourceFile, targetFile, demo = True) + + +def scpToWebServer_subprocess(sourceFile, targetFile = "", demo = False): + ''' + Copy the local source file to the WWW server using scp. + + @param sourceFile: file in local file space relative to /share1/local_livedata + @param targetFile: destination file (default is same path as sourceFile) + @param demo: If True, don't do the copy, just print the command + @return: a tuple (stdoutdata, stderrdata) -or- None (if demo=False) + ''' + # Can we replace scpToWebServer() with Python package capabilities? + # No major improvement. + # see: http://stackoverflow.com/questions/250283/how-to-scp-in-python + # see: http://stackoverflow.com/questions/68335/how-do-i-copy-a-file-to-a-remote-server-in-python-using-scp-or-ssh?lq=1 + if not os.path.exists(sourceFile): + raise Exception("Local file not found: " + sourceFile) + if len(targetFile) == 0: + targetFile = sourceFile + destinationName = os.path.join(SERVER_WWW_LIVEDATA, targetFile) + command = "%s -p %s %s" % (SCP, sourceFile, destinationName) + if demo: + print(command) + return None + else: + lex = shlex.split(command) + timeout_time = pvwatch.getTime() + datetime.timedelta(seconds=SCP_TIMEOUT_S) + p = subprocess.Popen(lex) + finished = False + while pvwatch.getTime() < timeout_time and not finished: + code = p.poll() + if code is not None: + finished = True + result = p.communicate(None) + if not finished or code != 0: + msg = {True: 'problem', False: 'timeout'}[finished] + msg += ': command `%s` returned code=%d' % (command, code) + msg += '\nSTDOUT=%s\nSTDERR=%s' % (str(result[0]), str(result[1])) + logger.info(msg) + return result + + +def execute_command(command): + ''' + execute the specified shell command + + @return: a tuple (stdoutdata, stderrdata) + ''' + # run the command but gobble up stdout (make it less noisy) + p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) + #p.wait() + return p.communicate(None) + + +def createSSHClient(server, port=None, user=None, password=None): + '''scp over a paramiko transport''' + # see: http://stackoverflow.com/questions/250283/how-to-scp-in-python + client = paramiko.SSHClient() + client.load_system_host_keys() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + #client.connect(server, port, user, password) + client.connect(server, username=user) + transport = client.get_transport() + transport.logger.setLevel(logging.WARNING) # otherwise, reports frequently + return client + + +if __name__ == '__main__': + scpToWebServer("wwwServerTransfers.py") + scpToWebServer_Demonstrate("wwwServerTransfers.py") + try: + scpToWebServer("wally.txt") + except Exception: + print(sys.exc_info()[1]) + scpToWebServer("wwwServerTransfers.py", "wally.txt") + scpToWebServer_Demonstrate("wwwServerTransfers.py", "wally.txt") diff --git a/xmlSupport.py b/xmlSupport.py new file mode 100755 index 0000000..622a5bc --- /dev/null +++ b/xmlSupport.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python + +'''read XML configuration files into a common data structure''' + +import logging +import os.path +import re +import sys +import time +from xml.etree import ElementTree +import xml.dom.minidom + +logger = logging.getLogger(__name__) + +#************************************************************************** + +def readPvlistXML(xmlFile): + '''get the list of PVs to monitor from the XML file''' + doc = openScanLogFile(xmlFile) + xref = {} + if doc != None: + db = [] + for element in doc.findall('EPICS_PV'): + arr = {} + arr['desc'] = element.text.strip() + for attribute in element.attrib.keys(): + arr[attribute] = element.attrib[attribute].strip() + db.append(arr) + xref['pvList'] = db + xref['scanLogFile'] = doc.find('scanLog_file').text + xref['local_www_dir'] = doc.find('local_www_dir').text + return(xref) + +#************************************************************************** +readConfigurationXML = readPvlistXML + +# def readConfigurationXML(pvListFile): +# '''locate the PV configuration from XML into memory''' +# return readPvlistXML(pvListFile) + +#************************************************************************** + +def openScanLogFile(xmlFile): + ''' + open the XML file with ElementTree + return the doc (doc.getroot() to get the root node) + ''' + doc = None + try: + doc = ElementTree.parse(xmlFile) + except Exception: + pass + return doc + +#************************************************************************** + +def locateScanID(doc, scanid): + ''' + find the XML scan entry with matching id attribute + return XML node or None if not found + ''' + result = None + #query = "scan/[@id='%s']" % scanid + for node in doc.findall("scan"): + if node.get("id") == scanid: + result = node + break + return result + +#************************************************************************** + +def flagRunawayScansAsUnknown(doc, scanid): + '''sometimes, a scan ends without this program finding out''' + for node in doc.findall("scan"): # look for any scan ... + if node.get("state") == "scanning": # with state="scanning" ... + if node.get("id") != scanid: # but not the newest node ... + node.set("state", "unknown") # THIS node is not scanning anymore + +#************************************************************************** + +def readFileAsLines(filename): + ''' + open a file and read all of it into + memory as a list separated by line breaks, + return None if error or cannot find file + ''' + if not os.path.exists(filename): + return None + try: + with open(filename, 'r') as f: + buf = f.read() + return buf.split("\n") + except Exception: + return None + +#************************************************************************** + +def writeLinesInFile(filename, lines): + '''write a list of lines to a file, ignore any errors''' + try: + with open(filename, 'w') as f: + f.write("\n".join(lines) + "\n") + except Exception: + pass + +#************************************************************************** + +def prettyXml(element): + '''fallback support for better code''' + return prettyXmlToString(element) + + +def prettyXmlToString(element): + ''' + make nice-looking XML that is human-readable + @see http://stackoverflow.com/questions/749796/pretty-printing-xml-in-python + ''' + txt = ElementTree.tostring(element) + dom = xml.dom.minidom.parseString(txt) + ugly = dom.toprettyxml() + #pretty = dom.toxml() + text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+\g<1> 0: + result.append(line) + return result + +#************************************************************************** + +def writeXmlDocToFile(xmlFile, doc): + '''write the XML doc to a file''' + # splice in the reference to the XSLT at the top of the file (on line 2) + xsltRef = '' + lines = prettyXmlToString(doc.getroot()).split("\n") + lines.insert(1, xsltRef) + writeLinesInFile(xmlFile, lines) + return + +#************************************************************************** + +def appendTextNode(doc, parent, tag, value): + '''append a text node to the XML document''' + elem = ElementTree.Element(tag) + elem.text = value + parent.append(elem) + +#************************************************************************** + +def appendDateTimeNode(doc, parent, tag): + '''append a date/time node to the XML document''' + elem = ElementTree.Element(tag) + elem.set('date', xmlDate()) + elem.set('time', xmlTime()) + parent.append(elem) + +#************************************************************************** + +def xmlDate(): + '''current date, for use in XML file (ISO8601)''' + txt = time.strftime('%Y-%m-%d') # XML date format + return txt + +#************************************************************************** + +def xmlTime(): + '''current time, for use in XML file (ISO8601)''' + txt = time.strftime('%H:%M:%S') # XML time format + return txt + +#************************************************************************** + +def main(): + ''' test routine ''' + if (len(sys.argv) == 2): + pwd = sys.argv[1] + else: + pwd = '.' + config = readConfigurationXML(os.path.join(pwd, 'pvlist.xml')) + if len(config) == 0: + logger.error("ERROR: could not read the configuration file") + return + doc = openScanLogFile(config['scanLogFile']) + root = doc.getroot() + scan = locateScanID(doc, '43:/share1/USAXS_data/2010-03/03_24_setup.dat') + scan.set("gotcha", "True") + print(prettyXmlToString(scan)) + appendTextNode(doc, root, "modifed.by", sys.argv[0]) + appendDateTimeNode(doc, root, "timestamp") + #print(prettyXml(root)) + writeXmlDocToFile('test.xml', doc) + +#************************************************************************** + +if __name__ == "__main__": + main() From 53e0d79da50f5cacf9f8569cf39d54118458d8c8 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 15:28:33 -0600 Subject: [PATCH 24/28] MNT #48 2->3 --- radialprofile.py | 10 +++++----- reduceAreaDetector.py | 2 +- reduceFlyData.py | 3 +-- scanplots.py | 2 +- xmlSupport.py | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/radialprofile.py b/radialprofile.py index 76e37a0..750d99c 100644 --- a/radialprofile.py +++ b/radialprofile.py @@ -63,7 +63,7 @@ def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return #nr = np.bincount(whichbin)[1:] nr = np.histogram(r, bins, weights=mask.astype('int'))[0] - # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or xrange(1,nbins+1) ) + # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or range(1,nbins+1) ) # radial_prof.shape = bin_centers.shape if stddev: # Find out which radial bin each point in the map belongs to @@ -72,7 +72,7 @@ def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return radial_prof = np.array( [ image.flat[mask.flat*(whichbin==b)].std() - for b in xrange(1,nbins+1) + for b in range(1,nbins+1) ] ) else: @@ -216,12 +216,12 @@ def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=F # there are never any in bin 0, because the lowest index returned by digitize is 1 nr = np.bincount(whichbin)[1:] - # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or xrange(1,nbins+1) ) + # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or range(1,nbins+1) ) # azimuthal_prof.shape = bin_centers.shape if stddev: - azimuthal_prof = np.array([image.flat[mask*(whichbin==b)].std() for b in xrange(1,nbins+1)]) + azimuthal_prof = np.array([image.flat[mask*(whichbin==b)].std() for b in range(1,nbins+1)]) else: - azimuthal_prof = np.array([(image*weights).flat[mask*(whichbin==b)].sum() / weights.flat[mask*(whichbin==b)].sum() for b in xrange(1,nbins+1)]) + azimuthal_prof = np.array([(image*weights).flat[mask*(whichbin==b)].sum() / weights.flat[mask*(whichbin==b)].sum() for b in range(1,nbins+1)]) #import pdb; pdb.set_trace() diff --git a/reduceAreaDetector.py b/reduceAreaDetector.py index 875a806..eab2ca5 100644 --- a/reduceAreaDetector.py +++ b/reduceAreaDetector.py @@ -313,7 +313,7 @@ def save(self, hfile = None, key = None): _ds = eznx.write_dataset(nxdata, key, ds[key]) if key in self.units: eznx.addAttributes(_ds, units=self.units[key]) - except RuntimeError as e: + except RuntimeError: pass # TODO: reporting hdf.close() return hfile diff --git a/reduceFlyData.py b/reduceFlyData.py index ecf416e..a38c576 100755 --- a/reduceFlyData.py +++ b/reduceFlyData.py @@ -473,7 +473,7 @@ def save(self, hfile = None, key = None): _ds = eznx.write_dataset(nxdata, key, ds[key]) if key in self.units: eznx.addAttributes(_ds, units=self.units[key]) - except RuntimeError as e: + except RuntimeError: pass # TODO: reporting return hfile @@ -665,7 +665,6 @@ def get_gain(self, hdf, amplifier): gain = list(map(lambda x: hdf[base+str(x)][0], range(5))) return gain - def get_bkg(self, hdf, amplifier): ''' get backgrounds for named amplifier from the HDF5 file diff --git a/scanplots.py b/scanplots.py index c17ff84..35c93f1 100755 --- a/scanplots.py +++ b/scanplots.py @@ -399,7 +399,7 @@ def format_as_mpl_data_one(scan): try: Q = map(float, scan['qVec']) I = map(float, scan['rVec']) - except TypeError as _e: + except TypeError: if scan is None: return None Q = scan['qVec'] I = scan['rVec'] diff --git a/xmlSupport.py b/xmlSupport.py index 622a5bc..9f9aa5b 100755 --- a/xmlSupport.py +++ b/xmlSupport.py @@ -18,7 +18,7 @@ def readPvlistXML(xmlFile): '''get the list of PVs to monitor from the XML file''' doc = openScanLogFile(xmlFile) xref = {} - if doc != None: + if doc is not None: db = [] for element in doc.findall('EPICS_PV'): arr = {} From bbaded9e1a2f5aec8168f6794d7bfbfa80f7433d Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 15:29:02 -0600 Subject: [PATCH 25/28] STY #48 black formatter --- localConfig.py | 33 ++- plot_mpl.py | 116 +++++---- pvwatch.py | 269 ++++++++++++--------- radialprofile.py | 192 +++++++++------ reduceAreaDetector.py | 390 +++++++++++++++--------------- reduceFlyData.py | 542 ++++++++++++++++++++++-------------------- scanplots.py | 188 ++++++++------- ustep.py | 46 ++-- wwwServerTransfers.py | 77 +++--- xmlSupport.py | 152 +++++++----- 10 files changed, 1098 insertions(+), 907 deletions(-) diff --git a/localConfig.py b/localConfig.py index 59a1626..bcd2245 100755 --- a/localConfig.py +++ b/localConfig.py @@ -1,14 +1,14 @@ #!/usr/bin/env python -''' +""" define 9-ID-C USAXS constants for these Python tools -''' +""" import os # general use -HOME_DIR = os.environ.get('HOME', '') +HOME_DIR = os.environ.get("HOME", "") BASE_DIR = "/share1/USAXS_data/" LOCAL_DATA_DIR = "/share1" LOCAL_USAXS_DATA_DIR = os.path.join(LOCAL_DATA_DIR, "/USAXS_data") @@ -19,16 +19,16 @@ # dirWatch.py -SKIP_DIRS = ['.AppleFileInfo'] -KEEP_EXTS = ['.dat'] -TIME_WINDOWS_SECS = 60*60*24*180 +SKIP_DIRS = [".AppleFileInfo"] +KEEP_EXTS = [".dat"] +TIME_WINDOWS_SECS = 60 * 60 * 24 * 180 # plotAllSpecFileScans.py LOCAL_SPECPLOTS_DIR = os.path.join(LOCAL_WWW_LIVEDATA_DIR, "specplots") WWW_SPECPLOTS_DIR = "specplots" SPEC_FILE = os.path.join(LOCAL_USAXS_DATA_DIR, "2010-03/03_27.dat") -MTIME_CACHE_FILE = os.path.join(LOCAL_SPECPLOTS_DIR, 'mtime_cache.txt') +MTIME_CACHE_FILE = os.path.join(LOCAL_SPECPLOTS_DIR, "mtime_cache.txt") # plot.py A_keV = 12.3984244 @@ -38,7 +38,7 @@ # pvwatch.py SOURCECODE_BASE = os.path.join(HOME_DIR, "Documents/eclipse/USAXS/livedata") -LOG_INTERVAL_S = 60*5 +LOG_INTERVAL_S = 60 * 5 NUM_SCANS_PLOTTED = 9 REPORT_INTERVAL_S = 10 SLEEP_INTERVAL_S = 0.1 @@ -50,11 +50,10 @@ LIVEDATA_XSL_STYLESHEET = "livedata.xsl" RAWTABLE_XSL_STYLESHEET = "raw-table.xsl" USAXSTV_XSL_STYLESHEET = "usaxstv.xsl" -#XSLT_COMMAND = "/usr/bin/xsltproc --novalid %s " +# XSLT_COMMAND = "/usr/bin/xsltproc --novalid %s " SPECMACRO_TXT_FILE = "specmacro.txt" - # specplot.py # TEST_SPEC_DATA = os.path.join(LOCAL_USAXS_DATA_DIR, "2011-06/06_22_setup2.dat") # TEST_SPEC_DATA = os.path.join("testdata", "03_19_LLNL.dat") @@ -67,7 +66,7 @@ # TEST_SPEC_DATA = '/share1/USAXS_data/2014-08/08_13_setup.dat' # TEST_SPEC_DATA = '/share1/USAXS_data/2015-01/02_08_Samples.dat' # TEST_SPEC_DATA = '/share1/USAXS_data/2016-06/06_08_DoyoonKim.dat' -TEST_SPEC_DATA = '/share1/USAXS_data/2018-01/01_22_TestHDF5.dat' +TEST_SPEC_DATA = "/share1/USAXS_data/2018-01/01_22_TestHDF5.dat" TEST_SPEC_SCAN_NUMBER = 15 TEST_PLOTFILE = "pete.png" @@ -75,14 +74,14 @@ # FlyScan -REDUCED_FLY_SCAN_BINS = 250 # sufficient to make a good plot -MCA_CLOCK_FREQUENCY = 50e6 # 50 MHz clock (not stored in older FlyScan files) -FLY_SCAN_Q_MIN = 1.01e-6 # absolute minimum Q for rebinning -FLY_SCAN_UATERM = 1.2 # for defining Q bins +REDUCED_FLY_SCAN_BINS = 250 # sufficient to make a good plot +MCA_CLOCK_FREQUENCY = 50e6 # 50 MHz clock (not stored in older FlyScan files) +FLY_SCAN_Q_MIN = 1.01e-6 # absolute minimum Q for rebinning +FLY_SCAN_UATERM = 1.2 # for defining Q bins # Area Detector images -REDUCED_AD_IMAGE_BINS = 250 # sufficient to make a good plot +REDUCED_AD_IMAGE_BINS = 250 # sufficient to make a good plot -HDF5_PATH_TO_IMAGE_DATA = '/entry/data/data' +HDF5_PATH_TO_IMAGE_DATA = "/entry/data/data" diff --git a/plot_mpl.py b/plot_mpl.py index 2d9ee6d..8c3296b 100755 --- a/plot_mpl.py +++ b/plot_mpl.py @@ -1,25 +1,28 @@ #!/usr/bin/env python -'''use MatPlotLib for the USAXS livedata and generic SPEC scan plots''' +"""use MatPlotLib for the USAXS livedata and generic SPEC scan plots""" import datetime import logging import matplotlib -matplotlib.use('Agg') + +matplotlib.use("Agg") from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas import numpy as np logger = logging.getLogger(__name__) -BISQUE_RGB = (255./255, 228./255, 196./255) # 255 228 196 bisque -MINTCREAM_RGB = (245./255, 255./255, 250./255) # 245 255 250 MintCream +BISQUE_RGB = (255.0 / 255, 228.0 / 255, 196.0 / 255) # 255 228 196 bisque +MINTCREAM_RGB = (245.0 / 255, 255.0 / 255, 250.0 / 255) # 245 255 250 MintCream SYMBOL_LIST = ("^", "D", "s", "v", "d", "<", ">") -COLOR_LIST = "orange salmon lime green blue purple violet chocolate gray black".split() # red is NOT in the list +COLOR_LIST = ( + "orange salmon lime green blue purple violet chocolate gray black".split() +) # red is NOT in the list -CHART_FILE = 'livedata.png' +CHART_FILE = "livedata.png" class PlotException(Exception): @@ -32,31 +35,33 @@ class PlotException(Exception): # and will eventually run out of memory. Here's the fix used in this module: # http://stackoverflow.com/questions/16334588/create-a-figure-that-is-reference-counted/16337909#16337909 + class Plottable_USAXS_Dataset(object): - '''data model for the plots below''' + """data model for the plots below""" + Q = None I = None label = None def livedata_plot(datasets, plotfile, title=None): - ''' + """ generate the USAXS livedata plot :param [Plottable_USAXS_Dataset] datasets: USAXS data to be plotted, newest data last :param str plotfile: file name to write plot image - ''' + """ fig = matplotlib.figure.Figure(figsize=(7.5, 8), dpi=300) fig.clf() - ax = fig.add_subplot('111', axisbg=MINTCREAM_RGB) - ax.set_xscale('log') - ax.set_yscale('log') - ax.set_xlabel(r'$|\vec{Q}|, 1/\AA$') - ax.set_ylabel(r'$R(|\vec{Q}|)$, Raw Intensity, a.u.') + ax = fig.add_subplot("111", axisbg=MINTCREAM_RGB) + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_xlabel(r"$|\vec{Q}|, 1/\AA$") + ax.set_ylabel(r"$R(|\vec{Q}|)$, Raw Intensity, a.u.") ax.grid(True) - timestamp_str = 'APS/XSD USAXS: ' + str(datetime.datetime.now()) + timestamp_str = "APS/XSD USAXS: " + str(datetime.datetime.now()) fig.suptitle(timestamp_str, fontsize=10) if title is not None: ax.set_title(title, fontsize=12) @@ -65,22 +70,24 @@ def livedata_plot(datasets, plotfile, title=None): faults = [] for i, ds in enumerate(datasets): try: - if i < len(datasets)-1: + if i < len(datasets) - 1: color = COLOR_LIST[i % len(COLOR_LIST)] symbol = SYMBOL_LIST[i % len(SYMBOL_LIST)] else: - color = 'red' - symbol = 'o' - if ds.label.find('(fly)') >= 0: - label = ds.label[:ds.label.find('(fly)')] + '(USAXS)' - elif ds.label.find('(SAXS)') >= 0: - label = ds.label[:ds.label.find('(SAXS)')] + '(SAXS)' + color = "red" + symbol = "o" + if ds.label.find("(fly)") >= 0: + label = ds.label[: ds.label.find("(fly)")] + "(USAXS)" + elif ds.label.find("(SAXS)") >= 0: + label = ds.label[: ds.label.find("(SAXS)")] + "(SAXS)" else: label = ds.label p = label.find(" ") if label.startswith("S") and p > 0: label = label[p:].strip() # remove the scan number - pl, = ax.plot(ds.Q, ds.I, symbol, label=label, mfc='w', mec=color, ms=3, mew=1) + (pl,) = ax.plot( + ds.Q, ds.I, symbol, label=label, mfc="w", mec=color, ms=3, mew=1 + ) legend_handlers[pl] = matplotlib.legend_handler.HandlerLine2D(numpoints=1) except Exception as exc: faults.append((i, ds, exc)) @@ -88,19 +95,25 @@ def livedata_plot(datasets, plotfile, title=None): # ax.legend(loc='lower left', fontsize=9, handler_map=legend_handlers) # the old way ax.legend(fontsize=8, handler_map=legend_handlers) # fig.tight_layout() # does not look good, crowds the title - FigureCanvas(fig).print_figure(plotfile, bbox_inches='tight', facecolor=BISQUE_RGB) + FigureCanvas(fig).print_figure(plotfile, bbox_inches="tight", facecolor=BISQUE_RGB) if len(faults) > 0: fault_text = "\n".join(["{}".format(f) for f in faults]) raise PlotException(fault_text) -def spec_plot(x, y, - plotfile, - title=None, subtitle=None, - xtitle=None, ytitle=None, - xlog=False, ylog=False, - timestamp_str=None): - ''' +def spec_plot( + x, + y, + plotfile, + title=None, + subtitle=None, + xtitle=None, + ytitle=None, + xlog=False, + ylog=False, + timestamp_str=None, +): + """ generate a plot of a scan (as if data from a scan in a SPEC file) :param [float] x: horizontal axis data @@ -113,15 +126,15 @@ def spec_plot(x, y, :param bool xlog: should X axis be log (default: False=linear) :param bool ylog: should Y axis be log (default: False=linear) :param str timestamp_str: date to use on plot (default: now) - ''' + """ fig = matplotlib.figure.Figure(figsize=(9, 5)) fig.clf() - ax = fig.add_subplot('111') + ax = fig.add_subplot("111") if xlog: - ax.set_xscale('log') + ax.set_xscale("log") if ylog: - ax.set_yscale('log') + ax.set_yscale("log") if not xlog and not ylog: ax.ticklabel_format(useOffset=False) if xtitle is not None: @@ -137,43 +150,50 @@ def spec_plot(x, y, if title is None: title = timestamp_str else: - fig.text(0.02, 0., timestamp_str, - fontsize=8, color='gray', - ha='left', va='bottom', alpha=0.5) + fig.text( + 0.02, + 0.0, + timestamp_str, + fontsize=8, + color="gray", + ha="left", + va="bottom", + alpha=0.5, + ) fig.suptitle(title, fontsize=10) - ax.plot(x, y, 'o-') + ax.plot(x, y, "o-") - FigureCanvas(fig).print_figure(plotfile, bbox_inches='tight') + FigureCanvas(fig).print_figure(plotfile, bbox_inches="tight") def main(): - '''demo of this code''' - x = np.arange(0.105, 2*np.pi, 0.01) + """demo of this code""" + x = np.arange(0.105, 2 * np.pi, 0.01) ds1 = Plottable_USAXS_Dataset() ds1.Q = x ds1.I = np.sin(x**2) * np.exp(-x) + 1.0e-5 - ds1.label = 'sin(x^2) exp(-x)' + ds1.label = "sin(x^2) exp(-x)" ds2 = Plottable_USAXS_Dataset() ds2.Q = x ds2.I = ds1.I**2 + 1.0e-5 - ds2.label = '$[\sin(x^2)\cdot\exp(-x)]^2$' + ds2.label = "$[\sin(x^2)\cdot\exp(-x)]^2$" ds3 = Plottable_USAXS_Dataset() ds3.Q = x - ds3.I = np.sin(5*x) / (5*x) + 1.0e-5 - ds3.label = 'sin(5x)/(5x)' + ds3.I = np.sin(5 * x) / (5 * x) + 1.0e-5 + ds3.label = "sin(5x)/(5x)" ds4 = Plottable_USAXS_Dataset() ds4.Q = x ds4.I = ds3.I**2 + 1.0e-5 - ds4.label = r'$[\sin(5x)/(5x)]^2$' + ds4.label = r"$[\sin(5x)/(5x)]^2$" livedata_plot([ds2, ds4], CHART_FILE) -#************************************************************************** +# ************************************************************************** if __name__ == "__main__": main() diff --git a/pvwatch.py b/pvwatch.py index 88bc121..bc88dc7 100755 --- a/pvwatch.py +++ b/pvwatch.py @@ -1,45 +1,53 @@ #!/APSshare/anaconda/x86_64/bin/python -''' +""" watch the USAXS EPICS process variables and periodically write them to a file Start this with the shell command:: /APSshare/anaconda/x86_64/bin/python ./pvwatch.py >>& log.txt -''' +""" -import datetime # date/time stamps -import epics # manages EPICS (PyEpics) connections for Python 2.6+ +import datetime # date/time stamps +import epics # manages EPICS (PyEpics) connections for Python 2.6+ import logging import numpy import os -os.environ['HDF5_DISABLE_VERSION_CHECK'] = '2' -import os.path # testing if a file exists -import shutil # file copies -import time # provides sleep() + +os.environ["HDF5_DISABLE_VERSION_CHECK"] = "2" +import os.path # testing if a file exists +import shutil # file copies +import time # provides sleep() import traceback from xml.dom import minidom from xml.etree import ElementTree -import localConfig # definitions for 9-ID +import localConfig # definitions for 9-ID import scanplots import wwwServerTransfers LOGGER_FORMAT = "%(asctime)s.%(msecs)03d (%(levelname)s,%(process)d,%(name)s,%(lineno)d) %(message)s" -logging.basicConfig(level=logging.INFO, format=LOGGER_FORMAT, datefmt='%Y-%m-%d %H:%M:%S',) +logging.basicConfig( + level=logging.INFO, + format=LOGGER_FORMAT, + datefmt="%Y-%m-%d %H:%M:%S", +) logger = logging.getLogger(__name__) + def logMessage(msg): - '''write a message with a timestamp and pid to the log file''' + """write a message with a timestamp and pid to the log file""" logger.info(msg) + try: # better reporting of SEGFAULT # http://faulthandler.readthedocs.org import faulthandler + faulthandler.enable() logger.debug("faulthandler: module enabled") except ImportError: @@ -47,25 +55,32 @@ def logMessage(msg): GLOBAL_MONITOR_COUNTER = 0 -pvdb = {} # EPICS data will go here, cache of last known good values -xref = {} # cross-reference between mnemonics and PV names: {mne:pvname} +pvdb = {} # EPICS data will go here, cache of last known good values +xref = {} # cross-reference between mnemonics and PV names: {mne:pvname} PVLIST_FILE = "pvlist.xml" MAINLOOP_COUNTER_TRIGGER = 10000 # print a log message periodically USAXS_DATA = None -'''value for expected EPICS PV is None''' -class NoneEpicsValue(Exception): pass +"""value for expected EPICS PV is None""" + + +class NoneEpicsValue(Exception): + pass -'''pv not in pvdb''' -class PvNotRegistered(Exception): pass + +"""pv not in pvdb""" + + +class PvNotRegistered(Exception): + pass def getSpecFileName(pv): - '''construct the name of the file, based on a PV''' - dir_pv = xref['spec_dir'] - userDir = pvdb[dir_pv]['value'] - rawName = pvdb[pv]['value'] + """construct the name of the file, based on a PV""" + dir_pv = xref["spec_dir"] + userDir = pvdb[dir_pv]["value"] + rawName = pvdb[pv]["value"] if userDir is None: raise NoneEpicsValue('"None" received for spec_dir PV: {}'.format(dir_pv)) if rawName is None: @@ -75,24 +90,21 @@ def getSpecFileName(pv): def updateSpecMacroFile(): - '''copy the current SPEC macro file to the WWW page space''' + """copy the current SPEC macro file to the WWW page space""" - if len(pvdb[xref['spec_macro_file']]['value'].strip()) == 0: + if len(pvdb[xref["spec_macro_file"]]["value"].strip()) == 0: # SPEC file name PV is empty return - specFile = getSpecFileName(xref['spec_macro_file']) + specFile = getSpecFileName(xref["spec_macro_file"]) if not os.path.exists(specFile): # Look in parent directory per 2020-09 changes otherFile = os.path.join( # os.path.dirname(os.path.dirname(specFile)), localConfig.LOCAL_WWW_LIVEDATA_DIR, - os.path.basename(specFile) + os.path.basename(specFile), ) if not os.path.exists(otherFile): - logger.debug( - "Cannot find macro file: %s or %s", - specFile, - otherFile) + logger.debug("Cannot find macro file: %s or %s", specFile, otherFile) return specFile = otherFile if not os.path.isfile(specFile): @@ -107,7 +119,7 @@ def updateSpecMacroFile(): spec_mtime = os.stat(specFile).st_mtime www_mtime = os.stat(wwwFile).st_mtime if spec_mtime > www_mtime: - updateFile = True # only if file is newer + updateFile = True # only if file is newer else: updateFile = True if updateFile: @@ -116,9 +128,9 @@ def updateSpecMacroFile(): def updatePlotImage(): - '''make a new PNG file with the most recent USAXS scans''' + """make a new PNG file with the most recent USAXS scans""" - specFile = getSpecFileName(xref['spec_data_file']) + specFile = getSpecFileName(xref["spec_data_file"]) if not os.path.exists(specFile): logger.info(specFile + " does not exist") return @@ -129,10 +141,10 @@ def updatePlotImage(): plotFile = localConfig.LOCAL_PLOTFILE plotFile = os.path.join(localConfig.LOCAL_WWW_LIVEDATA_DIR, plotFile) - makePlot = not os.path.exists(plotFile) # no plot yet, let's make one! + makePlot = not os.path.exists(plotFile) # no plot yet, let's make one! if os.path.exists(plotFile): plot_mtime = os.stat(plotFile).st_mtime - makePlot = spec_mtime > plot_mtime # plot only if new data + makePlot = spec_mtime > plot_mtime # plot only if new data if makePlot: logger.debug("updating the plots and gathering scan data for XML file") @@ -140,15 +152,16 @@ def updatePlotImage(): def writeFile(filename, contents): - '''write contents to file''' - with open(filename, 'w') as f: + """write contents to file""" + with open(filename, "w") as f: f.write(contents) def xslt_transformation(xslt_file, src_xml_file, result_xml_file): - '''transform an XML file using an XSLT''' + """transform an XML file using an XSLT""" # see: http://lxml.de/xpathxslt.html#xslt - from lxml import etree as lxml_etree # in THIS routine, use lxml's etree + from lxml import etree as lxml_etree # in THIS routine, use lxml's etree + src_doc = lxml_etree.parse(src_xml_file) xslt_doc = lxml_etree.parse(xslt_file) transform = lxml_etree.XSLT(xslt_doc) @@ -158,14 +171,14 @@ def xslt_transformation(xslt_file, src_xml_file, result_xml_file): def textArray(arr): - '''convert an ndarray to a text array''' + """convert an ndarray to a text array""" if isinstance(arr, numpy.ndarray): return [str(_) for _ in arr] return arr def buildReport(): - '''build the report''' + """build the report""" t = datetime.datetime.now() yyyymmdd = t.strftime("%Y-%m-%d") hhmmss = t.strftime("%H:%M:%S") @@ -178,8 +191,17 @@ def buildReport(): node.text = yyyymmdd + " " + hhmmss sorted_id_list = sorted(xref) - fields = ("name", "id", "description", "timestamp", - "counter", "units", "value", "raw_value", "format") + fields = ( + "name", + "id", + "description", + "timestamp", + "counter", + "units", + "value", + "raw_value", + "format", + ) for mne in sorted_id_list: pv = xref[mne] @@ -194,27 +216,29 @@ def buildReport(): subnode.text = str(entry[item]) global USAXS_DATA - if USAXS_DATA is not None and USAXS_DATA.get('usaxs', None) is not None: + if USAXS_DATA is not None and USAXS_DATA.get("usaxs", None) is not None: try: - specfile = USAXS_DATA['file'] + specfile = USAXS_DATA["file"] node = ElementTree.SubElement(root, "usaxs_scans") node.set("file", specfile) - for scan in USAXS_DATA['usaxs']: - scannode = ElementTree.SubElement(node, "scan") # FIXME: ? scan or node ? - for item in ('scan', 'key', 'label'): + for scan in USAXS_DATA["usaxs"]: + scannode = ElementTree.SubElement( + node, "scan" + ) # FIXME: ? scan or node ? + for item in ("scan", "key", "label"): scannode.set(item, str(scan[item])) - scannode.set('specfile', specfile) - ElementTree.SubElement(scannode, "title").text = scan['title'] + scannode.set("specfile", specfile) + ElementTree.SubElement(scannode, "title").text = scan["title"] # write the scan data to the XML file vec = ElementTree.SubElement(scannode, "Q") - vec.set('units', '1/A') - vec.text = ' '.join(textArray(scan['qVec'])) + vec.set("units", "1/A") + vec.text = " ".join(textArray(scan["qVec"])) vec = ElementTree.SubElement(scannode, "R") - vec.set('units', 'arbitrary') - vec.text = ' '.join(textArray(scan['rVec'])) + vec.set("units", "arbitrary") + vec.text = " ".join(textArray(scan["rVec"])) except Exception as e: - logger.info('caught Exception while writing USAXS scan data to XML file') - logger.info(' file: %s' % specfile) + logger.info("caught Exception while writing USAXS scan data to XML file") + logger.info(" file: %s" % specfile) logger.info(e) # final steps @@ -224,35 +248,38 @@ def buildReport(): doc = minidom.parseString(ElementTree.tostring(root)) # # insert XML Processing Instruction text after first line of XML - pi = doc.createProcessingInstruction('xml-stylesheet', - 'type="text/xsl" href="raw-table.xsl"') + pi = doc.createProcessingInstruction( + "xml-stylesheet", 'type="text/xsl" href="raw-table.xsl"' + ) root = doc.firstChild doc.insertBefore(pi, root) - xmlText = doc.toxml() # all on one line, looks bad, who cares? - #xmlText = doc.toprettyxml(indent = " ") # toprettyxml() adds extra unwanted whitespace + xmlText = doc.toxml() # all on one line, looks bad, who cares? + # xmlText = doc.toprettyxml(indent = " ") # toprettyxml() adds extra unwanted whitespace return xmlText def report(): - '''write the values out to files''' + """write the values out to files""" xmlText = buildReport() # WWW directory for livedata (absolute path) localDir = localConfig.LOCAL_WWW_LIVEDATA_DIR - #--- write the XML with the raw data from EPICS + # --- write the XML with the raw data from EPICS raw_xml = localConfig.XML_REPORT_FILE abs_raw_xml = os.path.join(localDir, raw_xml) writeFile(abs_raw_xml, xmlText) wwwServerTransfers.nfsCpToWebServer(abs_raw_xml, raw_xml) - #--- xslt transforms from XML to HTML + # --- xslt transforms from XML to HTML # make the index.html file index_html = localConfig.HTML_INDEX_FILE # short name abs_index_html = os.path.join(localDir, index_html) # absolute path - xslt_transformation(localConfig.LIVEDATA_XSL_STYLESHEET, abs_raw_xml, abs_index_html) + xslt_transformation( + localConfig.LIVEDATA_XSL_STYLESHEET, abs_raw_xml, abs_index_html + ) wwwServerTransfers.nfsCpToWebServer(abs_index_html, index_html) # copy to XSD # display the raw data (but pre-convert it in an html page) @@ -272,89 +299,91 @@ def report(): # make the usaxstv.html file usaxstv_html = localConfig.HTML_USAXSTV_FILE # short name abs_usaxstv_html = os.path.join(localDir, usaxstv_html) # absolute path - xslt_transformation(localConfig.USAXSTV_XSL_STYLESHEET, abs_raw_xml, abs_usaxstv_html) + xslt_transformation( + localConfig.USAXSTV_XSL_STYLESHEET, abs_raw_xml, abs_usaxstv_html + ) wwwServerTransfers.nfsCpToWebServer(abs_usaxstv_html, usaxstv_html) # copy to XSD def update_pvdb(pv, raw_value): if pv not in pvdb: - raise PvNotRegistered('!!!ERROR!!! %s was not found in pvdb!' % pv) + raise PvNotRegistered("!!!ERROR!!! %s was not found in pvdb!" % pv) entry = pvdb[pv] - #ch = entry['ch'] - entry['timestamp'] = datetime.datetime.now() - entry['counter'] += 1 - entry['raw_value'] = raw_value - entry['value'] = entry['format'] % raw_value + # ch = entry['ch'] + entry["timestamp"] = datetime.datetime.now() + entry["counter"] += 1 + entry["raw_value"] = raw_value + entry["value"] = entry["format"] % raw_value def EPICS_monitor_receiver(*args, **kws): - '''Response to an EPICS (PyEpics) monitor on the channel''' + """Response to an EPICS (PyEpics) monitor on the channel""" global GLOBAL_MONITOR_COUNTER - pv = kws['pvname'] + pv = kws["pvname"] if pv not in pvdb: - raise PvNotRegistered('!!!ERROR!!! %s was not found in pvdb!' % pv) + raise PvNotRegistered("!!!ERROR!!! %s was not found in pvdb!" % pv) if pvdb[pv]["waveform_char"]: - v = kws['char_value'] + v = kws["char_value"] logger.debug("CA monitor waveform string value: {} = {}".format(pv, v)) else: - v = kws['value'] - update_pvdb(pv, v) # cache the last known good value + v = kws["value"] + update_pvdb(pv, v) # cache the last known good value GLOBAL_MONITOR_COUNTER += 1 def add_pv(mne, pv, desc, fmt, waveform_char=False): - '''Connect to another EPICS (PyEpics) process variable''' + """Connect to another EPICS (PyEpics) process variable""" if pv in pvdb: - raise Exception("%s already defined by id=%s" % (pv, pvdb[pv]['id'])) + raise Exception("%s already defined by id=%s" % (pv, pvdb[pv]["id"])) ch = epics.PV(pv) - #ch.connect() + # ch.connect() entry = { - 'name': pv, # EPICS PV name - 'id': mne, # symbolic name used in the python code - 'description': desc, # text description for humans - 'timestamp': None, # client time last monitor was received - 'counter': 0, # number of monitor events received - 'units': "", # engineering units - 'ch': ch, # EPICS PV channel - 'format': fmt, # format for display - 'value': None, # formatted value - 'raw_value': None, # unformatted value - 'waveform_char': waveform_char, # PV is a waveform of CHAR + "name": pv, # EPICS PV name + "id": mne, # symbolic name used in the python code + "description": desc, # text description for humans + "timestamp": None, # client time last monitor was received + "counter": 0, # number of monitor events received + "units": "", # engineering units + "ch": ch, # EPICS PV channel + "format": fmt, # format for display + "value": None, # formatted value + "raw_value": None, # unformatted value + "waveform_char": waveform_char, # PV is a waveform of CHAR } pvdb[pv] = entry - xref[mne] = pv # mne is local mnemonic, define actual PV in pvlist.xml + xref[mne] = pv # mne is local mnemonic, define actual PV in pvlist.xml ch.add_callback(EPICS_monitor_receiver) # start callbacks now cv = ch.get_ctrlvars() - unit_renames = { # handle some non SI unit names + unit_renames = { # handle some non SI unit names # old new - 'millime': 'mm', - 'millira': 'mr', - 'degrees': 'deg', - 'Volts': 'V', - 'VDC': 'V', - 'eng': '', + "millime": "mm", + "millira": "mr", + "degrees": "deg", + "Volts": "V", + "VDC": "V", + "eng": "", } - if cv is not None and 'units' in cv: - units = cv['units'] + if cv is not None and "units" in cv: + units = cv["units"] if units in unit_renames: units = unit_renames[units] - entry['units'] = units + entry["units"] = units if waveform_char: v = ch.get(as_string=True) else: v = ch.get() - update_pvdb(pv, v) # initialize the cache + update_pvdb(pv, v) # initialize the cache def initiate_PV_connections(): - '''create connections to all defined PVs''' + """create connections to all defined PVs""" if not os.path.exists(PVLIST_FILE): - logger.info('could not find file: ' + PVLIST_FILE) + logger.info("could not find file: " + PVLIST_FILE) return try: tree = ElementTree.parse(PVLIST_FILE) except Exception: - logger.info('could not parse file: ' + PVLIST_FILE) + logger.info("could not parse file: " + PVLIST_FILE) return for key in tree.findall(".//EPICS_PV"): @@ -367,12 +396,15 @@ def initiate_PV_connections(): try: add_pv(mne, pv, desc, fmt, waveform_char=waveform_char) except Exception: - msg = "%s: problem connecting: %s" % (PVLIST_FILE, ElementTree.tostring(key)) + msg = "%s: problem connecting: %s" % ( + PVLIST_FILE, + ElementTree.tostring(key), + ) logger.warn(msg) def main_event_loop_checks(mainLoopCount, nextReport, nextLog, delta_report, delta_log): - '''check events for the main event loop''' + """check events for the main event loop""" global GLOBAL_MONITOR_COUNTER global MAINLOOP_COUNTER_TRIGGER dt = datetime.datetime.now() @@ -388,7 +420,7 @@ def main_event_loop_checks(mainLoopCount, nextReport, nextLog, delta_report, del # https://github.com/APS-USAXS/livedata/issues/6 logger.debug(pvdb["9idcLAX:USAXS:sampleTitle"]["value"]) t0 = time.time() - report() # write contents of pvdb to a file + report() # write contents of pvdb to a file logger.debug("report() completed in %.3f s" % (time.time() - t0)) except Exception as exc: msg = "problem with {}(): traceback={}".format("report", exc) @@ -396,14 +428,14 @@ def main_event_loop_checks(mainLoopCount, nextReport, nextLog, delta_report, del logger.warn(traceback.format_exc()) try: - updateSpecMacroFile() # copy the spec macro file + updateSpecMacroFile() # copy the spec macro file except Exception as exc: msg = "problem with {}(): traceback={}".format("updateSpecMacroFile", exc) logger.warn(msg) logger.warn(traceback.format_exc()) try: - updatePlotImage() # update the plot + updatePlotImage() # update the plot except Exception as exc: msg = "problem with {}(): traceback={}".format("updatePlotImage", exc) logger.warn(msg) @@ -419,18 +451,18 @@ def main_event_loop_checks(mainLoopCount, nextReport, nextLog, delta_report, del def main(): - ''' + """ run the main event loop - ''' + """ global GLOBAL_MONITOR_COUNTER - test_pv = 'S:SRcurrentAI' + test_pv = "S:SRcurrentAI" epics.caget(test_pv) ch = epics.PV(test_pv) epics.ca.poll() connected = ch.connect(timeout=5.0) if not connected: - logger.info('Did not connect PV: ' + str(ch)) - logger.info('program will exit') + logger.info("Did not connect PV: " + str(ch)) + logger.info("program will exit") return logger.info("starting pvwatch.py") @@ -449,8 +481,9 @@ def main(): mainLoopCount = 0 while True: mainLoopCount = (mainLoopCount + 1) % MAINLOOP_COUNTER_TRIGGER - nextReport, nextLog = main_event_loop_checks(mainLoopCount, - nextReport, nextLog, delta_report, delta_log) + nextReport, nextLog = main_event_loop_checks( + mainLoopCount, nextReport, nextLog, delta_report, delta_log + ) time.sleep(localConfig.SLEEP_INTERVAL_S) # # this exit handling will never be called @@ -461,5 +494,5 @@ def main(): # print "script is done" -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/radialprofile.py b/radialprofile.py index 750d99c..da9a31f 100644 --- a/radialprofile.py +++ b/radialprofile.py @@ -1,8 +1,20 @@ import numpy as np -def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return_nr=False, - binsize=0.5, weights=None, steps=False, interpnan=False, left=None, right=None, - mask=None ): + +def azimuthalAverage( + image, + center=None, + stddev=False, + returnradii=False, + return_nr=False, + binsize=0.5, + weights=None, + steps=False, + interpnan=False, + left=None, + right=None, + mask=None, +): """ Calculate the azimuthally averaged radial profile. @@ -36,7 +48,7 @@ def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return y, x = np.indices(image.shape) if center is None: - center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + center = np.array([(x.max() - x.min()) / 2.0, (y.max() - y.min()) / 2.0]) r = np.hypot(x - center[0], y - center[1]) @@ -46,100 +58,114 @@ def azimuthalAverage(image, center=None, stddev=False, returnradii=False, return raise ValueError("Weighted standard deviation is not defined.") if mask is None: - mask = np.ones(image.shape,dtype='bool') + mask = np.ones(image.shape, dtype="bool") # obsolete elif len(mask.shape) > 1: # obsolete mask = mask.ravel() # the 'bins' as initially defined are lower/upper bounds for each bin # so that values will be in [lower,upper) - nbins = int(np.round(r.max() / binsize)+1) + nbins = int(np.round(r.max() / binsize) + 1) maxbin = nbins * binsize - bins = np.linspace(0,maxbin,nbins+1) + bins = np.linspace(0, maxbin, nbins + 1) # but we're probably more interested in the bin centers than their left or right sides... - bin_centers = (bins[1:]+bins[:-1])/2.0 + bin_centers = (bins[1:] + bins[:-1]) / 2.0 # how many per bin (i.e., histogram)? # there are never any in bin 0, because the lowest index returned by digitize is 1 - #nr = np.bincount(whichbin)[1:] - nr = np.histogram(r, bins, weights=mask.astype('int'))[0] + # nr = np.bincount(whichbin)[1:] + nr = np.histogram(r, bins, weights=mask.astype("int"))[0] # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or range(1,nbins+1) ) # radial_prof.shape = bin_centers.shape if stddev: # Find out which radial bin each point in the map belongs to - whichbin = np.digitize(r.flat,bins) + whichbin = np.digitize(r.flat, bins) # This method is still very slow; is there a trick to do this with histograms? radial_prof = np.array( - [ - image.flat[mask.flat*(whichbin==b)].std() - for b in range(1,nbins+1) - ] + [image.flat[mask.flat * (whichbin == b)].std() for b in range(1, nbins + 1)] ) else: radial_prof = ( - np.histogram(r, bins, weights=(image*weights*mask))[0] - / - np.histogram(r, bins, weights=(weights*mask))[0] + np.histogram(r, bins, weights=(image * weights * mask))[0] + / np.histogram(r, bins, weights=(weights * mask))[0] ) if interpnan: radial_prof = np.interp( bin_centers, - bin_centers[radial_prof==radial_prof], # FIXME: tests identical numbers - radial_prof[radial_prof==radial_prof], # FIXME: tests identical numbers + bin_centers[radial_prof == radial_prof], # FIXME: tests identical numbers + radial_prof[radial_prof == radial_prof], # FIXME: tests identical numbers left=left, - right=right + right=right, ) if steps: - xarr = np.array(zip(bins[:-1],bins[1:])).ravel() - yarr = np.array(zip(radial_prof,radial_prof)).ravel() - return xarr,yarr + xarr = np.array(zip(bins[:-1], bins[1:])).ravel() + yarr = np.array(zip(radial_prof, radial_prof)).ravel() + return xarr, yarr elif returnradii: - return bin_centers,radial_prof + return bin_centers, radial_prof elif return_nr: - return nr,bin_centers,radial_prof + return nr, bin_centers, radial_prof else: return radial_prof -def azimuthalAverageBins(image,azbins,symmetric=None, center=None, **kwargs): - """ Compute the azimuthal average over a limited range of angles - kwargs are passed to azimuthalAverage """ + +def azimuthalAverageBins(image, azbins, symmetric=None, center=None, **kwargs): + """Compute the azimuthal average over a limited range of angles + kwargs are passed to azimuthalAverage""" y, x = np.indices(image.shape) if center is None: - center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + center = np.array([(x.max() - x.min()) / 2.0, (y.max() - y.min()) / 2.0]) # r = np.hypot(x - center[0], y - center[1]) theta = np.arctan2(x - center[0], y - center[1]) - theta[theta < 0] += 2*np.pi - theta_deg = theta*180.0/np.pi + theta[theta < 0] += 2 * np.pi + theta_deg = theta * 180.0 / np.pi - if isinstance(azbins,np.ndarray): + if isinstance(azbins, np.ndarray): pass - elif isinstance(azbins,int): + elif isinstance(azbins, int): if symmetric == 2: - azbins = np.linspace(0,90,azbins) + azbins = np.linspace(0, 90, azbins) theta_deg = theta_deg % 90 elif symmetric == 1: - azbins = np.linspace(0,180,azbins) + azbins = np.linspace(0, 180, azbins) theta_deg = theta_deg % 180 elif azbins == 1: - return azbins,azimuthalAverage(image,center=center,returnradii=True,**kwargs) + return azbins, azimuthalAverage( + image, center=center, returnradii=True, **kwargs + ) else: - azbins = np.linspace(0,359.9999999999999,azbins) + azbins = np.linspace(0, 359.9999999999999, azbins) else: raise ValueError("azbins must be an ndarray or an integer") azavlist = [] - for blow,bhigh in zip(azbins[:-1],azbins[1:]): + for blow, bhigh in zip(azbins[:-1], azbins[1:]): mask = (theta_deg > (blow % 360)) * (theta_deg < (bhigh % 360)) - rr,zz = azimuthalAverage(image,center=center,mask=mask,returnradii=True,**kwargs) + rr, zz = azimuthalAverage( + image, center=center, mask=mask, returnradii=True, **kwargs + ) azavlist.append(zz) - return azbins,rr,azavlist - -def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=False, - binsize=1.0, weights=None, steps=False, interpnan=False, left=None, right=None, - mask=None, symmetric=None ): + return azbins, rr, azavlist + + +def radialAverage( + image, + center=None, + stddev=False, + returnAz=False, + return_naz=False, + binsize=1.0, + weights=None, + steps=False, + interpnan=False, + left=None, + right=None, + mask=None, + symmetric=None, +): """ Calculate the radially averaged azimuthal profile. (this code has not been optimized; it could be speed boosted by ~20x) @@ -174,12 +200,12 @@ def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=F y, x = np.indices(image.shape) if center is None: - center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + center = np.array([(x.max() - x.min()) / 2.0, (y.max() - y.min()) / 2.0]) # r = np.hypot(x - center[0], y - center[1]) theta = np.arctan2(x - center[0], y - center[1]) - theta[theta < 0] += 2*np.pi - theta_deg = theta*180.0/np.pi + theta[theta < 0] += 2 * np.pi + theta_deg = theta * 180.0 / np.pi maxangle = 360 if weights is None: @@ -189,7 +215,7 @@ def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=F if mask is None: # mask is only used in a flat context - mask = np.ones(image.shape,dtype='bool').ravel() + mask = np.ones(image.shape, dtype="bool").ravel() elif len(mask.shape) > 1: mask = mask.ravel() @@ -205,12 +231,12 @@ def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=F # so that values will be in [lower,upper) nbins = int(np.round(maxangle / binsize)) maxbin = nbins * binsize - bins = np.linspace(0,maxbin,nbins+1) + bins = np.linspace(0, maxbin, nbins + 1) # but we're probably more interested in the bin centers than their left or right sides... - bin_centers = (bins[1:]+bins[:-1])/2.0 + bin_centers = (bins[1:] + bins[:-1]) / 2.0 # Find out which azimuthal bin each point in the map belongs to - whichbin = np.digitize(theta_deg.flat,bins) + whichbin = np.digitize(theta_deg.flat, bins) # how many per bin (i.e., histogram)? # there are never any in bin 0, because the lowest index returned by digitize is 1 @@ -219,52 +245,66 @@ def radialAverage(image, center=None, stddev=False, returnAz=False, return_naz=F # recall that bins are from 1 to nbins (which is expressed in array terms by arange(nbins)+1 or range(1,nbins+1) ) # azimuthal_prof.shape = bin_centers.shape if stddev: - azimuthal_prof = np.array([image.flat[mask*(whichbin==b)].std() for b in range(1,nbins+1)]) + azimuthal_prof = np.array( + [image.flat[mask * (whichbin == b)].std() for b in range(1, nbins + 1)] + ) else: - azimuthal_prof = np.array([(image*weights).flat[mask*(whichbin==b)].sum() / weights.flat[mask*(whichbin==b)].sum() for b in range(1,nbins+1)]) + azimuthal_prof = np.array( + [ + (image * weights).flat[mask * (whichbin == b)].sum() + / weights.flat[mask * (whichbin == b)].sum() + for b in range(1, nbins + 1) + ] + ) - #import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() if interpnan: - azimuthal_prof = np.interp(bin_centers, - bin_centers[azimuthal_prof==azimuthal_prof], - azimuthal_prof[azimuthal_prof==azimuthal_prof], - left=left,right=right) + azimuthal_prof = np.interp( + bin_centers, + bin_centers[azimuthal_prof == azimuthal_prof], + azimuthal_prof[azimuthal_prof == azimuthal_prof], + left=left, + right=right, + ) if steps: - xarr = np.array(zip(bins[:-1],bins[1:])).ravel() - yarr = np.array(zip(azimuthal_prof,azimuthal_prof)).ravel() - return xarr,yarr + xarr = np.array(zip(bins[:-1], bins[1:])).ravel() + yarr = np.array(zip(azimuthal_prof, azimuthal_prof)).ravel() + return xarr, yarr elif returnAz: - return bin_centers,azimuthal_prof + return bin_centers, azimuthal_prof elif return_naz: - return nr,bin_centers,azimuthal_prof + return nr, bin_centers, azimuthal_prof else: return azimuthal_prof -def radialAverageBins(image,radbins, corners=True, center=None, **kwargs): - """ Compute the radial average over a limited range of radii """ + +def radialAverageBins(image, radbins, corners=True, center=None, **kwargs): + """Compute the radial average over a limited range of radii""" y, x = np.indices(image.shape) if center is None: - center = np.array([(x.max()-x.min())/2.0, (y.max()-y.min())/2.0]) + center = np.array([(x.max() - x.min()) / 2.0, (y.max() - y.min()) / 2.0]) r = np.hypot(x - center[0], y - center[1]) - if isinstance(radbins,np.ndarray): + if isinstance(radbins, np.ndarray): pass - elif isinstance(radbins,int): + elif isinstance(radbins, int): if radbins == 1: - return radbins,radialAverage(image,center=center,returnAz=True,**kwargs) + return radbins, radialAverage(image, center=center, returnAz=True, **kwargs) elif corners: - radbins = np.linspace(0,r.max(),radbins) + radbins = np.linspace(0, r.max(), radbins) else: - radbins = np.linspace(0,np.max(np.abs(np.array([x-center[0],y-center[1]]))),radbins) + radbins = np.linspace( + 0, np.max(np.abs(np.array([x - center[0], y - center[1]]))), radbins + ) else: raise ValueError("radbins must be an ndarray or an integer") radavlist = [] - for blow,bhigh in zip(radbins[:-1],radbins[1:]): - mask = (rblow) - az,zz = radialAverage(image,center=center,mask=mask,returnAz=True,**kwargs) + for blow, bhigh in zip(radbins[:-1], radbins[1:]): + mask = (r < bhigh) * (r > blow) + az, zz = radialAverage(image, center=center, mask=mask, returnAz=True, **kwargs) radavlist.append(zz) - return radbins,az,radavlist + return radbins, az, radavlist diff --git a/reduceAreaDetector.py b/reduceAreaDetector.py index eab2ca5..98955d8 100644 --- a/reduceAreaDetector.py +++ b/reduceAreaDetector.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -'''reduceAreaDetector: reduce the raw data from Area Detector images to R(Q)''' +"""reduceAreaDetector: reduce the raw data from Area Detector images to R(Q)""" import h5py import logging @@ -31,51 +31,51 @@ # [x] resolve problem with h5py, cannot append reduced data to existing file -DEFAULT_BIN_COUNT = localConfig.REDUCED_AD_IMAGE_BINS +DEFAULT_BIN_COUNT = localConfig.REDUCED_AD_IMAGE_BINS PIXEL_SIZE_TOLERANCE = 1.0e-6 -ESD_FACTOR = 0.01 # estimate dr = ESD_FACTOR * r if r.std() = 0 : 1% errors +ESD_FACTOR = 0.01 # estimate dr = ESD_FACTOR * r if r.std() = 0 : 1% errors # define the locations of the source data in the HDF5 file # there are various possible layouts AD_HDF5_ADDRESS_MAP = { - 'local_name' : '/entry/data/local_name', - 'Dexela N2315' : { - 'image' : '/entry/data/data', - 'wavelength' : '/entry/Metadata/wavelength', - 'SDD' : '/entry/Metadata/SDD', - 'x_image_center_pixels' : '/entry/instrument/detector/beam_center_x', - 'y_image_center_pixels' : '/entry/instrument/detector/beam_center_y', - 'x_pixel_size_mm' : '/entry/instrument/detector/x_pixel_size', - 'y_pixel_size_mm' : '/entry/instrument/detector/y_pixel_size', - 'I0_counts' : '/entry/Metadata/I0_cts_gated', - 'I0_gain' : '/entry/Metadata/I0_gain', + "local_name": "/entry/data/local_name", + "Dexela N2315": { + "image": "/entry/data/data", + "wavelength": "/entry/Metadata/wavelength", + "SDD": "/entry/Metadata/SDD", + "x_image_center_pixels": "/entry/instrument/detector/beam_center_x", + "y_image_center_pixels": "/entry/instrument/detector/beam_center_y", + "x_pixel_size_mm": "/entry/instrument/detector/x_pixel_size", + "y_pixel_size_mm": "/entry/instrument/detector/y_pixel_size", + "I0_counts": "/entry/Metadata/I0_cts_gated", + "I0_gain": "/entry/Metadata/I0_gain", # need to consider a detector-dependent mask }, - 'Pilatus 100K' : { - 'image' : '/entry/data/data', - 'wavelength' : '/entry/Metadata/wavelength', - 'SDD' : '/entry/Metadata/SDD', + "Pilatus 100K": { + "image": "/entry/data/data", + "wavelength": "/entry/Metadata/wavelength", + "SDD": "/entry/Metadata/SDD", # image is transposed, consider that here - 'y_image_center_pixels' : '/entry/instrument/detector/beam_center_x', - 'x_image_center_pixels' : '/entry/instrument/detector/beam_center_y', - 'x_pixel_size_mm' : '/entry/instrument/detector/x_pixel_size', - 'y_pixel_size_mm' : '/entry/instrument/detector/y_pixel_size', - 'I0_counts' : '/entry/Metadata/I0_cts_gated', - 'I0_gain' : '/entry/Metadata/I0_gain', + "y_image_center_pixels": "/entry/instrument/detector/beam_center_x", + "x_image_center_pixels": "/entry/instrument/detector/beam_center_y", + "x_pixel_size_mm": "/entry/instrument/detector/x_pixel_size", + "y_pixel_size_mm": "/entry/instrument/detector/y_pixel_size", + "I0_counts": "/entry/Metadata/I0_cts_gated", + "I0_gain": "/entry/Metadata/I0_gain", # need to consider a detector-dependent mask }, - 'Pilatus 300Kw' : { - 'image' : '/entry/data/data', - 'wavelength' : '/entry/Metadata/dcm_wavelength', + "Pilatus 300Kw": { + "image": "/entry/data/data", + "wavelength": "/entry/Metadata/dcm_wavelength", # TODO: Now? 'wavelength' : '/entry/Metadata/wavelength', - 'SDD' : '/entry/Metadata/SDD', + "SDD": "/entry/Metadata/SDD", # image is transposed, consider that here - 'y_image_center_pixels' : '/entry/instrument/detector/beam_center_x', - 'x_image_center_pixels' : '/entry/instrument/detector/beam_center_y', - 'x_pixel_size_mm' : '/entry/instrument/detector/x_pixel_size', - 'y_pixel_size_mm' : '/entry/instrument/detector/y_pixel_size', - 'I0_counts' : '/entry/Metadata/I0_cts_gated', - 'I0_gain' : '/entry/Metadata/I0_gain', + "y_image_center_pixels": "/entry/instrument/detector/beam_center_x", + "x_image_center_pixels": "/entry/instrument/detector/beam_center_y", + "x_pixel_size_mm": "/entry/instrument/detector/x_pixel_size", + "y_pixel_size_mm": "/entry/instrument/detector/y_pixel_size", + "I0_counts": "/entry/Metadata/I0_cts_gated", + "I0_gain": "/entry/Metadata/I0_gain", # need to consider a detector-dependent mask }, } @@ -85,57 +85,60 @@ class AD_ScatteringImage(object): - def __init__(self, hdf5_file_name): if not os.path.exists(hdf5_file_name): - raise IOError('file not found: ' + hdf5_file_name) + raise IOError("file not found: " + hdf5_file_name) self.hdf5_file_name = hdf5_file_name self.image = None self.reduced = {} self.units = dict( - x = 'mm', - Q = '1/A', - R = 'none', - dR = 'none', + x="mm", + Q="1/A", + R="none", + dR="none", ) def read_image_data(self): - ''' + """ read image data from the HDF5 file, return as instance of :class:`Image` - ''' + """ with h5py.File(self.hdf5_file_name) as fp: self.image = Image(fp) self.image.read_image_data() return self.image - def has_reduced(self, key = 'full'): - ''' + def has_reduced(self, key="full"): + """ check if the reduced dataset is available :param str|int key: name of reduced dataset (default = 'full') - ''' + """ key = str(key) if key not in self.reduced: return False - return 'Q' in self.reduced[key] and 'R' in self.reduced[key] + return "Q" in self.reduced[key] and "R" in self.reduced[key] def reduce(self): - '''convert raw image data to R(Q), also get other terms''' - if self.image is None: # TODO: make this conditional on need to reduce image data + """convert raw image data to R(Q), also get other terms""" + if ( + self.image is None + ): # TODO: make this conditional on need to reduce image data self.read_image_data() if abs(self.image.xsize - self.image.ysize) > PIXEL_SIZE_TOLERANCE: - raise ValueError('X & Y pixels have different sizes, not prepared for this') + raise ValueError("X & Y pixels have different sizes, not prepared for this") # TODO: construct the image mask # radial averaging (average around the azimuth at constant radius, repeat at all radii) - with numpy.errstate(invalid='ignore'): - radii, rAvg = azimuthalAverage(self.image.image, - center=(self.image.x0, self.image.y0), - returnradii=True) + with numpy.errstate(invalid="ignore"): + radii, rAvg = azimuthalAverage( + self.image.image, + center=(self.image.x0, self.image.y0), + returnradii=True, + ) # standard deviation results do not look right, skip that in full data reduction # with numpy.errstate(invalid='ignore'): @@ -147,40 +150,46 @@ def reduce(self): # stddev=True) radii *= self.image.xsize - Q = (4*math.pi / self.image.wavelength) * numpy.sin(0.5*numpy.arctan2(radii, self.image.SDD)) + Q = (4 * math.pi / self.image.wavelength) * numpy.sin( + 0.5 * numpy.arctan2(radii, self.image.SDD) + ) # scale_factor = 1 /self.image.I0_gain / self.image.I0 # TODO: verify the equation - scale_factor = 1 / self.image.I0 # TODO: verify the equation + scale_factor = 1 / self.image.I0 # TODO: verify the equation rAvg = rAvg * scale_factor # remove NaNs and non-positives from output data rAvg = numpy.ma.masked_less_equal(rAvg, 0) # mask the non-positives - rAvg = numpy.ma.masked_invalid(rAvg) # mask the NaNs + rAvg = numpy.ma.masked_invalid(rAvg) # mask the NaNs Q = calc.remove_masked_data(Q, rAvg.mask) radii = calc.remove_masked_data(radii, rAvg.mask) # rAvgDev = calc.remove_masked_data(rAvgDev, rAvg.mask) - rAvg = calc.remove_masked_data(rAvg, rAvg.mask) # always remove the masked array last + rAvg = calc.remove_masked_data( + rAvg, rAvg.mask + ) # always remove the masked array last full = dict(Q=Q, R=rAvg, x=radii) - self.reduced = dict(full = full) # reset the entire dictionary with new "full" reduction + self.reduced = dict( + full=full + ) # reset the entire dictionary with new "full" reduction def rebin(self, bin_count=250): - ''' + """ generate R(Q) with a bin_count bins save in ``self.reduced[str(bin_count)]`` dict - ''' + """ if not self.has_reduced(): self.reduce() - if 'full' not in self.reduced: - raise IndexError('no data reduction: ' + self.hdf5_file_name) - #return + if "full" not in self.reduced: + raise IndexError("no data reduction: " + self.hdf5_file_name) + # return - bin_count_full = len(self.reduced['full']['Q']) + bin_count_full = len(self.reduced["full"]["Q"]) bin_count = min(bin_count, bin_count_full) s = str(bin_count) - Q_full = self.reduced['full']['Q'] - R_full = self.reduced['full']['R'] + Q_full = self.reduced["full"]["Q"] + R_full = self.reduced["full"]["R"] # lowest non-zero Q value > 0 or minimum acceptable Q Qmin = Q_full.min() @@ -196,24 +205,24 @@ def rebin(self, bin_count=250): r = R_full[xref] # average Q & R in log-log space, won't matter to WAXS data at higher Q if q.size > 0: - qVec.append( numpy.exp(numpy.mean(numpy.log(q))) ) - rVec.append( numpy.exp(numpy.mean(numpy.log(r))) ) + qVec.append(numpy.exp(numpy.mean(numpy.log(q)))) + rVec.append(numpy.exp(numpy.mean(numpy.log(r)))) dr = r.std() if dr == 0.0: - drVec.append( ESD_FACTOR * rVec[-1] ) + drVec.append(ESD_FACTOR * rVec[-1]) else: - drVec.append( r.std() ) + drVec.append(r.std()) reduced = dict( - Q = numpy.array(qVec), - R = numpy.array(rVec), - dR = numpy.array(drVec), + Q=numpy.array(qVec), + R=numpy.array(rVec), + dR=numpy.array(drVec), ) self.reduced[s] = reduced return reduced def read_reduced(self): - ''' + """ read any and all reduced data from the HDF5 file, return in a dictionary dictionary = { @@ -221,15 +230,15 @@ def read_reduced(self): '250': dict(Q, R, dR) '50': dict(Q, R, dR) } - ''' + """ fields = self.units.keys() reduced = {} - with h5py.File(self.hdf5_file_name, 'r') as hdf: - entry = hdf['/entry'] + with h5py.File(self.hdf5_file_name, "r") as hdf: + entry = hdf["/entry"] for key in entry.keys(): - if key.startswith('areaDetector_reduced_'): + if key.startswith("areaDetector_reduced_"): nxdata = entry[key] - nxname = key[len('areaDetector_reduced_'):] + nxname = key[len("areaDetector_reduced_") :] d = {} for dsname in fields: if dsname in nxdata: @@ -243,8 +252,8 @@ def read_reduced(self): self.reduced = reduced return reduced - def save(self, hfile = None, key = None): - ''' + def save(self, hfile=None, key=None): + """ save the reduced data group to an HDF5 file, return filename or None if not written :param str hfile: output HDF5 file name (default: input HDF5 file) @@ -272,15 +281,15 @@ def save(self, hfile = None, key = None): :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXentry.html :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXdata.html - ''' + """ key = str(key) if key not in self.reduced: return - nxname = 'areaDetector_reduced_' + key + nxname = "areaDetector_reduced_" + key hfile = hfile or self.hdf5_file_name ds = self.reduced[key] try: - hdf = h5py.File(hfile, 'a') + hdf = h5py.File(hfile, "a") except IOError as _exc: # FIXME: some h5py problem in /_hl/files.py, line 101 # this fails: fid = h5f.open(name, h5f.ACC_RDWR, fapl=fapl) @@ -293,63 +302,63 @@ def save(self, hfile = None, key = None): # TODO: change uid/gid on all the acquired HDF5 files (*.h5, *.hdf) under usaxscontrol:/share1/USAXS_data/2* # Files should be owned by usaxs:usaxs (1810:2026), but are owned by tomo2:usaxs (500:2026) as seen by usaxs@usaxscontrol # not enough to change the "umask" on the det@dec1122 computer, what else will fix this? - pvwatch.logMessage( "Problem writing reduced data back to file: " + hfile ) + pvwatch.logMessage("Problem writing reduced data back to file: " + hfile) return - if 'default' not in hdf.attrs: - hdf.attrs['default'] = 'entry' - nxentry = eznx.openGroup(hdf, 'entry', 'NXentry') - if 'default' not in nxentry.attrs: - nxentry.attrs['default'] = nxname - nxdata = eznx.openGroup(nxentry, - nxname, - 'NXdata', - signal='R', - axes='Q', - Q_indices=0, - timestamp=calc.iso8601_datetime(), - ) + if "default" not in hdf.attrs: + hdf.attrs["default"] = "entry" + nxentry = eznx.openGroup(hdf, "entry", "NXentry") + if "default" not in nxentry.attrs: + nxentry.attrs["default"] = nxname + nxdata = eznx.openGroup( + nxentry, + nxname, + "NXdata", + signal="R", + axes="Q", + Q_indices=0, + timestamp=calc.iso8601_datetime(), + ) for key in sorted(ds.keys()): try: _ds = eznx.write_dataset(nxdata, key, ds[key]) if key in self.units: eznx.addAttributes(_ds, units=self.units[key]) except RuntimeError: - pass # TODO: reporting + pass # TODO: reporting hdf.close() return hfile class Image(object): - def __init__(self, fp): self.fp = fp - self.filename = None - self.image = None - self.wavelength = None - self.SDD = None - self.x0 = None - self.y0 = None - self.xsize = None - self.ysize = None - self.I0 = None - self.I0_gain = None - self.hdf5_addr_map = None + self.filename = None + self.image = None + self.wavelength = None + self.SDD = None + self.x0 = None + self.y0 = None + self.xsize = None + self.ysize = None + self.I0 = None + self.I0_gain = None + self.hdf5_addr_map = None def read_image_data(self): - ''' + """ get the image from the HDF5 file determine if SAXS or WAXS based on detector name as coded into the h5addr - ''' - detector_name_h5addr = AD_HDF5_ADDRESS_MAP['local_name'] + """ + detector_name_h5addr = AD_HDF5_ADDRESS_MAP["local_name"] detector_name = str(self.fp[detector_name_h5addr].value[0]) self.hdf5_addr_map = h5addr = AD_HDF5_ADDRESS_MAP[detector_name] - self.filename = self.fp.filename + self.filename = self.fp.filename def read_keyed_dataset(key): dataset = self.fp[h5addr[key]] - if dataset.shape == (1, ): # historical representation of scalar value + if dataset.shape == (1,): # historical representation of scalar value value = dataset[0] elif dataset.shape == (): # scalar representation value = dataset.value @@ -357,69 +366,70 @@ def read_keyed_dataset(key): value = numpy.array(dataset) return value - self.image = read_keyed_dataset('image') - self.wavelength = read_keyed_dataset('wavelength') - self.SDD = read_keyed_dataset('SDD') - self.x0 = read_keyed_dataset('x_image_center_pixels') - self.y0 = read_keyed_dataset('y_image_center_pixels') - self.xsize = read_keyed_dataset('x_pixel_size_mm') - self.ysize = read_keyed_dataset('y_pixel_size_mm') - self.I0 = read_keyed_dataset('I0_counts') - self.I0_gain = read_keyed_dataset('I0_gain') + self.image = read_keyed_dataset("image") + self.wavelength = read_keyed_dataset("wavelength") + self.SDD = read_keyed_dataset("SDD") + self.x0 = read_keyed_dataset("x_image_center_pixels") + self.y0 = read_keyed_dataset("y_image_center_pixels") + self.xsize = read_keyed_dataset("x_pixel_size_mm") + self.ysize = read_keyed_dataset("y_pixel_size_mm") + self.I0 = read_keyed_dataset("I0_counts") + self.I0_gain = read_keyed_dataset("I0_gain") # later, scale each image by metadata I0_cts_gated and I0_gain # TODO: get image mask specifications return def get_user_options(): - '''parse the command line for the user options''' + """parse the command line for the user options""" import argparse - parser = argparse.ArgumentParser(prog='reduceAreaDetector', description=__doc__) - parser.add_argument('hdf5_file', - action='store', - help="NeXus/HDF5 data file name") - msg = 'how many bins in output R(Q)?' - msg += ' (default = %d)' % DEFAULT_BIN_COUNT - parser.add_argument('-n', - '--num_bins', - dest='num_bins', - type=int, - default=DEFAULT_BIN_COUNT, - help=msg) - msg = 'output file name?' - msg += ' (default = input HDF5 file)' - parser.add_argument('-o', - '--output_file', - dest='output_file', - type=str, - default='', - help=msg) - parser.add_argument('-V', - '--version', - action='version', - version='$Id$') - - parser.add_argument('--recompute-full', - dest='recompute_full', - action='store_true', - default=False, - help='(re)compute full R(Q): implies --recompute-rebinning') - - parser.add_argument('--recompute-rebinned', - dest='recompute_rebinned', - action='store_true', - default=False, - help='(re)compute rebinned R(Q)') + + parser = argparse.ArgumentParser(prog="reduceAreaDetector", description=__doc__) + parser.add_argument("hdf5_file", action="store", help="NeXus/HDF5 data file name") + msg = "how many bins in output R(Q)?" + msg += " (default = %d)" % DEFAULT_BIN_COUNT + parser.add_argument( + "-n", + "--num_bins", + dest="num_bins", + type=int, + default=DEFAULT_BIN_COUNT, + help=msg, + ) + msg = "output file name?" + msg += " (default = input HDF5 file)" + parser.add_argument( + "-o", "--output_file", dest="output_file", type=str, default="", help=msg + ) + parser.add_argument("-V", "--version", action="version", version="$Id$") + + parser.add_argument( + "--recompute-full", + dest="recompute_full", + action="store_true", + default=False, + help="(re)compute full R(Q): implies --recompute-rebinning", + ) + + parser.add_argument( + "--recompute-rebinned", + dest="recompute_rebinned", + action="store_true", + default=False, + help="(re)compute rebinned R(Q)", + ) return parser.parse_args() -def reduce_area_detector_data(hdf5_file, - num_bins, - recompute_full=False, - recompute_rebinned=False, - output_filename=None): - ''' +def reduce_area_detector_data( + hdf5_file, + num_bins, + recompute_full=False, + recompute_rebinned=False, + output_filename=None, +): + """ reduce areaDetector image data to R(Q) :param str hdf5_file: name of HDF5 file with AD image data @@ -430,44 +440,44 @@ def reduce_area_detector_data(hdf5_file, even if reduced data already in data file (default: False) :param str output_filename: name of file to write reduced data if None, use hdf5_file (default: None) - ''' + """ needs_calc = {} - pvwatch.logMessage( "Area Detector data file: " + hdf5_file ) - scan = AD_ScatteringImage(hdf5_file) # initialize the object + pvwatch.logMessage("Area Detector data file: " + hdf5_file) + scan = AD_ScatteringImage(hdf5_file) # initialize the object s_num_bins = str(num_bins) output_filename = output_filename or hdf5_file - pvwatch.logMessage( ' checking for previously-saved R(Q)' ) + pvwatch.logMessage(" checking for previously-saved R(Q)") scan.read_reduced() - needs_calc['full'] = not scan.has_reduced('full') + needs_calc["full"] = not scan.has_reduced("full") if recompute_full: - needs_calc['full'] = True + needs_calc["full"] = True needs_calc[s_num_bins] = not scan.has_reduced(s_num_bins) if recompute_rebinned: needs_calc[s_num_bins] = True - if needs_calc['full']: - pvwatch.logMessage(' reducing Area Detector image to R(Q)') + if needs_calc["full"]: + pvwatch.logMessage(" reducing Area Detector image to R(Q)") scan.reduce() - pvwatch.logMessage( ' saving reduced R(Q) to ' + output_filename) - scan.save(hdf5_file, 'full') + pvwatch.logMessage(" saving reduced R(Q) to " + output_filename) + scan.save(hdf5_file, "full") needs_calc[s_num_bins] = True if needs_calc[s_num_bins]: - msg = ' rebinning R(Q) (from %d) to %d points' - msg = msg % (scan.reduced['full']['Q'].size, num_bins) - pvwatch.logMessage( msg ) + msg = " rebinning R(Q) (from %d) to %d points" + msg = msg % (scan.reduced["full"]["Q"].size, num_bins) + pvwatch.logMessage(msg) scan.rebin(num_bins) - pvwatch.logMessage( ' saving rebinned R(Q) to ' + output_filename ) + pvwatch.logMessage(" saving rebinned R(Q) to " + output_filename) scan.save(hdf5_file, s_num_bins) return scan def command_line_interface(): - '''standard command-line interface''' + """standard command-line interface""" cmd_args = get_user_options() if len(cmd_args.output_file) > 0: @@ -475,15 +485,17 @@ def command_line_interface(): else: output_filename = cmd_args.hdf5_file - scan = reduce_area_detector_data(cmd_args.hdf5_file, - cmd_args.num_bins, - recompute_full=cmd_args.recompute_full, - recompute_rebinned=cmd_args.recompute_rebinned, - output_filename=output_filename) + scan = reduce_area_detector_data( + cmd_args.hdf5_file, + cmd_args.num_bins, + recompute_full=cmd_args.recompute_full, + recompute_rebinned=cmd_args.recompute_rebinned, + output_filename=output_filename, + ) return scan -if __name__ == '__main__': +if __name__ == "__main__": # # for developer use only # import sys # # sys.argv.append("/share1/USAXS_data/2018-01/01_30_Settle_waxs/Adam_0184.hdf") diff --git a/reduceFlyData.py b/reduceFlyData.py index a38c576..9e3cc7e 100755 --- a/reduceFlyData.py +++ b/reduceFlyData.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -'''reduceFlyScanData: reduce the raw data from USAXS Fly Scans to R(Q)''' +"""reduceFlyScanData: reduce the raw data from USAXS Fly Scans to R(Q)""" import h5py import logging @@ -19,27 +19,28 @@ logger = logging.getLogger(__name__) -ARCHIVE_SUBDIR_NAME = 'archive' -DEFAULT_BIN_COUNT = localConfig.REDUCED_FLY_SCAN_BINS -FIXED_VF_GAIN = localConfig.FIXED_VF_GAIN +ARCHIVE_SUBDIR_NAME = "archive" +DEFAULT_BIN_COUNT = localConfig.REDUCED_FLY_SCAN_BINS +FIXED_VF_GAIN = localConfig.FIXED_VF_GAIN MCA_CLOCK_FREQUENCY = localConfig.MCA_CLOCK_FREQUENCY -Q_MIN = localConfig.FLY_SCAN_Q_MIN -UATERM = localConfig.FLY_SCAN_UATERM -ESD_FACTOR = 0.01 # estimate dr = ESD_FACTOR * r if r.std() = 0 +Q_MIN = localConfig.FLY_SCAN_Q_MIN +UATERM = localConfig.FLY_SCAN_UATERM +ESD_FACTOR = 0.01 # estimate dr = ESD_FACTOR * r if r.std() = 0 # mbbi PV should return strings but instead returns index number # these are the values and a cross-reference to the name strings -AR_MODE_FIXED = 0 # fixed pulses (version 1) -AR_MODE_ARRAY = 1 # use PulsePositions +AR_MODE_FIXED = 0 # fixed pulses (version 1) +AR_MODE_ARRAY = 1 # use PulsePositions AR_MODE_TRAJECTORY = 2 # use trajectory points (a.k.a. waypoints) -MODENAME_XREF = { # these are the strings the PV *should* return - AR_MODE_FIXED: 'Fixed', - AR_MODE_ARRAY: 'Array', - AR_MODE_TRAJECTORY: 'TrajPts', - } +MODENAME_XREF = { # these are the strings the PV *should* return + AR_MODE_FIXED: "Fixed", + AR_MODE_ARRAY: "Array", + AR_MODE_TRAJECTORY: "TrajPts", +} # raised when HDF5 file exists but length of raw data is zero -class NoFlyScanData(IndexError): pass +class NoFlyScanData(IndexError): + pass def decode_h5py_byte_string(value): @@ -52,9 +53,9 @@ def decode_h5py_byte_string(value): Zero-dimenstional arrays are replaced with None. """ - if (isinstance(value, numpy.ndarray) and value.dtype.kind in ['O', 'S']): + if isinstance(value, numpy.ndarray) and value.dtype.kind in ["O", "S"]: if value.size > 0: - return value.astype('U').tolist() + return value.astype("U").tolist() else: return None elif isinstance(value, (bytes, numpy.bytes_)): @@ -64,7 +65,7 @@ def decode_h5py_byte_string(value): class UsaxsFlyScan(object): - ''' + """ reduce the raw data for one USAXS Fly Scan Goals @@ -181,24 +182,24 @@ class UsaxsFlyScan(object): ufs.save(hfile, 'full') ufs.save(hfile, 250) - ''' + """ def __init__(self, hdf5_file_name): if not os.path.exists(hdf5_file_name): - raise IOError('file not found: ' + hdf5_file_name) + raise IOError("file not found: " + hdf5_file_name) self.units = dict( - ar = 'degrees', - upd_ranges = '', - Q = '1/A', - R = 'none', - dR = 'none', - R_max = 'none', - AR_R_peak = 'degrees', - ar_r_peak = 'degrees', - r_peak = 'none', - ar_0 = 'degrees', - fwhm = 'degrees', + ar="degrees", + upd_ranges="", + Q="1/A", + R="none", + dR="none", + R_max="none", + AR_R_peak="degrees", + ar_r_peak="degrees", + r_peak="none", + ar_0="degrees", + fwhm="degrees", ) self.min_step_factor = 1.5 self.uaterm = UATERM @@ -207,37 +208,39 @@ def __init__(self, hdf5_file_name): self.hdf5_file_name = hdf5_file_name self.reduced = {} - def has_reduced(self, key = 'full'): - ''' + def has_reduced(self, key="full"): + """ check if the reduced dataset is available :param str|int key: name of reduced dataset (default = 'full') - ''' + """ if key not in self.reduced: return False - return 'Q' in self.reduced[key] and 'R' in self.reduced[key] + return "Q" in self.reduced[key] and "R" in self.reduced[key] def reduce(self): - '''convert raw Fly Scan data to R(Q), also get other terms''' + """convert raw Fly Scan data to R(Q), also get other terms""" if not os.path.exists(self.hdf5_file_name): - raise IOError('file not found: ' + self.hdf5_file_name) - with h5py.File(self.hdf5_file_name, 'r') as hdf: + raise IOError("file not found: " + self.hdf5_file_name) + with h5py.File(self.hdf5_file_name, "r") as hdf: pname = self.get_program_name(hdf) config_version = self.get_config_version(pname) mode_number = self.get_mode_number(hdf, config_version) # _mode_name = self.get_mode_name(mode_number) - raw = hdf['entry/flyScan'] + raw = hdf["entry/flyScan"] - wavelength = float(hdf['/entry/instrument/monochromator/wavelength'][0]) + wavelength = float(hdf["/entry/instrument/monochromator/wavelength"][0]) # ar_center = float(hdf['/entry/metadata/AR_center'][0]) - raw_clock_pulses = raw['mca1'] - raw_I0 = raw['mca2'] - raw_upd = raw['mca3'] + raw_clock_pulses = raw["mca1"] + raw_I0 = raw["mca2"] + raw_upd = raw["mca3"] if len(raw_clock_pulses) == 0: - raise NoFlyScanData('no data found in file: ' + str(self.hdf5_file_name)) + raise NoFlyScanData( + "no data found in file: " + str(self.hdf5_file_name) + ) # unused # AR_start = float(raw['AR_start'][0]) @@ -247,19 +250,21 @@ def reduce(self): raw_ar = self.get_raw_ar(hdf, mode_number) # V_f_gain = FIXED_VF_GAIN # unused - pulse_frequency = raw['mca_clock_frequency'][0] or MCA_CLOCK_FREQUENCY + pulse_frequency = raw["mca_clock_frequency"][0] or MCA_CLOCK_FREQUENCY channel_time_s = raw_clock_pulses / pulse_frequency amp_name = self.get_USAXS_PD_amplifier_name(hdf) upd_ranges = self.get_ranges(hdf, amp_name) - upd_ranges = self.apply_upd_range_change_time_mask(hdf, upd_ranges, channel_time_s) + upd_ranges = self.apply_upd_range_change_time_mask( + hdf, upd_ranges, channel_time_s + ) gains = self.get_gain(hdf, amp_name) - bkg = self.get_bkg(hdf, amp_name) + bkg = self.get_bkg(hdf, amp_name) upd_gain = get_channels_from_signals_and_ranges(gains, upd_ranges) - upd_dark = get_channels_from_signals_and_ranges(bkg, upd_ranges) + upd_dark = get_channels_from_signals_and_ranges(bkg, upd_ranges) - I0_amp_gain = float(hdf['/entry/metadata/I0AmpGain'][0]) + I0_amp_gain = float(hdf["/entry/metadata/I0AmpGain"][0]) if mode_number in (AR_MODE_ARRAY, AR_MODE_TRAJECTORY): # consequence of Aerotech HLe providing no useful data in 1st channel @@ -273,39 +278,47 @@ def reduce(self): upd_dark = upd_dark[:n] # ensure arrays have equal lengths - list_of_arrays = [raw_upd, channel_time_s, upd_dark, upd_gain, raw_ar, raw_I0] + list_of_arrays = [ + raw_upd, + channel_time_s, + upd_dark, + upd_gain, + raw_ar, + raw_I0, + ] min_n = min(map(len, list_of_arrays)) max_n = max(map(len, list_of_arrays)) - if min_n != max_n: # truncate arrays to shortest length + if min_n != max_n: # truncate arrays to shortest length n = min(min_n, max_n) # pvwatch.logMessage( " truncating all arrays to " + str(n) + " points" ) - raw_upd = raw_upd[:n] - channel_time_s = channel_time_s[:n] - upd_dark = upd_dark[:n] - upd_gain = upd_gain[:n] - raw_ar = raw_ar[:n] - raw_I0 = raw_I0[:n] - - full = calc.calc_R_Q(wavelength, # wavelength - raw_ar, # ar - channel_time_s, # seconds - raw_upd, # pd - upd_dark, # pd_bkg - upd_gain, # pd_gain - raw_I0, # I0 - I0_gain=I0_amp_gain, - # ar_center=ar_center, - ar_center=None, # compute center from R(ar) - ) - - if len(full['R']) == 0: + raw_upd = raw_upd[:n] + channel_time_s = channel_time_s[:n] + upd_dark = upd_dark[:n] + upd_gain = upd_gain[:n] + raw_ar = raw_ar[:n] + raw_I0 = raw_I0[:n] + + full = calc.calc_R_Q( + wavelength, # wavelength + raw_ar, # ar + channel_time_s, # seconds + raw_upd, # pd + upd_dark, # pd_bkg + upd_gain, # pd_gain + raw_I0, # I0 + I0_gain=I0_amp_gain, + # ar_center=ar_center, + ar_center=None, # compute center from R(ar) + ) + + if len(full["R"]) == 0: return - full['R_max'] = full['R'].max() + full["R_max"] = full["R"].max() - self.reduced = dict(full = full) + self.reduced = dict(full=full) def PSO_oscillation_correction(self, hdf, mode, num_AR): - ''' + """ compute array new AR values from 10Hz encoder sampling :param h5py.File hdf: HDF5 file object with the fly scan data @@ -323,68 +336,72 @@ def PSO_oscillation_correction(self, hdf, mode, num_AR): * interpolate AR(PSO) at each desired PSO Good luck. - ''' + """ if mode not in (AR_MODE_ARRAY, AR_MODE_TRAJECTORY): - raise ValueError('PSO_oscillation_correction: wrong mode = ' + str(mode)) + raise ValueError("PSO_oscillation_correction: wrong mode = " + str(mode)) # TODO: find better way to report this information # TODO: show scan identification, this is too generic - logger.warning(" possible vibrations during scan, re-generating AR from 10Hz sampling array") + logger.warning( + " possible vibrations during scan, re-generating AR from 10Hz sampling array" + ) pso, ar = self.getAR_10Hz_Array(hdf) linear_interpolation_func = scipy.interpolate.interp1d(pso, ar) new_ar = linear_interpolation_func(range(num_AR)) return new_ar - def rebin(self, bin_count = None): - '''generate R(Q) with a bin_count bins, save in ``self.reduced[str(bin_count)]`` dict''' + def rebin(self, bin_count=None): + """generate R(Q) with a bin_count bins, save in ``self.reduced[str(bin_count)]`` dict""" if not self.has_reduced(): self.reduce() bin_count = bin_count or self.bin_count s = str(bin_count) - Q_full = self.reduced['full']['Q'] - R_full = self.reduced['full']['R'] + Q_full = self.reduced["full"]["Q"] + R_full = self.reduced["full"]["R"] # lowest non-zero Q value > 0 or minimum acceptable Q - if 'full' not in self.reduced or len(self.reduced['full']['Q']) == 0: + if "full" not in self.reduced or len(self.reduced["full"]["Q"]) == 0: return - Qmin = max(Q_MIN, Q_full[numpy.where(Q_full > 0)].min() ) + Qmin = max(Q_MIN, Q_full[numpy.where(Q_full > 0)].min()) Qmax = 1.0001 * Q_full.max() # pick smallest Q step size from input data, scale by a factor minStep = self.min_step_factor * numpy.min(Q_full[1:] - Q_full[:-1]) # compute bin edges from ustep - Q_bins = numpy.array(ustep.ustep(Qmin, 0.0, Qmax, bin_count+1, self.uaterm, minStep).series) + Q_bins = numpy.array( + ustep.ustep(Qmin, 0.0, Qmax, bin_count + 1, self.uaterm, minStep).series + ) qVec, rVec, drVec = [], [], [] for xref in calc.bin_xref(Q_full, Q_bins): if len(xref) > 0: q = Q_full[xref] r = R_full[xref] - if r.min() <= 0: # TODO: fix this in reduce() + if r.min() <= 0: # TODO: fix this in reduce() r = numpy.ma.masked_less_equal(r, 0) q = calc.remove_masked_data(q, r.mask) r = calc.remove_masked_data(r, r.mask) if q.size > 0: - qVec.append( numpy.exp(numpy.mean(numpy.log(q))) ) - rVec.append( numpy.exp(numpy.mean(numpy.log(r))) ) + qVec.append(numpy.exp(numpy.mean(numpy.log(q)))) + rVec.append(numpy.exp(numpy.mean(numpy.log(r)))) dr = r.std() if dr == 0.0: - drVec.append( ESD_FACTOR * rVec[-1] ) + drVec.append(ESD_FACTOR * rVec[-1]) else: - drVec.append( r.std() ) + drVec.append(r.std()) reduced = dict( - Q = numpy.array(qVec), - R = numpy.array(rVec), - dR = numpy.array(drVec), + Q=numpy.array(qVec), + R=numpy.array(rVec), + dR=numpy.array(drVec), ) self.reduced[s] = reduced return reduced def read_reduced(self): - ''' + """ read any and all reduced data from the HDF5 file, return in a dictionary dictionary = { @@ -392,15 +409,15 @@ def read_reduced(self): '250': dict(Q, R, dR) '5000': dict(Q, R, dR) } - ''' + """ fields = self.units.keys() reduced = {} - with h5py.File(self.hdf5_file_name, 'r') as hdf: - entry = hdf['/entry'] + with h5py.File(self.hdf5_file_name, "r") as hdf: + entry = hdf["/entry"] for key in entry.keys(): - if key.startswith('flyScan_reduced_'): + if key.startswith("flyScan_reduced_"): nxdata = entry[key] - nxname = key[len('flyScan_reduced_'):] + nxname = key[len("flyScan_reduced_") :] d = {} for dsname in fields: if dsname in nxdata: @@ -414,8 +431,8 @@ def read_reduced(self): self.reduced = reduced return reduced - def save(self, hfile = None, key = None): - ''' + def save(self, hfile=None, key=None): + """ save the reduced data group to an HDF5 file, return filename or None if not written :param str hfile: output HDF5 file name (default: input HDF5 file) @@ -443,7 +460,7 @@ def save(self, hfile = None, key = None): :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXentry.html :see: http://download.nexusformat.org/doc/html/classes/base_classes/NXdata.html - ''' + """ # TODO: save with NXprocess/NXdata structure # TODO: link that NXdata up to NXentry level # TODO: change /NXentry@default to point to best NXdata reduced @@ -451,50 +468,51 @@ def save(self, hfile = None, key = None): key = str(key) if key not in self.reduced: return - nxname = 'flyScan_reduced_' + key + nxname = "flyScan_reduced_" + key hfile = hfile or self.hdf5_file_name ds = self.reduced[key] - with h5py.File(hfile, 'a') as hdf: - if 'default' not in hdf.attrs: - hdf.attrs['default'] = 'entry' - nxentry = eznx.openGroup(hdf, 'entry', 'NXentry') - if 'default' not in nxentry.attrs: - nxentry.attrs['default'] = nxname - nxdata = eznx.openGroup(nxentry, - nxname, - 'NXdata', - signal='R', - axes='Q', - Q_indices=0, - timestamp=calc.iso8601_datetime(), - ) + with h5py.File(hfile, "a") as hdf: + if "default" not in hdf.attrs: + hdf.attrs["default"] = "entry" + nxentry = eznx.openGroup(hdf, "entry", "NXentry") + if "default" not in nxentry.attrs: + nxentry.attrs["default"] = nxname + nxdata = eznx.openGroup( + nxentry, + nxname, + "NXdata", + signal="R", + axes="Q", + Q_indices=0, + timestamp=calc.iso8601_datetime(), + ) for key in sorted(ds.keys()): try: _ds = eznx.write_dataset(nxdata, key, ds[key]) if key in self.units: eznx.addAttributes(_ds, units=self.units[key]) except RuntimeError: - pass # TODO: reporting + pass # TODO: reporting return hfile def get_program_name(self, hdf): - if 'program_name' not in hdf['/entry']: - raise KeyError('no /entry/program_name in file: ' + self.hdf5_file_name) - return hdf['/entry/program_name'] + if "program_name" not in hdf["/entry"]: + raise KeyError("no /entry/program_name in file: " + self.hdf5_file_name) + return hdf["/entry/program_name"] def get_config_version(self, pname): - if 'config_version' in pname.attrs: - config_version = pname.attrs['config_version'] + if "config_version" in pname.attrs: + config_version = pname.attrs["config_version"] else: - config_version = '1' + config_version = "1" return config_version def get_mode_number(self, hdf, config_version): - if config_version in ('1', '1.0'): + if config_version in ("1", "1.0"): mode_number = 0 - elif config_version in ('1.1', '1.2'): - mode_number = hdf['/entry/flyScan/AR_PulseMode'][0] + elif config_version in ("1.1", "1.2"): + mode_number = hdf["/entry/flyScan/AR_PulseMode"][0] else: hdf.close() raise ValueError( @@ -508,43 +526,47 @@ def get_mode_name(self, mode_number): _mode_name = MODENAME_XREF[mode_number] else: raise ValueError( - 'Unexpected /entry/flyScan/AR_PulseMode value = ' + str(mode_number) + "Unexpected /entry/flyScan/AR_PulseMode value = " + str(mode_number) ) return _mode_name def get_raw_ar(self, hdf, mode_number): - raw = hdf['entry/flyScan'] - raw_num_points = int(raw['AR_pulses'][0]) - AR_start = float(raw['AR_start'][0]) + raw = hdf["entry/flyScan"] + raw_num_points = int(raw["AR_pulses"][0]) + AR_start = float(raw["AR_start"][0]) - if mode_number == AR_MODE_FIXED: # often more than ten thousand points - AR_increment = float(raw['AR_increment'][0]) + if mode_number == AR_MODE_FIXED: # often more than ten thousand points + AR_increment = float(raw["AR_increment"][0]) raw_ar = AR_start - numpy.arange(raw_num_points) * AR_increment PSO_oscillations_found = False elif mode_number in (AR_MODE_ARRAY, AR_MODE_TRAJECTORY): - keymap = {AR_MODE_ARRAY: '/entry/flyScan/AR_PulsePositions', # a few thousand points - AR_MODE_TRAJECTORY: '/entry/flyScan/AR_waypoints'} # a few hundred points + keymap = { + AR_MODE_ARRAY: "/entry/flyScan/AR_PulsePositions", # a few thousand points + AR_MODE_TRAJECTORY: "/entry/flyScan/AR_waypoints", + } # a few hundred points raw_ar = numpy.array(hdf[keymap[mode_number]]) raw_ar = raw_ar - raw_ar[0] + AR_start if len(raw_ar) > raw_num_points: - raw_ar = raw_ar[:raw_num_points] # truncate unused bins, if needed + raw_ar = raw_ar[:raw_num_points] # truncate unused bins, if needed # note: Aerotech HLe system does not report any data for first channel. # Shift data to have mean AR value for each point, # not the end of the AR value, when the system advanced to next point. - raw_ar = (raw_ar[1:] + raw_ar[:-1])/2 # midpoint of each interval - raw_clock_pulses = raw['mca1'] + raw_ar = (raw_ar[1:] + raw_ar[:-1]) / 2 # midpoint of each interval + raw_clock_pulses = raw["mca1"] PSO_oscillations_found = len(raw_clock_pulses) != len(raw_ar) if PSO_oscillations_found: # Aerotech's Position Synchronized Output (PSO) feature - raw_ar = self.PSO_oscillation_correction(hdf, mode_number, len(raw_clock_pulses)) + raw_ar = self.PSO_oscillation_correction( + hdf, mode_number, len(raw_clock_pulses) + ) return raw_ar def getAR_10Hz_Array(self, hdf): - ''' + """ return arrays of AR encoder v. PSO pulse :param h5py.File hdf: HDF5 file object with the fly scan data @@ -552,9 +574,9 @@ def getAR_10Hz_Array(self, hdf): read the AR encoder positions sampled in EPICS at 10Hz these are recorded with the corresponding PSO pulse number - ''' - ch_angle = hdf['/entry/flyScan/changes_AR_angle'] - ch_PSOpulse = hdf['/entry/flyScan/changes_AR_PSOpulse'] + """ + ch_angle = hdf["/entry/flyScan/changes_AR_angle"] + ch_PSOpulse = hdf["/entry/flyScan/changes_AR_PSOpulse"] # for every PSO channel, make a list of all ar values # use a temporary dictionary for this @@ -565,52 +587,56 @@ def getAR_10Hz_Array(self, hdf): y = ch_angle[index] if x not in channel: channel[x] = [] - if len(channel)>1 and x == 0.0: - break # all useful channels received, ignore remaining buffer + if len(channel) > 1 and x == 0.0: + break # all useful channels received, ignore remaining buffer channel[x].append(y) pso = sorted(channel.keys()) if pso[0] != 0.0: - raise ValueError('1st PSO pulse in 10Hz array is not zero') + raise ValueError("1st PSO pulse in 10Hz array is not zero") # first PSO channel 0 may be repeated, keep the last one channel[0.0] = channel[0.0][-1] # last PSO channel may be repeated, keep the first one - index = pso[-1] # index of the last channel + index = pso[-1] # index of the last channel channel[index] = channel[index][0] # average all the other channels, x:PSO, y:AR for x, y in channel.items(): - if isinstance(y, list): # list items have not been handled yet + if isinstance(y, list): # list items have not been handled yet if len(y) == 1: channel[x] = y[0] else: channel[x] = numpy.array(y).mean() - ar = list(map(lambda xx: channel[xx], pso)) # map() is faster than this: [channel[_] for _ in pso] + ar = list( + map(lambda xx: channel[xx], pso) + ) # map() is faster than this: [channel[_] for _ in pso] return numpy.array(pso), numpy.array(ar) def get_USAXS_PD_amplifier_name(self, hdf): - '''return the name of the chosen USAXS photodiode amplifier''' - base = '/entry/flyScan/upd_flyScan_amplifier' + """return the name of the chosen USAXS photodiode amplifier""" + base = "/entry/flyScan/upd_flyScan_amplifier" amp_index = hdf[base][0] - labels = {0: base+'_ZNAM', 1: base+'_ONAM'} + labels = {0: base + "_ZNAM", 1: base + "_ONAM"} return decode_h5py_byte_string(hdf[labels[amp_index]][0]) def get_range_changes(self, hdf, ampName): - '''get the arrays of range change information for the named amplifier''' + """get the arrays of range change information for the named amplifier""" + def get_key(key): return list(map(int, hdf[f"{base}{key}"][()])) - #num_channels = hdf['/entry/flyScan/AR_pulses'][0] # planned length + + # num_channels = hdf['/entry/flyScan/AR_pulses'][0] # planned length ampName = decode_h5py_byte_string(ampName) - base = f'/entry/flyScan/changes_{ampName}_' - arr_channel = get_key('mcsChan') - arr_requested = get_key('ampReqGain') - arr_actual = get_key('ampGain') + base = f"/entry/flyScan/changes_{ampName}_" + arr_channel = get_key("mcsChan") + arr_requested = get_key("ampReqGain") + arr_actual = get_key("ampGain") return arr_channel, arr_requested, arr_actual def get_ranges(self, hdf, identifier): - ''' + """ return a numpy masked array of detector range changes mask is applied during a range change when requested != actual @@ -618,76 +644,76 @@ def get_ranges(self, hdf, identifier): :param obj hdf: opened HDF5 file instance :param str identifier: amplifier name, as stored in the HDF5 file - ''' + """ def assign_range_value(start, end, range_value): - '''short-hand assignment''' + """short-hand assignment""" num_values = end - start - if num_values >= 0: # define the valid range + if num_values >= 0: # define the valid range ranges[start:end] = numpy.zeros((num_values,)) + range_value changes = self.get_range_changes(hdf, identifier) - #num_channels = hdf['/entry/flyScan/AR_pulses'][0] # planned length - num_channels = len(hdf['/entry/flyScan/mca1']) # actual length + # num_channels = hdf['/entry/flyScan/AR_pulses'][0] # planned length + num_channels = len(hdf["/entry/flyScan/mca1"]) # actual length ranges = numpy.arange(int(num_channels)) mask_value = -1 last = None for chan, requested, actual in zip(*changes): if last is not None: - if chan < last['chan']: # end of Fly Scan range change data + if chan < last["chan"]: # end of Fly Scan range change data break if requested == actual: # last range change complete -- mark the range change as invalid - assign_range_value(last['chan'], chan, mask_value) + assign_range_value(last["chan"], chan, mask_value) else: # next range change starting -- mark the range for these channels - assign_range_value(last['chan']+1, chan, last['actual']) + assign_range_value(last["chan"] + 1, chan, last["actual"]) if chan < num_channels: ranges[chan] = mask_value last = dict(chan=chan, requested=requested, actual=actual) - if last is not None: # mark the final range - assign_range_value(last['chan']+1, num_channels, last['actual']) + if last is not None: # mark the final range + assign_range_value(last["chan"] + 1, num_channels, last["actual"]) - ranges[-1] = mask_value # assume this, for good measure + ranges[-1] = mask_value # assume this, for good measure return numpy.ma.masked_less_equal(ranges, mask_value) def get_gain(self, hdf, amplifier): - ''' + """ get gains for named amplifier from the HDF5 file :param obj hdf: opened HDF5 file instance :param str amplifier: amplifier name, as stored in the HDF5 file - ''' - base = '/entry/metadata/' + amplifier + '_gain' - gain = list(map(lambda x: hdf[base+str(x)][0], range(5))) + """ + base = "/entry/metadata/" + amplifier + "_gain" + gain = list(map(lambda x: hdf[base + str(x)][0], range(5))) return gain def get_bkg(self, hdf, amplifier): - ''' + """ get backgrounds for named amplifier from the HDF5 file :param obj hdf: opened HDF5 file instance :param str amplifier: amplifier name, as stored in the HDF5 file - ''' - base = '/entry/metadata/' + amplifier + '_bkg' - bkg = list(map(lambda x: hdf[base+str(x)][0], range(5))) + """ + base = "/entry/metadata/" + amplifier + "_bkg" + bkg = list(map(lambda x: hdf[base + str(x)][0], range(5))) return bkg def apply_upd_range_change_time_mask(self, hdf, upd_ranges, channel_time_s): - ''' + """ apply mask for specified time after a range change :param obj hdf: open FlyScan data file (h5py File object) :param obj upd_ranges: photodiode amplifier range (numpy masked ndarray) :param obj channel_time_s: measurement time in each channel, s (numpy ndarray) - ''' + """ # :param [float] mask_times: elapsed time after after range change # in which data should be masked # mask is applied to pd_ranges - base = '/entry/metadata/upd_amp_change_mask_time' + base = "/entry/metadata/upd_amp_change_mask_time" mask_times = list(map(lambda r: float(hdf[base + str(r)][0]), range(5))) amp_name = self.get_USAXS_PD_amplifier_name(hdf) changes = self.get_range_changes(hdf, amp_name) @@ -701,36 +727,38 @@ def apply_upd_range_change_time_mask(self, hdf, upd_ranges, channel_time_s): continue if requested != actual: if i < num_channels: - upd_ranges[i] = numpy.ma.masked # mask this point + upd_ranges[i] = numpy.ma.masked # mask this point continue timer = mask_times[int(actual)] while timer > 0 and i < length: - upd_ranges[i] = numpy.ma.masked # mask this point - timer = max(0, timer - channel_time_s[i]) # decrement the time of this channel + upd_ranges[i] = numpy.ma.masked # mask this point + timer = max( + 0, timer - channel_time_s[i] + ) # decrement the time of this channel i += 1 return upd_ranges def mean_sigma(self, x, w): - ''' + """ return mean and standard deviation of weighted x array :param numpy.array x: array to be averaged :param numpy.array w: array of weights - ''' + """ xw = x * w sumX = numpy.sum(xw) sumXX = numpy.sum(xw * xw) sumWt = numpy.sum(w) centroid = sumX / sumWt try: - sigma = math.sqrt( (sumXX - (sumX*sumX)/sumWt) / (sumWt - 1) ) + sigma = math.sqrt((sumXX - (sumX * sumX) / sumWt) / (sumWt - 1)) except Exception: sigma = 0.0 return centroid, sigma def make_archive(self): - ''' + """ archive the original data before writing new items to it This method checks for the presence of such a file in @@ -738,7 +766,7 @@ def make_archive(self): this method does nothing. If not found, this method creates the subdirectory (if necessary) and copies the HDF5 to that subdirectory and makes the HDF5 file there to be read-only. - ''' + """ result = None path, hfile = os.path.split(self.hdf5_file_name) archive_dir = os.path.join(path, ARCHIVE_SUBDIR_NAME) @@ -746,16 +774,23 @@ def make_archive(self): if not os.path.exists(archive_dir): os.mkdir(archive_dir) if not os.path.exists(archive_file): - shutil.copy2(self.hdf5_file_name, archive_file) # copy hfile to archive_file + shutil.copy2( + self.hdf5_file_name, archive_file + ) # copy hfile to archive_file mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH - os.chmod(archive_file, mode) # make archive_file read-only to all + os.chmod(archive_file, mode) # make archive_file read-only to all result = archive_file return result def get_channels_from_signals_and_ranges(signal, ranges): - '''convert signal and upd amplifier ranges to value for channel''' - channels = numpy.array([0,] + signal)[ranges.data+1] + """convert signal and upd amplifier ranges to value for channel""" + channels = numpy.array( + [ + 0, + ] + + signal + )[ranges.data + 1] channels = numpy.ma.masked_less_equal(channels, 0) return channels @@ -764,57 +799,54 @@ def get_channels_from_signals_and_ranges(signal, ranges): def get_user_options(): - '''parse the command line for the user options''' + """parse the command line for the user options""" import argparse - parser = argparse.ArgumentParser(prog='reduceUsaxsFlyScan', description=__doc__) - parser.add_argument('hdf5_file', - action='store', - help="NeXus/HDF5 data file name") - msg = 'how many bins in output R(Q)?' - msg += ' (default = %d)' % DEFAULT_BIN_COUNT - parser.add_argument('-n', - '--num_bins', - dest='num_bins', - type=int, - default=DEFAULT_BIN_COUNT, - help=msg) - msg = 'output file name?' - msg += ' (default = input HDF5 file)' - parser.add_argument('-o', - '--output_file', - dest='output_file', - type=str, - default='', - help=msg) - parser.add_argument('-V', - '--version', - action='version', - version='$Id$') - - parser.add_argument('--recompute-full', - dest='recompute_full', - action='store_true', - default=False, - help='(re)compute full R(Q): implies --recompute-rebinning') - - parser.add_argument('--recompute-rebinned', - dest='recompute_rebinned', - action='store_true', - default=False, - help='(re)compute rebinned R(Q)') - - msg = 'do NOT archive the original file before saving R(Q)' - parser.add_argument('--no-archive', - dest='no_archive', - action='store_true', - default=False, - help=msg) + + parser = argparse.ArgumentParser(prog="reduceUsaxsFlyScan", description=__doc__) + parser.add_argument("hdf5_file", action="store", help="NeXus/HDF5 data file name") + msg = "how many bins in output R(Q)?" + msg += " (default = %d)" % DEFAULT_BIN_COUNT + parser.add_argument( + "-n", + "--num_bins", + dest="num_bins", + type=int, + default=DEFAULT_BIN_COUNT, + help=msg, + ) + msg = "output file name?" + msg += " (default = input HDF5 file)" + parser.add_argument( + "-o", "--output_file", dest="output_file", type=str, default="", help=msg + ) + parser.add_argument("-V", "--version", action="version", version="$Id$") + + parser.add_argument( + "--recompute-full", + dest="recompute_full", + action="store_true", + default=False, + help="(re)compute full R(Q): implies --recompute-rebinning", + ) + + parser.add_argument( + "--recompute-rebinned", + dest="recompute_rebinned", + action="store_true", + default=False, + help="(re)compute rebinned R(Q)", + ) + + msg = "do NOT archive the original file before saving R(Q)" + parser.add_argument( + "--no-archive", dest="no_archive", action="store_true", default=False, help=msg + ) return parser.parse_args() def command_line_interface(): - '''standard command-line interface''' + """standard command-line interface""" cmd_args = get_user_options() if len(cmd_args.output_file) > 0: @@ -824,43 +856,49 @@ def command_line_interface(): s_num_bins = str(cmd_args.num_bins) needs_calc = {} - pvwatch.logMessage( "Reading USAXS FlyScan data file: " + cmd_args.hdf5_file ) + pvwatch.logMessage("Reading USAXS FlyScan data file: " + cmd_args.hdf5_file) scan = UsaxsFlyScan(cmd_args.hdf5_file) # 2015-06-08,prj: no need for archives now - #if cmd_args.no_archive: + # if cmd_args.no_archive: # print ' skipping check for archived original file' - #else: + # else: # afile = scan.make_archive() # if afile is not None: # print ' archived original file to ' + afile - pvwatch.logMessage( ' checking for previously-saved R(Q)' ) + pvwatch.logMessage(" checking for previously-saved R(Q)") scan.read_reduced() - needs_calc['full'] = not scan.has_reduced('full') + needs_calc["full"] = not scan.has_reduced("full") if cmd_args.recompute_full: - needs_calc['full'] = True + needs_calc["full"] = True needs_calc[s_num_bins] = not scan.has_reduced(s_num_bins) if cmd_args.recompute_rebinned: needs_calc[s_num_bins] = True # needs_calc['250'] = True # FIXME: developer only - if needs_calc['full']: - pvwatch.logMessage(' reducing FlyScan to R(Q)') + if needs_calc["full"]: + pvwatch.logMessage(" reducing FlyScan to R(Q)") scan.reduce() - if 'full' not in scan.reduced: - pvwatch.logMessage( ' no reduced R(Q) when checking for previously-saved ' + output_filename) + if "full" not in scan.reduced: + pvwatch.logMessage( + " no reduced R(Q) when checking for previously-saved " + + output_filename + ) return - pvwatch.logMessage( ' saving reduced R(Q) to ' + output_filename) - scan.save(cmd_args.hdf5_file, 'full') + pvwatch.logMessage(" saving reduced R(Q) to " + output_filename) + scan.save(cmd_args.hdf5_file, "full") needs_calc[s_num_bins] = True if needs_calc[s_num_bins]: - pvwatch.logMessage( ' rebinning R(Q) (from %d) to %d points' % (scan.reduced['full']['Q'].size, cmd_args.num_bins) ) + pvwatch.logMessage( + " rebinning R(Q) (from %d) to %d points" + % (scan.reduced["full"]["Q"].size, cmd_args.num_bins) + ) scan.rebin(cmd_args.num_bins) - pvwatch.logMessage( ' saving rebinned R(Q) to ' + output_filename ) + pvwatch.logMessage(" saving rebinned R(Q) to " + output_filename) scan.save(cmd_args.hdf5_file, s_num_bins) return scan -if __name__ == '__main__': +if __name__ == "__main__": command_line_interface() diff --git a/scanplots.py b/scanplots.py index 35c93f1..b77438e 100755 --- a/scanplots.py +++ b/scanplots.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -'''make plots of the last *n* scans in the scanlog''' +"""make plots of the last *n* scans in the scanlog""" import datetime @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) # logger.setLevel(logging.DEBUG) -SCANLOG = '/share1/local_livedata/scanlog.xml' +SCANLOG = "/share1/local_livedata/scanlog.xml" NUMBER_SCANS_TO_PLOT = 5 scan_cache = None spec_file_cache = None @@ -27,7 +27,8 @@ class ScanCache(object): - '''cache of scans already parsed''' + """cache of scans already parsed""" + # singleton class def __init__(self): @@ -36,7 +37,7 @@ def __init__(self): def add(self, scan): key = scan.safe_id if key in self.db: - raise KeyError(key + ' already in ScanCache') + raise KeyError(key + " already in ScanCache") self.db[key] = scan def get(self, key): @@ -52,11 +53,12 @@ def delete(self, key): class SpecFileObject(object): - '''contents and metadata about a SPEC data file on disk''' + """contents and metadata about a SPEC data file on disk""" def __init__(self, specfile): if os.path.exists(specfile): from spec2nexus.spec import SpecDataFile + self.filename = specfile stat = os.stat(specfile) self.size = stat.st_size @@ -65,15 +67,16 @@ def __init__(self, specfile): class SpecFileCache(object): - '''cache of SPEC data files already parsed''' + """cache of SPEC data files already parsed""" + # singleton class def __init__(self): - self.db = {} # index by file name + self.db = {} # index by file name def _add_(self, specfile): if specfile in self.db: - raise KeyError(specfile + ' already in SpecFileCache') + raise KeyError(specfile + " already in SpecFileCache") self.db[specfile] = SpecFileObject(specfile) return self.db[specfile] @@ -101,7 +104,7 @@ def _del_(self, key): class Scan(object): - '''details about a scan that could be plotted''' + """details about a scan that could be plotted""" def __init__(self): self.title = None @@ -126,7 +129,7 @@ def setFileParms(self, title, data_file, scan_type, scan_number, scan_id): self.safe_id = self.makeSafeId() def makeSafeId(self): - '''for use as HDF5 dataset name in HDF5 data file''' + """for use as HDF5 dataset name in HDF5 data file""" if self.spec_scan is None: self.getData() @@ -134,11 +137,11 @@ def makeSafeId(self): logger.debug("No scan %d in file %s" % (self.scan_number, self.data_file)) return None - epoch = datetime.datetime.strptime(self.spec_scan.date, '%c') - fmt = '%Y_%m_%d__%H_%M_%S' + epoch = datetime.datetime.strptime(self.spec_scan.date, "%c") + fmt = "%Y_%m_%d__%H_%M_%S" scan_date_time_stamp = datetime.datetime.strftime(epoch, fmt) - self.safe_id = scan_date_time_stamp + '__%06d' % self.scan_number + self.safe_id = scan_date_time_stamp + "__%06d" % self.scan_number return self.safe_id def getSpecScan(self): @@ -161,53 +164,53 @@ def getData(self): def plottable_scan_node(scan_node): - ''' + """ Determine if the scan_node (XML) is plottable :param obj scan_node: scan_node entry (instance of XML _Element node) :return obj: instance of Scan or None - ''' + """ global scan_cache global spec_file_cache scan = None - filename = scan_node.find('file').text.strip() + filename = scan_node.find("file").text.strip() if not os.path.exists(filename): return scan - scan_type = scan_node.attrib['type'] + scan_type = scan_node.attrib["type"] # TODO: #23 similar code blocks could be combined - if scan_type in ('uascan', 'sbuascan'): - if scan_node.attrib['state'] in ('scanning', 'complete'): + if scan_type in ("uascan", "sbuascan"): + if scan_node.attrib["state"] in ("scanning", "complete"): if os.path.exists(filename): scan = Scan() scan.setFileParms( - scan_node.find('title').text.strip(), + scan_node.find("title").text.strip(), filename, scan_type, - scan_node.attrib['number'], - scan_node.attrib['id'], + scan_node.attrib["number"], + scan_node.attrib["id"], ) scan.getData() - elif scan_type in ('FlyScan', 'sbFlyScan', 'Flyscan'): - if scan_node.attrib['state'] in ('complete', ): + elif scan_type in ("FlyScan", "sbFlyScan", "Flyscan"): + if scan_node.attrib["state"] in ("complete",): # specfiledir = os.path.dirname(filename) scan = Scan() scan.setFileParms( - scan_node.find('title').text.strip(), + scan_node.find("title").text.strip(), filename, scan_type, - scan_node.attrib['number'], - scan_node.attrib['id'], + scan_node.attrib["number"], + scan_node.attrib["id"], ) if scan.spec_scan is None: - return None # reached a dead end here + return None # reached a dead end here scan.getData() # get the HDF5 file name from the SPEC file (no search needed) spec = spec_file_cache.get(filename) - spec_scan = spec.getScan(str(scan_node.attrib['number'])) + spec_scan = spec.getScan(str(scan_node.attrib["number"])) if not spec_scan.__interpreted__: try: spec_scan.interpret() @@ -218,29 +221,33 @@ def plottable_scan_node(scan_node): scan.scan_number, scan.scan_type, str(exc), - ) + ) return hdf5_file = get_Hdf5_Data_file_Name(spec_scan) if hdf5_file is None or not os.path.exists(hdf5_file): - scan = None # bail out, no HDF5 file found + scan = None # bail out, no HDF5 file found else: # actual data file scan_node.data_file = hdf5_file - elif scan_type in ('pinSAXS', 'SAXS', 'WAXS',): - if scan_node.attrib['state'] in ('complete', ): + elif scan_type in ( + "pinSAXS", + "SAXS", + "WAXS", + ): + if scan_node.attrib["state"] in ("complete",): # specfiledir = os.path.dirname(filename) scan = Scan() scan.setFileParms( - scan_node.find('title').text.strip(), + scan_node.find("title").text.strip(), filename, scan_type, - scan_node.attrib['number'], - scan_node.attrib['id'], + scan_node.attrib["number"], + scan_node.attrib["id"], ) if scan.spec_scan is None: - return None # reached a dead end here + return None # reached a dead end here scan.getData() if not scan.spec_scan.__interpreted__: try: @@ -252,7 +259,7 @@ def plottable_scan_node(scan_node): scan.scan_number, scan.scan_type, str(exc), - ) + ) return hdf5_file = get_Hdf5_Data_file_Name(scan.spec_scan) @@ -260,7 +267,7 @@ def plottable_scan_node(scan_node): # actual data file scan_node.data_file = hdf5_file else: - scan = None # bail out, no HDF5 file found + scan = None # bail out, no HDF5 file found if scan is not None: scan_cache.add(scan) @@ -269,13 +276,13 @@ def plottable_scan_node(scan_node): def last_n_scans(xml_log_file, number_scans): - '''get list of last *n* plottable scan objects, chronological, most recent last''' + """get list of last *n* plottable scan objects, chronological, most recent last""" xml_doc = xmlSupport.openScanLogFile(xml_log_file) if xml_doc is None: return [] scans = [] - node_list = xml_doc.findall('scan') or [] + node_list = xml_doc.findall("scan") or [] for scan_node in reversed(node_list): msg = scan_node.tag for k in "state type id".split(): @@ -296,29 +303,29 @@ def get_Hdf5_Data_file_Name(scan): scan_command_parts = scan.scanCmd.strip().split() if hasattr(scan, "MD"): # HDF5 data file (Flyscan, or area detector) written from Bluesky plan - #MD hdf5_file = blank_0755.h5 - #MD hdf5_path = /share1/USAXS_data/2019-05/05_02_test_usaxs + # MD hdf5_file = blank_0755.h5 + # MD hdf5_path = /share1/USAXS_data/2019-05/05_02_test_usaxs fname = scan.MD.get("hdf5_file") if fname is None: - return "no spec data file" # missing in an early version + return "no spec data file" # missing in an early version path = scan.MD["hdf5_path"] elif len(scan_command_parts) > 6 and scan_command_parts[0] in ("SAXS", "WAXS"): # EPICS area detector data file written from SPEC macros - #S 10 WAXS ./04_02_Cheng_INL_waxs/LaB6_0002.hdf 0 0 1 20 1 - #S 15 SAXS ./04_02_Cheng_INL_saxs/BlankHeater_0003.hdf 0 0 1 20 1 + # S 10 WAXS ./04_02_Cheng_INL_waxs/LaB6_0002.hdf 0 0 1 20 1 + # S 15 SAXS ./04_02_Cheng_INL_saxs/BlankHeater_0003.hdf 0 0 1 20 1 fname = scan_command_parts[1] path = os.path.dirname(scan.header.parent.fileName) - elif len(scan_command_parts) > 3 and scan_command_parts[0] in ("FlyScan", ): + elif len(scan_command_parts) > 3 and scan_command_parts[0] in ("FlyScan",): # FlyScan HDF5 data file written from SPEC macros - #C Tue Apr 02 20:49:39 2019. FlyScan file name = ./04_02_Cheng_INL_usaxs/2104H_1020C_47min_0013.h5. - key_string = 'FlyScan file name = ' + # C Tue Apr 02 20:49:39 2019. FlyScan file name = ./04_02_Cheng_INL_usaxs/2104H_1020C_47min_0013.h5. + key_string = "FlyScan file name = " fname = None for comment in scan.comments: index = comment.find(key_string) if index > 0: - fname = comment[index + len(key_string):-1] + fname = comment[index + len(key_string) : -1] break if fname is None: raise ValueError("HDF5 file name not found") @@ -337,29 +344,31 @@ def get_USAXS_FlyScan_Data(scan_obj): try: fly = reduceFlyData.UsaxsFlyScan(hdf5_file) # checks if file exists - #fly.make_archive() - fly.reduce() # open the file in this step - fly.save(hdf5_file, 'full') - if 'full' not in fly.reduced: + # fly.make_archive() + fly.reduce() # open the file in this step + fly.save(hdf5_file, "full") + if "full" not in fly.reduced: return None fly.rebin(localConfig.REDUCED_FLY_SCAN_BINS) fly.save(hdf5_file, str(localConfig.REDUCED_FLY_SCAN_BINS)) except IOError: - return None # file may not be available yet for reading if fly scan is still going + return ( + None # file may not be available yet for reading if fly scan is still going + ) except KeyError as exc: - logger.info('HDF5 file:' + hdf5_file) + logger.info("HDF5 file:" + hdf5_file) raise KeyError(exc) except reduceFlyData.NoFlyScanData as _exc: logger.info(str(_exc)) - return None # HDF5 file exists but length of raw data is zero + return None # HDF5 file exists but length of raw data is zero fname = os.path.splitext(os.path.split(hdf5_file)[-1])[0] - title = 'S%s %s (%s)' % (str(scan.scanNum), fname, 'fly') + title = "S%s %s (%s)" % (str(scan.scanNum), fname, "fly") numbins_str = str(localConfig.REDUCED_FLY_SCAN_BINS) if numbins_str not in fly.reduced: return None rebinned = fly.reduced[numbins_str] - entry = dict(qVec=rebinned['Q'], rVec=rebinned['R'], title=title) + entry = dict(qVec=rebinned["Q"], rVec=rebinned["R"], title=title) return entry @@ -370,13 +379,13 @@ def get_AreaDetector_Data(scan_obj): bins = dict(SAXS=250, WAXS=800)[scanMacro] scan_name = os.path.splitext(os.path.split(hdf5_file)[-1])[0] - ad = reduceAreaDetector.reduce_area_detector_data(hdf5_file, bins) - title = 'S%s %s (%s)' % (str(scan.scanNum), scan_name, scanMacro) + ad = reduceAreaDetector.reduce_area_detector_data(hdf5_file, bins) + title = "S%s %s (%s)" % (str(scan.scanNum), scan_name, scanMacro) rebinned = ad.reduced.get(str(bins)) if rebinned is None: raise KeyError("No rebinned %s data of length %d" % (scanMacro, bins)) rebinned = ad.reduced[str(bins)] - entry = dict(qVec=rebinned['Q'], rVec=rebinned['R'], title=title) + entry = dict(qVec=rebinned["Q"], rVec=rebinned["R"], title=title) return entry @@ -388,21 +397,22 @@ def get_USAXS_uascan_ScanData(scan, ar_center=None): title = scan.spec_scan.comments[0] usaxs = calc.reduce_uascan(scan.spec_scan) - usaxs['qVec'] = usaxs.pop('Q') - usaxs['rVec'] = usaxs.pop('R') - usaxs['title'] = "S{} {}".format(scan.scan_number, title) + usaxs["qVec"] = usaxs.pop("Q") + usaxs["rVec"] = usaxs.pop("R") + usaxs["title"] = "S{} {}".format(scan.scan_number, title) return usaxs def format_as_mpl_data_one(scan): - '''prepare one USAXS scan for plotting with MatPlotLib''' + """prepare one USAXS scan for plotting with MatPlotLib""" try: - Q = map(float, scan['qVec']) - I = map(float, scan['rVec']) + Q = map(float, scan["qVec"]) + I = map(float, scan["rVec"]) except TypeError: - if scan is None: return None - Q = scan['qVec'] - I = scan['rVec'] + if scan is None: + return None + Q = scan["qVec"] + I = scan["rVec"] Q = numpy.ma.masked_less_equal(numpy.abs(Q), 0) I = numpy.ma.masked_less_equal(I, 0) mask = numpy.ma.mask_or(Q.mask, I.mask) @@ -410,7 +420,7 @@ def format_as_mpl_data_one(scan): mpl_ds = plot_mpl.Plottable_USAXS_Dataset() mpl_ds.Q = numpy.ma.masked_array(data=Q, mask=mask).compressed() mpl_ds.I = numpy.ma.masked_array(data=I, mask=mask).compressed() - mpl_ds.label = scan['title'] + mpl_ds.label = scan["title"] if len(mpl_ds.Q) > 0 and len(mpl_ds.I) > 0: return mpl_ds @@ -434,7 +444,7 @@ def get_USAXS_data(cache): scan_obj.spec_scan.scan_number, scan_obj.spec_scan.scan_type, str(exc), - ) + ) return entry = get_USAXS_uascan_ScanData(scan_obj) elif scanMacro in ("FlyScan", "sbFlyScan", "Flyscan"): @@ -442,7 +452,8 @@ def get_USAXS_data(cache): elif scanMacro in ("SAXS", "WAXS"): entry = get_AreaDetector_Data(scan_obj) mpl_ds = format_as_mpl_data_one(entry) - if mpl_ds is None: continue + if mpl_ds is None: + continue if len(mpl_ds.Q) > 0 and len(mpl_ds.I) > 0: mpl_datasets.append(mpl_ds) logger.debug("{} = {}".format(key, scanMacro)) @@ -450,20 +461,21 @@ def get_USAXS_data(cache): def save_temporary_test_data(mpl_datasets): - '''save temporary test data sets''' + """save temporary test data sets""" from spec2nexus import eznx - hdf5_file = os.path.join(localConfig.LOCAL_WWW_LIVEDATA_DIR, 'testdata.h5') + + hdf5_file = os.path.join(localConfig.LOCAL_WWW_LIVEDATA_DIR, "testdata.h5") f = eznx.makeFile(hdf5_file) for i, ds in enumerate(mpl_datasets): - nxentry = eznx.makeGroup(f, 'entry_' + str(i), 'NXentry') + nxentry = eznx.makeGroup(f, "entry_" + str(i), "NXentry") eznx.makeDataset(nxentry, "title", ds.label) - nxdata = eznx.makeGroup(nxentry, 'data', 'NXdata', signal='R', axes='Q') - eznx.makeDataset(nxdata, "Q", ds.Q, units='1/A') - eznx.makeDataset(nxdata, "R", ds.I, units='a.u.') + nxdata = eznx.makeGroup(nxentry, "data", "NXdata", signal="R", axes="Q") + eznx.makeDataset(nxdata, "Q", ds.Q, units="1/A") + eznx.makeDataset(nxdata, "R", ds.I, units="a.u.") f.close() -def main(n = None, cp=False): +def main(n=None, cp=False): global scan_cache global spec_file_cache @@ -471,12 +483,12 @@ def main(n = None, cp=False): spec_file_cache = SpecFileCache() if n is None: n = NUMBER_SCANS_TO_PLOT - scan_list = last_n_scans(SCANLOG, n) # updates scan_cache & spec_file_cache + scan_list = last_n_scans(SCANLOG, n) # updates scan_cache & spec_file_cache logger.debug("scan list: " + ", ".join(map(str, scan_list))) local_plot = os.path.join( - localConfig.LOCAL_WWW_LIVEDATA_DIR, - localConfig.LOCAL_PLOTFILE) + localConfig.LOCAL_WWW_LIVEDATA_DIR, localConfig.LOCAL_PLOTFILE + ) mpl_datasets = get_USAXS_data(scan_cache) if len(mpl_datasets): @@ -490,7 +502,7 @@ def main(n = None, cp=False): wwwServerTransfers.nfsCpToWebServer(local_plot, www_plot) -def pr20(n = None, cp=False): +def pr20(n=None, cp=False): """ test code @@ -516,15 +528,15 @@ def pr20(n = None, cp=False): spec_file_cache = SpecFileCache() if n is None: n = NUMBER_SCANS_TO_PLOT - last_n_scans(SCANLOG, n) # updates scan_cache & spec_file_cache + last_n_scans(SCANLOG, n) # updates scan_cache & spec_file_cache mpl_datasets = get_USAXS_data(scan_cache) logger.debug("scan list: ") for i, s in enumerate(mpl_datasets): - logger.debug("%d %d %s" % (i+1, len(s.Q), s.label)) + logger.debug("%d %d %s" % (i + 1, len(s.Q), s.label)) if __name__ == "__main__": - #last_n_scans(SCANLOG, NUMBER_SCANS_TO_PLOT) + # last_n_scans(SCANLOG, NUMBER_SCANS_TO_PLOT) main() diff --git a/ustep.py b/ustep.py index 18fe7ee..7075abd 100755 --- a/ustep.py +++ b/ustep.py @@ -1,16 +1,18 @@ #!/usr/bin/env python -''' +""" Step-Size Algorithm for Bonse-Hart Ultra-Small-Angle Scattering Instruments :see: http://usaxs.xray.aps.anl.gov/docs/ustep/index.html -''' +""" import logging + logger = logging.getLogger(__name__) + class ustep(object): - ''' + """ find the series of positions for the USAXS :param float start: required first position of list @@ -21,7 +23,7 @@ class ustep(object): :param float minStep: smallest allowed step size :param float factor: :math:`k`, multiplying factor (computed internally) :param [float] series: computed list of positions - ''' + """ def __init__(self, start, center, finish, numPts, exponent, minStep): self.start = start @@ -35,12 +37,12 @@ def __init__(self, start, center, finish, numPts, exponent, minStep): self.factor = self.find_factor() def find_factor(self): - ''' + """ Determine the factor that will make a series with the specified parameters. This method improves on find_factor_simplistic() by choosing next choice for factor from recent history. - ''' + """ def assess(factor): self.make_series(factor) @@ -49,7 +51,7 @@ def assess(factor): span_target = abs(self.finish - self.start) span_precision = abs(self.minStep) * 0.2 - factor = abs(self.finish-self.start) / (self.numPts -1) + factor = abs(self.finish - self.start) / (self.numPts - 1) span_diff = assess(factor) f = [factor, factor] d = [span_diff, span_diff] @@ -57,7 +59,7 @@ def assess(factor): # first make certain that d[0] < 0 and d[1] > 0, expand f[0] and f[1] for _ in range(100): if d[0] * d[1] < 0: - break # now, d[0] and d[1] have opposite sign + break # now, d[0] and d[1] have opposite sign factor *= {True: 2, False: 0.5}[span_diff < 0] span_diff = assess(factor) key = {True: 1, False: 0}[span_diff > d[1]] @@ -67,9 +69,11 @@ def assess(factor): # now: d[0] < 0 and d[1] > 0, squeeze f[0] & f[1] to converge for _ in range(100): if (d[1] - d[0]) > span_target: - factor = (f[0] + f[1])/2 # bracket by bisection when not close + factor = (f[0] + f[1]) / 2 # bracket by bisection when not close else: - factor = f[0] - d[0] * (f[1]-f[0])/(d[1]-d[0]) # linear interpolation when close + factor = f[0] - d[0] * (f[1] - f[0]) / ( + d[1] - d[0] + ) # linear interpolation when close span_diff = assess(factor) if abs(span_diff) <= span_precision: break @@ -80,7 +84,7 @@ def assess(factor): return factor def find_factor_simplistic(self): - ''' + """ Determine the factor that will make a series with the specified parameters. Choose the factor that will minimize :math:`| x_n - finish |` subject to: @@ -94,11 +98,11 @@ def find_factor_simplistic(self): This search technique picks a new factor based on the fit of the present choice. It converges but not quickly. - ''' - logger.debug('\t'.join('factor diff'.split())) + """ + logger.debug("\t".join("factor diff".split())) span_target = abs(self.finish - self.start) span_precision = abs(self.minStep) * 0.2 - factor = abs(self.finish-self.start) / (self.numPts -1) + factor = abs(self.finish - self.start) / (self.numPts - 1) fStep = factor larger = 3.0 smaller = 0.5 @@ -106,7 +110,7 @@ def find_factor_simplistic(self): self.make_series(factor) span = abs(self.series[0] - self.series[-1]) span_diff = span - span_target - logger.debug('\t'.join(map(str,[factor, span_diff]))) + logger.debug("\t".join(map(str, [factor, span_diff]))) if abs(span_diff) <= span_precision: break if span_diff < 0: @@ -117,20 +121,22 @@ def find_factor_simplistic(self): return factor def make_series(self, factor): - '''create self.series with the given factor''' + """create self.series with the given factor""" x = self.start - series = [x, ] + series = [ + x, + ] for _ in range(self.numPts - 1): x += self.sign * self.uascanStepFunc(x, factor) series.append(x) self.series = series def uascanStepFunc(self, x, factor): - '''Calculate the next step size with the given parameters''' + """Calculate the next step size with the given parameters""" if abs(x - self.center) > 1e100: step = 1e100 else: - step = factor * pow( abs(x - self.center), self.exponent ) + self.minStep + step = factor * pow(abs(x - self.center), self.exponent) + self.minStep return step @@ -146,5 +152,5 @@ def main(): print(f"{u.series=}") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/wwwServerTransfers.py b/wwwServerTransfers.py index 68f6da8..e87a9eb 100755 --- a/wwwServerTransfers.py +++ b/wwwServerTransfers.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -''' +""" manage file transfers with the USAXS account on the XSD WWW server -''' +""" import datetime @@ -22,19 +22,19 @@ logger = logging.getLogger(__name__) # general use -#WWW_SERVER = 'www-i.xray.aps.anl.gov' -WWW_SERVER = 'joule.xray.aps.anl.gov' -WWW_SERVER_USER = 'webusaxs' -WWW_SERVER_ROOT = WWW_SERVER_USER + '@' + WWW_SERVER +# WWW_SERVER = 'www-i.xray.aps.anl.gov' +WWW_SERVER = "joule.xray.aps.anl.gov" +WWW_SERVER_USER = "webusaxs" +WWW_SERVER_ROOT = WWW_SERVER_USER + "@" + WWW_SERVER WWW_SERVER_NFS_ROOT = "/net/joule/export/joule/WEBUSAXS/" -#LIVEDATA_DIR = "www/livedata" +# LIVEDATA_DIR = "www/livedata" LIVEDATA_DIR = "www_live" SERVER_WWW_HOMEDIR = WWW_SERVER_ROOT + ":~" SERVER_WWW_LIVEDATA = os.path.join(SERVER_WWW_HOMEDIR, LIVEDATA_DIR) SERVER_WWW_LIVEDATA_NFS = os.path.join(WWW_SERVER_NFS_ROOT, LIVEDATA_DIR) LOCAL_DATA_DIR = "/share1" -LOCAL_WWW = os.path.join(LOCAL_DATA_DIR, 'local_livedata') +LOCAL_WWW = os.path.join(LOCAL_DATA_DIR, "local_livedata") LOCAL_WWW_LIVEDATA = os.path.join(LOCAL_DATA_DIR, LIVEDATA_DIR) LOCAL_USAXS_DATA__DIR = LOCAL_DATA_DIR + "/USAXS_data" @@ -45,18 +45,19 @@ RETRY_COUNT = 3 -class WwwServerScpException(Exception): pass +class WwwServerScpException(Exception): + pass -def nfsCpToWebServer(sourceFile, targetFile = "", demo = False): - ''' +def nfsCpToWebServer(sourceFile, targetFile="", demo=False): + """ Copy the local source file to the WWW server using NFS. @param sourceFile: file in local file space relative to /share1/local_livedata @param targetFile: destination file (default is same path as sourceFile) @param demo: If True, don't do the copy, just print the command @return: a tuple (stdoutdata, stderrdata) -or- None (if demo=False) - ''' + """ if not os.path.exists(sourceFile): raise Exception("Local file not found: " + sourceFile) if len(targetFile) == 0: @@ -64,7 +65,7 @@ def nfsCpToWebServer(sourceFile, targetFile = "", demo = False): destinationName = os.path.join(SERVER_WWW_LIVEDATA_NFS, targetFile) msg = "%s %s %s" % ("cp -f", sourceFile, destinationName) logger.debug(msg) - + if demo: return @@ -76,15 +77,15 @@ def nfsCpToWebServer(sourceFile, targetFile = "", demo = False): raise OSError(msg) -def scpToWebServer(sourceFile, targetFile = "", demo = False): - ''' +def scpToWebServer(sourceFile, targetFile="", demo=False): + """ Copy the local source file to the WWW server using scp. @param sourceFile: file in local file space relative to /share1/local_livedata @param targetFile: destination file (default is same path as sourceFile) @param demo: If True, don't do the copy, just print the command @return: a tuple (stdoutdata, stderrdata) -or- None (if demo=False) - ''' + """ if not os.path.exists(sourceFile): raise Exception("Local file not found: " + sourceFile) if len(targetFile) == 0: @@ -97,27 +98,29 @@ def scpToWebServer(sourceFile, targetFile = "", demo = False): with createSSHClient(WWW_SERVER, user=WWW_SERVER_USER) as ssh: report = None - #report = report_scp_progress # debugging (from scp.report_scp_progress) + # report = report_scp_progress # debugging (from scp.report_scp_progress) scp = SCPClient(ssh.get_transport(), progress=report) for _retry in range(RETRY_COUNT): try: scp.put(sourceFile, remote_path=LIVEDATA_DIR) - if _retry > 0: # only report after some retries, otherwise return quietly - msg = "scp was successful after %d tries" % (_retry+1) + if ( + _retry > 0 + ): # only report after some retries, otherwise return quietly + msg = "scp was successful after %d tries" % (_retry + 1) logger.info(msg) # ssh.close() return except (SCPException, paramiko.SSHException, socket.error) as exc: - msg = 'scp attempt %d: %s' % ((_retry+1), str(exc)) + msg = "scp attempt %d: %s" % ((_retry + 1), str(exc)) logger.info(msg) - msg = 'tried %d times: scp %s %s' % (RETRY_COUNT, sourceFile, targetFile) + msg = "tried %d times: scp %s %s" % (RETRY_COUNT, sourceFile, targetFile) raise WwwServerScpException(msg) -def scpToWebServer_Demonstrate(sourceFile, targetFile = ""): - ''' +def scpToWebServer_Demonstrate(sourceFile, targetFile=""): + """ Demonstrate a copy from the local source file to the WWW server using scp BUT DO NOT DO IT ... ... this is useful for code development only... @@ -126,19 +129,19 @@ def scpToWebServer_Demonstrate(sourceFile, targetFile = ""): @param sourceFile: file in local file space *relative* to /share1/local_livedata @param targetFile: destination file (default is same path as sourceFile) @return: None - ''' - return scpToWebServer(sourceFile, targetFile, demo = True) + """ + return scpToWebServer(sourceFile, targetFile, demo=True) -def scpToWebServer_subprocess(sourceFile, targetFile = "", demo = False): - ''' +def scpToWebServer_subprocess(sourceFile, targetFile="", demo=False): + """ Copy the local source file to the WWW server using scp. @param sourceFile: file in local file space relative to /share1/local_livedata @param targetFile: destination file (default is same path as sourceFile) @param demo: If True, don't do the copy, just print the command @return: a tuple (stdoutdata, stderrdata) -or- None (if demo=False) - ''' + """ # Can we replace scpToWebServer() with Python package capabilities? # No major improvement. # see: http://stackoverflow.com/questions/250283/how-to-scp-in-python @@ -163,39 +166,39 @@ def scpToWebServer_subprocess(sourceFile, targetFile = "", demo = False): finished = True result = p.communicate(None) if not finished or code != 0: - msg = {True: 'problem', False: 'timeout'}[finished] - msg += ': command `%s` returned code=%d' % (command, code) - msg += '\nSTDOUT=%s\nSTDERR=%s' % (str(result[0]), str(result[1])) + msg = {True: "problem", False: "timeout"}[finished] + msg += ": command `%s` returned code=%d" % (command, code) + msg += "\nSTDOUT=%s\nSTDERR=%s" % (str(result[0]), str(result[1])) logger.info(msg) return result def execute_command(command): - ''' + """ execute the specified shell command @return: a tuple (stdoutdata, stderrdata) - ''' + """ # run the command but gobble up stdout (make it less noisy) p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) - #p.wait() + # p.wait() return p.communicate(None) def createSSHClient(server, port=None, user=None, password=None): - '''scp over a paramiko transport''' + """scp over a paramiko transport""" # see: http://stackoverflow.com/questions/250283/how-to-scp-in-python client = paramiko.SSHClient() client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - #client.connect(server, port, user, password) + # client.connect(server, port, user, password) client.connect(server, username=user) transport = client.get_transport() transport.logger.setLevel(logging.WARNING) # otherwise, reports frequently return client -if __name__ == '__main__': +if __name__ == "__main__": scpToWebServer("wwwServerTransfers.py") scpToWebServer_Demonstrate("wwwServerTransfers.py") try: diff --git a/xmlSupport.py b/xmlSupport.py index 9f9aa5b..9775fd8 100755 --- a/xmlSupport.py +++ b/xmlSupport.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -'''read XML configuration files into a common data structure''' +"""read XML configuration files into a common data structure""" import logging import os.path @@ -12,39 +12,42 @@ logger = logging.getLogger(__name__) -#************************************************************************** +# ************************************************************************** + def readPvlistXML(xmlFile): - '''get the list of PVs to monitor from the XML file''' + """get the list of PVs to monitor from the XML file""" doc = openScanLogFile(xmlFile) xref = {} if doc is not None: db = [] - for element in doc.findall('EPICS_PV'): + for element in doc.findall("EPICS_PV"): arr = {} - arr['desc'] = element.text.strip() + arr["desc"] = element.text.strip() for attribute in element.attrib.keys(): arr[attribute] = element.attrib[attribute].strip() db.append(arr) - xref['pvList'] = db - xref['scanLogFile'] = doc.find('scanLog_file').text - xref['local_www_dir'] = doc.find('local_www_dir').text - return(xref) + xref["pvList"] = db + xref["scanLogFile"] = doc.find("scanLog_file").text + xref["local_www_dir"] = doc.find("local_www_dir").text + return xref + -#************************************************************************** +# ************************************************************************** readConfigurationXML = readPvlistXML # def readConfigurationXML(pvListFile): # '''locate the PV configuration from XML into memory''' # return readPvlistXML(pvListFile) -#************************************************************************** +# ************************************************************************** + def openScanLogFile(xmlFile): - ''' + """ open the XML file with ElementTree return the doc (doc.getroot() to get the root node) - ''' + """ doc = None try: doc = ElementTree.parse(xmlFile) @@ -52,78 +55,90 @@ def openScanLogFile(xmlFile): pass return doc -#************************************************************************** + +# ************************************************************************** + def locateScanID(doc, scanid): - ''' + """ find the XML scan entry with matching id attribute return XML node or None if not found - ''' + """ result = None - #query = "scan/[@id='%s']" % scanid + # query = "scan/[@id='%s']" % scanid for node in doc.findall("scan"): if node.get("id") == scanid: result = node break return result -#************************************************************************** + +# ************************************************************************** + def flagRunawayScansAsUnknown(doc, scanid): - '''sometimes, a scan ends without this program finding out''' - for node in doc.findall("scan"): # look for any scan ... - if node.get("state") == "scanning": # with state="scanning" ... - if node.get("id") != scanid: # but not the newest node ... - node.set("state", "unknown") # THIS node is not scanning anymore + """sometimes, a scan ends without this program finding out""" + for node in doc.findall("scan"): # look for any scan ... + if node.get("state") == "scanning": # with state="scanning" ... + if node.get("id") != scanid: # but not the newest node ... + node.set("state", "unknown") # THIS node is not scanning anymore + + +# ************************************************************************** -#************************************************************************** def readFileAsLines(filename): - ''' + """ open a file and read all of it into memory as a list separated by line breaks, return None if error or cannot find file - ''' + """ if not os.path.exists(filename): return None try: - with open(filename, 'r') as f: + with open(filename, "r") as f: buf = f.read() return buf.split("\n") except Exception: return None -#************************************************************************** + +# ************************************************************************** + def writeLinesInFile(filename, lines): - '''write a list of lines to a file, ignore any errors''' + """write a list of lines to a file, ignore any errors""" try: - with open(filename, 'w') as f: + with open(filename, "w") as f: f.write("\n".join(lines) + "\n") except Exception: pass -#************************************************************************** + +# ************************************************************************** + def prettyXml(element): - '''fallback support for better code''' + """fallback support for better code""" return prettyXmlToString(element) def prettyXmlToString(element): - ''' + """ make nice-looking XML that is human-readable @see http://stackoverflow.com/questions/749796/pretty-printing-xml-in-python - ''' + """ txt = ElementTree.tostring(element) dom = xml.dom.minidom.parseString(txt) ugly = dom.toprettyxml() - #pretty = dom.toxml() - text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+\g<1>\n\s+([^<>\s].*?)\n\s+\g<1>' lines = prettyXmlToString(doc.getroot()).split("\n") @@ -143,60 +160,71 @@ def writeXmlDocToFile(xmlFile, doc): writeLinesInFile(xmlFile, lines) return -#************************************************************************** + +# ************************************************************************** + def appendTextNode(doc, parent, tag, value): - '''append a text node to the XML document''' + """append a text node to the XML document""" elem = ElementTree.Element(tag) elem.text = value parent.append(elem) -#************************************************************************** + +# ************************************************************************** + def appendDateTimeNode(doc, parent, tag): - '''append a date/time node to the XML document''' + """append a date/time node to the XML document""" elem = ElementTree.Element(tag) - elem.set('date', xmlDate()) - elem.set('time', xmlTime()) + elem.set("date", xmlDate()) + elem.set("time", xmlTime()) parent.append(elem) -#************************************************************************** + +# ************************************************************************** + def xmlDate(): - '''current date, for use in XML file (ISO8601)''' - txt = time.strftime('%Y-%m-%d') # XML date format + """current date, for use in XML file (ISO8601)""" + txt = time.strftime("%Y-%m-%d") # XML date format return txt -#************************************************************************** + +# ************************************************************************** + def xmlTime(): - '''current time, for use in XML file (ISO8601)''' - txt = time.strftime('%H:%M:%S') # XML time format + """current time, for use in XML file (ISO8601)""" + txt = time.strftime("%H:%M:%S") # XML time format return txt -#************************************************************************** + +# ************************************************************************** + def main(): - ''' test routine ''' - if (len(sys.argv) == 2): + """test routine""" + if len(sys.argv) == 2: pwd = sys.argv[1] else: - pwd = '.' - config = readConfigurationXML(os.path.join(pwd, 'pvlist.xml')) + pwd = "." + config = readConfigurationXML(os.path.join(pwd, "pvlist.xml")) if len(config) == 0: logger.error("ERROR: could not read the configuration file") return - doc = openScanLogFile(config['scanLogFile']) + doc = openScanLogFile(config["scanLogFile"]) root = doc.getroot() - scan = locateScanID(doc, '43:/share1/USAXS_data/2010-03/03_24_setup.dat') + scan = locateScanID(doc, "43:/share1/USAXS_data/2010-03/03_24_setup.dat") scan.set("gotcha", "True") print(prettyXmlToString(scan)) appendTextNode(doc, root, "modifed.by", sys.argv[0]) appendDateTimeNode(doc, root, "timestamp") - #print(prettyXml(root)) - writeXmlDocToFile('test.xml', doc) + # print(prettyXml(root)) + writeXmlDocToFile("test.xml", doc) + -#************************************************************************** +# ************************************************************************** if __name__ == "__main__": main() From 9dd0b737f8e13c9574ca42fc2d9bdc2387fd978f Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 15:32:49 -0600 Subject: [PATCH 26/28] STY #48 flake8 --- .flake8 | 2 +- pvwatch.py | 2 +- reduceAreaDetector.py | 2 +- reduceFlyData.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.flake8 b/.flake8 index f7a1afc..a4d6502 100644 --- a/.flake8 +++ b/.flake8 @@ -10,4 +10,4 @@ exclude = # some in test cases max-line-length = 115 # TODO: re-enable E501 error -ignore: E226,E402,E501,E741,F401,F403,W503,W504,W605 +ignore: E226,E203,E402,E501,E741,F401,F403,W503,W504,W605 diff --git a/pvwatch.py b/pvwatch.py index bc88dc7..6eb0a0a 100755 --- a/pvwatch.py +++ b/pvwatch.py @@ -144,7 +144,7 @@ def updatePlotImage(): makePlot = not os.path.exists(plotFile) # no plot yet, let's make one! if os.path.exists(plotFile): plot_mtime = os.stat(plotFile).st_mtime - makePlot = spec_mtime > plot_mtime # plot only if new data + makePlot = spec_mtime > plot_mtime # plot only if new data if makePlot: logger.debug("updating the plots and gathering scan data for XML file") diff --git a/reduceAreaDetector.py b/reduceAreaDetector.py index 98955d8..501d683 100644 --- a/reduceAreaDetector.py +++ b/reduceAreaDetector.py @@ -290,7 +290,7 @@ def save(self, hfile=None, key=None): ds = self.reduced[key] try: hdf = h5py.File(hfile, "a") - except IOError as _exc: + except IOError: # FIXME: some h5py problem in /_hl/files.py, line 101 # this fails: fid = h5f.open(name, h5f.ACC_RDWR, fapl=fapl) # with IOError that is improperly caught on next and then: diff --git a/reduceFlyData.py b/reduceFlyData.py index 9e3cc7e..274262d 100755 --- a/reduceFlyData.py +++ b/reduceFlyData.py @@ -38,9 +38,9 @@ AR_MODE_TRAJECTORY: "TrajPts", } -# raised when HDF5 file exists but length of raw data is zero + class NoFlyScanData(IndexError): - pass + """HDF5 file exists but length of raw data is zero.""" def decode_h5py_byte_string(value): From c529281045618dd5b8efab7bd1e6a07e0845a932 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Tue, 8 Nov 2022 15:35:40 -0600 Subject: [PATCH 27/28] STY #48 flake8 --- .flake8 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.flake8 b/.flake8 index a4d6502..06fb69e 100644 --- a/.flake8 +++ b/.flake8 @@ -5,9 +5,5 @@ exclude = archive, # E501 line too long -# many long lines in python code -# most in documentation -# some in test cases max-line-length = 115 -# TODO: re-enable E501 error ignore: E226,E203,E402,E501,E741,F401,F403,W503,W504,W605 From 314c9b3fa7b065159470e7c606a7dc075431ea90 Mon Sep 17 00:00:00 2001 From: USAXS account Date: Wed, 9 Nov 2022 09:19:13 -0600 Subject: [PATCH 28/28] TST #48 pass tests for reducing fly and step scans --- tests/test_calc.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/test_calc.py b/tests/test_calc.py index b21bcc3..3c30f30 100644 --- a/tests/test_calc.py +++ b/tests/test_calc.py @@ -39,12 +39,27 @@ def test_flyScan(filename): arr_channel, arr_requested, arr_actual = changes assert len(arr_channel) == len(arr_requested) assert len(arr_channel) == len(arr_actual) + # compute the R(Q) profile fs.reduce() usaxs = fs.reduced assert usaxs is not None assert isinstance(usaxs, dict) - assert "full" in usaxs + + full = usaxs.get("full") + assert full is not None + assert_usaxs_reduced_data(full) + + +def assert_usaxs_reduced_data(reduced): + assert isinstance(reduced, dict) # data is reduceable + for k in "Q R ar r r0 ar_0 ar_r_peak r_peak".split(): + assert k in reduced + + # same lengths + n = len(reduced["Q"]) + for k in "R ar r r0".split(): + assert len(reduced[k]) == n @pytest.mark.parametrize( @@ -72,14 +87,7 @@ def test_uascan(filename, scan_number): # TODO: anything to test now? uascan = reduce_uascan(sds) - assert isinstance(uascan, dict) # data is reduceable - for k in "Q R ar r r0 ar_0 ar_r_peak r_peak".split(): - assert k in uascan - - # same lengths - n = len(uascan["Q"]) - for k in "R ar".split(): - assert len(uascan[k]) == n + assert_usaxs_reduced_data(uascan) # TODO: refactor as test(s)