diff --git a/.travis.yml b/.travis.yml index 61b5ce00b..098f523b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,10 +10,8 @@ cache: matrix: include: - - os: linux - env: CONDA=2.7 - #- os: linux - # env: CONDA=3.6 + - env: CONDA=2.7 + - env: CONDA=3.6 before_install: - | diff --git a/data/HB2B_1017_NoMask_Gold.h5 b/data/HB2B_1017_NoMask_Gold.h5 index ff65df997..3e4816b65 100644 Binary files a/data/HB2B_1017_NoMask_Gold.h5 and b/data/HB2B_1017_NoMask_Gold.h5 differ diff --git a/data/HB2B_1017_Pixels_Gold.h5 b/data/HB2B_1017_Pixels_Gold.h5 index 20361580e..50b619a1f 100644 Binary files a/data/HB2B_1017_Pixels_Gold.h5 and b/data/HB2B_1017_Pixels_Gold.h5 differ diff --git a/data/HB2B_CAL_Si333.json b/data/HB2B_CAL_Si333.json index 86436853c..c50b51faf 100644 --- a/data/HB2B_CAL_Si333.json +++ b/data/HB2B_CAL_Si333.json @@ -1 +1 @@ -{"Status": 3, "error_Rot_z": -1.0, "error_Shift_z": -1.0, "error_Rot_x": -1.0, "error_Rot_y": -1.0, "error_Shift_x": 0.0001, "Shift_z": 0.0, "Shift_x": 7.903208284128856e-05, "Shift_y": 0.0, "error_Lambda": 0.0001, "Rot_z": 0.0, "Rot_y": 0.0, "Rot_x": 0.0, "error_Shift_y": -1.0, "Lambda": 1.456274820626443} \ No newline at end of file +{"Status": 3, "error_Rot_z": -1.0, "error_Shift_z": -1.0, "error_Rot_x": -1.0, "error_Rot_y": -1.0, "error_Shift_x": 0.0001, "Shift_z": 0.0, "Shift_x": 0.000343639741127, "Shift_y": 0.0, "error_Lambda": 0.0001, "Rot_z": 0.0, "Rot_y": 0.0, "Rot_x": 0.0, "error_Shift_y": -1.0, "Lambda": 1.4499332864} diff --git a/data/XRay_Definition_1K.txt b/data/XRay_Definition_1K.txt index 9ed854177..1596d940b 100644 --- a/data/XRay_Definition_1K.txt +++ b/data/XRay_Definition_1K.txt @@ -3,5 +3,5 @@ arm = 0.985 rows = 1024 columns = 1024 -pixel_size_x = 0.0003 -pixel_size_y = 0.0003 +pixel_size_x = 0.00029296875 +pixel_size_y = 0.00029296875 diff --git a/prototypes/load_split_subruns_numpy.py b/prototypes/load_split_subruns_numpy.py deleted file mode 100644 index 7268e1928..000000000 --- a/prototypes/load_split_subruns_numpy.py +++ /dev/null @@ -1,135 +0,0 @@ -# This is a numpy version for prototyping to load NeXus and split events for sub runs -# by numpy and hdf5 -import h5py -import numpy as np -from matplotlib import pyplot as plt -import datetime -from mantid.simpleapi import Load, FilterByLogValue, DeleteWorkspace -import os - - -def load_nexus(nexus_file_name): - nexus_h5 = h5py.File(nexus_file_name, 'r') - return nexus_h5 - -def get_scan_indexes(nexus_h5): - scan_index_times = nexus_h5['entry']['DASlogs']['scan_index']['time'].value - scan_index_values = nexus_h5['entry']['DASlogs']['scan_index']['value'].value - - if scan_index_values[0] == 0: - scan_index_times = scan_index_times[1:] - scan_index_values = scan_index_values[1:] - - if scan_index_times.shape != scan_index_values.shape: - raise RuntimeError('Scan index time and value not in same shape') - if scan_index_times.shape[0] % 2 == 1: - raise RuntimeError("Scan index are not in (1, 0) pair") - - return scan_index_times, scan_index_values - - -def split_sub_runs(nexus_h5, scan_index_times, scan_index_values): - # pulse times - pulse_time_array = nexus_h5['entry']['bank1_events']['event_time_zero'].value - - # search scan index boundaries in pulse time array - subrun_pulseindex_array = np.searchsorted(pulse_time_array, scan_index_times) - print('Scan Index Times: {}'.format(scan_index_times)) - print('Pulse Ends: {}'.format(pulse_time_array[-1])) - print('Matched Pulse Indexes: {}'.format(subrun_pulseindex_array)) - - # get event index array: same size as pulse times - event_index_array = nexus_h5['entry']['bank1_events']['event_index'].value - event_id_array = nexus_h5['entry']['bank1_events']['event_id'].value - - # histogram boundaries - # bound_x = np.arange(1024 ** 2 + 1).astype(float) - 0.1 - - # split data - print(scan_index_values) - num_sub_runs = scan_index_values.shape[0] / 2 - # sub_run_data_set = np.ndarray((num_sub_runs, 1024**2), dtype=float) - sub_run_counts_dict = dict() - - for i_sub_run in range(num_sub_runs): - # get the start and stop index in pulse array - start_pulse_index = subrun_pulseindex_array[2 * i_sub_run] - stop_pulse_index = subrun_pulseindex_array[2 * i_sub_run + 1] - - # get start andn stop event ID from event index array - start_event_id = event_index_array[start_pulse_index] - if stop_pulse_index >= event_index_array.size: - print('[WARNING] for sub run {} out of {}, stop pulse index {} is out of boundary of {}' - ''.format(i_sub_run, num_sub_runs, stop_pulse_index, event_index_array.shape)) - # stop_pulse_index = event_index_array.size - 1 - # supposed to be the last pulse and thus use the last + 1 event ID's index - stop_event_id = event_id_array.shape[0] - else: - # natural one - stop_event_id = event_index_array[stop_pulse_index] - print('Sub run {}: Stop Event ID = {}'.format(i_sub_run, stop_event_id)) - - # get sub set of the events falling into this range - sub_run_events = event_id_array[start_event_id:stop_event_id] - # convert to float - counts = sub_run_events.astype(float) - - # histogram - # hist = np.histogram(counts, bound_x)[0] - hist = np.bincount(sub_run_events, minlength=1024**2) - # sub_run_data_set[i_sub_run] = hist - sub_run_counts_dict[int(scan_index_values[2 * i_sub_run])] = hist - - return sub_run_counts_dict - - -def verify(nexus, sub_run_list, sub_run_data_dict): - # Load - ws = Load(Filename=nexus, OutputWorkspace=os.path.basename(nexus).split('.')[0]) - - pp_str = '' - - for sub_run in sub_run_list: - sub_run_ws = FilterByLogValue(InputWorkspace=ws, - OutputWorkspace=str(ws) + '_{}'.format(sub_run), - LogName='scan_index', - LogBoundary='Left', - MinimumValue=float(sub_run) - .25, - MaximumValue=float(sub_run) + .25) - counts_vec = sub_run_ws.extractY().reshape((1024**2,)) - - diff = np.abs(sub_run_data_dict[sub_run] - counts_vec) - print('sub run {}: counts = {}, total difference = {}, maximum difference = {}' - ''.format(sub_run, np.sum(counts_vec), diff.sum(), diff.max())) - - pp_str += 'sub run {}: counts = {}, total difference = {}, maximum difference = {}\n' \ - ''.format(sub_run, np.sum(counts_vec), diff.sum(), diff.max()) - - DeleteWorkspace(Workspace=sub_run_ws) - # END - - print(pp_str) - - -def main(): - - start_time = datetime.datetime.now() - - # split_sub_runs = '/HFIR/HB2B/IPTS-22048/nexus/HB2B_1205.nxs.h5' - nexus = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5' - nexus_h5 = load_nexus(nexus) - - scan_index_times, scan_index_values = get_scan_indexes(nexus_h5) - - frames = split_sub_runs(nexus_h5, scan_index_times, scan_index_values) - - stop_time = datetime.datetime.now() - - duration = (stop_time - start_time).total_seconds() - print('Processing time = {}; Per sub run = {}'.format(duration, duration * 1. / len(frames))) - - verify(nexus, range(1, len(frames) + 1), frames) - - -if __name__ == '__main__': - main() diff --git a/prototypes/temp/convert_hzb_data.py b/prototypes/temp/convert_hzb_data.py deleted file mode 100644 index 3c0a56429..000000000 --- a/prototypes/temp/convert_hzb_data.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/python -# Convert HZB data to standard transformed-HDF5 format considering all the sample log values and instrument information -# This may be executed only once and moved out of build -import numpy -import os -from pyrs.utilities import file_util -from pyrs.utilities import rs_project_file -import pandas as pd - - -def parse_hzb_tiff(tiff_file_name): - """ - Parse HZB TIFF (image) data to numpy array (1D) - :param tiff_file_name: - :return: (1) 1D array (column major, from lower left corner) (2) (num_row, num_cols) - """ - # is it rotated? - # rotated - counts_array = file_util.load_rgb_tif(tiff_file_name, True) - - return counts_array - - -def load_excel_file(excel_file): - """ Load EXCEL file - Note: Excel file is closed after read (99%) - :param excel_file: name of Excel file - :return: pandas instance (pandas.core.frame.DataFrame) - """ - # use pandas to load - df = pd.read_excel(excel_file) - - return df - - -def import_hzb_summary(summary_excel): - """ - import and parse HZB summary file in EXCEL format - [ u'E3 file', u'Tiff', u'Tiff_Index', u'Index', - u'2th', u'mon', u'2th.1', u'Unnamed: 7', - u'L2', u'ADET', u'Unnamed: 10', u'Unnamed: 11', - u'Unnamed: 12', u'Unnamed: 13', u'SDET'], - # ['Tiff'][i] = E3-Y2O3 42-50 - # ['Tiff Index] = 1, 2, 3, ... - # 2th = 2th.1 - # L2: unit == mm - # tif_name = '{}_{:05}.tiff'.format(df['Tiff'][0], int(df['Tiff_Index'][0])) - :param summary_excel: experiment summary file in Excel format - :return: - """ - # load data - summary_pandas_data = load_excel_file(summary_excel) - - # get list of interested items - scan_index_array = numpy.array(summary_pandas_data['Index']) - two_theta_array = numpy.array(summary_pandas_data['2th']) - tiff_header_array = numpy.array(summary_pandas_data['Tiff']) - tiff_order_array = numpy.array(summary_pandas_data['Tiff_Index']) - monitor_array = numpy.array(summary_pandas_data['mon']) - l2_array = numpy.array(summary_pandas_data['L2']) * 1.E-3 # convert to meter - - # combine to files - tiff_files = list() - for item_index in range(len(tiff_header_array)): - tiff_header_i = tiff_header_array[item_index] - tiff_order_i = tiff_order_array[item_index] - tiff_file_i = '{}_{:05}.tiff'.format(tiff_header_i, int(tiff_order_i)) - tiff_files.append(tiff_file_i) - # END-FOR - - # create a dictionary of dictionary for the information - summary_dict = dict() - for item_index in range(len(tiff_files)): - scan_i_dict = {'2theta': two_theta_array[item_index], - 'Tiff': tiff_files[item_index], - 'Monitor': monitor_array[item_index], - 'L2': l2_array[item_index]} - if scan_index_array[item_index] in summary_dict: - raise RuntimeError('Experiment summary file {} contains 2 entries with same (scan) Index {}' - ''.format(summary_excel, scan_index_array[item_index])) - summary_dict[scan_index_array[item_index]] = scan_i_dict - # END-FOR - - return summary_dict, {'Scan Index': scan_index_array, '2Theta': two_theta_array, - 'Monitor': monitor_array, 'L2': l2_array} - - -def main(argv): - """ - main for the workflow to create the HDF5 - :param argv: - :return: - """ - # process inputs ... - exp_summary_excel = argv[1] - exp_data_dir = argv[2] - project_file_name = argv[3] - - # parse EXCEL spread sheet - exp_summary_dict, exp_logs_dict = import_hzb_summary(exp_summary_excel) - - # start project file - project_file = rs_project_file.HydraProjectFile(project_file_name, - mode=rs_project_file.HydraProjectFileMode.OVERWRITE) - - # add sample log data - for log_name in ['Scan Index', '2Theta', 'Monitor', 'L2']: - project_file.add_experiment_log(log_name, exp_logs_dict[log_name]) - # END-FOR - - # parse and add counts - sub_run_list = exp_summary_dict.keys() - for sub_run_i in sorted(sub_run_list): - tiff_name = exp_summary_dict[sub_run_i]['Tiff'] - counts_array = parse_hzb_tiff(os.path.join(exp_data_dir, tiff_name)) - print(counts_array.min(), counts_array.max(), (numpy.where(counts_array > 0.5)[0]).shape) - project_file.add_raw_counts(sub_run_i, counts_array) - # END-FOR - - # save - project_file.close() - - return - - -if __name__ == '__main__': - main(['Whatever', - '/SNS/users/wzz/Projects/HB2B/Quasi_HB2B_Calibration/calibration.xlsx', - '/SNS/users/wzz/Projects/HB2B/Quasi_HB2B_Calibration/', - 'tests/testdata/hzb/hzb_calibration.hdf5']) diff --git a/pyrs/calibration/peakfit_calibration.py b/pyrs/calibration/peakfit_calibration.py index 9905ee5f0..c6b0ce435 100644 --- a/pyrs/calibration/peakfit_calibration.py +++ b/pyrs/calibration/peakfit_calibration.py @@ -1,6 +1,7 @@ # Migrated from /HFIR/HB2B/shared/Quick_Calibration.py # Original can be found at ./Quick_Calibration_v3.py # Renamed from ./prototypes/calibration/Quick_Calibration_Class.py +from __future__ import (absolute_import, division, print_function) # python3 compatibility import numpy as np import time import json @@ -303,7 +304,7 @@ def FitDetector(self, fun, x0, jac='3-point', bounds=[], method='trf', ftol=1e-0 s_sq = (residual**2).sum()/(len(residual)-len(x0)) pcov = pcov * s_sq else: - pcov = np.inf + pcov = np.diag([np.inf]*len(pfit)) error = [0.0] * len(pfit) for i in range(len(pfit)): @@ -312,7 +313,7 @@ def FitDetector(self, fun, x0, jac='3-point', bounds=[], method='trf', ftol=1e-0 except ValueError: error[i] = (0.00) - print [pfit, np.array(error), success] + print([pfit, np.array(error), success]) return [pfit, np.array(error), success] else: @@ -528,7 +529,7 @@ def peak_alignment_single(self, x, roi_vec_set=None, ConstrainPosition=False, Re paramVec = np.copy(self._calib) paramVec[i_index] = x[0] - print x + print(x) residual = self.get_alignment_residual(paramVec, roi_vec_set, ConstrainPosition, False, start, stop) if ReturnScalar: @@ -599,7 +600,7 @@ def peaks_alignment_all(self, x, roi_vec_set=None, ConstrainPosition=False, :return: """ - print x + print(x) residual = self.get_alignment_residual(x, roi_vec_set, True, False, start, stop) if ReturnScalar: diff --git a/pyrs/core/nexus_conversion.py b/pyrs/core/nexus_conversion.py index 3c971a9d1..fdbf53f03 100644 --- a/pyrs/core/nexus_conversion.py +++ b/pyrs/core/nexus_conversion.py @@ -2,22 +2,209 @@ Convert HB2B NeXus file to Hidra project file for further reduction """ from __future__ import (absolute_import, division, print_function) # python3 compatibility -import bisect -from mantid.kernel import FloatPropertyWithValue, FloatTimeSeriesProperty, Int32TimeSeriesProperty, \ - Int64TimeSeriesProperty, logger, Logger -from mantid.simpleapi import mtd, ConvertToMatrixWorkspace, DeleteWorkspace, FilterByLogValue, \ - FilterByTime, LoadEventNexus, LoadMask, MaskDetectors -import numpy +import h5py +from mantid.kernel import Logger, BoolTimeSeriesProperty, FloatFilteredTimeSeriesProperty, FloatTimeSeriesProperty +from mantid.kernel import Int32TimeSeriesProperty, Int64TimeSeriesProperty, Int32FilteredTimeSeriesProperty,\ + Int64FilteredTimeSeriesProperty +from mantid.simpleapi import mtd, DeleteWorkspace, LoadEventNexus, LoadMask +import numpy as np import os from pyrs.core import workspaces from pyrs.core.instrument_geometry import AnglerCameraDetectorGeometry, HidraSetup from pyrs.dataobjects import HidraConstants from pyrs.projectfile import HidraProjectFile, HidraProjectFileMode from pyrs.utilities import checkdatatypes -from pyrs.split_sub_runs.load_split_sub_runs import load_split_nexus_python - SUBRUN_LOGNAME = 'scan_index' +NUM_PIXEL_1D = 1024 +HIDRA_PIXEL_NUMBER = NUM_PIXEL_1D * NUM_PIXEL_1D +PIXEL_SIZE = 0.3 / NUM_PIXEL_1D +ARM_LENGTH = 0.985 + + +def convert_pulses_to_datetime64(h5obj): + '''The h5object is the h5py handle to ``event_time_zero``. This only supports pulsetimes in seconds''' + if h5obj.attrs['units'].decode() != 'second': + raise RuntimeError('Do not understand time units "{}"'.format(h5obj.attrs['units'])) + + # the value is number of seconds as a float + pulse_time = h5obj.value + + # Convert deltas to times with units. This has to be done through + # nanoseconds because numpy truncates things to integers during the conversion + pulse_time = pulse_time * 1.e9 * np.timedelta64(1, 'ns') + + # get absolute offset and convert to absolute time + start_time = np.datetime64(h5obj.attrs['offset']) + + return pulse_time + start_time + + +def calculate_sub_run_time_average(log_property, time_filter): + '''Determine the time average value of the supplied log''' + if log_property.size() == 1: # single value property just copy + time_average_value = log_property.value + elif time_filter is None: # no filtering means use all values + time_average_value = log_property.timeAverageValue() + else: + # filter and get time average value + if isinstance(log_property, FloatTimeSeriesProperty): + filtered_tsp = FloatFilteredTimeSeriesProperty(log_property, time_filter) + elif isinstance(log_property, Int32TimeSeriesProperty): + filtered_tsp = Int32FilteredTimeSeriesProperty(log_property, time_filter) + elif isinstance(log_property, Int64TimeSeriesProperty): + filtered_tsp = Int64FilteredTimeSeriesProperty(log_property, time_filter) + else: + raise NotImplementedError('TSP log property {} of type {} is not supported' + ''.format(log_property.name, type(log_property))) + + time_average_value = filtered_tsp.timeAverageValue() + del filtered_tsp + + return time_average_value + + +class Splitter(object): + def __init__(self, runObj): + self._log = Logger(__name__) + + if runObj['scan_index'].size() == 0: + raise RuntimeError('"scan_index" is empty') + + # Get the time and value from the run object + scan_index_times = runObj['scan_index'].times # absolute times + scan_index_value = runObj['scan_index'].value + # TODO add final time from pcharge logs + 1s with scan_index=0 + + if np.unique(scan_index_value).size == 1: + raise RuntimeError('WARNING: only one scan_index value') # TODO should be something else + + self.times = None + self.subruns = None + self.propertyFilters = list() + + self.__generate_sub_run_splitter(scan_index_times, scan_index_value) + self.__correct_starting_scan_index_time(runObj) + self._createPropertyFilters() + + def __generate_sub_run_splitter(self, scan_index_times, scan_index_value): + """Generate event splitters according to sub runs + + """ + # Init + sub_run_time_list = list() + sub_run_value_list = list() + num_scan_index = scan_index_times.shape[0] + + # Loop through all scan indexes to get the correct splitters + curr_sub_run = 0 + for i_scan in range(num_scan_index): + if scan_index_value[i_scan] != curr_sub_run: + # New run no same as old one: There will be some change! + if curr_sub_run > 0: + # previous run shall be saved: it is ending: record the ending time/current time + sub_run_time_list.append(scan_index_times[i_scan]) + + if scan_index_value[i_scan] > 0: + # new scan index is valid: a new run shall start: record the starting time and sub run value + sub_run_time_list.append(scan_index_times[i_scan]) + sub_run_value_list.append(scan_index_value[i_scan]) + + # Update the curr_sub_run + curr_sub_run = scan_index_value[i_scan] + + # Note: there is one scenario to append 2 and same time stamp: scan index change from i to j, where + # both i and j are larger than 0 + # END-IF + # END-FOR + + # Check the ending + if curr_sub_run > 0: + # In case the stop (scan_index = 0) is not recorded + sub_run_time_list.append(np.nan) + + # Convert from list to array + self.times = np.array(sub_run_time_list) + self.subruns = np.array(sub_run_value_list) + + # Sanity check + if self.times.shape[0] % 2 == 1 or self.times.shape[0] == 0: + raise RuntimeError('Algorithm error: Failed to parse\nTime: {}\nValue: {}.\n' + 'Current resulted time ({}) is incorrect as odd/even' + ''.format(scan_index_times, scan_index_value, self.times)) + + if self.times.shape[0] != self.subruns.shape[0] * 2: + raise RuntimeError('Sub run number {} and sub run times do not match (as twice)' + ''.format(self.subruns, self.times)) + + def __correct_starting_scan_index_time(self, runObj, abs_tolerance=0.05): + """Correct the DAS-issue for mis-record the first scan_index/sub run before the motor is in position + + This goes through a subset of logs and compares when they actually + get to their specified setpoint, updating the start time for + event filtering. When this is done ``self._starttime`` will have been updated. + + Parameters + ---------- + start_time: numpy.datetime64 + The start time according to the scan_index log + abs_tolerance: float + When then log is within this absolute tolerance of the setpoint, it is correct + + Returns + ------- + float + Corrected value or None + + """ + start_time = self.times[0] + # loop through the 'special' logs + for log_name in ['sx', 'sy', 'sz', '2theta', 'omega', 'chi', 'phi']: + if log_name not in runObj: + continue # log doesn't exist - not a good one to look at + if log_name + 'Setpoint' not in runObj: + continue # log doesn't have a setpoint - not a good one to look at + if runObj[log_name].size() == 1: + continue # there is only one value + + # get the observed values of the log + observed = runObj[log_name].value + if observed.std() <= .5 * abs_tolerance: + continue # don't bother if the log is constant within half of the tolerance + + # look for the setpoint and find when the log first got there + # only look at first setpoint + set_point = runObj[log_name + 'Setpoint'].value[0] + for log_time, value in zip(runObj[log_name].times, observed): + if abs(value - set_point) < abs_tolerance: + # pick the larger of what was found and the previous largest value + if log_time > start_time: + start_time = log_time + break + + self._log.debug('Shift from start_time {} to {}'.format(np.datetime_as_string(self.times[0]), + np.datetime_as_string(start_time))) + self.times[0] = start_time + + @property + def durations(self): + return (self.times[1::2] - self.times[::2]) / np.timedelta64(1, 's') + + def _createPropertyFilters(self): + self.propertyFilters = list() + if self.subruns.size == 1: + self.propertyFilters.append(None) + else: + for subrun_index in range(self.subruns.size): + subrun_start_time = self.times[2 * subrun_index] + subrun_stop_time = self.times[2 * subrun_index + 1] + + # create a Boolean time series property as the filter + time_filter = BoolTimeSeriesProperty('filter') + time_filter.addValue(subrun_start_time, True) + time_filter.addValue(subrun_stop_time, False) + + self.propertyFilters.append(time_filter) class NeXusConvertingApp(object): @@ -37,11 +224,11 @@ def __init__(self, nexus_file_name, mask_file_name=None): # configure logging for this class self._log = Logger(__name__) - # NeXus name + # validate NeXus file exists checkdatatypes.check_file_name(nexus_file_name, True, False, False, 'NeXus file') self._nexus_name = nexus_file_name - # Mask + # validate mask file exists if mask_file_name is None: self._mask_file_name = None else: @@ -54,156 +241,248 @@ def __init__(self, nexus_file_name, mask_file_name=None): # workspaces self._event_ws_name = os.path.basename(nexus_file_name).split('.')[0] - self._sample_log_dict = dict() + self.__load_logs() + + # load the mask + self.mask_array = None # TODO to promote direct access + if mask_file_name: + self.__load_mask(mask_file_name) + + self._hidra_workspace = workspaces.HidraWorkspace(self._nexus_name) - self._hydra_workspace = workspaces.HidraWorkspace(self._nexus_name) # Set a default instrument with this workspace # set up instrument - # initialize instrument: hard code! - instrument = AnglerCameraDetectorGeometry(1024, 1024, 0.0003, 0.0003, 0.985, False) - self._hydra_workspace.set_instrument_geometry(instrument) + # initialize instrument with hard coded values + instrument = AnglerCameraDetectorGeometry(NUM_PIXEL_1D, NUM_PIXEL_1D, PIXEL_SIZE, PIXEL_SIZE, + ARM_LENGTH, False) + self._hidra_workspace.set_instrument_geometry(instrument) # project file self._project_file = None - self._starttime = 0. # start filtering at the beginning of the run - def __del__(self): - # all of the workspaces that were created should be deleted if they haven't been already - for name in [self._event_ws_name]: - if name in mtd: - DeleteWorkspace(Workspace=name) + if self._event_ws_name in mtd: + DeleteWorkspace(Workspace=self._event_ws_name, EnableLogging=False) + + def __load_logs(self): + '''Use mantid to load the logs then set up the Splitters object''' + self._event_wksp = LoadEventNexus(Filename=self._nexus_name, OutputWorkspace=self._event_ws_name, + MetaDataOnly=True, LoadMonitors=False) + + # raise an exception if there is only one scan index entry + # this is an underlying assumption of the rest of the code + if self._event_wksp.run()['scan_index'].size() == 1 \ + or np.unique(self._event_wksp.run()['scan_index'].value).size == 1: + self._splitter = None + else: + # object to be used for splitting times + self._splitter = Splitter(self._event_wksp.run()) + + def __load_mask(self, mask_file_name): + # Check input + checkdatatypes.check_file_name(mask_file_name, True, False, False, 'Mask XML file') + if self._event_wksp is None: + raise RuntimeError('Meta data only workspace {} does not exist'.format(self._event_ws_name)) + + # Load mask XML to workspace + mask_ws_name = os.path.basename(mask_file_name.split('.')[0]) + mask_ws = LoadMask(Instrument='nrsf2', InputFile=mask_file_name, RefWorkspace=self._event_wksp, + OutputWorkspace=mask_ws_name) + + # Extract mask out + # get the Y array from mask workspace: shape = (1048576, 1) + self.mask_array = mask_ws.extractY().flatten() + # in Mantid's mask workspace: one stands delete, zero stands for keep + # we multiply by the value: zero is delete, one is keep + self.mask_array = 1 - self.mask_array.astype(int) + + # clean up + DeleteWorkspace(Workspace=mask_ws_name) + + def _generate_subrun_event_indices(self, pulse_time_array, event_index_array, num_events): + # convert times to array indices + subrun_pulseindex_array = np.searchsorted(pulse_time_array, self._splitter.times) + + # locations that are greater than the number of pixels + mask = subrun_pulseindex_array < event_index_array.size + + # it doesn't matter what the initial values are + subrun_event_index = np.empty(subrun_pulseindex_array.size, dtype=subrun_pulseindex_array.dtype) + # standard method is mappping + subrun_event_index[mask] = event_index_array[subrun_pulseindex_array[mask]] + # things off the end should be set to consume the rest of the events + subrun_event_index[np.logical_not(mask)] = num_events + 1 + + # make sure filter is sorted + if not np.all(subrun_event_index[:-1] <= subrun_event_index[1:]): + raise RuntimeError('Filter indices are not ordered: {}'.format(subrun_event_index)) + + return subrun_event_index + + def split_events_sub_runs(self): + '''Filter the data by ``scan_index`` and set counts array in the hidra_workspace''' + # Load: this h5 will be opened all the time + with h5py.File(self._nexus_name, 'r') as nexus_h5: + bank1_events = nexus_h5['entry']['bank1_events'] + # Check number of neutron events. Raise exception if there is no neutron event + if bank1_events['total_counts'].value[0] < 0.1: + # no counts + raise RuntimeError('Run {} has no count. Proper reduction requires the run to have count' + ''.format(self._nexus_name)) + + # detector id for the events + event_id_array = bank1_events['event_id'].value + + if self._splitter: + # get event index array: same size as pulse times + event_index_array = bank1_events['event_index'].value + # get pulse times + pulse_time_array = convert_pulses_to_datetime64(bank1_events['event_time_zero']) + subrun_eventindex_array = self._generate_subrun_event_indices(pulse_time_array, event_index_array, + event_id_array.size) + # reduce memory foot print + del pulse_time_array, event_index_array + + # split data + subruns = list() + if self._splitter: + for subrun, start_event_index, stop_event_index in zip(self._splitter.subruns.tolist(), + subrun_eventindex_array[::2].tolist(), + subrun_eventindex_array[1::2].tolist()): + subruns.append(subrun) + # get sub set of the events falling into this range + # and count the occurrence of each event ID (aka detector ID) as counts on each detector pixel + hist = np.bincount(event_id_array[start_event_index:stop_event_index], minlength=HIDRA_PIXEL_NUMBER) + + # mask (set to zero) the pixels that are not wanted + if self.mask_array is not None: + assert hist.shape == self.mask_array.shape + hist *= self.mask_array + + # set it in the workspace + self._hidra_workspace.set_raw_counts(int(subrun), hist) + else: # or histogram everything + subruns.append(1) + hist = np.bincount(event_id_array, minlength=HIDRA_PIXEL_NUMBER) + + # mask (set to zero) the pixels that are not wanted + if self.mask_array is not None: + assert hist.shape == self.mask_array.shape + hist *= self.mask_array + + # set it in the workspace + self._hidra_workspace.set_raw_counts(1, hist) + + return np.array(subruns) + + def split_sample_logs(self, subruns): + """Create dictionary for sample log of a sub run - def convert(self, use_mantid=False): - """Main method to convert NeXus file to HidraProject File by + Goal: + 1. set sample logs on the hidra workspace + 2. set duration on the hidra worksapce + """ + run_obj = self._event_wksp.run() - 1. split the workspace to sub runs - 2. for each split workspace, aka a sub run, get the total counts for each spectrum and save to a 1D array + # this contains all of the sample logs + sample_log_dict = dict() - Parameters - ---------- - use_mantid : bool - Flag to use Mantid library to convert NeXus (True); - Otherwise, use PyRS/Python algorithms to convert NeXus + if self._splitter: + log_array_size = self._splitter.subruns.shape[0] + else: + log_array_size = 1 - Returns - ------- - pyrs.core.workspaces.HidraWorkspace - HidraWorkspace for converted data + # loop through all available logs + for log_name in run_obj.keys(): + # create and calculate the sample log + sample_log_dict[log_name] = self.__split_property(run_obj, log_name, log_array_size) + # END-FOR - """ - # This is a quick fix: TODO will make a proper refactor in future - if not use_mantid: - # Use PyRS/converter to load and split sub runs - try: - sub_run_counts, self._sample_log_dict, mask_array = load_split_nexus_python(self._nexus_name, - self._mask_file_name) - # set counts to each sub run - for sub_run in sub_run_counts: - self._hydra_workspace.set_raw_counts(sub_run, sub_run_counts[sub_run]) - # set mask - if mask_array is not None: - self._hydra_workspace.set_detector_mask(mask_array, is_default=True) - # get sub runs - sub_runs = numpy.array(sorted(sub_run_counts.keys())) - except RuntimeError as run_err: - if str(run_err).count('Sub scan') == 1 and str(run_err).count('is not valid') == 1: - # RuntimeError: Sub scan (time = [0.], value = [0]) is not valid - # use Mantid to reduce - use_mantid = True - else: - # unhandled exception: re-throw - raise run_err + # create a fictional log for duration + if HidraConstants.SUB_RUN_DURATION not in sample_log_dict: + if self._splitter: + sample_log_dict[HidraConstants.SUB_RUN_DURATION] = self._splitter.durations + else: + duration = np.ndarray(shape=(log_array_size,), dtype=float) + duration[0] = run_obj.getPropertyAsSingleValue('duration') + sample_log_dict[HidraConstants.SUB_RUN_DURATION] = duration - if use_mantid: - # Use Mantid algorithms to load and split sub runs - # Load data file, split to sub runs and sample logs - self._load_mask_event_nexus(True) - self._determine_start_time() - self._split_sub_runs() - sub_runs = self._sample_log_dict['scan_index'] - # END-IF - - # Add the sample logs to the hidra workspace - # sub_runs = self._sample_log_dict['scan_index'] - - for log_name in self._sample_log_dict: + # set the logs on the hidra workspace + for log_name, log_value in sample_log_dict.items(): if log_name in ['scan_index', HidraConstants.SUB_RUNS]: continue # skip 'SUB_RUNS' - self._hydra_workspace.set_sample_log(log_name, sub_runs, self._sample_log_dict[log_name]) - - return self._hydra_workspace + self._hidra_workspace.set_sample_log(log_name, subruns, log_value) - def _set_counts(self, sub_run, wkspname): - self._hydra_workspace.set_raw_counts(sub_run, mtd[wkspname].extractY()) + return sample_log_dict # needed for testing - def _create_sample_log_dict(self, wkspname, sub_run_index, log_array_size): - """Create dictioonary for sample log of a sub run - - Goal: - 1. set self._sample_log_dict[log_name][sub_run_index] with log value (single or time-averaged) - 2. set self._sample_log_dict[HidraConstants.SUB_RUN_DURATION][sub_run_index] with duration + def __split_property(self, runObj, log_name, log_array_size): + """Calculate the mean value of the sample log "within" the sub run time range Parameters ---------- - wkspname - sub_run_index + log_property + splitter_times log_array_size Returns ------- - + numpy.ndarray + split logs """ - # this contains all of the sample logs - runObj = mtd[wkspname].run() - # loop through all available logs - for log_name in runObj.keys(): - log_value, log_dtype = self._get_log_value_and_type(runObj, log_name) - - # if the entry for this log is not created, create it! - if log_name not in self._sample_log_dict: - self._sample_log_dict[log_name] = numpy.ndarray(shape=(log_array_size, ), - dtype=log_dtype) - self._sample_log_dict[log_name][sub_run_index] = log_value - # END-FOR + # Init split sample logs + log_property = runObj[log_name] + log_dtype = log_property.dtype() + split_log = np.ndarray(shape=(log_array_size,), dtype=log_dtype) - # create a fictional log for duration - if HidraConstants.SUB_RUN_DURATION not in self._sample_log_dict: - self._sample_log_dict[HidraConstants.SUB_RUN_DURATION] = numpy.ndarray(shape=(log_array_size, ), - dtype=float) - self._sample_log_dict[HidraConstants.SUB_RUN_DURATION][sub_run_index] \ - = self._calculate_sub_run_duration(runObj) + if self._splitter and isinstance(log_property.value, np.ndarray) and str(log_dtype) in ['f', 'i']: + # Float or integer time series property: split and get time average + for i_sb in range(log_array_size): + split_log[i_sb] = calculate_sub_run_time_average(log_property, + self._splitter.propertyFilters[i_sb]) + else: + try: + split_log[:] = runObj.getPropertyAsSingleValue(log_name) + except ValueError: + if isinstance(log_property.value, str): + split_log[:] = log_property.value + elif isinstance(log_property.value, list): + split_log[:] = log_property.value[0] + else: + raise ValueError('Cannot filter log "{}" of type "{}"'.format(log_name, log_dtype)) - def _calculate_sub_run_duration(self, runObj): - """Calculate the duration of a sub run from the logs + return split_log - The duration of each sub run is calculated from sample log 'splitter' with unit as second + def convert(self, use_mantid=False): + """Main method to convert NeXus file to HidraProject File by + + 1. split the workspace to sub runs + 2. for each split workspace, aka a sub run, get the total counts for each spectrum and save to a 1D array + + Parameters + ---------- + use_mantid : bool + Flag to use Mantid library to convert NeXus (True); + Otherwise, use PyRS/Python algorithms to convert NeXus Returns ------- - float - The sub run's duration + pyrs.core.workspaces.HidraWorkspace + HidraWorkspace for converted data """ - # get splitter - if runObj.hasProperty('splitter'): - # calculate duration - splitter_times = runObj.getProperty('splitter').times.astype(float) * 1E-9 - splitter_value = runObj.getProperty('splitter').value + if use_mantid: + raise RuntimeError('use_mantid=True is no longer supported') - if splitter_value[0] == 0: - splitter_times = splitter_times[1:] - assert len(splitter_times) % 2 == 0, 'If splitter starts from 0, there will be odd number of ' \ - 'splitter times; otherwise, even number' + # set counts to each sub run + sub_runs = self.split_events_sub_runs() - sub_split_durations = splitter_times[1::2] - splitter_times[::2] + # set mask + if self.mask_array is not None: + self._hidra_workspace.set_detector_mask(self.mask_array, is_default=True) - duration = numpy.sum(sub_split_durations) - else: - # no splitter (which is not right), use the duration property - duration = runObj.getPropertyAsSingleValue('duration') + self.split_sample_logs(sub_runs) - return duration + return self._hidra_workspace def save(self, projectfile): """ @@ -222,359 +501,6 @@ def save(self, projectfile): hydra_file = HidraProjectFile(projectfile, HidraProjectFileMode.OVERWRITE) # Set geometry - # if instrument is None: - # # initialize instrument: hard code! - # instrument = AnglerCameraDetectorGeometry(1024, 1024, 0.0003, 0.0003, 0.985, False) - hydra_file.write_instrument_geometry(HidraSetup(self._hydra_workspace.get_instrument_setup())) - - self._hydra_workspace.save_experimental_data(hydra_file) - - @staticmethod - def _get_log_value_and_type(runObj, name): - """ - Calculate the mean value of the sample log "within" the sub run time range - :param name: Mantid run property's name - :return: - """ - log_property = runObj.getProperty(name) - log_dtype = log_property.dtype() - try: - log_value = time_average_value(runObj, name) - # return runObj.getPropertyAsSingleValue(name), log_dtype - return log_value, log_dtype - except ValueError: - # if the value is a string, just return it - if isinstance(log_property.value, str): - return log_property.value, log_dtype - elif isinstance(log_property.value, list): - return log_property.value[0], log_dtype - else: - raise RuntimeError('Cannot convert "{}" to a single value'.format(name)) - - def _load_mask_event_nexus(self, extract_mask): - """Loads the event file using instance variables - - If mask file is not None, then also mask the EventWorkspace - - Parameters - ---------- - extract_mask : bool - If True, extract the mask out of MaskWorkspace and set HidraWorkspace - - Returns - ------- - None - - """ - # Load - ws = LoadEventNexus(Filename=self._nexus_name, OutputWorkspace=self._event_ws_name) - - # Mask - if self._mask_file_name is not None: - # Load mask with reference to event workspace just created - mask_ws_name = os.path.basename(self._mask_file_name).split('.')[0] + '_mask' - - # check zero spectrum - counts_vec = ws.extractY() - num_zero = numpy.where(counts_vec < 0.5)[0].shape[0] - - mask_ws = LoadMask(Instrument='nrsf2', InputFile=self._mask_file_name, RefWorkspace=self._event_ws_name, - OutputWorkspace=mask_ws_name) - - # Extract mask out - if extract_mask: - # get the Y array from mask workspace: shape = (1048576, 1) - mask_array = mask_ws.extractY() - # in Mantid's mask workspace, 1 stands for mask (value cleared), 0 stands for non-mask (value kept) - mask_array = 1 - mask_array.astype(int) - # set the HidraWorkspace - self._hydra_workspace.set_detector_mask(mask_array, is_default=True) - - # Mask detectors and set all the events in mask to zero - MaskDetectors(Workspace=self._event_ws_name, MaskedWorkspace=mask_ws_name) - - ws = mtd[self._event_ws_name] - counts_vec = ws.extractY() - self._log.information('{}: number of extra masked spectra = {}' - ''.format(self._event_ws_name, numpy.where(counts_vec < 0.5)[0].shape[0] - num_zero)) - - # get the start time from the run object - self._starttime = numpy.datetime64(mtd[self._event_ws_name].run()['start_time'].value) - - return - - def _determine_start_time(self, abs_tolerance=0.05): - '''This goes through a subset of logs and compares when they actually - get to their specified setpoint, updating the start time for - event filtering. When this is done ``self._starttime`` will have been updated. - - Parameters - ---------- - abs_tolerance: float - When then log is within this absolute tolerance of the setpoint, it is correct - ''' - # static view of the run object - runObj = mtd[self._event_ws_name].run() - - # loop through the 'special' logs - for logname in ['sx', 'sy', 'sz', '2theta', 'omega', 'chi', 'phi']: - if logname not in runObj: - continue # log doesn't exist - not a good one to look at - if logname + 'Setpoint' not in runObj: - continue # log doesn't have a setpoint - not a good one to look at - - # get the observed values of the log - observed = runObj[logname].value - if len(observed) <= 1 or observed.std() <= .5 * abs_tolerance: - continue # don't bother if the log is constant within half of the tolerance - - # look for the setpoint and find when the log first got there - setPoint = runObj[logname + 'Setpoint'].value[0] # only look at first setpoint - for i, value in enumerate(observed): - if abs(value - setPoint) < abs_tolerance: - # pick the larger of what was found and the previous largest value - self._starttime = max(runObj[logname].times[i], self._starttime) - break - - # unset the start time if it is before the actual start of the run - if self._starttime <= numpy.datetime64(mtd[self._event_ws_name].run()['start_time'].value): - self._starttime = None - else: - print('[DEBUG] Shift from start_time = {}' - ''.format(self._starttime - numpy.datetime64(mtd[self._event_ws_name].run()['start_time'].value))) - - def _split_sub_runs(self): - """Performing event filtering according to sample log sub-runs - - DAS log may not be correct from the run start, - - Returns - ------- - dict - split workspaces: key = sub run number (integer), value = workspace name (string) - - """ - - # first remove the data before the previously calculated start time - # don't bother if starttime isn't set - if self._starttime: - # numpy only likes integers for timedeltas - duration = int(mtd[self._event_ws_name].run().getPropertyAsSingleValue('duration')) - duration = numpy.timedelta64(duration, 's') + numpy.timedelta64(300, 's') # add 5 minutes - FilterByTime(InputWorkspace=self._event_ws_name, - OutputWorkspace=self._event_ws_name, - AbsoluteStartTime=str(self._starttime), - AbsoluteStopTime=str(self._starttime + duration)) - - # determine the range of subruns being used - scan_index = mtd[self._event_ws_name].run()[SUBRUN_LOGNAME].value - scan_index_min = scan_index.min() - scan_index_max = scan_index.max() - multiple_subrun = bool(scan_index_min != scan_index_max) - - if multiple_subrun: - # determine the duration of each subrun by correlating to the scan_index - scan_times = mtd[self._event_ws_name].run()[SUBRUN_LOGNAME].times - durations = {} - for timedelta, subrun in zip(scan_times[1:] - scan_times[:-1], scan_index[:-1]): - timedelta /= numpy.timedelta64(1, 's') # convert to seconds - if subrun not in durations: - durations[subrun] = timedelta - else: - durations[subrun] += timedelta - if 0 in durations: - del durations[0] - durations[scan_index_max] = 0. - numpy.sum(durations.values()) - durations[scan_index_max] = mtd[self._event_ws_name].run()['duration'].value \ - - numpy.sum(durations.values()) - if durations[scan_index_max] < 0.: - raise RuntimeError('Got negative duration ({}s) for subrun={}' - ''.format(durations[scan_index_max], scan_index_max)) - - # create the sorted, unique value version of the scan index and set it on the output workspace - scan_index = numpy.unique(scan_index) - scan_index.sort() - if scan_index[0] == 0: - scan_index = scan_index[1:] - self._hydra_workspace.set_sub_runs(scan_index) - - # skip scan_index=0 - # the +1 is to make it inclusive - for subrun_index, subrun in enumerate(scan_index): - self._log.information('Filtering scan_index={}'.format(subrun)) - # pad up to 5 zeros - ws_name = '{}_split_{:05d}'.format(self._event_ws_name, subrun) - # filter out the subrun - this assumes that subruns are integers - FilterByLogValue(InputWorkspace=self._event_ws_name, - OutputWorkspace=ws_name, - LogName=SUBRUN_LOGNAME, - LogBoundary='Left', - MinimumValue=float(subrun) - .25, - MaximumValue=float(subrun) + .25) - - # update the duration in the filtered workspace - duration = FloatPropertyWithValue('duration', durations[subrun]) - duration.units = 'second' - mtd[ws_name].run()['duration'] = duration - - # subrun found should match what was requested - if abs(mtd[ws_name].run().getPropertyAsSingleValue('scan_index') - subrun) > .1: - subrun_obs = mtd[ws_name].run().getPropertyAsSingleValue('scan_index') - # TODO this should match exactly - doesn't in test_split_log_time_average - self._log.warning('subrun {:.1f} is not the expected value {}'.format(subrun_obs, subrun)) - # raise RuntimeError('subrun {:.1f} is not the expected value {}'.format(subrun_obs, subrun)) - - # put the counts in the workspace - self._set_counts(subrun, ws_name) - - # put the sample logs together - self._create_sample_log_dict(ws_name, subrun_index, len(scan_index)) - - # cleanup - DeleteWorkspace(Workspace=ws_name) - - else: # nothing to filter so just histogram it - self._hydra_workspace.set_sub_runs(numpy.arange(1, 2)) - subrun = 1 - ConvertToMatrixWorkspace(InputWorkspace=self._event_ws_name, - OutputWorkspace=self._event_ws_name) - - # put the counts in the workspace - self._set_counts(subrun, self._event_ws_name) - - # put the sample logs together - self._create_sample_log_dict(self._event_ws_name, 0, 1) - # force the subrun number rather than just using it - self._sample_log_dict['scan_index'][0] = subrun - - -def time_average_value(run_obj, log_name): - """Get time averaged value for TimeSeriesProperty or single value - - Parameters - ---------- - run_obj - log_name - - Returns - ------- - - """ - # Get property - log_property = run_obj.getProperty(log_name) - - has_splitter_log = run_obj.hasProperty('splitter') - if has_splitter_log: - splitter_times = run_obj.getProperty('splitter').times - splitter_value = run_obj.getProperty('splitter').value - else: - splitter_times = splitter_value = None - - if has_splitter_log and isinstance(log_property, - (Int32TimeSeriesProperty, Int64TimeSeriesProperty, FloatTimeSeriesProperty)): - # Integer or float time series property and this is a split workspace - try: - log_value = calculate_log_time_average(log_property.times, log_property.value, - splitter_times, splitter_value) - except RuntimeError as run_err: - # Sample log may not meet requirement - # TODO - log the error! - logger.warning('Failed on sample log {}. Cause: {}'.format(log_name, run_err)) - # use current Mantid method instead - log_value = run_obj.getPropertyAsSingleValue(log_name) - - else: - # No a split workspace - # If a split workspace: string, boolean and others won't have time average issue - # Get single value - log_value = run_obj.getPropertyAsSingleValue(log_name) - # this is a workaround for some runs where there is more than one value - # in the scan_index log. It takes the largest unique value - if log_name == 'scan_index': - log_value = log_property.value.max() - - return log_value - - -def calculate_log_time_average(log_times, log_value, splitter_times, splitter_value): - """Calculate time average for sample log of split - - Parameters - ---------- - log_times : ~numpy.ndarray - sample log series time. Allowed value are float (nanosecond) or numpy.datetime64 - log_value : numpy.ndarray - sample log value series - splitter_times : ndarray - numpy array for splitter time - splitter_value : ndarray - numpy array for splitter value wit alternative 0 and 1. 1 stands for the period of events included - - Returns - ------- - Float - - """ - # Determine T0 (starting of the time period) - start_split_index = 0 if splitter_value[0] == 1 else 1 - - # Convert time to float and in unit of second (this may not be necessary but good for test) - splitter_times = splitter_times.astype(float) * 1E-9 - time_start = splitter_times[start_split_index] - - # convert splitter time to relative to time_start in unit of second - splitter_times -= time_start - - # convert log time to relative to time_start in unit of second - log_times = log_times.astype(float) * 1E-9 - log_times -= time_start - - # Calculate time average - total_time = 0. - weighted_sum = 0. - num_periods = int(.5 * splitter_times.shape[0]) - - for iperiod in range(num_periods): - # determine the start and stop time - start_time = splitter_times[2 * iperiod + start_split_index] - stop_time = splitter_times[2 * iperiod + start_split_index + 1] - - logger.debug('Start/Stop Time: {} {}'.format(start_time, stop_time)) - - # update the total time - total_time += stop_time - start_time - - # get the (partial) interval from sample log - # logger.debug('Log times: {}'.format(log_times)) - log_start_index = bisect.bisect(log_times, start_time) - log_stop_index = bisect.bisect(log_times, stop_time) - logger.debug('Start/Stop index: {} {}'.format(log_start_index, log_stop_index)) - - if log_start_index == 0: - logger.information('Sample log time start time: {}'.format(log_times[0])) - logger.information('Splitter star time: {}'.format(start_time)) - raise RuntimeError('It is not expected that the run (splitter[0]) starts with no sample value recorded') - - # set the partial log time - partial_time_size = log_stop_index - log_start_index + 2 - partial_log_times = numpy.ndarray((partial_time_size, ), dtype=float) - partial_log_times[0] = start_time - partial_log_times[-1] = stop_time - # to 1 + time size - 2 = time size - 1 - partial_log_times[1:partial_time_size - 1] = log_times[log_start_index:log_stop_index] - # logger.debug('Processed log times: {}'.format(partial_log_times)) - - # set the partial value: v[0] is the value before log_start_index (to the right) - # so it shall start from log_start_index - 1 - # the case with log_start_index == 0 is ruled out - partial_log_value = log_value[log_start_index - 1:log_stop_index] - # logger.debug('Partial log value: {}'.format(partial_log_value)) - - # Now for time average - weighted_sum += numpy.sum(partial_log_value * (partial_log_times[1:] - partial_log_times[:-1])) - # END-FOR - - time_averaged = weighted_sum / total_time - - return time_averaged + hydra_file.write_instrument_geometry(HidraSetup(self._hidra_workspace.get_instrument_setup())) + # save experimental data/detector counts + self._hidra_workspace.save_experimental_data(hydra_file) diff --git a/pyrs/core/peak_profile_utility.py b/pyrs/core/peak_profile_utility.py index 935acd58e..85b53167a 100644 --- a/pyrs/core/peak_profile_utility.py +++ b/pyrs/core/peak_profile_utility.py @@ -20,6 +20,10 @@ def __str__(self): @staticmethod def getShape(shape): + try: # for python 3 + shape = shape.decode() + except (UnicodeDecodeError, AttributeError): + pass if shape in PeakShape: return shape else: @@ -46,6 +50,10 @@ def __str__(self): @staticmethod def getFunction(function): + try: # for python 3 + function = function.decode() + except (UnicodeDecodeError, AttributeError): + pass if function in BackgroundFunction: return function else: @@ -381,13 +389,13 @@ def calculate_effective_parameters(self, param_value_array, param_error_array): param_error_array['Mixing']) # Set - eff_error_array['Center'] = param_value_array['PeakCentre'] # center + eff_error_array['Center'] = param_error_array['PeakCentre'] # center eff_error_array['Height'] = heights_error[:] # height - eff_error_array['FWHM'] = param_value_array['FWHM'] # FWHM - eff_error_array['Mixing'] = param_value_array['Mixing'] # no mixing for Gaussian - eff_error_array['A0'] = param_value_array['A0'] # A0 - eff_error_array['A1'] = param_value_array['A1'] # A1 - eff_error_array['Intensity'] = param_value_array['Intensity'] # intensity + eff_error_array['FWHM'] = param_error_array['FWHM'] # FWHM + eff_error_array['Mixing'] = param_error_array['Mixing'] # no mixing for Gaussian + eff_error_array['A0'] = param_error_array['A0'] # A0 + eff_error_array['A1'] = param_error_array['A1'] # A1 + eff_error_array['Intensity'] = param_error_array['Intensity'] # intensity return eff_value_array, eff_error_array diff --git a/pyrs/core/powder_pattern.py b/pyrs/core/powder_pattern.py index 29437274c..5f2d6e3f8 100644 --- a/pyrs/core/powder_pattern.py +++ b/pyrs/core/powder_pattern.py @@ -126,10 +126,10 @@ def load_hidra_workspace(self, hd_workspace): ------- """ - # set workspace to reduction manager - self._reduction_manager.init_session(self._session, hd_workspace) # set the workspace to self self._hydra_ws = hd_workspace + # set workspace to reduction manager + self._reduction_manager.init_session(self._session, self._hydra_ws) def reduce_data(self, sub_runs, instrument_file, calibration_file, mask, mask_id=None, van_file=None, num_bins=1000): @@ -137,10 +137,11 @@ def reduce_data(self, sub_runs, instrument_file, calibration_file, mask, mask_id Parameters ---------- - sub_runs : List or None + sub_runs : List or numpy.ndarray or None sub run numbers to reduce instrument_file - calibration_file + calibration_file : str or None + path of calibration file (optionally) mask : str or numpy.ndarray or None Mask name or mask (value) array. None for no mask mask_id : str or None @@ -158,8 +159,6 @@ def reduce_data(self, sub_runs, instrument_file, calibration_file, mask, mask_id # Check inputs if sub_runs is None or not bool(sub_runs): # None or empty list sub_runs = self._hydra_ws.get_sub_runs() - else: - checkdatatypes.check_list('Sub runs', sub_runs) # instrument file if instrument_file is not None: @@ -171,8 +170,10 @@ def reduce_data(self, sub_runs, instrument_file, calibration_file, mask, mask_id geometry_calibration = False if calibration_file is not None: if calibration_file.lower().endswith('.json'): - geometry_calibration =\ - calibration_file_io.read_calibration_json_file(calibration_file_name=calibration_file)[0] + calib_values = calibration_file_io.read_calibration_json_file(calibration_file_name=calibration_file) + geometry_calibration = calib_values[0] + wave_length = calib_values[2] + self._hydra_ws.set_wavelength(wave_length, True) else: geometry_calibration =\ calibration_file_io.import_calibration_ascii_file(geometry_file_name=calibration_file) @@ -209,12 +210,14 @@ def plot_reduced_data(self, sub_run_number=None): plt.plot(vec_x, vec_y) plt.show() - def save_diffraction_data(self, output_file_name=None): + def save_diffraction_data(self, output_file_name=None, append_mode=False): """Save reduced diffraction data to Hidra project file Parameters ---------- output_file_name: None or str if None, then append result to the input file + append_mode : bool + flag to force project file in appending/READWRITE mode Returns ------- @@ -223,6 +226,10 @@ def save_diffraction_data(self, output_file_name=None): if output_file_name is None or output_file_name == self._hydra_file_name: file_name = self._hydra_file_name mode = HidraProjectFileMode.READWRITE + elif append_mode: + # Append to existing file + file_name = output_file_name + mode = HidraProjectFileMode.READWRITE else: file_name = output_file_name mode = HidraProjectFileMode.OVERWRITE @@ -239,5 +246,8 @@ def save_diffraction_data(self, output_file_name=None): if mode == HidraProjectFileMode.OVERWRITE: self._hydra_ws.save_experimental_data(out_file, ignore_raw_counts=True) + # Calibrated wave length shall be written + self._hydra_ws.save_wavelength(out_file) + # Write & close self._hydra_ws.save_reduced_diffraction_data(out_file) diff --git a/pyrs/core/reduce_hb2b_pyrs.py b/pyrs/core/reduce_hb2b_pyrs.py index 6e49ca58e..54d0c5998 100644 --- a/pyrs/core/reduce_hb2b_pyrs.py +++ b/pyrs/core/reduce_hb2b_pyrs.py @@ -510,7 +510,7 @@ def reduce_to_2theta_histogram(self, two_theta_bins, mask_array, two_theta_bins : numpy.ndarray 2theta bin boundaries to binned to mask_array : numpy.ndarray or None - mask + mask: 1 to keep, 0 to mask (exclude) is_point_data : bool Flag whether the output is point data (numbers of X and Y are same) vanadium_counts_array : None or numpy.ndarray @@ -547,7 +547,7 @@ def reduce_to_2theta_histogram(self, two_theta_bins, mask_array, # exclude mask from histogramming counts_array = counts_array[np.where(mask_array == 1)] pixel_2theta_array = pixel_2theta_array[np.where(mask_array == 1)] - if vanadium_counts_array: + if vanadium_counts_array is not None: vanadium_counts_array = vanadium_counts_array[np.where(mask_array == 1)] else: # no mask: do nothing diff --git a/pyrs/core/reduction_manager.py b/pyrs/core/reduction_manager.py index 3c7750d31..e376657eb 100644 --- a/pyrs/core/reduction_manager.py +++ b/pyrs/core/reduction_manager.py @@ -1,4 +1,5 @@ # Reduction engine including slicing +from __future__ import (absolute_import, division, print_function) # python3 compatibility import os import numpy as np from pyrs.core import workspaces @@ -75,7 +76,7 @@ def get_reduced_diffraction_data(self, session_name, sub_run=None, mask_id=None) :param mask_id: :return: 2-vectors: 2theta and intensity """ - checkdatatypes.check_string_variable('Session name', session_name, self._session_dict.keys()) + checkdatatypes.check_string_variable('Session name', session_name, list(self._session_dict.keys())) workspace = self._session_dict[session_name] data_set = workspace.get_reduced_diffraction_data(sub_run, mask_id) @@ -88,7 +89,7 @@ def get_sub_runs(self, session_name): :param session_name: :return: """ - checkdatatypes.check_string_variable('Session name', session_name, self._session_dict.keys()) + checkdatatypes.check_string_variable('Session name', session_name, list(self._session_dict.keys())) workspace = self._session_dict[session_name] return workspace.get_sub_runs() @@ -100,7 +101,7 @@ def get_sub_run_detector_counts(self, session_name, sub_run): :param sub_run: :return: """ - checkdatatypes.check_string_variable('Session name', session_name, self._session_dict.keys()) + checkdatatypes.check_string_variable('Session name', session_name, list(self._session_dict.keys())) workspace = self._session_dict[session_name] return workspace.get_detector_counts(sub_run) @@ -154,7 +155,7 @@ def get_sub_run_2theta(self, session_name, sub_run): :param sub_run: :return: """ - checkdatatypes.check_string_variable('Session name', session_name, self._session_dict.keys()) + checkdatatypes.check_string_variable('Session name', session_name, list(self._session_dict.keys())) workspace = self._session_dict[session_name] return workspace.get_detector_2theta(sub_run) @@ -175,7 +176,7 @@ def get_hidra_workspace(self, session_name): :param session_name: string as the session/workspace name :return: HidraWorkspace instance """ - checkdatatypes.check_string_variable('Session name', session_name, self._session_dict.keys()) + checkdatatypes.check_string_variable('Session name', session_name, list(self._session_dict.keys())) # Check availability if session_name not in self._session_dict: @@ -367,7 +368,7 @@ def get_mask_vector(self, mask_id): :param mask_id: String as ID :return: a 1D array (0: mask, 1: keep) """ - checkdatatypes.check_string_variable('Mask ID', mask_id, self._loaded_mask_dict.keys()) + checkdatatypes.check_string_variable('Mask ID', mask_id, list(self._loaded_mask_dict.keys())) return self._loaded_mask_dict[mask_id][0] @@ -443,20 +444,23 @@ def reduce_diffraction_data(self, session_name, apply_calibrated_geometry, num_b workspace = self._session_dict[session_name] # Process mask: No mask, Mask ID and mask vector + default_mask = workspace.get_detector_mask(is_default=True, mask_id=None) if mask is None: - mask_vec = None - # mask_id = None + # No use mask: use default detector mask. It could be None but does not matter + mask_vec = default_mask elif isinstance(mask, str): # mask is determined by mask ID mask_vec = self.get_mask_vector(mask) - # mask_id = mask else: # user supplied an array for mask checkdatatypes.check_numpy_arrays('Mask', [mask], dimension=1, check_same_shape=False) mask_vec = mask - # mask_id = 'Mask_{0:04}'.format(random.randint(1, 1000)) # END-IF-ELSE + # Operate AND with default mask + if default_mask is not None: + mask_vec *= default_mask + # Apply (or not) instrument geometry calibration shift if isinstance(apply_calibrated_geometry, instrument_geometry.AnglerCameraDetectorShift): det_pos_shift = apply_calibrated_geometry @@ -591,7 +595,8 @@ def convert_counts_to_diffraction(self, reduction_engine, l2, instrument_2theta, det_counts_array two_theta_range num_bins - mask_array + mask_array : numpy.ndarray or None + mask: 1 to keep, 0 to mask (exclude) vanadium_array Returns diff --git a/pyrs/core/summary_generator.py b/pyrs/core/summary_generator.py index 68091e978..bd8383f50 100644 --- a/pyrs/core/summary_generator.py +++ b/pyrs/core/summary_generator.py @@ -1,6 +1,7 @@ """ This module generates reduction summary for user in plain text CSV file """ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from pyrs.core.peak_profile_utility import EFFECTIVE_PEAK_PARAMETERS # TODO get from the first peak collection # Default summary titles shown in the CSV file. This is a list of tuples ot enforce order @@ -122,6 +123,7 @@ def write_csv(self, sample_logs, peak_collections, tolerance=1E-10): def _classify_logs(self, sample_logs, tolerance): self._constant_logs = [logname for logname in sample_logs.constant_logs(tolerance) if logname in self._sample_log_list] + self._constant_logs.sort() # keep the order stable for python3 tests # loop through all of the requested logs and classify as present or missing for logname in self._sample_log_list: @@ -141,6 +143,10 @@ def _write_header_information(self, handle, sample_logs): value = '' if logname in sample_logs: value = sample_logs[logname][0] # only use first value + try: # for python 3 + value.decode() + except (UnicodeDecodeError, AttributeError): + pass self._header_information[logname] = value # fix up particular values @@ -148,11 +154,19 @@ def _write_header_information(self, handle, sample_logs): if logname == 'run_number': self._header_information[logname] = int(self._header_information[logname]) elif logname == 'experiment_identifier': - self._header_information[logname] = self._header_information[logname].split('-')[-1] + try: # for python 3 + experiment_identifier = self._header_information[logname].decode() + except (UnicodeDecodeError, AttributeError): + experiment_identifier = self._header_information[logname] + self._header_information[logname] = experiment_identifier.split('-')[-1] # write out the text for logname, label in HEADER_MAPPING: value = self._header_information[logname] + try: # for python 3 + value = value.decode() + except (UnicodeDecodeError, AttributeError): + pass if value: line = ' = '.join((label, str(value))) else: @@ -168,13 +182,17 @@ def _write_header_constants(self, handle, sample_logs): '''Write only the sample logs that are constants into the header. These do not appear in the body. ''' for name in self._constant_logs: + try: # for python 3 + value = sample_logs[name].decode() + except (UnicodeDecodeError, AttributeError): + value = sample_logs[name] try: - handle.write('# {} = {:.5g} +/- {:.2g}\n'.format(name, sample_logs[name].mean(), - sample_logs[name].std())) + handle.write('# {} = {:.5g} +/- {:.2g}\n'.format(name, value.mean(), + value.std())) except TypeError: # strings don't have a "mean" or "std" so use the first value # this is intended for strings - handle.write('# {} = {}\n'.format(name, sample_logs[name][0])) + handle.write('# {} = {}\n'.format(name, value[0])) def _write_column_names(self, handle, peak_collections): '''This writes the names of all of the columns''' @@ -186,9 +204,11 @@ def _write_column_names(self, handle, peak_collections): for peak_collection in peak_collections: tag = peak_collection.peak_tag # name of the peak # values first + column_names.append('{}_dspacing_center'.format(tag)) for param in EFFECTIVE_PEAK_PARAMETERS: column_names.append('{}_{}'.format(tag, param)) # errors after values + column_names.append('{}_dspacing_center_error'.format(tag)) for param in EFFECTIVE_PEAK_PARAMETERS: column_names.append('{}_{}_error'.format(tag, param)) column_names.append('{}_chisq'.format(tag)) @@ -203,23 +223,26 @@ def _write_data(self, handle, sample_logs, peak_collections): log_names = [name for name in self._present_logs if name not in self._constant_logs] - for i in range(len(sample_logs.subruns)): + for subrun_index in range(len(sample_logs.subruns)): line = [] # sub-run goes in first - line.append(str(sample_logs.subruns[i])) + line.append(str(sample_logs.subruns[subrun_index])) # then sample logs for name in log_names: - line.append(str(sample_logs[name][i])) # get by index rather than subrun + line.append(str(sample_logs[name][subrun_index])) # get by index rather than subrun for peak_collection in peak_collections: fit_cost = peak_collection.fitting_costs + dspacing_center, dspacing_center_error = peak_collection.get_dspacing_center() values, errors = peak_collection.get_effective_params() - for value in values[i]: + line.append(str(dspacing_center[subrun_index])) + for value in values[subrun_index]: line.append(str(value)) - for value in errors[i]: + line.append(str(dspacing_center_error[subrun_index])) + for value in errors[subrun_index]: line.append(str(value)) - line.append(str(fit_cost[i])) + line.append(str(fit_cost[subrun_index])) handle.write(self.separator.join(line) + '\n') diff --git a/pyrs/core/workspaces.py b/pyrs/core/workspaces.py index 791230368..c25ad9509 100644 --- a/pyrs/core/workspaces.py +++ b/pyrs/core/workspaces.py @@ -1,4 +1,5 @@ # Data manager +from __future__ import (absolute_import, division, print_function) # python3 compatibility import numpy from pyrs.dataobjects import HidraConstants, SampleLogs from pyrs.projectfile import HidraProjectFile @@ -26,6 +27,7 @@ def __init__(self, name='hidradata'): self._raw_counts = dict() # dict [sub-run] = count vector # wave length + self._wave_length = None # single wave length for all sub runs self._wave_length_dict = None self._wave_length_calibrated_dict = None @@ -56,6 +58,16 @@ def name(self): """ return self._name + @property + def hidra_project_file(self): + """Name of the associated HiDRA project file + + Returns + ------- + + """ + return self._project_file_name + def _load_raw_counts(self, hidra_file): """ Load raw detector counts from HIDRA file :param hidra_file: @@ -132,11 +144,12 @@ def _load_instrument(self, hidra_file): self._instrument_setup = hidra_file.read_instrument_geometry() def _load_masks(self, hidra_file): - """ + """Load masks from Hidra project file Parameters ---------- - hidra_file + hidra_file : pyrs.projectfile.file_object.HidraProjectFile + Hidra project file instance Returns ------- @@ -169,14 +182,22 @@ def _load_sample_logs(self, hidra_file): self._sample_logs = hidra_file.read_sample_logs() def _load_wave_length(self, hidra_file): - """ Load wave length - :param hidra_file: HIDRA project file instance - :return: + """Load wave length from HidraProject file + + Parameters + ---------- + hidra_file : pyrs.projectfile.file_object.HidraProjectFile + Project file (instance) + + Returns + ------- + None + """ checkdatatypes.check_type('HIDRA project file', hidra_file, HidraProjectFile) # reset the wave length (dictionary) from HIDRA project file - self._wave_length_dict = hidra_file.read_wavelengths() + self._wave_length = hidra_file.read_wavelengths() def get_detector_2theta(self, sub_run): """ Get 2theta value from sample log @@ -222,12 +243,19 @@ def get_instrument_setup(self): return self._instrument_setup def get_detector_counts(self, sub_run): + """Get the detector counts of a sub run (split) + + Parameters + ---------- + sub_run : int + sub run number + + Returns + ------- + numpy.ndarray + """ - Get raw detector counts in the order of pixel IDs by a given sub run - :param sub_run: - :return: - """ - checkdatatypes.check_int_variable('Sub run number', sub_run, (1, None)) + checkdatatypes.check_int_variable('Sub run number', sub_run, (0, None)) # consider 0 as a single sub run if int(sub_run) not in self._raw_counts: raise RuntimeError('Sub run {} does not exist in loaded raw counts. FYI loaded ' 'sub runs are {}'.format(sub_run, self._raw_counts.keys())) @@ -248,12 +276,27 @@ def get_sub_runs(self): return self._sample_logs.subruns - def get_wavelength(self, calibrated, throw_if_not_set): - """ Get the wave length from the workspace - :param calibrated: Flag for returning calibrated wave length - :param throw_if_not_set: Flag to throw an exception if relative wave length (dict) is not set - :return: + def get_wavelength(self, calibrated, throw_if_not_set, sub_run=None): + """Get wave length + + Parameters + ---------- + calibrated : bool + whether the wave length is calibrated or raw + throw_if_not_set : bool + throw an exception if wave length is not set to workspace + sub_run : None or int + sub run number for the wave length associated with + + Returns + ------- + float or dict + """ + # Return the universal wave length if it is set + if sub_run is None and self._wave_length is not None: + return self._wave_length + if calibrated: # calibrated wave length if self._wave_length_calibrated_dict is None: @@ -274,6 +317,10 @@ def get_wavelength(self, calibrated, throw_if_not_set): else: wave_length_dict = self._wave_length_dict + # Return the wave length of the sub run + if sub_run is not None: + return wave_length_dict[sub_run] + return wave_length_dict def load_hidra_project(self, hidra_file, load_raw_counts, load_reduced_diffraction): @@ -322,8 +369,8 @@ def get_detector_mask(self, is_default, mask_id=None): Returns ------- - numpy.ndarray - detector mask + numpy.ndarray or None + detector mask. None in case no default detector mask """ # Default mask @@ -442,7 +489,7 @@ def get_reduced_diffraction_data(self, sub_run, mask_id=None): return vec_2theta, vec_intensity def get_sample_log_names(self): - return self._sample_logs.keys() + return sorted(self._sample_logs.keys()) def get_sample_log_value(self, sample_log_name, sub_run=None): """ @@ -459,7 +506,7 @@ def get_sample_log_value(self, sample_log_name, sub_run=None): """ checkdatatypes.check_string_variable('Sample log name', sample_log_name, - self._sample_logs.keys()) + list(self._sample_logs.keys())) log_value = self._sample_logs[sample_log_name, sub_run] @@ -494,7 +541,7 @@ def get_sample_log_values(self, sample_log_name, sub_runs=None): return self.get_sub_runs() checkdatatypes.check_string_variable('Sample log name', sample_log_name, - self._sample_logs.keys()) + list(self._sample_logs.keys())) return self._sample_logs[sample_log_name, sub_runs] @@ -763,6 +810,20 @@ def save_experimental_data(self, hidra_project, sub_runs=None, ignore_raw_counts hidra_project.append_experiment_log(log_name, sample_log_value) # END-FOR + # Save default mask + if self._default_mask is not None: + hidra_project.write_mask_detector_array(HidraConstants.DEFAULT_MASK, self._default_mask) + # Save other masks + for mask_id in self._mask_dict: + hidra_project.write_mask_detector_array(mask_id, self._mask_dict[mask_id]) + + # Save wave length + self.save_wavelength(hidra_project) + + def save_wavelength(self, hidra_project): + if self._wave_length is not None: + hidra_project.write_wavelength(self._wave_length) + def save_reduced_diffraction_data(self, hidra_project): """ Export reduced diffraction data to project :param hidra_project: HidraProjectFile instance @@ -778,7 +839,7 @@ def sample_log_names(self): return the sample log names :return: """ - return self._sample_logs.keys() + return sorted(self._sample_logs.keys()) @property def sample_logs_for_plot(self): @@ -792,6 +853,9 @@ def set_wavelength(self, wave_length, calibrated): :param calibrated: Flag for calibrated wave length :return: """ + # Set universal wave length + self._wave_length = wave_length + # Get the sub runs sub_runs = self.get_sub_runs() diff --git a/pyrs/dataobjects/sample_logs.py b/pyrs/dataobjects/sample_logs.py index 868ec98f5..ab8c4cc25 100644 --- a/pyrs/dataobjects/sample_logs.py +++ b/pyrs/dataobjects/sample_logs.py @@ -1,4 +1,5 @@ # extentable version of dict https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from collections import Iterable, MutableMapping import numpy as np from pyrs.dataobjects import HidraConstants @@ -174,7 +175,7 @@ def constant_logs(self, atol=0.): Logs with a smaller stddev than the atol (inclusive) will be considered constant''' result = list() # plottable logs are the numeric ones - for key in self.keys(): + for key in sorted(self.keys()): if key == self.SUBRUN_KEY: continue elif key in self._plottable: # plottable things are numbers diff --git a/pyrs/icons/horitzontal_splitter.png b/pyrs/icons/horitzontal_splitter.png deleted file mode 100644 index 2616af355..000000000 Binary files a/pyrs/icons/horitzontal_splitter.png and /dev/null differ diff --git a/pyrs/icons/horizontal_splitter_short.png b/pyrs/icons/horizontal_splitter_short.png new file mode 100644 index 000000000..6749fe2ae Binary files /dev/null and b/pyrs/icons/horizontal_splitter_short.png differ diff --git a/pyrs/icons/icons.qrc b/pyrs/icons/icons.qrc index e515ee249..05aa22484 100644 --- a/pyrs/icons/icons.qrc +++ b/pyrs/icons/icons.qrc @@ -1,7 +1,9 @@ horizontal_splitter.png + horizontal_splitter_short.png vertical_splitter.png + vertical_splitter_short.png warning_icon.png diff --git a/pyrs/icons/icons_rc.py b/pyrs/icons/icons_rc4.py similarity index 56% rename from pyrs/icons/icons_rc.py rename to pyrs/icons/icons_rc4.py index 968a0ea0d..e955cf7e7 100644 --- a/pyrs/icons/icons_rc.py +++ b/pyrs/icons/icons_rc4.py @@ -6,7 +6,7 @@ # # WARNING! All changes made in this file will be lost! -from qtpy import QtCore +from PyQt4 import QtCore qt_resource_data = "\ \x00\x00\x0e\x81\ @@ -244,6 +244,431 @@ \x26\x57\x8a\xe8\x4d\xca\x69\xdc\xb5\x79\xcd\x3f\x9f\xe6\x7d\x0b\ \x64\x55\xea\x96\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ +\x00\x00\x1a\x64\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x1a\x00\x00\x03\xc3\x08\x06\x00\x00\x00\xad\x93\x1b\xb4\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x08\x73\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\ +\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\ +\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\ +\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\ +\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\ +\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x65\ +\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\ +\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\x66\x2f\x31\x2e\ +\x30\x2f\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\ +\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\x31\x37\x20\x28\x4d\x61\ +\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\x78\x6d\x70\x3a\x43\x72\ +\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x36\x2d\x31\ +\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\x35\ +\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\ +\x61\x74\x65\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\ +\x31\x34\x3a\x31\x35\x3a\x31\x36\x2d\x30\x35\x3a\x30\x30\x22\x20\ +\x78\x6d\x70\x3a\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\ +\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\ +\x31\x35\x3a\x31\x36\x2d\x30\x35\x3a\x30\x30\x22\x20\x64\x63\x3a\ +\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\ +\x67\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\ +\x6f\x72\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\ +\x22\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\ +\x2e\x31\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\ +\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x38\x38\ +\x66\x34\x62\x61\x38\x63\x2d\x31\x34\x39\x61\x2d\x34\x33\x63\x34\ +\x2d\x39\x63\x65\x61\x2d\x31\x66\x61\x61\x33\x38\x61\x36\x34\x66\ +\x64\x30\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\ +\x6e\x74\x49\x44\x3d\x22\x61\x64\x6f\x62\x65\x3a\x64\x6f\x63\x69\ +\x64\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x61\x62\x63\x34\ +\x32\x31\x32\x37\x2d\x34\x61\x61\x32\x2d\x33\x34\x34\x37\x2d\x39\ +\x30\x39\x34\x2d\x38\x64\x33\x61\x64\x37\x30\x34\x32\x30\x33\x32\ +\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\x61\x6c\ +\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\ +\x64\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\ +\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\ +\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x74\x69\x66\x66\x3a\x4f\ +\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x22\x31\x22\x20\x74\ +\x69\x66\x66\x3a\x58\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x55\x6e\ +\x69\x74\x3d\x22\x32\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\x6c\x6f\ +\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\x20\x65\ +\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\x6e\x73\ +\x69\x6f\x6e\x3d\x22\x31\x32\x30\x34\x22\x20\x65\x78\x69\x66\x3a\ +\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\x6e\x3d\ +\x22\x32\x30\x22\x3e\x20\x3c\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\ +\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\x74\x6f\ +\x72\x73\x3e\x20\x3c\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\x3c\x72\ +\x64\x66\x3a\x6c\x69\x3e\x78\x6d\x70\x2e\x64\x69\x64\x3a\x62\x30\ +\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\ +\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\ +\x33\x62\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x2f\x72\x64\ +\x66\x3a\x42\x61\x67\x3e\x20\x3c\x2f\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\ +\x74\x6f\x72\x73\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\ +\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\ +\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\x61\x63\ +\x74\x69\x6f\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\ +\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\ +\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x2f\ +\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\ +\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x63\x36\x66\x63\x62\x66\ +\x38\x2d\x34\x34\x34\x35\x2d\x34\x66\x33\x62\x2d\x61\x34\x64\x36\ +\x2d\x33\x30\x36\x62\x66\x31\x30\x31\x62\x61\x30\x39\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x33\x38\x3a\x31\x32\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\ +\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\x22\x2f\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x38\x38\x66\x34\x62\ +\x61\x38\x63\x2d\x31\x34\x39\x61\x2d\x34\x33\x63\x34\x2d\x39\x63\ +\x65\x61\x2d\x31\x66\x61\x61\x33\x38\x61\x36\x34\x66\x64\x30\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x32\ +\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\x31\x35\x3a\x31\x36\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\ +\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\x79\ +\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x20\ +\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\x3f\x78\ +\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\x3f\x3e\ +\xb3\xee\x42\x20\x00\x00\x11\x97\x49\x44\x41\x54\x78\xda\xed\x5d\ +\x4b\xa8\x64\x47\x19\xfe\xeb\xf4\xbd\x73\xe7\x3d\x13\xcd\x3c\x9c\ +\xcc\x90\x51\x66\x91\x45\x92\x5d\x30\x0c\x12\x8c\x1b\x11\x42\x08\ +\x22\xba\x11\x71\x23\xba\x8c\x3b\x05\xdd\x89\x3b\x41\x70\x61\x16\ +\xae\x24\x20\x2e\x24\x91\x10\x05\x17\xd9\x68\x30\x10\x71\x30\x32\ +\x8c\x09\xa8\xe3\xcc\xe4\x31\x89\x33\x99\xe7\x7d\xf7\x29\xff\xea\ +\xdb\x7d\xe7\xdc\x9a\xaa\xf3\xa8\xf3\x7f\x75\xbb\xaf\x7f\x43\xd1\ +\xf7\xf6\xe9\x3e\xdf\xf9\xdf\x8f\xaa\x53\x67\xce\x5a\x4b\xbb\xe6\ +\x7f\x40\xf7\xbf\x2c\x8f\x5d\x3c\xf6\xf0\xf8\x88\x8a\xe2\x4f\xa6\ +\x28\xfe\xc2\x7f\xaf\xf3\x18\xf0\x18\x6e\x7c\xcb\x7e\xda\x96\xe5\ +\xd3\xfc\x7e\x66\xfc\xd9\xf2\x7d\x67\x5a\x5d\xfb\x11\xcd\x51\xa6\ +\x57\x5b\x20\xd3\xe2\x98\x95\x00\xca\x46\x91\x2f\x3c\x93\x03\xc8\ +\xe4\xa2\x88\x14\x48\x81\x14\x48\x81\x14\x48\x81\x14\x08\x14\xca\ +\x2d\x12\xc8\x4f\xab\x4c\x24\x71\x11\xcb\xeb\x4c\xe0\xc4\x06\x2d\ +\x23\xa3\x5a\xa7\x40\x0a\xa4\x40\x0a\xa4\x81\x2f\x1c\xdc\x4c\xdb\ +\xa0\x97\x1a\x61\x55\x19\x14\x68\x7b\x81\x5a\xb5\xd0\xba\xe6\x75\ +\x7e\x3e\x67\xba\xa8\xfe\x5c\x4f\x4a\x54\x19\xa6\x07\xc8\xa8\xaf\ +\x9b\x09\xa0\xd6\x5e\x20\x15\xc8\xb4\xf4\x02\x46\xa2\xb4\x54\x65\ +\x98\x0d\xcf\x50\x8c\x85\x6d\x3d\xc1\x1b\x14\x45\x26\x25\x54\xa8\ +\x32\x64\x4f\xf2\xb3\xf8\x3a\x95\x91\x02\xe5\x05\x0a\x05\xb7\x56\ +\x2e\xa9\x6b\xba\x15\x9a\x55\x36\x15\x67\xab\x32\x9a\xbe\x86\x46\ +\xb6\x76\x74\xeb\x25\x07\xa9\x2b\x34\x0c\x4a\xbd\xd5\x7b\x2b\x50\ +\xab\xd2\x52\x54\xbd\x1b\x9d\xa6\x34\x45\x76\x47\x29\x43\x68\x6e\ +\x0f\x4e\x91\x69\x5b\x52\x4e\xb5\x7a\x6f\x8b\x8c\x42\x1a\x29\xee\ +\xbd\x6d\x2e\xd6\xf9\xc2\x6f\xd5\x90\x9a\xba\x0e\xa4\x69\x21\x23\ +\x9a\x49\x19\x59\xca\x34\xfb\xef\x9f\xb0\x18\x7f\x36\x94\x00\x6a\ +\x93\xe5\x88\xd9\x51\xe8\x44\x16\xa1\x0c\x26\x62\x3f\x30\x17\x14\ +\x6b\x10\x16\xd2\x14\x15\x91\x42\xcc\x22\x58\x97\x64\x4f\x7d\xe2\ +\x91\x41\x7a\x6f\x83\x76\x41\x66\x3b\x80\x42\x1d\x48\x51\xd6\x75\ +\xaa\x87\xa4\x28\x0a\x29\x83\x38\x90\xa1\x84\x55\xec\x52\x79\x9d\ +\x41\xb9\x20\x48\xa6\x6a\x5a\xf8\x39\xf1\x4c\xd5\xe4\xc8\xeb\x7a\ +\x65\x43\x12\x0d\x8d\x02\xed\x19\xa0\xb9\x77\xf2\x34\xa9\x44\xd9\ +\x22\xee\x19\xa6\xa2\xcf\x00\xf3\x0c\xd9\xec\x68\xf6\x67\xc4\x9c\ +\x51\x0e\x72\x54\x13\x75\x82\x87\x55\x7c\xdb\x9e\xd7\x89\x87\x09\ +\xa8\xaf\xab\x5e\x75\x91\xa2\x85\x12\x09\x64\x56\x17\x04\x53\x6f\ +\x8b\xf4\x75\x31\x65\x10\x77\x41\x49\xae\x47\xd2\xa9\x12\x01\x7b\ +\x41\x16\x6d\x47\xf0\xfa\xa8\x37\x85\x12\x85\x98\xa8\x8c\x2c\xdd\ +\xdf\xcd\xef\xd4\x70\xef\x2b\x23\x43\xc0\x50\x0e\x77\x41\xa1\x95\ +\x9d\xa2\x59\x50\x9d\x5a\xcf\x76\x4a\x5c\xd7\x4a\xb3\x92\xea\xdd\ +\x54\xf5\xcd\x5e\x92\x1f\x0a\x13\x16\x99\x05\x25\xe5\x12\xba\xf2\ +\x36\x7b\xba\x65\x02\xca\x00\x6d\x75\x1a\x84\x8c\xda\x18\xae\xa8\ +\x0b\x32\x11\xef\x0d\x0b\x13\x9d\xf2\xbc\xa9\x0c\x7c\x36\x47\xc5\ +\x37\x99\x97\x28\x03\xfe\x0d\x5e\xc3\x6e\x5b\xd9\x62\xd0\xca\x20\ +\xbe\x45\x8c\xe9\x53\xba\x4c\xf5\x22\xb1\x10\x08\x74\x12\x04\x52\ +\x5a\x36\x85\x02\x68\x28\x87\xfb\x3a\xa8\xc1\x6e\x0b\xeb\xb6\x5d\ +\x19\x60\xcd\x5b\x71\x8a\x4c\x4d\xc1\x0c\x2d\x5b\x92\xe4\x34\xd5\ +\x33\xcb\x36\xe2\xe7\xc4\x3a\x90\x31\x99\x15\xd2\xea\x5d\xd4\x9c\ +\xd4\x48\x03\x85\x58\x57\x90\xf0\x0a\x8d\xea\x89\x3b\x3b\x57\x09\ +\xd6\x41\xb4\x2e\x04\x54\x50\x8b\x99\xcb\xd4\xc9\xc4\xaa\x9c\xc4\ +\xa6\x47\x8d\x77\xd5\x26\xa2\xe6\x22\xac\x2b\x3c\x19\x19\x8f\x9d\ +\x03\x49\xd6\xc5\x64\x51\x20\x29\xf2\x65\x24\xa6\x0c\xfe\xc9\x7c\ +\x59\x89\x19\x6c\xec\x84\x66\x2c\x1f\x11\x8a\x26\x27\x1b\x8c\xeb\ +\xa3\x50\xd1\x0c\xf1\x75\x14\xf1\x75\xe2\x13\x55\x70\xad\x9b\xb0\ +\xcf\xaf\x89\x20\x5a\x57\xa7\x0c\x62\x2e\x68\x50\xe3\xbd\x45\x65\ +\x54\x34\x78\x6f\x78\x98\x18\x48\x53\xe4\xb3\xce\x20\x28\x1a\xb4\ +\x08\x13\x46\x9a\x75\x7e\x9f\xa1\x90\xd4\x3a\x13\xb0\x17\x8b\xf2\ +\xde\x03\x2f\xf0\xf9\x6c\xa5\x5c\xea\x0d\xf1\x0c\x30\xef\xed\xb3\ +\x0e\x12\x61\x4d\xc0\x60\x21\xe9\x56\x53\x21\x06\xd3\x3a\x78\x92\ +\x1f\x5a\x42\x2a\x5e\x95\x87\x12\x48\x8b\x90\x11\x05\xd4\xbb\x40\ +\x29\x43\xe1\xc9\x08\x92\x40\x86\xb4\xce\x22\x9c\x6a\x51\xa3\xe2\ +\xf0\xb9\x09\xb8\x1d\x41\x4b\xcb\xaa\x23\x4d\x9a\xb9\x4c\xa1\x28\ +\xd4\x74\x2a\x50\x32\x82\x2d\x40\x32\xb9\x94\x21\xdb\x6a\xb4\x2c\ +\x73\x7c\x26\x92\x5f\x43\x02\x5f\x9d\xe0\xc5\xd5\x3b\x96\x9c\x88\ +\xca\xa8\xce\x13\xc0\x22\x2c\x94\x75\x75\x06\x2b\xca\xba\x3a\xa3\ +\x1c\x20\x5d\x50\x16\x83\x85\x02\x51\x20\x84\xc3\x8a\xe5\x98\x22\ +\x88\x7b\x86\xe4\xe4\xb1\x6b\x72\x52\x44\x52\xe2\xd9\x9f\x94\x4f\ +\x52\xfd\x3e\xea\x1d\x5b\xff\x0d\xb5\x23\x88\xd6\x41\x95\xa1\xf3\ +\x3d\x61\xa8\x74\xcb\xe4\xf0\x0c\x30\x5f\x07\xcf\xbd\x63\xf7\x5a\ +\x8a\x6a\x5d\x68\xb6\x05\xee\x54\x63\x2d\x1a\x83\x90\x11\x3c\xc9\ +\x27\xa4\x67\x30\x35\xf2\xa1\x86\x63\xa2\x2e\x88\xda\x64\xaf\x53\ +\xb7\x42\xa3\x4d\x28\x87\xaf\xd0\x10\x35\xd8\xa6\xdc\x1b\x1e\xf8\ +\xe0\x4f\xd0\xe8\xd4\x9e\xe9\x5b\xc3\x42\xe3\x51\xf6\x30\xd1\x79\ +\x7f\xa7\x14\x19\x4d\x72\x06\x58\x07\x32\xcb\xba\xa0\x50\x69\x02\ +\xe9\x12\xf7\x4e\x56\xa4\x92\x13\x11\x20\x7f\x92\x2a\xb4\xb5\x45\ +\x81\xd0\xba\xa4\x7a\xb6\xcf\xed\x75\xd0\x2e\x71\x41\xf1\x9b\xd1\ +\xc4\x9a\x4e\x26\x92\x09\x89\xef\xd5\x59\x57\xb6\x14\x94\x61\x9f\ +\x13\x58\xce\x40\x04\xbc\x61\xb0\x68\xa0\x48\xd4\x05\xc5\x7a\xaa\ +\xd0\x29\x03\x68\x69\x99\xa5\x73\x52\xd0\x36\xdc\x4a\x0c\x2f\xc4\ +\x88\x7a\xec\x07\xd9\x67\x55\x27\xdc\x60\x43\x73\x13\xa2\x93\xf2\ +\x94\x5b\x19\xa0\x06\x6b\x6a\xec\x08\xaa\xde\xd5\x18\x94\x25\xf7\ +\x86\xde\x6b\xd9\xb9\x50\xee\x4b\x51\x81\x4a\xb7\xb2\x97\x96\xd0\ +\x9d\x5b\x6c\x43\x45\x2e\x9a\x6e\xf9\x40\xb0\x8d\x20\xea\x7a\xaa\ +\x90\xbc\xae\x4d\xed\xd4\x3b\xc2\xfa\x4b\x40\xb2\x28\x83\x74\x98\ +\x18\x11\xe2\x69\x96\x19\x15\xcb\xee\x53\x6b\x8b\xf1\xff\xa6\x8d\ +\x8c\x86\x01\x02\xdc\x70\x13\x92\x6b\x0c\xb3\x32\x7a\xbf\xb7\x66\ +\x79\xbd\x72\xf5\x43\xfe\x7b\x6d\x3c\xdc\x79\xd6\x83\x8a\x38\x06\ +\x3a\x10\x28\x7b\xdc\x95\xee\xe6\x2b\xde\xe3\x40\x8c\x31\x87\xf8\ +\xc0\x01\x1e\xcb\xfc\xa5\x05\x77\x62\x3e\xe6\xbe\x77\xd8\x58\xfb\ +\x09\x1e\x9f\xe4\x0b\x72\x20\x8b\x41\x25\x1c\x01\x59\x7b\x32\x42\ +\xb0\xa3\x68\x9e\x8f\xcf\x51\x59\x3e\xc1\x27\x3b\x6a\x36\xae\x78\ +\xd7\x98\xaa\x92\x8f\x1d\xe7\xf1\x19\x72\x40\x1b\x14\xaf\xc5\x59\ +\x37\x37\xf7\xc3\x08\xeb\x6c\x45\xad\x37\xc3\x4f\x45\x19\xec\x58\ +\x11\xdc\x67\x75\x4b\x50\xbe\xb1\x01\x64\xcc\xa5\x3e\x65\x4b\x17\ +\xcf\xb0\xbb\x6b\x43\x03\xd6\x0b\xf2\xdb\x68\x25\x81\x16\x89\xf9\ +\x02\x2c\x50\xf1\xa8\xc9\xd7\x41\x9b\xb7\x59\x36\xf8\x82\xef\xbb\ +\x55\x57\x5a\x9a\x5c\x14\x59\x14\x45\x90\x9c\x21\x6b\x83\x3d\x26\ +\x9f\x6c\x4b\xaa\xe0\xea\x0d\xd9\xcc\xd0\x5f\xc1\xee\xbb\x21\xd1\ +\xc5\x2d\xbd\xd8\x99\xca\xba\x82\x80\xfb\x40\xd6\x15\xcd\x90\x15\ +\x83\xf0\x49\x10\xeb\xb1\x0d\x7a\x73\x74\x8c\x6d\x59\xb6\xdd\x86\ +\x85\x09\xa8\x8c\x9a\x6e\x67\x80\x96\xff\x26\x37\xeb\x4c\x2e\x8a\ +\xa0\x2b\x34\xa0\x61\xc2\xf4\x51\x79\x89\x67\x54\x89\x87\xf2\x22\ +\xe2\xb5\x61\x14\x41\x77\x7b\xf3\xe5\x61\x09\x78\x1f\x1f\x35\x38\ +\x55\x98\xc1\xc2\x43\x39\x4c\x46\x6d\x02\x1c\xec\xe9\xde\x70\x5f\ +\x07\x7f\x96\x81\x6f\xb8\x44\xa0\xc7\x40\x34\xed\xfe\x0f\xcd\xeb\ +\x60\xa1\x3c\xc6\x42\xd1\xbe\x77\x5d\x1d\x04\x35\xd8\x6d\x5d\x79\ +\x0b\xa9\x26\x3a\x47\xda\xa9\x5c\xf6\x96\x25\x4c\x64\x73\xaa\x29\ +\x2c\x15\x95\x91\xa1\xcc\xab\xa3\xb3\x46\x58\x48\x21\x06\xd9\x83\ +\xbd\x69\xcf\x68\x31\x3b\x6a\x0a\xd9\xa2\xab\xd1\x4c\x82\xdc\xc4\ +\xd4\x1b\xfa\x64\xb4\xa4\xae\x49\x5f\xcf\x00\xdd\x9e\x31\xfb\x8e\ +\xe5\xf0\xe5\xa3\x59\x6f\xea\x24\xca\xf4\xdc\x16\x68\x02\x59\x97\ +\x6e\xc1\x5a\x34\x49\xed\x9a\xbe\x9e\xc1\x12\xe0\x11\xd5\xb1\x94\ +\x18\xda\x74\x82\xf9\xba\x3a\x4a\xb2\xa4\x5b\xad\x9f\xbf\x9c\xaa\ +\x0c\xad\x57\x38\xa5\x50\xe4\x2f\xa1\x82\x56\x13\x05\xd2\x60\xdb\ +\xb0\x28\xcb\xc3\xb0\xc4\x3d\x43\x5d\x33\x43\x34\xc9\xf7\xf7\xa2\ +\xa9\x1a\x2d\x64\x0b\xcd\xba\x7c\x7c\x76\xca\x96\x50\x48\xb0\x08\ +\xcf\x50\x97\xcf\x41\x76\x05\x49\x7e\x4d\x5d\x27\x3f\xd6\x53\x85\ +\xde\x8c\x66\x52\x58\xda\xf7\x9e\xe5\xd6\x51\x56\x32\xf7\xb6\xd2\ +\x76\x44\x94\xb0\x4d\xcc\xd4\x55\x13\xb1\x6d\xff\x20\xc9\x89\xa5\ +\x4c\x0f\x74\xce\xd6\x25\x4e\xee\x9a\x4c\xb5\xaf\x83\x17\x62\x59\ +\x76\x95\x8f\xcd\xb4\x14\x68\xd6\xc1\xf7\x39\xc9\xb6\xe9\xcd\xb6\ +\x3f\x05\x32\x5b\x27\x1f\xb2\xfd\x48\x6c\xc2\x17\xc2\xba\xa4\xdd\ +\x5b\x52\x29\x82\xcf\xb6\xc0\xef\x9b\xa0\x40\x0c\x82\xf5\x82\x26\ +\xe9\xaf\xcd\xa1\xde\x9d\x1b\xeb\x7d\x95\xa1\x73\x95\x2e\xd5\xaf\ +\xb3\x39\x3c\xc3\x6c\xe6\x0c\x54\x93\xa5\x42\xca\xff\xba\x0d\x55\ +\xc4\x1b\x1a\x49\xf7\xf2\xf5\x51\xef\x2c\xb7\xe4\x67\x7b\xb4\x6e\ +\x96\x5b\x20\x3b\x27\xf8\x7d\x65\xe4\x2b\x08\x74\x4d\xbe\x45\x51\ +\x94\x1c\xfc\xe6\x12\x6d\x28\x14\x97\x7a\x17\xcb\x21\x2f\x0d\x7f\ +\x04\x5b\x96\x27\xb0\xfb\xde\x1a\xb2\xb5\x45\xd3\xf3\x95\x0d\xd2\ +\x8e\xb2\x2e\x33\x80\x26\x27\xd9\xda\x68\x05\x65\x9c\x1e\x0d\x51\ +\x6a\x72\x28\x83\xe8\x8d\xb7\x75\xea\x0c\x69\xa3\x65\xa9\x26\xb2\ +\x45\xd8\xa4\x54\x2b\x95\x75\xf0\x3d\x34\xea\xb4\x0e\x9a\x05\xd9\ +\x5c\xd5\x44\xa7\x45\x48\x53\x79\x0b\x64\xac\xa1\x31\xfb\x99\x2a\ +\xf4\xd1\xba\xa6\xaf\x2b\x9a\xfa\x3b\x13\xb3\xd6\x47\x59\x94\x81\ +\xd0\x76\x14\x53\x06\xf1\x6d\xe5\x8a\x5e\x32\x5a\x59\x59\xa9\xfb\ +\xce\xd0\x18\xf3\xfe\x60\x30\xb8\xc5\xe3\x7a\x40\xaf\xdd\x9e\x0b\ +\x57\x79\x2c\x37\x02\x9d\x3e\x7d\x3a\x18\xe1\x8a\xa2\x70\xe3\xbf\ +\x6b\xab\xab\xcf\xdd\xbc\x79\x93\xee\x2e\x2e\x92\x2d\x4b\x62\x60\ +\xb2\x6e\x73\x06\x7e\x77\x7f\x17\xe3\xf7\x46\xa0\xc7\x1e\x7f\x7c\ +\x2b\xc8\xc6\x0e\x0f\x34\x37\x3f\x4f\xf3\x3c\x6e\x7c\xfc\x31\xbd\ +\xf3\xf6\xdb\x74\xf3\xd6\x2d\x2a\xcb\x72\xab\xb0\x1c\x10\x5f\x90\ +\x69\xc3\xba\xd5\xd5\xd5\x70\xdc\x76\x80\xd6\x0e\xd6\xd7\xd7\x1f\ +\x2f\xad\xfd\x22\x7f\x74\x85\xc7\x8b\xb4\xb1\xfd\xc1\x37\x79\xec\ +\xe1\xef\xbc\x3c\x1c\x0e\x2f\xb6\x4a\xb7\xdc\x55\x31\xff\x47\x57\ +\x56\xa5\xc8\x7d\xc6\xc7\x16\xf8\xff\x2f\xf0\xf8\x3e\x8f\x3f\x8c\ +\x81\xf6\xf3\xf8\x1a\x8f\x4f\xf1\xf8\x17\x8f\xcb\x74\x6f\xf3\x8c\ +\xcd\xd7\xee\xdd\xbb\x69\xef\xde\xbd\xf7\x80\xd6\xd6\xd6\xe8\x63\ +\x66\xcf\xf2\xf2\xf2\xe8\xe4\x6e\x30\x15\xb4\xb0\xb0\x40\x0f\x3c\ +\xf0\xc0\x06\x6b\x4c\x7c\xd7\x0f\x77\xc2\xa3\x47\x8f\x8e\x4e\xea\ +\xce\xf3\xe1\x87\x1f\x8e\x58\xfe\xf0\xc3\x0f\xd3\x99\x33\x67\xb6\ +\x02\x5d\xb9\x72\x85\xae\x5e\xbd\x4a\x7b\xf6\xec\x19\xfd\xd0\x81\ +\x3a\x90\x83\x07\x0f\x5a\xfe\x7f\x38\x37\x37\x17\x4a\xee\xdd\x7b\ +\x79\xfc\xf8\x71\x7a\xea\xa9\xa7\xe8\xf0\xe1\xc3\x74\xee\xdc\x39\ +\xba\x73\xe7\xce\x88\x2b\xbb\x76\xed\x22\xfe\xdd\x3d\xa0\x09\xdb\ +\xdc\xbb\x3b\xe8\x86\x53\x3a\x06\x2a\x4f\x9e\x3c\x39\x3a\xf9\xc5\ +\x8b\x17\x83\x62\x3c\x72\xe4\x88\x7d\xf4\xd1\x47\x8d\xa3\xe8\xc6\ +\x8d\x1b\xf3\x0c\xb2\x36\x61\x3d\xcb\x6e\xc4\x99\x2d\x06\x5b\x61\ +\xd9\x80\xa9\xf9\x3c\xdb\xd6\xb3\x8b\x8b\x8b\xff\xe0\xf7\x5f\xed\ +\xdf\xbf\xff\x1a\xb3\x65\x65\xc2\xd2\x4a\xfc\x61\x8d\x5f\xbc\x7c\ +\xe1\xc2\x85\x85\x77\xdf\x7d\xf7\xc7\x7c\xec\x20\x83\xfd\x9c\xbf\ +\xf7\x37\xbe\x70\x3b\x91\x79\x55\x19\xf6\xf1\xc1\xfd\x4e\xbb\xf8\ +\x8a\x9e\xe3\x8f\xbe\xca\xe3\x41\x3e\xc9\x8b\xb7\x6f\xdf\xfe\xe7\ +\xa1\x43\x87\x1e\xbb\x75\xeb\xd6\x61\xda\xfa\x14\x48\x67\xc8\xc7\ +\x96\x96\x96\x9e\xbc\x7c\xf9\xf2\x51\x66\xd1\x93\xac\xfa\x9f\x63\ +\x0d\x7e\x86\xdf\x7f\xc9\x2c\xff\x1d\x03\x5d\xe0\xef\xdc\xe5\xef\ +\x2e\x8d\x80\xf8\xc0\xd3\x3c\xdc\x17\xbe\x5d\xb5\x0f\x26\xfd\xeb\ +\xef\xbd\xf7\x9e\x1b\x93\x8f\xd7\x2a\x40\xcb\xfc\x9d\x53\x7c\xb2\ +\x17\x9c\x6d\x39\x99\x8e\x6d\xec\x04\x8f\xef\xf1\x6f\x9f\xe7\xff\ +\x7f\xc2\xe3\xf7\xfc\xff\xeb\x23\x20\xa6\xe4\xcf\xfc\xa3\xf3\x07\ +\x0e\x1c\x78\x8d\xbf\xf0\x65\x66\xd7\x97\xf8\x4a\x0f\xb2\xac\x5e\ +\x3a\x75\xea\xd4\x4b\x2c\xe4\xcf\x5e\xbf\x7e\xfd\x5b\xac\x30\xf3\ +\x4e\x71\xc6\x6c\x5b\xe0\x93\xbc\xcf\xe3\xa7\xcc\x8d\x2b\xfc\xdd\ +\xe7\xf9\xfd\x09\xd6\xb6\x8f\x98\xba\x97\xf9\xfd\x55\x96\xd5\x5f\ +\xf9\xfb\xb7\x36\x59\xc7\x5f\xbe\xc6\x00\xd7\x58\x9d\xff\xcd\x6c\ +\xfa\x2d\x03\x3f\x73\xed\xda\xb5\x67\x4f\x9c\x38\x71\xee\xec\xd9\ +\xb3\xaf\xb0\xd6\x95\x6f\xbc\xf1\xc6\x57\x2e\x5d\xba\x54\x5d\x80\ +\xe4\x7e\x7b\x99\x59\xf4\xea\x43\x0f\x3d\xf4\x1f\xfe\xdd\x59\xa7\ +\x33\x7c\xe2\x9f\xdd\xbd\x7b\xf7\x75\x66\x7b\xe9\x28\xdc\xf4\x32\ +\x13\x03\x75\x42\x76\x57\xcb\x07\x57\x98\x1d\xbf\xe1\xab\x7b\xc5\ +\x39\x0d\xa6\x6e\x2f\xf3\xfd\x08\xff\x78\xa1\xe2\x7e\x26\x85\xd7\ +\x01\xe6\xc4\x69\xd6\xb8\x77\x1e\x79\xe4\x91\xef\xf2\xf1\x39\xd6\ +\xce\x25\x96\xe7\xe8\x5c\xee\xbc\x5b\x80\x9c\x1a\x9a\xb1\x63\x9c\ +\x1c\xe0\x1f\xad\x3a\x7b\xf8\xe0\x83\x0f\xac\xd3\x36\x66\x65\x30\ +\x4e\xb1\xb3\x2d\xce\x9f\x3f\x6f\x99\x1b\xeb\xc7\x8e\x1d\x73\xbb\ +\xe3\xd0\x84\x12\x67\x43\xce\xe8\x37\x81\x9c\x45\x3b\x2b\x76\x07\ +\x9d\x45\xbb\x17\xdb\xc7\xc8\x9e\xf8\x8b\x86\xa9\x19\xf0\xc5\xc4\ +\x6e\x44\x2b\x58\xa5\xe9\xcd\x37\xdf\x1c\x9d\xd4\x85\x1c\x77\x81\ +\x4e\xb5\x9d\xe1\x33\x0b\xef\x01\xb9\x0f\xf6\xed\xdb\xb7\x85\x22\ +\xf7\xb7\xbb\x22\xf7\x83\xf1\x97\x4d\xf5\x78\xf5\xe5\xd8\xee\x5c\ +\x8f\xff\x62\xb5\x77\x1c\xb9\x07\x34\xa1\x62\x02\x50\x3d\x19\xb3\ +\xcd\x19\xea\x6b\xa6\x28\xe6\xed\x86\xf7\x76\xaf\x3b\x3c\x7e\xed\ +\xbc\x37\x8f\xb7\xe8\xfe\x9d\x8d\x36\x3d\x83\x1b\x9b\x40\x7f\x7f\ +\xeb\xad\xba\xc0\x37\xe4\xc0\x77\x8e\x63\xd1\xb9\x39\x96\x55\xb9\ +\x11\x7f\x5c\x34\x7d\xc1\x74\x0d\xe5\x11\x3f\xb6\x85\x4a\xc7\xc6\ +\xf9\xb1\x83\x74\x5b\x1f\xd9\x94\x2c\x68\xa2\x19\x91\xd7\x83\x3c\ +\x7e\xc1\xe3\x69\x66\xe9\xf5\xc0\xf1\x49\xce\xf0\x1d\x1e\x7f\xec\ +\x93\x05\x0d\xc6\x01\xee\xe0\x78\x04\x43\x12\x35\x6c\x3a\x34\xb5\ +\xf3\x47\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\ +\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\ +\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\ +\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\ +\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\ +\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\ +\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\xd9\x81\x60\x3b\xff\ +\x57\x01\xb2\xb3\xae\xba\x35\x96\x55\x65\x50\xa0\xd9\x05\xb2\xb9\ +\x29\x0a\x01\xaa\x1d\x4d\x97\x32\xd8\x9a\xe3\x56\x59\x27\x1e\xf4\ +\xfa\xc8\xc8\x76\x35\xe8\x14\x20\xf8\x53\x20\xeb\xae\x7e\x76\x3d\ +\x83\x4d\x75\xba\x53\x67\xb0\x49\x72\xe9\xcb\xba\x90\x1d\x89\xb0\ +\xae\xca\x96\x12\x9d\xa9\x36\xd9\x51\x99\xd3\x8e\x6c\x2e\xad\x13\ +\x67\x9d\x25\xe0\x63\x20\xca\x80\x32\xb4\x66\x9b\x14\xeb\x2c\x42\ +\x46\x65\x84\xda\x52\xd2\x8e\xfc\x2b\x37\x15\x20\x31\x8a\x26\x57\ +\x3d\x44\x52\xd4\x24\x87\xb2\x42\x95\x08\x50\xe9\x01\x1a\x04\xd0\ +\x84\x6d\xc3\x88\x82\x94\x92\x76\x34\x6c\x90\x91\x95\xd0\xba\xd2\ +\xbb\x72\x83\x52\x86\x09\x45\x65\x20\x44\x0c\xa5\x5d\x50\x95\x75\ +\x3e\x45\xeb\x4d\x54\xa5\x00\xf9\x01\x6f\x28\xcd\x3a\x5f\x16\xe2\ +\x9e\xc1\x06\x28\xaa\xba\xa1\xe1\x98\x75\x30\x3b\x9a\x50\xb4\x2e\ +\xc9\xba\xc9\x55\x0f\xe9\xfe\x47\xb5\x0e\xdb\x68\x5e\x0a\xeb\x7c\ +\x8a\xc4\x80\x7c\x8a\x8c\x17\xce\xd7\x11\x40\xeb\x01\xf5\x8e\x85\ +\x8f\x5e\xca\x30\x79\x1c\x47\xf5\xf9\x2d\xeb\x92\x32\x1a\x7a\x2e\ +\xa8\xf4\xd4\x5b\x94\x75\x6b\x11\xd6\x89\x52\xb4\x1e\x90\x51\xd5\ +\x8e\xe0\x06\x5b\x48\xb3\x2e\xe6\xbd\x87\x08\xd6\xad\x46\x58\x07\ +\xf1\x75\x31\xf5\x2e\x25\xb5\xae\xca\xba\x12\xe5\x82\x42\x81\xcf\ +\x07\x22\x84\x53\x85\x78\x06\x5f\xbb\x42\xac\x5b\x47\xc9\x88\x2a\ +\x31\x69\x28\x9d\x12\x0f\x03\x39\x83\x91\x4c\x4e\xac\xa7\xde\x26\ +\x90\xd7\x89\xb3\xae\x0c\x00\x89\xd6\x47\xd6\x53\x6f\x83\xac\x26\ +\x4a\x8f\x22\x23\x9d\x40\xda\xc0\x55\xc3\xea\x23\xeb\x51\xe4\x67\ +\xaa\x43\x49\x20\x5b\x01\x2c\xbc\x42\x0c\x52\x4d\x18\x64\x43\x23\ +\xc4\x46\x42\x6b\x1d\x45\xd4\x5b\x9c\xa2\x50\xab\x46\x94\x75\x36\ +\xd0\x18\x14\x7f\xce\x72\xa8\x65\x66\xda\x2a\x42\x2a\x45\x7e\xe0\ +\x13\xed\x6e\x85\x40\x0d\x5a\xbd\x89\x32\x3e\x39\x1a\x32\x7f\x64\ +\xa9\xe3\xda\x85\x54\x20\x93\xaa\xd2\xa9\xac\xf3\x23\x2b\x6c\xcd\ +\x89\xa5\x4c\x6b\x4e\xea\x26\x40\x2c\x8a\x75\xdb\xf2\xd8\x56\x43\ +\xc0\xa7\x12\xc3\x17\x20\x85\xa8\x82\xb9\xa0\x32\x02\x24\xea\x54\ +\x6d\x8d\x92\x18\x49\xf5\xae\xf3\xde\x22\xc5\x72\x68\x4e\xc2\x22\ +\x64\x64\x23\x0e\x56\x5c\xeb\x4c\x8d\x67\x10\xcf\x19\x6c\x24\x77\ +\x10\xcf\x54\x43\xf1\xc8\x20\x3c\x83\xdf\x3e\xf3\x93\x7f\x88\xf7\ +\x86\xa8\x77\xcc\xcf\xd9\x2e\x41\x50\x62\x66\xd9\x4a\x2b\x43\x55\ +\x16\x36\xe0\xef\x20\xde\x1b\xea\x19\xca\x80\x5c\x6c\x1b\x8d\xeb\ +\x9b\xe4\x57\xa7\x0e\x4a\x49\xad\x0b\xd9\x0b\x84\x22\xdf\xa1\x8a\ +\x2b\x43\x6c\xfa\xda\x22\x0c\xd6\x3f\x59\x19\xf1\x83\xa2\xca\x10\ +\xb2\x23\x51\x65\xb0\x91\x98\x54\x22\xed\xc8\xa2\x64\x14\x52\x65\ +\x13\x28\x9e\xc5\x7c\x5d\x68\x71\x8b\x78\xd3\xa9\xda\xdd\xb2\xa8\ +\x04\xb2\x8c\xb4\xd1\x8c\xa4\x32\xd8\x88\x32\x4c\x00\x07\xd2\x5a\ +\x17\x53\x06\xa8\x8c\x60\xf1\xc8\xd7\x3a\x0a\x18\x2c\xa4\x5f\xd7\ +\x14\x3e\x44\x64\x64\x03\xbd\x07\xb1\x35\x27\xa1\xb9\x89\xc9\x4b\ +\x74\xe1\xc4\x90\xe2\x8b\x5b\x06\x04\xea\x7b\x13\x85\x57\xd1\xc0\ +\xd4\x1b\x12\x26\x42\x73\x13\x96\x3a\xb4\x3b\x53\x03\x9f\x9f\xd7\ +\x89\xaf\xd0\x88\xb9\x20\xd8\x72\x1d\x13\xa8\xf8\x20\x59\x10\x54\ +\x19\xfc\x19\xb1\x4e\xd9\x6a\x57\x8a\x88\xee\x9f\x15\x83\x2d\x7b\ +\xb3\x81\x42\x59\xdc\x60\x87\x81\x78\x54\x48\xdb\x51\xec\xaa\xc5\ +\x03\x5f\x5d\x4f\x08\x92\x12\x53\xa4\xea\x83\x45\x58\xf1\x74\x2b\ +\xd4\xd0\x88\xd5\x4d\xe2\x32\x32\xa8\xa6\x53\xa8\x82\x80\x74\x20\ +\x9b\xd6\x40\x96\x28\x8a\x60\xed\x68\x4b\xf1\xd9\xe4\x02\x59\x88\ +\x19\x02\xcd\xf1\xd9\x1a\x65\x28\x08\x94\x7b\x87\xfa\x0c\x44\xc0\ +\x08\x6b\xd1\x5a\x67\xbc\x13\xc3\xbd\x37\xa4\x86\x0d\x5d\xb9\x09\ +\x54\x18\x62\x35\x6c\x19\x50\x6b\x43\x99\xd2\x2d\xf1\x5e\x10\x45\ +\xd4\xd9\x10\x28\xc9\xb7\x68\xcf\xe0\xb3\xa7\x44\xf9\x3a\x8a\x34\ +\x06\xc5\xd7\xe4\xc7\x4e\x08\x75\xaa\x94\xa3\xb4\x8c\x51\x2c\xd6\ +\x74\x8a\x39\x55\x51\x3b\xaa\xeb\x9c\xc0\x6a\x58\xa8\x8c\xea\x28\ +\x12\x8f\xb0\x21\xf5\x36\x04\xec\xe4\xdb\x48\xcf\x8e\x10\xea\x6d\ +\x03\x21\x44\xbc\x8d\x06\x53\x86\x50\x1b\xad\x73\x3f\x28\x45\x46\ +\x7e\x72\x52\x44\xc0\xc5\x4b\x4b\x68\x1b\x2d\xeb\xb4\x0e\xe4\xc6\ +\xdb\x50\xee\x4d\x88\x9c\xa1\xce\x73\xb7\x5a\x48\x91\x3a\x37\x61\ +\x02\xb9\xb7\x45\xb3\x4e\x54\x46\x4d\x27\x9c\x2d\x19\x35\x25\x27\ +\xd9\x22\x6c\x2b\xb0\xa9\xdd\xc6\x27\x19\xb4\x2f\xeb\x2c\xda\x05\ +\x65\x61\xdd\xce\x05\xb2\x48\x83\xa5\x1c\xe5\x3f\xa9\x32\xcc\x2c\ +\xd0\x6c\x2e\xfe\x0f\x55\x12\xf0\x65\x6f\x36\x85\x2a\xa9\xd5\xd1\ +\x34\x73\x32\x52\x83\x55\xa0\xff\x53\xa0\x56\x77\x87\xcc\x25\x78\ +\x02\x28\x45\xb6\xaf\xef\x93\xa0\x48\x94\x75\x75\xcd\x74\x51\x8a\ +\xa6\x22\x0b\x12\x65\xdd\xce\x33\xd8\x26\xad\xcb\x52\x5a\xda\x1d\ +\x27\x23\x51\xd6\xd5\xbd\xca\x99\xd4\xba\x6d\x07\x6a\xbd\x13\x69\ +\x5f\x20\xbb\xe3\x58\xb7\x33\x7d\x9d\x38\x45\x49\x7b\xdd\x6a\xe0\ +\xeb\x5d\x6a\xa6\xde\x4a\x5c\x3d\x69\x81\xf6\x0c\xa1\x95\x1a\x90\ +\xaa\xbc\x93\x36\x4a\xdc\x85\xbd\x33\x5c\x10\x6c\xca\xa0\x37\x0b\ +\xfb\x74\x4e\x6c\x2e\x8a\x66\x5f\x19\x9a\xf6\x8d\x16\x33\x58\xbf\ +\xa3\x65\xd0\x09\xa4\x8d\x54\x7e\x06\xe1\x19\x2c\xb2\x9a\xb0\xa9\ +\x36\xd4\xd7\xa9\xc2\x6e\xc9\x8f\xdd\x66\x62\x72\x79\x06\x78\x21\ +\x66\x08\x30\xf5\x36\x15\xd5\x04\x2c\xc2\xee\xec\x24\x3f\xb4\x5a\ +\x5a\x65\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\ +\xc1\xe4\xb0\x53\x8f\xb5\x4f\xf3\x16\x72\xff\x91\xed\x5b\xc7\x76\ +\x2d\x2d\x5b\xb3\x4a\x52\x19\xb2\x35\x9d\x76\x56\x1b\x6d\xdb\x36\ +\x9c\x9c\x4d\xcf\x60\x22\x32\x2a\x24\x4b\xcb\xea\xfd\x12\x36\xa0\ +\x0c\xed\xba\x5b\x8b\x4b\x4b\x4d\x82\x5f\xe9\xcb\xba\xff\x01\x57\ +\x19\x2b\xd0\x52\x3a\x09\xbb\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ \x00\x00\x15\x74\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -590,6 +1015,368 @@ \x50\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x61\xe2\xbf\xd7\ \x83\x06\x69\xb3\xda\xf0\x57\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ \x42\x60\x82\ +\x00\x00\x16\x7b\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x03\x4b\x00\x00\x00\x1a\x08\x06\x00\x00\x00\x66\x7e\xc7\xb9\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x08\x73\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\ +\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\ +\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\ +\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\ +\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\ +\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x65\ +\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\ +\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\x66\x2f\x31\x2e\ +\x30\x2f\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\ +\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\x31\x37\x20\x28\x4d\x61\ +\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\x78\x6d\x70\x3a\x43\x72\ +\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x36\x2d\x31\ +\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\x35\ +\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\ +\x61\x74\x65\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\ +\x31\x34\x3a\x31\x34\x3a\x34\x37\x2d\x30\x35\x3a\x30\x30\x22\x20\ +\x78\x6d\x70\x3a\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\ +\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\ +\x31\x34\x3a\x34\x37\x2d\x30\x35\x3a\x30\x30\x22\x20\x64\x63\x3a\ +\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\ +\x67\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\ +\x6f\x72\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\ +\x22\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\ +\x2e\x31\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\ +\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x38\ +\x66\x61\x39\x66\x61\x32\x2d\x30\x39\x30\x65\x2d\x34\x33\x31\x37\ +\x2d\x62\x63\x39\x30\x2d\x33\x62\x30\x63\x39\x30\x31\x32\x33\x64\ +\x62\x34\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\ +\x6e\x74\x49\x44\x3d\x22\x61\x64\x6f\x62\x65\x3a\x64\x6f\x63\x69\ +\x64\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x31\x61\x36\x61\ +\x36\x33\x66\x62\x2d\x37\x33\x31\x34\x2d\x61\x62\x34\x36\x2d\x39\ +\x30\x39\x30\x2d\x35\x32\x32\x31\x36\x39\x38\x31\x32\x62\x65\x61\ +\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\x61\x6c\ +\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\ +\x64\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\ +\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\ +\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x74\x69\x66\x66\x3a\x4f\ +\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x22\x31\x22\x20\x74\ +\x69\x66\x66\x3a\x58\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x55\x6e\ +\x69\x74\x3d\x22\x32\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\x6c\x6f\ +\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\x20\x65\ +\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\x6e\x73\ +\x69\x6f\x6e\x3d\x22\x31\x32\x30\x34\x22\x20\x65\x78\x69\x66\x3a\ +\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\x6e\x3d\ +\x22\x32\x30\x22\x3e\x20\x3c\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\ +\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\x74\x6f\ +\x72\x73\x3e\x20\x3c\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\x3c\x72\ +\x64\x66\x3a\x6c\x69\x3e\x78\x6d\x70\x2e\x64\x69\x64\x3a\x62\x30\ +\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\ +\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\ +\x33\x62\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x2f\x72\x64\ +\x66\x3a\x42\x61\x67\x3e\x20\x3c\x2f\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\ +\x74\x6f\x72\x73\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\ +\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\ +\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\x61\x63\ +\x74\x69\x6f\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\ +\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\ +\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x2f\ +\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\ +\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x63\x36\x66\x63\x62\x66\ +\x38\x2d\x34\x34\x34\x35\x2d\x34\x66\x33\x62\x2d\x61\x34\x64\x36\ +\x2d\x33\x30\x36\x62\x66\x31\x30\x31\x62\x61\x30\x39\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x33\x38\x3a\x31\x32\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\ +\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\x22\x2f\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x38\x66\x61\x39\ +\x66\x61\x32\x2d\x30\x39\x30\x65\x2d\x34\x33\x31\x37\x2d\x62\x63\ +\x39\x30\x2d\x33\x62\x30\x63\x39\x30\x31\x32\x33\x64\x62\x34\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x32\ +\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\x31\x34\x3a\x34\x37\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\ +\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\x79\ +\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x20\ +\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\x3f\x78\ +\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\x3f\x3e\ +\x88\x34\x49\xc3\x00\x00\x0d\xae\x49\x44\x41\x54\x78\xda\xed\x9d\ +\x4f\xa8\x1d\x57\x1d\xc7\xbf\xbf\x33\x33\xef\x6f\xf3\x5e\x08\xaf\ +\x3c\xd2\x24\x46\x09\xa1\xa4\xb6\xa5\x11\x49\xa1\x22\x68\x41\xc1\ +\x85\x90\x45\x36\x56\xc4\x85\xba\x74\xe3\xc6\x45\x57\x5d\x75\x21\ +\x82\x41\xdc\x08\x2e\xdc\xb8\x72\x23\x82\x35\x14\x41\xb0\x50\xdb\ +\x07\xb6\x44\x7c\xb4\x04\x69\x63\x9a\x6a\xe2\x8b\xe9\x7d\xcd\x7b\ +\x79\xf7\xde\x99\xf3\x73\xf1\xee\x4d\x27\x27\xe7\xef\xdc\xb9\x7f\ +\x9a\xf7\xfb\xc0\xe3\xdd\x99\x39\x73\xe6\xcc\x6f\xce\x39\xf3\xfb\ +\x9e\x33\xf3\x1b\x62\x66\x08\x82\x20\x08\x82\x20\x08\x82\x20\x08\ +\xf7\x43\x8b\x0b\x00\x50\xa0\xaa\x5e\x00\xf3\x63\x00\xba\x00\xd4\ +\x08\x59\x6a\x00\xf3\x00\x56\x07\xbf\x05\x41\x78\x88\xbb\x10\x00\ +\x19\xb4\xfe\x22\x8a\xe2\x47\x50\xea\x6d\x30\x2f\x88\x59\x04\x77\ +\x8d\x21\x42\xaf\xdf\x47\x59\x96\x38\x79\xf2\x24\x9e\x7b\xee\x39\ +\xec\xec\xec\x0c\x37\xcf\x6b\xad\x9f\x66\xe6\x77\x00\x7c\x0c\x00\ +\xcb\xcb\xcb\x78\xfd\xf5\xd7\x71\xed\xda\x35\x14\x79\x8e\xa2\x28\ +\x20\x83\x7c\x82\x20\x08\xc2\x24\xd8\xbd\x7b\x17\xb9\x98\x41\x10\ +\x04\x41\x98\x06\x57\xaf\x5e\xc5\xf5\xeb\xd7\xa1\x94\x42\x59\x96\ +\x58\x5a\x5a\x5a\x3a\x71\xe2\xc4\xf3\x00\x3e\x04\xf0\x71\x51\x14\ +\xb8\x7d\xfb\x36\xae\x5d\xbb\x26\xc6\x12\x04\x41\x10\xa6\x82\x12\ +\x13\x08\x82\x20\x08\xd3\x26\xcb\x32\xec\xed\xed\x1d\x7f\xf7\xdd\ +\x77\x5f\xec\xf5\x7a\x9f\xcb\x73\x19\xcb\x13\x04\x41\x10\x44\x2c\ +\x09\x82\x20\x08\x07\x98\x3c\xcf\xf1\xf2\xcb\x2f\xe3\xc2\x85\x0b\ +\x28\xcb\xf2\xab\x5a\xeb\x43\xb7\x6e\xdd\x7a\xfe\xa9\xa7\x9e\xc2\ +\xf9\xf3\xe7\x71\xf3\xe6\x4d\x31\x92\x20\x08\x82\x20\x62\x49\x10\ +\x04\x41\x38\xd0\x1c\x02\xf0\x5d\x00\xe8\x74\x3a\x2f\x68\xad\x8f\ +\x12\x91\x58\x45\x10\x04\x41\x10\xb1\x24\x08\x82\x20\x1c\x6c\x98\ +\xf9\x42\x51\x14\x5f\x00\x80\xa2\x28\x1e\xef\xf5\x7a\xdf\x12\xb1\ +\x24\x08\x82\x20\x4c\x9b\x71\x3e\x14\x3e\xcd\xbb\x1c\x8d\xb0\x1f\ +\xb7\x90\x27\x35\xd8\x46\x89\x69\x9a\xe4\xdb\xb6\xfd\x28\xc1\x96\ +\x29\xe5\xb3\xd9\x22\xe6\xff\xa8\xdb\x6c\xc7\xf6\x5d\x17\x8a\x3c\ +\x87\x71\xa6\x19\xda\xb9\x49\x5e\x29\xe5\x68\x23\x5d\xdb\xe5\x6c\ +\x52\xef\xa9\x85\x76\xe5\xb2\xf9\xb8\xda\x29\x8d\xb1\xad\xd6\xaf\ +\xcd\x28\xe7\x9c\x72\xac\xbf\x00\xb8\x3b\x5c\xb9\xb7\xb7\x87\x8b\ +\x17\x2f\x62\x6d\x6d\x6d\x6f\x6e\x6e\xee\x8d\xaa\xaa\x9e\x25\xa2\ +\xb7\xaf\x5c\xb9\xf2\xd1\x9b\x6f\xbe\x89\x6e\xb7\x6b\xe6\xb3\x08\ +\xe0\xcb\x96\x72\x0b\xc2\x4c\xe8\xfe\x84\x6d\xb6\x65\xb6\x2c\x73\ +\xc4\xb2\xf9\xa7\x3d\x69\xea\xa1\x8a\xb5\x51\x16\x33\xff\xd8\x73\ +\x1b\xd5\x16\xbe\xf4\xba\xd6\x07\xb1\x25\xbd\xad\xcc\x1c\xb0\x29\ +\x1c\xe7\x19\xbb\x2e\x94\xc6\x55\x36\x4e\xac\x0f\x4d\x6c\x9b\xe2\ +\xef\xd6\x7d\x35\x32\xf2\xb7\xa5\x09\xf5\xfb\x31\xe9\xc9\x71\x1e\ +\xbe\xf5\xd3\xec\xeb\x75\x3d\x74\x38\xb5\x13\x3a\xbc\x15\xb1\x34\ +\xaa\xa3\x43\x2d\xed\xd7\x86\xa3\x66\x3a\xe5\x31\xcb\xe4\x71\xea\ +\x7d\x79\xc3\xe3\xe8\xfb\xca\xc5\x91\xdb\x54\x82\xa8\x69\x22\x7a\ +\xa8\x76\x0c\xd3\x16\x0a\xc3\x38\xd5\x9f\x2c\x67\xb5\xf5\xca\x58\ +\x67\xfe\xd5\xd7\x53\x83\xdf\xf5\x32\xc0\x52\x36\x18\x65\xb7\xad\ +\x43\xc4\x35\x37\xed\xa2\x2c\xd7\x34\xb6\x0e\x99\xc7\x53\x01\xdb\ +\x93\x47\x2c\x3a\x8e\x53\x82\x68\x8f\xb4\xfe\x12\xe5\xf9\x4f\xa1\ +\xd4\xdb\xc4\xbc\xe0\x3a\x17\x5b\x19\x7c\xe7\x17\x23\x78\x95\xa3\ +\xde\x53\xe0\x1c\xea\xe5\x89\x29\xa7\xcd\x2e\x1c\xb0\x9b\xad\x1d\ +\x51\xa2\x98\x0f\x95\x01\x9e\xb6\xcf\x81\x7e\x80\x3c\xfd\x1a\x05\ +\xfa\x02\xd7\xb9\xf9\xfa\xca\x82\x88\x74\x55\x55\x4f\x74\x7b\xbd\ +\xf7\x6b\xed\x19\x79\x9e\x73\x55\x55\x55\x96\x65\xe7\xb2\x2c\x7b\ +\xa3\xd7\xeb\x7d\x03\xc0\x1f\x95\x52\x59\x55\x55\xc3\xfc\xaa\x22\ +\xcf\xb9\x28\x8a\xcf\x32\xf3\xe6\xe0\x1a\x95\xe2\x9b\x0b\x53\x14\ +\x3f\x3e\x07\x37\x46\xf4\x68\x87\x90\x31\xff\xaa\xda\xef\xb2\xb6\ +\x6e\xf8\x57\x1a\xff\x87\xbf\xeb\x7f\x95\x25\xcd\x70\x99\x2d\xc7\ +\xaa\xaf\x73\x39\xfb\x36\x5b\x90\x23\x1d\x1b\xdb\xb5\xc3\x3e\x6c\ +\x88\x38\xd3\x46\x95\xe5\xb7\xf6\xfc\x55\x46\x1e\xe6\xf9\x99\x79\ +\xd6\xf3\x33\xc5\x64\x55\xfb\xad\x8d\x7c\xb5\xc5\x8e\xb6\x6b\xca\ +\x0e\xfb\xba\x44\x6e\x1b\xc2\xaa\x89\x58\x4a\x6d\x07\x64\x11\x58\ +\x88\xa8\x2b\xae\xe3\x50\x44\xda\x71\x43\x00\xaa\xdd\xbb\x77\xbb\ +\x39\x30\x07\x80\x28\xcb\x7e\x37\x03\xe2\x6d\xa2\x06\x18\x87\x50\ +\x8b\x71\x74\x54\xa2\xd3\x0c\xc3\x61\x4f\x99\x05\xa1\x08\xf1\x18\ +\x23\x68\x10\x21\x96\x54\x20\xef\x90\xd3\x67\x3a\xc0\xa6\x40\xb1\ +\x09\x9f\xba\x78\xca\x2c\xa2\x28\xb3\xec\x17\x12\x5b\xca\xc8\x1b\ +\x46\x1a\x9b\xa8\x1b\xfe\xce\x0c\x7b\xa8\x08\xa7\x9d\x8c\xf4\x21\ +\xd1\x43\x0e\xa1\xa1\x22\xc4\x82\x0a\xd4\xc5\x08\x71\x64\x6e\x63\ +\x02\xba\x04\xec\x12\x50\xd2\xe0\xf3\x37\x64\x11\x32\x94\x78\x2c\ +\x04\xca\xc8\xc6\x71\x38\x72\xb0\x01\x9e\x34\x21\xfb\x85\xca\x1f\ +\x12\x44\x48\x3c\x6e\x6c\xbb\xa1\x08\xb1\x33\x8b\xfd\xef\xe3\x00\ +\x9e\x00\x50\x96\x65\x09\x00\xef\x6b\xad\x4f\x29\xa5\xc0\xcc\x27\ +\x00\x7c\xbe\xaa\xaa\x53\x83\xb4\x39\x80\x4d\x00\xef\x0c\xf6\x5f\ +\x1c\xac\x9f\x17\x5f\x5e\x78\x88\x44\x19\x5b\xc4\x91\x29\x8c\xaa\ +\x9a\x68\xaa\x2c\x82\xa8\xb4\xfc\xf5\x3d\x69\x2b\xe3\x98\x95\xc5\ +\xb9\x87\x45\xe0\xa4\x88\x25\x9b\xd8\x32\xc5\x92\x4d\x94\x99\xc2\ +\x02\x16\x81\x54\x19\xa2\xc7\x76\x0e\xda\x61\x4f\x78\xc4\x8c\xb6\ +\x94\x2d\xb3\x88\x1c\x57\xb9\xea\xa2\x50\x0d\xb6\x53\x6d\x9b\xf6\ +\x88\x69\xc0\x3f\xc3\x17\x63\xff\x36\xfa\x69\x5b\x7e\x31\xf9\xab\ +\xc8\x72\xfa\x44\x51\xec\x93\x2c\xa3\xb6\x39\xf2\xd8\xf0\xde\xbd\ +\x34\x07\x14\xed\x5f\xb3\x8f\x0e\x5a\xa7\x44\x9e\xce\x8a\x12\x2b\ +\x46\x93\x51\x2a\xae\x39\xd3\xb6\x11\x82\xba\x93\xa8\x3d\x8e\x22\ +\x1c\x23\x03\x3e\xc7\x8b\x1b\x08\x27\x97\xe2\x27\xcf\x36\x20\x6e\ +\x86\xcb\x2c\xab\xae\xd9\x46\xd7\x9c\x61\x6d\x38\xc6\xaa\xb6\x4e\ +\x0f\x3a\x32\x5d\x13\x1e\x95\x21\x98\x2a\x43\x08\x65\x78\x70\xd6\ +\x4a\x79\x04\x9b\x6d\x26\xc9\x35\x23\x66\x13\xb9\x29\x33\x42\x2e\ +\x51\xed\x13\x48\xb1\xe9\x5d\xe2\x43\x05\x84\xca\x03\xbf\x89\xf6\ +\x97\x89\xfe\x1b\x9a\x25\x09\x09\x38\x20\x6e\x96\x46\x39\x84\x09\ +\x23\x6e\x16\x2f\x24\x4c\x42\xe5\x81\x47\xa0\x01\x69\x8f\x7b\x86\ +\x06\x2a\x62\x06\x3f\x08\x69\x83\x1f\xae\x19\x27\xd7\x32\x47\x1e\ +\xc7\x37\xc8\xe4\xba\xf9\x7d\x07\xc0\xf7\x06\x37\x1f\x06\xb0\xa7\ +\xb5\x3e\x34\x10\x4e\x3f\xc6\xfe\x47\x69\x17\x06\xfb\x1d\x06\xf0\ +\x2b\x00\x2f\x8a\x4f\x2d\x4c\xc1\x5f\x88\xdd\x16\x7a\x2c\xcb\x14\ +\x0b\xae\x19\x25\x36\x1c\xfe\xca\xe2\xe8\xdb\x66\x8b\x5c\xc2\xa9\ +\x8f\x07\x67\x9c\xea\xcb\xb6\xe3\xd8\x44\x43\xaa\xf3\x1c\xb2\x81\ +\xeb\x11\x42\x53\x9c\xd5\x67\x75\xaa\x9a\xfd\xaa\x80\xdd\x7c\xb3\ +\x65\xe6\x6c\x52\x05\xf7\x6c\x96\x6b\x06\xc9\x35\xe3\x65\x9b\x75\ +\xd2\x8e\x7d\x11\xb0\x01\xe0\x9e\xa9\x43\xc0\xb7\x8a\xa9\xbb\x1c\ +\x59\x7f\x53\x7c\xdb\x18\x11\x47\x16\x9f\x9b\x1b\xb4\xc1\xb6\x75\ +\x01\x59\xca\x37\x7c\x67\x89\x00\x14\xd2\x25\x8e\xff\x22\x51\x8b\ +\x17\xb4\xed\xf2\x50\x03\xe5\x4d\x53\x28\x73\x28\x3f\x46\xfc\xb3\ +\xd0\xec\x10\x9c\xd3\x66\x96\x66\x03\xbc\x65\x61\x66\x10\x95\xd8\ +\x1f\xe8\x57\x93\x38\x37\x6e\xb1\x9e\xc7\xd6\xcb\xd8\x01\x14\xd7\ +\x00\x8c\x8a\x3c\x1e\x4d\xa1\xde\x90\x71\x93\x4b\xb5\xcf\x28\x65\ +\xde\x01\xf0\x6f\x00\x9d\xda\xb1\xfe\xa7\xb5\x7e\x67\x50\xa1\xb2\ +\x81\x90\x22\xec\xbf\xe3\xb4\x23\xb7\x23\x41\x68\xdc\x5f\xc6\x3a\ +\xdc\xb6\x36\xae\x67\xf0\xde\xc4\x23\xa4\x71\xd9\x21\x56\x60\xa4\ +\x96\xb3\xcd\x99\x9e\x49\xfb\x5b\x93\x16\x2e\xa9\xaf\xc8\xb4\x61\ +\xd3\x60\xfe\x35\xb1\xa4\xc6\x61\x50\x6a\x79\x5f\x6e\xc9\x70\xb3\ +\xd0\xe0\xc9\xe2\x4c\x25\x5f\x40\x8f\x78\xa1\x84\xb4\xa3\xd8\xa7\ +\x89\xc3\x19\x53\x06\x9b\xe3\xe6\x1b\xa1\x0a\x75\x54\x2a\xf1\x7c\ +\x9a\xd6\x5f\x4a\xb4\x41\xd3\x86\xed\x9a\x61\x24\x87\x83\xee\x7b\ +\x2e\xd9\xb4\x4f\xc2\x2c\xc1\xf0\x1e\xaa\x5c\x75\x37\x34\xeb\xc0\ +\xb5\x0c\x42\xb3\x92\x14\x39\x1a\x84\xc4\xb6\x03\x34\x0b\x5e\xe1\ +\x7a\xa1\xd5\x25\xc0\x7d\x83\x0e\x94\x78\xde\xa1\x3a\xeb\x13\x88\ +\x0a\x0f\x8e\xe8\x91\xc5\x61\xa0\xc4\xb6\xce\x11\xd7\xbc\x5e\x9e\ +\x9f\x01\xf8\xa5\xc5\x6e\xda\xa8\xbf\xc3\x6d\xbb\xe2\xfb\x0a\x33\ +\xe8\x90\xa7\x3a\xb9\xa6\xf8\x88\x19\xdc\xa3\xc0\x7d\xd0\x27\x00\ +\x5c\x82\x49\x23\xfc\x4e\x0c\x5b\x06\x53\x6c\xb3\x01\xae\xf7\x4c\ +\xea\xfb\x69\x8f\x88\xe0\xc8\x32\x9b\x79\x99\xe9\xb4\xa5\xdf\xa8\ +\x02\xe7\x14\x1b\x54\x23\xb6\x8e\xb0\xe7\xba\xdb\xec\x30\x8e\xfa\ +\x3a\xad\x77\x7c\x6c\x36\x21\x8f\x4f\x47\x13\x14\x61\x21\x7b\xd9\ +\xee\xa5\xf5\xc7\xf0\x3e\x99\x1d\x24\xd2\xcc\xdc\x4a\xac\xd6\x51\ +\x46\xe9\x53\xa2\x68\x8c\x43\xc8\x8d\xe3\x39\x49\x1a\xb1\x7c\x14\ +\xb9\xdd\x26\x42\x62\x85\x67\xdb\x8d\x2b\x75\x3a\x98\x22\x9c\x40\ +\xd7\x3b\x30\xa1\x7c\x7c\xd7\xd4\xd5\xd1\x8f\x1a\xe9\x2d\x65\x44\ +\xde\xf6\x08\x5c\xe8\x5a\xda\x04\x11\x3b\x6c\x07\xb8\x1f\x47\x73\ +\x75\x58\x21\xb1\x3d\x38\x3e\x0f\xd6\xf5\xb1\xff\xfe\x92\xb7\xdc\ +\xec\x38\x6f\x04\x6e\xb6\x14\x10\x05\xb6\x69\xfc\x50\x3b\x76\x0d\ +\x30\xf8\x3a\xf4\xd4\x59\x96\x94\x99\x24\x8e\xa8\x4f\xae\x68\x82\ +\x31\xfd\xa5\xcb\xd6\x66\x3d\xb1\xd9\x56\x45\x96\x9f\x42\x76\x60\ +\x66\x64\x59\x86\x3c\xcf\x51\x96\xe5\xae\x08\x20\x61\xc6\x49\xf5\ +\x65\x62\xda\x63\x13\x67\x99\x1d\x8e\xbd\x29\x5e\x6c\x8f\x87\xd9\ +\x04\x87\x0e\xe4\x6f\x3e\x46\x06\xdc\x3f\x3a\xe6\xeb\xb3\x29\xe2\ +\x5c\x39\xe0\x24\xb3\xe3\x7e\xa6\x03\xe2\xc5\x14\x4c\x3a\x20\x1e\ +\x63\x84\x0f\x07\x84\x2f\x1c\xfd\xa8\xeb\x9c\x08\xe1\xf7\x91\x9a\ +\xfa\xd0\x14\x29\xf0\x26\xd5\x6e\x42\x41\x7f\x46\x1d\x9c\x9e\x94\ +\x80\x42\xbe\x1f\xcd\xea\x9e\x5d\x69\xff\xf7\x58\xc5\xc8\xb8\x47\ +\x72\xda\x8e\xc2\xd7\x64\x36\xcb\x74\xc4\xea\xa3\xb9\xb1\x91\xfa\ +\x46\x9d\xf9\x09\x8d\x12\xc7\xe6\x43\x81\x06\x19\x7b\xfc\x50\x3d\ +\x89\x71\x5e\xeb\xef\x2c\xc5\x74\x4e\xa1\x1b\x8d\xad\x73\x51\x9e\ +\xb2\x21\xe0\xa8\x23\x90\x87\xf2\x8c\x36\x85\x6c\x1c\x1b\x32\x1c\ +\x0e\x71\xc4\x0e\x61\x41\x11\x37\x3e\x0a\x38\xc4\x83\x20\x0f\xda\ +\x27\x68\x52\x3b\x7a\x5f\x00\x83\x90\xb0\x8d\x15\x50\x40\xda\xac\ +\x63\xac\x83\x44\x81\x3a\xee\xeb\x2f\x7c\xb3\x3a\xb1\xed\x9c\x03\ +\x83\x0e\x2e\x1b\xb0\xa3\xae\xf8\x66\xcc\x92\xa2\x19\x11\x11\xaa\ +\xaa\xe2\xc1\x7b\x49\x3f\x04\xf0\x4d\xec\xbf\x9b\x54\x77\x86\xba\ +\xd8\x0f\xdc\x50\x2f\xd3\x21\x00\xbf\x07\xf0\x73\xf1\xdf\x85\x4f\ +\x19\xbe\x41\xac\xd0\xa7\x4a\x42\xef\x06\x9a\xf7\x47\x04\xfa\x0e\ +\xdb\xf1\x42\x02\x24\xa6\x9c\x80\x3f\xb8\x00\x5b\xfa\x06\xed\xe8\ +\x4f\xd8\xd1\x97\x68\x87\xed\xcc\x19\x6d\x82\xff\x71\x7c\xf2\x08\ +\xb2\x90\xc8\xe5\x48\xe1\x45\x0e\x5f\x83\x30\xf9\xc7\xf2\x26\x91\ +\x57\xaa\x7f\x19\x13\x41\x75\x1a\xa2\xce\xe9\x8b\xe7\xc0\x0a\xb4\ +\x5e\x03\x70\x14\xc0\xde\xa7\x61\x94\xa7\xc9\x3e\xe3\x0a\x13\x1e\ +\x7a\xf4\x25\xf4\x82\xbb\x42\x7c\xe8\xe7\xd8\xa8\x75\x29\x8f\x33\ +\x35\x89\xe6\xe7\xfa\xad\x1c\xce\x67\xea\xcb\xe7\x66\x94\x39\x57\ +\x44\xbc\x7a\x34\x3b\x5b\x44\xbc\x0c\xee\xd0\xe1\x66\x70\x07\x33\ +\x1d\xc1\x1e\x46\xdc\x16\xa1\x6f\x48\x06\x77\xa0\x06\xdb\x6f\x33\ +\x8d\x2f\x9a\x9d\xad\x6e\xd8\x82\x38\xc4\x04\x88\x70\x95\xc3\xb7\ +\xec\x09\x0c\xa1\x01\x74\x49\xeb\xb3\x2a\xcb\xae\xb3\xa7\x3c\xa1\ +\x88\x7c\x80\x3b\xf2\x9d\xaf\xbe\xa5\x44\xae\x4b\xcd\xcb\xd6\xb6\ +\x62\xae\x91\x6f\x3f\x20\x2e\x52\x1f\x22\x97\x3f\x55\xf4\xfb\x7d\ +\xcc\xcf\xcf\xe3\xe8\x63\x8f\x41\x6b\x7d\x8a\x99\xbf\xd6\xef\xf7\ +\x3b\xcb\xcb\xcb\x7c\xec\xd8\x31\xbd\xb5\xb5\x95\x6f\x6e\x6e\xae\ +\x9c\x39\x73\x66\x7b\x7d\x7d\xbd\xfc\xe0\x83\x0f\xd4\x9d\x3b\x77\ +\xa8\x28\x8a\xd5\xa2\x28\x36\xb7\xb7\xb7\xb1\xdd\xe9\x20\xcb\x32\ +\x71\xc1\x85\x59\xf0\x3b\xc6\xd5\x16\x5d\x91\xf0\xea\x51\xf0\x6c\ +\x91\xef\x86\x81\x1c\xfa\xc6\xba\x7a\x34\xbc\xca\xb2\x5c\xcf\xdf\ +\x15\x00\xc1\x25\x0e\x6c\x8f\xe6\xd9\x06\x2b\x7d\x03\x97\xbe\x80\ +\x0a\xe6\xf6\xca\x28\x9f\x2b\xc4\x7a\x05\x77\x90\x87\xfa\xf9\xd6\ +\xed\xed\x0a\xe9\xee\x0a\xd8\x60\x2b\x9b\xc2\x83\x41\xb9\x6c\xa2\ +\xd5\x35\xdb\xa7\x30\x9d\x77\x93\x68\xc2\xfb\xce\x4a\x68\x70\x5f\ +\xf9\x87\xd7\x12\x39\xf3\x21\xd6\xfa\x69\xd2\xfa\xeb\x20\xba\x89\ +\xfb\x1f\xef\x9c\x99\xd1\x98\xd4\xfd\x46\xbd\x08\x4d\xc5\x12\x22\ +\x44\x13\x3c\xc2\x28\xe4\x88\x85\x04\x88\xcb\x99\x8a\x15\x8c\x29\ +\xc7\x88\x15\x53\x4d\xf6\xb3\x45\x9b\x73\x85\x12\x87\x21\x96\x4c\ +\x01\xe4\x5b\x26\xc4\x45\xbf\xb3\x95\x01\x88\x8f\x60\xd7\xe4\xb7\ +\xeb\x1a\xa6\x7e\x0b\xc9\x55\x07\x52\x1d\x78\x76\x9c\x1f\x00\x4d\ +\x5a\x3f\x4b\x59\xf6\x0a\x88\x00\x66\xef\xe3\x92\xca\x53\x37\x63\ +\xbe\x2d\x16\xca\x33\x35\x82\x9c\xab\xde\xaa\x48\x81\xdf\x66\x9a\ +\x50\x7b\x8e\x99\x85\x6e\x63\x20\x24\x26\x02\x5e\x72\x9f\x41\x44\ +\x79\xbf\x2c\xab\x95\x95\x95\xee\x33\xcf\x3c\x83\xb2\x2c\x7f\x0b\ +\xe0\x3f\x44\xd4\x5d\x5e\x5e\xa6\x95\x95\x95\x1b\xef\xbd\xf7\xde\ +\xa9\xcd\xcd\xcd\x97\xd6\xd7\xd7\x7f\x71\xfa\xf4\xe9\xcb\xc7\x8f\ +\x1f\x3f\xba\xbd\xbd\x8d\xaa\xaa\xe6\x57\x57\x57\x5f\xfb\xfb\xe5\ +\xcb\xd8\xda\xda\xea\x2e\x66\xd9\xc6\xa0\xbd\xcb\x77\x96\x84\x59\ +\xf3\x53\x62\xde\xbf\xf1\x85\xc8\xb6\x85\xa6\xb6\x89\x27\xc6\x83\ +\xdf\x4b\xd2\x0e\x11\xe4\x12\x5c\x36\xd1\xe1\x7a\x0c\x2f\x34\x53\ +\x13\xb2\x93\xf9\x28\x9d\x29\x0e\xeb\xff\x01\x7f\xc4\x38\x5f\x59\ +\x6d\xdf\x51\xf2\xa5\x0f\x7d\xd0\x17\x35\x1b\xb9\xca\x65\x13\x4e\ +\xa1\x6f\x29\xb9\xc2\xb2\xfb\x04\x6a\xec\x4c\x58\xac\xff\x4c\x11\ +\x22\x78\xdc\x42\x6b\xda\x62\xc9\x77\xec\x7b\xa2\x38\x1f\x51\x98\ +\x8c\x43\xec\x8c\x78\xc8\x0c\x40\x4e\x33\xfc\xbd\x28\x9a\xf0\x7e\ +\x78\xc8\x8e\x37\x89\x8f\x10\xb7\x19\x01\x8c\x26\x6c\xfb\x29\xd4\ +\x2f\x06\x50\xd0\x0c\xd4\xb9\x36\xf6\x9f\xe5\x20\x32\x34\xc1\x36\ +\xd8\xfa\xcd\x8b\x07\x1f\xe1\x52\x4a\x6d\x65\x59\x06\x66\xfe\x2b\ +\x80\xbf\x2d\x2d\x2d\xf1\x8d\x1b\x37\xf8\xd2\xa5\x4b\x7b\x44\xf4\ +\x6c\x96\x65\x2f\xbd\xf5\xd6\x5b\xaf\x5d\xbe\x7c\xf9\x0f\x67\xcf\ +\x9e\x5d\x5c\x5f\x5f\xc7\xce\xce\x0e\x65\x59\xd6\x23\xa5\x00\xe0\ +\x06\xf6\x1f\xdf\x13\x84\x87\x4d\x68\xc5\x88\xb0\x90\xe3\x1c\x0a\ +\xff\xdc\xe6\xc7\x4d\x53\xc4\x52\xdb\x3e\x24\x27\xec\xc3\x2d\xe4\ +\x8d\x96\xf2\x68\x3b\xda\x9e\x30\x01\xf2\x03\xd8\x09\x49\xd9\x04\ +\x41\x10\xa6\x4f\x09\xa0\xcc\xb2\x0c\xdd\x6e\x17\xb7\x6f\xdf\xfe\ +\x0a\x11\x7d\x3f\xcb\x32\x74\x3a\x9d\x1f\x00\xe8\xef\xee\xee\xbe\ +\x6a\x79\xe4\xae\x1a\x08\x26\x41\x10\x04\x41\x18\x3b\x4a\x4c\x20\ +\x08\x82\x20\x4c\x83\x3c\xcf\xd1\xe9\x74\xb0\xb1\xb1\x81\x85\x85\ +\x85\xcf\x14\x45\xf1\xed\xb2\x2c\x31\x37\x37\x77\x7e\x71\x71\xf1\ +\xcc\xc6\xc6\x06\x3a\xf2\x8e\x92\x20\x08\x82\x20\x62\x49\x10\x04\ +\x41\x38\xa8\x94\x65\x89\xb5\xb5\xb5\x3f\xaf\xac\xac\x5c\x01\x80\ +\x23\x47\x8e\x7c\xf8\xe8\xa3\x8f\xfe\x69\x10\x35\x4f\x10\x04\x41\ +\x10\x44\x2c\x09\x82\x20\x08\x07\x8f\x5b\xb7\x6e\xe1\xc9\x27\x9f\ +\xc4\xfa\xfa\xfa\xbf\xca\xb2\xfc\x35\x00\xcc\xcd\xcd\xfd\xe6\xe4\ +\xc9\x93\xff\x38\x77\xee\x1c\xb6\xb7\xb7\xc5\x48\x82\x20\x08\x82\ +\x88\x25\x41\x10\x04\xe1\x60\xa3\xb5\xbe\x34\x10\x4b\xaf\x88\x35\ +\x04\x41\x10\x84\x59\x20\x17\x13\x08\x82\x20\x08\xd3\xa6\x2c\x4b\ +\x2c\x2c\x2c\x5c\x3d\x72\xe4\xc8\x4f\xe6\xe7\xe7\xff\x59\x55\x95\ +\x18\x45\x10\x04\x41\x10\xb1\x24\x08\x82\x20\x1c\x4c\xfa\xfd\x3e\ +\x56\x57\x57\x71\xf8\xf0\x61\x00\xc0\xb1\x63\xc7\xee\x68\xad\x5f\ +\x65\xe6\x7b\xcf\xde\x31\x33\x7a\xbd\x1e\x1e\x79\xe4\x11\x31\x98\ +\x20\x08\x82\x30\x71\x68\xf8\xed\x0b\x41\x10\x04\x41\x10\x04\x41\ +\x10\x04\xe1\x13\xfe\x0f\xb6\x45\x73\xbb\x5f\x84\x1a\x5c\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x19\xf7\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -1019,11 +1806,21 @@ \x04\x77\x9e\xe7\ \x00\x77\ \x00\x61\x00\x72\x00\x6e\x00\x69\x00\x6e\x00\x67\x00\x5f\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1b\ +\x04\xf8\xcb\xe7\ +\x00\x76\ +\x00\x65\x00\x72\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\x00\x65\x00\x72\ +\x00\x5f\x00\x73\x00\x68\x00\x6f\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x01\x66\xef\x27\ \x00\x68\ \x00\x6f\x00\x72\x00\x69\x00\x7a\x00\x6f\x00\x6e\x00\x74\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\ \x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1d\ +\x04\x58\xf4\x67\ +\x00\x68\ +\x00\x6f\x00\x72\x00\x69\x00\x7a\x00\x6f\x00\x6e\x00\x74\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\ +\x00\x65\x00\x72\x00\x5f\x00\x73\x00\x68\x00\x6f\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x01\x36\xf0\xe7\ \x00\x76\ @@ -1033,10 +1830,12 @@ qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x02\ -\x00\x00\x00\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x23\xfd\ -\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x0e\x85\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ +\x00\x00\x00\xea\x00\x00\x00\x00\x00\x01\x00\x00\x54\xe4\ +\x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x28\xed\ +\x00\x00\x00\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x65\ \x00\x00\x00\x14\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x0e\x85\ " def qInitResources(): diff --git a/pyrs/icons/icons_rc5.py b/pyrs/icons/icons_rc5.py new file mode 100644 index 000000000..1e7a0da21 --- /dev/null +++ b/pyrs/icons/icons_rc5.py @@ -0,0 +1,1847 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.9.2) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x16\x7b\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x03\x4b\x00\x00\x00\x1a\x08\x06\x00\x00\x00\x66\x7e\xc7\xb9\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x08\x73\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\ +\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\ +\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\ +\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\ +\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\ +\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x65\ +\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\ +\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\x66\x2f\x31\x2e\ +\x30\x2f\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\ +\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\x31\x37\x20\x28\x4d\x61\ +\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\x78\x6d\x70\x3a\x43\x72\ +\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x36\x2d\x31\ +\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\x35\ +\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\ +\x61\x74\x65\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\ +\x31\x34\x3a\x31\x34\x3a\x34\x37\x2d\x30\x35\x3a\x30\x30\x22\x20\ +\x78\x6d\x70\x3a\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\ +\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\ +\x31\x34\x3a\x34\x37\x2d\x30\x35\x3a\x30\x30\x22\x20\x64\x63\x3a\ +\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\ +\x67\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\ +\x6f\x72\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\ +\x22\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\ +\x2e\x31\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\ +\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x38\ +\x66\x61\x39\x66\x61\x32\x2d\x30\x39\x30\x65\x2d\x34\x33\x31\x37\ +\x2d\x62\x63\x39\x30\x2d\x33\x62\x30\x63\x39\x30\x31\x32\x33\x64\ +\x62\x34\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\ +\x6e\x74\x49\x44\x3d\x22\x61\x64\x6f\x62\x65\x3a\x64\x6f\x63\x69\ +\x64\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x31\x61\x36\x61\ +\x36\x33\x66\x62\x2d\x37\x33\x31\x34\x2d\x61\x62\x34\x36\x2d\x39\ +\x30\x39\x30\x2d\x35\x32\x32\x31\x36\x39\x38\x31\x32\x62\x65\x61\ +\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\x61\x6c\ +\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\ +\x64\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\ +\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\ +\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x74\x69\x66\x66\x3a\x4f\ +\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x22\x31\x22\x20\x74\ +\x69\x66\x66\x3a\x58\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x55\x6e\ +\x69\x74\x3d\x22\x32\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\x6c\x6f\ +\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\x20\x65\ +\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\x6e\x73\ +\x69\x6f\x6e\x3d\x22\x31\x32\x30\x34\x22\x20\x65\x78\x69\x66\x3a\ +\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\x6e\x3d\ +\x22\x32\x30\x22\x3e\x20\x3c\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\ +\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\x74\x6f\ +\x72\x73\x3e\x20\x3c\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\x3c\x72\ +\x64\x66\x3a\x6c\x69\x3e\x78\x6d\x70\x2e\x64\x69\x64\x3a\x62\x30\ +\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\ +\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\ +\x33\x62\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x2f\x72\x64\ +\x66\x3a\x42\x61\x67\x3e\x20\x3c\x2f\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\ +\x74\x6f\x72\x73\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\ +\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\ +\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\x61\x63\ +\x74\x69\x6f\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\ +\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\ +\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x2f\ +\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\ +\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x63\x36\x66\x63\x62\x66\ +\x38\x2d\x34\x34\x34\x35\x2d\x34\x66\x33\x62\x2d\x61\x34\x64\x36\ +\x2d\x33\x30\x36\x62\x66\x31\x30\x31\x62\x61\x30\x39\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x33\x38\x3a\x31\x32\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\ +\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\x22\x2f\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x38\x66\x61\x39\ +\x66\x61\x32\x2d\x30\x39\x30\x65\x2d\x34\x33\x31\x37\x2d\x62\x63\ +\x39\x30\x2d\x33\x62\x30\x63\x39\x30\x31\x32\x33\x64\x62\x34\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x32\ +\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\x31\x34\x3a\x34\x37\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\ +\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\x79\ +\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x20\ +\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\x3f\x78\ +\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\x3f\x3e\ +\x88\x34\x49\xc3\x00\x00\x0d\xae\x49\x44\x41\x54\x78\xda\xed\x9d\ +\x4f\xa8\x1d\x57\x1d\xc7\xbf\xbf\x33\x33\xef\x6f\xf3\x5e\x08\xaf\ +\x3c\xd2\x24\x46\x09\xa1\xa4\xb6\xa5\x11\x49\xa1\x22\x68\x41\xc1\ +\x85\x90\x45\x36\x56\xc4\x85\xba\x74\xe3\xc6\x45\x57\x5d\x75\x21\ +\x82\x41\xdc\x08\x2e\xdc\xb8\x72\x23\x82\x35\x14\x41\xb0\x50\xdb\ +\x07\xb6\x44\x7c\xb4\x04\x69\x63\x9a\x6a\xe2\x8b\xe9\x7d\xcd\x7b\ +\x79\xf7\xde\x99\xf3\x73\xf1\xee\x4d\x27\x27\xe7\xef\xdc\xb9\x7f\ +\x9a\xf7\xfb\xc0\xe3\xdd\x99\x39\x73\xe6\xcc\x6f\xce\x39\xf3\xfb\ +\x9e\x33\xf3\x1b\x62\x66\x08\x82\x20\x08\x82\x20\x08\x82\x20\x08\ +\xf7\x43\x8b\x0b\x00\x50\xa0\xaa\x5e\x00\xf3\x63\x00\xba\x00\xd4\ +\x08\x59\x6a\x00\xf3\x00\x56\x07\xbf\x05\x41\x78\x88\xbb\x10\x00\ +\x19\xb4\xfe\x22\x8a\xe2\x47\x50\xea\x6d\x30\x2f\x88\x59\x04\x77\ +\x8d\x21\x42\xaf\xdf\x47\x59\x96\x38\x79\xf2\x24\x9e\x7b\xee\x39\ +\xec\xec\xec\x0c\x37\xcf\x6b\xad\x9f\x66\xe6\x77\x00\x7c\x0c\x00\ +\xcb\xcb\xcb\x78\xfd\xf5\xd7\x71\xed\xda\x35\x14\x79\x8e\xa2\x28\ +\x20\x83\x7c\x82\x20\x08\xc2\x24\xd8\xbd\x7b\x17\xb9\x98\x41\x10\ +\x04\x41\x98\x06\x57\xaf\x5e\xc5\xf5\xeb\xd7\xa1\x94\x42\x59\x96\ +\x58\x5a\x5a\x5a\x3a\x71\xe2\xc4\xf3\x00\x3e\x04\xf0\x71\x51\x14\ +\xb8\x7d\xfb\x36\xae\x5d\xbb\x26\xc6\x12\x04\x41\x10\xa6\x82\x12\ +\x13\x08\x82\x20\x08\xd3\x26\xcb\x32\xec\xed\xed\x1d\x7f\xf7\xdd\ +\x77\x5f\xec\xf5\x7a\x9f\xcb\x73\x19\xcb\x13\x04\x41\x10\x44\x2c\ +\x09\x82\x20\x08\x07\x98\x3c\xcf\xf1\xf2\xcb\x2f\xe3\xc2\x85\x0b\ +\x28\xcb\xf2\xab\x5a\xeb\x43\xb7\x6e\xdd\x7a\xfe\xa9\xa7\x9e\xc2\ +\xf9\xf3\xe7\x71\xf3\xe6\x4d\x31\x92\x20\x08\x82\x20\x62\x49\x10\ +\x04\x41\x38\xd0\x1c\x02\xf0\x5d\x00\xe8\x74\x3a\x2f\x68\xad\x8f\ +\x12\x91\x58\x45\x10\x04\x41\x10\xb1\x24\x08\x82\x20\x1c\x6c\x98\ +\xf9\x42\x51\x14\x5f\x00\x80\xa2\x28\x1e\xef\xf5\x7a\xdf\x12\xb1\ +\x24\x08\x82\x20\x4c\x9b\x71\x3e\x14\x3e\xcd\xbb\x1c\x8d\xb0\x1f\ +\xb7\x90\x27\x35\xd8\x46\x89\x69\x9a\xe4\xdb\xb6\xfd\x28\xc1\x96\ +\x29\xe5\xb3\xd9\x22\xe6\xff\xa8\xdb\x6c\xc7\xf6\x5d\x17\x8a\x3c\ +\x87\x71\xa6\x19\xda\xb9\x49\x5e\x29\xe5\x68\x23\x5d\xdb\xe5\x6c\ +\x52\xef\xa9\x85\x76\xe5\xb2\xf9\xb8\xda\x29\x8d\xb1\xad\xd6\xaf\ +\xcd\x28\xe7\x9c\x72\xac\xbf\x00\xb8\x3b\x5c\xb9\xb7\xb7\x87\x8b\ +\x17\x2f\x62\x6d\x6d\x6d\x6f\x6e\x6e\xee\x8d\xaa\xaa\x9e\x25\xa2\ +\xb7\xaf\x5c\xb9\xf2\xd1\x9b\x6f\xbe\x89\x6e\xb7\x6b\xe6\xb3\x08\ +\xe0\xcb\x96\x72\x0b\xc2\x4c\xe8\xfe\x84\x6d\xb6\x65\xb6\x2c\x73\ +\xc4\xb2\xf9\xa7\x3d\x69\xea\xa1\x8a\xb5\x51\x16\x33\xff\xd8\x73\ +\x1b\xd5\x16\xbe\xf4\xba\xd6\x07\xb1\x25\xbd\xad\xcc\x1c\xb0\x29\ +\x1c\xe7\x19\xbb\x2e\x94\xc6\x55\x36\x4e\xac\x0f\x4d\x6c\x9b\xe2\ +\xef\xd6\x7d\x35\x32\xf2\xb7\xa5\x09\xf5\xfb\x31\xe9\xc9\x71\x1e\ +\xbe\xf5\xd3\xec\xeb\x75\x3d\x74\x38\xb5\x13\x3a\xbc\x15\xb1\x34\ +\xaa\xa3\x43\x2d\xed\xd7\x86\xa3\x66\x3a\xe5\x31\xcb\xe4\x71\xea\ +\x7d\x79\xc3\xe3\xe8\xfb\xca\xc5\x91\xdb\x54\x82\xa8\x69\x22\x7a\ +\xa8\x76\x0c\xd3\x16\x0a\xc3\x38\xd5\x9f\x2c\x67\xb5\xf5\xca\x58\ +\x67\xfe\xd5\xd7\x53\x83\xdf\xf5\x32\xc0\x52\x36\x18\x65\xb7\xad\ +\x43\xc4\x35\x37\xed\xa2\x2c\xd7\x34\xb6\x0e\x99\xc7\x53\x01\xdb\ +\x93\x47\x2c\x3a\x8e\x53\x82\x68\x8f\xb4\xfe\x12\xe5\xf9\x4f\xa1\ +\xd4\xdb\xc4\xbc\xe0\x3a\x17\x5b\x19\x7c\xe7\x17\x23\x78\x95\xa3\ +\xde\x53\xe0\x1c\xea\xe5\x89\x29\xa7\xcd\x2e\x1c\xb0\x9b\xad\x1d\ +\x51\xa2\x98\x0f\x95\x01\x9e\xb6\xcf\x81\x7e\x80\x3c\xfd\x1a\x05\ +\xfa\x02\xd7\xb9\xf9\xfa\xca\x82\x88\x74\x55\x55\x4f\x74\x7b\xbd\ +\xf7\x6b\xed\x19\x79\x9e\x73\x55\x55\x55\x96\x65\xe7\xb2\x2c\x7b\ +\xa3\xd7\xeb\x7d\x03\xc0\x1f\x95\x52\x59\x55\x55\xc3\xfc\xaa\x22\ +\xcf\xb9\x28\x8a\xcf\x32\xf3\xe6\xe0\x1a\x95\xe2\x9b\x0b\x53\x14\ +\x3f\x3e\x07\x37\x46\xf4\x68\x87\x90\x31\xff\xaa\xda\xef\xb2\xb6\ +\x6e\xf8\x57\x1a\xff\x87\xbf\xeb\x7f\x95\x25\xcd\x70\x99\x2d\xc7\ +\xaa\xaf\x73\x39\xfb\x36\x5b\x90\x23\x1d\x1b\xdb\xb5\xc3\x3e\x6c\ +\x88\x38\xd3\x46\x95\xe5\xb7\xf6\xfc\x55\x46\x1e\xe6\xf9\x99\x79\ +\xd6\xf3\x33\xc5\x64\x55\xfb\xad\x8d\x7c\xb5\xc5\x8e\xb6\x6b\xca\ +\x0e\xfb\xba\x44\x6e\x1b\xc2\xaa\x89\x58\x4a\x6d\x07\x64\x11\x58\ +\x88\xa8\x2b\xae\xe3\x50\x44\xda\x71\x43\x00\xaa\xdd\xbb\x77\xbb\ +\x39\x30\x07\x80\x28\xcb\x7e\x37\x03\xe2\x6d\xa2\x06\x18\x87\x50\ +\x8b\x71\x74\x54\xa2\xd3\x0c\xc3\x61\x4f\x99\x05\xa1\x08\xf1\x18\ +\x23\x68\x10\x21\x96\x54\x20\xef\x90\xd3\x67\x3a\xc0\xa6\x40\xb1\ +\x09\x9f\xba\x78\xca\x2c\xa2\x28\xb3\xec\x17\x12\x5b\xca\xc8\x1b\ +\x46\x1a\x9b\xa8\x1b\xfe\xce\x0c\x7b\xa8\x08\xa7\x9d\x8c\xf4\x21\ +\xd1\x43\x0e\xa1\xa1\x22\xc4\x82\x0a\xd4\xc5\x08\x71\x64\x6e\x63\ +\x02\xba\x04\xec\x12\x50\xd2\xe0\xf3\x37\x64\x11\x32\x94\x78\x2c\ +\x04\xca\xc8\xc6\x71\x38\x72\xb0\x01\x9e\x34\x21\xfb\x85\xca\x1f\ +\x12\x44\x48\x3c\x6e\x6c\xbb\xa1\x08\xb1\x33\x8b\xfd\xef\xe3\x00\ +\x9e\x00\x50\x96\x65\x09\x00\xef\x6b\xad\x4f\x29\xa5\xc0\xcc\x27\ +\x00\x7c\xbe\xaa\xaa\x53\x83\xb4\x39\x80\x4d\x00\xef\x0c\xf6\x5f\ +\x1c\xac\x9f\x17\x5f\x5e\x78\x88\x44\x19\x5b\xc4\x91\x29\x8c\xaa\ +\x9a\x68\xaa\x2c\x82\xa8\xb4\xfc\xf5\x3d\x69\x2b\xe3\x98\x95\xc5\ +\xb9\x87\x45\xe0\xa4\x88\x25\x9b\xd8\x32\xc5\x92\x4d\x94\x99\xc2\ +\x02\x16\x81\x54\x19\xa2\xc7\x76\x0e\xda\x61\x4f\x78\xc4\x8c\xb6\ +\x94\x2d\xb3\x88\x1c\x57\xb9\xea\xa2\x50\x0d\xb6\x53\x6d\x9b\xf6\ +\x88\x69\xc0\x3f\xc3\x17\x63\xff\x36\xfa\x69\x5b\x7e\x31\xf9\xab\ +\xc8\x72\xfa\x44\x51\xec\x93\x2c\xa3\xb6\x39\xf2\xd8\xf0\xde\xbd\ +\x34\x07\x14\xed\x5f\xb3\x8f\x0e\x5a\xa7\x44\x9e\xce\x8a\x12\x2b\ +\x46\x93\x51\x2a\xae\x39\xd3\xb6\x11\x82\xba\x93\xa8\x3d\x8e\x22\ +\x1c\x23\x03\x3e\xc7\x8b\x1b\x08\x27\x97\xe2\x27\xcf\x36\x20\x6e\ +\x86\xcb\x2c\xab\xae\xd9\x46\xd7\x9c\x61\x6d\x38\xc6\xaa\xb6\x4e\ +\x0f\x3a\x32\x5d\x13\x1e\x95\x21\x98\x2a\x43\x08\x65\x78\x70\xd6\ +\x4a\x79\x04\x9b\x6d\x26\xc9\x35\x23\x66\x13\xb9\x29\x33\x42\x2e\ +\x51\xed\x13\x48\xb1\xe9\x5d\xe2\x43\x05\x84\xca\x03\xbf\x89\xf6\ +\x97\x89\xfe\x1b\x9a\x25\x09\x09\x38\x20\x6e\x96\x46\x39\x84\x09\ +\x23\x6e\x16\x2f\x24\x4c\x42\xe5\x81\x47\xa0\x01\x69\x8f\x7b\x86\ +\x06\x2a\x62\x06\x3f\x08\x69\x83\x1f\xae\x19\x27\xd7\x32\x47\x1e\ +\xc7\x37\xc8\xe4\xba\xf9\x7d\x07\xc0\xf7\x06\x37\x1f\x06\xb0\xa7\ +\xb5\x3e\x34\x10\x4e\x3f\xc6\xfe\x47\x69\x17\x06\xfb\x1d\x06\xf0\ +\x2b\x00\x2f\x8a\x4f\x2d\x4c\xc1\x5f\x88\xdd\x16\x7a\x2c\xcb\x14\ +\x0b\xae\x19\x25\x36\x1c\xfe\xca\xe2\xe8\xdb\x66\x8b\x5c\xc2\xa9\ +\x8f\x07\x67\x9c\xea\xcb\xb6\xe3\xd8\x44\x43\xaa\xf3\x1c\xb2\x81\ +\xeb\x11\x42\x53\x9c\xd5\x67\x75\xaa\x9a\xfd\xaa\x80\xdd\x7c\xb3\ +\x65\xe6\x6c\x52\x05\xf7\x6c\x96\x6b\x06\xc9\x35\xe3\x65\x9b\x75\ +\xd2\x8e\x7d\x11\xb0\x01\xe0\x9e\xa9\x43\xc0\xb7\x8a\xa9\xbb\x1c\ +\x59\x7f\x53\x7c\xdb\x18\x11\x47\x16\x9f\x9b\x1b\xb4\xc1\xb6\x75\ +\x01\x59\xca\x37\x7c\x67\x89\x00\x14\xd2\x25\x8e\xff\x22\x51\x8b\ +\x17\xb4\xed\xf2\x50\x03\xe5\x4d\x53\x28\x73\x28\x3f\x46\xfc\xb3\ +\xd0\xec\x10\x9c\xd3\x66\x96\x66\x03\xbc\x65\x61\x66\x10\x95\xd8\ +\x1f\xe8\x57\x93\x38\x37\x6e\xb1\x9e\xc7\xd6\xcb\xd8\x01\x14\xd7\ +\x00\x8c\x8a\x3c\x1e\x4d\xa1\xde\x90\x71\x93\x4b\xb5\xcf\x28\x65\ +\xde\x01\xf0\x6f\x00\x9d\xda\xb1\xfe\xa7\xb5\x7e\x67\x50\xa1\xb2\ +\x81\x90\x22\xec\xbf\xe3\xb4\x23\xb7\x23\x41\x68\xdc\x5f\xc6\x3a\ +\xdc\xb6\x36\xae\x67\xf0\xde\xc4\x23\xa4\x71\xd9\x21\x56\x60\xa4\ +\x96\xb3\xcd\x99\x9e\x49\xfb\x5b\x93\x16\x2e\xa9\xaf\xc8\xb4\x61\ +\xd3\x60\xfe\x35\xb1\xa4\xc6\x61\x50\x6a\x79\x5f\x6e\xc9\x70\xb3\ +\xd0\xe0\xc9\xe2\x4c\x25\x5f\x40\x8f\x78\xa1\x84\xb4\xa3\xd8\xa7\ +\x89\xc3\x19\x53\x06\x9b\xe3\xe6\x1b\xa1\x0a\x75\x54\x2a\xf1\x7c\ +\x9a\xd6\x5f\x4a\xb4\x41\xd3\x86\xed\x9a\x61\x24\x87\x83\xee\x7b\ +\x2e\xd9\xb4\x4f\xc2\x2c\xc1\xf0\x1e\xaa\x5c\x75\x37\x34\xeb\xc0\ +\xb5\x0c\x42\xb3\x92\x14\x39\x1a\x84\xc4\xb6\x03\x34\x0b\x5e\xe1\ +\x7a\xa1\xd5\x25\xc0\x7d\x83\x0e\x94\x78\xde\xa1\x3a\xeb\x13\x88\ +\x0a\x0f\x8e\xe8\x91\xc5\x61\xa0\xc4\xb6\xce\x11\xd7\xbc\x5e\x9e\ +\x9f\x01\xf8\xa5\xc5\x6e\xda\xa8\xbf\xc3\x6d\xbb\xe2\xfb\x0a\x33\ +\xe8\x90\xa7\x3a\xb9\xa6\xf8\x88\x19\xdc\xa3\xc0\x7d\xd0\x27\x00\ +\x5c\x82\x49\x23\xfc\x4e\x0c\x5b\x06\x53\x6c\xb3\x01\xae\xf7\x4c\ +\xea\xfb\x69\x8f\x88\xe0\xc8\x32\x9b\x79\x99\xe9\xb4\xa5\xdf\xa8\ +\x02\xe7\x14\x1b\x54\x23\xb6\x8e\xb0\xe7\xba\xdb\xec\x30\x8e\xfa\ +\x3a\xad\x77\x7c\x6c\x36\x21\x8f\x4f\x47\x13\x14\x61\x21\x7b\xd9\ +\xee\xa5\xf5\xc7\xf0\x3e\x99\x1d\x24\xd2\xcc\xdc\x4a\xac\xd6\x51\ +\x46\xe9\x53\xa2\x68\x8c\x43\xc8\x8d\xe3\x39\x49\x1a\xb1\x7c\x14\ +\xb9\xdd\x26\x42\x62\x85\x67\xdb\x8d\x2b\x75\x3a\x98\x22\x9c\x40\ +\xd7\x3b\x30\xa1\x7c\x7c\xd7\xd4\xd5\xd1\x8f\x1a\xe9\x2d\x65\x44\ +\xde\xf6\x08\x5c\xe8\x5a\xda\x04\x11\x3b\x6c\x07\xb8\x1f\x47\x73\ +\x75\x58\x21\xb1\x3d\x38\x3e\x0f\xd6\xf5\xb1\xff\xfe\x92\xb7\xdc\ +\xec\x38\x6f\x04\x6e\xb6\x14\x10\x05\xb6\x69\xfc\x50\x3b\x76\x0d\ +\x30\xf8\x3a\xf4\xd4\x59\x96\x94\x99\x24\x8e\xa8\x4f\xae\x68\x82\ +\x31\xfd\xa5\xcb\xd6\x66\x3d\xb1\xd9\x56\x45\x96\x9f\x42\x76\x60\ +\x66\x64\x59\x86\x3c\xcf\x51\x96\xe5\xae\x08\x20\x61\xc6\x49\xf5\ +\x65\x62\xda\x63\x13\x67\x99\x1d\x8e\xbd\x29\x5e\x6c\x8f\x87\xd9\ +\x04\x87\x0e\xe4\x6f\x3e\x46\x06\xdc\x3f\x3a\xe6\xeb\xb3\x29\xe2\ +\x5c\x39\xe0\x24\xb3\xe3\x7e\xa6\x03\xe2\xc5\x14\x4c\x3a\x20\x1e\ +\x63\x84\x0f\x07\x84\x2f\x1c\xfd\xa8\xeb\x9c\x08\xe1\xf7\x91\x9a\ +\xfa\xd0\x14\x29\xf0\x26\xd5\x6e\x42\x41\x7f\x46\x1d\x9c\x9e\x94\ +\x80\x42\xbe\x1f\xcd\xea\x9e\x5d\x69\xff\xf7\x58\xc5\xc8\xb8\x47\ +\x72\xda\x8e\xc2\xd7\x64\x36\xcb\x74\xc4\xea\xa3\xb9\xb1\x91\xfa\ +\x46\x9d\xf9\x09\x8d\x12\xc7\xe6\x43\x81\x06\x19\x7b\xfc\x50\x3d\ +\x89\x71\x5e\xeb\xef\x2c\xc5\x74\x4e\xa1\x1b\x8d\xad\x73\x51\x9e\ +\xb2\x21\xe0\xa8\x23\x90\x87\xf2\x8c\x36\x85\x6c\x1c\x1b\x32\x1c\ +\x0e\x71\xc4\x0e\x61\x41\x11\x37\x3e\x0a\x38\xc4\x83\x20\x0f\xda\ +\x27\x68\x52\x3b\x7a\x5f\x00\x83\x90\xb0\x8d\x15\x50\x40\xda\xac\ +\x63\xac\x83\x44\x81\x3a\xee\xeb\x2f\x7c\xb3\x3a\xb1\xed\x9c\x03\ +\x83\x0e\x2e\x1b\xb0\xa3\xae\xf8\x66\xcc\x92\xa2\x19\x11\x11\xaa\ +\xaa\xe2\xc1\x7b\x49\x3f\x04\xf0\x4d\xec\xbf\x9b\x54\x77\x86\xba\ +\xd8\x0f\xdc\x50\x2f\xd3\x21\x00\xbf\x07\xf0\x73\xf1\xdf\x85\x4f\ +\x19\xbe\x41\xac\xd0\xa7\x4a\x42\xef\x06\x9a\xf7\x47\x04\xfa\x0e\ +\xdb\xf1\x42\x02\x24\xa6\x9c\x80\x3f\xb8\x00\x5b\xfa\x06\xed\xe8\ +\x4f\xd8\xd1\x97\x68\x87\xed\xcc\x19\x6d\x82\xff\x71\x7c\xf2\x08\ +\xb2\x90\xc8\xe5\x48\xe1\x45\x0e\x5f\x83\x30\xf9\xc7\xf2\x26\x91\ +\x57\xaa\x7f\x19\x13\x41\x75\x1a\xa2\xce\xe9\x8b\xe7\xc0\x0a\xb4\ +\x5e\x03\x70\x14\xc0\xde\xa7\x61\x94\xa7\xc9\x3e\xe3\x0a\x13\x1e\ +\x7a\xf4\x25\xf4\x82\xbb\x42\x7c\xe8\xe7\xd8\xa8\x75\x29\x8f\x33\ +\x35\x89\xe6\xe7\xfa\xad\x1c\xce\x67\xea\xcb\xe7\x66\x94\x39\x57\ +\x44\xbc\x7a\x34\x3b\x5b\x44\xbc\x0c\xee\xd0\xe1\x66\x70\x07\x33\ +\x1d\xc1\x1e\x46\xdc\x16\xa1\x6f\x48\x06\x77\xa0\x06\xdb\x6f\x33\ +\x8d\x2f\x9a\x9d\xad\x6e\xd8\x82\x38\xc4\x04\x88\x70\x95\xc3\xb7\ +\xec\x09\x0c\xa1\x01\x74\x49\xeb\xb3\x2a\xcb\xae\xb3\xa7\x3c\xa1\ +\x88\x7c\x80\x3b\xf2\x9d\xaf\xbe\xa5\x44\xae\x4b\xcd\xcb\xd6\xb6\ +\x62\xae\x91\x6f\x3f\x20\x2e\x52\x1f\x22\x97\x3f\x55\xf4\xfb\x7d\ +\xcc\xcf\xcf\xe3\xe8\x63\x8f\x41\x6b\x7d\x8a\x99\xbf\xd6\xef\xf7\ +\x3b\xcb\xcb\xcb\x7c\xec\xd8\x31\xbd\xb5\xb5\x95\x6f\x6e\x6e\xae\ +\x9c\x39\x73\x66\x7b\x7d\x7d\xbd\xfc\xe0\x83\x0f\xd4\x9d\x3b\x77\ +\xa8\x28\x8a\xd5\xa2\x28\x36\xb7\xb7\xb7\xb1\xdd\xe9\x20\xcb\x32\ +\x71\xc1\x85\x59\xf0\x3b\xc6\xd5\x16\x5d\x91\xf0\xea\x51\xf0\x6c\ +\x91\xef\x86\x81\x1c\xfa\xc6\xba\x7a\x34\xbc\xca\xb2\x5c\xcf\xdf\ +\x15\x00\xc1\x25\x0e\x6c\x8f\xe6\xd9\x06\x2b\x7d\x03\x97\xbe\x80\ +\x0a\xe6\xf6\xca\x28\x9f\x2b\xc4\x7a\x05\x77\x90\x87\xfa\xf9\xd6\ +\xed\xed\x0a\xe9\xee\x0a\xd8\x60\x2b\x9b\xc2\x83\x41\xb9\x6c\xa2\ +\xd5\x35\xdb\xa7\x30\x9d\x77\x93\x68\xc2\xfb\xce\x4a\x68\x70\x5f\ +\xf9\x87\xd7\x12\x39\xf3\x21\xd6\xfa\x69\xd2\xfa\xeb\x20\xba\x89\ +\xfb\x1f\xef\x9c\x99\xd1\x98\xd4\xfd\x46\xbd\x08\x4d\xc5\x12\x22\ +\x44\x13\x3c\xc2\x28\xe4\x88\x85\x04\x88\xcb\x99\x8a\x15\x8c\x29\ +\xc7\x88\x15\x53\x4d\xf6\xb3\x45\x9b\x73\x85\x12\x87\x21\x96\x4c\ +\x01\xe4\x5b\x26\xc4\x45\xbf\xb3\x95\x01\x88\x8f\x60\xd7\xe4\xb7\ +\xeb\x1a\xa6\x7e\x0b\xc9\x55\x07\x52\x1d\x78\x76\x9c\x1f\x00\x4d\ +\x5a\x3f\x4b\x59\xf6\x0a\x88\x00\x66\xef\xe3\x92\xca\x53\x37\x63\ +\xbe\x2d\x16\xca\x33\x35\x82\x9c\xab\xde\xaa\x48\x81\xdf\x66\x9a\ +\x50\x7b\x8e\x99\x85\x6e\x63\x20\x24\x26\x02\x5e\x72\x9f\x41\x44\ +\x79\xbf\x2c\xab\x95\x95\x95\xee\x33\xcf\x3c\x83\xb2\x2c\x7f\x0b\ +\xe0\x3f\x44\xd4\x5d\x5e\x5e\xa6\x95\x95\x95\x1b\xef\xbd\xf7\xde\ +\xa9\xcd\xcd\xcd\x97\xd6\xd7\xd7\x7f\x71\xfa\xf4\xe9\xcb\xc7\x8f\ +\x1f\x3f\xba\xbd\xbd\x8d\xaa\xaa\xe6\x57\x57\x57\x5f\xfb\xfb\xe5\ +\xcb\xd8\xda\xda\xea\x2e\x66\xd9\xc6\xa0\xbd\xcb\x77\x96\x84\x59\ +\xf3\x53\x62\xde\xbf\xf1\x85\xc8\xb6\x85\xa6\xb6\x89\x27\xc6\x83\ +\xdf\x4b\xd2\x0e\x11\xe4\x12\x5c\x36\xd1\xe1\x7a\x0c\x2f\x34\x53\ +\x13\xb2\x93\xf9\x28\x9d\x29\x0e\xeb\xff\x01\x7f\xc4\x38\x5f\x59\ +\x6d\xdf\x51\xf2\xa5\x0f\x7d\xd0\x17\x35\x1b\xb9\xca\x65\x13\x4e\ +\xa1\x6f\x29\xb9\xc2\xb2\xfb\x04\x6a\xec\x4c\x58\xac\xff\x4c\x11\ +\x22\x78\xdc\x42\x6b\xda\x62\xc9\x77\xec\x7b\xa2\x38\x1f\x51\x98\ +\x8c\x43\xec\x8c\x78\xc8\x0c\x40\x4e\x33\xfc\xbd\x28\x9a\xf0\x7e\ +\x78\xc8\x8e\x37\x89\x8f\x10\xb7\x19\x01\x8c\x26\x6c\xfb\x29\xd4\ +\x2f\x06\x50\xd0\x0c\xd4\xb9\x36\xf6\x9f\xe5\x20\x32\x34\xc1\x36\ +\xd8\xfa\xcd\x8b\x07\x1f\xe1\x52\x4a\x6d\x65\x59\x06\x66\xfe\x2b\ +\x80\xbf\x2d\x2d\x2d\xf1\x8d\x1b\x37\xf8\xd2\xa5\x4b\x7b\x44\xf4\ +\x6c\x96\x65\x2f\xbd\xf5\xd6\x5b\xaf\x5d\xbe\x7c\xf9\x0f\x67\xcf\ +\x9e\x5d\x5c\x5f\x5f\xc7\xce\xce\x0e\x65\x59\xd6\x23\xa5\x00\xe0\ +\x06\xf6\x1f\xdf\x13\x84\x87\x4d\x68\xc5\x88\xb0\x90\xe3\x1c\x0a\ +\xff\xdc\xe6\xc7\x4d\x53\xc4\x52\xdb\x3e\x24\x27\xec\xc3\x2d\xe4\ +\x8d\x96\xf2\x68\x3b\xda\x9e\x30\x01\xf2\x03\xd8\x09\x49\xd9\x04\ +\x41\x10\xa6\x4f\x09\xa0\xcc\xb2\x0c\xdd\x6e\x17\xb7\x6f\xdf\xfe\ +\x0a\x11\x7d\x3f\xcb\x32\x74\x3a\x9d\x1f\x00\xe8\xef\xee\xee\xbe\ +\x6a\x79\xe4\xae\x1a\x08\x26\x41\x10\x04\x41\x18\x3b\x4a\x4c\x20\ +\x08\x82\x20\x4c\x83\x3c\xcf\xd1\xe9\x74\xb0\xb1\xb1\x81\x85\x85\ +\x85\xcf\x14\x45\xf1\xed\xb2\x2c\x31\x37\x37\x77\x7e\x71\x71\xf1\ +\xcc\xc6\xc6\x06\x3a\xf2\x8e\x92\x20\x08\x82\x20\x62\x49\x10\x04\ +\x41\x38\xa8\x94\x65\x89\xb5\xb5\xb5\x3f\xaf\xac\xac\x5c\x01\x80\ +\x23\x47\x8e\x7c\xf8\xe8\xa3\x8f\xfe\x69\x10\x35\x4f\x10\x04\x41\ +\x10\x44\x2c\x09\x82\x20\x08\x07\x8f\x5b\xb7\x6e\xe1\xc9\x27\x9f\ +\xc4\xfa\xfa\xfa\xbf\xca\xb2\xfc\x35\x00\xcc\xcd\xcd\xfd\xe6\xe4\ +\xc9\x93\xff\x38\x77\xee\x1c\xb6\xb7\xb7\xc5\x48\x82\x20\x08\x82\ +\x88\x25\x41\x10\x04\xe1\x60\xa3\xb5\xbe\x34\x10\x4b\xaf\x88\x35\ +\x04\x41\x10\x84\x59\x20\x17\x13\x08\x82\x20\x08\xd3\xa6\x2c\x4b\ +\x2c\x2c\x2c\x5c\x3d\x72\xe4\xc8\x4f\xe6\xe7\xe7\xff\x59\x55\x95\ +\x18\x45\x10\x04\x41\x10\xb1\x24\x08\x82\x20\x1c\x4c\xfa\xfd\x3e\ +\x56\x57\x57\x71\xf8\xf0\x61\x00\xc0\xb1\x63\xc7\xee\x68\xad\x5f\ +\x65\xe6\x7b\xcf\xde\x31\x33\x7a\xbd\x1e\x1e\x79\xe4\x11\x31\x98\ +\x20\x08\x82\x30\x71\x68\xf8\xed\x0b\x41\x10\x04\x41\x10\x04\x41\ +\x10\x04\xe1\x13\xfe\x0f\xb6\x45\x73\xbb\x5f\x84\x1a\x5c\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x15\x74\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x04\xb4\x00\x00\x00\x14\x08\x06\x00\x00\x00\x94\x08\x64\x5e\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x08\x65\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\ +\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\ +\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\ +\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\ +\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\ +\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x65\ +\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\ +\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\x66\x2f\x31\x2e\ +\x30\x2f\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\ +\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\x31\x37\x20\x28\x4d\x61\ +\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\x78\x6d\x70\x3a\x43\x72\ +\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x36\x2d\x31\ +\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\x35\ +\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\ +\x61\x74\x65\x3d\x22\x32\x30\x31\x39\x2d\x30\x31\x2d\x32\x39\x54\ +\x31\x31\x3a\x35\x37\x3a\x35\x39\x2d\x30\x35\x3a\x30\x30\x22\x20\ +\x78\x6d\x70\x3a\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\ +\x3d\x22\x32\x30\x31\x39\x2d\x30\x31\x2d\x32\x39\x54\x31\x31\x3a\ +\x35\x37\x3a\x35\x39\x2d\x30\x35\x3a\x30\x30\x22\x20\x64\x63\x3a\ +\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\ +\x67\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\ +\x6f\x72\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\ +\x22\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\ +\x2e\x31\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\ +\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x31\ +\x30\x39\x33\x66\x62\x34\x2d\x30\x62\x34\x64\x2d\x34\x35\x39\x35\ +\x2d\x61\x38\x35\x64\x2d\x39\x66\x62\x63\x65\x30\x34\x61\x63\x32\ +\x61\x62\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\ +\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\x64\x69\x64\x3a\x62\x30\ +\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\ +\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\ +\x33\x62\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\ +\x61\x6c\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\ +\x70\x2e\x64\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\ +\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\ +\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x74\x69\x66\x66\ +\x3a\x4f\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x22\x31\x22\ +\x20\x74\x69\x66\x66\x3a\x58\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\ +\x6e\x3d\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\ +\x20\x74\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\ +\x6e\x3d\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\ +\x20\x74\x69\x66\x66\x3a\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\ +\x55\x6e\x69\x74\x3d\x22\x32\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\ +\x6c\x6f\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\ +\x20\x65\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\ +\x6e\x73\x69\x6f\x6e\x3d\x22\x31\x32\x30\x34\x22\x20\x65\x78\x69\ +\x66\x3a\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\ +\x6e\x3d\x22\x32\x30\x22\x3e\x20\x3c\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\ +\x74\x6f\x72\x73\x3e\x20\x3c\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\ +\x3c\x72\x64\x66\x3a\x6c\x69\x3e\x78\x6d\x70\x2e\x64\x69\x64\x3a\ +\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\ +\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\ +\x39\x32\x33\x62\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x2f\ +\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\x3c\x2f\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\ +\x65\x73\x74\x6f\x72\x73\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\ +\x69\x73\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\ +\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\ +\x61\x63\x74\x69\x6f\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x30\x33\x31\x36\ +\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\ +\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\ +\x36\x2d\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x63\x36\x66\x63\ +\x62\x66\x38\x2d\x34\x34\x34\x35\x2d\x34\x66\x33\x62\x2d\x61\x34\ +\x64\x36\x2d\x33\x30\x36\x62\x66\x31\x30\x31\x62\x61\x30\x39\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\ +\x36\x2d\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x33\x38\x3a\x31\x32\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\ +\x45\x76\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\ +\x64\x22\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\ +\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x31\x30\ +\x39\x33\x66\x62\x34\x2d\x30\x62\x34\x64\x2d\x34\x35\x39\x35\x2d\ +\x61\x38\x35\x64\x2d\x39\x66\x62\x63\x65\x30\x34\x61\x63\x32\x61\ +\x62\x22\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\ +\x30\x31\x39\x2d\x30\x31\x2d\x32\x39\x54\x31\x31\x3a\x35\x37\x3a\ +\x35\x39\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\ +\x73\x6f\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\ +\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\ +\x43\x20\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\ +\x68\x29\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\ +\x64\x3d\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\ +\x71\x3e\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\ +\x72\x79\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\ +\x70\x74\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\ +\x3e\x20\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\ +\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\ +\x3f\x3e\x96\xdc\x99\xad\x00\x00\x0c\xb5\x49\x44\x41\x54\x78\x9c\ +\xed\x9c\x4f\x68\x54\xdf\x15\xc7\xcf\x7b\x49\x26\x1d\x31\x8e\xa2\ +\xd1\x8a\xb8\x28\x91\x28\xb5\x22\xed\x42\x2c\xa4\x2e\x5c\x54\x8a\ +\x88\x5a\xb4\xbb\xee\x4b\xbb\xe9\x8f\xdf\xca\xd2\x65\xa1\x14\xda\ +\x52\x0b\xed\xa2\x8b\x82\x2d\xfc\xd0\x76\x57\x70\xa1\x50\xff\x74\ +\x53\x29\x08\xc5\x95\x01\xab\xb8\x49\x30\xe3\x98\x89\xd1\x4c\x26\ +\xbe\x3f\x5d\xe8\xb3\xd7\x93\x73\xce\xbd\xef\xcd\x24\x93\x29\xdf\ +\x0f\x0c\xf3\xe6\xde\x73\xcf\x39\xf7\xff\xb9\x37\x93\x89\xf2\x3c\ +\x27\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x86\x85\xd1\x41\ +\x3b\x00\x00\x00\x00\x00\x30\x8c\x44\x51\xf4\xf1\xf9\xc2\x85\x0b\ +\x74\xf8\xf0\x61\x5a\x5e\x5e\xa6\x3c\xcf\x69\xfb\xf6\xed\xf4\xf8\ +\xf1\xe3\x43\x69\x9a\x6e\x3b\x76\xec\xd8\xa3\x57\xaf\x5e\x51\x1c\ +\xc7\x54\xaf\xd7\x69\x7e\x7e\x9e\x6e\xdc\xb8\xf1\xb1\x2c\xfe\xb8\ +\x08\x00\x00\x00\x00\x50\x1e\x5c\x68\x01\x00\x00\x00\x00\xf4\xc8\ +\x93\x27\x4f\xe8\xed\xdb\xb7\xd4\xe9\x74\x88\x88\x68\x72\x72\x92\ +\x1e\x3c\x78\xf0\xdb\x2c\xcb\xbe\x7c\xfc\xf8\xf1\x6f\x8c\x8d\x8d\ +\x51\x9a\xa6\x34\x3a\x3a\x4a\x2b\x2b\x2b\x03\xf6\x16\x00\x00\x00\ +\x00\x60\xf8\x89\x07\xed\x00\x00\x00\x00\x00\xc0\xb0\x33\x37\x37\ +\x47\x59\x96\x51\x92\x24\x54\xaf\xd7\xe9\xee\xdd\xbb\xdf\x6b\x36\ +\x9b\xdf\x69\xb5\x5a\x5f\xbf\x79\xf3\xe6\x67\xfb\xf6\xed\xa3\x7a\ +\xbd\x4e\x8d\x46\x83\x5a\xad\xd6\xa0\xdd\x05\x00\x00\x00\x00\x18\ +\x7a\xf0\x0d\x2d\x00\x00\x00\x00\x80\x1e\xc9\xb2\x8c\x56\x57\x57\ +\x29\xcb\x32\x5a\x58\x58\x38\xd1\x6e\xb7\x3f\xfe\x4f\xe1\xa3\x47\ +\x8f\x7e\x3d\x3e\x3e\xfe\x9f\x03\x07\x0e\xfc\xed\xf9\xf3\xe7\xf4\ +\xec\xd9\xb3\x41\xba\x0a\x00\x00\x00\x00\xf0\x7f\x01\xbe\xa1\x05\ +\x00\x00\x00\x00\xd0\x23\xed\x76\x9b\x8e\x1e\x3d\x4a\x33\x33\x33\ +\x3b\x17\x17\x17\xff\x78\xe8\xd0\xa1\xc5\x38\x8e\x53\x22\xa2\xa9\ +\xa9\xa9\x97\xf3\xf3\xf3\xbf\xdb\xbb\x77\xef\xfe\x83\x07\x0f\x52\ +\xb3\xd9\x1c\xb4\xbb\x00\x00\x00\x00\x00\x43\xcf\x68\x6d\xec\xa7\ +\xee\xe7\x48\x13\xdc\x20\x36\xca\x5e\x88\x5e\x4b\x46\xcb\x2b\x9b\ +\x1e\xe2\x4b\xaf\xbe\x6a\xf9\x3c\x2d\xf2\x3c\x5b\xef\x5c\x5e\xca\ +\x2b\xa3\xd3\x27\xa7\xe5\x5b\x32\xd2\xe7\x88\x88\x72\x41\x26\x44\ +\x8e\xd7\xbb\xaa\x7d\x9f\x5d\xeb\x99\xb7\xb3\xf4\xb9\x48\x8b\x59\ +\x5a\xcc\x9e\x63\xe7\x99\x9c\xb4\x98\xc9\xf3\x77\xa9\xbc\x66\x83\ +\xeb\x77\xed\x90\x50\x86\xfb\x4f\x4c\xd6\x57\x7f\x72\xca\xf0\xbc\ +\x9c\x3e\xbd\xb0\x97\xc6\xb1\x66\x87\xdb\xcc\x95\xf2\xbe\x71\x1e\ +\xd3\xa7\xe3\x4a\xf2\x9b\xeb\x0e\x9d\x5b\xdc\x47\xab\x9e\x7c\x1c\ +\x6a\x76\xd7\xd5\x35\xcb\x4e\x45\xb5\xda\xf9\xaa\x7e\x59\xe9\x5c\ +\xa7\xf4\xae\xd9\xd5\xf4\x6a\x73\x3a\xd4\x17\x6b\x4d\x90\xda\xcc\ +\x2a\x6b\xd5\x43\x4b\xd3\xd6\x12\xa9\x6f\xac\x71\x15\x6a\xd7\xb2\ +\xa9\xe5\x69\xfa\xa4\xf5\xb3\x8a\x3d\x9e\x2f\xc9\x5a\xba\xa5\xf5\ +\x9e\x97\xe3\x6d\xa7\xf9\xc6\x75\x7c\xcc\x5f\xe9\x74\x54\xd9\xfb\ +\xf7\xef\xd3\xe9\xd3\xa7\xdb\xe7\xcf\x9f\xff\x5a\xb7\xdb\xa5\xb9\ +\xb9\xb9\xd9\x6e\xb7\x3b\x3d\x33\x33\x33\xd9\x68\x34\x28\x4d\x53\ +\xba\x77\xef\x1e\x25\x49\xa2\x1a\xdb\x56\xaf\xe3\x17\xe2\x01\xd8\ +\x7c\xca\xcc\x3b\x49\x96\xa7\xe5\xc2\xb3\xf5\xce\x5f\x45\x7a\xe6\ +\xa4\x65\xc6\xbb\xfb\x9c\xb2\xe7\xcc\x78\x4e\x3d\xcf\xa9\x20\x6f\ +\xd9\x70\xfd\x2a\x64\xdc\x97\x5b\x1f\xad\x7d\x2c\x0a\x99\x88\x7d\ +\x2e\xd6\x76\x49\x97\xfb\x9c\x31\xf9\xcc\x79\x2f\xd2\xb9\x8e\x42\ +\x26\x15\x64\xdd\xfa\x84\xf4\x93\x54\xc6\x6d\x4b\xd7\xc7\x54\xd1\ +\x2f\xb5\xa3\x5b\x96\xd7\x81\x8f\xab\x94\xb5\x0d\xb7\xc1\xf5\x68\ +\xbe\x6b\x32\x6e\x5d\x34\xfd\xda\x78\x97\xde\xf9\xb3\x95\x66\xa5\ +\x97\xc1\x1d\x4b\xee\x73\xf1\xd9\x47\x59\x1f\xa4\x18\x05\x78\x58\ +\xf9\xf0\xa3\xa5\xa3\xa3\xa3\xbf\x2a\xd2\x06\xd5\x78\x83\xe8\xb8\ +\x7e\xdb\x0b\xb9\x34\x29\x5b\xb6\x28\x5f\x75\x52\x96\x6d\x57\x4b\ +\xbe\x1f\x7d\x64\x95\x77\x37\xa1\xc8\x93\x2e\xc9\x10\xad\xdf\xd8\ +\xb8\x7e\x9f\x0f\x3c\x8f\xeb\xb1\xec\x6b\x07\x52\xe9\x90\xa7\xd5\ +\x85\x2f\x9a\x52\x1d\x43\xfd\xb0\xfc\xe6\x36\xf9\x66\x21\xe9\xb1\ +\x36\x0c\x7e\xc8\xb5\xda\x43\x2a\xcb\xed\x65\xf4\xbf\x8b\xa8\xd0\ +\x3a\x16\xfe\xf1\x0b\x2c\xee\x9f\x9b\x27\xf9\xc2\xf3\xa5\xb1\xe5\ +\xda\x28\x33\x77\x25\x1f\x34\xbb\x5c\xbf\x64\xc7\xad\x97\xaf\x9d\ +\x78\x9f\xf8\xda\xf1\x23\x49\x12\x59\x7e\x49\x69\x9a\x4f\x3c\x3d\ +\x64\x6c\x68\x17\x11\xbc\x2e\xbc\x5c\xc8\x7c\x97\x7c\xe4\xe5\xb8\ +\x9f\xd2\xb8\xf0\x5d\x9e\x48\x36\x8b\xcb\xb1\x10\x9f\xb4\xb5\xa2\ +\xb0\xad\xad\x77\xd6\xbc\xd7\x08\x59\x1f\xad\x35\xd9\x57\xb7\x32\ +\x01\xa0\xd6\x8e\xd2\x7a\xea\x3e\x87\xce\xf5\xd0\xbd\xce\x1a\x1f\ +\x05\xa7\x88\xe8\x70\xf1\x61\x76\x76\x96\x66\x67\x67\x33\x22\x5a\ +\x23\x22\xaa\xd5\x6a\x7b\xa2\x28\xa2\x6b\xd7\xae\x7d\xff\x83\xc8\ +\x18\x11\x8d\x30\x1d\xb3\x44\xf4\x0f\xc5\x27\x00\xc0\xd6\xa2\x1f\ +\x87\xe5\x10\x9d\x52\x8c\x16\xa2\x47\xba\x0c\x20\x96\x6e\x5d\x30\ +\xf8\x9e\x7d\xba\xdc\x34\x52\xd2\xa5\x7a\x15\x97\x46\x52\xec\x65\ +\xc5\xf8\x52\x1d\xa5\x74\x57\xb7\xf6\x5e\xf8\x60\xe5\xf1\x76\xe3\ +\x76\xa4\x97\xe4\xa3\x95\xef\xa6\x47\x24\x5f\x52\xf1\xfa\x48\xfd\ +\xae\xa5\xb9\xf5\x90\xfa\xca\x2a\xcf\x91\x62\x31\x29\xbd\x17\xca\ +\xea\x71\x63\x82\xaa\x3e\x58\x73\x8f\x7f\x96\xe2\x11\x2d\x3f\xd4\ +\x5e\x68\xec\x26\x95\x29\x6c\x6a\x36\x34\xdd\x65\xec\x6d\x14\xda\ +\xf9\xc5\xac\xdf\xa8\x94\xb8\xc9\x0c\xc2\xf6\x66\x75\x58\xa8\x1d\ +\x4d\x6e\x23\x36\x4d\xcd\x9e\x35\xa8\x43\x0e\x9f\x9a\x1e\x57\xce\ +\xcd\x0f\x3d\x50\x84\x20\xc9\x97\xa9\x9f\x34\x39\xac\x8b\x0a\x0d\ +\xdf\xe2\xd0\xeb\xb8\xd3\x0e\x8e\x92\x4d\xfe\xec\x3b\xfc\xf1\x8b\ +\x24\x7e\xf0\xcb\xe8\xfd\x21\xcc\xd5\x2d\x6d\xfa\x7c\x21\xe2\xdf\ +\x6c\xd1\xc6\x8b\x54\xd6\xea\x57\xee\x1f\xdf\x84\x43\xda\x59\x1a\ +\x8b\xd6\xb8\xb7\xfc\x0f\xb9\x0c\x28\xf4\x97\xb9\x14\xf3\xc1\xfd\ +\x95\x36\x41\x6d\x6d\xe1\x63\x89\xb7\xa7\x35\xc6\xc8\x93\xe7\xbb\ +\x48\x08\x99\x0b\xda\x86\x5e\x75\x1e\x85\xac\x55\x9a\xdf\x52\x59\ +\xc9\x27\x9f\xcf\x3c\x00\xf5\x7d\x8b\x4f\xf2\x45\x0a\xec\xdd\xbe\ +\x90\x2e\x7d\x5c\x3b\xda\x3a\x60\xe9\x08\x81\xd7\xcd\x17\x84\x68\ +\x01\x95\x14\x88\x4a\xe3\xa6\x97\xf5\x54\x5b\xab\x7d\x7a\xa5\xf4\ +\x5f\x10\xd1\x49\xcd\xd0\xda\xda\x5a\xf1\xf8\x27\xc3\x9f\x07\x44\ +\xf4\x4d\x23\x1f\x00\xb0\xb5\xb1\xe2\x75\x2d\xaf\x4c\x8c\x6f\x5d\ +\x32\xf0\xf8\x87\x97\xd3\x2e\x3b\x78\x7e\xc6\x64\xf2\xc0\x97\x24\ +\xab\x95\xb7\xfc\xb6\x62\xd9\x90\xcf\x1c\x69\x1f\x96\x62\x62\xbe\ +\xf6\xbb\xfe\x86\xc6\x61\xae\x5e\xab\x1d\x25\xdf\xf9\x37\xc3\x34\ +\xb9\x5c\x90\xe7\xf6\x43\xfc\xe4\x76\xdc\xb6\x90\x3e\xf3\xfd\x98\ +\x98\x6c\x26\xa4\xf1\xb6\x95\xf6\xf5\x32\xfd\x29\xd9\xf7\xd5\x59\ +\x8a\xc9\xca\xcc\xb9\x32\x68\x7d\x66\xa5\x85\xe8\xb4\xe2\x77\x49\ +\x2f\x1f\xc3\x56\x3c\x15\x12\x03\x6f\x26\x92\xdf\x05\x3c\x4e\x26\ +\x96\x3f\xf0\x1f\x85\xe7\x8d\xdb\x4f\xbd\x21\x87\xa5\x7e\xd9\xee\ +\xe5\x40\xba\x55\xd1\x0e\x63\x9a\x1c\x9f\x38\xfd\xb0\x6b\xa5\xb9\ +\xf0\x81\xce\x27\xf8\x46\x4c\x5a\xa9\x4d\xca\x5e\x40\xf0\xcf\xda\ +\x61\xda\xd5\x6f\xb5\x33\xdf\x50\x63\xfa\x74\x81\x08\xf5\xd1\xd5\ +\x67\xf5\xbd\xe4\x23\xb7\x91\x93\xfe\x8d\x2b\x9e\xe6\xca\x86\x10\ +\x5a\x17\xab\xcd\x35\xbf\xaa\xda\xe7\xf3\x41\xab\x9b\xd4\xae\x52\ +\x99\x42\xa7\xd5\x76\x11\x7b\xb6\x36\x2b\x12\xf2\x79\x1f\x70\x9d\ +\x6e\x39\x29\x2d\xa4\xed\x78\x9d\x62\x96\x6e\xf9\x67\xcd\x0f\xf7\ +\x59\x92\x93\x9e\xcb\xf6\xb5\x34\xe7\xa4\x76\xb0\x74\x6b\xf5\xb4\ +\x02\x1f\xe9\x32\x38\x44\x2f\xf7\xcf\x6a\x17\xad\xad\xac\x39\x51\ +\xa6\x3d\x2c\x5f\x79\xdd\xb4\xb5\x84\xaf\x5d\x21\xbe\x85\xe6\x85\ +\x04\x78\xdc\x17\x2e\x73\x8e\x88\x26\x8c\x72\x21\x2c\xf7\x58\x1e\ +\x00\x50\x9d\x8d\x3a\xe4\x16\x58\xeb\xb7\xef\x32\xc2\x3a\xc4\x87\ +\x1c\x9c\xa5\x4b\x0f\xeb\xb2\xc1\xfd\xb7\x32\xae\x4f\xba\x48\x71\ +\xff\xcd\xd0\xb5\xe9\xbb\xb4\xe0\x32\x91\xf0\x99\x97\xe3\x97\x2e\ +\x52\x59\x37\x4d\x6b\x77\x57\x1f\x8f\x4d\xb8\x1d\xe9\xd9\xca\x97\ +\xda\xcd\xd5\x2b\x95\x75\xdb\x9d\x97\x21\x5a\xff\x0d\x2a\x57\x5f\ +\x61\xcf\x1d\x4b\x9a\xac\xd6\x56\x2e\x85\x2f\x1c\xde\xf6\x5a\xbb\ +\x6a\x63\x5a\xfa\x6c\xe9\x09\xc1\x57\x5e\x1b\x83\xc3\x84\x34\xe6\ +\xdd\x74\x6d\x9e\xf0\xd8\xa9\xca\x19\x70\xb3\xf1\xcd\xbb\xe2\x59\ +\x8c\xd7\xb4\x0b\xad\xaa\x07\xbc\x32\xf4\x3a\x90\xfb\xa1\xb7\x5f\ +\x75\xd4\xda\x2b\xb4\x1d\xcb\x1c\x82\x42\xf1\x05\xe6\xbd\xea\x2f\ +\x28\x7b\xb0\xf3\xb5\x89\x3a\x58\x8d\xb2\xee\x66\x2c\x6d\x88\xfd\ +\x98\xc8\xd2\x21\xce\x92\x93\xec\x95\x19\x9b\xda\x86\x2e\x1d\x3a\ +\xb9\x4d\xc9\xae\xf4\xac\xd9\xe1\x70\x9b\xfc\x37\xab\x34\x7f\xa4\ +\x4b\x12\x4b\xb7\xe4\x1b\xbf\xe0\xb2\xc6\x80\xa4\x43\x2a\x5b\x76\ +\xbe\x95\x19\x93\x92\x0d\x6b\xe3\xe6\x6d\x16\x32\x9f\x34\x39\x49\ +\x47\xe8\x01\x9f\xb7\xb5\xa5\x4b\x4a\xd7\xfc\xf2\x8d\x09\x5f\x7b\ +\x85\xd6\x4b\x4b\x0f\x99\xfb\xbe\xb5\x5b\x9b\x4f\xda\x1c\x2d\x64\ +\x7c\xe3\xcd\xe7\x4f\x68\x3f\x72\x7c\x7e\x69\xb2\xbc\xce\xd6\x5c\ +\xab\xba\xcf\x6a\xfd\xed\xca\x69\x87\x0c\xae\x2b\xa4\x8d\xad\x7c\ +\x6d\x0c\x69\x32\x5a\x7d\x5f\x7e\x78\x01\x00\x86\x93\x7e\x9c\x47\ +\xb8\x8e\x32\xb1\x5e\x19\xbd\x21\x36\xa5\x0b\x05\x69\x1d\x2b\xca\ +\x4b\xff\xce\xc6\xdf\xf9\xb7\x88\xb4\x0b\x18\xfe\x2a\xd2\x53\x41\ +\x46\x2a\xcb\xeb\xe2\xfa\xce\xe5\xdd\xb5\x3d\x22\xf9\x22\x46\x2a\ +\xe7\xe2\x2b\xa3\xf9\x2a\x7d\xf6\xf5\xa5\xd4\x76\x52\x7b\xf0\xb6\ +\x0b\x19\x4b\x5a\xfb\x69\xe9\xbe\x31\xaf\xe5\xf9\xca\xb8\xfa\xf9\ +\x6f\x93\xf1\x67\x69\x8c\xf6\x3a\x0f\xb9\x3e\xc9\xb7\x61\xc1\x17\ +\xef\x86\x96\x29\x13\x3f\x6e\x65\xcc\x79\xa6\x7d\x1b\x62\x33\x2a\ +\x5e\x35\xd8\x0f\xd1\xbb\x11\x94\x3d\x10\x15\x84\x6c\x56\x56\xd9\ +\x7e\x4f\x42\xed\x20\x61\xd9\x08\xb9\x04\x08\xed\xc7\x10\x39\x6d\ +\xa3\xd3\x0e\x16\xd2\x64\x0d\x9d\xfc\x9a\x8c\x16\x0c\x70\xf8\x46\ +\x61\x2d\xa0\xda\x64\xb4\x36\x2e\x6b\x53\x97\xfa\xcd\x0a\x0a\x24\ +\x7f\x7d\x68\xc1\x89\x44\xa4\xc8\x58\xfd\xa6\x05\x50\x6e\x39\xcd\ +\xa6\x74\x10\x96\xfa\x80\x1f\xd2\xad\x43\xb2\x44\xc8\xa6\x28\xf9\ +\xe2\xd3\xe5\x7e\xf6\xf9\xc8\xed\xb8\xb2\x9a\xbd\x90\x80\xb6\x48\ +\x97\xfc\xe2\x17\x50\x9a\x5e\x57\x77\x59\x78\xdf\xb9\x69\x21\x6b\ +\x8b\x35\xef\x25\x1b\x2e\x65\x82\x50\x9f\x1f\xbd\x10\xda\xc6\xa1\ +\x69\xda\xbc\xe1\x63\xcc\xa2\x4c\x7e\xe8\x5a\x6b\xad\xe1\x9a\x7e\ +\x3e\x06\xb4\x20\x4d\x9a\x97\xee\xc5\x5b\xc8\x3e\x6a\xcd\x6d\x6b\ +\x0c\xdc\x63\x36\xaa\xbc\xee\x79\x7c\x03\x00\x0c\x17\xbe\xf8\x25\ +\x54\xce\x8a\x45\xad\xfd\x8e\x97\xd5\xe2\x25\x5e\x8e\xdb\xe3\x17\ +\x3e\xbe\xf8\x55\x8b\x17\xb5\xdf\x57\x2c\x13\x57\xfa\x6c\xf2\xba\ +\x4b\x3f\xf2\xce\xeb\xe9\x6b\x43\x6e\x53\xca\xe7\xb1\x98\xf4\x2d\ +\x2d\x7e\xf9\xe6\xea\xd5\xea\xee\xb6\x9d\x2b\x6b\xc5\x85\x16\x5a\ +\xcc\x68\xf5\x41\xc8\x1f\xb9\x5c\x59\xeb\x2c\x15\xea\xa7\x64\xbf\ +\x6a\x7e\x55\xbb\x55\x6d\xf5\xa2\x9b\xbf\xca\xda\xad\x12\x97\x6d\ +\x35\xdc\xf1\x16\xfa\x22\x22\xa2\xd1\x24\xf9\x9c\xc8\x3f\x48\x37\ +\x8a\x41\xd9\xa5\x0a\x76\xab\xfa\xea\x2b\x13\x72\x58\x0a\x9d\xb0\ +\xbe\x4b\x27\x2e\xe7\xbb\xbd\xd5\xca\x85\x50\xc8\xfb\x2e\x82\xaa\ +\xe8\x2c\x9b\x17\x2a\xaf\xf9\xac\xc9\xf9\xf2\xa4\xfc\xaa\xb8\xed\ +\x25\x1d\xf6\xb4\x85\xac\xea\xc6\xc7\xdb\x42\x1a\x33\xd2\x85\xb8\ +\xb4\x18\x87\x8c\x4d\xad\xcd\xcb\x8c\x4d\x4d\x2e\x67\xef\xbe\xf1\ +\x6f\xf5\x6f\x28\x65\xc6\x85\x56\x17\xad\x5f\x79\x7d\x88\xc2\xea\ +\x2f\xf9\xe1\xea\x5a\xe7\x4b\x96\x7d\x2b\x22\xfa\xa5\xa0\x76\x1d\ +\x21\xfd\xc4\xe5\xb4\x4b\x8b\x10\x3b\x56\x5b\x58\xf6\x2d\x5f\xb5\ +\x72\x52\xdb\x15\xe3\xbf\x9f\xf3\xdd\x67\xd3\x4a\xd3\x6c\x6b\xc1\ +\xa8\x6f\x7e\x86\xd4\x83\xf7\x23\x7f\xb6\xf4\x97\xd1\xcd\xeb\x10\ +\xaa\xaf\x17\xbb\xa1\xe9\x74\xe6\xcc\x99\x3f\x44\x51\xf4\x6f\x22\ +\xa2\x2c\xcb\xa8\x56\xab\xd1\xae\x5d\xbb\xa8\x56\xab\xe5\xdd\x6e\ +\x97\xae\x5f\xbf\xfe\x83\x24\x49\xbe\x74\xf9\xf2\xe5\xdf\xec\xdc\ +\xb9\x93\xba\xdd\x6e\xd4\x6e\xb7\x69\x75\x75\x95\xe2\xf8\xfd\x12\ +\x9a\xe7\xf9\xbf\x4a\xfa\x09\x00\xe8\x2f\x7c\xbf\xd5\xe8\xd7\xe1\ +\xb8\x8c\x2e\x77\xaf\xcc\x84\x74\x4d\x67\x99\x7c\xed\x42\x4a\x2b\ +\xeb\x7e\xd6\x2e\x44\x34\x7d\xd2\x67\xc9\x56\x51\x5f\xa9\x6f\xa4\ +\x58\x86\x68\xfd\x3e\xc1\xf7\xed\xaa\x7d\x6c\xb5\x3d\x97\x77\xed\ +\x6a\x3f\x82\xef\x6b\xe3\xdc\x23\x5b\x26\xb6\x2f\xea\x2f\xfd\xcb\ +\xa8\x55\x5e\xab\x97\x86\xd5\xee\xdc\x17\x9f\xee\x41\xcc\x47\x09\ +\x7e\x86\xf0\xc9\x55\xb5\xd1\x0f\xfa\x75\x3e\x1e\x14\x3d\xf9\x38\ +\xba\xf6\xee\x67\xc5\xf3\x46\x0f\x0a\x8d\x41\xd9\x05\x00\x00\xe0\ +\xa5\x33\x68\x07\x00\xd8\xb2\x5c\xba\x74\xe9\x8b\x38\x8e\xbf\x20\ +\x22\x1a\x1f\x1f\xa7\xb5\xb5\x35\xba\x73\xe7\x0e\xbd\x7c\xf9\x92\ +\x92\x24\xa1\xb1\xb1\xb1\x6f\x47\x51\xf4\xd5\xa5\xa5\xa5\xcf\x92\ +\x24\xa1\x5a\xad\x46\xe7\xce\x9d\xa3\x6d\xdb\xb6\xd1\xea\xea\x2a\ +\x11\x11\xe5\xf9\xa7\x61\xd0\x4a\xa7\x33\x0c\xc1\x27\x00\x00\x00\ +\x00\xc0\xc0\x19\xf4\x8f\xc2\x03\x00\x00\x00\x00\x0c\x25\x2f\x5e\ +\xbc\xa0\x38\x8e\x29\x8e\x63\xaa\xd7\xeb\x74\xf5\xea\x55\x7a\xfa\ +\xf4\x69\x9d\x88\x6e\x10\xd1\xc4\xc8\xc8\xc8\x57\xf2\x3c\xa7\xdb\ +\xb7\x6f\xff\x9d\xde\x7f\xab\xef\xbb\xb7\x6e\xdd\x5a\xbc\x72\xe5\ +\x0a\xc5\x71\x4c\xef\xde\xbd\x5b\x77\xa1\x05\x00\x00\x00\x00\x00\ +\xc2\xc0\x85\x16\x00\x00\x00\x00\x40\x0f\x8c\x8c\x8c\x50\xab\xd5\ +\xa2\xfd\xfb\xf7\xd3\xf4\xf4\x74\xa7\xd9\x6c\xfe\xf9\xe1\xc3\x87\ +\x7f\x49\xd3\xe2\xb7\x90\xe9\xf4\x89\x13\x27\x7e\x7c\xe4\xc8\x91\ +\xc5\x85\x85\x05\x6a\xb7\xdb\xb4\x7b\xf7\xee\x41\xba\x0c\x00\x00\ +\x00\x00\x30\xf4\x68\x3f\x0a\x0f\x00\x00\x00\x00\x00\x02\x18\x1f\ +\x1f\xa7\x56\xab\x45\xaf\x5f\xbf\xa6\xe5\xe5\x65\xda\xb3\x67\xcf\ +\x5f\x27\x26\x26\x7e\x54\xe4\x4f\x4f\x4f\xff\xfc\xec\xd9\xb3\x57\ +\x27\x27\x27\x69\x6a\x6a\x8a\xe2\x38\xc6\x37\xb3\x00\x00\x00\x00\ +\x00\x7a\x04\x17\x5a\x00\x00\x00\x00\x00\x3d\x10\xc7\x31\xbd\x79\ +\xf3\x86\xd2\x34\xa5\x24\x49\x68\x69\x69\x89\x4e\x9d\x3a\xf5\xfb\ +\x46\xa3\xf1\xcf\x1d\x3b\x76\x3c\xbb\x78\xf1\xe2\x4f\x9a\xcd\x26\ +\x2d\x2f\x2f\x53\xa7\xd3\xa1\x95\x95\x95\x41\xbb\x0c\x00\x00\x00\ +\x00\x30\xf4\xe0\x5f\x0e\x01\x00\x00\x00\x00\x7a\xa0\xd3\xe9\x50\ +\xa3\xd1\xa0\x93\x27\x4f\xd2\xd8\xd8\x18\xe5\x79\x4e\x13\x13\x13\ +\x94\x24\xc9\x0f\xd3\x34\xdd\x11\xc7\x31\x75\xbb\x5d\x8a\xe3\x98\ +\xa2\x28\xa2\x34\x4d\xf1\x0d\x2d\x00\x00\x00\x00\x80\x1e\x89\x10\ +\x50\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x61\xe2\xbf\xd7\ +\x83\x06\x69\xb3\xda\xf0\x57\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x0e\x81\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x19\x00\x00\x00\x16\x08\x06\x00\x00\x00\x35\xbf\x37\xb6\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x09\x51\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x4d\x4d\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\ +\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\ +\x6d\x6d\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\ +\x79\x70\x65\x2f\x52\x65\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\ +\x74\x23\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x74\x52\x65\x66\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\ +\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\ +\x70\x65\x2f\x52\x65\x73\x6f\x75\x72\x63\x65\x52\x65\x66\x23\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\ +\x65\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\ +\x6e\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\ +\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x74\x69\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\ +\x6e\x73\x3a\x65\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ +\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\ +\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\ +\x70\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\ +\x62\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\ +\x44\x3d\x22\x61\x64\x6f\x62\x65\x3a\x64\x6f\x63\x69\x64\x3a\x70\ +\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x65\x36\x39\x33\x61\x37\x35\ +\x62\x2d\x36\x32\x66\x32\x2d\x34\x63\x34\x35\x2d\x39\x66\x39\x33\ +\x2d\x62\x31\x63\x62\x66\x61\x62\x62\x31\x61\x66\x62\x22\x20\x78\ +\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x63\x61\x63\x32\x39\x31\x38\ +\x37\x2d\x36\x66\x39\x38\x2d\x34\x36\x61\x65\x2d\x39\x33\x35\x33\ +\x2d\x39\x66\x38\x61\x30\x36\x37\x33\x30\x31\x32\x36\x22\x20\x78\ +\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\x61\x6c\x44\x6f\x63\ +\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x33\x42\x42\x42\x46\x45\x43\ +\x33\x34\x35\x46\x34\x45\x45\x32\x45\x34\x30\x31\x30\x33\x42\x39\ +\x32\x44\x32\x33\x36\x30\x35\x31\x31\x22\x20\x64\x63\x3a\x66\x6f\ +\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\x67\x22\ +\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\x6f\x72\ +\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\x22\x22\ +\x20\x74\x69\x66\x66\x3a\x49\x6d\x61\x67\x65\x57\x69\x64\x74\x68\ +\x3d\x22\x32\x33\x38\x22\x20\x74\x69\x66\x66\x3a\x49\x6d\x61\x67\ +\x65\x4c\x65\x6e\x67\x74\x68\x3d\x22\x32\x31\x32\x22\x20\x74\x69\ +\x66\x66\x3a\x50\x68\x6f\x74\x6f\x6d\x65\x74\x72\x69\x63\x49\x6e\ +\x74\x65\x72\x70\x72\x65\x74\x61\x74\x69\x6f\x6e\x3d\x22\x32\x22\ +\x20\x74\x69\x66\x66\x3a\x53\x61\x6d\x70\x6c\x65\x73\x50\x65\x72\ +\x50\x69\x78\x65\x6c\x3d\x22\x33\x22\x20\x74\x69\x66\x66\x3a\x58\ +\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\x22\x31\x2f\x31\x22\ +\x20\x74\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\ +\x6e\x3d\x22\x31\x2f\x31\x22\x20\x74\x69\x66\x66\x3a\x52\x65\x73\ +\x6f\x6c\x75\x74\x69\x6f\x6e\x55\x6e\x69\x74\x3d\x22\x31\x22\x20\ +\x65\x78\x69\x66\x3a\x45\x78\x69\x66\x56\x65\x72\x73\x69\x6f\x6e\ +\x3d\x22\x30\x32\x33\x31\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\x6c\ +\x6f\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\x20\ +\x65\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\x6e\ +\x73\x69\x6f\x6e\x3d\x22\x32\x33\x38\x22\x20\x65\x78\x69\x66\x3a\ +\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\x6e\x3d\ +\x22\x32\x31\x32\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x65\ +\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x39\x2d\x31\x32\x2d\x30\x34\ +\x54\x31\x30\x3a\x32\x34\x3a\x30\x35\x2d\x30\x35\x3a\x30\x30\x22\ +\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\x61\x74\x65\x3d\ +\x22\x32\x30\x31\x39\x2d\x31\x32\x2d\x30\x34\x54\x31\x30\x3a\x32\ +\x35\x3a\x34\x37\x2d\x30\x35\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\ +\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\x3d\x22\x32\x30\ +\x31\x39\x2d\x31\x32\x2d\x30\x34\x54\x31\x30\x3a\x32\x35\x3a\x34\ +\x37\x2d\x30\x35\x3a\x30\x30\x22\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\ +\x3a\x48\x69\x73\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\ +\x65\x71\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x37\x63\x62\x66\x39\ +\x66\x39\x64\x2d\x61\x31\x38\x61\x2d\x34\x63\x33\x32\x2d\x61\x38\ +\x33\x33\x2d\x61\x35\x63\x66\x34\x62\x64\x35\x36\x33\x36\x65\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\ +\x39\x2d\x31\x32\x2d\x30\x34\x54\x31\x30\x3a\x32\x35\x3a\x34\x37\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\ +\x45\x76\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x63\x6f\x6e\x76\ +\x65\x72\x74\x65\x64\x22\x20\x73\x74\x45\x76\x74\x3a\x70\x61\x72\ +\x61\x6d\x65\x74\x65\x72\x73\x3d\x22\x66\x72\x6f\x6d\x20\x69\x6d\ +\x61\x67\x65\x2f\x6a\x70\x65\x67\x20\x74\x6f\x20\x69\x6d\x61\x67\ +\x65\x2f\x70\x6e\x67\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\ +\x20\x73\x74\x45\x76\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x64\ +\x65\x72\x69\x76\x65\x64\x22\x20\x73\x74\x45\x76\x74\x3a\x70\x61\ +\x72\x61\x6d\x65\x74\x65\x72\x73\x3d\x22\x63\x6f\x6e\x76\x65\x72\ +\x74\x65\x64\x20\x66\x72\x6f\x6d\x20\x69\x6d\x61\x67\x65\x2f\x6a\ +\x70\x65\x67\x20\x74\x6f\x20\x69\x6d\x61\x67\x65\x2f\x70\x6e\x67\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x63\x61\x63\x32\x39\ +\x31\x38\x37\x2d\x36\x66\x39\x38\x2d\x34\x36\x61\x65\x2d\x39\x33\ +\x35\x33\x2d\x39\x66\x38\x61\x30\x36\x37\x33\x30\x31\x32\x36\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\ +\x39\x2d\x31\x32\x2d\x30\x34\x54\x31\x30\x3a\x32\x35\x3a\x34\x37\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\ +\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\x79\ +\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x44\x65\x72\x69\x76\x65\x64\ +\x46\x72\x6f\x6d\x20\x73\x74\x52\x65\x66\x3a\x69\x6e\x73\x74\x61\ +\x6e\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x37\ +\x63\x62\x66\x39\x66\x39\x64\x2d\x61\x31\x38\x61\x2d\x34\x63\x33\ +\x32\x2d\x61\x38\x33\x33\x2d\x61\x35\x63\x66\x34\x62\x64\x35\x36\ +\x33\x36\x65\x22\x20\x73\x74\x52\x65\x66\x3a\x64\x6f\x63\x75\x6d\ +\x65\x6e\x74\x49\x44\x3d\x22\x33\x42\x42\x42\x46\x45\x43\x33\x34\ +\x35\x46\x34\x45\x45\x32\x45\x34\x30\x31\x30\x33\x42\x39\x32\x44\ +\x32\x33\x36\x30\x35\x31\x31\x22\x20\x73\x74\x52\x65\x66\x3a\x6f\ +\x72\x69\x67\x69\x6e\x61\x6c\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\ +\x44\x3d\x22\x33\x42\x42\x42\x46\x45\x43\x33\x34\x35\x46\x34\x45\ +\x45\x32\x45\x34\x30\x31\x30\x33\x42\x39\x32\x44\x32\x33\x36\x30\ +\x35\x31\x31\x22\x2f\x3e\x20\x3c\x74\x69\x66\x66\x3a\x42\x69\x74\ +\x73\x50\x65\x72\x53\x61\x6d\x70\x6c\x65\x3e\x20\x3c\x72\x64\x66\ +\x3a\x53\x65\x71\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x3e\x38\x3c\ +\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\ +\x3e\x38\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x72\x64\x66\ +\x3a\x6c\x69\x3e\x38\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\ +\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\x3c\x2f\x74\x69\x66\x66\ +\x3a\x42\x69\x74\x73\x50\x65\x72\x53\x61\x6d\x70\x6c\x65\x3e\x20\ +\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\ +\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x20\x3c\x2f\ +\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\x3f\x78\x70\x61\ +\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\x3f\x3e\xc3\x9d\ +\x1e\xb4\x00\x00\x04\xd6\x49\x44\x41\x54\x48\x89\x85\x95\x5b\x6c\ +\x14\x65\x14\xc7\x7f\xdf\x5c\x76\x67\xbb\xdb\xdd\x6d\xa9\x6d\x81\ +\xa2\x50\x28\x15\x0b\x4a\x94\xa8\xd1\x78\x89\x12\xa3\x28\x04\x48\ +\x04\xf5\x05\x89\x2f\x82\x8a\x31\x8a\x41\x43\x50\x30\x78\x89\x84\ +\xc4\x18\x08\xc6\x5b\x41\x7d\xd0\xa0\x21\x21\x31\x62\x88\x86\x44\ +\xa2\x3c\x40\x09\xd4\x22\x50\xc4\xa2\xb6\xdb\xd2\x6e\xbb\xb7\xd9\ +\xdd\xb9\x1d\x1f\x58\x10\xb1\x2d\x33\xf9\x1e\xe6\xcb\x39\xdf\x7f\ +\x7e\xff\x73\xe6\x8c\x2a\xb4\x7f\xc0\xd5\x2e\xa5\x29\x02\xc3\x24\ +\x7d\xe8\xd0\x4a\x75\xe4\xe4\x7b\xfa\xbc\x59\xcf\x86\x97\x2e\xdd\ +\xa5\xca\x65\x24\x08\xae\x9a\x6f\x64\xec\xec\xf8\x11\x22\x48\xd8\ +\x42\x4f\x0f\xde\x1c\x73\xf2\x9f\xa8\x45\x0d\x38\xa7\xd2\x3b\xfd\ +\x8e\x8e\xa3\x7a\x2c\x7e\x4c\x4a\x45\x50\x6a\x7c\x11\xcd\xb4\xae\ +\x82\x01\xca\x8a\x10\x1c\xfe\xeb\x5d\x63\x4e\x84\xea\x55\x55\x64\ +\xb6\x66\x28\x1c\xec\x7a\xc7\xbb\x7d\xda\x43\xba\xe7\x23\x81\x8c\ +\x2f\xd2\xd0\x38\x65\x5c\x0a\xa2\xd5\x14\xff\xe8\x59\x59\xb0\x8a\ +\xf7\x59\x4b\x1a\x01\xa1\x6a\x59\x08\xf7\xd8\xe0\x83\x5e\x49\x96\ +\x79\x8d\xf5\x5f\x61\xdb\xe3\xd2\x18\x25\x73\x1c\x54\xdd\x00\xbf\ +\x60\x15\x7f\xeb\x7d\x33\x32\x7f\x02\x66\x63\x35\xe0\x63\x36\xd9\ +\x44\x1f\x76\x18\xfe\xfa\xef\x77\xb8\xcb\xda\xad\x8b\x16\x88\xef\ +\x8d\x2d\x92\x4f\x9f\x1f\x93\x42\xc5\x62\xf8\xdd\xbd\x1b\x8d\x44\ +\xbe\x31\xba\x60\x12\xc5\x20\x46\xef\x59\x87\xba\xeb\x7c\x12\x8b\ +\x23\x14\x7e\x1c\x9a\x6a\x0e\x95\x36\x59\xb7\xb5\xad\xf7\x33\x99\ +\x31\x69\x0c\x9c\xd2\xe8\x02\x96\x45\x90\xea\x6d\xe1\xf8\x99\xb5\ +\xb5\x2f\xd7\xf3\xfb\x30\xec\xdd\x09\x99\xac\x90\xcf\xc3\x8a\x17\ +\xa1\xed\xa9\x2a\xfa\x5e\x3f\xf1\x6a\x79\x4a\xcd\xa7\x66\x32\x79\ +\x46\x4a\xa3\x9c\x05\x18\xc8\x28\x85\x57\x1a\x22\x06\x1c\x4f\x6d\ +\x8e\xb4\xba\x8a\x59\x1e\x3f\xb4\xdb\x9c\xfb\xd3\x63\xd2\xa4\x80\ +\x9e\x1e\x97\x7d\xbb\x7d\xda\x9e\xd1\x09\xb7\xd8\xca\xdb\x7f\x74\ +\x33\xf7\xb4\x3d\xe6\xdb\x05\x46\x63\x31\x48\xf7\x5f\x89\x81\x66\ +\x55\xe1\x9f\x1e\x78\xc4\xe9\x3e\xfd\x68\xcd\x9a\x7a\x20\x4f\x48\ +\x0f\xc8\x17\x0c\x32\x99\x00\xc7\x71\x89\x5b\x25\xa0\x48\x74\xa5\ +\xc5\xc0\x53\x27\x97\xdb\x31\xf9\x48\xc5\x6a\xf7\x8b\x9d\xff\x9f\ +\x6d\x86\x99\xea\xb9\x02\x42\x83\xb0\x41\xf9\x40\xf7\x1b\xd1\x45\ +\x26\x46\x4b\x11\x10\xea\xeb\x7c\x06\xcf\x6b\x54\x45\x84\x91\x11\ +\x8f\xc9\x13\x8b\x40\x99\xf0\x1c\x07\xeb\x01\x1f\x77\xcf\xef\x6f\ +\x4d\x58\xde\xb4\x5f\x12\x51\xc4\xf3\xae\x10\x31\x12\xff\xd9\xd0\ +\x22\x51\x0a\xc7\x4e\xbc\xe0\x33\x30\x37\xb6\xb2\x01\xc8\x03\x2e\ +\x33\x66\x94\x11\xd1\x49\xa5\x04\x5d\xf7\xb9\xfe\x7a\x07\x28\x01\ +\x36\xc9\xd5\x3a\x7d\x7b\x7a\xe7\x9d\xef\xec\x78\xc6\x9c\x3d\x65\ +\x1b\x6e\x01\x2e\x33\xce\xb0\x07\xba\xff\x55\xd0\x75\x34\x4d\xab\ +\xcb\x1f\xec\x7a\x2d\xbe\x5e\xc7\xa8\x2b\x00\x1e\xe0\xd2\x3c\xdd\ +\xa0\x3a\xae\xe8\x3c\xae\x98\x33\xdb\x67\x5a\xb3\x0b\x94\x81\x12\ +\xe6\xa4\x32\xf1\x27\x3d\x86\xd6\xfd\xb2\x29\xa4\x0a\x5f\x88\x27\ +\x23\xe2\xba\x97\x6c\xd3\xfa\xb3\x69\xfa\xb3\x69\xfa\x33\x43\xa4\ +\x82\x32\x83\xfb\x3a\xde\x30\xa7\xf4\x25\x92\xab\x5d\x20\x87\x60\ +\x03\x79\x34\x6d\x84\xfa\xba\x1c\x9d\x9d\x79\x1a\x1a\xb2\x40\xb6\ +\x42\x59\x00\x72\x24\x5e\xf2\x31\x5a\x86\x6b\xed\x03\xa9\x8d\x46\ +\x72\x26\xba\xd9\x70\x69\x19\x13\x8d\x7a\x90\x00\x3d\x96\xa4\x94\ +\xea\xbb\x35\x73\xf6\xec\xd3\xd5\x5b\xe3\x28\x72\x88\x38\x28\x5c\ +\x50\x21\x40\xa7\x6d\xb6\x86\xe3\x08\x37\xde\xe4\x03\x01\x70\xd1\ +\xb2\x12\x8a\x12\x89\x0d\x06\x03\x4f\x9c\x58\x63\x38\xb7\xec\x32\ +\x9b\xa7\x1f\xf6\x73\x23\xa0\x14\x5a\x38\x5e\x83\x55\x5b\x8f\x95\ +\xac\xa1\xf8\xed\xaf\x5b\xac\xfb\x3d\xaa\x17\xdb\x40\x0e\x85\x8d\ +\x90\x05\x46\x80\x11\x5a\x5b\x47\x80\x2c\xad\xad\x39\x20\x03\x92\ +\x03\xec\x0a\x4d\x96\xe4\xe3\x01\xe1\x3b\x60\x70\xf7\xcf\x5b\x54\ +\x55\x12\x2d\x52\x83\x0a\xc5\xd1\x5f\x9e\x35\x0b\x2d\x1a\x27\xd7\ +\xf1\xeb\x8a\xdc\xd9\xc3\xcf\x4f\xfe\xc6\xc0\x68\x74\x00\x17\x54\ +\x80\x52\x1e\xe0\x03\x0e\x55\x91\x32\xba\x51\x66\xe1\xc2\x12\x96\ +\x55\x02\x75\xa1\xf0\x17\x2c\xf5\x50\x04\x84\xe7\x6a\x64\xb6\xa7\ +\xa7\xba\xbe\x7e\xaa\x9c\xac\xea\x2c\x0d\xa5\x50\x83\xcf\x3d\x4b\ +\xe0\x07\xd6\xd0\xf6\xcf\x4e\x27\x57\xe7\x9a\x1a\xb7\x69\x40\xc0\ +\xc5\xb9\xaa\xd0\x11\x34\x40\x43\xa1\x03\x7a\x45\xd4\x05\x7c\x84\ +\xa0\xd2\x47\xea\x52\x46\xff\xaa\x80\xf4\x8e\x68\x77\x7c\xc9\xbd\ +\x2d\x2a\x08\xd0\xa8\x49\x60\x1f\x38\xb2\xc1\x48\xe4\x9a\xae\x79\ +\xdb\xac\x78\xad\x2e\xdd\xe0\xa3\x70\x51\xb8\xac\x5d\xeb\xa1\x54\ +\x9e\x75\xaf\x78\x95\xae\x0b\x2a\x31\x97\x7f\x7c\x01\x75\x6f\x9b\ +\x58\xc9\xc2\x0c\xe7\xf0\x1f\xaf\xb9\x91\x3a\xe8\x59\xb6\xb4\xa5\ +\x0b\x25\xc3\x3b\x10\x11\x4d\x44\xd4\x28\x0b\x11\xd1\x65\xfa\x74\ +\x4b\x00\x69\x6d\x35\x45\xc4\xa8\xec\x8f\x16\xab\xc9\xf0\xfb\x48\ +\x17\xa6\x9c\x9b\xbf\x60\x1a\xa7\xa9\xfd\xb2\xf7\x31\x44\xc4\x1c\ +\x23\xe9\xe2\xd2\xe4\xe3\xef\x42\x52\x7b\x77\x48\xda\xbf\xb7\x2a\ +\x2f\x34\x56\xfc\x85\xf3\xfa\x16\x23\x67\xc2\x13\x3f\x54\xa7\x08\ +\xff\x14\x5f\x52\xbe\x33\x7c\x37\xb8\x7f\x55\xba\xf5\x8a\x1f\x9d\ +\x54\x9e\x6b\xaf\x05\x5d\x03\x3f\x80\x74\xcf\x05\x97\x46\x9d\xee\ +\x3e\x98\xcd\x90\x6d\x87\xf2\xc9\x96\xbd\xaa\x67\xe6\xcc\x1b\xdc\ +\xe1\xcc\xe7\x86\xd5\x3c\x2d\x28\xe6\xba\xf0\xbd\x0c\x4a\x99\xff\ +\x4b\x14\xd0\x34\x08\x85\xc0\x71\x84\x20\x50\x8c\x3a\x72\x2f\x56\ +\x26\x57\x8a\xe8\x4d\xca\x69\xdc\xb5\x79\xcd\x3f\x9f\xe6\x7d\x0b\ +\x64\x55\xea\x96\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\ +\x00\x00\x19\xf7\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x14\x00\x00\x04\xb4\x08\x06\x00\x00\x00\x60\xda\x28\xf0\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x08\x73\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\ +\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\ +\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\ +\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\ +\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\ +\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x65\ +\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\ +\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\x66\x2f\x31\x2e\ +\x30\x2f\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\ +\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\x31\x37\x20\x28\x4d\x61\ +\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\x78\x6d\x70\x3a\x43\x72\ +\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x36\x2d\x31\ +\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\x35\ +\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\ +\x61\x74\x65\x3d\x22\x32\x30\x31\x39\x2d\x31\x31\x2d\x32\x35\x54\ +\x30\x38\x3a\x31\x35\x3a\x31\x32\x2d\x30\x35\x3a\x30\x30\x22\x20\ +\x78\x6d\x70\x3a\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\ +\x3d\x22\x32\x30\x31\x39\x2d\x31\x31\x2d\x32\x35\x54\x30\x38\x3a\ +\x31\x35\x3a\x31\x32\x2d\x30\x35\x3a\x30\x30\x22\x20\x64\x63\x3a\ +\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\ +\x67\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\ +\x6f\x72\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\ +\x22\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\ +\x2e\x31\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\ +\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x66\x37\ +\x30\x32\x33\x34\x66\x32\x2d\x34\x31\x33\x38\x2d\x34\x30\x65\x31\ +\x2d\x38\x64\x66\x37\x2d\x39\x38\x61\x35\x64\x36\x32\x62\x64\x61\ +\x66\x61\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\ +\x6e\x74\x49\x44\x3d\x22\x61\x64\x6f\x62\x65\x3a\x64\x6f\x63\x69\ +\x64\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x61\x65\x63\x38\ +\x35\x36\x35\x30\x2d\x65\x39\x31\x34\x2d\x37\x39\x34\x64\x2d\x38\ +\x37\x38\x33\x2d\x33\x37\x30\x62\x36\x66\x63\x36\x37\x34\x35\x35\ +\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\x61\x6c\ +\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\ +\x64\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\ +\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\ +\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x74\x69\x66\x66\x3a\x4f\ +\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x22\x31\x22\x20\x74\ +\x69\x66\x66\x3a\x58\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x55\x6e\ +\x69\x74\x3d\x22\x32\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\x6c\x6f\ +\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\x20\x65\ +\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\x6e\x73\ +\x69\x6f\x6e\x3d\x22\x31\x32\x30\x34\x22\x20\x65\x78\x69\x66\x3a\ +\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\x6e\x3d\ +\x22\x32\x30\x22\x3e\x20\x3c\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\ +\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\x74\x6f\ +\x72\x73\x3e\x20\x3c\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\x3c\x72\ +\x64\x66\x3a\x6c\x69\x3e\x78\x6d\x70\x2e\x64\x69\x64\x3a\x62\x30\ +\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\ +\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\ +\x33\x62\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x2f\x72\x64\ +\x66\x3a\x42\x61\x67\x3e\x20\x3c\x2f\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\ +\x74\x6f\x72\x73\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\ +\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\ +\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\x61\x63\ +\x74\x69\x6f\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\ +\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\ +\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x2f\ +\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\ +\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x63\x36\x66\x63\x62\x66\ +\x38\x2d\x34\x34\x34\x35\x2d\x34\x66\x33\x62\x2d\x61\x34\x64\x36\ +\x2d\x33\x30\x36\x62\x66\x31\x30\x31\x62\x61\x30\x39\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x33\x38\x3a\x31\x32\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\ +\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\x22\x2f\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x66\x37\x30\x32\x33\ +\x34\x66\x32\x2d\x34\x31\x33\x38\x2d\x34\x30\x65\x31\x2d\x38\x64\ +\x66\x37\x2d\x39\x38\x61\x35\x64\x36\x32\x62\x64\x61\x66\x61\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\ +\x39\x2d\x31\x31\x2d\x32\x35\x54\x30\x38\x3a\x31\x35\x3a\x31\x32\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\ +\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\x79\ +\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x20\ +\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\x3f\x78\ +\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\x3f\x3e\ +\x41\x9d\x06\xc5\x00\x00\x11\x2a\x49\x44\x41\x54\x78\x9c\xed\x9d\ +\xcf\x8b\x5c\xc7\x11\xc7\xbf\x33\xbb\x1a\x6b\x46\x3b\xab\xb5\x27\ +\x8b\x8d\xec\x4b\x62\x21\x99\x80\x4d\x1c\x90\xc8\x25\x51\x64\x0c\ +\x06\x07\x13\x88\x51\x0e\xb1\xc9\x51\xe0\x9c\x42\x2e\x81\xa0\xab\ +\xc9\x31\xc9\x7f\xe1\x04\x9d\x12\x12\xf0\x45\x10\x3b\x88\x24\xc2\ +\x07\x0b\x64\xf0\xc5\x17\x1f\x16\xdb\x8a\x65\x79\x2d\xef\x8e\x3c\ +\x33\x2f\x07\x6d\xcf\xb6\x7b\xbb\x5f\xd7\xaf\x37\x6f\x76\xdd\x05\ +\x62\x66\x9f\x66\x3e\xaf\x7f\x54\x57\x57\x55\x77\xbf\xe9\x54\x55\ +\x05\x4b\xe9\xd6\xfd\x67\xef\xd8\x15\x0c\xfa\xfd\x4e\xf0\x0f\xbd\ +\x63\x57\x64\x40\x00\x1d\xe2\x35\x32\x90\x73\x23\x15\x30\x29\x54\ +\x20\xb9\xe7\xa8\xc0\xda\x76\x93\x00\xc9\x52\x80\x05\x58\x80\x05\ +\x58\x80\x05\x58\x80\xa1\x90\xe7\xe5\x55\x22\xac\xf2\xde\x3b\x89\ +\x7a\x13\x12\xef\xab\xd6\x2d\xa1\x94\x30\x04\x37\xe2\xce\x15\x60\ +\x01\x16\x60\x01\x16\xe0\xb2\x02\x4d\x67\x3d\x20\x3e\xeb\x89\x80\ +\xe4\x92\x39\x39\x3a\x9d\x52\x80\xb6\x40\x95\x6f\x13\x7e\xb9\x93\ +\xb8\x4e\x06\xba\x2f\x37\x92\x08\x22\x41\x97\xa2\x53\x0e\x39\xb0\ +\x98\xaf\x43\x0a\x64\x75\x0c\xb5\x97\x4b\x52\xf7\x28\x03\x7d\x6b\ +\x4d\x8a\x99\xb9\x01\x78\x56\x7d\x0e\x7f\x1b\x1e\x2e\x20\xa9\x63\ +\xa8\x9e\x43\xea\x95\x0d\xcc\x96\x48\x02\x64\x49\x01\x1e\x52\x20\ +\x6b\x05\x57\x12\x3c\xd6\xea\x25\xc7\x59\x2a\x1e\xec\x92\x01\x1b\ +\xcd\x12\xab\xf2\xd8\xa9\x2c\x7b\x52\x8a\x81\x2d\x40\x81\x2c\x3c\ +\xd6\x63\x6f\x17\x92\x2c\x21\xa9\x80\x6c\x69\xb5\x53\xaa\xc4\x75\ +\x16\x30\x05\x4f\x4a\x2b\x71\x0a\x4b\xa8\x4b\x99\x64\x59\x1a\xdf\ +\x46\x0c\x4c\xc5\x79\xcb\xa3\x36\xec\x9c\x03\xa7\x97\xdb\x99\x46\ +\xa9\x13\x7d\x38\xa6\x4d\x23\x29\x33\x0f\x96\x24\xad\x4f\x52\xb5\ +\xed\x07\xc8\x13\xe3\x49\x59\xb8\xb5\x09\xc7\x6d\x56\xc9\x25\x25\ +\x34\x4b\x11\x90\xaa\xce\xb1\x36\xfe\x0d\xc4\xc0\xc3\xa7\xd8\x59\ +\x91\xa4\xfb\x6a\x3b\x88\x1b\x49\x99\x75\x8a\xd9\x62\x6b\x08\xce\ +\x0a\xb5\x0d\xcd\xaa\x1c\xc2\xb3\x50\xf3\x65\xb8\x85\x27\x31\x0e\ +\xa7\x3b\xb7\xd8\x73\x01\x9c\x91\x62\xea\xb4\x37\xe2\x7d\x39\x31\ +\xd9\xdd\x57\x77\x83\xaf\xc9\xd2\x04\x8f\xea\x5e\x26\x4b\xab\xe7\ +\x53\xcc\xcc\x17\x19\x06\x34\xd0\x86\x47\x27\x00\x4f\xf9\x89\x07\ +\xc4\xdc\xfb\xa2\x4e\x52\x66\xa7\x90\x52\xea\x62\xb6\x94\x99\x95\ +\xa5\x19\xcb\xe6\xc0\xc5\xf9\x87\x1c\xb5\x69\x64\x29\x33\x2b\xd2\ +\x3d\x4b\x26\x06\x96\x34\x5a\xb8\x51\x80\xd9\x1a\x3d\x39\xb3\x24\ +\xd5\xc3\x64\xf5\xcd\xf7\x39\x50\xf2\xd8\x24\xd3\xef\x44\x62\x1c\ +\x1a\x71\x38\x17\x07\xe4\xfa\x87\x8b\xcf\xdb\xb4\x52\xe5\x50\xd4\ +\x55\x8e\x01\x92\x23\x25\x57\x42\xd7\x09\xe4\xb6\x5c\xb8\x8f\xdd\ +\x48\xf0\x18\x82\x1b\xc9\xc1\x9a\xdb\xc3\xa4\xb4\x16\x80\xb7\x17\ +\x8d\x72\xe7\xe5\xc5\xa7\x4c\x39\x8b\xfe\x59\x18\x05\x48\x82\xf8\ +\x22\x4d\xea\x9a\xb5\xe1\xe2\x2d\xb6\x24\x92\x32\xc9\xce\x85\x19\ +\x4e\xb5\x81\x6d\x64\xa4\x98\x02\x1b\x9d\x53\x54\x43\xcf\x07\x98\ +\x9a\x2f\xb2\xb4\xb6\x67\xc9\xd7\xc3\x5a\xeb\x6d\x7e\xf6\x76\x69\ +\xe2\xe5\xc5\x65\x45\x96\x26\xe7\x60\x12\xd1\x93\x4a\xb9\x34\xeb\ +\x7a\x8b\xcb\xce\x71\x4b\x78\x04\xd4\x46\xea\x8a\xa8\xa6\x80\xd0\ +\x62\xd7\xea\x24\xf7\xc0\x4b\x76\x20\xb4\xe2\x1f\x86\xc6\x55\x65\ +\xb1\x5d\x15\x7d\x48\x6d\xb5\xa9\x7a\xe8\x43\x54\x55\xee\x82\xb9\ +\x0f\x96\x52\xc2\x58\x15\x55\x6a\xc3\xf2\x60\xa9\x40\x72\x34\x45\ +\xed\xe5\x46\xe6\xe5\x0e\x0c\x46\x4a\x08\x30\x0b\x7c\x62\xa3\x45\ +\x04\x5c\xf1\xde\xfb\x19\x12\xb5\xb5\x71\xef\xd5\xd1\x28\x2b\xdf\ +\x40\x05\xfa\x7a\x98\x85\x72\xda\xb0\x91\x69\x34\x0b\x96\x9e\x79\ +\x54\x1b\xd8\x10\x62\x02\xf4\xc5\x4c\x6d\xea\x6e\x42\x02\x9a\x97\ +\x70\x21\x55\x36\x9d\x97\xd5\x40\x73\x7b\xe8\xa6\x51\x5f\xc1\xd5\ +\xe7\x53\x4c\x8d\x83\x5f\x42\x07\x55\xe9\x61\xd6\x42\x73\x81\x31\ +\x57\xa4\x11\x03\xab\x06\x86\xd7\xc4\xc0\x15\xc4\x4b\x66\xe2\x2c\ +\x99\xf8\x36\xb1\x52\xa9\x26\xfa\x94\x71\x30\xf5\xb1\xd5\x25\x8c\ +\x8d\x8e\xe4\x68\xe1\x2a\x76\x76\x8d\x99\x3b\x8d\x66\x7b\x99\x12\ +\xeb\x75\x41\xd4\x41\x40\xe6\x7d\xa9\x22\xa9\x10\x92\x35\x5f\xd2\ +\x13\xd6\x49\xe1\x74\x8a\x5f\x4a\x35\xd0\x7f\xdf\x88\xe7\x60\xf7\ +\x85\x3d\x31\x4b\x11\x64\x25\x57\x42\xa7\xd4\xe4\xde\xe6\xac\x3c\ +\x9a\x4d\x52\xe1\xc8\x30\x9b\xa4\x48\x53\x00\x27\x21\xe9\x7f\x56\ +\x65\x0f\x43\x30\xeb\x0b\xa1\xb0\xe7\x14\x6e\x2f\x9b\x04\x8f\x7e\ +\xa7\x64\xc5\x7c\xc7\xf8\xe1\xcf\x70\xc6\xaa\x6c\x66\x60\xeb\xae\ +\xcd\xc5\x5c\x0f\xb9\x69\x96\xac\xea\x48\x8f\x35\x89\x81\x31\x48\ +\x68\x28\x58\xc0\xd8\x50\x53\xb5\xa1\x6f\x60\x49\xfe\xa1\xf4\xf4\ +\x87\xb9\x07\x9b\x14\xe9\x58\x56\x55\x99\xf5\x3d\xc9\xac\x27\xbb\ +\x53\xe2\x33\xd9\xc8\x74\xe1\xa9\x2a\xf7\x65\xb3\x0d\x3c\xe6\x6d\ +\x18\x46\xf4\xd9\x36\x94\x38\x4b\xe1\x7b\x16\x30\x2c\x99\xda\x69\ +\x8f\x95\xc6\xac\xca\xfe\x7b\x53\x97\x58\xa5\xd8\x7e\x0d\x4c\x5c\ +\x91\x98\xa8\xcd\x57\xac\xda\xaa\xcd\xc9\x9c\xeb\x22\xcf\x41\xb5\ +\xa1\xb1\x31\xcf\x21\x76\xf4\x3d\x2a\xe6\x5b\x8c\x16\xde\x86\x31\ +\xb0\xc9\x62\x6b\x08\x15\x03\x1b\xd7\xc3\x6c\x96\x93\x5b\x65\xd3\ +\x24\x46\xdd\x35\x32\x30\x06\x59\xbe\x28\x80\x95\xc8\xe0\x56\xd9\ +\x2c\x5e\x6e\x7c\xa2\x57\xf9\x87\xbe\x54\xc1\x2b\x1b\x18\xc2\xb3\ +\x8a\xcd\x89\x02\x48\x8b\x0b\x9c\x29\x80\xb4\xbd\x83\x7b\xd2\xbf\ +\x16\x16\xde\x35\x07\xae\x8d\xf1\xa8\xc0\xd8\x18\x36\xf3\x1c\x48\ +\x05\x91\x6e\x82\x52\xbb\x22\xa9\xd7\x03\xd2\xfa\xb9\x80\xe5\x8d\ +\x02\xc2\x15\x8b\xa4\x98\x9f\x0b\x90\xa6\x59\xc4\x55\x66\x2d\x12\ +\x02\xf2\xfc\xa1\x89\x2b\x62\x92\x76\x26\x57\xd5\xc9\xc2\x1f\x54\ +\xe2\x3e\x43\x06\x72\xf4\x90\xe4\xdf\x50\x37\xe3\x91\x77\xf8\x71\ +\x72\x0e\xa4\x99\x4f\xb2\xa4\x5e\x0b\xa4\x74\x8a\xef\x2d\x64\xcd\ +\x17\x47\xb1\xfd\x4c\x9d\x18\x18\x42\xb3\xa3\x45\x1a\xa7\x98\x86\ +\x66\xe6\x4e\x7b\xad\x50\x8d\x43\xb8\x35\x46\x35\x8d\x86\x37\x6d\ +\x24\xa2\x27\xdf\x3d\xf6\x65\x72\xfb\x01\xf2\x83\xa8\x26\xbe\x4d\ +\x23\x51\x80\xbb\x66\x16\x05\xd4\xdd\x88\x0c\x24\x67\x44\x00\xfa\ +\x82\x35\xf5\xf3\x64\xcf\x01\x38\x68\xbd\x45\xc0\xd0\xfb\x57\xfb\ +\x36\xa9\xf6\x33\xed\x65\xf3\x88\x5e\xd5\x86\x21\x40\xb7\xc1\x7b\ +\x36\xfb\x51\x67\x32\x99\x17\x66\xde\xcb\xb3\xd9\x0f\xd3\x77\xaf\ +\xfb\x61\xf1\x41\xbf\xff\xf7\xd8\x77\x00\x54\x5f\xee\xec\xfc\x84\ +\x5d\xc2\x00\x52\xf7\xf7\x5c\x16\x9e\x66\x61\x7b\xb0\xe6\x55\xa6\ +\x02\xc9\xb1\x5e\x6b\x1b\xbc\x9d\xa8\xb7\xba\xd5\xc1\xa3\x22\x59\ +\xb0\xae\x85\x9b\x6f\xc6\x93\x56\xd9\x74\xcb\x65\xdd\xf5\xf6\x52\ +\xf7\xb9\x9b\xcc\x45\x1a\x49\x89\x81\x64\x90\x13\xee\xd3\x12\xb2\ +\x37\x90\x9e\x79\x34\x9b\xa4\xcc\x27\xfa\x5a\x18\x05\x18\xfb\xac\ +\x3a\x4e\x31\xcd\xdb\x34\x36\x49\x91\xa1\xe6\x8f\x01\x31\x1f\x7a\ +\x52\x8b\x2d\x06\xa6\x3a\x45\x3d\x2f\x9b\x75\x0a\x6b\xd8\x51\x80\ +\x3e\x98\x04\x3d\xfc\x25\x8c\x41\x6b\x81\x52\xe3\x90\x94\x56\x52\ +\x04\xfe\xfb\x6c\x1b\x4a\xab\xac\xde\x3b\xe7\x24\x7c\xca\xdb\x01\ +\x31\xdf\x31\xce\xa9\x72\xa3\x79\x6c\xb5\xf7\xd5\xd8\x8a\x8f\xba\ +\x53\x52\x60\x53\x87\x13\x30\xda\x11\x64\x6a\x1c\xcc\x32\xed\xd9\ +\x12\x85\xc2\x9d\xa4\xd4\x69\x67\x72\x58\x4b\x05\x86\x10\xb3\x73\ +\x52\xa9\xbf\xd9\xc0\x18\x48\x7d\xac\x89\x72\x6d\x2e\xad\xb8\x73\ +\x2c\x68\x6b\x87\x86\x7c\x51\x6f\xed\xa0\xdc\x64\x2e\x4b\x13\x8d\ +\x9a\xa4\x4c\xeb\x6e\x30\x97\x85\x1f\xaf\x4b\x41\xd5\xe1\x6d\xf8\ +\x5e\x9d\x66\x21\x5b\x1d\x6e\x42\x32\x0b\xe4\xac\x05\xd4\x82\x9c\ +\x70\x4e\xb6\x92\x46\x15\xa5\x84\x61\xaf\x9a\xb9\xc4\x94\xeb\xa2\ +\x0c\xa7\x59\x12\x23\x76\x03\x36\x90\x95\xc0\xa0\x00\x53\x37\x30\ +\xcb\x2c\xd5\xc2\x80\x25\x59\x01\xaf\x15\xe9\x58\x4e\x8a\x34\x7f\ +\x68\x12\x2f\xfb\xd0\xa4\x2c\x45\x96\x58\x75\x06\x3c\x26\xa6\x7b\ +\x89\xcd\x8d\x83\x6e\xa1\x10\x07\x67\x39\xb3\xcd\x27\xe6\xf9\x43\ +\x32\x50\x92\x3f\x34\xeb\x65\xb3\x13\xd6\x24\xa3\xc0\x01\xba\x52\ +\xf9\x89\x71\x93\x44\x50\x85\x83\x6a\x74\x40\xcc\x03\x70\xf3\x74\ +\x1f\xc7\x83\x25\x3d\xbd\x43\x7a\xac\xc9\x2c\x3b\x07\x34\xb4\x67\ +\x69\x39\xc3\x8a\xac\x0e\x02\x0d\x24\x75\x5b\xab\x32\x59\x16\xbe\ +\x94\x19\xa6\x9a\x4d\xfc\x43\x07\x30\x39\x5b\x11\x42\xb3\x22\x3d\ +\x88\x6a\x16\x8d\xaa\x3d\x87\x50\xa9\xb3\xeb\xa3\xd4\xd0\x2c\xf5\ +\x37\x1b\x68\x9e\x22\x88\x19\x04\xb5\xda\x90\xf6\x10\x73\x80\x2c\ +\x6b\x63\x1e\x05\x2c\xc5\x22\x97\x0a\x18\x83\xab\xb3\x73\x9c\xeb\ +\xed\x45\xf4\xa6\xc9\x34\xc0\xf8\x89\x13\xac\x9e\x5e\xfe\xa4\xae\ +\x79\x1e\xbb\xd5\xc4\x78\x3b\x1b\x1a\xa5\xdb\xf8\x93\xd2\xca\x36\ +\xad\x94\x5b\x1c\x15\x4a\x09\x63\x13\x94\xe9\xee\x3e\x75\x9a\x25\ +\xfc\xb2\x3a\xcd\x12\x56\x6f\xb9\xd3\x2c\xa6\xf3\x32\x49\xb8\x01\ +\xb8\xff\xb7\x08\xc8\x9a\xe4\x01\xfe\xe9\xe0\x2c\x98\x1a\x56\x90\ +\x57\x2d\xa4\x5b\x8c\x92\x62\xbe\xd8\x2a\x3d\x37\x9a\x14\x6e\x62\ +\xbc\xb1\xb3\x66\x49\xe1\x8c\x14\xf3\x27\xe3\x99\x3e\xff\x30\x84\ +\x9b\xa5\xaa\x4c\xfd\x43\xb7\x79\xc7\x6c\x1a\x35\x0f\x7c\xcc\x8c\ +\x43\xf6\xa1\x10\x1c\xa0\x5f\x22\xf3\x4e\x31\x51\x1b\xf3\x33\xe0\ +\x0e\x10\x82\xd5\x0e\xa7\x99\x3d\x0c\xc1\x59\xb8\x64\xef\x9c\xf9\ +\x43\x87\x6a\xc5\xbc\xca\xad\xef\xb7\xc9\x7a\xb0\xad\x1a\x58\x92\ +\x98\x6f\x41\x97\x7a\x5f\xe2\x28\x20\xe5\x13\xaa\x42\x33\x72\xde\ +\x0b\x90\x6f\x0a\x15\x03\xc3\x36\x34\x7d\xf8\x95\x0f\x5a\xdc\xf2\ +\x47\xab\x4f\x24\x23\x25\x83\x5a\x19\x7a\x4e\xcc\xe3\x14\xff\xbd\ +\x3a\x89\xe1\xbf\x37\x79\xb4\x54\xdd\xdf\x6c\x20\x19\x26\x01\x66\ +\xc5\xfc\xcc\x63\x2b\xa1\x19\xeb\x06\x12\x97\x58\x05\xf4\x95\xb8\ +\xb1\x74\x5f\x2d\xb4\xf6\x50\xb9\x44\x5a\xb5\x36\x24\x59\x05\x80\ +\xd7\x5f\x7f\xfd\xc0\x7f\x3c\xfa\xe8\xa3\xb8\x7a\xf5\xea\x2f\xde\ +\x7c\xf3\xcd\xcb\x00\x7e\x0c\xe0\x5b\x00\xfe\x06\xe0\xb7\x00\xde\ +\x8a\xc1\xaa\xaa\x7a\x50\xc2\x6e\xb7\x1b\xfd\xd7\xe9\x74\xce\x03\ +\xb8\xb0\xf7\xf9\x21\x80\x1f\x00\x38\x9b\x2d\xe1\xb1\x63\xc7\xd0\ +\xef\xf7\x31\x9b\xcd\x30\x1e\x8f\x31\x9d\x4e\x05\x95\xf5\x80\x1f\ +\x7e\xf8\x21\xee\xde\xbd\x8b\xb5\xb5\x35\x8c\x46\x23\x8c\x46\x23\ +\x3c\xf4\xd0\x43\x98\xcd\x66\xb5\x5f\xde\xd8\xd8\x40\xb7\xdb\xc5\ +\xa9\x53\xa7\x70\xfa\xf4\xe9\x7d\xe0\x17\x5f\x7c\x81\x1b\x37\x6e\ +\x60\x65\x65\x05\xeb\xeb\xeb\xd8\xda\xda\xc2\x95\x2b\x57\xd0\xeb\ +\xf5\xa2\xa0\xb3\x67\xcf\xe2\xc2\x85\x0b\xb8\x75\xeb\x16\x8e\x1f\ +\x3f\x8e\x6e\xb7\x8b\xb3\x67\x1f\xb4\x44\xd7\x55\x79\x38\x1c\x62\ +\x7d\x7d\x1d\xc3\xe1\x10\x1f\x7c\xf0\x41\xff\xda\xb5\x6b\x78\xf8\ +\xe1\x87\x63\xbc\xd9\x73\xcf\x3d\x87\xb5\xb5\xb5\x8d\xaf\xbe\xfa\ +\x0a\xf7\xee\xdd\xc3\xce\xce\x0e\xb6\xb7\xb7\xf7\x81\x55\x55\xa1\ +\xd3\xe9\x60\x6d\x6d\x0d\x9f\x7c\xf2\xc9\x25\x00\x7f\xde\xde\xde\ +\x46\xaf\xd7\x8b\x69\xfd\xfd\xd5\xd5\x55\x5c\xbd\x7a\xf5\x5f\xe3\ +\xf1\xf8\xfc\x70\x38\x84\x63\xcc\x81\xc3\xe1\x10\xa3\xd1\x08\xd7\ +\xaf\x5f\xff\xd5\x3b\xef\xbc\xf3\x17\x00\xc3\xc9\x64\x82\xf1\x78\ +\x1c\xad\xf2\x78\x3c\x46\xaf\xd7\x3b\xf5\xee\xbb\xef\xfe\xe7\xc6\ +\x8d\x1b\x3f\xdf\xdc\xdc\xc4\xda\xda\x1a\xe6\xe4\x17\x5e\x78\xe1\ +\x7b\x27\x4f\x9e\xbc\x8e\xbd\x34\xc0\xca\xca\xca\x97\xfd\x7e\xff\ +\xd6\xea\xea\xea\x0e\xf6\x8d\xe9\xb7\x01\x54\xbd\x5e\xef\x7f\x83\ +\xc1\xe0\xfd\x6e\xb7\x3b\x71\x9f\xdf\xdc\xdc\xfc\xc7\x4b\x2f\xbd\ +\x74\xba\xaa\xaa\x07\x9d\x32\x9d\x4e\xd7\xab\xaa\x7a\xcc\x95\xa0\ +\xaa\xaa\xfe\x64\x32\xf9\xee\x64\x32\x39\xd8\x80\xb3\xd9\x23\xe3\ +\xf1\xf8\x11\x5f\x03\x66\xb3\xd9\x63\xd3\xe9\x74\x30\xaf\xf2\xb9\ +\x73\xe7\xde\x7a\xed\xb5\xd7\xbe\x73\xe6\xcc\x99\xdf\xef\x7d\xe0\ +\xda\xc5\x8b\x17\x3b\x97\x2e\x5d\xfa\x63\x08\x9c\x4c\x26\xbf\x7c\ +\xf5\xd5\x57\x3b\x4f\x3e\xf9\xe4\x6d\x00\x78\xe6\x99\x67\x7e\x73\ +\xf9\xf2\xe5\xef\x3f\xfd\xf4\xd3\x37\xe7\xc0\xdb\xb7\x6f\xe3\xde\ +\xbd\x7b\x78\xe5\x95\x57\x7e\x77\xfe\xfc\xf9\x5f\x03\xe8\x9e\x38\ +\x71\x02\x1b\x1b\x1b\xd1\x36\x3c\x79\xf2\x24\xc6\xe3\xf1\xee\xb9\ +\x73\xe7\x7e\xfa\xf2\xcb\x2f\xff\xe1\xee\xdd\xbb\xf8\xf4\xd3\x4f\ +\xf7\x3b\xa5\xdb\xed\xe2\xfe\xfd\xfb\xf8\xfc\xf3\xcf\xf1\xd4\x53\ +\x4f\xfd\x09\xc0\xcf\x7a\xbd\x1e\xc6\xe3\x71\xcc\xf6\x1d\x9b\x4e\ +\xa7\x78\xf1\xc5\x17\xcf\x3f\xfe\xf8\xe3\x7f\xbd\x73\xe7\x0e\xa6\ +\xd3\x29\xba\xdd\xee\x3e\x70\x32\x99\x60\x32\x99\x60\x77\x77\x17\ +\x1f\x7f\xfc\x31\x36\x36\x36\xee\x3c\xff\xfc\xf3\xf8\xec\xb3\xcf\ +\x62\x05\x5c\x79\xfb\xed\xb7\x71\xfc\xf8\xf1\xad\x4e\xa7\x83\xd1\ +\x68\x84\xe1\x70\x88\x7e\xbf\x0f\x60\x6f\xa4\x9c\x38\x71\x02\x2b\ +\x2b\x2b\x18\x0c\x06\x78\xf6\xd9\x67\x71\xf1\xe2\x45\x0c\x06\x03\ +\xec\xee\xee\x46\xab\x7c\xf3\xe6\x4d\xbc\xf7\xde\x7b\xd8\xdc\xdc\ +\xc4\x47\x1f\x7d\x84\xd1\x68\x84\xc1\x60\xb0\x0f\x7c\xe2\x89\x27\ +\xd0\xe9\x3c\xa8\x5d\xa7\xd3\x41\x55\x55\xd8\xdd\xdd\x9d\x57\x23\ +\x26\x93\xc9\x04\x5b\x5b\x5b\xd8\xda\xda\x9a\x5f\x7b\xe3\x8d\x37\ +\x1e\x00\x63\xea\x21\x95\x55\x60\x7f\xd8\xf8\x52\x55\x15\xaa\xaa\ +\xfa\x2f\x80\x7f\xee\x5d\xda\x06\xf0\x6f\x00\xef\xd7\x01\xbf\xa9\ +\x93\x54\x4a\x06\xfd\x7e\xb2\x3d\xbe\xdc\xd9\x89\x4e\xf8\xcb\x5f\ +\xe5\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\ +\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\ +\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\ +\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\ +\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\ +\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\xc0\x02\x2c\ +\xc0\x02\x2c\xc0\x02\x2c\x40\x53\xa0\xc9\xf3\xb1\xc9\x20\x2a\x90\ +\xf5\x68\x7f\xe0\xa8\x75\x4a\x01\x1e\x55\x20\xfb\x31\x51\xd2\xa1\ +\x67\xfe\xb4\x55\x93\x47\x31\x93\xe4\x68\x01\xdd\xc3\xf3\x6b\x85\ +\x6b\x0f\x53\xd7\xe6\x72\x74\xda\x90\x3c\x62\x38\x6d\x68\xfe\x73\ +\xc9\x24\xa1\x18\x07\x56\x29\x39\xbf\xc9\x45\x82\x9a\x57\x59\xa2\ +\xd8\xb5\x72\xf8\x4b\xc8\xf6\x6d\xa8\x53\x00\xe9\x47\x74\x01\x9e\ +\x1e\x9a\xf8\x87\xa9\x2a\xab\x7e\xb7\x82\x65\x13\x25\x23\xa5\x56\ +\xcc\xd5\x66\x69\x80\xe2\x67\xdd\xbb\x67\xb4\x92\x7e\x14\x88\x02\ +\x74\x90\x50\x17\x55\xbf\x68\x40\xfe\xe9\x27\x0a\x30\x56\x9a\xda\ +\xaa\xe7\x80\x53\xc4\x7f\x78\xc0\xac\xca\xea\xdf\xf1\x61\xbb\x22\ +\x14\xb5\x09\x7f\x65\x48\x5d\xc2\xb0\xca\xea\x12\x86\xa2\x2a\x61\ +\xa3\x40\xd2\x14\xc0\xe9\x14\x1f\xaa\x36\x0e\x0e\xa2\xfe\x65\x8d\ +\x58\x95\x6b\x85\x3a\xf4\xc2\xdf\x87\x13\x97\x30\xd4\xbb\xd4\x8f\ +\xcf\x91\x81\xe6\x6a\xe3\x1e\xd8\xee\x5b\x6c\x53\xc5\x56\xab\x8d\ +\x6f\x0f\x49\x3f\x05\xd5\x4a\x09\x63\x80\xa4\x0b\xc3\xd5\x43\xf5\ +\x14\x10\x1b\x29\xb5\x3d\x4d\xad\x32\xf0\xa0\x9a\x59\xd5\xe1\xf4\ +\xb2\xb3\x3c\x6a\xc5\xf6\xab\x5a\xed\x95\x54\x5d\x42\x1f\xea\xdf\ +\x44\x5d\x42\x93\xa1\xe7\x03\x9c\xfe\x99\x19\xd8\x6c\xc7\xe4\x80\ +\xee\x01\xee\x7e\xc9\x4c\xc6\xb2\x5f\x75\x53\xc5\x36\x01\xba\x2f\ +\xcf\x90\xd1\x41\x0a\xd0\xf7\xb1\x7d\x1d\x34\xeb\x65\x93\xa1\xe7\ +\xc4\xbc\x0d\x4d\x27\x29\x07\x71\x9d\xa2\x6a\xc3\xd8\xc4\x2e\x9e\ +\x02\xc2\x12\x86\x81\x10\x1b\xe8\xab\x09\x69\xb2\xa7\xfa\x36\xa1\ +\x4d\x34\x09\x1e\xb3\x3d\xac\x01\x8a\x3b\x25\x16\x8d\xaa\xdd\xb9\ +\xd8\x44\x9f\xfc\x19\x1d\x6a\x68\xe6\x97\x4c\xdd\xcb\xae\xa4\x2b\ +\x99\xcf\x92\x80\x61\x8a\x20\x2b\x9c\x2c\xf1\x2c\x78\x8d\x0a\x77\ +\x1a\x75\x6d\x98\xfc\x09\x22\x6e\xbc\x9c\xad\xba\x74\x71\x41\xb5\ +\x40\xc3\x4a\x3b\x53\x33\x9c\xe4\x3c\x22\xa7\x84\x61\xe7\x88\x81\ +\x0e\xe0\x2b\x79\x52\x24\x69\x67\x95\x71\xf0\x85\x14\x8d\x72\xd2\ +\xce\xa4\x78\x39\x07\x8c\x41\x6a\xc1\x94\x12\x92\x4a\x46\x05\xc6\ +\x12\x91\xaa\xac\x88\x83\xc6\xde\x8b\x81\xac\x84\xa4\x44\x0f\xcd\ +\xd6\x02\x48\x50\x4a\x09\x49\x91\xbc\x13\xc9\x2f\x44\x9b\xb5\xa1\ +\xd9\xf6\x18\x27\xa4\xec\x1c\x67\x2c\x9b\x96\x30\xe5\xc9\x1e\x10\ +\xea\xd0\x8b\x5d\x17\x01\x53\x55\x16\xeb\xa1\x3f\x9f\x98\xe8\x21\ +\xb0\x1f\xe3\xb9\x1b\xa8\xe2\x94\xd0\xc1\xcc\x96\x52\xda\x29\x49\ +\x91\x64\xda\xdd\x8d\xa2\x22\x99\x02\xd4\x6d\x18\x03\x8b\x5d\x62\ +\x1f\xea\xff\xad\xd2\x43\xf2\x02\x17\x20\x5f\xca\x34\x71\x96\x4c\ +\xda\xd0\x2f\x09\x29\x22\x90\xf8\xd8\xfe\x2b\x1b\x98\xba\x81\xba\ +\x97\xc9\x0e\x27\xd7\x38\x20\xf2\x37\x0b\xe8\xa0\xb1\xf7\x22\xa0\ +\xf9\xfa\x72\x6c\x2d\xa5\xee\x46\x6c\xa0\x7a\x4d\xca\x41\xc3\x8e\ +\x10\xab\x8d\x53\x11\x3f\xac\xad\x9d\x57\xa4\xdb\x63\xc4\x39\x07\ +\x20\x5e\x2a\x75\x32\xcd\x07\xab\x80\x0e\x14\x8a\x89\x1e\xfa\x20\ +\x75\xce\x21\xcc\xdb\xa8\x14\x3b\x0b\xe1\x02\xdd\xab\x9f\xb2\x12\ +\x03\x43\xb3\xef\x2f\x23\x89\x4b\xe8\x40\x40\x66\x71\x86\x0a\x8c\ +\x95\xd4\x24\x65\x0a\x64\x60\x14\x60\xcc\x59\x57\x95\x30\xcc\x7d\ +\xa1\x0e\x46\x01\x86\xd3\xa6\x89\xc5\xce\xda\x40\x0e\x30\x36\x07\ +\xab\x15\x3b\x54\x6a\xc0\xa8\x53\xcc\x9d\xa5\xd0\xe2\x88\x81\x64\ +\xc3\x40\x01\x86\xa9\x7b\x7f\x5c\x8b\x4b\xe8\x5e\x1d\x54\xed\x7d\ +\xf9\xef\x4d\xd6\xa4\x62\x69\x67\x31\x30\x35\x75\x9a\x1a\x07\x33\ +\xb5\xf1\x41\x26\xcb\x70\xfe\x6b\xd2\xb7\xa1\xa6\x08\x66\x38\xe8\ +\x89\x89\x4b\xe8\xeb\xa2\xda\x38\x98\xcf\x29\x31\xdf\x46\x65\x0f\ +\x63\x51\x68\x37\x72\x7d\x2e\x92\xc5\x05\xd5\x8a\x8f\x0f\x75\xa2\ +\x5a\xf1\x09\xbf\x58\x5b\x5d\x0a\x30\x54\x93\xd8\x50\x3c\x70\x47\ +\x0a\x30\xd6\x96\x51\x91\x58\x1b\xb3\x38\xc5\xc4\x83\x35\xdf\xf7\ +\x15\x06\x8f\xb1\xeb\x2c\x60\x0a\x9e\x94\xd6\xc2\x5b\x1f\x1a\x5e\ +\x63\x01\xc3\xdc\x57\xb6\x53\x38\xf1\x72\x2c\xb1\x26\x2e\xa1\x99\ +\x6f\xe3\x4f\x4e\x29\x15\x62\x01\x43\xa8\x13\x95\xf9\x62\x65\x8a\ +\xa9\xab\xb7\xa4\x0e\xa1\x00\xc3\xb6\xf3\xb3\x23\x51\x91\x56\x39\ +\xd9\x86\xdc\x25\xf5\xba\x6b\x00\xf8\x49\x5d\x93\xb0\xc2\x17\xd3\ +\x89\xde\x74\xa4\x84\x70\xf5\x44\x9f\xca\xdf\xb0\x81\xa9\xfd\xd8\ +\x62\xdf\xc6\x89\x99\x6f\xe3\xbe\x1c\xb6\xa3\x3a\x78\xcc\xba\x20\ +\x5c\xa0\x7b\x6f\x16\x3c\x92\x45\x9a\xc7\x36\x05\x9a\x24\xd3\x7c\ +\x31\x5d\x4f\x49\x5d\x23\x03\xc3\xd0\x2c\x2b\xdc\xcc\x92\x5a\x6d\ +\x80\xaf\x7b\x0f\xee\xd5\xc4\x38\xa4\x4c\x19\x0b\x18\xfa\xd8\xa6\ +\xbe\x4d\xb8\xe3\x54\x04\xf4\xdb\x8f\xb4\xcf\x41\x9a\xaa\x52\x03\ +\x63\x1e\x98\x08\xe8\xa0\xa6\x55\x26\x2b\x35\xd0\xc2\x5e\x91\x98\ +\x93\x64\x9a\x73\x48\x5d\x53\x01\x6b\x45\xba\x84\x94\x14\x6e\xa7\ +\x98\x38\x4b\x94\x6b\x73\x59\xf8\x39\x7a\xa0\xc1\x36\x24\x89\x74\ +\xe3\x84\x18\xc8\x2e\xa1\xf9\xee\x3e\x69\x09\xd5\x1e\x2c\x59\x8e\ +\x1e\xd0\x34\x45\x40\x82\x4a\xa3\x51\x15\x90\x25\x05\x48\x16\x93\ +\xa1\x67\x92\x66\x89\x41\xd5\x69\x67\x56\x9a\x80\xfb\x8c\xa0\xac\ +\x92\x73\xaa\x4c\xda\x18\x6a\x9e\xd4\x5d\x1a\x3d\x2c\xc0\x02\x3c\ +\x92\x40\xf1\xe2\x42\xea\x8b\x62\x8b\x6d\xee\x1f\xa6\x4a\x68\xba\ +\x1f\xdb\x5d\x17\x01\x6b\xbf\x2c\x05\xb2\x64\xe1\x6a\xc3\x96\xc3\ +\x0f\x8c\xb5\x95\xc9\x82\x75\xee\x1a\x19\xc8\x16\xe9\x01\x40\x53\ +\x60\x23\x55\xfe\x86\xc5\x7a\x87\xa3\x53\x0a\xb0\x45\x20\x69\x85\ +\x82\x03\x64\x43\x8f\xee\x24\x65\x6e\x6d\xd4\x55\x26\xed\x8e\xe4\ +\x00\x7d\x98\x69\x78\xeb\xa0\x6a\x20\x59\x17\xb9\xf1\xb2\x29\x90\ +\x24\xd2\xa7\x25\x98\x00\x01\x42\xb5\x5b\xad\x32\x69\x27\xc6\xd2\ +\x18\x07\x15\x30\xac\xa6\x7a\xe8\x85\xeb\x79\xcb\x6b\x1c\x4c\x7b\ +\x99\xb4\x55\x10\x90\x1d\x7b\xaf\x15\xc9\xb6\x55\x15\xd0\xdf\x6f\ +\x48\xda\x9a\x60\xbe\x16\x20\x31\xb0\xa6\x0e\x67\x23\xe6\x6b\xf9\ +\x7d\x6c\xf3\x12\xd6\xca\xd1\x04\x96\x4e\x29\xc0\x02\x2c\xc0\x02\ +\x2c\xc0\x02\x2c\xc0\x02\x3c\x34\x40\xd6\x29\x7f\x0a\x30\x84\xa8\ +\x03\x1f\xd6\x41\x0d\x0a\x30\xdc\xce\xb1\xdc\xb1\x1e\x09\x2a\x89\ +\xf5\x4c\x7d\x6c\x93\xed\xd3\x4e\xcc\xc3\x5b\xf3\xbc\x4d\x23\x01\ +\x78\x56\x16\xbe\x4d\x8b\x2d\x9d\xaa\x62\x6f\x65\xa8\x15\xf3\x12\ +\xfe\x1f\x95\x6c\x1f\xcb\x64\xe0\x51\xc0\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x1a\x64\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x1a\x00\x00\x03\xc3\x08\x06\x00\x00\x00\xad\x93\x1b\xb4\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ +\x01\x00\x9a\x9c\x18\x00\x00\x08\x73\x69\x54\x58\x74\x58\x4d\x4c\ +\x3a\x63\x6f\x6d\x2e\x61\x64\x6f\x62\x65\x2e\x78\x6d\x70\x00\x00\ +\x00\x00\x00\x3c\x3f\x78\x70\x61\x63\x6b\x65\x74\x20\x62\x65\x67\ +\x69\x6e\x3d\x22\xef\xbb\xbf\x22\x20\x69\x64\x3d\x22\x57\x35\x4d\ +\x30\x4d\x70\x43\x65\x68\x69\x48\x7a\x72\x65\x53\x7a\x4e\x54\x63\ +\x7a\x6b\x63\x39\x64\x22\x3f\x3e\x20\x3c\x78\x3a\x78\x6d\x70\x6d\ +\x65\x74\x61\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x3d\x22\x61\x64\x6f\ +\x62\x65\x3a\x6e\x73\x3a\x6d\x65\x74\x61\x2f\x22\x20\x78\x3a\x78\ +\x6d\x70\x74\x6b\x3d\x22\x41\x64\x6f\x62\x65\x20\x58\x4d\x50\x20\ +\x43\x6f\x72\x65\x20\x35\x2e\x36\x2d\x63\x31\x34\x35\x20\x37\x39\ +\x2e\x31\x36\x33\x34\x39\x39\x2c\x20\x32\x30\x31\x38\x2f\x30\x38\ +\x2f\x31\x33\x2d\x31\x36\x3a\x34\x30\x3a\x32\x32\x20\x20\x20\x20\ +\x20\x20\x20\x20\x22\x3e\x20\x3c\x72\x64\x66\x3a\x52\x44\x46\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\ +\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ +\x39\x2f\x30\x32\x2f\x32\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\ +\x61\x78\x2d\x6e\x73\x23\x22\x3e\x20\x3c\x72\x64\x66\x3a\x44\x65\ +\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x20\x72\x64\x66\x3a\x61\x62\ +\x6f\x75\x74\x3d\x22\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\ +\x65\x2e\x63\x6f\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x22\x20\ +\x78\x6d\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x70\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\ +\x6d\x65\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\ +\x2f\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x2f\x31\x2e\x30\x2f\x22\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6d\x70\x4d\x4d\x3d\x22\x68\x74\ +\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\ +\x6d\x2f\x78\x61\x70\x2f\x31\x2e\x30\x2f\x6d\x6d\x2f\x22\x20\x78\ +\x6d\x6c\x6e\x73\x3a\x73\x74\x45\x76\x74\x3d\x22\x68\x74\x74\x70\ +\x3a\x2f\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\ +\x78\x61\x70\x2f\x31\x2e\x30\x2f\x73\x54\x79\x70\x65\x2f\x52\x65\ +\x73\x6f\x75\x72\x63\x65\x45\x76\x65\x6e\x74\x23\x22\x20\x78\x6d\ +\x6c\x6e\x73\x3a\x74\x69\x66\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\ +\x2f\x6e\x73\x2e\x61\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x74\x69\ +\x66\x66\x2f\x31\x2e\x30\x2f\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x65\ +\x78\x69\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6e\x73\x2e\x61\ +\x64\x6f\x62\x65\x2e\x63\x6f\x6d\x2f\x65\x78\x69\x66\x2f\x31\x2e\ +\x30\x2f\x22\x20\x78\x6d\x70\x3a\x43\x72\x65\x61\x74\x6f\x72\x54\ +\x6f\x6f\x6c\x3d\x22\x41\x64\x6f\x62\x65\x20\x50\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\x31\x37\x20\x28\x4d\x61\ +\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\x78\x6d\x70\x3a\x43\x72\ +\x65\x61\x74\x65\x44\x61\x74\x65\x3d\x22\x32\x30\x31\x36\x2d\x31\ +\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\x35\ +\x3a\x30\x30\x22\x20\x78\x6d\x70\x3a\x4d\x6f\x64\x69\x66\x79\x44\ +\x61\x74\x65\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\ +\x31\x34\x3a\x31\x35\x3a\x31\x36\x2d\x30\x35\x3a\x30\x30\x22\x20\ +\x78\x6d\x70\x3a\x4d\x65\x74\x61\x64\x61\x74\x61\x44\x61\x74\x65\ +\x3d\x22\x32\x30\x32\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\ +\x31\x35\x3a\x31\x36\x2d\x30\x35\x3a\x30\x30\x22\x20\x64\x63\x3a\ +\x66\x6f\x72\x6d\x61\x74\x3d\x22\x69\x6d\x61\x67\x65\x2f\x70\x6e\ +\x67\x22\x20\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x43\x6f\x6c\ +\x6f\x72\x4d\x6f\x64\x65\x3d\x22\x33\x22\x20\x70\x68\x6f\x74\x6f\ +\x73\x68\x6f\x70\x3a\x49\x43\x43\x50\x72\x6f\x66\x69\x6c\x65\x3d\ +\x22\x73\x52\x47\x42\x20\x49\x45\x43\x36\x31\x39\x36\x36\x2d\x32\ +\x2e\x31\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x49\x6e\x73\x74\x61\x6e\ +\x63\x65\x49\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x38\x38\ +\x66\x34\x62\x61\x38\x63\x2d\x31\x34\x39\x61\x2d\x34\x33\x63\x34\ +\x2d\x39\x63\x65\x61\x2d\x31\x66\x61\x61\x33\x38\x61\x36\x34\x66\ +\x64\x30\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x44\x6f\x63\x75\x6d\x65\ +\x6e\x74\x49\x44\x3d\x22\x61\x64\x6f\x62\x65\x3a\x64\x6f\x63\x69\ +\x64\x3a\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\x3a\x61\x62\x63\x34\ +\x32\x31\x32\x37\x2d\x34\x61\x61\x32\x2d\x33\x34\x34\x37\x2d\x39\ +\x30\x39\x34\x2d\x38\x64\x33\x61\x64\x37\x30\x34\x32\x30\x33\x32\ +\x22\x20\x78\x6d\x70\x4d\x4d\x3a\x4f\x72\x69\x67\x69\x6e\x61\x6c\ +\x44\x6f\x63\x75\x6d\x65\x6e\x74\x49\x44\x3d\x22\x78\x6d\x70\x2e\ +\x64\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\ +\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\ +\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x74\x69\x66\x66\x3a\x4f\ +\x72\x69\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x22\x31\x22\x20\x74\ +\x69\x66\x66\x3a\x58\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x59\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x3d\ +\x22\x37\x32\x30\x30\x30\x30\x2f\x31\x30\x30\x30\x30\x22\x20\x74\ +\x69\x66\x66\x3a\x52\x65\x73\x6f\x6c\x75\x74\x69\x6f\x6e\x55\x6e\ +\x69\x74\x3d\x22\x32\x22\x20\x65\x78\x69\x66\x3a\x43\x6f\x6c\x6f\ +\x72\x53\x70\x61\x63\x65\x3d\x22\x36\x35\x35\x33\x35\x22\x20\x65\ +\x78\x69\x66\x3a\x50\x69\x78\x65\x6c\x58\x44\x69\x6d\x65\x6e\x73\ +\x69\x6f\x6e\x3d\x22\x31\x32\x30\x34\x22\x20\x65\x78\x69\x66\x3a\ +\x50\x69\x78\x65\x6c\x59\x44\x69\x6d\x65\x6e\x73\x69\x6f\x6e\x3d\ +\x22\x32\x30\x22\x3e\x20\x3c\x70\x68\x6f\x74\x6f\x73\x68\x6f\x70\ +\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\x74\x6f\ +\x72\x73\x3e\x20\x3c\x72\x64\x66\x3a\x42\x61\x67\x3e\x20\x3c\x72\ +\x64\x66\x3a\x6c\x69\x3e\x78\x6d\x70\x2e\x64\x69\x64\x3a\x62\x30\ +\x33\x31\x36\x34\x33\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\ +\x2d\x39\x65\x64\x37\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\ +\x33\x62\x3c\x2f\x72\x64\x66\x3a\x6c\x69\x3e\x20\x3c\x2f\x72\x64\ +\x66\x3a\x42\x61\x67\x3e\x20\x3c\x2f\x70\x68\x6f\x74\x6f\x73\x68\ +\x6f\x70\x3a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x41\x6e\x63\x65\x73\ +\x74\x6f\x72\x73\x3e\x20\x3c\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\ +\x74\x6f\x72\x79\x3e\x20\x3c\x72\x64\x66\x3a\x53\x65\x71\x3e\x20\ +\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\x61\x63\ +\x74\x69\x6f\x6e\x3d\x22\x63\x72\x65\x61\x74\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x62\x30\x33\x31\x36\x34\x33\ +\x33\x2d\x30\x64\x62\x33\x2d\x34\x63\x66\x66\x2d\x39\x65\x64\x37\ +\x2d\x65\x37\x32\x35\x35\x61\x35\x64\x39\x32\x33\x62\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x31\x34\x3a\x30\x38\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x2f\ +\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\x74\x3a\ +\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\x44\x3d\ +\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x35\x63\x36\x66\x63\x62\x66\ +\x38\x2d\x34\x34\x34\x35\x2d\x34\x66\x33\x62\x2d\x61\x34\x64\x36\ +\x2d\x33\x30\x36\x62\x66\x31\x30\x31\x62\x61\x30\x39\x22\x20\x73\ +\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x31\x36\x2d\ +\x31\x32\x2d\x30\x38\x54\x31\x37\x3a\x33\x38\x3a\x31\x32\x2d\x30\ +\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\x66\x74\ +\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\x62\x65\ +\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\x32\x30\ +\x31\x37\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\x22\x20\ +\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\x22\x2f\ +\x22\x2f\x3e\x20\x3c\x72\x64\x66\x3a\x6c\x69\x20\x73\x74\x45\x76\ +\x74\x3a\x61\x63\x74\x69\x6f\x6e\x3d\x22\x73\x61\x76\x65\x64\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x69\x6e\x73\x74\x61\x6e\x63\x65\x49\ +\x44\x3d\x22\x78\x6d\x70\x2e\x69\x69\x64\x3a\x38\x38\x66\x34\x62\ +\x61\x38\x63\x2d\x31\x34\x39\x61\x2d\x34\x33\x63\x34\x2d\x39\x63\ +\x65\x61\x2d\x31\x66\x61\x61\x33\x38\x61\x36\x34\x66\x64\x30\x22\ +\x20\x73\x74\x45\x76\x74\x3a\x77\x68\x65\x6e\x3d\x22\x32\x30\x32\ +\x30\x2d\x30\x32\x2d\x31\x30\x54\x31\x34\x3a\x31\x35\x3a\x31\x36\ +\x2d\x30\x35\x3a\x30\x30\x22\x20\x73\x74\x45\x76\x74\x3a\x73\x6f\ +\x66\x74\x77\x61\x72\x65\x41\x67\x65\x6e\x74\x3d\x22\x41\x64\x6f\ +\x62\x65\x20\x50\x68\x6f\x74\x6f\x73\x68\x6f\x70\x20\x43\x43\x20\ +\x32\x30\x31\x39\x20\x28\x4d\x61\x63\x69\x6e\x74\x6f\x73\x68\x29\ +\x22\x20\x73\x74\x45\x76\x74\x3a\x63\x68\x61\x6e\x67\x65\x64\x3d\ +\x22\x2f\x22\x2f\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x53\x65\x71\x3e\ +\x20\x3c\x2f\x78\x6d\x70\x4d\x4d\x3a\x48\x69\x73\x74\x6f\x72\x79\ +\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x44\x65\x73\x63\x72\x69\x70\x74\ +\x69\x6f\x6e\x3e\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x20\ +\x3c\x2f\x78\x3a\x78\x6d\x70\x6d\x65\x74\x61\x3e\x20\x3c\x3f\x78\ +\x70\x61\x63\x6b\x65\x74\x20\x65\x6e\x64\x3d\x22\x72\x22\x3f\x3e\ +\xb3\xee\x42\x20\x00\x00\x11\x97\x49\x44\x41\x54\x78\xda\xed\x5d\ +\x4b\xa8\x64\x47\x19\xfe\xeb\xf4\xbd\x73\xe7\x3d\x13\xcd\x3c\x9c\ +\xcc\x90\x51\x66\x91\x45\x92\x5d\x30\x0c\x12\x8c\x1b\x11\x42\x08\ +\x22\xba\x11\x71\x23\xba\x8c\x3b\x05\xdd\x89\x3b\x41\x70\x61\x16\ +\xae\x24\x20\x2e\x24\x91\x10\x05\x17\xd9\x68\x30\x10\x71\x30\x32\ +\x8c\x09\xa8\xe3\xcc\xe4\x31\x89\x33\x99\xe7\x7d\xf7\x29\xff\xea\ +\xdb\x7d\xe7\xdc\x9a\xaa\xf3\xa8\xf3\x7f\x75\xbb\xaf\x7f\x43\xd1\ +\xf7\xf6\xe9\x3e\xdf\xf9\xdf\x8f\xaa\x53\x67\xce\x5a\x4b\xbb\xe6\ +\x7f\x40\xf7\xbf\x2c\x8f\x5d\x3c\xf6\xf0\xf8\x88\x8a\xe2\x4f\xa6\ +\x28\xfe\xc2\x7f\xaf\xf3\x18\xf0\x18\x6e\x7c\xcb\x7e\xda\x96\xe5\ +\xd3\xfc\x7e\x66\xfc\xd9\xf2\x7d\x67\x5a\x5d\xfb\x11\xcd\x51\xa6\ +\x57\x5b\x20\xd3\xe2\x98\x95\x00\xca\x46\x91\x2f\x3c\x93\x03\xc8\ +\xe4\xa2\x88\x14\x48\x81\x14\x48\x81\x14\x48\x81\x14\x08\x14\xca\ +\x2d\x12\xc8\x4f\xab\x4c\x24\x71\x11\xcb\xeb\x4c\xe0\xc4\x06\x2d\ +\x23\xa3\x5a\xa7\x40\x0a\xa4\x40\x0a\xa4\x81\x2f\x1c\xdc\x4c\xdb\ +\xa0\x97\x1a\x61\x55\x19\x14\x68\x7b\x81\x5a\xb5\xd0\xba\xe6\x75\ +\x7e\x3e\x67\xba\xa8\xfe\x5c\x4f\x4a\x54\x19\xa6\x07\xc8\xa8\xaf\ +\x9b\x09\xa0\xd6\x5e\x20\x15\xc8\xb4\xf4\x02\x46\xa2\xb4\x54\x65\ +\x98\x0d\xcf\x50\x8c\x85\x6d\x3d\xc1\x1b\x14\x45\x26\x25\x54\xa8\ +\x32\x64\x4f\xf2\xb3\xf8\x3a\x95\x91\x02\xe5\x05\x0a\x05\xb7\x56\ +\x2e\xa9\x6b\xba\x15\x9a\x55\x36\x15\x67\xab\x32\x9a\xbe\x86\x46\ +\xb6\x76\x74\xeb\x25\x07\xa9\x2b\x34\x0c\x4a\xbd\xd5\x7b\x2b\x50\ +\xab\xd2\x52\x54\xbd\x1b\x9d\xa6\x34\x45\x76\x47\x29\x43\x68\x6e\ +\x0f\x4e\x91\x69\x5b\x52\x4e\xb5\x7a\x6f\x8b\x8c\x42\x1a\x29\xee\ +\xbd\x6d\x2e\xd6\xf9\xc2\x6f\xd5\x90\x9a\xba\x0e\xa4\x69\x21\x23\ +\x9a\x49\x19\x59\xca\x34\xfb\xef\x9f\xb0\x18\x7f\x36\x94\x00\x6a\ +\x93\xe5\x88\xd9\x51\xe8\x44\x16\xa1\x0c\x26\x62\x3f\x30\x17\x14\ +\x6b\x10\x16\xd2\x14\x15\x91\x42\xcc\x22\x58\x97\x64\x4f\x7d\xe2\ +\x91\x41\x7a\x6f\x83\x76\x41\x66\x3b\x80\x42\x1d\x48\x51\xd6\x75\ +\xaa\x87\xa4\x28\x0a\x29\x83\x38\x90\xa1\x84\x55\xec\x52\x79\x9d\ +\x41\xb9\x20\x48\xa6\x6a\x5a\xf8\x39\xf1\x4c\xd5\xe4\xc8\xeb\x7a\ +\x65\x43\x12\x0d\x8d\x02\xed\x19\xa0\xb9\x77\xf2\x34\xa9\x44\xd9\ +\x22\xee\x19\xa6\xa2\xcf\x00\xf3\x0c\xd9\xec\x68\xf6\x67\xc4\x9c\ +\x51\x0e\x72\x54\x13\x75\x82\x87\x55\x7c\xdb\x9e\xd7\x89\x87\x09\ +\xa8\xaf\xab\x5e\x75\x91\xa2\x85\x12\x09\x64\x56\x17\x04\x53\x6f\ +\x8b\xf4\x75\x31\x65\x10\x77\x41\x49\xae\x47\xd2\xa9\x12\x01\x7b\ +\x41\x16\x6d\x47\xf0\xfa\xa8\x37\x85\x12\x85\x98\xa8\x8c\x2c\xdd\ +\xdf\xcd\xef\xd4\x70\xef\x2b\x23\x43\xc0\x50\x0e\x77\x41\xa1\x95\ +\x9d\xa2\x59\x50\x9d\x5a\xcf\x76\x4a\x5c\xd7\x4a\xb3\x92\xea\xdd\ +\x54\xf5\xcd\x5e\x92\x1f\x0a\x13\x16\x99\x05\x25\xe5\x12\xba\xf2\ +\x36\x7b\xba\x65\x02\xca\x00\x6d\x75\x1a\x84\x8c\xda\x18\xae\xa8\ +\x0b\x32\x11\xef\x0d\x0b\x13\x9d\xf2\xbc\xa9\x0c\x7c\x36\x47\xc5\ +\x37\x99\x97\x28\x03\xfe\x0d\x5e\xc3\x6e\x5b\xd9\x62\xd0\xca\x20\ +\xbe\x45\x8c\xe9\x53\xba\x4c\xf5\x22\xb1\x10\x08\x74\x12\x04\x52\ +\x5a\x36\x85\x02\x68\x28\x87\xfb\x3a\xa8\xc1\x6e\x0b\xeb\xb6\x5d\ +\x19\x60\xcd\x5b\x71\x8a\x4c\x4d\xc1\x0c\x2d\x5b\x92\xe4\x34\xd5\ +\x33\xcb\x36\xe2\xe7\xc4\x3a\x90\x31\x99\x15\xd2\xea\x5d\xd4\x9c\ +\xd4\x48\x03\x85\x58\x57\x90\xf0\x0a\x8d\xea\x89\x3b\x3b\x57\x09\ +\xd6\x41\xb4\x2e\x04\x54\x50\x8b\x99\xcb\xd4\xc9\xc4\xaa\x9c\xc4\ +\xa6\x47\x8d\x77\xd5\x26\xa2\xe6\x22\xac\x2b\x3c\x19\x19\x8f\x9d\ +\x03\x49\xd6\xc5\x64\x51\x20\x29\xf2\x65\x24\xa6\x0c\xfe\xc9\x7c\ +\x59\x89\x19\x6c\xec\x84\x66\x2c\x1f\x11\x8a\x26\x27\x1b\x8c\xeb\ +\xa3\x50\xd1\x0c\xf1\x75\x14\xf1\x75\xe2\x13\x55\x70\xad\x9b\xb0\ +\xcf\xaf\x89\x20\x5a\x57\xa7\x0c\x62\x2e\x68\x50\xe3\xbd\x45\x65\ +\x54\x34\x78\x6f\x78\x98\x18\x48\x53\xe4\xb3\xce\x20\x28\x1a\xb4\ +\x08\x13\x46\x9a\x75\x7e\x9f\xa1\x90\xd4\x3a\x13\xb0\x17\x8b\xf2\ +\xde\x03\x2f\xf0\xf9\x6c\xa5\x5c\xea\x0d\xf1\x0c\x30\xef\xed\xb3\ +\x0e\x12\x61\x4d\xc0\x60\x21\xe9\x56\x53\x21\x06\xd3\x3a\x78\x92\ +\x1f\x5a\x42\x2a\x5e\x95\x87\x12\x48\x8b\x90\x11\x05\xd4\xbb\x40\ +\x29\x43\xe1\xc9\x08\x92\x40\x86\xb4\xce\x22\x9c\x6a\x51\xa3\xe2\ +\xf0\xb9\x09\xb8\x1d\x41\x4b\xcb\xaa\x23\x4d\x9a\xb9\x4c\xa1\x28\ +\xd4\x74\x2a\x50\x32\x82\x2d\x40\x32\xb9\x94\x21\xdb\x6a\xb4\x2c\ +\x73\x7c\x26\x92\x5f\x43\x02\x5f\x9d\xe0\xc5\xd5\x3b\x96\x9c\x88\ +\xca\xa8\xce\x13\xc0\x22\x2c\x94\x75\x75\x06\x2b\xca\xba\x3a\xa3\ +\x1c\x20\x5d\x50\x16\x83\x85\x02\x51\x20\x84\xc3\x8a\xe5\x98\x22\ +\x88\x7b\x86\xe4\xe4\xb1\x6b\x72\x52\x44\x52\xe2\xd9\x9f\x94\x4f\ +\x52\xfd\x3e\xea\x1d\x5b\xff\x0d\xb5\x23\x88\xd6\x41\x95\xa1\xf3\ +\x3d\x61\xa8\x74\xcb\xe4\xf0\x0c\x30\x5f\x07\xcf\xbd\x63\xf7\x5a\ +\x8a\x6a\x5d\x68\xb6\x05\xee\x54\x63\x2d\x1a\x83\x90\x11\x3c\xc9\ +\x27\xa4\x67\x30\x35\xf2\xa1\x86\x63\xa2\x2e\x88\xda\x64\xaf\x53\ +\xb7\x42\xa3\x4d\x28\x87\xaf\xd0\x10\x35\xd8\xa6\xdc\x1b\x1e\xf8\ +\xe0\x4f\xd0\xe8\xd4\x9e\xe9\x5b\xc3\x42\xe3\x51\xf6\x30\xd1\x79\ +\x7f\xa7\x14\x19\x4d\x72\x06\x58\x07\x32\xcb\xba\xa0\x50\x69\x02\ +\xe9\x12\xf7\x4e\x56\xa4\x92\x13\x11\x20\x7f\x92\x2a\xb4\xb5\x45\ +\x81\xd0\xba\xa4\x7a\xb6\xcf\xed\x75\xd0\x2e\x71\x41\xf1\x9b\xd1\ +\xc4\x9a\x4e\x26\x92\x09\x89\xef\xd5\x59\x57\xb6\x14\x94\x61\x9f\ +\x13\x58\xce\x40\x04\xbc\x61\xb0\x68\xa0\x48\xd4\x05\xc5\x7a\xaa\ +\xd0\x29\x03\x68\x69\x99\xa5\x73\x52\xd0\x36\xdc\x4a\x0c\x2f\xc4\ +\x88\x7a\xec\x07\xd9\x67\x55\x27\xdc\x60\x43\x73\x13\xa2\x93\xf2\ +\x94\x5b\x19\xa0\x06\x6b\x6a\xec\x08\xaa\xde\xd5\x18\x94\x25\xf7\ +\x86\xde\x6b\xd9\xb9\x50\xee\x4b\x51\x81\x4a\xb7\xb2\x97\x96\xd0\ +\x9d\x5b\x6c\x43\x45\x2e\x9a\x6e\xf9\x40\xb0\x8d\x20\xea\x7a\xaa\ +\x90\xbc\xae\x4d\xed\xd4\x3b\xc2\xfa\x4b\x40\xb2\x28\x83\x74\x98\ +\x18\x11\xe2\x69\x96\x19\x15\xcb\xee\x53\x6b\x8b\xf1\xff\xa6\x8d\ +\x8c\x86\x01\x02\xdc\x70\x13\x92\x6b\x0c\xb3\x32\x7a\xbf\xb7\x66\ +\x79\xbd\x72\xf5\x43\xfe\x7b\x6d\x3c\xdc\x79\xd6\x83\x8a\x38\x06\ +\x3a\x10\x28\x7b\xdc\x95\xee\xe6\x2b\xde\xe3\x40\x8c\x31\x87\xf8\ +\xc0\x01\x1e\xcb\xfc\xa5\x05\x77\x62\x3e\xe6\xbe\x77\xd8\x58\xfb\ +\x09\x1e\x9f\xe4\x0b\x72\x20\x8b\x41\x25\x1c\x01\x59\x7b\x32\x42\ +\xb0\xa3\x68\x9e\x8f\xcf\x51\x59\x3e\xc1\x27\x3b\x6a\x36\xae\x78\ +\xd7\x98\xaa\x92\x8f\x1d\xe7\xf1\x19\x72\x40\x1b\x14\xaf\xc5\x59\ +\x37\x37\xf7\xc3\x08\xeb\x6c\x45\xad\x37\xc3\x4f\x45\x19\xec\x58\ +\x11\xdc\x67\x75\x4b\x50\xbe\xb1\x01\x64\xcc\xa5\x3e\x65\x4b\x17\ +\xcf\xb0\xbb\x6b\x43\x03\xd6\x0b\xf2\xdb\x68\x25\x81\x16\x89\xf9\ +\x02\x2c\x50\xf1\xa8\xc9\xd7\x41\x9b\xb7\x59\x36\xf8\x82\xef\xbb\ +\x55\x57\x5a\x9a\x5c\x14\x59\x14\x45\x90\x9c\x21\x6b\x83\x3d\x26\ +\x9f\x6c\x4b\xaa\xe0\xea\x0d\xd9\xcc\xd0\x5f\xc1\xee\xbb\x21\xd1\ +\xc5\x2d\xbd\xd8\x99\xca\xba\x82\x80\xfb\x40\xd6\x15\xcd\x90\x15\ +\x83\xf0\x49\x10\xeb\xb1\x0d\x7a\x73\x74\x8c\x6d\x59\xb6\xdd\x86\ +\x85\x09\xa8\x8c\x9a\x6e\x67\x80\x96\xff\x26\x37\xeb\x4c\x2e\x8a\ +\xa0\x2b\x34\xa0\x61\xc2\xf4\x51\x79\x89\x67\x54\x89\x87\xf2\x22\ +\xe2\xb5\x61\x14\x41\x77\x7b\xf3\xe5\x61\x09\x78\x1f\x1f\x35\x38\ +\x55\x98\xc1\xc2\x43\x39\x4c\x46\x6d\x02\x1c\xec\xe9\xde\x70\x5f\ +\x07\x7f\x96\x81\x6f\xb8\x44\xa0\xc7\x40\x34\xed\xfe\x0f\xcd\xeb\ +\x60\xa1\x3c\xc6\x42\xd1\xbe\x77\x5d\x1d\x04\x35\xd8\x6d\x5d\x79\ +\x0b\xa9\x26\x3a\x47\xda\xa9\x5c\xf6\x96\x25\x4c\x64\x73\xaa\x29\ +\x2c\x15\x95\x91\xa1\xcc\xab\xa3\xb3\x46\x58\x48\x21\x06\xd9\x83\ +\xbd\x69\xcf\x68\x31\x3b\x6a\x0a\xd9\xa2\xab\xd1\x4c\x82\xdc\xc4\ +\xd4\x1b\xfa\x64\xb4\xa4\xae\x49\x5f\xcf\x00\xdd\x9e\x31\xfb\x8e\ +\xe5\xf0\xe5\xa3\x59\x6f\xea\x24\xca\xf4\xdc\x16\x68\x02\x59\x97\ +\x6e\xc1\x5a\x34\x49\xed\x9a\xbe\x9e\xc1\x12\xe0\x11\xd5\xb1\x94\ +\x18\xda\x74\x82\xf9\xba\x3a\x4a\xb2\xa4\x5b\xad\x9f\xbf\x9c\xaa\ +\x0c\xad\x57\x38\xa5\x50\xe4\x2f\xa1\x82\x56\x13\x05\xd2\x60\xdb\ +\xb0\x28\xcb\xc3\xb0\xc4\x3d\x43\x5d\x33\x43\x34\xc9\xf7\xf7\xa2\ +\xa9\x1a\x2d\x64\x0b\xcd\xba\x7c\x7c\x76\xca\x96\x50\x48\xb0\x08\ +\xcf\x50\x97\xcf\x41\x76\x05\x49\x7e\x4d\x5d\x27\x3f\xd6\x53\x85\ +\xde\x8c\x66\x52\x58\xda\xf7\x9e\xe5\xd6\x51\x56\x32\xf7\xb6\xd2\ +\x76\x44\x94\xb0\x4d\xcc\xd4\x55\x13\xb1\x6d\xff\x20\xc9\x89\xa5\ +\x4c\x0f\x74\xce\xd6\x25\x4e\xee\x9a\x4c\xb5\xaf\x83\x17\x62\x59\ +\x76\x95\x8f\xcd\xb4\x14\x68\xd6\xc1\xf7\x39\xc9\xb6\xe9\xcd\xb6\ +\x3f\x05\x32\x5b\x27\x1f\xb2\xfd\x48\x6c\xc2\x17\xc2\xba\xa4\xdd\ +\x5b\x52\x29\x82\xcf\xb6\xc0\xef\x9b\xa0\x40\x0c\x82\xf5\x82\x26\ +\xe9\xaf\xcd\xa1\xde\x9d\x1b\xeb\x7d\x95\xa1\x73\x95\x2e\xd5\xaf\ +\xb3\x39\x3c\xc3\x6c\xe6\x0c\x54\x93\xa5\x42\xca\xff\xba\x0d\x55\ +\xc4\x1b\x1a\x49\xf7\xf2\xf5\x51\xef\x2c\xb7\xe4\x67\x7b\xb4\x6e\ +\x96\x5b\x20\x3b\x27\xf8\x7d\x65\xe4\x2b\x08\x74\x4d\xbe\x45\x51\ +\x94\x1c\xfc\xe6\x12\x6d\x28\x14\x97\x7a\x17\xcb\x21\x2f\x0d\x7f\ +\x04\x5b\x96\x27\xb0\xfb\xde\x1a\xb2\xb5\x45\xd3\xf3\x95\x0d\xd2\ +\x8e\xb2\x2e\x33\x80\x26\x27\xd9\xda\x68\x05\x65\x9c\x1e\x0d\x51\ +\x6a\x72\x28\x83\xe8\x8d\xb7\x75\xea\x0c\x69\xa3\x65\xa9\x26\xb2\ +\x45\xd8\xa4\x54\x2b\x95\x75\xf0\x3d\x34\xea\xb4\x0e\x9a\x05\xd9\ +\x5c\xd5\x44\xa7\x45\x48\x53\x79\x0b\x64\xac\xa1\x31\xfb\x99\x2a\ +\xf4\xd1\xba\xa6\xaf\x2b\x9a\xfa\x3b\x13\xb3\xd6\x47\x59\x94\x81\ +\xd0\x76\x14\x53\x06\xf1\x6d\xe5\x8a\x5e\x32\x5a\x59\x59\xa9\xfb\ +\xce\xd0\x18\xf3\xfe\x60\x30\xb8\xc5\xe3\x7a\x40\xaf\xdd\x9e\x0b\ +\x57\x79\x2c\x37\x02\x9d\x3e\x7d\x3a\x18\xe1\x8a\xa2\x70\xe3\xbf\ +\x6b\xab\xab\xcf\xdd\xbc\x79\x93\xee\x2e\x2e\x92\x2d\x4b\x62\x60\ +\xb2\x6e\x73\x06\x7e\x77\x7f\x17\xe3\xf7\x46\xa0\xc7\x1e\x7f\x7c\ +\x2b\xc8\xc6\x0e\x0f\x34\x37\x3f\x4f\xf3\x3c\x6e\x7c\xfc\x31\xbd\ +\xf3\xf6\xdb\x74\xf3\xd6\x2d\x2a\xcb\x72\xab\xb0\x1c\x10\x5f\x90\ +\x69\xc3\xba\xd5\xd5\xd5\x70\xdc\x76\x80\xd6\x0e\xd6\xd7\xd7\x1f\ +\x2f\xad\xfd\x22\x7f\x74\x85\xc7\x8b\xb4\xb1\xfd\xc1\x37\x79\xec\ +\xe1\xef\xbc\x3c\x1c\x0e\x2f\xb6\x4a\xb7\xdc\x55\x31\xff\x47\x57\ +\x56\xa5\xc8\x7d\xc6\xc7\x16\xf8\xff\x2f\xf0\xf8\x3e\x8f\x3f\x8c\ +\x81\xf6\xf3\xf8\x1a\x8f\x4f\xf1\xf8\x17\x8f\xcb\x74\x6f\xf3\x8c\ +\xcd\xd7\xee\xdd\xbb\x69\xef\xde\xbd\xf7\x80\xd6\xd6\xd6\xe8\x63\ +\x66\xcf\xf2\xf2\xf2\xe8\xe4\x6e\x30\x15\xb4\xb0\xb0\x40\x0f\x3c\ +\xf0\xc0\x06\x6b\x4c\x7c\xd7\x0f\x77\xc2\xa3\x47\x8f\x8e\x4e\xea\ +\xce\xf3\xe1\x87\x1f\x8e\x58\xfe\xf0\xc3\x0f\xd3\x99\x33\x67\xb6\ +\x02\x5d\xb9\x72\x85\xae\x5e\xbd\x4a\x7b\xf6\xec\x19\xfd\xd0\x81\ +\x3a\x90\x83\x07\x0f\x5a\xfe\x7f\x38\x37\x37\x17\x4a\xee\xdd\x7b\ +\x79\xfc\xf8\x71\x7a\xea\xa9\xa7\xe8\xf0\xe1\xc3\x74\xee\xdc\x39\ +\xba\x73\xe7\xce\x88\x2b\xbb\x76\xed\x22\xfe\xdd\x3d\xa0\x09\xdb\ +\xdc\xbb\x3b\xe8\x86\x53\x3a\x06\x2a\x4f\x9e\x3c\x39\x3a\xf9\xc5\ +\x8b\x17\x83\x62\x3c\x72\xe4\x88\x7d\xf4\xd1\x47\x8d\xa3\xe8\xc6\ +\x8d\x1b\xf3\x0c\xb2\x36\x61\x3d\xcb\x6e\xc4\x99\x2d\x06\x5b\x61\ +\xd9\x80\xa9\xf9\x3c\xdb\xd6\xb3\x8b\x8b\x8b\xff\xe0\xf7\x5f\xed\ +\xdf\xbf\xff\x1a\xb3\x65\x65\xc2\xd2\x4a\xfc\x61\x8d\x5f\xbc\x7c\ +\xe1\xc2\x85\x85\x77\xdf\x7d\xf7\xc7\x7c\xec\x20\x83\xfd\x9c\xbf\ +\xf7\x37\xbe\x70\x3b\x91\x79\x55\x19\xf6\xf1\xc1\xfd\x4e\xbb\xf8\ +\x8a\x9e\xe3\x8f\xbe\xca\xe3\x41\x3e\xc9\x8b\xb7\x6f\xdf\xfe\xe7\ +\xa1\x43\x87\x1e\xbb\x75\xeb\xd6\x61\xda\xfa\x14\x48\x67\xc8\xc7\ +\x96\x96\x96\x9e\xbc\x7c\xf9\xf2\x51\x66\xd1\x93\xac\xfa\x9f\x63\ +\x0d\x7e\x86\xdf\x7f\xc9\x2c\xff\x1d\x03\x5d\xe0\xef\xdc\xe5\xef\ +\x2e\x8d\x80\xf8\xc0\xd3\x3c\xdc\x17\xbe\x5d\xb5\x0f\x26\xfd\xeb\ +\xef\xbd\xf7\x9e\x1b\x93\x8f\xd7\x2a\x40\xcb\xfc\x9d\x53\x7c\xb2\ +\x17\x9c\x6d\x39\x99\x8e\x6d\xec\x04\x8f\xef\xf1\x6f\x9f\xe7\xff\ +\x7f\xc2\xe3\xf7\xfc\xff\xeb\x23\x20\xa6\xe4\xcf\xfc\xa3\xf3\x07\ +\x0e\x1c\x78\x8d\xbf\xf0\x65\x66\xd7\x97\xf8\x4a\x0f\xb2\xac\x5e\ +\x3a\x75\xea\xd4\x4b\x2c\xe4\xcf\x5e\xbf\x7e\xfd\x5b\xac\x30\xf3\ +\x4e\x71\xc6\x6c\x5b\xe0\x93\xbc\xcf\xe3\xa7\xcc\x8d\x2b\xfc\xdd\ +\xe7\xf9\xfd\x09\xd6\xb6\x8f\x98\xba\x97\xf9\xfd\x55\x96\xd5\x5f\ +\xf9\xfb\xb7\x36\x59\xc7\x5f\xbe\xc6\x00\xd7\x58\x9d\xff\xcd\x6c\ +\xfa\x2d\x03\x3f\x73\xed\xda\xb5\x67\x4f\x9c\x38\x71\xee\xec\xd9\ +\xb3\xaf\xb0\xd6\x95\x6f\xbc\xf1\xc6\x57\x2e\x5d\xba\x54\x5d\x80\ +\xe4\x7e\x7b\x99\x59\xf4\xea\x43\x0f\x3d\xf4\x1f\xfe\xdd\x59\xa7\ +\x33\x7c\xe2\x9f\xdd\xbd\x7b\xf7\x75\x66\x7b\xe9\x28\xdc\xf4\x32\ +\x13\x03\x75\x42\x76\x57\xcb\x07\x57\x98\x1d\xbf\xe1\xab\x7b\xc5\ +\x39\x0d\xa6\x6e\x2f\xf3\xfd\x08\xff\x78\xa1\xe2\x7e\x26\x85\xd7\ +\x01\xe6\xc4\x69\xd6\xb8\x77\x1e\x79\xe4\x91\xef\xf2\xf1\x39\xd6\ +\xce\x25\x96\xe7\xe8\x5c\xee\xbc\x5b\x80\x9c\x1a\x9a\xb1\x63\x9c\ +\x1c\xe0\x1f\xad\x3a\x7b\xf8\xe0\x83\x0f\xac\xd3\x36\x66\x65\x30\ +\x4e\xb1\xb3\x2d\xce\x9f\x3f\x6f\x99\x1b\xeb\xc7\x8e\x1d\x73\xbb\ +\xe3\xd0\x84\x12\x67\x43\xce\xe8\x37\x81\x9c\x45\x3b\x2b\x76\x07\ +\x9d\x45\xbb\x17\xdb\xc7\xc8\x9e\xf8\x8b\x86\xa9\x19\xf0\xc5\xc4\ +\x6e\x44\x2b\x58\xa5\xe9\xcd\x37\xdf\x1c\x9d\xd4\x85\x1c\x77\x81\ +\x4e\xb5\x9d\xe1\x33\x0b\xef\x01\xb9\x0f\xf6\xed\xdb\xb7\x85\x22\ +\xf7\xb7\xbb\x22\xf7\x83\xf1\x97\x4d\xf5\x78\xf5\xe5\xd8\xee\x5c\ +\x8f\xff\x62\xb5\x77\x1c\xb9\x07\x34\xa1\x62\x02\x50\x3d\x19\xb3\ +\xcd\x19\xea\x6b\xa6\x28\xe6\xed\x86\xf7\x76\xaf\x3b\x3c\x7e\xed\ +\xbc\x37\x8f\xb7\xe8\xfe\x9d\x8d\x36\x3d\x83\x1b\x9b\x40\x7f\x7f\ +\xeb\xad\xba\xc0\x37\xe4\xc0\x77\x8e\x63\xd1\xb9\x39\x96\x55\xb9\ +\x11\x7f\x5c\x34\x7d\xc1\x74\x0d\xe5\x11\x3f\xb6\x85\x4a\xc7\xc6\ +\xf9\xb1\x83\x74\x5b\x1f\xd9\x94\x2c\x68\xa2\x19\x91\xd7\x83\x3c\ +\x7e\xc1\xe3\x69\x66\xe9\xf5\xc0\xf1\x49\xce\xf0\x1d\x1e\x7f\xec\ +\x93\x05\x0d\xc6\x01\xee\xe0\x78\x04\x43\x12\x35\x6c\x3a\x34\xb5\ +\xf3\x47\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\ +\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\ +\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\ +\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\ +\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\ +\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\ +\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\xd9\x81\x60\x3b\xff\ +\x57\x01\xb2\xb3\xae\xba\x35\x96\x55\x65\x50\xa0\xd9\x05\xb2\xb9\ +\x29\x0a\x01\xaa\x1d\x4d\x97\x32\xd8\x9a\xe3\x56\x59\x27\x1e\xf4\ +\xfa\xc8\xc8\x76\x35\xe8\x14\x20\xf8\x53\x20\xeb\xae\x7e\x76\x3d\ +\x83\x4d\x75\xba\x53\x67\xb0\x49\x72\xe9\xcb\xba\x90\x1d\x89\xb0\ +\xae\xca\x96\x12\x9d\xa9\x36\xd9\x51\x99\xd3\x8e\x6c\x2e\xad\x13\ +\x67\x9d\x25\xe0\x63\x20\xca\x80\x32\xb4\x66\x9b\x14\xeb\x2c\x42\ +\x46\x65\x84\xda\x52\xd2\x8e\xfc\x2b\x37\x15\x20\x31\x8a\x26\x57\ +\x3d\x44\x52\xd4\x24\x87\xb2\x42\x95\x08\x50\xe9\x01\x1a\x04\xd0\ +\x84\x6d\xc3\x88\x82\x94\x92\x76\x34\x6c\x90\x91\x95\xd0\xba\xd2\ +\xbb\x72\x83\x52\x86\x09\x45\x65\x20\x44\x0c\xa5\x5d\x50\x95\x75\ +\x3e\x45\xeb\x4d\x54\xa5\x00\xf9\x01\x6f\x28\xcd\x3a\x5f\x16\xe2\ +\x9e\xc1\x06\x28\xaa\xba\xa1\xe1\x98\x75\x30\x3b\x9a\x50\xb4\x2e\ +\xc9\xba\xc9\x55\x0f\xe9\xfe\x47\xb5\x0e\xdb\x68\x5e\x0a\xeb\x7c\ +\x8a\xc4\x80\x7c\x8a\x8c\x17\xce\xd7\x11\x40\xeb\x01\xf5\x8e\x85\ +\x8f\x5e\xca\x30\x79\x1c\x47\xf5\xf9\x2d\xeb\x92\x32\x1a\x7a\x2e\ +\xa8\xf4\xd4\x5b\x94\x75\x6b\x11\xd6\x89\x52\xb4\x1e\x90\x51\xd5\ +\x8e\xe0\x06\x5b\x48\xb3\x2e\xe6\xbd\x87\x08\xd6\xad\x46\x58\x07\ +\xf1\x75\x31\xf5\x2e\x25\xb5\xae\xca\xba\x12\xe5\x82\x42\x81\xcf\ +\x07\x22\x84\x53\x85\x78\x06\x5f\xbb\x42\xac\x5b\x47\xc9\x88\x2a\ +\x31\x69\x28\x9d\x12\x0f\x03\x39\x83\x91\x4c\x4e\xac\xa7\xde\x26\ +\x90\xd7\x89\xb3\xae\x0c\x00\x89\xd6\x47\xd6\x53\x6f\x83\xac\x26\ +\x4a\x8f\x22\x23\x9d\x40\xda\xc0\x55\xc3\xea\x23\xeb\x51\xe4\x67\ +\xaa\x43\x49\x20\x5b\x01\x2c\xbc\x42\x0c\x52\x4d\x18\x64\x43\x23\ +\xc4\x46\x42\x6b\x1d\x45\xd4\x5b\x9c\xa2\x50\xab\x46\x94\x75\x36\ +\xd0\x18\x14\x7f\xce\x72\xa8\x65\x66\xda\x2a\x42\x2a\x45\x7e\xe0\ +\x13\xed\x6e\x85\x40\x0d\x5a\xbd\x89\x32\x3e\x39\x1a\x32\x7f\x64\ +\xa9\xe3\xda\x85\x54\x20\x93\xaa\xd2\xa9\xac\xf3\x23\x2b\x6c\xcd\ +\x89\xa5\x4c\x6b\x4e\xea\x26\x40\x2c\x8a\x75\xdb\xf2\xd8\x56\x43\ +\xc0\xa7\x12\xc3\x17\x20\x85\xa8\x82\xb9\xa0\x32\x02\x24\xea\x54\ +\x6d\x8d\x92\x18\x49\xf5\xae\xf3\xde\x22\xc5\x72\x68\x4e\xc2\x22\ +\x64\x64\x23\x0e\x56\x5c\xeb\x4c\x8d\x67\x10\xcf\x19\x6c\x24\x77\ +\x10\xcf\x54\x43\xf1\xc8\x20\x3c\x83\xdf\x3e\xf3\x93\x7f\x88\xf7\ +\x86\xa8\x77\xcc\xcf\xd9\x2e\x41\x50\x62\x66\xd9\x4a\x2b\x43\x55\ +\x16\x36\xe0\xef\x20\xde\x1b\xea\x19\xca\x80\x5c\x6c\x1b\x8d\xeb\ +\x9b\xe4\x57\xa7\x0e\x4a\x49\xad\x0b\xd9\x0b\x84\x22\xdf\xa1\x8a\ +\x2b\x43\x6c\xfa\xda\x22\x0c\xd6\x3f\x59\x19\xf1\x83\xa2\xca\x10\ +\xb2\x23\x51\x65\xb0\x91\x98\x54\x22\xed\xc8\xa2\x64\x14\x52\x65\ +\x13\x28\x9e\xc5\x7c\x5d\x68\x71\x8b\x78\xd3\xa9\xda\xdd\xb2\xa8\ +\x04\xb2\x8c\xb4\xd1\x8c\xa4\x32\xd8\x88\x32\x4c\x00\x07\xd2\x5a\ +\x17\x53\x06\xa8\x8c\x60\xf1\xc8\xd7\x3a\x0a\x18\x2c\xa4\x5f\xd7\ +\x14\x3e\x44\x64\x64\x03\xbd\x07\xb1\x35\x27\xa1\xb9\x89\xc9\x4b\ +\x74\xe1\xc4\x90\xe2\x8b\x5b\x06\x04\xea\x7b\x13\x85\x57\xd1\xc0\ +\xd4\x1b\x12\x26\x42\x73\x13\x96\x3a\xb4\x3b\x53\x03\x9f\x9f\xd7\ +\x89\xaf\xd0\x88\xb9\x20\xd8\x72\x1d\x13\xa8\xf8\x20\x59\x10\x54\ +\x19\xfc\x19\xb1\x4e\xd9\x6a\x57\x8a\x88\xee\x9f\x15\x83\x2d\x7b\ +\xb3\x81\x42\x59\xdc\x60\x87\x81\x78\x54\x48\xdb\x51\xec\xaa\xc5\ +\x03\x5f\x5d\x4f\x08\x92\x12\x53\xa4\xea\x83\x45\x58\xf1\x74\x2b\ +\xd4\xd0\x88\xd5\x4d\xe2\x32\x32\xa8\xa6\x53\xa8\x82\x80\x74\x20\ +\x9b\xd6\x40\x96\x28\x8a\x60\xed\x68\x4b\xf1\xd9\xe4\x02\x59\x88\ +\x19\x02\xcd\xf1\xd9\x1a\x65\x28\x08\x94\x7b\x87\xfa\x0c\x44\xc0\ +\x08\x6b\xd1\x5a\x67\xbc\x13\xc3\xbd\x37\xa4\x86\x0d\x5d\xb9\x09\ +\x54\x18\x62\x35\x6c\x19\x50\x6b\x43\x99\xd2\x2d\xf1\x5e\x10\x45\ +\xd4\xd9\x10\x28\xc9\xb7\x68\xcf\xe0\xb3\xa7\x44\xf9\x3a\x8a\x34\ +\x06\xc5\xd7\xe4\xc7\x4e\x08\x75\xaa\x94\xa3\xb4\x8c\x51\x2c\xd6\ +\x74\x8a\x39\x55\x51\x3b\xaa\xeb\x9c\xc0\x6a\x58\xa8\x8c\xea\x28\ +\x12\x8f\xb0\x21\xf5\x36\x04\xec\xe4\xdb\x48\xcf\x8e\x10\xea\x6d\ +\x03\x21\x44\xbc\x8d\x06\x53\x86\x50\x1b\xad\x73\x3f\x28\x45\x46\ +\x7e\x72\x52\x44\xc0\xc5\x4b\x4b\x68\x1b\x2d\xeb\xb4\x0e\xe4\xc6\ +\xdb\x50\xee\x4d\x88\x9c\xa1\xce\x73\xb7\x5a\x48\x91\x3a\x37\x61\ +\x02\xb9\xb7\x45\xb3\x4e\x54\x46\x4d\x27\x9c\x2d\x19\x35\x25\x27\ +\xd9\x22\x6c\x2b\xb0\xa9\xdd\xc6\x27\x19\xb4\x2f\xeb\x2c\xda\x05\ +\x65\x61\xdd\xce\x05\xb2\x48\x83\xa5\x1c\xe5\x3f\xa9\x32\xcc\x2c\ +\xd0\x6c\x2e\xfe\x0f\x55\x12\xf0\x65\x6f\x36\x85\x2a\xa9\xd5\xd1\ +\x34\x73\x32\x52\x83\x55\xa0\xff\x53\xa0\x56\x77\x87\xcc\x25\x78\ +\x02\x28\x45\xb6\xaf\xef\x93\xa0\x48\x94\x75\x75\xcd\x74\x51\x8a\ +\xa6\x22\x0b\x12\x65\xdd\xce\x33\xd8\x26\xad\xcb\x52\x5a\xda\x1d\ +\x27\x23\x51\xd6\xd5\xbd\xca\x99\xd4\xba\x6d\x07\x6a\xbd\x13\x69\ +\x5f\x20\xbb\xe3\x58\xb7\x33\x7d\x9d\x38\x45\x49\x7b\xdd\x6a\xe0\ +\xeb\x5d\x6a\xa6\xde\x4a\x5c\x3d\x69\x81\xf6\x0c\xa1\x95\x1a\x90\ +\xaa\xbc\x93\x36\x4a\xdc\x85\xbd\x33\x5c\x10\x6c\xca\xa0\x37\x0b\ +\xfb\x74\x4e\x6c\x2e\x8a\x66\x5f\x19\x9a\xf6\x8d\x16\x33\x58\xbf\ +\xa3\x65\xd0\x09\xa4\x8d\x54\x7e\x06\xe1\x19\x2c\xb2\x9a\xb0\xa9\ +\x36\xd4\xd7\xa9\xc2\x6e\xc9\x8f\xdd\x66\x62\x72\x79\x06\x78\x21\ +\x66\x08\x30\xf5\x36\x15\xd5\x04\x2c\xc2\xee\xec\x24\x3f\xb4\x5a\ +\x5a\x65\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\x0a\xa4\x40\ +\xc1\xe4\xb0\x53\x8f\xb5\x4f\xf3\x16\x72\xff\x91\xed\x5b\xc7\x76\ +\x2d\x2d\x5b\xb3\x4a\x52\x19\xb2\x35\x9d\x76\x56\x1b\x6d\xdb\x36\ +\x9c\x9c\x4d\xcf\x60\x22\x32\x2a\x24\x4b\xcb\xea\xfd\x12\x36\xa0\ +\x0c\xed\xba\x5b\x8b\x4b\x4b\x4d\x82\x5f\xe9\xcb\xba\xff\x01\x57\ +\x19\x2b\xd0\x52\x3a\x09\xbb\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x0d\x0b\xb0\x87\ +\x00\x66\ +\x00\x69\x00\x74\x00\x74\x00\x69\x00\x6e\x00\x67\ +\x00\x1d\ +\x04\x58\xf4\x67\ +\x00\x68\ +\x00\x6f\x00\x72\x00\x69\x00\x7a\x00\x6f\x00\x6e\x00\x74\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\ +\x00\x65\x00\x72\x00\x5f\x00\x73\x00\x68\x00\x6f\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x17\ +\x01\x66\xef\x27\ +\x00\x68\ +\x00\x6f\x00\x72\x00\x69\x00\x7a\x00\x6f\x00\x6e\x00\x74\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\ +\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x04\x77\x9e\xe7\ +\x00\x77\ +\x00\x61\x00\x72\x00\x6e\x00\x69\x00\x6e\x00\x67\x00\x5f\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x15\ +\x01\x36\xf0\xe7\ +\x00\x76\ +\x00\x65\x00\x72\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\x00\x65\x00\x72\ +\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1b\ +\x04\xf8\xcb\xe7\ +\x00\x76\ +\x00\x65\x00\x72\x00\x74\x00\x69\x00\x63\x00\x61\x00\x6c\x00\x5f\x00\x73\x00\x70\x00\x6c\x00\x69\x00\x74\x00\x74\x00\x65\x00\x72\ +\x00\x5f\x00\x73\x00\x68\x00\x6f\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ +\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x3a\x7c\ +\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x16\x7f\ +\x00\x00\x00\x14\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00\x2b\xf7\ +\x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00\x54\x77\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/pyrs/icons/vertical_splitter_short.png b/pyrs/icons/vertical_splitter_short.png new file mode 100644 index 000000000..828631197 Binary files /dev/null and b/pyrs/icons/vertical_splitter_short.png differ diff --git a/pyrs/interface/advpeakfitdialog.py b/pyrs/interface/advpeakfitdialog.py index a7fee1a2b..4bbde50f7 100644 --- a/pyrs/interface/advpeakfitdialog.py +++ b/pyrs/interface/advpeakfitdialog.py @@ -1,9 +1,10 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from pyrs.utilities import load_ui from qtpy.QtCore import Signal from qtpy.QtWidgets import QDialog, QMainWindow import os -import gui_helper +from . import gui_helper class SmartPeakFitControlDialog(QDialog): diff --git a/pyrs/interface/designer/manualreductionwindow.ui b/pyrs/interface/designer/manualreductionwindow.ui index 68812274c..abbedf588 100644 --- a/pyrs/interface/designer/manualreductionwindow.ui +++ b/pyrs/interface/designer/manualreductionwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1422 - 857 + 1308 + 840 @@ -19,38 +19,42 @@ - + + + + Run Number + + + + Calibration File - - + + - Output DIR + Vanadium - - + + - Browse + Mask File - - - - - 0 - 0 - + + + + Browse - + @@ -60,29 +64,25 @@ - - + + - Run Number + Output DIR - - + + - Mask File + Default - - - - - - + + true - - + + Browse @@ -107,7 +107,7 @@ - + Default @@ -118,7 +118,7 @@ - + Default @@ -127,18 +127,49 @@ - - + + + + + 0 + 0 + + + + + + - Default + Browse - + + + + + + Browse + + + + + + true + + <html><head/><body><p>Vanadium run number (integer) for detector pixels' efficiecy correction.</p></body></html> + - - + + + + + + + + + Browse @@ -147,132 +178,28 @@ - - - Calibration - - - - - - - 0 - 0 - - - - - 120 - 16777215 - - - - - - - - - 9 - - - - Arm Length - - - - - - - - 9 - - - - Shift X - - - - - - - - 0 - 0 - - - - - 120 - 16777215 - - - - - - - - - 9 - - - - Shift Y - - - - - - - - 9 - - - - 2theta Zero - - - - - - - - 0 - 0 - - - - - 120 - 16777215 - - - - - - - - - 0 - 0 - - - - - 120 - 16777215 - - - - - - + + + + + + 75 + true + + + + Split (sub run) / Convert (powder pattern) / Save + + + + 0 - + Simple Reduction @@ -280,45 +207,7 @@ - - - - - Run Numbers - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Experiment numbers in numpy convention:</p><p>a, b:c, d:e, f, ...</p></body></html> - - - all - - - - - - - Reduce - - - - - - - Save - - - - + @@ -347,6 +236,157 @@ + + + Powder Pattern + + + + + + Calibration + + + + + + + 0 + 0 + + + + + 120 + 16777215 + + + + + + + + + 0 + 0 + + + + + 120 + 16777215 + + + + + + + + + 9 + + + + Shift X + + + + + + + + 0 + 0 + + + + + 120 + 16777215 + + + + + + + + false + + + Convert to Powder Pattern + + + + + + + + 9 + + + + Arm Length + + + + + + + + 0 + 0 + + + + + 120 + 16777215 + + + + + + + + + 9 + + + + 2theta Zero + + + + + + + + 9 + + + + Shift Y + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Slice and Reduce @@ -740,24 +780,17 @@ - - + + + Qt::Horizontal + + - 100 - 16777215 + 40 + 20 - - Plot Option - - - - - - - <html><head/><body><p>For chopped run only!</p></body></html> - - + @@ -777,8 +810,8 @@ 0 0 - 1422 - 22 + 1308 + 25 diff --git a/pyrs/interface/designer/peakfitwindow.ui b/pyrs/interface/designer/peakfitwindow.ui index c1c779cba..f28b8be31 100644 --- a/pyrs/interface/designer/peakfitwindow.ui +++ b/pyrs/interface/designer/peakfitwindow.ui @@ -6,7 +6,7 @@ 0 0 - 1363 + 1527 966 @@ -14,37 +14,28 @@ MainWindow - + - + Qt::Horizontal - 6 + 7 - - true - - + Qt::Vertical - - true - - - - true - + Qt::Vertical true - - true + + 10 @@ -123,361 +114,346 @@ - - - - QLayout::SetMaximumSize + + + 1 + + + Qt::Horizontal + + + 7 + + + + + 0 + 0 + - - - - - - - 300 - 270 - + + + 0 + 300 + + + + + 1000 + 300 + + + + Peak Ranges + + + + + + + + Load ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Save ... + + + + + + + + + + 300 + 200 + + + + + 700 + 200 + + + + Qt::CustomContextMenu + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + + x_left - - - 300 - 270 - + + + + x_right - - Peak Ranges + + + + Label - - - - - - - Load ... - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Save ... - - - - - - - - - - 280 - 200 - - - - - 280 - 200 - - - - true - - - QAbstractItemView::ContiguousSelection - - - QAbstractItemView::SelectRows - - - true - - + + + + + + + + + + + + 0 + 0 + + + + Sub. Runs. + + + + + + + + + false + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 2 + + + Qt::Horizontal + + + + + - x_left + -1 + + + + + + + + + + + + true + + + + + + + + + false - - - x_right + 1 + + + + + + + false - - - Label + (ex: 1,2,3) - - - - - - - - - - - 0 - 200 - - - - - 16777215 - 250 - - - - 4 - - - Qt::Vertical - - - - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + 0 + 0 + + + + Fitting Functions + + - - - - 0 - 0 - - - - Sub. Runs. - - - - + + + + + <html><head/><body><p>Peak types</p><p><br/></p></body></html> + + + + PseudoVoigt + + + + + Gaussian + + + - + Voigt - - false + + + + + + + + Linear - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - 2 - - - Qt::Horizontal - - - - - - - -1 - - - - - - - + + - + Flat - - true + + + + Quadratic - - - - - - - - false - - - 1 - - - - - - - false - - - (ex: 1,2,3) - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 40 + 20 + + + + + + + + Fit Peak(s) + + + + - - - - 0 - 0 - - - - Fitting Functions - - - - - - - - <html><head/><body><p>Peak types</p><p><br/></p></body></html> - - - - PseudoVoigt - - - - - Gaussian - - - - - Voigt - - - - - - - - - Linear - - - - - Flat - - - - - Quadratic - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 40 - 20 - - - - - - - - Fit Peak(s) - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - Export CSV ... - - - - - - - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 120 + 0 + + + + + 120 + 16777215 + + + + Export CSV ... + + + + - - - - + + + + @@ -549,409 +525,450 @@ - + Qt::Vertical - - 10 - - - false - - - + + - - - - - Qt::Vertical - - - - - - - false - - - - 0 - 0 - - - - - 0 - 75 - - - - - 16777215 - 75 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - X-axis - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Ignored - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - Y-axis - - - - - - - - - - - - - - d0 - - - - - - - - 70 - 0 - - - - - 70 - 16777215 - - - - - - - - microstrength - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Single peak fit</p></body></html> - - - - - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - false - - - - 0 - 0 - - - - - 0 - 75 - - - - - 16777215 - 75 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - X-axis - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Ignored - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - Y-axis - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Ignored - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - Z-axis - - - - - - - - - - - - - - d0 - - - - - - - - 70 - 0 - - - - - 70 - 16777215 - - - - - - - - microstrength - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - List sub-runs - - - - - - - - - - - - - - - - - ex: 1,3:5,7 - - - - - - - - - - - - - + + + false + + + + 0 + 0 + + + + + 0 + 75 + + + + + 16777215 + 75 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + X-axis + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + Y-axis + + + + + + + + + + + + + + + + + d0 + + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + + + + + microstrength + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Single peak fit</p></body></html> + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + false + + + + 0 + 0 + + + + + 0 + 110 + + + + + 16777215 + 110 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Contour + + + false + + + + + + + 3D line + + + false + + + + + + + 3D scatter + + + true + + + + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + X-axis + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + Y-axis + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + Z-axis + + + + + + + + + + + + + + + + + d0 + + + + + + + + 70 + 0 + + + + + 70 + 16777215 + + + + + + + + microstrength + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + List sub-runs + + + + + + + + + + + + + + + + + ex: 1,3:5,7 + + + + + + + @@ -965,8 +982,8 @@ 0 0 - 1363 - 22 + 1527 + 23 @@ -1033,5 +1050,25 @@ - + + + peak_range_table + customContextMenuRequested(QPoint) + MainWindow + peak_range_table_right_click() + + + 162 + 519 + + + 763 + 482 + + + + + + peak_range_table_right_click() + diff --git a/pyrs/interface/dialogs.py b/pyrs/interface/dialogs.py index af3b9eafb..1221a37a3 100644 --- a/pyrs/interface/dialogs.py +++ b/pyrs/interface/dialogs.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from pyrs.utilities import load_ui from qtpy.QtCore import Signal from qtpy.QtWidgets import QDialog, QMainWindow @@ -6,7 +7,7 @@ from pyrs.interface.ui.rstables import GridAlignmentTable, GridsStatisticsTable, ParamValueGridTable,\ ParamValueMapAnalysisTable, MismatchedGridsTable, StrainStressValueTable -import gui_helper +from . import gui_helper from pyrs.utilities import checkdatatypes import os diff --git a/pyrs/interface/gui_helper.py b/pyrs/interface/gui_helper.py index da76270c2..c42e4fad1 100644 --- a/pyrs/interface/gui_helper.py +++ b/pyrs/interface/gui_helper.py @@ -1,6 +1,7 @@ # a collection of helper methdos for GUI +from __future__ import (absolute_import, division, print_function) # python3 compatibility from pyrs.utilities import checkdatatypes -from qtpy.QtWidgets import QLineEdit, QFileDialog, QMessageBox, QVBoxLayout +from qtpy.QtWidgets import QLineEdit, QFileDialog, QMessageBox, QVBoxLayout, QComboBox def browse_dir(parent, caption, default_dir): @@ -117,6 +118,32 @@ def get_save_file_name(parent, dir_name, caption, file_filter): return file_name, file_filter +def parse_combo_box(combo_box, data_type): + """ + + Parameters + ---------- + combo_box + data_type + + Returns + ------- + + """ + assert isinstance(combo_box, QComboBox), 'Method parse_combo_box expects 0-th input {} to be a ' \ + 'QComboBox instance but not a {}' \ + ''.format(combo_box, type(combo_box)) + assert isinstance(data_type, type), 'Method parse_line_edit expects 1-st input {} to be a type ' \ + 'but not a {}'.format(data_type, type(data_type)) + + # parse + input_str = str(combo_box.currentText()).strip() + # cast to specified type + return_value = data_type(input_str) + + return return_value + + def parse_line_edit(line_edit, data_type, throw_if_blank=False, edit_name=None, default=None): """ Parse a LineEdit @@ -295,7 +322,7 @@ def get_boolean_from_dialog(window_title, message): """ def msgbtn(i): # debugging output - print "Button pressed is:", i.text() + print("Button pressed is:", i.text()) message_box = QMessageBox() message_box.setIcon(QMessageBox.Information) diff --git a/pyrs/interface/manual_reduction/event_handler.py b/pyrs/interface/manual_reduction/event_handler.py index d37ec2bcc..b9fc43664 100644 --- a/pyrs/interface/manual_reduction/event_handler.py +++ b/pyrs/interface/manual_reduction/event_handler.py @@ -1,76 +1,492 @@ import os +from qtpy.QtCore import Qt +from qtpy.QtWidgets import QApplication +from pyrs.interface.gui_helper import pop_message, browse_file, browse_dir, parse_combo_box +from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback +from pyrs.interface.manual_reduction.pyrs_api import ReductionController +from pyrs.dataobjects.constants import HidraConstants +from pyrs.utilities import get_default_output_dir, get_ipts_dir, get_nexus_file -from pyrs.interface.gui_helper import browse_file -from pyrs.interface.gui_helper import pop_message -from pyrs.interface.manual_reduction.load_project_file import LoadProjectFile +class EventHandler(object): + """Class to handle the event sent from UI widget + """ + def __init__(self, parent): + """Init -class EventHandler: - - def __init__(self, parent=None): + Parameters + ---------- + parent : qtpy.QtWidgets.QMainWindow + GUI main Window + """ self.parent = parent + self.ui = self.parent.ui + + # controller + self._controller = ReductionController() + self.__last_run_number = '' - # project ID (current) - self._project_data_id = None + @property + def controller(self): + return self._controller + + def _current_runnumber(self): + run_number = self.ui.spinBox_runNumber.text().strip() + if len(run_number) == 0: + return None + else: + return int(run_number) + + def _set_sub_run_numbers(self, sub_runs): + """Set sub run numbers to (1) Table and (2) Combo box + + Parameters + ---------- + sub_runs + + Returns + ------- - def browse_nexus_file(self): """ - allow users to browse for a nexus file to convert to project file + self.ui.comboBox_sub_runs.clear() + + for sub_run in sorted(sub_runs): + self.ui.comboBox_sub_runs.addItem('{}'.format(sub_run)) + + # TEST: Use menu bar to load a file + def browse_load_nexus(self): + """Allow users to manually determine a NeXus file by browsing for a nexus file to convert to project file + + Returns + ------- + """ + # default is current working directory + default_dir = self._controller.working_dir + try: + run_number = self._current_runnumber() + if run_number is not None: + default_dir = get_ipts_dir(run_number) + nexus_file = 'HB2B_{}.nxs.h5'.format(run_number) + if os.path.exists(os.path.join(default_dir, 'nexus', nexus_file)): + default_dir = os.path.join(default_dir, 'nexus') + del nexus_file + except RuntimeError as e: + print('While looking for {}:'.format(run_number)) + print(e) + + # If specified information is not sufficient, browse nexus_file = browse_file(self.parent, caption='Select a NeXus file', - default_dir=self.parent._core.working_dir, - file_filter='NeXus (*.h5)', - file_list=False, - save_file=False) + default_dir=default_dir, + file_filter='NeXus (*.nxs.h5)', + file_list=False, save_file=False) + + # Return if it is cancelled + if nexus_file is None: + return + + # Load Nexus + self._controller.load_nexus_file(nexus_file) + + # sub runs + sub_runs = self._controller.get_sub_runs() + + # set sub runs to (1) Table and (2) Combo box + self._set_sub_run_numbers(sub_runs) + + def browse_load_hidra(self): + """Allow users to browse for a HiDRA project file + + Returns + ------- + + """ + # default is current working directory + default_dir = self._controller.working_dir + + # get default output directory for the run + try: + run_number = self._current_runnumber() + if run_number is not None: + default_dir = get_default_output_dir(self._current_runnumber()) + except RuntimeError as e: + print('While looking for {}:'.format(run_number)) + print(e) + + # Browse + hidra_file = browse_file(self.parent, + caption='Select a HIDRA project file', + default_dir=default_dir, + file_filter='HiDRA project (*.h5)', + file_list=False, save_file=False) + if not hidra_file: + return + + # Load Nexus + try: + self._controller.load_project_file(hidra_file, load_counts=False, load_pattern=True) + except RuntimeError as run_err: + pop_message(self.parent, 'Loading {} failed.\nTry to load diffraction only!'.format(hidra_file), + detailed_message='{}'.format(run_err), + message_type='error') + return - self.convert_to_project_file(nexus_file) + # sub runs + sub_runs = self._controller.get_sub_runs() - @staticmethod - def convert_to_project_file(nexus_filename): + # set sub runs to (1) Table and (2) Combo box + self._set_sub_run_numbers(sub_runs) + # Set to first sub run and plot + self.ui.comboBox_sub_runs.setCurrentIndex(0) + # Fill in self.ui.frame_subRunInfoTable + meta_data_array = self._controller.get_sample_logs_values([HidraConstants.SUB_RUNS, HidraConstants.TWO_THETA]) + self.ui.rawDataTable.add_subruns_info(meta_data_array, clear_table=True) + + def browse_calibration_file(self): + calibration_file = browse_file(self.parent, caption='Choose and set up the calibration file', + default_dir=self._controller.get_default_calibration_dir(), + file_filter='hdf5 (*hdf)', file_list=False, save_file=False) + if calibration_file is None or calibration_file == '': + # operation canceled + return + + # set to the browser + self.ui.lineEdit_calibrationFile.setText(calibration_file) + + def browse_idf(self): """ - Convert nexus_filename into a project file - :param nexus_filename: + + Returns + ------- + """ - # TODO - Implement! + idf_name = browse_file(self.parent, 'Instrument definition file', os.getcwd(), + 'Text (*.txt);;XML (*.xml)', False, False) + if len(idf_name) == 0: + return # user cancels operation + else: + self.ui.lineEdit_idfName.setText(idf_name) + # END-IF + def browse_mask_file(self): + """Browse masking file + + Returns + ------- + + """ + mask_file_name = browse_file(self.parent, 'Hidra Mask File', self._controller.get_default_mask_dir(), + 'Mantid Mask(*.xml)', False, False) + self.ui.lineEdit_maskFile.setText(mask_file_name) return - @staticmethod - def browse_project_file(parent): - """Browse Hidra project file in h5 format + def browse_output_dir(self): + """Browse output directory - Parameters - ---------- - parent + Returns + ------- + + """ + output_dir = browse_dir(self.parent, caption='Output directory for reduced data', + default_dir='/HFIR/HB2B/') + if output_dir != '': + self.ui.lineEdit_outputDirectory.setText(output_dir) + + def browse_vanadium_file(self): + """Browse vanadium HiDRA project file and set to line edit + + Returns + ------- + + """ + vanadium_file_name = browse_file(self.parent, 'HiDRA Vanadium File', self._controller.get_default_mask_dir(), + 'HiDRA project(*.h5)', False, False) + + if vanadium_file_name is not None and vanadium_file_name != '': + self.ui.lineEdit_vanRunNumber.setText(vanadium_file_name) + + def plot_detector_counts(self): + """ Returns ------- - str or None - project file name or None for user's canceling the browse operation """ - project_h5_name = browse_file(parent, - 'HIDRA Project File', - os.getcwd(), - file_filter='*.hdf5;;*.h5', - file_list=False, - save_file=False) + # Get valid sub run + sub_run = parse_combo_box(self.ui.comboBox_sub_runs, int) + if sub_run is None: + return - return project_h5_name + # Get counts + try: + counts_matrix = self._controller.get_detector_counts(sub_run, output_matrix=True) + except RuntimeError as run_err: + pop_message(self.parent, 'Unable to plot sub run {} counts on detector view'.format(sub_run), + str(run_err), message_type='error') + return + + # Plot + # set information + det_2theta = self._controller.get_sample_log_value(HidraConstants.TWO_THETA, sub_run) + info = 'sub-run: {}, 2theta = {}'.format(sub_run, det_2theta) + + # mask ID is not None + # if mask_id is not None: + # # Get mask in array and do a AND operation to detector counts (array) + # mask_array = self._core.reduction_service.get_mask_array(self._curr_project_name, mask_id) + # detector_counts_array *= mask_array + # info += ', mask ID = {}'.format(mask_id) + + # Set information + self.ui.lineEdit_detViewInfo.setText(info) + + # Plot: + self.ui.graphicsView_detectorView.plot_detector_view(counts_matrix, (sub_run, None)) + + def plot_powder_pattern(self): + """ + + Returns + ------- - @staticmethod - def load_project_file(parent, file_name): + """ + # Get valid sub run + sub_run = parse_combo_box(self.ui.comboBox_sub_runs, int) + print('[TEST-OUTPUT] sub run = {}, type = {}'.format(sub_run, type(sub_run))) + if sub_run is None: + return + # Get diffraction pattern try: - o_load = LoadProjectFile(parent=parent) - project_data_id = o_load.load_hydra_file(file_name) + pattern = self._controller.get_powder_pattern(sub_run) except RuntimeError as run_err: - pop_message(parent, - 'Failed to load project file {}: {}'.format(file_name, run_err), - None, 'error') - project_data_id = None + pop_message(self.parent, 'Unable to plot sub run {} histogram/powder pattern'.format(sub_run), + str(run_err), message_type='error') + return + + # Get detector 2theta of this sub run + det_2theta = self._controller.get_sample_log_value(HidraConstants.TWO_THETA, sub_run) + info = 'sub-run: {}, 2theta = {}'.format(sub_run, det_2theta) + + # Plot + self.ui.graphicsView_1DPlot.plot_diffraction(pattern[0], pattern[1], '2theta', 'intensity', + line_label=info, keep_prev=False) + + def _set_sub_runs(self): + """ set the sub runs to comboBox_sub_runs + :return: + """ + # sub_runs = self._core.reduction_manager.get_sub_runs(data_id) + # + # self.ui.comboBox_sub_runs.clear() + # for sub_run in sorted(sub_runs): + # self.ui.comboBox_sub_runs.addItem('{:04}'.format(sub_run)) + # + # + # """ + # """ + # # + sub_runs = self._controller.get_sub_runs() + sub_runs.sort() + + # set sub runs: lock and release + self._mutexPlotRuns = True + # clear and set + self.ui.comboBox_sub_runs.clear() + for sub_run in sub_runs: + self.ui.comboBox_sub_runs.addItem('{:04}'.format(sub_run)) + self._mutexPlotRuns = False + + return + + def _setup_plot_runs(self, append_mode, run_number_list): + """ set the runs (or sliced runs to plot) + :param append_mode: + :param run_number_list: + :return: + """ + # checkdatatypes.check_list('Run numbers', run_number_list) + + # non-append mode + self._plot_run_numbers_mutex = True + if not append_mode: + self.ui.comboBox_runs.clear() + + # add run numbers + for run_number in run_number_list: + self.ui.comboBox_runs.addItem('{}'.format(run_number)) + + # open + self._plot_run_numbers_mutex = False + + # if append-mode, then set to first run + if append_mode: + self.ui.comboBox_runs.setCurrentIndex(0) + + return + + def _setup_plot_selection(self, append_mode, item_list): + """ + set up the combobox to select items to plot + :param append_mode: + :param item_list: + :return: + """ + # checkdatatypes.check_bool_variable('Flag for appending the items to current combo-box or from start', + # append_mode) + # checkdatatypes.check_list('Combo-box item list', item_list) + + # turn on mutex lock + self._plot_selection_mutex = True + if append_mode is False: + self.ui.comboBox_sampleLogNames.clear() + for item in item_list: + self.ui.comboBox_sampleLogNames.addItem(item) + if append_mode is False: + self.ui.comboBox_sampleLogNames.setCurrentIndex(0) + self._plot_selection_mutex = False + return + + def manual_reduce_run(self): + """ + + (simply) reduce a list of runs in same experiment in a batch + + Returns + ------- + + """ + # Get run number + run_number = self._current_runnumber() + if not run_number: + return # no need to update + + # Files names: NeXus, (output) project, mask, calibration + nexus_file = get_nexus_file(run_number) + project_file = str(self.ui.lineEdit_outputDirectory.text().strip()) + # mask file + mask_file = str(self.ui.lineEdit_maskFile.text().strip()) + if mask_file == '': + mask_file = None + # calibration file + calibration_file = str(self.ui.lineEdit_calibrationFile.text().strip()) + if calibration_file == '': + calibration_file = None + # vanadium file + vanadium_file = str(self.ui.lineEdit_vanRunNumber.text().strip()) + if vanadium_file == '': + vanadium_file = None + + # Start task + if True: + # single thread: + hidra_ws = self._controller.reduce_hidra_workflow(nexus_file, project_file, + self.ui.progressBar, mask=mask_file, + calibration=calibration_file, + vanadium_file=vanadium_file) + + # Update table + # TODO - Need to fill the table! + sub_runs = list(hidra_ws.get_sub_runs()) + # for sub_run in sub_runs: + # self.ui.rawDataTable.update_reduction_state(sub_run, True) + + # Set the sub runs combo box + self._set_sub_run_numbers(sub_runs) + else: - print('Loaded {} to {}'.format(file_name, project_data_id)) + task = BlockingAsyncTaskWithCallback(self._controller.reduce_hidra_workflow, + args=(nexus_file, project_file, self.ui.progressBar), + kwargs={'mask': mask_file, 'calibration': calibration_file}, + blocking_cb=QApplication.processEvents) + # TODO - catch RuntimeError! ... + # FIXME - check output directory + task.start() + + return + + def save_project(self): + self._controller.save_project() - return project_data_id + def set_mask_file_widgets(self, state): + """Set the default value of HB2B mask XML + + Parameters + ---------- + state : Qt.State + Qt state as unchecked or checked + + Returns + ------- + None + + """ + + if state != Qt.Unchecked: + self.ui.lineEdit_maskFile.setText(self._controller.get_default_mask_dir() + 'HB2B_MASK_Latest.xml') + self.ui.lineEdit_maskFile.setEnabled(state == Qt.Unchecked) + self.ui.pushButton_browseMaskFile.setEnabled(state == Qt.Unchecked) + + def set_calibration_file_widgets(self, state): + """Set the default value of HB2B geometry calibration file + + Parameters + ---------- + state : Qt.State + Qt state as unchecked or checked + + Returns + ------- + + """ + if state != Qt.Unchecked: + self.ui.lineEdit_calibrationFile.setText(self._controller.get_default_calibration_dir() + + 'HB2B_Latest.json') + self.ui.lineEdit_calibrationFile.setEnabled(state == Qt.Unchecked) + self.ui.pushButton_browseCalibrationFile.setEnabled(state == Qt.Unchecked) + + def set_output_dir_widgets(self, state): + """Set the default value of directory for output files + + Parameters + ---------- + state : Qt.State + Qt state as unchecked or checked + + Returns + ------- + None + + """ + if state != Qt.Unchecked: + self.update_run_changed(self._current_runnumber()) + self.ui.lineEdit_outputDirectory.setEnabled(state == Qt.Unchecked) + self.ui.pushButton_browseOutputDirectory.setEnabled(state == Qt.Unchecked) + + def update_run_changed(self, run_number): + """Update widgets including output directory and etc due to change of run number + + Parameters + ---------- + run_number : int + run number + + Returns + ------- + None + + """ + # don't do anything if the run number didn't change + if run_number == self.__last_run_number: + return + + # new default + try: + project_dir = get_default_output_dir(run_number) + # set to line edit + self.ui.lineEdit_outputDirectory.setText(project_dir) + self.__last_run_number = run_number + except RuntimeError as e: + print('Failed to find project directory for {}'.format(run_number)) + print(e) diff --git a/pyrs/interface/manual_reduction/load_project_file.py b/pyrs/interface/manual_reduction/load_project_file.py deleted file mode 100644 index a17706978..000000000 --- a/pyrs/interface/manual_reduction/load_project_file.py +++ /dev/null @@ -1,68 +0,0 @@ -import os - -from pyrs.interface.gui_helper import pop_message -from pyrs.dataobjects import HidraConstants - - -class LoadProjectFile: - - def __init__(self, parent=None): - self.parent = parent - - def load_hydra_file(self, project_file_name): - """Load Hidra project file to the core - - Parameters - ---------- - project_file_name - - Returns - ------- - str - project ID to refer - - """ - # Load data file - project_name = os.path.basename(project_file_name).split('.')[0] - try: - self.parent._hydra_workspace = self.parent._core.load_hidra_project(project_file_name, - project_name=project_name, - load_detector_counts=True, - load_diffraction=True) - except (KeyError, RuntimeError, IOError) as load_err: - self.parent._hydra_workspace = None - pop_message(self.parent, 'Loading {} failed.\nTry to load diffraction only!'.format(project_file_name), - detailed_message='{}'.format(load_err), - message_type='error') - - # Load - try: - self.parent._hydra_workspace = self.parent._core.load_hidra_project(project_file_name, - project_name=project_name, - load_detector_counts=False, - load_diffraction=True) - except (KeyError, RuntimeError, IOError) as load_err: - self.parent._hydra_workspace = None - pop_message(self.parent, 'Loading {} failed.\nNothing is loaded'.format(project_file_name), - detailed_message='{}'.format(load_err), - message_type='error') - - return - - # Set value for the loaded project - self.parent._project_file_name = project_file_name - self.parent._project_data_id = project_name - - # Fill sub runs to self.ui.comboBox_sub_runs - self.parent._set_sub_runs() - - # Set to first sub run and plot - self.parent.ui.comboBox_sub_runs.setCurrentIndex(0) - - # Fill in self.ui.frame_subRunInfoTable - meta_data_array = self.parent._core.reduction_service.get_sample_logs_values(self.parent._project_data_id, - [HidraConstants.SUB_RUNS, - HidraConstants.TWO_THETA]) - self.parent.ui.rawDataTable.add_subruns_info(meta_data_array, clear_table=True) - - return self.parent._project_data_id diff --git a/pyrs/interface/manual_reduction/manualreductionwindow.py b/pyrs/interface/manual_reduction/manualreductionwindow.py index f3e901772..448066789 100644 --- a/pyrs/interface/manual_reduction/manualreductionwindow.py +++ b/pyrs/interface/manual_reduction/manualreductionwindow.py @@ -1,22 +1,12 @@ -from qtpy.QtWidgets import QMainWindow, QVBoxLayout, QApplication -from qtpy.QtCore import Qt +from qtpy.QtWidgets import QMainWindow, QVBoxLayout import os -from mantid.simpleapi import Logger, GetIPTS -from mantid.api import FileFinder -from pyrs.core.nexus_conversion import NeXusConvertingApp -from pyrs.core.powder_pattern import ReductionApp -from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback from pyrs.utilities import load_ui - -from pyrs.core.pyrscore import PyRsCore -from pyrs.utilities import calibration_file_io +from pyrs.interface.gui_helper import promote_widget from pyrs.interface.ui.diffdataviews import DetectorView, GeneralDiffDataView -from pyrs.interface import gui_helper -from pyrs.utilities import checkdatatypes -from pyrs.dataobjects import HidraConstants from pyrs.interface.ui import rstables from pyrs.interface.manual_reduction.event_handler import EventHandler + # TODO LIST - #84 - 1. UI: change segments to masks # TODO 2. UI: add solid angle input # TODO 3. UI: add option to use reduced data from input project file @@ -24,85 +14,11 @@ # TODO 5. Implement method to reduce data # TODO 6. Add parameters for reducing data -DEFAULT_MASK_DIRECTORY = '/HFIR/HB2B/shared/CALIBRATION/' -DEFAULT_CALIBRATION_DIRECTORY = DEFAULT_MASK_DIRECTORY - - -def _nexus_to_subscans(nexusfile, projectfile, mask_file_name, save_project_file, logger): - """Split raw data from NeXus file to sub runs/scans - Parameters - ---------- - nexusfile : str - HB2B event NeXus file's name - projectfile : str - Target HB2B HiDRA project file's name - mask_file_name : str - Mask file name; None for no mask - save_project_file : str - Project file to save to. None for not being saved - Returns - ------- - pyrs.core.workspaces.HidraWorkspace - Hidra workspace containing the raw counts and sample logs - """ - - if os.path.exists(projectfile): - logger.information('Removing existing projectfile {}'.format(projectfile)) - os.remove(projectfile) - - logger.notice('Creating subscans from {} into project file {}'.format(nexusfile, projectfile)) - converter = NeXusConvertingApp(nexusfile, mask_file_name) - hidra_ws = converter.convert() - - converter.save(projectfile) - - # save project file as an option - if save_project_file: - converter.save(projectfile) - - return hidra_ws - - -def _create_powder_patterns(hidra_workspace, instrument, calibration, mask, subruns, project_file_name, logger): - logger.notice('Adding powder patterns to project file {}'.format(hidra_workspace)) - - reducer = ReductionApp(False) - reducer.load_hidra_workspace(hidra_workspace) - - # TODO - Need to add some debugging output for user to feel good - reducer.reduce_data(instrument_file=instrument, - calibration_file=calibration, - mask=mask, - sub_runs=subruns) - - reducer.save_diffraction_data(project_file_name) - - -# TODO - Need to input a dictionary for HidraWorksapce generated -# TODO - Need to input the table to write the workspace result! -def reduce_hidra_workflow(nexus, outputdir, progressbar, subruns=list(), instrument=None, calibration=None, mask=None): - - project = os.path.basename(nexus).split('.')[0] + '.h5' - project = os.path.join(outputdir, project) - - logger = Logger('reduce_HB2B') - # process the data - progressbar.setVisible(True) - progressbar.setValue(0.) - hidra_ws = _nexus_to_subscans(nexus, project, mask, False, logger) - progressbar.setValue(50.) - # add powder patterns - _create_powder_patterns(hidra_ws, instrument, calibration, - None, subruns, project, logger) - progressbar.setValue(100.) - progressbar.setVisible(False) - class ManualReductionWindow(QMainWindow): """ GUI window for user to fit peaks """ - def __init__(self, parent): """ initialization @@ -129,27 +45,39 @@ def __init__(self, parent): # promote some widgets self._promote_widgets() - # set up the event handling + # Hide some not-yet-implemented + self.ui.tabWidget_reduceRuns.setTabEnabled(1, False) # User specified instrument parameter (shifts) + self.ui.tabWidget_reduceRuns.setTabEnabled(2, False) # advanced slicing tab + + # Event handler: handler must be set up after UI is loaded + self._event_handler = EventHandler(parent=self) + + # Mask file: check box and line edit + # set default self._mask_state(self.ui.checkBox_defaultMaskFile.checkState()) + # link event handling self.checkBox_defaultMaskFile.stateChanged.connect(self._mask_state) - self.ui.pushButton_browseMaskFile.clicked.connect(self.do_browse_mask_file) - - self.ui.spinBox_runNumber.valueChanged.connect(self._update_output_ipts) + self.ui.pushButton_browseMaskFile.clicked.connect(self.browse_mask_file) + self.ui.pushButton_browseVanadium.clicked.connect(self.browse_vanadium_file) + # Calibration file: check box and line edit self._calibration_state(self.ui.checkBox_defaultCalibrationFile.checkState()) self.checkBox_defaultCalibrationFile.stateChanged.connect(self._calibration_state) - self.ui.pushButton_browseCalibrationFile.clicked.connect(self.do_browse_calibration_file) + self.ui.pushButton_browseCalibrationFile.clicked.connect(self.browse_calibration_file) - self._output_state(self.ui.checkBox_defaultOutputDirectory.checkState()) + # Output directory: check box, spin box and line edit + # change of run number won't trigger the scan of NeXus file + self.ui.spinBox_runNumber.valueChanged.connect(self._event_handler.update_run_changed) + # self._output_state(self.ui.checkBox_defaultOutputDirectory.checkState()) self.checkBox_defaultOutputDirectory.stateChanged.connect(self._output_state) - self.ui.pushButton_browseOutputDirectory.clicked.connect(self.do_browse_output_dir) + self.ui.pushButton_browseOutputDirectory.clicked.connect(self.browse_output_dir) - self.ui.pushButton_batchReduction.clicked.connect(self.do_reduce_batch_runs) - self.ui.pushButton_saveProject.clicked.connect(self.do_save_project) - self.ui.pushButton_chopReduce.clicked.connect(self.do_chop_reduce_run) + # Push button for split, convert and save project file + self.ui.pushButton_splitConvertSaveProject.clicked.connect(self.split_convert_save_nexus) + # Next: self.ui.pushButton_chopReduce.clicked.connect(self.slice_nexus) - self.ui.pushButton_launchAdvSetupWindow.clicked.connect(self.do_launch_slice_setup) - self.ui.pushButton_plotDetView.clicked.connect(self.do_plot) + # Plotting + self.ui.pushButton_plotDetView.clicked.connect(self.plot_sub_runs) # radio button operation self.ui.radioButton_chopByTime.toggled.connect(self.event_change_slice_type) @@ -159,7 +87,8 @@ def __init__(self, parent): self.ui.actionQuit.triggered.connect(self.do_quit) self.ui.progressBar.setVisible(False) # event handling for combobox - # self.ui.comboBox_sub_runs.currentIndexChanged.connect(self.event_new_run_to_plot) + # TODO - Make this work properly + # self.ui.comboBox_sub_runs.currentIndexChanged.connect(self.event_different_sub_run_selected) # TODO - ASAP - Use these 2 buttons to enable/disable write access to configuration # actionEdit_Calibrations @@ -176,9 +105,8 @@ def __init__(self, parent): # load NeXus self.ui.actionLoad_nexus_file.triggered.connect(self.load_nexus_file) - self.ui.actionLoad_Image.triggered.connect(self.event_load_image) # Load project file (*.h5) - self.ui.actionLoad_Project_File.triggered.connect(self.do_load_project_h5) + self.ui.actionLoad_Project_File.triggered.connect(self.load_hidra_project_file) # init widgets self._init_widgets_setup() @@ -188,38 +116,13 @@ def __init__(self, parent): return - def _mask_state(self, state): - if state != Qt.Unchecked: - self.ui.lineEdit_maskFile.setText(DEFAULT_MASK_DIRECTORY+'HB2B_MASK_Latest.xml') - self.ui.lineEdit_maskFile.setEnabled(state == Qt.Unchecked) - self.ui.pushButton_browseMaskFile.setEnabled(state == Qt.Unchecked) - - def _calibration_state(self, state): - if state != Qt.Unchecked: - self.ui.lineEdit_calibrationFile.setText(DEFAULT_CALIBRATION_DIRECTORY+'HB2B_Latest.json') - self.ui.lineEdit_calibrationFile.setEnabled(state == Qt.Unchecked) - self.ui.pushButton_browseCalibrationFile.setEnabled(state == Qt.Unchecked) - - def _output_state(self, state): - if state != Qt.Unchecked: - self._update_output_ipts(self.ui.spinBox_runNumber.value()) - self.ui.lineEdit_outputDirectory.setEnabled(state == Qt.Unchecked) - self.ui.pushButton_browseOutputDirectory.setEnabled(state == Qt.Unchecked) - - def _update_output_ipts(self, run_number): - if self.ui.checkBox_defaultOutputDirectory.checkState() != Qt.Unchecked: - try: - ipts = GetIPTS(run_number) - project_file = ipts + 'shared/manualreduce/' - self.ui.lineEdit_outputDirectory.setText(project_file) - except RuntimeError: - pass - def _init_widgets_setup(self): """ init setup widgets :return: """ + self.ui.tabWidget_reduceRuns.setCurrentIndex(0) + # Event slicer type is set to log value as default self.ui.radioButton_chopByLogValue.setChecked(True) @@ -247,237 +150,129 @@ def _promote_widgets(self): # Sub run information table self.ui.rawDataTable = rstables.RawDataTable(self) - gui_helper.promote_widget(self.ui.frame_subRunInfoTable, self.ui.rawDataTable) + promote_widget(self.ui.frame_subRunInfoTable, self.ui.rawDataTable) return - # Menu event handler - - def load_nexus_file(self): - o_handler = EventHandler(parent=self) - o_handler.browse_nexus_file() + def _mask_state(self, state): + """Set the default value of HB2B mask XML - def do_load_project_h5(self): - """Browse and load Hidra project file + Parameters + ---------- + state : Qt.State + Qt state as unchecked or checked Returns ------- None """ - o_handler = EventHandler(parent=self) - hidra_project_file = o_handler.browse_project_file(self) - if hidra_project_file is not None: - o_handler.load_project_file(self, hidra_project_file) + self._event_handler.set_mask_file_widgets(state) + + def _calibration_state(self, state): + """Set the default value of HB2B geometry calibration file + + Parameters + ---------- + state : Qt.State + Qt state as unchecked or checked + + Returns + ------- - def do_browse_calibration_file(self): - """ Browse and set up calibration file - :return: """ - calibration_file = gui_helper.browse_file(self, caption='Choose and set up the calibration file', - default_dir=DEFAULT_CALIBRATION_DIRECTORY, - file_filter='hdf5 (*hdf)', file_list=False, save_file=False) - if calibration_file is None or calibration_file == '': - # operation canceled - return + self._event_handler.set_calibration_file_widgets(state) - # set to the browser - self.ui.lineEdit_calibratonFile.setText(calibration_file) + def _output_state(self, state): + """Set the default value of directory for output files - # set to core - self._core.reduction_service.set_calibration_file(calibration_file) + Parameters + ---------- + state : Qt.State + Qt state as unchecked or checked - return + Returns + ------- + None - def do_browse_set_idf(self): """ - Browse (optonally) and set instrument definition file - :return: + self._event_handler.set_output_dir_widgets(state) + + # Menu event handler + def load_nexus_file(self): + """Browse NeXus file + + Returns + ------- + """ - idf_name = str(self.ui.lineEdit_idfName.text()).strip() - if idf_name == '' or not os.path.exists(idf_name): - # browse IDF and set - idf_name = gui_helper.browse_file(self, 'Instrument definition file', os.getcwd(), - 'Text (*.txt);;XML (*.xml)', False, False) - if len(idf_name) == 0: - return # user cancels operation - else: - self.ui.lineEdit_idfName.setText(idf_name) - # END-IF - - # set - instrument = calibration_file_io.import_instrument_setup(idf_name) - self._core.reduction_service.set_instrument(instrument) + self._event_handler.browse_load_nexus() - return + def load_hidra_project_file(self): + """Browse and load Hidra project file + + Returns + ------- + None - def do_browse_output_dir(self): """ - browse and set output directory + self._event_handler.browse_load_hidra() + + def browse_calibration_file(self): + """ Browse and set up calibration file :return: """ - output_dir = gui_helper.browse_dir(self, caption='Output directory for reduced data', - default_dir=os.path.expanduser('~')) - if output_dir != '': - self.ui.lineEdit_outputDir.setText(output_dir) - self._core.reduction_service.set_output_dir(output_dir) - self._output_dir = output_dir - - return + self._event_handler.browse_calibration_file() - def do_chop_reduce_run(self): + def browse_idf(self): """ - chop and reduce the selected run + Browse (optonally) and set instrument definition file :return: """ - if self.ui.radioButton_chopByTime.isChecked(): - # set up slicers by time - self.set_slicers_by_time() - elif self.ui.radioButton_chopByLogValue.isChecked(): - # set up slicers by sample log value - self.set_slicers_by_sample_log_value() - else: - # set from the table - self.set_slicers_manually() - # END-IF-ELSE - - try: - data_key = self._core.reduction_service.chop_data() - except RuntimeError as run_err: - gui_helper.pop_message(self, message='Unable to slice data', detailed_message=str(run_err), - message_type='error') - return - - try: - self._core.reduction_service.reduced_chopped_data(data_key) - except RuntimeError as run_err: - gui_helper.pop_message(self, message='Failed to reduce sliced data', detailed_message=str(run_err), - message_type='error') - return - - # fill the run numbers to plot selection - self._setup_plot_selection(append_mode=False, item_list=self._core.reduction_service.get_chopped_names()) - - # plot - self._plot_data() + self._event_handler.browse_idf() - return + def browse_output_dir(self): + """ + browse and set output directory + :return: + """ + self._event_handler.browse_output_dir() - def do_launch_slice_setup(self): - """Launch event slicing setup dialog + def browse_vanadium_file(self): + """Browse vanadium HiDRA file Returns ------- None + """ - # from pyrs.interface import slicersetupwindow - # self._slice_setup_window = slicersetupwindow.EventSlicerSetupWindow(self) - # self._slice_setup_window.show() - raise NotImplementedError('Slicer setup window has not implemented.') + self._event_handler.browse_vanadium_file() - def do_plot(self): - """ Plot detector counts as 2D detector view view OR reduced data according to the tab that is current on + def browse_mask_file(self): + """ + set IPTS number :return: """ - print('[DB...BAT] Plotting tab index = {}'.format(self.ui.tabWidget_View.currentIndex())) + self._event_handler.browse_mask_file() + def plot_sub_runs(self): + """ Plot detector counts as 2D detector view view OR reduced data according to the tab that is current on + :return: + """ current_tab_index = self.ui.tabWidget_View.currentIndex() - sub_run = int(self.ui.comboBox_sub_runs.currentText()) - # FIXME - TODO - ValueError: invalid literal for int() with base 10: '' if current_tab_index == 0: # raw view - self.plot_detector_counts(sub_run, mask_id=None) + self._event_handler.plot_detector_counts() elif current_tab_index == 1: # reduced view - self.plot_reduced_data(sub_run, mask_id=None) + self._event_handler.plot_powder_pattern() else: raise NotImplementedError('Tab {} with index {} is not defined' ''.format(self.ui.tabWidget_View.name(), current_tab_index)) return - def _set_sub_runs(self): - """ set the sub runs to comboBox_sub_runs - :return: - """ - # sub_runs = self._core.reduction_manager.get_sub_runs(data_id) - # - # self.ui.comboBox_sub_runs.clear() - # for sub_run in sorted(sub_runs): - # self.ui.comboBox_sub_runs.addItem('{:04}'.format(sub_run)) - # - # - # """ - # """ - # # - sub_runs = self._core.reduction_service.get_sub_runs(self._project_data_id) - sub_runs.sort() - - # set sub runs: lock and release - self._mutexPlotRuns = True - # clear and set - self.ui.comboBox_sub_runs.clear() - for sub_run in sub_runs: - self.ui.comboBox_sub_runs.addItem('{:04}'.format(sub_run)) - self._mutexPlotRuns = False - - return - - def _setup_plot_sliced_runs(self, run_number, sliced_): - """ - - :return: - """ - - def _setup_plot_runs(self, append_mode, run_number_list): - """ set the runs (or sliced runs to plot) - :param append_mode: - :param run_number_list: - :return: - """ - checkdatatypes.check_list('Run numbers', run_number_list) - - # non-append mode - self._plot_run_numbers_mutex = True - if not append_mode: - self.ui.comboBox_runs.clear() - - # add run numbers - for run_number in run_number_list: - self.ui.comboBox_runs.addItem('{}'.format(run_number)) - - # open - self._plot_run_numbers_mutex = False - - # if append-mode, then set to first run - if append_mode: - self.ui.comboBox_runs.setCurrentIndex(0) - - return - - def _setup_plot_selection(self, append_mode, item_list): - """ - set up the combobox to select items to plot - :param append_mode: - :param item_list: - :return: - """ - checkdatatypes.check_bool_variable('Flag for appending the items to current combo-box or from start', - append_mode) - checkdatatypes.check_list('Combo-box item list', item_list) - - # turn on mutex lock - self._plot_selection_mutex = True - if append_mode is False: - self.ui.comboBox_sampleLogNames.clear() - for item in item_list: - self.ui.comboBox_sampleLogNames.addItem(item) - if append_mode is False: - self.ui.comboBox_sampleLogNames.setCurrentIndex(0) - self._plot_selection_mutex = False - return - def do_quit(self): """ Quit manual reduction window @@ -495,88 +290,17 @@ def do_quit(self): return - def do_reduce_batch_runs(self): - """ - (simply) reduce a list of runs in same experiment in a batch - :return: - """ - # get (sub) run numbers - sub_runs_str = str(self.ui.lineEdit_runNumbersList.text()).strip().lower() - if sub_runs_str == 'all': - sub_run_list = list() - else: - try: - sub_run_list = gui_helper.parse_integers(sub_runs_str) - except RuntimeError as run_err: - gui_helper.pop_message(self, 'Failed to parse integer list', - '{}'.format(run_err), 'error') - return - # Reduce data - run_number = self.ui.spinBox_runNumber.text().strip() - self._update_output_ipts(run_number) - nexus_file = FileFinder.findRuns('HB2B'+run_number)[0] - project_file = str(self.ui.lineEdit_outputDirectory.text().strip()) - mask_file = str(self.ui.lineEdit_maskFile.text().strip()) - calibration_file = str(self.ui.lineEdit_calibrationFile.text().strip()) - task = BlockingAsyncTaskWithCallback(reduce_hidra_workflow, args=(nexus_file, project_file, - self.ui.progressBar), - kwargs={'subruns': sub_run_list, 'mask': mask_file, - 'calibration': calibration_file}, - blocking_cb=QApplication.processEvents) - # TODO - catch RuntimeError! ... - # FIXME - check output directory - task.start() - # <<<< - - # Update table - # TODO - Need to fill the table! - for sub_run in list(): - self.ui.rawDataTable.update_reduction_state(sub_run, True) - - return - - def do_save_project(self): - """Save project - :return: - """ - output_project_name = os.path.join(self._output_dir, os.path.basename(self._project_file_name)) - if output_project_name != self._project_file_name: - import shutil - shutil.copyfile(self._project_file_name, output_project_name) - - self._core.reduction_service.save_project(self._project_data_id, output_project_name) + def split_convert_save_nexus(self): + """Reduce (split sub runs, convert to powder pattern and save) manually - def do_load_hidra_projec_file(self): - """ - set IPTS number - :return: - """ - # try: - # ipts_number = gui_helper.parse_integer(str(self.ui.lineEdit_iptsNumber.text())) - # exp_number = gui_helper.parse_integer(str(self.ui.lineEdit_expNumber.text())) - # self._currIPTSNumber = ipts_number - # self._currExpNumber = exp_number - # project_file_name = 'blabla.hdf5' - # except RuntimeError: - # gui_helper.pop_message(self, 'IPTS number shall be set to an integer.', message_type='error') - project_file_name = gui_helper.browse_file( - self, 'Hidra Project File', os.getcwd(), 'hdf5 (*.h5)', False, False) - self.ui.lineEdit_runNumber.setText(project_file_name) - # self.load_hydra_file(project_file_name) - - return + Returns + ------- + None - def do_browse_mask_file(self): - """ - set IPTS number - :return: """ - mask_file_name = gui_helper.browse_file( - self, 'Hidra Mask File', DEFAULT_MASK_DIRECTORY, 'hdf5 (*.h5);;xml (*.xml)', False, False) - self.ui.lineEdit_maskFile.setText(mask_file_name) - # self.load_hydra_file(project_file_name) - return + self._event_handler.manual_reduce_run() + # Next: it is not implemented now def event_change_slice_type(self): """Handle the event as the event slicing type is changed @@ -607,194 +331,3 @@ def event_change_slice_type(self): # self.ui.groupBox_advancedSetup.setEnabled(not disable_adv_slice) return - - def event_load_image(self): - """ - Load image binary file (as HFIR SPICE binary standard) - :return: - """ - gui_helper.pop_message(self, 'Tell me', 'What do you want from me!', 'error') - - bin_file = gui_helper.browse_file(self, caption='Select a SPICE Image Binary File', - default_dir=self._core.working_dir, - file_filter='Binary (*.bin)', file_list=False, save_file=False) - - # return if user cancels operation - if bin_file == '': - return - - return - - def event_new_run_to_plot(self): - """ User selects a different run number to plot - :return: - """ - if self._mutexPlotRuns: - return - - curr_run_number = int(str(self.ui.comboBox_runs.currentText())) - if not self._core.reduction_service.has_run_reduced(curr_run_number): - return - - is_chopped = self._core.reduction_service.is_chopped_run(curr_run_number) - - # set the sliced box - self._plot_sliced_mutex = True - self.ui.comboBox_slicedRuns.clear() - if is_chopped: - sliced_segment_list = self._core.reduction_service.get_chopped_names(curr_run_number) - for segment in sorted(sliced_segment_list): - self.ui.comboBox_slicedRuns.addItem('{}'.format(segment)) - else: - pass - - # set the plot options - # TODO - 20181008 - ASAP - self._plot_selection_mutex = True - if is_chopped: - # set up with chopped data - pass - else: - # set up with non-chopped data - pass - - self._plot_sliced_mutex = False - - return - - # def load_hydra_file(self, project_file_name): - # """ - # Load Hidra project file to the core - # :param project_file_name: - # :return: - # """ - # # Load data file - # project_name = os.path.basename(project_file_name).split('.')[0] - # try: - # self._hydra_workspace = self._core.load_hidra_project(project_file_name, project_name=project_name, - # load_detector_counts=True, load_diffraction=True) - # except (RuntimeError, IOError) as load_err: - # self._hydra_workspace = None - # gui_helper.pop_message(self, 'Loading {} failed'.format(project_file_name), - # detailed_message='{}'.format(load_err), - # message_type='error') - # return - # - # # Set value for the loaded project - # self._project_file_name = project_file_name - # self._project_data_id = project_name - # - # # Fill sub runs to self.ui.comboBox_sub_runs - # self._set_sub_runs() - # - # # Set to first sub run and plot - # self.ui.comboBox_sub_runs.setCurrentIndex(0) - # - # # Fill in self.ui.frame_subRunInfoTable - # meta_data_array = self._core.reduction_manager.get_sample_logs_values(self._project_data_id, - # [HidraConstants.SUB_RUNS, - # HidraConstants.TWO_THETA]) - # self.ui.rawDataTable.add_subruns_info(meta_data_array, clear_table=True) - # - # return - - def plot_detector_counts(self, sub_run_number, mask_id): - """ - Plot detector counts on the detector view - :param sub_run_number: sub run number (integer) - :param mask_id: Mask ID (string) or None - :return: - """ - # Check inputs - checkdatatypes.check_int_variable('Sub run number', sub_run_number, (0, None)) - - # Get the detector counts - detector_counts_array = self._core.reduction_service.get_detector_counts(self._project_data_id, - sub_run_number) - - # set information - det_2theta = self._core.reduction_service.get_sample_log_value(self._project_data_id, - HidraConstants.TWO_THETA, - sub_run_number) - info = 'sub-run: {}, 2theta = {}' \ - ''.format(sub_run_number, det_2theta) - - # If mask ID is not None - if mask_id is not None: - # Get mask in array and do a AND operation to detector counts (array) - mask_array = self._core.reduction_service.get_mask_array(self._curr_project_name, mask_id) - detector_counts_array *= mask_array - info += ', mask ID = {}'.format(mask_id) - - # Set information - self.ui.lineEdit_detViewInfo.setText(info) - - # Plot - self.ui.graphicsView_detectorView.plot_detector_view(detector_counts_array, (sub_run_number, mask_id)) - - return - - def plot_reduced_data(self, sub_run_number, mask_id): - """ - Plot reduced data - :param sub_run_number: sub run number (integer) - :param mask_id: Mask ID (string) or None - :return: - """ - # Check inputs - checkdatatypes.check_int_variable('Sub run number', sub_run_number, (0, None)) - - try: - two_theta_array, diff_array = self._core.reduction_service.get_reduced_diffraction_data( - self._project_data_id, sub_run_number, mask_id) - if two_theta_array is None: - raise NotImplementedError('2theta array is not supposed to be None.') - except RuntimeError as run_err: - gui_helper.pop_message(self, 'Unable to retrieve reduced data', - 'For sub run {} due to {}'.format(sub_run_number, run_err), - 'error') - return - - # set information - det_2theta = self._core.reduction_service.get_sample_log_value(self._project_data_id, - HidraConstants.TWO_THETA, - sub_run_number) - info = 'sub-run: {}, 2theta = {}' \ - ''.format(sub_run_number, det_2theta) - - # plot diffraction data - self.ui.graphicsView_1DPlot.plot_diffraction(two_theta_array, diff_array, '2theta', - 'intensity', line_label=info, keep_prev=False) - - return - - def reduce_sub_runs(self, sub_runs): - """ - - :param sub_runs: - :return: - """ - # TODO - #84 - Need mask, calibration, and etc. - - self._core.reduce_diffraction_data(session_name=self._project_data_id, - num_bins=1000, - pyrs_engine=True, - mask_file_name=None, - geometry_calibration=None, - sub_run_list=sub_runs) - - return - - def setup_window(self, pyrs_core): - """ - set up the manual reduction window from its parent - :param pyrs_core: - :return: - """ - # check - assert isinstance(pyrs_core, PyRsCore), 'Controller core {0} must be a PyRSCore instance but not a {1}.' \ - ''.format(pyrs_core, pyrs_core.__class__.__name__) - - self._core = pyrs_core - - return diff --git a/pyrs/interface/manual_reduction/pyrs_api.py b/pyrs/interface/manual_reduction/pyrs_api.py new file mode 100644 index 000000000..bcd03730b --- /dev/null +++ b/pyrs/interface/manual_reduction/pyrs_api.py @@ -0,0 +1,333 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility +import os +from mantid.simpleapi import Logger +import numpy as np +from pyrs.core.nexus_conversion import NeXusConvertingApp +from pyrs.core.powder_pattern import ReductionApp + +DEFAULT_MASK_DIRECTORY = '/HFIR/HB2B/shared/CALIBRATION/' +DEFAULT_CALIBRATION_DIRECTORY = DEFAULT_MASK_DIRECTORY + + +class ReductionController(object): + """Control the data objects for manual reduction + + """ + def __init__(self): + """Initialization of data structures + + """ + # current HidraWorkspace used in reduction + self._curr_hidra_ws = None + # record of previously and currently processed HidraWorksapce + self._hidra_ws_dict = dict() + # Working directory + self._working_dir = '/HFIR/HB2B/' + + @property + def working_dir(self): + return self._working_dir + + @staticmethod + def get_default_calibration_dir(): + """Default calibration directory on analysis cluster + + Returns + ------- + str + directory path + + """ + return DEFAULT_CALIBRATION_DIRECTORY + + @staticmethod + def get_default_mask_dir(): + """Get default directory for masks on analysis cluster + + Returns + ------- + str + directory path for maskings + + """ + return DEFAULT_MASK_DIRECTORY + + def get_sub_runs(self): + """Get sub runs of the current loaded HidraWorkspace + + Returns + ------- + numpy.ndarray + 1D array for sorted sub runs + + """ + if self._curr_hidra_ws is None: + raise RuntimeError('No HidraWorkspace is created or loaded') + + return self._curr_hidra_ws.get_sub_runs() + + def get_detector_counts(self, sub_run_number, output_matrix): + """Get detector counts + + Exception: RuntimeError + 1. self._curr_hidra_ws does not exist + 2. sub run does not exist + + Parameters + ---------- + sub_run_number : int + sub run number + output_matrix : bool + True: output 2D, otherwise 1D + + Returns + ------- + numpy.ndarray + detector counts in 1D or 2D array + + """ + if self._curr_hidra_ws is None: + raise RuntimeError('No HidraWorkspace is created or loaded') + + # Get detector counts from HidraWorkspace. Possibly raise a RuntimeError from called method + det_counts_array = self._curr_hidra_ws.get_detector_counts(sub_run_number) + + # Convert to 2D array for plotting as an option + if output_matrix: + # sanity check for array size + counts_size = det_counts_array.shape[0] + linear_size = int(np.sqrt(counts_size)) + assert linear_size == 1024 + + # convert + det_counts_array = det_counts_array.reshape((linear_size, linear_size)) + # END-IF + + return det_counts_array + + def get_powder_pattern(self, sub_run_number): + """Retrieve powder pattern from current HidraWorkspace + + Exception: RuntimeError + 1. self._curr_hidra_ws does not exist + 2. sub run does not exist + + Parameters + ---------- + sub_run_number : int + sub run number + + Returns + ------- + numpy.ndarray, numpy.ndarray + vector of 2theta and intensity + + """ + if self._curr_hidra_ws is None: + raise RuntimeError('No HidraWorkspace is created or loaded') + + # Get powder pattern + vec_2theta, vec_intensity = self._curr_hidra_ws.get_reduced_diffraction_data(sub_run=sub_run_number, + mask_id=None) + + return vec_2theta, vec_intensity + + def get_sample_log_value(self, log_name, sub_run_number): + """Get sample log value + + Exception: RuntimeError + 1. self._curr_hidra_ws does not exist + 2. sub run does not exist + + Parameters + ---------- + log_name : str + log name + sub_run_number : int + sub run number + + Returns + ------- + object + sample log value + + """ + if self._curr_hidra_ws is None: + raise RuntimeError('No HidraWorkspace is created or loaded') + + return self._curr_hidra_ws.get_sample_log_value(log_name, sub_run_number) + + def get_sample_logs_values(self, sample_log_names): + """Get sample logs' values + + Note: as the heterogeneous type of sample logs, a dictionary as the return type is more + convenient than numpy.ndarray + + Parameters + ---------- + sample_log_names : ~list + List of sample logs + + Returns + ------- + ~dict + sample log values in format of dictionary of numpy.ndarray + + """ + if self._curr_hidra_ws is None: + raise RuntimeError('No HidraWorkspace is created or loaded') + + # Create a dictionary for sample logs + logs_value_dict = dict() + + for log_name in sample_log_names: + log_value_array = self._curr_hidra_ws.get_sample_log_values(log_name) + logs_value_dict[log_name] = log_value_array + # END-FOR + + return logs_value_dict + + def load_nexus_file(self, nexus_name): + # TODO - ASAP - Need use case to decide functionality + raise NotImplementedError('ASAP') + + def load_project_file(self, file_name, load_counts, load_pattern): + from pyrs.core.pyrscore import PyRsCore + core = PyRsCore() + project_name = os.path.basename(file_name).split('.')[0] + try: + self._curr_hidra_ws = core.load_hidra_project(file_name, project_name, load_counts, load_pattern) + except RuntimeError as run_err: + raise RuntimeError('Failed to load project file {}: {}'.format(file_name, run_err)) + + return + + def save_project(self): + """Save HidraWorkspace to project file + + Exception: RuntimeError if + (1) no curr_hidra_ws + (2) curr_hidra_ws does not have file name associated + + Returns + ------- + + """ + if self._curr_hidra_ws is None: + raise RuntimeError('No HidraWorkspace is created or loaded') + + project_file_name = self._curr_hidra_ws.hidra_project_file + if project_file_name is None: + raise RuntimeError('HiDRA workspace {} is not associated with any project file' + ''.format(self._curr_hidra_ws.name)) + + # TODO - Need to find out the scenario! + raise NotImplementedError('Need use cases!') + + def reduce_hidra_workflow(self, nexus, output_dir, progressbar, instrument=None, calibration=None, mask=None, + vanadium_file=None, project_file_name=None): + """Full workflow to reduce NeXus file + + Parameters + ---------- + nexus + output_dir + progressbar + instrument + calibration + mask + vanadium_file : str or None + Vanadium file (reduced project file with vanadium counts at sub run 1) + project_file_name + + Returns + ------- + + """ + self._curr_hidra_ws = reduce_hidra_workflow(nexus, output_dir, progressbar, instrument, + calibration, mask, vanadium_file, project_file_name) + + self._hidra_ws_dict[self._curr_hidra_ws.name] = self._curr_hidra_ws + + return self._curr_hidra_ws + + +def reduce_hidra_workflow(nexus, output_dir, progressbar, instrument=None, calibration=None, mask=None, + vanadium_file=None, project_file_name=None): + """Workflow of algorithms to reduce HB2B NeXus file to powder patterns + + Parameters + ---------- + nexus + output_dir + progressbar + instrument + calibration : str + calibration file name + mask : str or None + Mask file (so far, only Mantid XML file) + vanadium_file : str or None + Vanadium file (reduced project file with vanadium counts at sub run 1) + project_file_name : str or None + if str, then the output file name won't use the default + + Returns + ------- + pyrs.core.workspaces.HidraWorkspace + HiDRA workspace + + """ + logger = Logger('reduce_HB2B') + + # Create project file (name) for default + if project_file_name is None: + project_file_name = os.path.basename(nexus).split('.')[0] + '.h5' + project_file_name = os.path.join(output_dir, project_file_name) + + # Remove previous existing file + if os.path.exists(project_file_name): + logger.information('Will overwrite existing projectfile {}'.format(project_file_name)) + + # Init logger + logger = Logger('reduce_HB2B') + + # Set progress bar + if progressbar: + progressbar.setVisible(True) + progressbar.setValue(0.) + + # process the data + converter = NeXusConvertingApp(nexus, mask) + hidra_ws = converter.convert(use_mantid=False) + + # Update + if progressbar: + progressbar.setValue(50.) + # add powder patterns + + # Calculate powder pattern + logger.notice('Adding powder patterns to Hidra Workspace {}'.format(hidra_ws)) + + # Initialize a reducer + reducer = ReductionApp(False) + # add workspace to reducer + reducer.load_hidra_workspace(hidra_ws) + # reduce + reducer.reduce_data(instrument_file=instrument, + calibration_file=calibration, + mask=None, + van_file=vanadium_file, + sub_runs=list(hidra_ws.get_sub_runs())) + + if progressbar: + progressbar.setVisible(True) + progressbar.setValue(95.) + + # Save + reducer.save_diffraction_data(project_file_name) + + if progressbar: + progressbar.setVisible(True) + progressbar.setValue(100.) + + return hidra_ws diff --git a/pyrs/interface/peak_fitting/config.py b/pyrs/interface/peak_fitting/config.py index 327cd7018..c954c9b00 100644 --- a/pyrs/interface/peak_fitting/config.py +++ b/pyrs/interface/peak_fitting/config.py @@ -5,12 +5,15 @@ ('vx', 'vx'), ('vy', 'vy'), ('vz', 'vz'), ('phi', 'phi'), ('chi', 'chi'), ('omega', 'omega')]) -fit_dict = OrderedDict([('Peak Height', 'PeakHeight'), +fit_dict = OrderedDict([('Peak Height', 'Height'), ('Full Width Half Max', 'FWHM'), - ('intensity', 'intensity'), - ('PeakCenter', 'PeakCenter'), - ('d-spacing', 'd-spacing'), - ('strain', 'strain')]) + ('Intensity', 'Intensity'), + ('Peak Center', 'Center'), + ('A0', 'A0'), + ('A1', 'A1'), + # ('d-spacing', 'd-spacing'), + # ('strain', 'strain'), + ]) full_dict = OrderedDict([('Sub-runs', 'subrun'), ('sx', 'sx'), ('sy', 'sy'), ('sz', 'sz'), @@ -25,11 +28,15 @@ LIST_AXIS_TO_PLOT = {'raw': raw_dict, 'fit': fit_dict, 'full': full_dict, + '3d_axis': {'xy_axis': OrderedDict([('sx', 'sx'), ('sy', 'sy'), ('sz', 'sz'), + ('vx', 'vx'), ('vy', 'vy'), ('vz', 'vz')]), + 'z_axis': fit_dict, + }, } DEFAUT_AXIS = {'1d': {'xaxis': 'Sub-runs', 'yaxis': 'sx'}, - '2d': {'xaxis': 'Sub-runs', - 'yaxis': 'sx', - 'zaxis': 'sy'}} + '2d': {'xaxis': 'sx', + 'yaxis': 'sy', + 'zaxis': 'Peak Center'}} RAW_LIST_COLORS = ['blue', 'green', 'red', 'cyan', 'magenta', 'yellow', 'black'] diff --git a/pyrs/interface/peak_fitting/data_retriever.py b/pyrs/interface/peak_fitting/data_retriever.py new file mode 100644 index 000000000..427cb417a --- /dev/null +++ b/pyrs/interface/peak_fitting/data_retriever.py @@ -0,0 +1,37 @@ +import numpy as np + +from pyrs.interface.peak_fitting.config import LIST_AXIS_TO_PLOT +from pyrs.interface.peak_fitting.config import fit_dict as FIT_DICT + + +class DataRetriever: + + def __init__(self, parent=None): + self.parent = parent + self.hidra_workspace = self.parent.hidra_workspace + + def get_data(self, name='Sub-runs', peak_index=0): + + if name == 'Sub-runs': + return [np.array(self.hidra_workspace.get_sub_runs()), None] + + if name in LIST_AXIS_TO_PLOT['raw'].keys(): + return [self.hidra_workspace._sample_logs[name], None] + + if name in LIST_AXIS_TO_PLOT['fit'].keys(): + return self.get_fitted_value(peak=self.parent.fit_result.peakcollections[peak_index], + value_to_display=name) + + def get_fitted_value(self, peak=None, value_to_display='Center'): + """ + return the values and errors of the fitted parameters of the given peak + :param peak: + :param value_to_display: + :return: + """ + value, error = peak.get_effective_params() + + mantid_value_to_display = FIT_DICT[value_to_display] + value_selected = value[mantid_value_to_display] + error_selected = error[mantid_value_to_display] + return value_selected, error_selected diff --git a/pyrs/interface/peak_fitting/event_handler.py b/pyrs/interface/peak_fitting/event_handler.py index 8f1fbae70..23be31aef 100644 --- a/pyrs/interface/peak_fitting/event_handler.py +++ b/pyrs/interface/peak_fitting/event_handler.py @@ -46,6 +46,9 @@ def browse_load_plot_hdf(self): except RuntimeError as run_err: pop_message(self, 'Failed to load {}'.format(hidra_file_name), str(run_err), 'error') + except KeyError as key_err: + pop_message(self, 'Failed to load {}'.format(hidra_file_name), + str(key_err), 'error') self.parent.current_root_statusbar_message = "Working with: {} " \ "\t\t\t\t Project Name: {}" \ @@ -69,9 +72,11 @@ def browse_load_plot_hdf(self): # enabled all fitting widgets and main plot o_gui = GuiUtilities(parent=self.parent) o_gui.check_if_fitting_widgets_can_be_enabled() + o_gui.enabled_sub_runs_interation_widgets(True) # o_gui.enabled_fitting_widgets(True) o_gui.enabled_data_fit_plot(True) o_gui.enabled_peak_ranges_widgets(True) + o_gui.enabled_1dplot_widgets(True) except RuntimeError as run_err: pop_message(self, 'Failed to initialize widgets for {}'.format(hidra_file_name), @@ -267,3 +272,13 @@ def remove_peak_range_table_row(self): self.parent._ui_graphicsView_fitSetup.list_peak_labels_matplotlib_id = new_list_matplotlib_id self.parent._ui_graphicsView_fitSetup.plot_data_with_fitting_ranges() + + def fit_table_selection_changed(self): + '''as soon as a row is selected, switch to the slider view and go to right sub_run''' + row_selected = GuiUtilities.get_row_selected(table_ui=self.parent.ui.tableView_fitSummary) + if row_selected is None: + return + self.parent.ui.radioButton_individualSubRuns.setChecked(True) + self.parent.check_subRunsDisplayMode() + self.parent.ui.horizontalScrollBar_SubRuns.setValue(row_selected+1) + self.parent.plot_scan() diff --git a/pyrs/interface/peak_fitting/export.py b/pyrs/interface/peak_fitting/export.py index 6793a5c06..2b9fdc5f6 100644 --- a/pyrs/interface/peak_fitting/export.py +++ b/pyrs/interface/peak_fitting/export.py @@ -27,13 +27,15 @@ def select_output_folder(self): self._csv_file_name = os.path.join(out_folder, self.parent._project_name + '.csv') def create_csv(self): - peaks = self.parent._core.get_peak(self.parent._project_name, 'Peak 1') + peaks = self.parent.fit_result.peakcollections sample_logs = self.parent.hidra_workspace._sample_logs + print("sample_log: {}".format(sample_logs)) + generator = SummaryGenerator(self._csv_file_name, log_list=sample_logs.keys()) generator.setHeaderInformation(dict()) - generator.write_csv(sample_logs, [peaks]) + generator.write_csv(sample_logs, peaks) new_message = self.parent.current_root_statusbar_message + "\t\t\t\t Last Exported CSV: {}" \ "".format(self._csv_file_name) diff --git a/pyrs/interface/peak_fitting/fit.py b/pyrs/interface/peak_fitting/fit.py index 2a466c77d..7155122a0 100644 --- a/pyrs/interface/peak_fitting/fit.py +++ b/pyrs/interface/peak_fitting/fit.py @@ -7,6 +7,8 @@ from pyrs.interface.gui_helper import pop_message from pyrs.interface.peak_fitting.gui_utilities import GuiUtilities from pyrs.peaks import FitEngineFactory as PeakFitEngineFactory +from qtpy.QtWidgets import QApplication +from qtpy.QtCore import Qt PeakInfo = namedtuple('PeakInfo', 'center left_bound right_bound tag') @@ -18,6 +20,8 @@ def __init__(self, parent=None): def fit_multi_peaks(self): + QApplication.setOverrideCursor(Qt.WaitCursor) + _peak_range_list = [tuple(_range) for _range in self.parent._ui_graphicsView_fitSetup.list_peak_ranges] _peak_center_list = [np.mean([left, right]) for (left, right) in _peak_range_list] _peak_tag_list = ["peak{}".format(_index) for _index, _ in enumerate(_peak_center_list)] @@ -29,21 +33,28 @@ def fit_multi_peaks(self): # Fit peak hd_ws = self.parent.hidra_workspace - _wavelength = 1.071 - hd_ws.set_wavelength(_wavelength, False) - + _wavelength = hd_ws.get_wavelength(True, True) fit_engine = PeakFitEngineFactory.getInstance(hd_ws, - _peak_function_name, - 'Linear') + _peak_function_name, 'Linear', + wavelength=_wavelength) fit_result = fit_engine.fit_multiple_peaks(_peak_tag_list, _peak_xmin_list, _peak_xmax_list) + self.parent.fit_result = fit_result + self.parent.populate_fit_result_table(fit_result=fit_result) - self.parent.update_list_of_2d_plots_axis() + # self.parent.update_list_of_2d_plots_axis() o_gui = GuiUtilities(parent=self.parent) o_gui.set_1D_2D_axis_comboboxes(with_clear=True, fill_raw=True, fill_fit=True) o_gui.initialize_combobox() + o_gui.enabled_export_csv_widgets(enabled=True) + o_gui.enabled_2dplot_widgets(enabled=True) + + o_plot = Plot(parent=self.parent) + o_plot.plot_2d() + + QApplication.restoreOverrideCursor() def fit_peaks(self, all_sub_runs=False): """ Fit peaks either all peaks or selected peaks diff --git a/pyrs/interface/peak_fitting/fit_table.py b/pyrs/interface/peak_fitting/fit_table.py index 66197972c..759e17b6f 100644 --- a/pyrs/interface/peak_fitting/fit_table.py +++ b/pyrs/interface/peak_fitting/fit_table.py @@ -1,5 +1,5 @@ import numpy as np -from qtpy.QtWidgets import QTableWidgetItem +from qtpy.QtWidgets import QTableWidgetItem, QTableWidgetSelectionRange from qtpy.QtGui import QColor SUCCESS = "success" @@ -93,10 +93,14 @@ def _clear_rows(self): self.parent.ui.tableView_fitSummary.removeRow(0) def _clear_columns(self): - _nbr_column = self.parent.ui.tableView_fitSummary.columnCount() + _nbr_column = self.get_number_of_columns() for _ in np.arange(_nbr_column): self.parent.ui.tableView_fitSummary.removeColumn(0) + def get_number_of_columns(self): + _nbr_column = self.parent.ui.tableView_fitSummary.columnCount() + return _nbr_column + def _clear_table(self): self._clear_rows() self._clear_columns() @@ -118,5 +122,9 @@ def _get_list_of_columns(self): # add a status column clean_column_names.append("Status message") - return clean_column_names + + def select_first_row(self): + _nbr_column = self.get_number_of_columns() + selection_first_row = QTableWidgetSelectionRange(0, 0, 0, _nbr_column-1) + self.parent.ui.tableView_fitSummary.setRangeSelected(selection_first_row, True) diff --git a/pyrs/interface/peak_fitting/fitpeakswindow.py b/pyrs/interface/peak_fitting/fitpeakswindow.py index b25c5a8ab..2d539c868 100644 --- a/pyrs/interface/peak_fitting/fitpeakswindow.py +++ b/pyrs/interface/peak_fitting/fitpeakswindow.py @@ -1,10 +1,11 @@ import os from qtpy.QtWidgets import QVBoxLayout, QFileDialog, QMainWindow -from qtpy import QtGui +from qtpy import QtGui, PYQT4, PYQT5 from pyrs.utilities import load_ui from pyrs.interface.ui import qt_util from pyrs.interface.ui.diffdataviews import GeneralDiffDataView +from pyrs.interface.ui.mplgraphicsviewcontourplot import MplGraphicsViewContourPlot from pyrs.interface.ui.rstables import FitResultTable from pyrs.interface.ui.diffdataviews import PeakFitSetupView import pyrs.interface.advpeakfitdialog @@ -15,10 +16,18 @@ from pyrs.interface.peak_fitting.fit_table import FitTable from pyrs.interface.peak_fitting.export import ExportCSV from pyrs.interface.peak_fitting.gui_utilities import GuiUtilities -from pyrs.icons import icons_rc # noqa: F401 + +if PYQT5: + from pyrs.icons import icons_rc5 as icons_rc # noqa: F401 +elif PYQT4: + from pyrs.icons import icons_rc4 as icons_rc # noqa: F401 +else: + raise RuntimeError('Do not know pyqt version') VERTICAL_SPLITTER = """QSplitter::handle {image: url(':/fitting/vertical_splitter.png'); }""" +VERTICAL_SPLITTER_SHORT = """QSplitter::handle {image: url(':/fitting/vertical_splitter_short.png'); }""" HORIZONTAL_SPLITTER = """QSplitter::handle {image: url(':/fitting/horizontal_splitter.png'); }""" +HORIZONTAL_SPLITTER_SHORT = """QSplitter::handle {image: url(':/fitting/horizontal_splitter_short.png'); }""" MICROSTRAIN = u"\u212B" D0 = u"d\u2080" @@ -65,14 +74,13 @@ def setup_ui(self): self.ui.graphicsView_fitResult.setEnabled(False) self.ui.graphicsView_fitResult.set_subplots(1, 1) self.ui.graphicsView_plot2D = qt_util.promote_widget(self, self.ui.graphicsView_2dPlot_frame, - GeneralDiffDataView) + MplGraphicsViewContourPlot) self.ui.tableView_fitSummary = qt_util.promote_widget(self, self.ui.tableView_fitSummary_frame, FitResultTable) self._promote_peak_fit_setup() self._init_widgets() # set up handling - # self.ui.pushButton_loadHDF.clicked.connect(self.load_hidra_file) self.ui.pushButton_browseHDF.clicked.connect(self.browse_hdf) self.ui.lineEdit_listSubRuns.returnPressed.connect(self.plot_diff_data) self.ui.pushButton_FitPeaks.clicked.connect(self.fit_peaks) @@ -89,17 +97,27 @@ def setup_ui(self): self.ui.lineEdit_subruns_2dplot.textChanged.connect(self.list_subruns_2dplot_changed) self.ui.pushButton_save_peak_range.clicked.connect(self.clicked_save_peak_range) self.ui.pushButton_load_peak_range.clicked.connect(self.clicked_load_peak_range) + self.ui.tableView_fitSummary.itemSelectionChanged.connect(self.fit_result_table_selection_changed) self.ui.radioButton_fit_value.clicked.connect(self.fit_table_radio_buttons) self.ui.radioButton_fit_error.clicked.connect(self.fit_table_radio_buttons) self.ui.spinBox_peak_index.valueChanged.connect(self.fit_table_radio_buttons) - self.ui.comboBox_xaxisNames.currentIndexChanged.connect(self.xaxis_1d_changed) - self.ui.comboBox_yaxisNames.currentIndexChanged.connect(self.yaxis_1d_changed) + self.ui.comboBox_xaxisNames.currentIndexChanged.connect(self.axis_1d_changed) + self.ui.comboBox_yaxisNames.currentIndexChanged.connect(self.axis_1d_changed) + self.ui.plot1d_xaxis_peak_label_comboBox.currentIndexChanged.connect(self.axis_1d_changed) + self.ui.plot1d_yaxis_peak_label_comboBox.currentIndexChanged.connect(self.axis_1d_changed) + + self.ui.comboBox_xaxisNames_2dplot.currentIndexChanged.connect(self.axis_2d_changed) + self.ui.comboBox_yaxisNames_2dplot.currentIndexChanged.connect(self.axis_2d_changed) + self.ui.comboBox_zaxisNames_2dplot.currentIndexChanged.connect(self.axis_2d_changed) + self.ui.plot2d_xaxis_peak_label_comboBox.currentIndexChanged.connect(self.axis_2d_changed) + self.ui.plot2d_yaxis_peak_label_comboBox.currentIndexChanged.connect(self.axis_2d_changed) + self.ui.plot2d_zaxis_peak_label_comboBox.currentIndexChanged.connect(self.axis_2d_changed) - self.ui.comboBox_xaxisNames_2dplot.currentIndexChanged.connect(self.xaxis_2d_changed) - self.ui.comboBox_yaxisNames_2dplot.currentIndexChanged.connect(self.yaxis_2d_changed) - self.ui.comboBox_zaxisNames_2dplot.currentIndexChanged.connect(self.zaxis_2d_changed) + self.ui.radioButton_contour.clicked.connect(self.axis_2d_changed) + self.ui.radioButton_3dline.clicked.connect(self.axis_2d_changed) + self.ui.radioButton_3dscatter.clicked.connect(self.axis_2d_changed) self.ui.peak_range_table.cellChanged.connect(self.peak_range_table_changed) @@ -120,11 +138,17 @@ def setup_ui(self): o_gui = GuiUtilities(parent=self) o_gui.enabled_fitting_widgets(False) o_gui.enabled_1dplot_widgets(False) + o_gui.check_axis1d_status() o_gui.enabled_2dplot_widgets(False) + o_gui.check_axis2d_status() o_gui.make_visible_listsubruns_warning(False) o_gui.enabled_export_csv_widgets(False) o_gui.enabled_peak_ranges_widgets(False) o_gui.enabled_save_peak_range_widget(False) + o_gui.enabled_sub_runs_interation_widgets(False) + + def test(self): + print("in test") # Menu event handler def browse_hdf(self): @@ -178,29 +202,17 @@ def list_subruns_2dplot_changed(self): o_handle = EventHandler(parent=self) o_handle.list_subruns_2dplot_changed() - def xaxis_1d_changed(self): + def axis_1d_changed(self): o_gui = GuiUtilities(parent=self) o_gui.check_axis1d_status() o_plot = Plot(parent=self) o_plot.plot_1d() - def yaxis_1d_changed(self): - o_gui = GuiUtilities(parent=self) - o_gui.check_axis1d_status() - o_plot = Plot(parent=self) - o_plot.plot_1d() - - def xaxis_2d_changed(self): - o_gui = GuiUtilities(parent=self) - o_gui.check_axis2d_status() - - def yaxis_2d_changed(self): - o_gui = GuiUtilities(parent=self) - o_gui.check_axis2d_status() - - def zaxis_2d_changed(self): + def axis_2d_changed(self): o_gui = GuiUtilities(parent=self) o_gui.check_axis2d_status() + o_plot = Plot(parent=self) + o_plot.plot_2d() def export_csv(self): o_export = ExportCSV(parent=self) @@ -212,8 +224,7 @@ def update_peak_ranges_table(self, **kwargs): o_handle.update_fit_peak_ranges_table(**kwargs) def update_list_of_2d_plots_axis(self): - print("in update_list_of_2d_plots_axis") - print(self.fit_result.fitted) + pass def _promote_peak_fit_setup(self): # 2D detector view @@ -235,6 +246,7 @@ def populate_fit_result_table(self, fit_result=None): o_table = FitTable(parent=self, fit_result=fit_result) o_table.initialize_fit_result_widgets() o_table.populate_fit_result_table() + o_table.select_first_row() def fit_table_radio_buttons(self): o_table = FitTable(parent=self, fit_result=self.fit_result) @@ -258,6 +270,10 @@ def peak_range_table_right_click(self, position): o_handler = EventHandler(parent=self) o_handler.peak_range_table_right_click(position=position) + def fit_result_table_selection_changed(self): + o_handler = EventHandler(parent=self) + o_handler.fit_table_selection_changed() + def _init_widgets(self): """ initialize the some widgets @@ -266,12 +282,12 @@ def _init_widgets(self): self.ui.actionSave.setEnabled(False) self.ui.actionSaveAs.setEnabled(False) - self.ui.splitter_4.setStyleSheet(VERTICAL_SPLITTER) - self.ui.splitter_4.setSizes([100, 0]) + self.ui.splitter.setStyleSheet(VERTICAL_SPLITTER_SHORT) self.ui.splitter_2.setStyleSheet(HORIZONTAL_SPLITTER) - self.ui.splitter_2.setSizes([100, 0]) - self.ui.splitter_3.setStyleSheet(HORIZONTAL_SPLITTER) - # self.ui.splitter.setStyleSheet(HORIZONTAL_SPLITTER) + self.ui.splitter_4.setStyleSheet(HORIZONTAL_SPLITTER) + self.ui.splitter_5.setStyleSheet(HORIZONTAL_SPLITTER) + self.ui.splitter_3.setStyleSheet(VERTICAL_SPLITTER) + self.ui.splitter_3.setSizes([80, 20]) # status bar self.setStyleSheet("QStatusBar{padding-left:8px;color:green;}") @@ -289,6 +305,10 @@ def _init_widgets(self): o_gui.make_visible_d01d_widgets(visible=False) o_gui.make_visible_d02d_widgets(visible=False) + # until issue with plot3d has been found + self.ui.radioButton_contour.setEnabled(False) + self.ui.radioButton_3dline.setEnabled(False) + def do_launch_adv_fit(self): """ launch the dialog window for advanced peak fitting setup and control diff --git a/pyrs/interface/peak_fitting/gui_utilities.py b/pyrs/interface/peak_fitting/gui_utilities.py index 0154dff52..f69ed46b9 100644 --- a/pyrs/interface/peak_fitting/gui_utilities.py +++ b/pyrs/interface/peak_fitting/gui_utilities.py @@ -10,9 +10,14 @@ class GuiUtilities: def __init__(self, parent=None): self.parent = parent + def enabled_sub_runs_interation_widgets(self, enabled=True): + list_widgets = [self.parent.ui.groupBox_SubRuns, + ] + self.enabled_list_widgets(list_widgets=list_widgets, + enabled=enabled) + def enabled_fitting_widgets(self, enabled=True): list_widgets = [self.parent.ui.groupBox_FittingFunctions, - self.parent.ui.groupBox_SubRuns, ] self.enabled_list_widgets(list_widgets=list_widgets, enabled=enabled) @@ -51,7 +56,8 @@ def update_save_peak_range_widget_status(self): def enabled_1dplot_widgets(self, enabled=True): list_widgets = [self.parent.ui.frame_1dplot, - self.parent.ui.graphicsView_fitResult_frame, + # self.parent.ui.graphicsView_fitResult_frame, + self.parent.ui.graphicsView_fitResult, ] self.enabled_list_widgets(list_widgets=list_widgets, enabled=enabled) @@ -95,11 +101,13 @@ def set_1D_2D_axis_comboboxes(self, with_clear=False, fill_raw=False, fill_fit=F # Set the widgets about viewer: get the sample logs and add the combo boxes for plotting sample_log_names = self.parent._core.reduction_service.get_sample_logs_names(self.parent._project_name, can_plot=True) - GuiUtilities.block_widgets(list_ui=[self.parent.ui.comboBox_xaxisNames, - self.parent.ui.comboBox_yaxisNames, - self.parent.ui.comboBox_yaxisNames_2dplot, - self.parent.ui.comboBox_yaxisNames_2dplot, - self.parent.ui.comboBox_zaxisNames_2dplot]) + + list_ui = [self.parent.ui.comboBox_xaxisNames, + self.parent.ui.comboBox_yaxisNames, + self.parent.ui.comboBox_xaxisNames_2dplot, + self.parent.ui.comboBox_yaxisNames_2dplot, + self.parent.ui.comboBox_zaxisNames_2dplot] + GuiUtilities.block_widgets(list_ui=list_ui) if with_clear: self.parent.ui.comboBox_xaxisNames.clear() @@ -113,25 +121,51 @@ def set_1D_2D_axis_comboboxes(self, with_clear=False, fill_raw=False, fill_fit=F if fill_raw: _list_axis_to_plot = LIST_AXIS_TO_PLOT['raw'] - print("filling raw") - print(_list_axis_to_plot) - print("------------") - self._update_plots_combobox_items(list_axis_to_plot=_list_axis_to_plot) + self._update_plots_1D_combobox_items(list_axis_to_plot=_list_axis_to_plot) if fill_fit: _list_axis_to_plot = LIST_AXIS_TO_PLOT['fit'] - self._update_plots_combobox_items(list_axis_to_plot=_list_axis_to_plot) - - GuiUtilities.unblock_widgets(list_ui=[self.parent.ui.comboBox_xaxisNames, - self.parent.ui.comboBox_yaxisNames]) + self._update_plots_1D_combobox_items(list_axis_to_plot=_list_axis_to_plot) + self._update_plots_2D_combobox_items() - # enabled the 1D and 2D plot widgets - self.enabled_1dplot_widgets(enabled=True) - self.enabled_2dplot_widgets(enabled=True) + GuiUtilities.unblock_widgets(list_ui=list_ui) def initialize_combobox(self): self.initialize_combobox_1d() self.initialize_combobox_2d() + self.initialize_combobox_peak_label() + + def initialize_combobox_peak_label(self): + list_of_labels = self.get_list_of_peak_label() + + list_ui = [self.parent.ui.plot1d_xaxis_peak_label_comboBox, + self.parent.ui.plot1d_yaxis_peak_label_comboBox, + self.parent.ui.plot2d_xaxis_peak_label_comboBox, + self.parent.ui.plot2d_yaxis_peak_label_comboBox, + self.parent.ui.plot2d_zaxis_peak_label_comboBox] + + GuiUtilities.block_widgets(list_ui) + + GuiUtilities.clear_comboboxes(list_ui=list_ui) + GuiUtilities.fill_comboboxes(list_ui=list_ui, list_values=list_of_labels) + + GuiUtilities.unblock_widgets(list_ui) + + @staticmethod + def fill_comboboxes(list_ui=[], list_values=[]): + for _ui in list_ui: + _ui.addItems(list_values) + + @staticmethod + def clear_comboboxes(list_ui=[]): + for _ui in list_ui: + _ui.clear() + + def get_number_of_peak_selected(self): + return len(self.get_list_of_peak_label()) + + def get_list_of_peak_label(self): + return self.parent._ui_graphicsView_fitSetup.list_fit_peak_labels def initialize_combobox_1d(self): _index_xaxis = self.parent.ui.comboBox_xaxisNames.findText(DEFAUT_AXIS['1d']['xaxis']) @@ -140,24 +174,39 @@ def initialize_combobox_1d(self): self.parent.ui.comboBox_yaxisNames.setCurrentIndex(_index_yaxis) def initialize_combobox_2d(self): + list_ui = [self.parent.ui.comboBox_xaxisNames_2dplot, + self.parent.ui.comboBox_yaxisNames_2dplot, + self.parent.ui.comboBox_zaxisNames_2dplot] + GuiUtilities.__block_widgets(list_ui, True) _index_xaxis = self.parent.ui.comboBox_xaxisNames_2dplot.findText(DEFAUT_AXIS['2d']['xaxis']) self.parent.ui.comboBox_xaxisNames_2dplot.setCurrentIndex(_index_xaxis) - _index_yaxis = self.parent.ui.comboBox_xaxisNames_2dplot.findText(DEFAUT_AXIS['2d']['yaxis']) + _index_yaxis = self.parent.ui.comboBox_yaxisNames_2dplot.findText(DEFAUT_AXIS['2d']['yaxis']) self.parent.ui.comboBox_yaxisNames_2dplot.setCurrentIndex(_index_yaxis) - _index_zaxis = self.parent.ui.comboBox_xaxisNames_2dplot.findText(DEFAUT_AXIS['2d']['zaxis']) + _index_zaxis = self.parent.ui.comboBox_zaxisNames_2dplot.findText(DEFAUT_AXIS['2d']['zaxis']) self.parent.ui.comboBox_zaxisNames_2dplot.setCurrentIndex(_index_zaxis) + GuiUtilities.__block_widgets(list_ui, False) - def _update_plots_combobox_items(self, list_axis_to_plot=[]): + def _update_plots_1D_combobox_items(self, list_axis_to_plot=[]): _list_comboboxes = [self.parent.ui.comboBox_xaxisNames, - self.parent.ui.comboBox_yaxisNames, - self.parent.ui.comboBox_xaxisNames_2dplot, - self.parent.ui.comboBox_yaxisNames_2dplot, - self.parent.ui.comboBox_zaxisNames_2dplot] + self.parent.ui.comboBox_yaxisNames] for sample_log in list_axis_to_plot: for _ui in _list_comboboxes: _ui.addItem(sample_log) self.parent._sample_log_name_set.add(sample_log) + def _update_plots_2D_combobox_items(self): + _list_xy_comboboxes = [self.parent.ui.comboBox_xaxisNames_2dplot, + self.parent.ui.comboBox_yaxisNames_2dplot] + _list_z_comboboxes = [self.parent.ui.comboBox_zaxisNames_2dplot] + for sample_log in LIST_AXIS_TO_PLOT['3d_axis']['xy_axis']: + for _ui in _list_xy_comboboxes: + _ui.addItem(sample_log) + self.parent._sample_log_name_set.add(sample_log) + for sample_log in LIST_AXIS_TO_PLOT['3d_axis']['z_axis']: + for _ui in _list_z_comboboxes: + _ui.addItem(sample_log) + self.parent._sample_log_name_set.add(sample_log) + def make_visible_d01d_widgets(self, visible=True): list_ui = [self.parent.ui.label_d01d, self.parent.ui.label_d0units1d, @@ -165,6 +214,12 @@ def make_visible_d01d_widgets(self, visible=True): GuiUtilities.make_visible_ui(list_ui=list_ui, visible=visible) + def make_visible_peak_label_of_1d_widgets(self, visible=True): + list_ui = [self.parent.ui.plot1d_peak_label, + self.parent.ui.plot1d_peak_label_comboBox] + GuiUtilities.make_visible_ui(list_ui=list_ui, + visible=visible) + def make_visible_d02d_widgets(self, visible=True): list_ui = [self.parent.ui.label_d02d, self.parent.ui.label_d0units2d, @@ -173,22 +228,94 @@ def make_visible_d02d_widgets(self, visible=True): visible=visible) def check_axis1d_status(self): - xaxis_selected = str(self.parent.ui.comboBox_xaxisNames.currentText()) - yaxis_selected = str(self.parent.ui.comboBox_yaxisNames.currentText()) - if (xaxis_selected == 'strain') or (yaxis_selected == 'strain'): - self.make_visible_d01d_widgets(True) + # check first if the widgets are enabled + if self.parent.ui.comboBox_xaxisNames.isEnabled(): + xaxis_selected = str(self.parent.ui.comboBox_xaxisNames.currentText()) + yaxis_selected = str(self.parent.ui.comboBox_yaxisNames.currentText()) + if (xaxis_selected == 'strain') or (yaxis_selected == 'strain'): + self.make_visible_d01d_widgets(True) + else: + self.make_visible_d01d_widgets(False) + + if self.get_number_of_peak_selected() < 2: + self.parent.ui.plot1d_xaxis_peak_label_comboBox.setVisible(False) + self.parent.ui.plot1d_yaxis_peak_label_comboBox.setVisible(False) + else: + if xaxis_selected in LIST_AXIS_TO_PLOT['fit']: + self.parent.ui.plot1d_xaxis_peak_label_comboBox.setVisible(True) + else: + self.parent.ui.plot1d_xaxis_peak_label_comboBox.setVisible(False) + + if yaxis_selected in LIST_AXIS_TO_PLOT['fit']: + self.parent.ui.plot1d_yaxis_peak_label_comboBox.setVisible(True) + else: + self.parent.ui.plot1d_yaxis_peak_label_comboBox.setVisible(False) else: self.make_visible_d01d_widgets(False) + self.parent.ui.plot1d_xaxis_peak_label_comboBox.setVisible(False) + self.parent.ui.plot1d_yaxis_peak_label_comboBox.setVisible(False) + + def get_plot1d_axis_peak_label_index(self, is_xaxis=True): + if is_xaxis: + ui = self.parent.ui.plot1d_xaxis_peak_label_comboBox + else: + ui = self.parent.ui.plot1d_yaxis_peak_label_comboBox + + if ui.isVisible(): + return ui.currentIndex() + else: + return 0 + + def get_plot2d_axis_peak_label_index(self, axis='x'): + if axis == 'x': + ui = self.parent.ui.plot2d_xaxis_peak_label_comboBox + elif axis == 'y': + ui = self.parent.ui.plot2d_yaxis_peak_label_comboBox + else: + ui = self.parent.ui.plot2d_zaxis_peak_label_comboBox + + if ui.isVisible(): + return ui.currentIndex() + else: + return 0 def check_axis2d_status(self): - xaxis_selected = str(self.parent.ui.comboBox_xaxisNames_2dplot.currentText()) - yaxis_selected = str(self.parent.ui.comboBox_yaxisNames_2dplot.currentText()) - zaxis_selected = str(self.parent.ui.comboBox_zaxisNames_2dplot.currentText()) - if (xaxis_selected == 'strain') or (yaxis_selected == 'strain') or \ - (zaxis_selected == 'strain'): - self.make_visible_d02d_widgets(True) + + if self.parent.ui.comboBox_xaxisNames_2dplot.isEnabled(): + xaxis_selected = str(self.parent.ui.comboBox_xaxisNames_2dplot.currentText()) + yaxis_selected = str(self.parent.ui.comboBox_yaxisNames_2dplot.currentText()) + zaxis_selected = str(self.parent.ui.comboBox_zaxisNames_2dplot.currentText()) + if (xaxis_selected == 'strain') or (yaxis_selected == 'strain') or \ + (zaxis_selected == 'strain'): + self.make_visible_d02d_widgets(True) + else: + self.make_visible_d02d_widgets(False) + + if self.get_number_of_peak_selected() < 2: + self.parent.ui.plot2d_xaxis_peak_label_comboBox.setVisible(False) + self.parent.ui.plot2d_yaxis_peak_label_comboBox.setVisible(False) + self.parent.ui.plot2d_zaxis_peak_label_comboBox.setVisible(False) + else: + if xaxis_selected in LIST_AXIS_TO_PLOT['3d_axis']: + self.parent.ui.plot2d_xaxis_peak_label_comboBox.setVisible(True) + else: + self.parent.ui.plot2d_xaxis_peak_label_comboBox.setVisible(False) + + if yaxis_selected in LIST_AXIS_TO_PLOT['3d_axis']: + self.parent.ui.plot2d_yaxis_peak_label_comboBox.setVisible(True) + else: + self.parent.ui.plot2d_yaxis_peak_label_comboBox.setVisible(False) + + if zaxis_selected in LIST_AXIS_TO_PLOT['fit']: + self.parent.ui.plot2d_zaxis_peak_label_comboBox.setVisible(True) + else: + self.parent.ui.plot2d_zaxis_peak_label_comboBox.setVisible(False) + else: self.make_visible_d02d_widgets(False) + self.parent.ui.plot2d_xaxis_peak_label_comboBox.setVisible(False) + self.parent.ui.plot2d_yaxis_peak_label_comboBox.setVisible(False) + self.parent.ui.plot2d_zaxis_peak_label_comboBox.setVisible(False) def reset_peak_range_table(self): nbr_row = self.parent.ui.peak_range_table.rowCount() @@ -210,6 +337,14 @@ def fill_peak_range_table(self, list_fit_peak_ranges=[], _label = QTableWidgetItem(list_fit_peak_labels[_index]) self.parent.ui.peak_range_table.setItem(_index, 2, _label) + @staticmethod + def get_row_selected(table_ui=None): + selection = table_ui.selectedRanges() + if len(selection) > 0: + return selection[0].topRow() + else: + return None + @staticmethod def make_visible_ui(list_ui=[], visible=True): for _ui in list_ui: diff --git a/pyrs/interface/peak_fitting/load.py b/pyrs/interface/peak_fitting/load.py index bea5cc79c..a90217451 100644 --- a/pyrs/interface/peak_fitting/load.py +++ b/pyrs/interface/peak_fitting/load.py @@ -42,6 +42,7 @@ def load(self, project_file=None): o_gui.initialize_fitting_slider(max=len(sub_run_list)) o_gui.set_1D_2D_axis_comboboxes(with_clear=True, fill_raw=True) + o_gui.enabled_1dplot_widgets(enabled=True) o_gui.initialize_combobox() def __set_up_project_name(self, project_file=""): diff --git a/pyrs/interface/peak_fitting/plot.py b/pyrs/interface/peak_fitting/plot.py index 3a9bfcfc3..bdac96def 100644 --- a/pyrs/interface/peak_fitting/plot.py +++ b/pyrs/interface/peak_fitting/plot.py @@ -1,8 +1,13 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility import numpy as np +from matplotlib import cm +from mpl_toolkits.mplot3d import Axes3D # noqa: F401 from pyrs.interface.gui_helper import parse_integers from pyrs.interface.gui_helper import pop_message from pyrs.dataobjects import HidraConstants +from pyrs.interface.peak_fitting.gui_utilities import GuiUtilities +from pyrs.interface.peak_fitting.data_retriever import DataRetriever from pyrs.interface.peak_fitting.config import LIST_AXIS_TO_PLOT @@ -20,7 +25,7 @@ def plot_diff_data(self, plot_model=True): # gather the information try: scan_log_index_list = parse_integers(str(self.parent.ui.lineEdit_listSubRuns.text())) - except RuntimeError as run_err: + except RuntimeError: pop_message(self, "Unable to parse the string", message_type='error') return @@ -85,6 +90,13 @@ def plot_diff_and_fitted_data(self, sub_run_number, plot_model): y_array = self.parent.fit_result.fitted.readY(sub_run_index) self.parent._ui_graphicsView_fitSetup.plot_fitted_data(x_array, y_array) + err_x_array = self.parent.fit_result.difference.readX(sub_run_index) + err_y_array = self.parent.fit_result.difference.readY(sub_run_index) + self.parent._ui_graphicsView_fitSetup.plot_fitting_diff_data(x_axis=err_x_array, y_axis=err_y_array) + # self.parent._ui_graphicsView_fitSetup.plot_model_data(diff_data_set=model_data_set, + # model_label='fit', + # residual_set=residual_data_set) + # # Plot fitted model data # model_data_set = None # if plot_model: @@ -114,87 +126,145 @@ def plot_scan(self, value=None): self.parent.ui.label_SubRunsValue.setText('{}'.format(scan_value)) + def plot_2d(self): + + o_gui = GuiUtilities(parent=self.parent) + x_axis_name = str(self.parent.ui.comboBox_xaxisNames_2dplot.currentText()) + y_axis_name = str(self.parent.ui.comboBox_yaxisNames_2dplot.currentText()) + z_axis_name = str(self.parent.ui.comboBox_zaxisNames_2dplot.currentText()) + + x_axis_peak_index = o_gui.get_plot2d_axis_peak_label_index(axis='x') + y_axis_peak_index = o_gui.get_plot2d_axis_peak_label_index(axis='y') + z_axis_peak_index = o_gui.get_plot2d_axis_peak_label_index(axis='z') + + o_data_retriever = DataRetriever(parent=self.parent) + + axis_x_data, axis_x_error = o_data_retriever.get_data(name=x_axis_name, peak_index=x_axis_peak_index) + axis_y_data, axis_y_error = o_data_retriever.get_data(name=y_axis_name, peak_index=y_axis_peak_index) + axis_z_data, axis_z_error = o_data_retriever.get_data(name=z_axis_name, peak_index=z_axis_peak_index) + + array_dict = self.format_3D_axis_data(axis_x=axis_x_data, axis_y=axis_y_data, axis_z=axis_z_data) + x_axis = array_dict['x_axis'] + y_axis = array_dict['y_axis'] + z_axis = array_dict['z_axis'] + + if len(x_axis) < 2: + return + + self.parent.ui.graphicsView_plot2D.ax.clear() + if self.parent.ui.graphicsView_plot2D.colorbar: + self.parent.ui.graphicsView_plot2D.colorbar.remove() + self.parent.ui.graphicsView_plot2D.colorbar = None + + if self.parent.ui.radioButton_contour.isChecked(): + + self.parent.ui.graphicsView_plot2D.ax = self.parent.ui.graphicsView_plot2D.figure.gca() + my_plot = self.parent.ui.graphicsView_plot2D.ax.contourf(x_axis, y_axis, z_axis) + + self.parent.ui.graphicsView_plot2D.colorbar = \ + self.parent.ui.graphicsView_plot2D.figure.colorbar(my_plot) + self.parent.ui.graphicsView_plot2D._myCanvas.draw() + + self.parent.ui.graphicsView_plot2D.ax.set_xlabel(x_axis_name) + self.parent.ui.graphicsView_plot2D.ax.set_ylabel(y_axis_name) + + elif self.parent.ui.radioButton_3dline.isChecked(): + + x, y = np.meshgrid(x_axis, y_axis) + + self.parent.ui.graphicsView_plot2D.ax = self.parent.ui.graphicsView_plot2D.figure.gca(projection='3d') + my_plot = self.parent.ui.graphicsView_plot2D.ax.plot_surface(x, y, z_axis, + cmap=cm.coolwarm, + linewidth=0, + antialiased=False) + self.parent.ui.graphicsView_plot2D.colorbar = self.parent.ui.graphicsView_plot2D.figure.colorbar(my_plot) + self.parent.ui.graphicsView_plot2D.ax.set_xlabel(x_axis_name) + self.parent.ui.graphicsView_plot2D.ax.set_ylabel(y_axis_name) + self.parent.ui.graphicsView_plot2D.ax.set_zlabel(z_axis_name) + + # maybe look at projections on the wall (matplotlib.org/gallery/mplot3d/contour3d_3.html) + self.parent.ui.graphicsView_plot2D.ax.contour(x, y, z_axis, zdir='z', offset=np.nanmin(z_axis), + cmap=cm.coolwarm) + self.parent.ui.graphicsView_plot2D.ax.contour(x, y, z_axis, zdir='y', offset=np.nanmax(y), + cmap=cm.coolwarm) + self.parent.ui.graphicsView_plot2D.ax.contour(x, y, z_axis, zdir='x', offset=np.nanmax(x), + cmap=cm.coolwarm) + + self.parent.ui.graphicsView_plot2D._myCanvas.draw() + + else: + + self.parent.ui.graphicsView_plot2D.ax = self.parent.ui.graphicsView_plot2D.figure.gca(projection='3d') + self.parent.ui.graphicsView_plot2D.ax.scatter(axis_x_data, axis_y_data, axis_z_data) + self.parent.ui.graphicsView_plot2D._myCanvas.draw() + + self.parent.ui.graphicsView_plot2D.ax.set_xlabel(x_axis_name) + self.parent.ui.graphicsView_plot2D.ax.set_ylabel(y_axis_name) + self.parent.ui.graphicsView_plot2D.ax.set_zlabel(z_axis_name) + + def format_3D_axis_data(self, axis_x=[], axis_y=[], axis_z=[]): + + set_axis_x_data = set(axis_x) + set_axis_y_data = set(axis_y) + + size_set_x = len(set_axis_x_data) + size_set_y = len(set_axis_y_data) + + set_x = list(set_axis_x_data) + set_y = list(set_axis_y_data) + + set_x.sort() + set_y.sort() + + array3d = np.zeros((size_set_x, size_set_y), dtype=np.float32).flatten() + axis_xy_meshgrid = [[_x, _y] for _x in set_x for _y in set_y] + axis_xy_zip = list(zip(axis_x, axis_y)) + + for _xy in axis_xy_meshgrid: + for _index, _xy_zip in enumerate(axis_xy_zip): + if np.array_equal(_xy, _xy_zip): + array3d[_index] = axis_z[_index] + break + + array_3d = np.reshape(array3d, (size_set_x, size_set_y)) + return {'x_axis': list(set_axis_x_data), + 'y_axis': list(set_axis_y_data), + 'z_axis': np.transpose(array_3d)} + def plot_1d(self): self.parent.ui.graphicsView_fitResult.reset_viewer() # get the sample log/meta data name + o_gui = GuiUtilities(parent=self.parent) x_axis_name = str(self.parent.ui.comboBox_xaxisNames.currentText()) y_axis_name = str(self.parent.ui.comboBox_yaxisNames.currentText()) + x_axis_peak_index = o_gui.get_plot1d_axis_peak_label_index(is_xaxis=True) + y_axis_peak_index = o_gui.get_plot1d_axis_peak_label_index(is_xaxis=False) - hidra_workspace = self.parent.hidra_workspace - if x_axis_name == 'Sub-runs': - axis_x = np.array(hidra_workspace.get_sub_runs()) - if y_axis_name == 'Sub-runs': - axis_y = np.array(hidra_workspace.get_sub_runs()) - elif y_axis_name in LIST_AXIS_TO_PLOT['raw'].keys(): - axis_y = hidra_workspace._sample_logs[y_axis_name] - elif y_axis_name in LIST_AXIS_TO_PLOT['fit'].keys(): - print("self.parent.fit_results: {}".format(self.parent.fit_result.fitted)) - # vec_y, vec_x = self.get_function_parameter_data(x_axis_name) - # print("vec_y: " + format(vec_y)) - # print("vec_x: " + format(vec_x)) - # else: - raise NotImplementedError("y_axis choice not supported yet: {}".format(y_axis_name)) - elif x_axis_name in LIST_AXIS_TO_PLOT['raw'].keys(): - axis_x = hidra_workspace._sample_logs[x_axis_name] - if y_axis_name == 'Sub-runs': - axis_y = np.array(hidra_workspace.get_sub_runs()) - elif y_axis_name in LIST_AXIS_TO_PLOT['raw'].keys(): - axis_y = hidra_workspace._sample_logs[y_axis_name] - elif y_axis_name in LIST_AXIS_TO_PLOT['fit'].keys(): - pass - else: - raise NotImplementedError("y_axis choice not supported yet: {}!".format(y_axis_name)) - elif x_axis_name in LIST_AXIS_TO_PLOT['fit'].keys(): - if y_axis_name == 'Sub-runs': - axis_y = np.array(hidra_workspace.get_sub_runs()) - elif y_axis_name in LIST_AXIS_TO_PLOT['raw'].keys(): - axis_y = hidra_workspace._sample_logs[y_axis_name] - elif y_axis_name in LIST_AXIS_TO_PLOT['fit'].keys(): - pass - else: - raise NotImplementedError("y_axis choice not supported yet: {}!".format(y_axis_name)) - else: - raise NotImplementedError("x_axis choice not supported yet: {}!".format(x_axis_name)) + o_data_retriever = DataRetriever(parent=self.parent) - self.parent.ui.graphicsView_fitResult.plot_scatter(axis_x, axis_y, - 'sub_runs', y_axis_name) + is_plot_with_error = False - # return - # - # param_names, param_data = self.parent._core.get_peak_fitting_result(self.parent._project_name, - # 0, - # return_format=dict, - # effective_parameter=False) - # - # return - # - # # Return if sample logs combo box not set - # if x_axis_name == '' and y_axis_name == '': - # return - # - # if x_axis_name in self.parent._function_param_name_set and y_axis_name == HidraConstants.SUB_RUNS: - # vec_y, vec_x = self.get_function_parameter_data(x_axis_name) - # elif y_axis_name in self.parent._function_param_name_set and x_axis_name == HidraConstants.SUB_RUNS: - # vec_x, vec_y = self.get_function_parameter_data(y_axis_name) - # elif x_axis_name in self.parent._function_param_name_set or y_axis_name in \ - # self.parent._function_param_name_set: - # pop_message(self, 'It has not considered how to plot 2 function parameters ' - # '{} and {} against each other' - # ''.format(x_axis_name, y_axis_name), - # message_type='error') - # return - # else: - # vec_x = self.get_meta_sample_data(x_axis_name) - # vec_y = self.get_meta_sample_data(y_axis_name) - # # END-IF-ELSE - # - # if vec_x is None or vec_y is None: - # raise RuntimeError('{} or {} cannot be None ({}, {})' - # ''.format(x_axis_name, y_axis_name, vec_x, vec_y)) - # - # self.parent.ui.graphicsView_fitResult.plot_scatter(vec_x, vec_y, x_axis_name, y_axis_name) + axis_x_data, axis_x_error = o_data_retriever.get_data(name=x_axis_name, peak_index=x_axis_peak_index) + axis_y_data, axis_y_error = o_data_retriever.get_data(name=y_axis_name, peak_index=y_axis_peak_index) + + if ((x_axis_name in LIST_AXIS_TO_PLOT['fit'].keys()) or + (y_axis_name in LIST_AXIS_TO_PLOT['fit'].keys())): + is_plot_with_error = True + + if is_plot_with_error: + self.parent.ui.graphicsView_fitResult.plot_scatter_with_errors(vec_x=axis_x_data, + vec_y=axis_y_data, + vec_x_error=axis_x_error, + vec_y_error=axis_y_error, + x_label=x_axis_name, + y_label=y_axis_name) + else: + self.parent.ui.graphicsView_fitResult.plot_scatter(axis_x_data, + axis_y_data, + x_axis_name, + y_axis_name) def get_function_parameter_data(self, param_name): """ get the parameter function data @@ -202,40 +272,11 @@ def get_function_parameter_data(self, param_name): :return: """ - print(param_name) - # get data key if self.parent._project_name is None: pop_message(self, 'No data loaded', 'error') return - # fitted_peak = self.parent._core._peak_fitting_dict[self.parent._project_name] - # from pyrs.core import peak_profile_utility - - # param_set = fitted_peak.get_effective_parameters_values() - # eff_params_list, sub_run_array, fit_cost_array, eff_param_value_array, eff_param_error_array = param_set - - # retrieve Center: EFFECTIVE_PEAK_PARAMETERS = ['Center', 'Height', 'Intensity', 'FWHM', 'Mixing', 'A0', 'A1'] - # i_center = peak_profile_utility.EFFECTIVE_PEAK_PARAMETERS.index('Center') - # centers = eff_param_value_array[i_center] - - # retrieve Height - # i_height = peak_profile_utility.EFFECTIVE_PEAK_PARAMETERS.index('Height') - # heights = eff_param_value_array[i_height] - - # retrieve intensity - # i_intensity = peak_profile_utility.EFFECTIVE_PEAK_PARAMETERS.index('Intensity') - # intensities = eff_param_value_array[i_intensity] - - # retrieve FWHM - # i_fwhm = peak_profile_utility.EFFECTIVE_PEAK_PARAMETERS.index('FWHM') - # fwhms = eff_param_value_array[i_fwhm] - - # import pprint - # pprint.pprint("fwhms: {}".format(fwhms)) - - return - param_names, param_data = self.parent._core.get_peak_fitting_result(self.parent._project_name, 0, return_format=dict, diff --git a/pyrs/interface/pyrs_main.py b/pyrs/interface/pyrs_main.py index e5a9a06ec..9424220e9 100644 --- a/pyrs/interface/pyrs_main.py +++ b/pyrs/interface/pyrs_main.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from qtpy import QtCore from qtpy.QtWidgets import QMainWindow, QSizePolicy, QWidget, QLabel, QMenuBar, QToolBar, QStatusBar, QGridLayout from pyrs.utilities import load_ui @@ -25,7 +26,7 @@ def __init__(self, parent=None): Init :param parent: """ - from ui.workspaceviewwidget import WorkspaceViewWidget + from .ui.workspaceviewwidget import WorkspaceViewWidget QMainWindow.__init__(self) @@ -80,7 +81,7 @@ def __init__(self): self.ui = load_ui('pyrsmain.ui', baseinstance=self) # define - self.ui.pushButton_manualReduction.clicked.connect(self.do_reduce_manually) + self.ui.pushButton_manualReduction.clicked.connect(self.do_launch_manual_reduction) self.ui.pushButton_fitPeaks.clicked.connect(self.do_launch_fit_peak_window) self.ui.pushButton_launchTextureAnalysis.clicked.connect(self.do_launch_texture_window) self.ui.pushButton_launchStrainStressCalculation.clicked.connect(self.do_launch_strain_stress_window) @@ -187,17 +188,14 @@ def do_launch_texture_window(self): return - def do_reduce_manually(self): + def do_launch_manual_reduction(self): """ launch manual data reduction window :return: """ - # core - reduction_core = pyrscore.PyRsCore() - if self.manual_reduction_window is None: self.manual_reduction_window = manualreductionwindow.ManualReductionWindow(self) - self.manual_reduction_window.setup_window(reduction_core) + # self.manual_reduction_window.setup_window() # show self.manual_reduction_window.show() diff --git a/pyrs/interface/slicersetupwindow.py b/pyrs/interface/slicersetupwindow.py index 2366ec6a2..2333c4e9d 100644 --- a/pyrs/interface/slicersetupwindow.py +++ b/pyrs/interface/slicersetupwindow.py @@ -4,6 +4,7 @@ # Migrated from LogPickerWindow.py in PyVDrive # ######################################################################## +from __future__ import (absolute_import, division, print_function) # python3 compatibility import os import numpy import ManualSlicerSetupDialog @@ -210,7 +211,7 @@ def _set_main_slice_method(self): # enable to disable if self.ui.radioButton_timeSlicer.isChecked(): # time slicer - print '[DB...BAT] Turn on 1; Turn off 2 and 3' + print('[DB...BAT] Turn on 1; Turn off 2 and 3') self.ui.groupBox_sliceSetupAuto.setEnabled(True) self.ui.groupBox_slicerSetupManual.setEnabled(False) @@ -234,7 +235,7 @@ def _set_main_slice_method(self): elif self.ui.radioButton_logValueSlicer.isChecked(): # log value slicer - print '[DB...BAT] Turn on 2; Turn off 1 and 3' + print('[DB...BAT] Turn on 2; Turn off 1 and 3') self.ui.groupBox_sliceSetupAuto.setEnabled(True) self.ui.groupBox_slicerSetupManual.setEnabled(False) @@ -263,7 +264,7 @@ def _set_main_slice_method(self): else: # manual slicer - print '[DB...BAT] Turn on 3; Turn off 1 and 2' + print('[DB...BAT] Turn on 3; Turn off 1 and 2') self.ui.groupBox_sliceSetupAuto.setEnabled(False) self.ui.groupBox_slicerSetupManual.setEnabled(True) @@ -390,7 +391,7 @@ def do_load_next_log_frame(self): delta_points = self.get_data_size_to_load() if delta_points <= 0: # no point to load - print '[DB INFO] calculated delta-points = %d < 0. No loading' % delta_points + print('[DB INFO] calculated delta-points = %d < 0. No loading' % delta_points) # get the file mts_log_file = str(self.ui.lineEdit_logFileName.text()) @@ -724,7 +725,7 @@ def evt_switch_slicer_method(self): self._mutexLockSwitchSliceMethod = True # Only 1 situation requires - print '[DB...BAT] called!' + print('[DB...BAT] called!') self._set_main_slice_method() # Unlock diff --git a/pyrs/interface/strain_stress_calculation/strainstresscalwindow.py b/pyrs/interface/strain_stress_calculation/strainstresscalwindow.py index 8ee71d314..adda00e65 100644 --- a/pyrs/interface/strain_stress_calculation/strainstresscalwindow.py +++ b/pyrs/interface/strain_stress_calculation/strainstresscalwindow.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from qtpy.QtWidgets import QMainWindow from pyrs.utilities import load_ui @@ -10,6 +11,7 @@ import numpy import pyrs.interface.dialogs import datetime +import six # setup of constants SLICE_VIEW_RESOLUTION = 0.0001 @@ -648,7 +650,7 @@ def create_new_session(self, session_name, is_plane_strain, is_plane_stress): :return: """ # get new session's name - if isinstance(session_name, unicode) or session_name.__class__.__name__.count('QString') == 1: + if isinstance(session_name, six.string_types) or session_name.__class__.__name__.count('QString') == 1: session_name = str(session_name).strip() else: checkdatatypes.check_string_variable('Strain/stress session name', session_name) @@ -882,9 +884,9 @@ def _convert_grid_dict_to_vectors(ss_direction, grid_param_value_dict): assert grid_param_value_dict[grid_pos]['dir'] == ss_direction, 'Direction not matching' grid_vec = numpy.array(grid_list) - print grid_vec.shape + print(grid_vec.shape) value_vec = numpy.array(value_list) - print value_vec.shape + print(value_vec.shape) return grid_vec, value_vec diff --git a/pyrs/interface/ui/diffdataviews.py b/pyrs/interface/ui/diffdataviews.py index 5fc355259..507a2d451 100644 --- a/pyrs/interface/ui/diffdataviews.py +++ b/pyrs/interface/ui/diffdataviews.py @@ -1,10 +1,11 @@ -from mplgraphicsview1d import MplGraphicsView1D -from mplgraphicsview2d import MplGraphicsView2D -from mplgraphicsviewpolar import MplGraphicsPolarView +from __future__ import (absolute_import, division, print_function) # python3 compatibility +from .mplgraphicsview1d import MplGraphicsView1D +from .mplgraphicsview2d import MplGraphicsView2D +from .mplgraphicsviewpolar import MplGraphicsPolarView import numpy as np -import mplgraphicsviewpolar -import slice_view_widget -from mplfitplottingwidget import MplFitPlottingWidget +from . import mplgraphicsviewpolar +from . import slice_view_widget +from .mplfitplottingwidget import MplFitPlottingWidget class Diffraction2DPlot(MplGraphicsPolarView): @@ -101,12 +102,18 @@ def plot_detector_view(self, detector_counts, info_tuple): title = 'Sub run {}, Mask: {}'.format(sub_run_number, mask_id) self.set_title(title) - image_size = int(np.sqrt(detector_counts.shape[0])) - if image_size * image_size != detector_counts.shape[0]: - raise RuntimeError('Detector with {} counts cannot convert to a 2D view without further information' - ''.format(detector_counts.shape)) - - counts2d = detector_counts.reshape(image_size, image_size) + # Resize detector count array + if len(detector_counts.shape) == 1: + # 1D array: reshape to 2D + image_size = int(np.sqrt(detector_counts.shape[0])) + if image_size * image_size != detector_counts.shape[0]: + raise RuntimeError('Detector with {} counts cannot convert to a 2D view without further information' + ''.format(detector_counts.shape)) + counts2d = detector_counts.reshape(image_size, image_size) + else: + # As Is + counts2d = detector_counts + image_size = counts2d.shape[0] # Rotate 90 degree to match the view: IT COULD BE WRONG! self.add_2d_plot(np.rot90(counts2d), 0, image_size, 0, image_size) @@ -186,6 +193,33 @@ def plot_diffraction(self, vec_x, vec_y, x_label, y_label, line_label=None, keep self._last_line_reference = ref_id self._current_x_axis_name = x_label + def plot_scatter_with_errors(self, vec_x=None, vec_y=None, + vec_x_error=None, vec_y_error=None, + x_label="", y_label=""): + + # # TODO Future: Need to write use cases. Now it is for demo + # # It is not allowed to plot 2 plot with different x-axis + # if self._last_line_reference is not None: + # if x_label != self.get_label_x(): + # self.reset_viewer() + + # plot data in a scattering plot with auto re-scale + ref_id = self.add_plot(vec_x, + vec_y, + x_err=vec_x_error, + y_err=vec_y_error, + line_style='', + marker='*', + markersize=6, + color='red', + x_label=x_label, + y_label=y_label) + + # TODO - 20181101 - Enable after auto_scale is fixed: self.auto_rescale() + self._line_reference_list.append(ref_id) + self._last_line_reference = ref_id + self._current_x_axis_name = x_label + def plot_scatter(self, vec_x, vec_y, x_label, y_label): """ plot figure in scatter-style :param vec_x: @@ -218,7 +252,6 @@ def plot_scatter(self, vec_x, vec_y, x_label, y_label): def reset_viewer(self): """ reset current graphics view - :return: """ # reset all the management data structures self._last_line_reference = None @@ -265,10 +298,9 @@ def plot_experiment_data(self, diff_data_set, data_reference): self._last_diff_reference = ref_id def plot_fitted_data(self, x_array, y_array): - ref_id = self.plot_data(data_set=(x_array, y_array), - line_label='-', - color='black') - print(ref_id) + self.plot_data(data_set=(x_array, y_array), + line_label='-', + color='black') def plot_model_data(self, diff_data_set, model_label, residual_set): """Plot model data from fitting @@ -314,6 +346,9 @@ def plot_diff_data(self, diff_data_set, data_reference): self._diff_reference_list.append(ref_id) self._last_diff_reference = ref_id + def plot_fitting_diff_data(self, x_axis, y_axis): + self._last_fit_diff_reference = self._myCanvas.add_plot_lower_axis((x_axis, y_axis)) + def plot_fit_diff(self, diff_data_set, model_data_set): """ plot the difference between fitted diffraction data (model) and experimental data diff --git a/pyrs/interface/ui/mantidipythonwidget.py b/pyrs/interface/ui/mantidipythonwidget.py index 8008ebc98..586cf2c74 100644 --- a/pyrs/interface/ui/mantidipythonwidget.py +++ b/pyrs/interface/ui/mantidipythonwidget.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from mantid.simpleapi import mtd from pygments.lexer import RegexLexer import threading @@ -35,7 +36,7 @@ from qtconsole.rich_ipython_widget import RichIPythonWidget from qtconsole.inprocess import QtInProcessKernelManager print('mantidipythonwidget: import PyQt5') -except ImportError as import_err: +except ImportError: # This is PyQt4 compatible from IPython.qt.console.rich_ipython_widget import RichIPythonWidget from IPython.qt.inprocess import QtInProcessKernelManager diff --git a/pyrs/interface/ui/mplfitplottingwidget.py b/pyrs/interface/ui/mplfitplottingwidget.py index dc687aa12..bc349a30f 100644 --- a/pyrs/interface/ui/mplfitplottingwidget.py +++ b/pyrs/interface/ui/mplfitplottingwidget.py @@ -241,6 +241,18 @@ def plot_data(self, data_set, color=None, line_label=''): if color is None: color = self._get_next_color() + self._color = color + self._data_set = data_set + self._line_label = line_label + self._diff_data_set = data_set + + data_line_id = self._myCanvas.add_plot_upper_axis(data_set, line_color=color, label=line_label) + self._data_line_list.append(data_line_id) + + def plot_diff_data(self, data_set, color=None, line_label=''): + if color is None: + color = self._get_next_color() + self._color = color self._data_set = data_set self._line_label = line_label @@ -249,7 +261,7 @@ def plot_data(self, data_set, color=None, line_label=''): self._data_line_list.append(data_line_id) def plot_data_with_fitting_ranges(self): - self.clear_canvas() + # self.clear_canvas() for _peak_label in self.list_peak_labels_matplotlib_id: _peak_label.remove() @@ -389,10 +401,6 @@ def _setup_legend(self, location='best', font_size=10): handles, labels = self._data_subplot.get_legend_handles_labels() self._data_subplot.legend(handles, labels, loc=location, fontsize=font_size) - # END-IF - - return - def add_plot_lower_axis(self, data_set): """ add a plot to the lower axis as residual @@ -454,7 +462,7 @@ def add_plot_upper_axis(self, data_set, label, line_color, line_marker='.', mark self._data_subplot.set_ylim([0, max_value_with_offset]) # set/update legend - self._setup_legend() + # self._setup_legend() # Register line_id = self._line_index # share the line ID counter with main axis diff --git a/pyrs/interface/ui/mplgraphicsview1d.py b/pyrs/interface/ui/mplgraphicsview1d.py index 43c8a6bb3..4879ba140 100644 --- a/pyrs/interface/ui/mplgraphicsview1d.py +++ b/pyrs/interface/ui/mplgraphicsview1d.py @@ -67,7 +67,7 @@ def __init__(self, parent, row_size=None, col_size=None, tool_bar=True): self._vBox.addWidget(self._myToolBar) def button_clicked_in_canvas(self, event): - print("button pressed here!") + # print("button pressed here!") print("-> %s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f".format(event.dblclick, event.button, event.x, event.y, event.xdata, event.ydata)) @@ -183,27 +183,11 @@ def add_arrow(self, start_x, start_y, stop_x, stop_y): # Add default to (0, 0) figure self._myCanvas.add_arrow(0, 0, start_x, start_y, stop_x, stop_y) - def add_plot(self, vec_x, vec_y, y_err=None, row_index=0, col_index=0, is_right=False, + def add_plot(self, vec_x, vec_y, x_err=None, y_err=None, row_index=0, col_index=0, is_right=False, color=None, label='', x_label=None, y_label=None, marker=None, markersize=2, line_style=None, line_width=1, show_legend=True): - """Add a plot in 1D - :param row_index: - :param col_index: - :param is_right: flag to show whether the line is added to the right axis - :param vec_x: - :param vec_y: - :param y_err: - :param color: - :param label: - :param x_label: - :param y_label: - :param marker: - :param line_style: - :param line_width: - :param show_legend: - :return: string/integer as line reference ID - """ + # check whether the input is empty if len(vec_y) == 0: print('[WARNING] Input is an empty vector set') @@ -222,7 +206,8 @@ def add_plot(self, vec_x, vec_y, y_err=None, row_index=0, col_index=0, is_right= else: # plot at the main axis - line_key = self._myCanvas.add_main_plot(row_index, col_index, vec_x, vec_y, y_err, color, label, x_label, + line_key = self._myCanvas.add_main_plot(row_index, col_index, vec_x, vec_y, x_err, y_err, + color, label, x_label, y_label, marker, line_style, line_width, show_legend, markersize=markersize) @@ -347,6 +332,8 @@ def clear_all_lines(self, row_number=None, col_number=None, include_main=True, i self._isZoomed = False self._homeXYLimit = None + self._myCanvas.clear_canvas() + def clear_canvas(self): """ Clear canvas """ @@ -741,9 +728,13 @@ def add_arrow(self, row_index, col_index, start_x, start_y, stop_x, stop_y): return - def add_main_plot(self, row_index, col_index, vec_x, vec_y, y_err=None, color=None, label='', + def add_main_plot(self, row_index, col_index, + vec_x, vec_y, + x_err=None, y_err=None, + color=None, label='', x_label=None, y_label=None, - marker=None, line_style=None, line_width=1, show_legend=True, markersize=4,): + marker=None, line_style=None, + line_width=1, show_legend=True, markersize=4,): """Add 1D plot on the main side (left) :param row_index: numpy array X :param col_index: numpy array Y @@ -768,7 +759,7 @@ def add_main_plot(self, row_index, col_index, vec_x, vec_y, y_err=None, color=No if isinstance(vec_x, np.ndarray) is False or isinstance(vec_y, np.ndarray) is False: raise NotImplementedError('Input vec_x or vec_y for addPlot() must be numpy.array,' 'but not {} and {}.'.format(type(vec_x), type(vec_y))) - plot_error = y_err is not None + plot_error = (y_err is not None) or (x_err is not None) if plot_error is True: if isinstance(y_err, np.ndarray) is False: raise NotImplementedError('Input y_err must be either None or numpy.array.') @@ -803,10 +794,25 @@ def add_main_plot(self, row_index, col_index, vec_x, vec_y, y_err=None, color=No self.axes_main[row_index, col_index].autoscale() else: - r = self.axes_main[row_index, col_index].errorbar(vec_x, vec_y, - yerr=y_err, color=color, marker=marker, - linestyle=line_style, label=label, - linewidth=line_width) + if y_err is None: + r = self.axes_main[row_index, col_index].errorbar(vec_x, vec_y, + xerr=x_err, + color=color, marker=marker, + linestyle=line_style, label=label, + linewidth=line_width) + elif x_err is None: + r = self.axes_main[row_index, col_index].errorbar(vec_x, vec_y, + yerr=y_err, + color=color, marker=marker, + linestyle=line_style, label=label, + linewidth=line_width) + else: + # both error + r = self.axes_main[row_index, col_index].errorbar(vec_x, vec_y, + xerr=x_err, yerr=y_err, + color=color, marker=marker, + linestyle=line_style, label=label, + linewidth=line_width) # set aspect ratio self.axes_main[row_index, col_index].set_aspect('auto') @@ -819,16 +825,18 @@ def add_main_plot(self, row_index, col_index, vec_x, vec_y, y_err=None, color=No if show_legend: self._setup_legend(row_index, col_index, is_main=True) - # Register + # # Register line_key = self._line_count if len(r) == 1: + # single line plot self._mainLineDict[row_index, col_index][line_key] = r[0] self._line_count += 1 else: - msg = 'Return from plot is a %d-tuple: %s.. \n' % (len(r), r) - for i_r in range(len(r)): - msg += 'r[%d] = %s\n' % (i_r, str(r[i_r])) - raise NotImplementedError(msg) + # line with error bars + # TODO FIXME - need to find out the returned's data structure + self._mainLineDict[row_index, col_index][line_key] = r + self._line_count += 1 + # END-IF # Flush/commit self.draw() @@ -1228,13 +1236,24 @@ def remove_plot_1d(self, row_index, col_index, plot_key, apply_change=True): raise RuntimeError('Plot key {} does not exist in main line dict. Available plot keys are ' '{}'.format(plot_key, self._mainLineDict.keys())) try: - self.axes_main[row_index, col_index].lines.remove(self._mainLineDict[(row_index, col_index)][plot_key]) + line_instance = self._mainLineDict[(row_index, col_index)][plot_key] + if isinstance(line_instance, tuple): + # line with error bar and etc + line_instance[0].remove() + for line in line_instance[1]: + line.remove() + for line in line_instance[2]: + line.remove() + else: + # single line w/o error bar + self.axes_main[row_index, col_index].lines.remove(line_instance) except ValueError as r_error: error_message = 'Unable to remove to 1D line {} (ID={}) due to {}' \ ''.format(self._mainLineDict[(row_index, col_index)][plot_key], plot_key, str(r_error)) raise RuntimeError(error_message) # remove the plot key from dictionary del self._mainLineDict[(row_index, col_index)][plot_key] + # set the flag to be on main/left or right is_on_main = True elif (row_index, col_index) in self._rightLineDict and plot_key in self._rightLineDict[row_index, col_index]: @@ -1251,7 +1270,7 @@ def remove_plot_1d(self, row_index, col_index, plot_key, apply_change=True): is_on_main = False else: # unable to locate plot key - raise RuntimeError('Line with ID %s is not recorded.' % plot_key) + raise RuntimeError('Line with ID %s is not recorded.'.format(plot_key)) self._setup_legend(row_index, col_index, location='best', font_size=self._legend_font_size, is_main=is_on_main) diff --git a/pyrs/interface/ui/mplgraphicsviewcontourplot.py b/pyrs/interface/ui/mplgraphicsviewcontourplot.py new file mode 100644 index 000000000..6b0bc437c --- /dev/null +++ b/pyrs/interface/ui/mplgraphicsviewcontourplot.py @@ -0,0 +1,961 @@ +import matplotlib.image +from matplotlib.figure import Figure +import os +import numpy as np + +from qtpy.QtCore import Signal +from qtpy.QtWidgets import QWidget, QSizePolicy, QVBoxLayout +from mantidqt.MPLwidgets import FigureCanvasQTAgg as FigureCanvas +from mantidqt.MPLwidgets import NavigationToolbar2QT as NavigationToolbar2 + + +class MplGraphicsViewContourPlot(QWidget): + """ A combined graphics view including matplotlib canvas and + a navigation tool bar + + Note: Merged with HFIR_Powder_Reduction.MplFigureCAnvas + """ + + def __init__(self, parent): + """ Initialization + """ + # Initialize parent + super(MplGraphicsViewContourPlot, self).__init__(parent) + + # set up canvas + self.figure = Figure() + self.colorbar = None + # self._myCanvas = Qt4Mpl2DCanvas(self.figure) + self._myCanvas = FigureCanvas(self.figure) + self._myToolBar = NavigationToolbar2(self._myCanvas, self) + # self._myToolBar = MyNavigationToolbar(self._myCanvas, self) + + # state of operation + self._isZoomed = False + # X and Y limit with home button + self._homeXYLimit = None + + self.ax = self.figure.add_subplot(111) + + # set up layout + self._vBox = QVBoxLayout(self) + # self._vBox.addWidget(self._myCanvas) + self._vBox.addWidget(self._myCanvas) + self._vBox.addWidget(self._myToolBar) + + self._arrowList = list() + + self._hasImage = False + + def add_arrow(self, start_x, start_y, stop_x, stop_y): + """ + + :param start_x: + :param start_y: + :param stop_x: + :param stop_y: + :return: + """ + arrow = self._myCanvas.add_arrow(start_x, start_y, stop_x, stop_y) + self._arrowList.append(arrow) + + return + + def add_image(self, image_file_name): + """ Add an image by file + """ + # check + if os.path.exists(image_file_name) is False: + raise NotImplementedError("Image file %s does not exist." % image_file_name) + + self._myCanvas.add_image_file(image_file_name) + + return + + def add_2d_plot(self, array2d, x_min, x_max, y_min, y_max, y_tick_label=None, plot_type='image'): + """ + Add a 2D image to canvas + :param array2d: numpy 2D array + :param x_min: + :param x_max: + :param y_min: + :param y_max: + :param y_tick_label: + :return: + """ + # obsoleted: self._myCanvas.addPlot2D(array2d, x_min, x_max, y_min, y_max, hold_prev_image, y_tick_label) + + if plot_type == 'image': + self._myCanvas.add_image_plot(array2d, x_min, x_max, y_min, y_max, yticklabels=y_tick_label) + elif plot_type == 'image file': + self._myCanvas.add_image_file() + elif plot_type == 'scatter': + raise NotImplementedError('plot_type="scatter" has not been implemented') + else: + raise RuntimeError('Do not know how to add_2d_plot(..., plot_type="{}")'.format(plot_type)) + + self._hasImage = True + + def has_image_on_canvas(self): + # TODO/ASAP + return self._hasImage + + def update_2d_plot(self): + pass + + def canvas(self): + """Get the canvas + """ + return self._myCanvas + + def clear_canvas(self): + """Clear canvas + """ + # clear all the records + # to-be-filled + + # about zoom + # to-be-filled + + r = self._myCanvas.clear_canvas() + + return r + + def draw(self): + """Draw to commit the change + """ + return self._myCanvas.draw() + + def evt_toolbar_home(self): + # turn off zoom mode + self._isZoomed = False + + return + + def evt_view_updated(self): + """Event handling as canvas size updated + """ + # # update the indicator + # new_x_range = self.getXLimit() + # new_y_range = self.getYLimit() + # + # self._myIndicatorsManager.update_indicators_range(new_x_range, new_y_range) + # for indicator_key in self._myIndicatorsManager.get_live_indicator_ids(): + # canvas_line_id = self._myIndicatorsManager.get_canvas_line_index(indicator_key) + # data_x, data_y = self._myIndicatorsManager.get_data(indicator_key) + # self.updateLine(canvas_line_id, data_x, data_y) + # # END-FOR + + return + + def evt_zoom_released(self): + """event for zoom is release + """ + # record home XY limit if it is never zoomed + if self._isZoomed is False: + self._homeXYLimit = list(self.getXLimit()) + self._homeXYLimit.extend(list(self.getYLimit())) + # END-IF + + # set the state of being zoomed + self._isZoomed = True + + return + + def getLastPlotIndexKey(self): + """Get ... + """ + return self._myCanvas.getLastPlotIndexKey() + + def getXLimit(self): + """Get limit of Y-axis + :return: 2-tuple as xmin, xmax + """ + return self._myCanvas.getXLimit() + + def getYLimit(self): + """Get limit of Y-axis + """ + return self._myCanvas.getYLimit() + + def get_y_min(self): + """ + Get the minimum Y value of the plots on canvas + :return: + """ + if len(self._statDict) == 0: + return 1E10 + + line_id_list = self._statDict.keys() + min_y = self._statDict[line_id_list[0]][2] + for i_plot in range(1, len(line_id_list)): + if self._statDict[line_id_list[i_plot]][2] < min_y: + min_y = self._statDict[line_id_list[i_plot]][2] + + return min_y + + def get_y_max(self): + """ + Get the maximum Y value of the plots on canvas + :return: + """ + if len(self._statDict) == 0: + return -1E10 + + line_id_list = self._statDict.keys() + max_y = self._statDict[line_id_list[0]][3] + for i_plot in range(1, len(line_id_list)): + if self._statDict[line_id_list[i_plot]][3] > max_y: + max_y = self._statDict[line_id_list[i_plot]][3] + + return max_y + + def move_indicator(self, line_id, dx, dy): + """ + Move the indicator line in horizontal + :param line_id: + :param dx: + :return: + """ + # Shift value + self._myIndicatorsManager.shift(line_id, dx=dx, dy=dy) + + # apply to plot on canvas + if self._myIndicatorsManager.get_line_type(line_id) < 2: + # horizontal or vertical + canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(line_id) + vec_x, vec_y = self._myIndicatorsManager.get_data(line_id) + self._myCanvas.updateLine(ikey=canvas_line_index, vecx=vec_x, vecy=vec_y) + else: + # 2-way + canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(line_id) + h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(line_id) + + self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=h_vec_set[0], vecy=h_vec_set[1]) + self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=v_vec_set[0], vecy=v_vec_set[1]) + + return + + def remove_indicator(self, indicator_key): + """ Remove indicator line + :param indicator_key: + :return: + """ + # + plot_id = self._myIndicatorsManager.get_canvas_line_index(indicator_key) + self._myCanvas.remove_plot_1d(plot_id) + self._myIndicatorsManager.delete(indicator_key) + + return + + def remove_line(self, line_id): + """ Remove a line + :param line_id: + :return: + """ + # remove line + self._myCanvas.remove_plot_1d(line_id) + + # remove the records + if line_id in self._statDict: + del self._statDict[line_id] + del self._my1DPlotDict[line_id] + else: + del self._statRightPlotDict[line_id] + + return + + def set_indicator_position(self, line_id, pos_x, pos_y): + """ Set the indicator to new position + :param line_id: indicator ID + :param pos_x: + :param pos_y: + :return: + """ + # Set value + self._myIndicatorsManager.set_position(line_id, pos_x, pos_y) + + # apply to plot on canvas + if self._myIndicatorsManager.get_line_type(line_id) < 2: + # horizontal or vertical + canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(line_id) + vec_x, vec_y = self._myIndicatorsManager.get_data(line_id) + self._myCanvas.updateLine(ikey=canvas_line_index, vecx=vec_x, vecy=vec_y) + else: + # 2-way + canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(line_id) + h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(line_id) + + self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=h_vec_set[0], vecy=h_vec_set[1]) + self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=v_vec_set[0], vecy=v_vec_set[1]) + + return + + def removePlot(self, ikey): + """ + """ + return self._myCanvas.remove_plot_1d(ikey) + + def updateLine(self, ikey, vecx=None, vecy=None, linestyle=None, linecolor=None, marker=None, markercolor=None): + """ + update a line's set up + Parameters + ---------- + ikey + vecx + vecy + linestyle + linecolor + marker + markercolor + + Returns + ------- + + """ + # check + assert isinstance(ikey, int), 'Line key must be an integer.' + assert ikey in self._my1DPlotDict, 'Line with ID %d is not on canvas. ' % ikey + + return self._myCanvas.updateLine(ikey, vecx, vecy, linestyle, linecolor, marker, markercolor) + + def update_indicator(self, i_key, color): + """ + Update indicator with new color + :param i_key: + :param vec_x: + :param vec_y: + :param color: + :return: + """ + if self._myIndicatorsManager.get_line_type(i_key) < 2: + # horizontal or vertical + canvas_line_index = self._myIndicatorsManager.get_canvas_line_index(i_key) + self._myCanvas.updateLine(ikey=canvas_line_index, vecx=None, vecy=None, linecolor=color) + else: + # 2-way + canvas_line_index_h, canvas_line_index_v = self._myIndicatorsManager.get_canvas_line_index(i_key) + # h_vec_set, v_vec_set = self._myIndicatorsManager.get_2way_data(i_key) + + self._myCanvas.updateLine(ikey=canvas_line_index_h, vecx=None, vecy=None, linecolor=color) + self._myCanvas.updateLine(ikey=canvas_line_index_v, vecx=None, vecy=None, linecolor=color) + + return + + def get_canvas(self): + """ + get canvas + Returns: + + """ + return self._myCanvas + + def set_title(self, title, color='black'): + """ + set title to canvas + :param title: + :param color: + :return: + """ + self._myCanvas.set_title(title, color) + + return + + def setXYLimit(self, xmin=None, xmax=None, ymin=None, ymax=None): + """ Set X-Y limit automatically + """ + self._myCanvas.axes.set_xlim([xmin, xmax]) + self._myCanvas.axes.set_ylim([ymin, ymax]) + + self._myCanvas.draw() + + return + + +class Qt4Mpl2DCanvas(FigureCanvas): + """ A customized Qt widget for matplotlib figure. + It can be used to replace GraphicsView of QtGui + """ + + def __init__(self, parent): + """ Initialization + """ + # Instantiating matplotlib Figure + self.fig = Figure() + self.fig.patch.set_facecolor('white') + + # initialization + super(Qt4Mpl2DCanvas, self).__init__(self.fig) + + # set up axis/subplot (111) only for 2D + self.axes = self.fig.add_subplot(111, polar=False) # return: matplotlib.axes.AxesSubplot + + # plot management + self._scatterPlot = None + self._imagePlot = None + + # Initialize parent class and set parent + FigureCanvas.__init__(self, self.fig) + self.setParent(parent) + + # Set size policy to be able to expanding and resizable with frame + FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) + FigureCanvas.updateGeometry(self) + + # Variables to manage all lines/subplot + self._lineDict = {} + self._lineIndex = 0 + + # legend and color bar + self._colorBar = None + self._isLegendOn = False + self._legendFontSize = 8 + + return + + def update_image(self, array2d): + """ + + @return: + """ + + self._imagePlot.set_data(array2d) + + self._flush() + + return + + def has_plot(self, plot_type): + if plot_type == 'image' and self._imagePlot is not None: + return True + + return False + + @property + def is_legend_on(self): + """ + check whether the legend is shown or hide + Returns: + boolean + """ + return self._isLegendOn + + def add_arrow(self, start_x, start_y, stop_x, stop_y): + """ + Example: (0, 0, 0.5, 0.5, head_width=0.05, head_length=0.1, fc='k', ec='k') + @param start_x: + @param start_y: + @param stop_x: + @param stop_y: + @return: + """ + head_width = 0.05 + head_length = 0.1 + fc = 'k' + ec = 'k' + + arrow = self.axes.arrrow(start_x, start_y, stop_x, stop_y, head_width, + head_length, fc, ec) + + return arrow + + def add_contour_plot(self, vec_x, vec_y, matrix_z): + """ add a contour plot + Example: reduced data: vec_x: d-values, vec_y: run numbers, matrix_z, matrix for intensities + :param vec_x: a list of a vector for X axis + :param vec_y: a list of a vector for Y axis + :param matrix_z: + :return: + """ + # check input + # TODO - labor + assert isinstance(vec_x, list) or isinstance(vec_x, np.ndarray), 'blabla' + assert isinstance(vec_y, list) or isinstance(vec_y, np.ndarray), 'blabla' + assert isinstance(matrix_z, np.ndarray), 'blabla' + + # create mesh grid + grid_x, grid_y = np.meshgrid(vec_x, vec_y) + # + # print '[DB...BAT] Grid X and Grid Y size: ', grid_x.shape, grid_y.shape + + # check size + assert grid_x.shape == matrix_z.shape, 'Size of X (%d) and Y (%d) must match size of Z (%s).' \ + '' % (len(vec_x), len(vec_y), matrix_z.shape) + + # # Release the current image + # self.axes.hold(False) + + # Do plot: resolution on Z axis (color bar is set to 100) + self.axes.clear() + contour_plot = self.axes.contourf(grid_x, grid_y, matrix_z, 100) + + labels = [item.get_text() for item in self.axes.get_yticklabels()] + + # Set the Y-axis ticks + # TODO/ISSUE/NOW: how to make this part more flexible + if len(labels) == 2 * len(vec_y) - 1: + new_labels = [''] * len(labels) + for i in range(len(vec_y)): + new_labels[i * 2] = '%d' % int(vec_y[i]) + self.axes.set_yticklabels(new_labels) + # END-IF + + # explicitly set aspect ratio of the image + self.axes.set_aspect('auto') + + # Set color bar. plt.colorbar() does not work! + if self._colorBar is None: + # set color map type + contour_plot.set_cmap('spectral') + self._colorBar = self.fig.colorbar(contour_plot) + else: + self._colorBar.update_bruteforce(contour_plot) + + # Flush... + self._flush() + + def add_image_plot(self, array2d, xmin, xmax, ymin, ymax, yticklabels=None): + """ + + @param array2d: + @param xmin: + @param xmax: + @param ymin: + @param ymax: + @param holdprev: + @param yticklabels: list of string for y ticks + @return: + """ + # check + assert isinstance(array2d, np.ndarray), 'blabla' + assert len(array2d.shape) == 2, 'blabla' + + # show image + self._imagePlot = self.axes.imshow(array2d, extent=[xmin, xmax, ymin, ymax], interpolation='none') + + print(self._imagePlot, type(self._imagePlot)) + + # set y ticks as an option: + if yticklabels is not None: + # it will always label the first N ticks even image is zoomed in + print("[FIXME]: The way to set up the Y-axis ticks is wrong!") + self.axes.set_yticklabels(yticklabels) + + # explicitly set aspect ratio of the image + self.axes.set_aspect('auto') + + # set up color bar + # # Set color bar. plt.colorbar() does not work! + # if self._colorBar is None: + # # set color map type + # imgplot.set_cmap('spectral') + # self._colorBar = self.fig.colorbar(imgplot) + # else: + # self._colorBar.update_bruteforce(imgplot) + + # Flush... + self._flush() + + return + + def add_image_file(self, imagefilename): + """Add an image by file + """ + # set aspect to auto mode + self.axes.set_aspect('auto') + + img = matplotlib.image.imread(str(imagefilename)) + # lum_img = img[:,:,0] + # FUTURE : refactor for image size, interpolation and origin + imgplot = self.axes.imshow(img, extent=[0, 1000, 800, 0], interpolation='none', origin='lower') + + # Set color bar. plt.colorbar() does not work! + if self._colorBar is None: + # set color map type + imgplot.set_cmap('spectral') + self._colorBar = self.fig.colorbar(imgplot) + else: + self._colorBar.update_bruteforce(imgplot) + + self._flush() + + return + + def add_scatter_plot(self, array2d): + """ + add scatter plot + @param array2d: + @return: + """ + # check! + # TODO - 20180801 - Make it work + assert isinstance(array2d, np.ndarray), 'blabla' + if array2d.shape[1] < 3: + raise RuntimeError('blabla3') + + if False: + array2d = np.ndarray(shape=(100, 3), dtype='float') + array2d[0][0] = 0 + array2d[0][1] = 0 + array2d[0][2] = 1 + + import random + for index in range(1, 98): + x = random.randint(1, 255) + y = random.randint(1, 255) + z = random.randint(1, 20000) + array2d[index][0] = float(x) + array2d[index][1] = float(y) + array2d[index][2] = float(z) + + array2d[99][0] = 255 + array2d[99][1] = 255 + array2d[99][2] = 1 + + self._scatterPlot = self.axes.scatter(array2d[:, 0], array2d[:, 1], s=80, c=array2d[:, 2], + marker='s') + + return + + def clear_canvas(self): + """ Clear data including lines and image from canvas + """ + # clear the image for next operation + # self.axes.hold(False) + + # clear image + self.axes.cla() + # Try to clear the color bar + if len(self.fig.axes) > 1: + self.fig.delaxes(self.fig.axes[1]) + self._colorBar = None + # This clears the space claimed by color bar but destroys sub_plot too. + self.fig.clear() + # Re-create subplot + self.axes = self.fig.add_subplot(111) + self.fig.subplots_adjust(bottom=0.15) + + # flush/commit + self._flush() + + return + + def decrease_legend_font_size(self): + """ + reset the legend with the new font size + Returns: + + """ + # minimum legend font size is 2! return if it already uses the smallest font size. + if self._legendFontSize <= 2: + return + + self._legendFontSize -= 1 + self._setup_legend(font_size=self._legendFontSize) + + self.draw() + + return + + def getLastPlotIndexKey(self): + """ Get the index/key of the last added line + """ + return self._lineIndex - 1 + + def getPlot(self): + """ reture figure's axes to expose the matplotlib figure to PyQt client + """ + return self.axes + + def getXLimit(self): + """ Get limit of Y-axis + """ + return self.axes.get_xlim() + + def getYLimit(self): + """ Get limit of Y-axis + """ + return self.axes.get_ylim() + + def hide_legend(self): + """ + hide the legend if it is not None + Returns: + + """ + if self.axes.legend() is not None: + # set visible to be False and re-draw + self.axes.legend().set_visible(False) + self.draw() + + self._isLegendOn = False + + return + + def increase_legend_font_size(self): + """ + reset the legend with the new font size + Returns: + + """ + self._legendFontSize += 1 + + self._setup_legend(font_size=self._legendFontSize) + + self.draw() + + return + + def setXYLimit(self, xmin, xmax, ymin, ymax): + """ + """ + # for X + xlims = self.axes.get_xlim() + xlims = list(xlims) + if xmin is not None: + xlims[0] = xmin + if xmax is not None: + xlims[1] = xmax + self.axes.set_xlim(xlims) + + # for Y + ylims = self.axes.get_ylim() + ylims = list(ylims) + if ymin is not None: + ylims[0] = ymin + if ymax is not None: + ylims[1] = ymax + self.axes.set_ylim(ylims) + + # try draw + self.draw() + + return + + def set_title(self, title, color): + """ + set the tile to an axis + :param title: + :param color + :return: + """ + # check input + assert isinstance(title, str), 'Title must be a string but not a {0}.'.format(type(title)) + assert isinstance(color, str), 'Color must be a string but not a {0}.'.format(type(color)) + + self.setWindowTitle(title) + + self.draw() + + return + + def show_legend(self): + """ + show the legend if the legend is not None + Returns: + + """ + if self.axes.legend() is not None: + # set visible to be True and re-draw + # self.axes.legend().set_visible(True) + self._setup_legend(font_size=self._legendFontSize) + self.draw() + + # set flag on + self._isLegendOn = True + + return + + def _flush(self): + """ A dirty hack to flush the image + """ + w, h = self.get_width_height() + self.resize(w + 1, h) + self.resize(w, h) + + return + + def _setup_legend(self, location='best', font_size=10): + """ + Set up legend + self.axes.legend(): Handler is a Line2D object. Lable maps to the line object + Args: + location: + font_size: + + Returns: + + """ + allowed_location_list = [ + "best", + "upper right", + "upper left", + "lower left", + "lower right", + "right", + "center left", + "center right", + "lower center", + "upper center", + "center"] + + # Check legend location valid or not + if location not in allowed_location_list: + location = 'best' + + handles, labels = self.axes.get_legend_handles_labels() + self.axes.legend(handles, labels, loc=location, fontsize=font_size) + + self._isLegendOn = True + + return + +# END-OF-CLASS (MplGraphicsView) + + +class MyNavigationToolbar(NavigationToolbar2): + """ A customized navigation tool bar attached to canvas + Note: + * home, left, right: will not disable zoom/pan mode + * zoom and pan: will turn on/off both's mode + + Other methods + * drag_pan(self, event): event handling method for dragging canvas in pan-mode + """ + NAVIGATION_MODE_NONE = 0 + NAVIGATION_MODE_PAN = 1 + NAVIGATION_MODE_ZOOM = 2 + + # This defines a signal called 'home_button_pressed' that takes 1 boolean + # argument for being in zoomed state or not + home_button_pressed = Signal() + + # This defines a signal called 'canvas_zoom_released' + canvas_zoom_released = Signal() + + def __init__(self, parent, canvas): + """ Initialization + built-in methods + - drag_zoom(self, event): triggered during holding the mouse and moving + """ + NavigationToolbar2.__init__(self, canvas, canvas) + + # parent + self._myParent = parent + # tool bar mode + self._myMode = MyNavigationToolbar.NAVIGATION_MODE_NONE + + # connect the events to parent + self.home_button_pressed.connect(self._myParent.evt_toolbar_home) + self.canvas_zoom_released.connect(self._myParent.evt_zoom_released) + + return + + @property + def is_zoom_mode(self): + """ + check whether the tool bar is in zoom mode + Returns + ------- + + """ + return self._myMode == MyNavigationToolbar.NAVIGATION_MODE_ZOOM + + def get_mode(self): + """ + :return: integer as none/pan/zoom mode + """ + return self._myMode + + # Overriding base's methods + def draw(self): + """ + Canvas is drawn called by pan(), zoom() + :return: + """ + NavigationToolbar2.draw(self) + + self._myParent.evt_view_updated() + + return + + def home(self, *args): + """ + + Parameters + ---------- + args + + Returns + ------- + + """ + # call super's home() method + NavigationToolbar2.home(self, args) + + # send a signal to parent class for further operation + self.home_button_pressed.emit() + + return + + def pan(self, *args): + """ + + :param args: + :return: + """ + NavigationToolbar2.pan(self, args) + + if self._myMode == MyNavigationToolbar.NAVIGATION_MODE_PAN: + # out of pan mode + self._myMode = MyNavigationToolbar.NAVIGATION_MODE_NONE + else: + # into pan mode + self._myMode = MyNavigationToolbar.NAVIGATION_MODE_PAN + + return + + def zoom(self, *args): + """ + Turn on/off zoom (zoom button) + :param args: + :return: + """ + NavigationToolbar2.zoom(self, args) + + if self._myMode == MyNavigationToolbar.NAVIGATION_MODE_ZOOM: + # out of zoom mode + self._myMode = MyNavigationToolbar.NAVIGATION_MODE_NONE + else: + # into zoom mode + self._myMode = MyNavigationToolbar.NAVIGATION_MODE_ZOOM + + return + + def release_zoom(self, event): + """ + override zoom released method + Parameters + ---------- + event + + Returns + ------- + + """ + self.canvas_zoom_released.emit() + + NavigationToolbar2.release_zoom(self, event) + + return + + def _update_view(self): + """ + view update called by home(), back() and forward() + :return: + """ + NavigationToolbar2._update_view(self) + + self._myParent.evt_view_updated() + + return diff --git a/pyrs/interface/ui/rstables.py b/pyrs/interface/ui/rstables.py index a7faf6b20..487eb9206 100644 --- a/pyrs/interface/ui/rstables.py +++ b/pyrs/interface/ui/rstables.py @@ -1,7 +1,9 @@ # Module containing extended TableWidgets for PyRS project -import NTableWidget +from __future__ import (absolute_import, division, print_function) # python3 compatibility +from . import NTableWidget from pyrs.utilities import checkdatatypes import numpy +from qtpy import QtWidgets class FitResultTable(NTableWidget.NTableWidget): @@ -36,7 +38,8 @@ def __init__(self, parent): self._column_names = None - return + self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) def init_exp(self, sub_run_number_list): """ diff --git a/pyrs/interface/ui/slice_view_widget.py b/pyrs/interface/ui/slice_view_widget.py index a4d1a8cf8..6d4e1f8a3 100644 --- a/pyrs/interface/ui/slice_view_widget.py +++ b/pyrs/interface/ui/slice_view_widget.py @@ -1,5 +1,6 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility import matplotlib.tri as tri -import sliceviewwidgets +from . import sliceviewwidgets import numpy as np from qtpy.QtWidgets import QGridLayout, QWidget, QSizePolicy from pyrs.interface.ui.mplconstants import MplBasicColors @@ -292,10 +293,6 @@ def plot_scatter(self, vec_x, vec_y, flush=True): if flush: self.main_canvas._flush() - return - -# END-CLASS-DEF - class IndicatorManager(object): """ Manager for all indicator lines diff --git a/pyrs/interface/ui/sliceviewwidgets.py b/pyrs/interface/ui/sliceviewwidgets.py index 5582e2d3d..37aa05823 100644 --- a/pyrs/interface/ui/sliceviewwidgets.py +++ b/pyrs/interface/ui/sliceviewwidgets.py @@ -209,7 +209,6 @@ def add_contour_plot(self, vec_x, vec_y, matrix_z): time_f = time.time() print('[DB...BAT] Stopping time (float) = {}. Used {} second for plotting'.format(time_f, time_f - time_s)) - labels = [item.get_text() for item in self.axes.get_yticklabels()] # TODO/ISSUE/NOW: how to make this part more flexible diff --git a/pyrs/interface/ui/workspaceviewwidget.py b/pyrs/interface/ui/workspaceviewwidget.py index e68367750..ed43cac7e 100644 --- a/pyrs/interface/ui/workspaceviewwidget.py +++ b/pyrs/interface/ui/workspaceviewwidget.py @@ -3,14 +3,15 @@ # General-purposed plotting window # ######################################################################## +from __future__ import (absolute_import, division, print_function) # python3 compatibility import datetime from qtpy import QtCore from qtpy.QtWidgets import QWidget, QVBoxLayout from pyrs.utilities import load_ui -from mplgraphicsview import MplGraphicsView -import NTableWidget as baseTable +from .mplgraphicsview import MplGraphicsView +from . import NTableWidget as baseTable from pyrs.interface.ui.mantidipythonwidget import MantidIPythonWidget from mantid.api import mtd diff --git a/pyrs/peaks/fit_factory.py b/pyrs/peaks/fit_factory.py index a32bb51df..0382b491e 100644 --- a/pyrs/peaks/fit_factory.py +++ b/pyrs/peaks/fit_factory.py @@ -11,7 +11,7 @@ class FitEngineFactory(object): """ @staticmethod def getInstance(hidraworkspace, peak_function_name, background_function_name, - out_of_plane_angle=None, engine_name='mantid'): + wavelength, out_of_plane_angle=None, engine_name='mantid'): """Get instance of Peak fitting engine """ engine_name = str(engine_name).lower() @@ -22,7 +22,7 @@ def getInstance(hidraworkspace, peak_function_name, background_function_name, if engine_name == 'mantid': return MantidPeakFitEngine(hidraworkspace, peak_function_name, background_function_name, - out_of_plane_angle) + wavelength=wavelength, out_of_plane_angle=out_of_plane_angle) elif engine_name == 'pyrs': # this is known to be broken 2010-01-09 return ScipyPeakFitEngine(hidraworkspace, out_of_plane_angle) diff --git a/pyrs/peaks/mantid_fit_peak.py b/pyrs/peaks/mantid_fit_peak.py index 824961772..07c33b961 100644 --- a/pyrs/peaks/mantid_fit_peak.py +++ b/pyrs/peaks/mantid_fit_peak.py @@ -13,9 +13,10 @@ class MantidPeakFitEngine(PeakFitEngine): '''Peak fitting engine by calling mantid''' - def __init__(self, hidraworkspace, peak_function_name, background_function_name, out_of_plane_angle): + def __init__(self, hidraworkspace, peak_function_name, background_function_name, out_of_plane_angle, wavelength): super(MantidPeakFitEngine, self).__init__(hidraworkspace, peak_function_name, - background_function_name, out_of_plane_angle) + background_function_name, wavelength=wavelength, + out_of_plane_angle=out_of_plane_angle) # configure logging for this class self._log = Logger(__name__) @@ -34,11 +35,8 @@ def fit_peaks(self, peak_tag, x_min, x_max): # add in extra parameters for starting values kwargs = {} if self._peak_function == PeakShape.PSEUDOVOIGT: - # max_intensity = PseudoVoigt.cal_intensity(max_estimated_height, hidra_fwhm, default_mixing) - intensity = self._mtd_wksp.extractY().max() - self._mtd_wksp.extractY().mean() # TODO improve this - - kwargs['PeakParameterNames'] = 'Mixing, Intensity' # FWHM also available - kwargs['PeakParameterValues'] = '{}, {}'.format(0.6, intensity) # mixing agreed upon default + kwargs['PeakParameterNames'] = 'Mixing' # FWHM and Intensity also available + kwargs['PeakParameterValues'] = '0.6' # mixing agreed upon default # Fit peak by Mantid.FitPeaks fit_return = FitPeaks(InputWorkspace=self._mtd_wksp, @@ -99,7 +97,7 @@ def convert_from_table_to_arrays(table_ws): # TODO put this in mantid_helper # Create PeakCollection instance peak_object = PeakCollection(peak_tag=peak_tag, peak_profile=self._peak_function, - background_type=self._background_function) + background_type=self._background_function, wavelength=self._wavelength) peak_object.set_peak_fitting_values(self._subruns, peak_params_value_array, peak_params_error_array, fit_cost_array) diff --git a/pyrs/peaks/peak_collection.py b/pyrs/peaks/peak_collection.py index 4992b3b31..98768e404 100644 --- a/pyrs/peaks/peak_collection.py +++ b/pyrs/peaks/peak_collection.py @@ -11,7 +11,7 @@ class PeakCollection(object): """ Object to contain peak parameters (names and values) of a collection of peaks for sub runs """ - def __init__(self, peak_tag, peak_profile, background_type): + def __init__(self, peak_tag, peak_profile, background_type, wavelength=np.nan, d_reference=np.nan): """Initialization Parameters @@ -30,6 +30,8 @@ def __init__(self, peak_tag, peak_profile, background_type): # Init other parameters self._peak_profile = PeakShape.getShape(peak_profile) self._background_type = BackgroundFunction.getFunction(background_type) + self._wavelength = wavelength + self._d_reference = d_reference # sub run numbers: 1D array self._sub_run_array = SubRuns() @@ -82,14 +84,6 @@ def background_type(self): def sub_runs(self): return self._sub_run_array - @property - def parameters_values(self): - return self._params_value_array - - @property - def parameters_errors(self): - return self._params_error_array - @property def fitting_costs(self): return self._fit_cost_array @@ -154,6 +148,44 @@ def set_peak_fitting_values(self, subruns, parameter_values, parameter_errors, f self._fit_cost_array = np.copy(fit_costs) self.__set_fit_status() + def get_d_reference(self): + return self._d_reference + + def set_d_reference(self, values=np.nan): + """Set d reference values + + Parameters + ---------- + values : + 1D numpy array or floats + + Returns + ------- + + """ + if isinstance(values, np.ndarray): + self._d_reference = values + else: + self._d_reference = np.array([values] * self._sub_run_array.size) + + def get_strain(self): + """get strain values and uncertainties + + Parameters + ---------- + values : + 1D numpy array or floats + + Returns + ------- + tuple + A two-item tuple containing the strain and its uncertainty. + """ + d_fitted, d_fitted_error = self.get_dspacing_center() + strain = (d_fitted - self._d_reference)/self._d_reference + strain_error = d_fitted_error/self._d_reference + return strain, strain_error + def get_native_params(self): return self._params_value_array, self._params_error_array @@ -169,6 +201,23 @@ def get_effective_params(self): return eff_values, eff_errors + def get_dspacing_center(self): + r""" + peak center in unit of d spacing. + + Returns + ------- + tuple + A two-item tuple containing the peak center and its uncertainty. + """ + effective_values, effective_errors = self.get_effective_params() + theta_center_value = 0.5 * effective_values['Center'] + theta_center_error = 0.5 * effective_errors['Center'] + dspacing_center = 0.5 * self._wavelength / np.sin(np.deg2rad(theta_center_value)) + dspacing_center_error = 0.5 * self._wavelength * abs(np.cos(theta_center_value)) * theta_center_error /\ + (np.sin(np.deg2rad(theta_center_value))**2) + return dspacing_center, dspacing_center_error + def get_integrated_intensity(self): pass diff --git a/pyrs/peaks/peak_fit_engine.py b/pyrs/peaks/peak_fit_engine.py index 7ebdac605..d6189ae8d 100644 --- a/pyrs/peaks/peak_fit_engine.py +++ b/pyrs/peaks/peak_fit_engine.py @@ -18,7 +18,7 @@ class FitResult(namedtuple('FitResult', 'peakcollections fitted difference')): class PeakFitEngine(object): '''This is the virtual base class as the fitting frame''' def __init__(self, hidraworkspace, peak_function_name, background_function_name, - out_of_plane_angle): + wavelength, out_of_plane_angle): '''It is not expected that any subclass will need to implement this method. It is designed to unpack all of the information necessary for fitting.''' # configure logging for this class @@ -35,6 +35,8 @@ def __init__(self, hidraworkspace, peak_function_name, background_function_name, self._peak_function = PeakShape.getShape(peak_function_name) self._background_function = BackgroundFunction.getFunction(background_function_name) + self._wavelength = wavelength + @staticmethod def _check_fit_range(x_min, x_max): scalar = True diff --git a/pyrs/projectfile/file_object.py b/pyrs/projectfile/file_object.py index 7606820dc..b2d972613 100644 --- a/pyrs/projectfile/file_object.py +++ b/pyrs/projectfile/file_object.py @@ -209,7 +209,7 @@ def read_user_masks(self, mask_dict): """ # Get mask names except default mask try: - mask_names = self._project_h5[HidraConstants.MASK][HidraConstants.DETECTOR_MASK].keys() + mask_names = sorted(self._project_h5[HidraConstants.MASK][HidraConstants.DETECTOR_MASK].keys()) except KeyError: # return if the file has an old format return @@ -236,10 +236,12 @@ def read_mask_detector_array(self, mask_name): Returns ------- + numpy.ndarray + mask array """ try: - mask_array = self._project_h5[HidraConstants.MASK][HidraConstants.DETECTOR_MASK][mask_name] + mask_array = self._project_h5[HidraConstants.MASK][HidraConstants.DETECTOR_MASK][mask_name].value except KeyError as key_err: if HidraConstants.MASK not in self._project_h5.keys(): err_msg = 'Project file {} does not have "{}" entry. Its format is not up-to-date.' \ @@ -372,7 +374,8 @@ def read_diffraction_intensity_vector(self, mask_id, sub_run): if mask_id is None: mask_id = HidraConstants.REDUCED_MAIN - checkdatatypes.check_string_variable('Mask ID', mask_id, self._project_h5[HidraConstants.REDUCED_DATA].keys()) + checkdatatypes.check_string_variable('Mask ID', mask_id, + list(self._project_h5[HidraConstants.REDUCED_DATA].keys())) # Get data to return if sub_run is None: @@ -395,7 +398,7 @@ def read_diffraction_masks(self): """ Get the list of masks """ - masks = self._project_h5[HidraConstants.REDUCED_DATA].keys() + masks = list(self._project_h5[HidraConstants.REDUCED_DATA].keys()) # Clean up data entry '2theta' (or '2Theta') if HidraConstants.TWO_THETA in masks: @@ -656,15 +659,15 @@ def write_peak_fit_result(self, fitted_peaks): single_peak_entry.create_dataset(HidraConstants.PEAK_PARAMS_ERROR, data=peak_errors) def read_wavelengths(self): - """ - Get calibrated wave length + """Get calibrated wave length + Returns ------- - Float or None - Calibrated wave length or No wave length ever set + Float + Calibrated wave length. NaN for wave length is not ever set """ # Init wave length - wl = None + wl = numpy.nan # Get the node try: @@ -672,8 +675,8 @@ def read_wavelengths(self): if HidraConstants.WAVELENGTH in mono_node: wl = self._project_h5[HidraConstants.INSTRUMENT][HidraConstants.MONO][HidraConstants.WAVELENGTH].value if wl.shape[0] == 0: - # empty numpy array: no data - wl = None + # empty numpy array: no data. keep as nan + pass elif wl.shape[0] == 1: # 1 calibrated wave length wl = wl[0] @@ -702,7 +705,15 @@ def write_wavelength(self, wave_length): """ checkdatatypes.check_float_variable('Wave length', wave_length, (0, 1000)) + # Create 'monochromator setting' node if it does not exist + if HidraConstants.MONO not in list(self._project_h5[HidraConstants.INSTRUMENT].keys()): + self._project_h5[HidraConstants.INSTRUMENT].create_group(HidraConstants.MONO) + + # Get node and write value wl_entry = self._project_h5[HidraConstants.INSTRUMENT][HidraConstants.MONO] + # delete the dataset if it does exist to replace + if HidraConstants.WAVELENGTH in list(wl_entry.keys()): + del wl_entry[HidraConstants.WAVELENGTH] wl_entry.create_dataset(HidraConstants.WAVELENGTH, data=numpy.array([wave_length])) def read_efficiency_correction(self): diff --git a/pyrs/split_sub_runs/__init__.py b/pyrs/split_sub_runs/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pyrs/split_sub_runs/load_split_sub_runs.py b/pyrs/split_sub_runs/load_split_sub_runs.py deleted file mode 100644 index 98ada789e..000000000 --- a/pyrs/split_sub_runs/load_split_sub_runs.py +++ /dev/null @@ -1,585 +0,0 @@ -# This is a numpy version for prototyping to load NeXus and split events for sub runs -# by numpy and hdf5 -import h5py -import numpy as np -from pyrs.utilities import checkdatatypes -from pyrs.dataobjects.constants import HidraConstants -import datetime -import os -from mantid.simpleapi import Load, LoadMask -from mantid.kernel import BoolTimeSeriesProperty, FloatFilteredTimeSeriesProperty, FloatTimeSeriesProperty -from mantid.kernel import Int32TimeSeriesProperty, Int64TimeSeriesProperty, Int32FilteredTimeSeriesProperty,\ - Int64FilteredTimeSeriesProperty - - -HIDRA_PIXEL_NUMBER = 1024**2 - - -def load_split_nexus_python(nexus_name, mask_file_name): - """Wrapping method to load and split event NeXus by sub runs - - Parameters - ---------- - nexus_name : str - NeXus file name - mask_file_name: str or None - Mantid mask file in XML - - Returns - ------- - dict, dict - counts, sample logs - - """ - # Init processor - nexus_processor = NexusProcessor(nexus_name) - - # Mask detector - if mask_file_name: - mask_array = nexus_processor.process_mask(mask_file_name) - else: - mask_array = None - - # Get splitters - sub_run_times, sub_runs = nexus_processor.get_sub_run_times_value() - - # Split counts - time_split_start = datetime.datetime.now() - sub_run_counts = nexus_processor.split_events_sub_runs(sub_run_times, sub_runs, mask_array) - time_split_end = datetime.datetime.now() - print('[INFO] Sub run splitting duration = {} second from {} to {}' - ''.format((time_split_end - time_split_start).total_seconds(), time_split_start, time_split_end)) - - # Split logs - sample_logs = nexus_processor.split_sample_logs(sub_run_times, sub_runs) - log_split_end = datetime.datetime.now() - print('[INFO] Sub run splitting duration = {} second from {} to {}' - ''.format((log_split_end - time_split_end).total_seconds(), time_split_end, log_split_end)) - - return sub_run_counts, sample_logs, mask_array - - -class NexusProcessor(object): - """ - Class to process NeXus files in PyRS - """ - def __init__(self, nexus_file_name): - """Init - - Parameters - ---------- - nexus_file_name : str - HB2B Event NeXus file name - """ - self._nexus_name = nexus_file_name - - # check and load - checkdatatypes.check_file_name(nexus_file_name, True, False, False, 'HB2B event NeXus file name') - - # Create workspace for sample logs and optionally mask - # Load file - self._ws_name = os.path.basename(self._nexus_name).split('.')[0] - self._workspace = Load(Filename=self._nexus_name, MetaDataOnly=True, OutputWorkspace=self._ws_name) - - # Load: this h5 will be opened all the time - self._nexus_h5 = h5py.File(nexus_file_name, 'r') - - # Check number of neutron events. Raise exception if there is no neutron event - if self._nexus_h5['entry']['bank1_events']['total_counts'].value[0] < 0.1: - # no counts - self._nexus_h5.close() - raise RuntimeError('Run {} has no count. Proper reduction requires the run to have count' - ''.format(self._nexus_name)) - elif len(self._nexus_h5['entry']['DASlogs']['scan_index']['value'].value) == 1: - # Get the time and value of 'scan_index' (entry) in H5 - scan_index_times = self._nexus_h5['entry']['DASlogs']['scan_index']['time'].value - scan_index_value = self._nexus_h5['entry']['DASlogs']['scan_index']['value'].value - # close file - self._nexus_h5.close() - raise RuntimeError('Sub scan (time = {}, value = {}) is not valid' - ''.format(scan_index_times, scan_index_value)) - - def __del__(self): - """Destructor - - Close h5py.File if it is not closed - - Returns - ------- - None - - """ - self._nexus_h5.close() - - def process_mask(self, mask_file_name): - """ - - Parameters - ---------- - mask_file_name - - Returns - ------- - - """ - # Check input - checkdatatypes.check_file_name(mask_file_name, True, False, False, 'Mask XML file') - if self._workspace is None: - raise RuntimeError('Meta data only workspace {} does not exist'.format(self._ws_name)) - - # Load mask XML to workspace - mask_ws_name = os.path.basename(mask_file_name.split('.')[0]) - mask_ws = LoadMask(Instrument='nrsf2', InputFile=mask_file_name, RefWorkspace=self._workspace, - OutputWorkspace=mask_ws_name) - - # Extract mask out - # get the Y array from mask workspace: shape = (1048576, 1) - mask_array = mask_ws.extractY().flatten() - # in Mantid's mask workspace, 1 stands for mask (value cleared), 0 stands for non-mask (value kept) - mask_array = 1 - mask_array.astype(int) - - return mask_array - - def get_sub_run_times_value(self): - """Get the sample log (time and value) of sub run (aka scan indexes) - - Returns - ------- - numpy.ndarray, numpy.ndarray - sub run (scan index) times - - """ - # Get the time and value of 'scan_index' (entry) in H5 - scan_index_times = self._nexus_h5['entry']['DASlogs']['scan_index']['time'].value - scan_index_value = self._nexus_h5['entry']['DASlogs']['scan_index']['value'].value - - if scan_index_times.shape[0] <= 1: - raise RuntimeError('Sub scan (time = {}, value = {}) is not valid' - ''.format(scan_index_times, scan_index_value)) - - sub_run_times, sub_runs = self.generate_sub_run_splitter(scan_index_times, scan_index_value) - - # Correct start scan_index time - corrected_value = self.correct_starting_scan_index_time() - if corrected_value is not None: - sub_run_times[0] = corrected_value - - return sub_run_times, sub_runs - - @staticmethod - def generate_sub_run_splitter(scan_index_times, scan_index_value): - """Generate event splitters according to sub runs - - """ - # Init - sub_run_time_list = list() - sub_run_value_list = list() - num_scan_index = scan_index_times.shape[0] - - # Loop through all scan indexes to get the correct splitters - curr_sub_run = 0 - for i_scan in range(num_scan_index): - if scan_index_value[i_scan] != curr_sub_run: - # New run no same as old one: There will be some change! - if curr_sub_run > 0: - # previous run shall be saved: it is ending: record the ending time/current time - sub_run_time_list.append(scan_index_times[i_scan]) - - if scan_index_value[i_scan] > 0: - # new scan index is valid: a new run shall start: record the starting time and sub run value - sub_run_time_list.append(scan_index_times[i_scan]) - sub_run_value_list.append(scan_index_value[i_scan]) - - # Update the curr_sub_run - curr_sub_run = scan_index_value[i_scan] - - # Note: there is one scenario to append 2 and same time stamp: scan index change from i to j, where - # both i and j are larger than 0 - # END-IF - # END-FOR - - # Check the ending - if curr_sub_run > 0: - # In case the stop (scan_index = 0) is not recorded - sub_run_time_list.append(np.nan) - - # Convert from list to array - sub_run_times = np.array(sub_run_time_list) - sub_run_numbers = np.array(sub_run_value_list) - - # Sanity check - if sub_run_times.shape[0] % 2 == 1 or sub_run_times.shape[0] == 0: - raise RuntimeError('Algorithm error: Failed to parse\nTime: {}\nValue: {}.\n' - 'Current resulted time ({}) is incorrect as odd/even' - ''.format(scan_index_times, scan_index_value, sub_run_times)) - - if sub_run_times.shape[0] != sub_run_numbers.shape[0] * 2: - raise RuntimeError('Sub run number {} and sub run times do not match (as twice)' - ''.format(sub_run_numbers, sub_run_times)) - - return sub_run_times, sub_run_numbers - - def correct_starting_scan_index_time(self, abs_tolerance=0.05): - """Correct the DAS-issue for mis-record the first scan_index/sub run before the motor is in position - - This goes through a subset of logs and compares when they actually - get to their specified setpoint, updating the start time for - event filtering. When this is done ``self._starttime`` will have been updated. - - Parameters - ---------- - abs_tolerance: float - When then log is within this absolute tolerance of the setpoint, it is correct - - Returns - ------- - float - Corrected value or None - - """ - # loop through the 'special' logs - start_time = -1E-9 - - for log_name in ['sx', 'sy', 'sz', '2theta', 'omega', 'chi', 'phi']: - if log_name not in self._nexus_h5['entry']['DASlogs'].keys(): - continue # log doesn't exist - not a good one to look at - if log_name + 'Setpoint' not in self._nexus_h5['entry']['DASlogs'].keys(): - continue # log doesn't have a setpoint - not a good one to look at - - # get the observed values of the log - observed = self._nexus_h5['entry']['DASlogs'][log_name]['value'].value - if len(observed) <= 1 or observed.std() <= .5 * abs_tolerance: - continue # don't bother if the log is constant within half of the tolerance - - # look for the setpoint and find when the log first got there - # only look at first setpoint - set_point = self._nexus_h5['entry']['DASlogs'][log_name + 'Setpoint']['value'].value[0] - for i, value in enumerate(observed): - if abs(value - set_point) < abs_tolerance: - # pick the larger of what was found and the previous largest value - start_time = max(self._nexus_h5['entry']['DASlogs'][log_name]['time'].value[i], 0.) - break - - # unset the start time if it is before the actual start of the run - if start_time <= 0: - start_time = None - else: - print('[DEBUG] Shift from start_time = {}' - ''.format(start_time)) - - return start_time - - def split_events_sub_runs(self, sub_run_times, sub_run_values, mask_array): - """Split events by sub runs - - Note: this filters events in the resolution of pulse time. It is same as Mantid.FilterByLogValue - - Parameters - ---------- - sub_run_times: numpy.ndarray - sub run times. T[2n] = sub run start time, T[2n + 1] = sub run stop time - sub_run_values: numpy.ndarray - sub run value: V[n] = sub run number - V.shape[0] = T.shape[0] / 2 - mask_array : numpy.ndarray or None - array of 1 or 0 for masking - - Returns - ------- - dict - Dictionary of split counts for each sub runs. key = sub run number, value = numpy.ndarray - - """ - # Get pulse times - pulse_time_array = self._nexus_h5['entry']['bank1_events']['event_time_zero'].value - - # Search index of sub runs' boundaries (start/stop time) in pulse time array - subrun_pulseindex_array = np.searchsorted(pulse_time_array, sub_run_times) - - # get event index array: same size as pulse times - event_index_array = self._nexus_h5['entry']['bank1_events']['event_index'].value - event_id_array = self._nexus_h5['entry']['bank1_events']['event_id'].value - - # split data - num_sub_runs = sub_run_values.shape[0] - sub_run_counts_dict = dict() - - for i_sub_run in range(num_sub_runs): - # get the start and stop index in pulse array - start_pulse_index = subrun_pulseindex_array[2 * i_sub_run] - stop_pulse_index = subrun_pulseindex_array[2 * i_sub_run + 1] - - # In case of start - if start_pulse_index >= event_index_array.size: - # event ID out of boundary - start_event_id = event_id_array.shape[0] - else: - # get start andn stop event ID from event index array - start_event_id = event_index_array[start_pulse_index] - if stop_pulse_index >= event_index_array.size: - print('[WARNING] for sub run {} out of {}, stop pulse index {} is out of boundary of {}' - ''.format(i_sub_run, num_sub_runs, stop_pulse_index, event_index_array.shape)) - # stop_pulse_index = event_index_array.size - 1 - # supposed to be the last pulse and thus use the last + 1 event ID's index - stop_event_id = event_id_array.shape[0] - else: - # natural one - stop_event_id = event_index_array[stop_pulse_index] - - # get sub set of the events falling into this range - sub_run_events = event_id_array[start_event_id:stop_event_id] - - # Count the occurrence of each event ID (aka detector ID) as counts on each detector pixel - hist = np.bincount(sub_run_events, minlength=HIDRA_PIXEL_NUMBER) - - # Mask - if mask_array is not None: - assert hist.shape == mask_array.shape - hist *= mask_array - - sub_run_counts_dict[int(sub_run_values[i_sub_run])] = hist - - return sub_run_counts_dict - - def split_sample_logs_prototype(self, sub_run_times, sub_run_value): - """Split sample logs according to sub runs - - Parameters - ---------- - sub_run_times : numpy.ndarray - sub run times - sub_run_value : numpy.ndarray - sub run numbers - - Returns - ------- - dict - split sample logs. key = sub run, value = numpy.ndarray - - """ - # Log entry - das_log_entry = self._nexus_h5['entry']['DASlogs'] - log_names = list(das_log_entry.keys()) # some version of h5py returns KeyMap instead of list - # move scan index - log_names.remove('scan_index') - - # make sure sub runs are integer - sub_run_value = sub_run_value.astype(int) - - # Import sample logs in Time-Series - split_log_dict = dict() - irregular_log_names = list() - for log_name in log_names: - - single_log_entry = das_log_entry[log_name] - try: - times = single_log_entry['time'] - value = single_log_entry['value'] - - # check type - value_type = str(value.dtype) - - if value_type.count('float') == 0 and value_type.count('int') == 0: - print('[WARNING] Log {} has dtype {}. No split'.format(log_name, value_type)) - continue - - split_log, time_i = split_das_log(times, value, sub_run_times, sub_run_value) - split_log_dict[log_name] = split_log - except KeyError: - irregular_log_names.append(log_name) - # END-FOR - - # Warning output - if len(irregular_log_names) > 0: - print('[WARNING] DAS logs: {} are not time series'.format(irregular_log_names)) - - # Add back scan index - split_log_dict['scan_index'] = sub_run_value - - # Duration - duration_logs = sub_run_times[1::2] - sub_run_times[::2] - split_log_dict['duration'] = duration_logs - - return split_log_dict - - def split_sample_logs(self, sub_run_times, sub_run_numbers): - """Create dictionary for sample log of a sub run - - Goal: - 1. set self._sample_log_dict[log_name][sub_run_index] with log value (single or time-averaged) - 2. set self._sample_log_dict[HidraConstants.SUB_RUN_DURATION][sub_run_index] with duration - - Parameters - ---------- - sub_run_times : numpy.ndarray - sub run times as the relative time to 'start_time' - sub_run_numbers : numpy.ndarray - sub run values - - Returns - ------- - - """ - # Check - if sub_run_numbers.shape[0] * 2 != sub_run_times.shape[0]: - raise RuntimeError('....') - - run_obj = self._workspace.run() - - # Get 'start_time' and create a new sub_run_times in datetime64 - run_start_abs = np.datetime64(run_obj['start_time'].value) - delta_times = (sub_run_times * 1E9).astype(np.timedelta64) # convert to nanosecond then to timedetal64 - sub_run_splitter_times = run_start_abs + delta_times - - # this contains all of the sample logs - sample_log_dict = dict() - log_array_size = sub_run_numbers.shape[0] - # loop through all available logs - for log_name in run_obj.keys(): - # create and calculate the sample log - sample_log_dict[log_name] = self.split_property(run_obj.getProperty(log_name), sub_run_splitter_times, - log_array_size) - # END-FOR - - # create a fictional log for duration - if HidraConstants.SUB_RUN_DURATION not in sample_log_dict: - sample_log_dict[HidraConstants.SUB_RUN_DURATION] = sub_run_times[1::2] - sub_run_times[::2] - - return sample_log_dict - - def split_property(self, log_property, splitter_times, log_array_size): - """Calculate the mean value of the sample log "within" the sub run time range - - Parameters - ---------- - log_property - splitter_times - log_array_size - - Returns - ------- - numpy.ndarray - split logs - - """ - # Init split sample logs - log_dtype = log_property.dtype() - split_log = np.ndarray(shape=(log_array_size,), dtype=log_dtype) - - if isinstance(log_property.value, np.ndarray) and str(log_dtype) in ['f', 'i']: - # Float or integer time series property: split and get time average - for i_sb in range(log_array_size): - split_log[i_sb] = self._calculate_sub_run_time_average(log_property, splitter_times[2 * i_sb], - splitter_times[2 * i_sb + 1]) - # END-FOR - elif isinstance(log_property.value, np.ndarray) and str(log_dtype) in ['f', 'i']: - # value is ndarray. but not float or integer: get the first value - split_log[:] = log_property.value[0] - elif isinstance(log_property.value, list): - # list, but not time series property: get the first value - split_log[:] = log_property.value[0] - else: - # single value log - split_log[:] = log_property.value - - return split_log - - @staticmethod - def _calculate_sub_run_time_average(log_property, sub_run_start_time, sub_run_stop_time): - - # create a Boolean time series property as the filter - time_filter = BoolTimeSeriesProperty('filter') - time_filter.addValue(sub_run_start_time, True) - time_filter.addValue(sub_run_stop_time, False) - - # filter and get time average value - if isinstance(log_property, FloatTimeSeriesProperty): - filtered_tsp = FloatFilteredTimeSeriesProperty(log_property, time_filter) - elif isinstance(log_property, Int32TimeSeriesProperty): - filtered_tsp = Int32FilteredTimeSeriesProperty(log_property, time_filter) - elif isinstance(log_property, Int64TimeSeriesProperty): - filtered_tsp = Int64FilteredTimeSeriesProperty(log_property, time_filter) - else: - raise NotImplementedError('TSP log property {} of type {} is not supported' - ''.format(log_property.name, type(log_property))) - # END-IF - - time_average_value = filtered_tsp.timeAverageValue() - - return time_average_value - - -def split_das_log(log_times, log_values, sub_run_times, sub_run_numbers): - """Split a time series property to sub runs - - Parameters - ---------- - log_times : numpy.ndarray - relative sample log time in second to run start - log_values : numpy.ndarray - sample log value - sub_run_times: numpy.ndarray - relative sub run start and stop time in second to run start. - It is twice size of sub runs. 2n index for run start and (2n + 1) index for run stop - sub_run_numbers : numpy.ndarray - sub run number, dtype as integer - - Returns - ------- - numpy.nddarry, float - split das log in time average for each sub run, duration (second) to split log - - """ - # Profile - start_time = datetime.datetime.now() - - # Initialize the output numpy array - split_log_values = np.ndarray(shape=sub_run_numbers.shape, dtype=log_values.dtype) - - # Two cases: single and multiple value - if log_values.shape[0] == 1: - # single value: no need to split. all the sub runs will have same value - split_log_values[:] = log_values[0] - - else: - # multiple values: split - split_bound_indexes = np.searchsorted(log_times, sub_run_times) - - # then calculate time average for each sub run - for i_sr in range(sub_run_numbers.shape[0]): - # the starting value of the log in a sub run shall be the last change before the sub run start, - # so the searched index shall be subtracted by 1 - # avoid (1) breaking lower boundary and (2) breaking the upper boundary - start_index = min(max(0, split_bound_indexes[2 * i_sr] - 1), log_times.shape[0] - 1) - - # the stopped value of the log shall be the log value 1 prior to the searched result - stop_index = split_bound_indexes[2 * i_sr + 1] - - # re-define the range of the log time - sub_times = log_times[start_index:stop_index] - if sub_times.shape[0] == 0: - raise RuntimeError('Algorithm error!') - # change times at the start and append the stop time - sub_times[0] = sub_run_times[2 * i_sr] - sub_times = np.append(sub_times, sub_run_times[2 * i_sr + 1]) - - # get the range value - sub_values = log_values[start_index:stop_index] - - # calculate the time average - try: - weighted_sum = np.sum(sub_values[:] * (sub_times[1:] - sub_times[:-1])) - except TypeError as type_err: - print('Sub values: {}\nSub times: {}'.format(sub_values, sub_times)) - print('Sub value type: {}'.format(sub_values.dtype)) - raise type_err - time_averaged = weighted_sum / (sub_times[-1] - sub_times[0]) - - # record - split_log_values[i_sr] = time_averaged - # END-FOR (sub runs) - # END-IF - - stop_time = datetime.datetime.now() - - return split_log_values, (stop_time - start_time).total_seconds() diff --git a/pyrs/utilities/__init__.py b/pyrs/utilities/__init__.py index 099889080..2ce0a7f09 100644 --- a/pyrs/utilities/__init__.py +++ b/pyrs/utilities/__init__.py @@ -1,6 +1,10 @@ +# flake8: noqa import os from qtpy.uic import loadUi from pyrs.interface import designer +from .file_util import * + +__all__ = ['load_ui'] + file_util.__all__ def load_ui(ui_filename, baseinstance): diff --git a/pyrs/utilities/calibration_file_io.py b/pyrs/utilities/calibration_file_io.py index 447ad739d..9f650db9c 100644 --- a/pyrs/utilities/calibration_file_io.py +++ b/pyrs/utilities/calibration_file_io.py @@ -59,7 +59,8 @@ def read_calibration_json_file(calibration_file_name): Returns ------- - AnglerCameraDetectorShift, AnglerCameraDetectorShift, float, float, int + ~tuple + (AnglerCameraDetectorShift, AnglerCameraDetectorShift, float, float, int) detector position shifts as the calibration result,detector position shifts error from fitting status diff --git a/pyrs/utilities/checkdatatypes.py b/pyrs/utilities/checkdatatypes.py index 7e95c4aaf..a1d4f2a5a 100644 --- a/pyrs/utilities/checkdatatypes.py +++ b/pyrs/utilities/checkdatatypes.py @@ -1,6 +1,8 @@ # PyRS static helper methods +from __future__ import (absolute_import, division, print_function) # python3 compatibility import os import numpy +import six def check_bool_variable(var_name, bool_var): @@ -71,7 +73,7 @@ def check_int_variable(var_name, variable, value_range): :param value_range: if not None, then must be a 2 tuple as [min, max) ''' check_string_variable('var_name', var_name) - assert isinstance(variable, (int, numpy.int8, numpy.int16, numpy.int32)), '{0} "{1}" must be an integer ' \ + assert isinstance(variable, (int, numpy.integer)), '{0} "{1}" must be an integer ' \ 'but not a {2}'.format(var_name, variable, type(variable)) if value_range is not None: @@ -97,7 +99,7 @@ def check_float_variable(var_name, variable, value_range): :param value_range: if not None, then must be a 2 tuple as [min, max) ''' check_string_variable('var_name', var_name) - assert isinstance(variable, (float, int, numpy.float32, numpy.float)), '{0} {1} must be a float but not a {2}'\ + assert isinstance(variable, (float, int, numpy.integer, numpy.floating)), '{0} {1} must be a float but not a {2}'\ .format(var_name, variable, type(variable)) if value_range is not None: @@ -208,8 +210,8 @@ def check_string_variable(var_name, variable, allowed_values=None, allow_empty=T assert isinstance(var_name, str), 'Variable name {0} must be a string but not a {1}'\ .format(var_name, type(var_name)) - assert isinstance(variable, (str, unicode)), '{0} {1} must be a string or unicode but not a {2}' \ - ''.format(var_name, variable, type(variable)) + assert isinstance(variable, six.string_types), '{0} {1} must be a string or unicode but not a {2}'\ + .format(var_name, variable, type(variable)) if isinstance(allowed_values, list): if variable not in allowed_values: diff --git a/pyrs/utilities/file_util.py b/pyrs/utilities/file_util.py index 20aedf2c8..5734f85a0 100644 --- a/pyrs/utilities/file_util.py +++ b/pyrs/utilities/file_util.py @@ -1,125 +1,12 @@ -# Zoo of methods to work with file properties -import time +from __future__ import (absolute_import, division, print_function) # python3 compatibility +from . import checkdatatypes +from contextlib import contextmanager +from mantid import ConfigService +from mantid.api import FileFinder +from mantid.simpleapi import mtd, GetIPTS, SaveNexusProcessed import os -import h5py -import checkdatatypes -import platform -from mantid.simpleapi import mtd, CreateWorkspace, SaveNexusProcessed -from skimage import io -from PIL import Image -import numpy as np - -def export_md_array_hdf5(md_array, sliced_dir_list, file_name): - """ - export 2D data from - :param md_array: - :param sliced_dir_list: None or integer last - :param file_name: - :return: - """ - checkdatatypes.check_numpy_arrays('2D numpy array to export', [md_array], 2, False) - checkdatatypes.check_file_name(file_name, check_exist=False, check_writable=True) - - if sliced_dir_list is not None: - # delete selected columns: axis=1 - checkdatatypes.check_list('Sliced directions', sliced_dir_list) - try: - md_array = np.delete(md_array, sliced_dir_list, 1) # axis = 1 - except ValueError as val_err: - raise RuntimeError('Unable to delete column {} of input numpy 2D array due to {}' - ''.format(sliced_dir_list, val_err)) - # END-IF - - # write out - out_h5_file = h5py.File(file_name, 'w') - out_h5_file.create_dataset('Sliced-{}'.format(sliced_dir_list), data=md_array) - out_h5_file.close() - - return - - -def load_rgb_tif(rgb_tiff_name, convert_to_1d): - """ - Load TIFF file in RGB mode and convert to grey scale - :param rgb_tiff_name: - :param convert_to_1d: flag to convert the data to 1D from 2D - :return: 2D array (in Fortran column major) or (flattened) 1D array - """ - # check - checkdatatypes.check_file_name(rgb_tiff_name, True, False, False, '(RBG) Tiff File') - - # open TIFF - image_data = Image.open(rgb_tiff_name) - rgb_tuple = image_data.split() - if len(rgb_tuple) < 3: - raise RuntimeError('{} is not a RGB Tiff file'.format(rgb_tiff_name)) - - # convert RGB to grey scale - # In[24]: gray = (0.299 * ra + 0.587 * ga + 0.114 * ba) - red_array = np.array(rgb_tuple[0]).astype('float64') - green_array = np.array(rgb_tuple[1]).astype('float64') - blue_array = np.array(rgb_tuple[2]).astype('float64') - gray_array = (0.299 * red_array + 0.587 * green_array + 0.114 * blue_array) - - if convert_to_1d: - gray_array = gray_array.flatten(order='F') - print('{}: Max counts = {}, Mean counts = {}'.format(rgb_tiff_name, gray_array.max(), gray_array.mean())) - - return gray_array - - -def load_gray_scale_tif(raw_tiff_name, pixel_size=2048, rotate=True): - """ - Load data from TIFF - :param raw_tiff_name: - :param pixel_size - :param rotate: - :return: - """ - ImageData = Image.open(raw_tiff_name) - # im = img_as_uint(np.array(ImageData)) - io.use_plugin('freeimage') - image_2d_data = np.array(ImageData, dtype=np.int32) - - # # convert from RGB to gray scale - # img.getdata() - # r, g, b = img.split() - # ra = np.array(r) - # ga = np.array(g) - # ba = np.array(b) - # - # gray = (0.299 * ra + 0.587 * ga + 0.114 * ba) - - print(image_2d_data.shape, type(image_2d_data), image_2d_data.min(), image_2d_data.max()) - # image_2d_data.astype(np.uint32) - image_2d_data.astype(np.float64) - if rotate: - image_2d_data = image_2d_data.transpose() - - # TODO - TONIGHT 1 - Better to split the part below to other methods - # Merge/compress data if required - if pixel_size == 1024: - counts_vec = image_2d_data[::2, ::2] + image_2d_data[::2, 1::2] + \ - image_2d_data[1::2, ::2] + image_2d_data[1::2, 1::2] - pixel_type = '1K' - # print (DataR.shape, type(DataR)) - else: - # No merge - counts_vec = image_2d_data - pixel_type = '2K' - - counts_vec = counts_vec.reshape((pixel_size * pixel_size,)) - print(counts_vec.min()) - - if False: - data_ws_name = os.path.basename(raw_tiff_name).split('.')[0] + '_{}'.format(pixel_type) - CreateWorkspace(DataX=np.zeros((pixel_size**2,)), DataY=counts_vec, DataE=np.sqrt(counts_vec), - NSpec=pixel_size**2, OutputWorkspace=data_ws_name, VerticalAxisUnit='SpectraNumber') - - # return data_ws_name, counts_vec - - return image_2d_data +__all__ = ['get_ipts_dir', 'get_default_output_dir', 'get_ipts_dir', 'get_input_project_file', 'get_nexus_file'] def save_mantid_nexus(workspace_name, file_name, title=''): @@ -146,42 +33,6 @@ def save_mantid_nexus(workspace_name, file_name, title=''): 'workspaces are {1}.' ''.format(workspace_name, mtd.getObjectNames())) - # END-IF-ELSE - - return - - -def check_creation_date(file_name): - """ - check the create date (year, month, date) for a file - :except RuntimeError: if the file does not exist - :param file_name: - :return: - """ - checkdatatypes.check_file_name(file_name, check_exist=True) - - # get the creation date in float (epoch time) - if platform.system() == 'Windows': - # windows not tested - epoch_time = os.path.getctime(file_name) - else: - # mac osx/linux - stat = os.stat(file_name) - try: - epoch_time = stat.st_birthtime - except AttributeError: - # We're probably on Linux. No easy way to get creation dates here, - # so we'll settle for when its content was last modified. - epoch_time = stat.st_mtime - # END-TRY - # END-IF-ELSE - - # convert epoch time to a string as YYYY-MM-DD - file_create_time = time.localtime(epoch_time) - file_create_time_str = time.strftime('%Y-%m-%d', file_create_time) - - return file_create_time_str - def get_temp_directory(): """ @@ -206,3 +57,116 @@ def get_temp_directory(): # testing # print (check_creation_date('__init__.py')) +@contextmanager +def archive_search(): + DEFAULT_FACILITY = 'default.facility' + DEFAULT_INSTRUMENT = 'default.instrument' + SEARCH_ARCHIVE = 'datasearch.searcharchive' + HFIR = 'HFIR' + HB2B = 'HB2B' + + # get the old values + config = ConfigService.Instance() + old_config = {} + for property_name in [DEFAULT_FACILITY, DEFAULT_INSTRUMENT, SEARCH_ARCHIVE]: + old_config[property_name] = config[property_name] + + # don't update things that are already set correctly + if config[DEFAULT_FACILITY] == HFIR: + del old_config[DEFAULT_FACILITY] + else: + config[DEFAULT_FACILITY] = HFIR + + if config[DEFAULT_INSTRUMENT] == HB2B: + del old_config[DEFAULT_INSTRUMENT] + else: + config[DEFAULT_INSTRUMENT] = HB2B + + if HFIR in config[SEARCH_ARCHIVE]: + del old_config[SEARCH_ARCHIVE] + else: + config[SEARCH_ARCHIVE] = HFIR + + try: + # give back context + yield + + finally: + # set properties back to original values + for property_name in old_config.keys(): + config[property_name] = old_config[property_name] + + +def get_ipts_dir(run_number): + """Get IPTS directory from run number. Throws an exception if the file wasn't found. + + Parameters + ---------- + run_number : int + run number + + Returns + ------- + str + IPTS path: example '/HFIR/HB2B/IPTS-22731/', None for not supported IPTS + """ + # try with GetIPTS + with archive_search(): + ipts = GetIPTS(RunNumber=run_number, Instrument='HB2B') + return ipts + + +def get_default_output_dir(run_number): + """Get NeXus directory + + Parameters + ---------- + run_number : int + run number + + Returns + ------- + str + path to Nexus files ``/HFIR/IPTS-####/shared/manualreduce`` + + """ + # this can generate an exception + ipts_dir = get_ipts_dir(run_number) + + return os.path.join(ipts_dir, 'shared', 'manualreduce') + + +def get_input_project_file(run_number, preferredType='manual'): + # this can generate an exception + shared_dir = os.path.join(get_ipts_dir(run_number), 'shared') + + if not os.path.exists(shared_dir): + raise RuntimeError('Shared directory "{}" does not exist'.format(shared_dir)) + + # generate places to look for + auto_dir = os.path.join(shared_dir, 'autoreduce') + manual_dir = os.path.join(shared_dir, 'manualreduce') + err_msg = 'Failed to find project file for run "{}" in "{}"'.format(run_number, shared_dir) + + preferredType = preferredType.lower() + if preferredType.startswith('manual'): + if os.path.exists(manual_dir): + return manual_dir + elif os.path.exists(auto_dir): + return auto_dir + else: + raise RuntimeError(err_msg) + elif preferredType.startswith('auto'): + if os.path.exists(auto_dir): + return auto_dir + else: + raise RuntimeError(err_msg) + else: + raise ValueError('Do not understand preferred type "{}"'.format(preferredType)) + + +def get_nexus_file(run_number): + with archive_search(): + nexus_file = FileFinder.findRuns('HB2B{}'.format(run_number))[0] + # return after `with` scope so cleanup is run + return nexus_file diff --git a/pyrs/utilities/hb2b_utilities.py b/pyrs/utilities/hb2b_utilities.py index 8b6fccd7c..f3d6cbad1 100644 --- a/pyrs/utilities/hb2b_utilities.py +++ b/pyrs/utilities/hb2b_utilities.py @@ -1,6 +1,7 @@ # TODO - 20181130 - Implement this class # A module contains a set of static methods to provide instrument geometry and data archiving knowledge of HB2B -import checkdatatypes +from __future__ import (absolute_import, division, print_function) # python3 compatibility +from . import checkdatatypes from pyrs.utilities.calibration_file_io import ResidualStressCalibrationFile import os diff --git a/pyrs/utilities/rs_scan_io.py b/pyrs/utilities/rs_scan_io.py index ebec5a17b..a863655c3 100644 --- a/pyrs/utilities/rs_scan_io.py +++ b/pyrs/utilities/rs_scan_io.py @@ -1,7 +1,9 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility from pyrs.utilities import checkdatatypes import h5py import math import numpy +import six from shutil import copyfile @@ -216,12 +218,9 @@ def add_peak_fit_parameters(sample_log_dict, h5_group, log_index, total_scans): item_name_str = str(item_name) if item_name_str not in sample_logs: # create entry as ndarray if it does not exist - if isinstance(item_i, str): + if isinstance(item_i, six.string_types): # string can only be object type sample_logs[item_name_str] = numpy.ndarray(shape=(num_scan_logs,), dtype=object) - elif isinstance(item_i, unicode): - # unicode - sample_logs[item_name_str] = numpy.ndarray(shape=(num_scan_logs,), dtype=object) else: # raw type try: diff --git a/scripts/preparetest/convert_hzb_data.py b/scripts/preparetest/convert_hzb_data.py deleted file mode 100755 index c6a3f3819..000000000 --- a/scripts/preparetest/convert_hzb_data.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/python -"""" -Convert the old HZB raw data to test -1. raw counts writing and writing -2. PyRS reduction -3. PyRS calibration - -Note: most of the methods to parse HZB data are copied from script convert_hzb_data.py - -*How to run* -1. Add PyRS path to python path (refer to pyrsdev.sh) -2. Run this script -""" -import numpy -import os -from pyrs.core.instrument_geometry import HidraSetup -from pyrs.dataobjects import HidraConstants -from pyrs.projectfile import HidraProjectFile, HidraProjectFileMode -from pyrs.utilities import file_util - - -def parse_hzb_tiff(tiff_file_name): - """ - Parse HZB TIFF (image) data to numpy array (1D) - :param tiff_file_name: - :return: (1) 1D array (column major, from lower left corner) (2) (num_row, num_cols) - """ - # is it rotated? - # rotated - counts_array = file_util.load_rgb_tif(tiff_file_name, True) - - return counts_array - - -def import_hzb_summary(summary_excel): - """ - import and parse HZB summary file in EXCEL format - [ u'E3 file', u'Tiff', u'Tiff_Index', u'Index', - u'2th', u'mon', u'2th.1', u'Unnamed: 7', - u'L2', u'ADET', u'Unnamed: 10', u'Unnamed: 11', - u'Unnamed: 12', u'Unnamed: 13', u'SDET'], - # ['Tiff'][i] = E3-Y2O3 42-50 - # ['Tiff Index] = 1, 2, 3, ... - # 2th = 2th.1 - # L2: unit == mm - # tif_name = '{}_{:05}.tiff'.format(df['Tiff'][0], int(df['Tiff_Index'][0])) - :param summary_excel: experiment summary file in Excel format - :return: - """ - # load data - summary_pandas_data = file_util.load_excel_file(summary_excel) - - # get list of interested items - scan_index_array = numpy.array(summary_pandas_data['Index']) - two_theta_array = numpy.array(summary_pandas_data['2th']) - tiff_header_array = numpy.array(summary_pandas_data['Tiff']) - tiff_order_array = numpy.array(summary_pandas_data['Tiff_Index']) - monitor_array = numpy.array(summary_pandas_data['mon']) - l2_array = numpy.array(summary_pandas_data['L2']) * 1.E-3 # convert to meter - - # combine to files - tiff_files = list() - for item_index in range(len(tiff_header_array)): - tiff_header_i = tiff_header_array[item_index] - tiff_order_i = tiff_order_array[item_index] - tiff_file_i = '{}_{:05}.tiff'.format(tiff_header_i, int(tiff_order_i)) - tiff_files.append(tiff_file_i) - # END-FOR - - # create a dictionary of dictionary for the information - summary_dict = dict() - for item_index in range(len(tiff_files)): - scan_i_dict = {HidraConstants.TWO_THETA: two_theta_array[item_index], - 'Tiff': tiff_files[item_index], - 'Monitor': monitor_array[item_index], - 'L2': l2_array[item_index]} - if scan_index_array[item_index] in summary_dict: - raise RuntimeError('Experiment summary file {} contains 2 entries with same (scan) Index {}' - ''.format(summary_excel, scan_index_array[item_index])) - summary_dict[scan_index_array[item_index]] = scan_i_dict - # END-FOR - - return summary_dict, {HidraConstants.SUB_RUNS: scan_index_array, - HidraConstants.TWO_THETA: two_theta_array, - 'Monitor': monitor_array, 'L2': l2_array} - - -def generate_hzb_instrument(): - """ - Create an instrument setup for HZB - :return: - """ - from pyrs.core import instrument_geometry - - wavelength = 1.222 - arm_length = 1.13268 - x = 0.001171875 - y = 0.001171875 - detector = instrument_geometry.AnglerCameraDetectorGeometry(num_rows=256, num_columns=256, - pixel_size_x=x, - pixel_size_y=y, - arm_length=arm_length, - calibrated=False) - - hzb = HidraSetup(l1=1.0, detector_setup=detector) # single wave length - hzb.set_single_wavelength(wavelength) - - return hzb - - -def main(): - """ - Main method to do the conversion - :param argv: - :return: - """ - hzb_summary_name = '/SNS/users/wzz/Projects/HB2B/Quasi_HB2B_Calibration/calibration.xlsx' - output_file_name = 'tests/testdata/HZB_Raw_Project.h5' - exp_data_dir = '/SNS/users/wzz/Projects/HB2B/Quasi_HB2B_Calibration/' # raw HZB TIFF exp data directory - - # parse EXCEL spread sheet - exp_summary_dict, exp_logs_dict = import_hzb_summary(hzb_summary_name) - - # start project file - project_file = HidraProjectFile(output_file_name, mode=HidraProjectFileMode.OVERWRITE) - - # parse and add counts - sub_run_list = exp_summary_dict.keys() - for sub_run_i in sorted(sub_run_list): - tiff_name = exp_summary_dict[sub_run_i]['Tiff'] - counts_array = parse_hzb_tiff(os.path.join(exp_data_dir, tiff_name)) - # print (counts_array.min(), counts_array.max(), (numpy.where(counts_array > 0.5)[0]).shape) - project_file.append_raw_counts(sub_run_i, counts_array) - # END-FOR - - # add sample log data & sub runs - for log_name in [HidraConstants.SUB_RUNS, HidraConstants.TWO_THETA, 'Monitor', 'L2']: - project_file.append_experiment_log(log_name, exp_logs_dict[log_name]) - # END-FOR - # project_file.add_experiment_information('sub-run', sub_run_list) - - # add instrument information - hzb_instrument_setup = generate_hzb_instrument() - project_file.write_instrument_geometry(hzb_instrument_setup) - - # save - project_file.close() - - -if __name__ == '__main__': - main() diff --git a/scripts/pyrs_calibration.py b/scripts/pyrs_calibration.py index ca1ff0463..dbb204533 100644 --- a/scripts/pyrs_calibration.py +++ b/scripts/pyrs_calibration.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility import numpy import time from pyrs.projectfile import HidraProjectFile, HidraProjectFileMode @@ -30,7 +31,7 @@ def SaveCalibError(calibrator, fName): for i_tth in tths: for j in list(calibrator.ReductionResults[i_tth].keys()): tempdata = calibrator.ReductionResults[i_tth][j] - print i_tth, j + print(i_tth, j) lcv += 1 DataOut[:, lcv*3+0] = tempdata[0] @@ -41,7 +42,7 @@ def SaveCalibError(calibrator, fName): DataOut = DataOut[:, :lcv*3+3] - print DataOut.shape + print(DataOut.shape) numpy.savetxt(fName, DataOut, delimiter=',', header=header[1:]) @@ -100,7 +101,7 @@ def SaveCalibError(calibrator, fName): if options.method in [DEFAULT_CALIBRATION, 'geometry']: SaveCalibError(calibrator, 'HB2B_{}_before.csv'.format(options.pin)) calibrator.CalibrateGeometry() - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method in [DEFAULT_CALIBRATION, 'testshift']: SaveCalibError(calibrator, 'HB2B_{}_shift1.txt'.format(options.pin)) @@ -110,38 +111,38 @@ def SaveCalibError(calibrator, fName): if options.method in ['shift']: calibrator.singlepeak = False calibrator.CalibrateShift(ConstrainPosition=True) - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method in ['rotate']: calibrator.CalibrateRotation(ConstrainPosition=True) - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method in ['wavelength']: calibrator.singlepeak = False calibrator.calibrate_wave_length() - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method in ['full']: calibrator.singlepeak = False calibrator.FullCalibration(ConstrainPosition=True) - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method == 'distance': calibrator.calibrate_distance(ConstrainPosition=True, Brute=True) - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method == 'geoNew': - print 'Calibrating Geometry in Steps' + print('Calibrating Geometry in Steps') calibrator.calibrate_shiftx(ConstrainPosition=True) - print calibrator.get_calib() + print(calibrator.get_calib()) calibrator.calibrate_distance(ConstrainPosition=False) - print calibrator.get_calib() + print(calibrator.get_calib()) calibrator.calibrate_wave_length(ConstrainPosition=True) - print calibrator.get_calib() + print(calibrator.get_calib()) calibrator.CalibrateRotation() - print calibrator.get_calib() + print(calibrator.get_calib()) calibrator.FullCalibration() - print calibrator.get_calib() + print(calibrator.get_calib()) if options.method in [DEFAULT_CALIBRATION, 'runAll']: calibrator = peakfit_calibration.PeakFitCalibration(hb2b, pin_engine, powder_engine) @@ -164,11 +165,11 @@ def SaveCalibError(calibrator, fName): calibrator.calibrate_wave_length() LambdaCalib = calibrator.get_calib() - print FullCalib - print GeoCalib - print RotateCalib - print ShiftCalib - print LambdaCalib + print(FullCalib) + print(GeoCalib) + print(RotateCalib) + print(ShiftCalib) + print(LambdaCalib) datatime = time.strftime('%Y-%m-%dT%H-%M', time.localtime()) fName = '/HFIR/HB2B/shared/CAL/cycle{}/HB2B_{}_{}.json'.format(options.cycle, calibrator.mono, datatime) diff --git a/scripts/reduce_HB2B.py b/scripts/reduce_manual_HB2B.py similarity index 89% rename from scripts/reduce_HB2B.py rename to scripts/reduce_manual_HB2B.py index 28bfb90a1..b7fde825f 100755 --- a/scripts/reduce_HB2B.py +++ b/scripts/reduce_manual_HB2B.py @@ -45,7 +45,8 @@ def _nexus_to_subscans(nexusfile, projectfile, mask_file_name, save_project_file return hydra_ws -def _create_powder_patterns(hidra_workspace, instrument, calibration, mask, subruns, project_file_name): +def _create_powder_patterns(hidra_workspace, instrument, calibration, mask, subruns, project_file_name, + append_mode): logger.notice('Adding powder patterns to Hidra Workspace{}'.format(hidra_workspace)) reducer = ReductionApp(bool(options.engine == 'mantid')) @@ -56,9 +57,10 @@ def _create_powder_patterns(hidra_workspace, instrument, calibration, mask, subr reducer.reduce_data(instrument_file=instrument, calibration_file=calibration, mask=mask, - sub_runs=subruns) + sub_runs=subruns, + van_file=None) - reducer.save_diffraction_data(project_file_name) + reducer.save_diffraction_data(project_file_name, append_mode) def _view_raw(hidra_workspace, mask, subruns, engine): @@ -80,13 +82,14 @@ def reduce_hidra_workflow(user_options): # split into sub runs fro NeXus file hidra_ws = _nexus_to_subscans(user_options.nexus, user_options.project, mask_file_name=user_options.mask, - save_project_file=False) + save_project_file=user_options.savecounts) if user_options.viewraw: # plot data _view_raw(hidra_ws, None, user_options.subruns, user_options.engine) else: # add powder patterns _create_powder_patterns(hidra_ws, user_options.instrument, user_options.calibration, - None, user_options.subruns, user_options.project) + None, user_options.subruns, user_options.project, + append_mode=user_options.savecounts) logger.notice('Successful reduced {}'.format(user_options.nexus)) @@ -98,6 +101,8 @@ def reduce_hidra_workflow(user_options): parser.add_argument('--project', help='Project file with subscans and powder patterns (default is based on' ' nexus filename in )') + parser.add_argument('--savecounts', action='store_true', + help='Save the counts array to the project file which will be significantly larger') parser.add_argument('--instrument', nargs='?', default=DEFAULT_INSTRUMENT, help='instrument configuration file overriding embedded (arm, pixel number' ' and size) (default=%(default)s)') diff --git a/setup.cfg b/setup.cfg index d31748f88..3aa7fed8b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,5 +20,5 @@ versionfile_build = pyrs/_version.py tag_prefix = v [flake8] -exclude = .git,build,docs,prototypes,versioneer.py,pyrs/icons/icons_rc.py +exclude = .git,build,docs,prototypes,versioneer.py,pyrs/icons/ max-line-length = 119 diff --git a/setup.py b/setup.py index 34e65513d..cb9274b3c 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ def find_meta(meta): main """ scripts = ['scripts/pyrsplot', - 'scripts/reduce_HB2B.py', + 'scripts/reduce_manual_HB2B.py', 'scripts/pyrs_calibration.py', 'scripts/create_mask.py'] setup( diff --git a/tests/data/scripts/generate_slice_view_data.py b/tests/data/scripts/generate_slice_view_data.py index fac98149c..27d0cb3f6 100644 --- a/tests/data/scripts/generate_slice_view_data.py +++ b/tests/data/scripts/generate_slice_view_data.py @@ -1,4 +1,5 @@ # Generate a list of 3D data for testing slice view +from __future__ import (absolute_import, division, print_function) # python3 compatibility import numpy as np import random @@ -32,8 +33,8 @@ # END-FOR data_set = np.array(data_set) -print data_set -print data_set.shape +print(data_set) +print(data_set.shape) def get_height(x, y, z): @@ -68,7 +69,7 @@ def get_width(x, y, z): print(vec_height, '\n', vec_height.shape) vec_width = get_width(data_set[:, 0], data_set[:, 1], data_set[:, 2]) -print vec_width +print(vec_width) wbuf = '# {0:12}{1:12}{2:12}{3:12}{4:12}\n'.format('X', 'Y', 'Z', 'Height', 'Width') @@ -77,7 +78,7 @@ def get_width(x, y, z): '{0:.5f}'.format(vec_height[index]), '{0:.5f}'.format(vec_width[index])) -print wbuf +print(wbuf) # write to file ofile = open('slice_test.dat', 'w') diff --git a/tests/integration/test_load_split.py b/tests/integration/test_load_split.py deleted file mode 100644 index ff3d26d55..000000000 --- a/tests/integration/test_load_split.py +++ /dev/null @@ -1,177 +0,0 @@ -from pyrs.projectfile import HidraProjectFile -from pyrs.core.nexus_conversion import NeXusConvertingApp -from pyrs.utilities import calibration_file_io -from pyrs.core import workspaces -import numpy as np -from pyrs.split_sub_runs.load_split_sub_runs import NexusProcessor -from pyrs.core.powder_pattern import ReductionApp -import os -import pytest - - -def test_calibration_json(): - """Test reduce data with calibration file (.json) - - Returns - ------- - None - """ - # Get simulated test data - project_file_name = 'data/HB2B_000.h5' - calib_file = 'data/HB2B_CAL_Si333.json' - - # Import file - calib_obj = calibration_file_io.read_calibration_json_file(calib_file) - shift, shift_error, wave_length, wl_error, status = calib_obj - - # Verify result - assert shift - assert shift_error - assert wave_length - assert wl_error - assert status == 3 - - # Import project file - project_file = HidraProjectFile(project_file_name, 'r') - - # Reduce - test_workspace = workspaces.HidraWorkspace('test calibration') - test_workspace.load_hidra_project(project_file, load_raw_counts=True, load_reduced_diffraction=False) - - -def test_log_time_average(): - """Test the log time average calculation - - Returns - ------- - - """ - nexus_file_name = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5' - if os.path.exists(nexus_file_name) is False: - pytest.skip('File {} is not accessible'.format(nexus_file_name)) - - processor = NexusProcessor(nexus_file_name) - - sub_run_times, sub_run_numbers = processor.get_sub_run_times_value() - - # sample log (TSP) - tsp_sample_logs = processor.split_sample_logs(sub_run_times, sub_run_numbers) - # sample log (PyRS) - pyrs_sample_logs = processor.split_sample_logs_prototype(sub_run_times, sub_run_numbers) - - # compare some - for log_name in ['2theta', 'DOSC']: - np.testing.assert_allclose(tsp_sample_logs[log_name], pyrs_sample_logs[log_name]) - - -@pytest.mark.parametrize('nexus_file_name, mask_file_name', - [('/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5', 'data/HB2B_Mask_12-18-19.xml'), - ('/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5', None)], - ids=('HB2B_1017_Masked', 'HB2B_1017_NoMask')) -def test_compare_nexus_reader(nexus_file_name, mask_file_name): - """Verify NeXus converters including counts and sample log values - - Returns - ------- - - """ - from matplotlib import pyplot as plt - - # Test on a light weight NeXus - if os.path.exists(nexus_file_name) is False: - pytest.skip('Unable to access test file {}'.format(nexus_file_name)) - - # reduce with Mantid - nexus_mtd_converter = NeXusConvertingApp(nexus_file_name, mask_file_name=mask_file_name) - hidra_mtd_ws = nexus_mtd_converter.convert(use_mantid=True) - - # reduce with PyRS/Python - nexus_pyrs_converter = NeXusConvertingApp(nexus_file_name, mask_file_name=mask_file_name) - hidra_pyrs_ws = nexus_pyrs_converter.convert(use_mantid=False) - - # compare sub runs - sub_runs_mtd = hidra_mtd_ws.get_sub_runs() - sub_run_pyrs = hidra_pyrs_ws.get_sub_runs() - np.testing.assert_allclose(sub_runs_mtd, sub_run_pyrs) - - # compare counts - for sub_run in sub_runs_mtd: - mtd_counts = hidra_mtd_ws.get_detector_counts(sub_run) - pyrs_counts = hidra_pyrs_ws.get_detector_counts(sub_run) - - diff_counts = mtd_counts - pyrs_counts - print('Sub Run {}: Mantid counts = {}, PyRS counts = {}\n' - 'Number of pixels with different counts = {}. Maximum difference = {}' - ''.format(sub_run, np.sum(mtd_counts), np.sum(pyrs_counts), - len(np.where(diff_counts != 0)[0]), np.max(np.abs(diff_counts)))) - np.testing.assert_allclose(mtd_counts, pyrs_counts) - - # Test reduction to diffraction pattern - # instrument = AnglerCameraDetectorGeometry(1024, 1024, 0.0003, 0.0003, 0.985, False) - # hidra_mtd_ws.set_instrument_geometry(instrument) - reducer_mtd = ReductionApp(False) - reducer_mtd.load_hidra_workspace(hidra_mtd_ws) - reducer_mtd.reduce_data(sub_runs=None, - instrument_file=None, - calibration_file=None, - mask=None) - vec_mtd_x, vec_mtd_y = reducer_mtd.get_diffraction_data(1) - - # hidra_pyrs_ws.set_instrument_geometry(instrument) - reducer_pyrs = ReductionApp(False) - reducer_pyrs.load_hidra_workspace(hidra_pyrs_ws) - reducer_pyrs.reduce_data(sub_runs=None, - instrument_file=None, - calibration_file=None, - mask=None) - vec_rs_x, vec_rs_y = reducer_pyrs.get_diffraction_data(1) - - plt.plot(vec_mtd_x, vec_mtd_y) - plt.plot(vec_rs_x, vec_rs_y, color='red') - plt.show() - - for sub_run in [1, 2, 3]: - print('Comparing pattern of sub run {}'.format(sub_run)) - vec_mtd_x, vec_mtd_y = reducer_mtd.get_diffraction_data(sub_run) - vec_rs_x, vec_rs_y = reducer_pyrs.get_diffraction_data(sub_run) - np.testing.assert_allclose(vec_mtd_y, vec_rs_y) - - # compare number of sample logs - log_names_mantid = hidra_mtd_ws.get_sample_log_names() - log_names_pyrs = hidra_pyrs_ws.get_sample_log_names() - - print('Diff set Mantid vs PyRS: {} ... {}' - ''.format(set(log_names_mantid) - set(log_names_pyrs), - set(log_names_pyrs) - set(log_names_mantid))) - if len(log_names_mantid) != len(log_names_pyrs): - raise AssertionError('Sample logs entries are not same') - - not_compare_output = '' - # compare sample log values - for log_name in log_names_pyrs: - if log_name == 'pulse_flags': - continue - - mtd_log_values = hidra_mtd_ws.get_sample_log_values(log_name) - pyrs_log_values = hidra_pyrs_ws.get_sample_log_values(log_name) - - if str(pyrs_log_values.dtype).count('float') == 0 and str(pyrs_log_values.dtype).count('int') == 0: - # Not float or integer: cannot be compared - not_compare_output += '{}: Mantid {} vs PyRS {}\n'.format(log_name, mtd_log_values, pyrs_log_values) - else: - # Int or float: comparable - log_diff = np.sqrt(np.sum((mtd_log_values - pyrs_log_values) ** 2)) - print('{}: sum(diff) = {}, size = {}'.format(log_name, log_diff, len(mtd_log_values))) - try: - np.testing.assert_allclose(mtd_log_values, pyrs_log_values) - print() - except AssertionError as ass_err: - print('........... Not matched!: \n{}'.format(ass_err)) - print('----------------------------------------------------\n') - # END-FOR - - print(not_compare_output) - - -if __name__ == '__main__': - pytest.main([__file__]) diff --git a/tests/integration/test_manual_reduction_ui.py b/tests/integration/test_manual_reduction_ui.py index bd139d99f..9beeec51b 100644 --- a/tests/integration/test_manual_reduction_ui.py +++ b/tests/integration/test_manual_reduction_ui.py @@ -2,69 +2,170 @@ Integration test for workflow used in manual reduction UI """ import numpy as np +import os import pytest -from pyrs.core.workspaces import HidraWorkspace -from pyrs.interface.manual_reduction.event_handler import EventHandler -from pyrs.core.powder_pattern import ReductionApp +from pyrs.interface.manual_reduction.pyrs_api import ReductionController, reduce_hidra_workflow import h5py -def parse_gold_file(file_name): +def test_default_calibration_file(): + """Test to find current/latest calibration file + + Returns + ------- + """ + default_calib_file = os.path.join(ReductionController.get_default_calibration_dir(), + 'HB2B_Latest.json') + + if os.path.exists('/HFIR/HB2B/shared'): + assert os.path.exists(default_calib_file) + print(default_calib_file) + print(default_calib_file.lower()) + assert default_calib_file.lower().endswith('.json'), 'Must be a JSON file' + else: + pytest.skip('Unable to access HB2B archive') + + +@pytest.mark.parametrize('nexus_file, calibration_file, mask_file, gold_file', + [('/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5', None, None, + 'data/HB2B_1017_NoMask_Gold.h5'), + ('/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5', None, + 'data/HB2B_Mask_12-18-19.xml', 'data/HB2B_1017_NoMask_Gold.h5'), + ('/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5', 'data/HB2B_CAL_Si333.json', + 'data/HB2B_Mask_12-18-19.xml', 'data/HB2B_1017_NoMask_Gold.h5')], + ids=('HB2B_1017_NoCal_NoMask', 'HB2B_1017_NoCal_Mask', 'HB2B_1017_Cal_Mask')) +def test_manual_reduction(nexus_file, calibration_file, mask_file, gold_file): + """Test the workflow to do manual reduction. + + From splitting sub runs and sample logs, converting to powder pattern and then save + including 3 cases: + (1) No calibration, No masking + (2) No calibration, Masking + (3) Calibration, Masking Parameters ---------- - file_name + nexus_file + calibration_file + mask_file + gold_file Returns ------- - ~dict or ~numpy.ndarray - gold data in array or dictionary of arrays """ - # Init output - data_dict = dict() + if os.path.exists(nexus_file) is False: + pytest.skip('Testing file {} cannot be accessed'.format(nexus_file)) - # Parse file - gold_file = h5py.File(file_name, 'r') - data_set_names = list(gold_file.keys()) - for name in data_set_names: - if isinstance(gold_file[name], h5py.Dataset): - data_dict[name] = gold_file[name].value - else: - data_dict[name] = gold_file[name]['x'].value, gold_file[name]['y'].value - # END-FOR + # Get output directory and reduce + output_dir = os.getcwd() + target_file_path = 'HB2B_1017_{}_{}.h5'.format(calibration_file is not None, mask_file is not None) + # remove previously generated + if os.path.exists(target_file_path): + os.remove(target_file_path) - if len(data_dict) == 1 and data_dict.keys()[0] == 'data': - # only 1 array with default name - return data_dict['data'] + # reduce data + test_ws = reduce_hidra_workflow(nexus_file, output_dir, progressbar=None, + calibration=calibration_file, mask=mask_file, + project_file_name=target_file_path) - return data_dict + # get sub run 2 + sub_run_2_pattern = test_ws.get_reduced_diffraction_data(2, mask_id=None) + other_file = 'Gold_{}_Cal{}.h5'.format(mask_file is not None, + calibration_file is not None) + write_gold_file(other_file, {'sub run 2': sub_run_2_pattern}) + # Check whether the target file generated + assert os.path.exists(target_file_path), 'Hidra project file {} is not generated'.format(target_file_path) -def test_load_split(): - """ + # using gold file to compare the result + parse_gold_file(gold_file) + + # delete + for filename in [other_file, target_file_path]: + if os.path.isfile(filename): + os.remove(filename) + + +def test_reduction_with_vanadium(): + """Test manual reduction workflow with vanadium correction Returns ------- """ - pytest.skip('Manual reduction UI classes has not been refactored yet.') + # Set up + nexus_file = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5' + calibration_file = 'data/HB2B_CAL_Si333.json' + mask_file = 'data/HB2B_Mask_12-18-19.xml' + vanadium_file = 'data/HB2B_931.h5' + output_dir = os.getcwd() + target_file_path = 'HB2B_1017_Cal_Mask_Van.h5' + + if os.path.exists(nexus_file) is False: + pytest.skip('Skip as {} cannot be accessed'.format(nexus_file)) + # Reduce + # reduce data + test_ws = reduce_hidra_workflow(nexus_file, output_dir, progressbar=None, + calibration=calibration_file, mask=mask_file, + vanadium_file=vanadium_file, + project_file_name=target_file_path) + + assert test_ws + + +def test_load_split(): + """Test method to load, split, convert to powder pattern and save + + Returns + ------- + + """ # Init load/split service instance + nexus_file = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5' + if os.path.exists(nexus_file) is False: + pytest.skip('Unable to access {}'.format(nexus_file)) # Get list of sub runs + # Get output directory and reduce + output_dir = os.getcwd() + target_file_path = 'HB2B_1017_test.h5' + # remove previously generated + if os.path.exists(target_file_path): + os.remove(target_file_path) + + # Reduce (full) + controller = ReductionController() + controller.reduce_hidra_workflow(nexus_file, output_dir, progressbar=None, + calibration=None, mask=None, + project_file_name=target_file_path) + + # Get counts + sub_run_1_counts = controller.get_detector_counts(1, True) + assert sub_run_1_counts.shape == (1024, 1024) + assert np.sum(sub_run_1_counts) > 500000, 'Sub run 1 counts = {} is too small'.format(sub_run_1_counts.sum()) - # Split runs + # Get diffraction pattern + vec_2theta, vec_intensity = controller.get_powder_pattern(2) + assert 78 < vec_2theta.mean() < 82, '2theta range ({}, {}) shall be centered around 80 for sub run 2.' \ + ''.format(vec_2theta[0], vec_2theta[-1]) - # Split logs + # from matplotlib import pyplot as plt + # plt.plot(vec_2theta, vec_intensity) + # plt.show() - # Save to file + assert vec_intensity[~np.isnan(vec_intensity)].max() > 2,\ + 'Max intensity {} must larger than 2'.format(vec_intensity[~np.isnan(vec_intensity)].max()) - return + # Sample logs + assert abs(controller.get_sample_log_value('2theta', 1) - 69.99525) < 1E-5 + assert controller.get_sample_log_value('2theta', 2) == 80.0 + assert abs(controller.get_sample_log_value('2theta', 3) - 97.50225) < 1E-5 -def test_load_split_visualization(): +def test_diffraction_pattern_geometry_shift(): """ Returns @@ -73,77 +174,70 @@ def test_load_split_visualization(): """ pytest.skip('Manual reduction UI classes has not been refactored yet.') - # Init load/split service instance - - # Get list of sub runs - - # Split one specified sub run - - # verify result with gold data + return -@pytest.mark.parametrize('project_file, calibration_file, mask_file, gold_file', - [('data/HB2B_1017.h5', None, None, 'data/gold/1017_NoMask.h5'), - ('data/HB2B_1017.h5', None, 'data/HB2B_Mask_12-18-19.xml', 'data/gold/1017_Mask.h5')], - ids=('HB2B_1017_Masked', 'HB2B_1017_NoMask')) -def test_diffraction_pattern(project_file, calibration_file, mask_file, gold_file): +def parse_gold_file(file_name): """ Parameters ---------- - project_file - calibration_file - mask_file : str or None - mask file name - gold_file + file_name Returns ------- + ~dict or ~numpy.ndarray + gold data in array or dictionary of arrays """ - pytest.skip('Manual reduction UI classes has not been refactored yet.') - - # Load gold Hidra project file for diffraction pattern (run 1017) - hidra_project = EventHandler.load_project_file(None, project_file) - test_workspace = HidraWorkspace() - test_workspace.load_hidra_project(hidra_project, True, False) - - # Convert to diffraction pattern - powder_red_service = ReductionApp(use_mantid_engine=False) - - # Set workspace - powder_red_service.load_hidra_workspace(test_workspace) - - # Reduction - powder_red_service.reduce_data(sub_runs=None, - instrument_file=None, - calibration_file=calibration_file, - mask=mask_file, - mask_id=None, - van_file=None, - num_bins=1000) + # Init output + data_dict = dict() - # Load gold data - gold_data_set = parse_gold_file(gold_file) - gold_sub_runs = np.array(gold_data_set.keys()) - gold_sub_runs.sort() + # Parse file + gold_file = h5py.File(file_name, 'r') + data_set_names = list(gold_file.keys()) + for name in data_set_names: + if isinstance(gold_file[name], h5py.Dataset): + data_dict[name] = gold_file[name].value + else: + data_dict[name] = gold_file[name]['x'].value, gold_file[name]['y'].value + # END-FOR - # Test number of sub runs - np.testing.assert_allclose(gold_sub_runs, powder_red_service.get_sub_runs()) + if len(data_dict) == 1 and data_dict.keys()[0] == 'data': + # only 1 array with default name + return data_dict['data'] - # Get diffraction pattern - for sub_run_i in gold_sub_runs: - np.testing.assert_allclose(gold_data_set[sub_run_i], powder_red_service.get_diffraction_data(sub_run_i), - rtol=1e-8) + return data_dict -def test_diffraction_pattern_geometry_shift(): - """ +def write_gold_file(file_name, data): + """Write value to gold file (format) + Parameters + ---------- + file_name : str + output file + data : ~tuple or ~dict + numpy array data or dictionary of data Returns ------- """ - pytest.skip('Manual reduction UI classes has not been refactored yet.') + gold_file = h5py.File(file_name, 'w') + + if isinstance(data, np.ndarray): + dataset = {'data': data} + else: + dataset = data + + for data_name in dataset: + if isinstance(dataset[data_name], tuple): + # write (x, y) + group = gold_file.create_group(data_name) + group.create_dataset('x', data=dataset[data_name][0]) + group.create_dataset('y', data=dataset[data_name][1]) + else: + # write value directly + gold_file.create_dataset(data_name, data=dataset[data_name]) - return + gold_file.close() diff --git a/tests/integration/test_peak_fitting.py b/tests/integration/test_peak_fitting.py index 59f17dfe2..8feef103a 100644 --- a/tests/integration/test_peak_fitting.py +++ b/tests/integration/test_peak_fitting.py @@ -366,14 +366,14 @@ def test_write_csv(): assert exp == obs # verify the column headers - assert contents[len(EXPECTED_HEADER)].startswith('sub-run,variable1,fake_Center,') + assert contents[len(EXPECTED_HEADER)].startswith('sub-run,variable1,fake_dspacing_center,') assert contents[len(EXPECTED_HEADER)].endswith(',fake_chisq') assert len(contents) == len(EXPECTED_HEADER) + 1 + len(subruns), 'Does not have full body' # verify that the number of columns is correct # columns are (subruns, one log, parameter values, uncertainties, chisq) for line in contents[len(EXPECTED_HEADER) + 1:]: # skip past header and constant log - assert len(line.split(',')) == 1 + 1 + 7 * 2 + 1 + assert len(line.split(',')) == 1 + 1 + 8 * 2 + 1 # cleanup os.remove(csv_filename) @@ -391,9 +391,11 @@ def test_write_csv(): # Hidra project file = /some/place/random.h5 # Manual vs auto reduction # missing: S1width, S1height, S1distance, RadialDistance +# 2theta = 90.993 +/- 0 # chi = 0 +/- 0 -# phi = 0 +/- 0 -# omega = 135 +/- 0'''.split('\n') +# omega = 135 +/- 0 +# phi = 0 +/- 0'''.split('\n') + EXPECTED_HEADER_938 = '''# IPTS number = 22731 # Run = 938 @@ -407,21 +409,21 @@ def test_write_csv(): # Hidra project file = /some/place/random.h5 # Manual vs auto reduction # missing: S1width, S1height, S1distance, RadialDistance -# sx = 0.00073674 +/- 0 +# 2theta = 90.001 +/- 0 # chi = 0 +/- 0 -# phi = 0 +/- 0 # omega = 0 +/- 0 -# sz = 3.8844 +/- 0 +# phi = 0 +/- 0 +# sx = 0.00073674 +/- 0 # sy = 0.00057072 +/- 0 -# 2theta = 90.001 +/- 0'''.split('\n') +# sz = 3.8844 +/- 0'''.split('\n') @pytest.mark.parametrize('project_file_name, csv_filename, expected_header, num_subruns, num_logs,' ' startswith, endswith', - [('/HFIR/HB2B/shared/PyRS/HB2B_1065_Peak.h5', 'HB2B_1065.csv', EXPECTED_HEADER_1065, 99, 7, + [('/HFIR/HB2B/shared/PyRS/HB2B_1065_Peak.h5', 'HB2B_1065.csv', EXPECTED_HEADER_1065, 99, 6, 'sub-run,vx,vy,vz,', ',311_chisq'), ('data/HB2B_938_peak.h5', 'HB2B_938.csv', EXPECTED_HEADER_938, 1, 3, - 'sub-run,vx,vy,vz,Si111_Center', ',Si111_chisq')], + 'sub-run,vx,vy,vz,Si111_dspacing_center', ',Si111_chisq')], ids=['HB2B_1065_CSV', 'HB2B_938_CSV']) def test_write_csv_from_project(project_file_name, csv_filename, expected_header, num_subruns, num_logs, startswith, endswith): @@ -461,9 +463,9 @@ def test_write_csv_from_project(project_file_name, csv_filename, expected_header assert len(contents) == len(expected_header) + 1 + num_subruns, 'Does not have full body' # verify that the number of columns is correct - # columns are (subruns, seven logs, parameter values, uncertainties, chisq) + # columns are (subruns, seven logs, parameter values, uncertainties, d_spacing values, and uncertainties, chisq) for line in contents[len(expected_header) + 1:]: # skip past header and constant log - assert len(line.split(',')) == 1 + num_logs + 7 * 2 + 1 + assert len(line.split(',')) == 1 + num_logs + 7 * 2 + (1*2) + 1 # cleanup os.remove(csv_filename) diff --git a/tests/integration/test_powder_pattern.py b/tests/integration/test_powder_pattern.py index cd2b4f3df..456079f35 100644 --- a/tests/integration/test_powder_pattern.py +++ b/tests/integration/test_powder_pattern.py @@ -85,7 +85,9 @@ def test_2theta_calculation(): """ # Create geometry setup - test_setup = AnglerCameraDetectorGeometry(1024, 1024, 0.0003, 0.0003, 0.985, False) + pixel_size = 0.3 / 1024.0 + arm_length = 0.985 + test_setup = AnglerCameraDetectorGeometry(1024, 1024, pixel_size, pixel_size, arm_length, False) # Create instrument instrument = ResidualStressInstrument(test_setup) @@ -131,7 +133,9 @@ def test_powder_pattern_engine(project_file_name, mask_file_name, gold_file): test_project.close() # Create geometry setup: this is the default instrument setup - test_setup = AnglerCameraDetectorGeometry(1024, 1024, 0.0003, 0.0003, 0.985, False) + pixel_size = 0.3/1024.0 + arm_length = 0.985 + test_setup = AnglerCameraDetectorGeometry(1024, 1024, pixel_size, pixel_size, arm_length, False) # Create instrument # instrument = ResidualStressInstrument(test_setup) pyrs_engine = PyHB2BReduction(test_setup, None) diff --git a/tests/integration/test_autoreduction.py b/tests/integration/test_reduction.py similarity index 59% rename from tests/integration/test_autoreduction.py rename to tests/integration/test_reduction.py index ffe2c0ad3..6ec270a7c 100644 --- a/tests/integration/test_autoreduction.py +++ b/tests/integration/test_reduction.py @@ -1,11 +1,16 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility import os from pyrs.core.nexus_conversion import NeXusConvertingApp from pyrs.core.powder_pattern import ReductionApp from pyrs.dataobjects import HidraConstants +from pyrs.projectfile import HidraProjectFile, HidraProjectFileMode +from pyrs.core.workspaces import HidraWorkspace from matplotlib import pyplot as plt import numpy as np import pytest +DIAGNOSTIC_PLOTS = False + def checkFileExists(filename, feedback): '''``feedback`` should be 'skip' to skip the test if it doesn't exist, @@ -24,21 +29,34 @@ def checkFileExists(filename, feedback): def convertNeXusToProject(nexusfile, projectfile, skippable, mask_file_name=None): + ''' + Parameters + ========== + nexusfile: str + Path to Nexus file to reduce + projectfile: str or None + Path to the project file to save. If this is :py:obj:`None`, then the project file is not created + skippable: bool + Whether missing the nexus file skips the test or fails it + mask_file_name: str or None + Name of the masking file to use + :return: + ''' if skippable: checkFileExists(nexusfile, feedback='skip') else: checkFileExists(nexusfile, feedback='assert') # remove the project file if it currently exists - if os.path.exists(projectfile): + if projectfile and os.path.exists(projectfile): os.remove(projectfile) converter = NeXusConvertingApp(nexusfile, mask_file_name=mask_file_name) hidra_ws = converter.convert(use_mantid=False) - converter.save(projectfile) - - # tests for the created file - assert os.path.exists(projectfile), 'Project file {} does not exist'.format(projectfile) + if projectfile is not None: + converter.save(projectfile) + # tests for the created file + assert os.path.exists(projectfile), 'Project file {} does not exist'.format(projectfile) return hidra_ws @@ -57,11 +75,17 @@ def addPowderToProject(projectfile, use_mantid_engine=False, calibration_file=No assert os.path.exists(projectfile) +def test_no_counts(): + '''File when reactor was off''' + with pytest.raises(RuntimeError) as error_msg: + _ = convertNeXusToProject('/HFIR/HB2B/IPTS-22731/nexus/HB2B_439.nxs.h5', 'HB2B_439.h5', skippable=True) + assert 'has no count' in str(error_msg.value) + + @pytest.mark.parametrize('nexusfile, projectfile', - [('/HFIR/HB2B/IPTS-22731/nexus/HB2B_439.nxs.h5', 'HB2B_439.h5'), # file when reactor was off - ('/HFIR/HB2B/IPTS-22731/nexus/HB2B_931.nxs.h5', 'HB2B_931.h5'), # Vanadium + [('/HFIR/HB2B/IPTS-22731/nexus/HB2B_931.ORIG.nxs.h5', 'HB2B_931.h5'), # Vanadium ('data/HB2B_938.nxs.h5', 'HB2B_938.h5')], # A good peak - ids=('HB2B_439', 'HB2B_931', 'RW_938')) + ids=('HB2B_931', 'RW_938')) def test_nexus_to_project(nexusfile, projectfile): """Test converting NeXus to project and convert to diffraction pattern @@ -78,18 +102,12 @@ def test_nexus_to_project(nexusfile, projectfile): """ # convert the nexus file to a project file and do the "simple" checks - try: - test_hidra_ws = convertNeXusToProject(nexusfile, projectfile, skippable=True) - except RuntimeError as run_err: - if str(run_err).count('has no count') > 0: - pytest.skip('{} has not count and thus not supported now'.format(nexusfile)) - else: - raise run_err + test_hidra_ws = convertNeXusToProject(nexusfile, projectfile, skippable=True) # verify sub run duration sub_runs = test_hidra_ws.get_sub_runs() durations = test_hidra_ws.get_sample_log_values(HidraConstants.SUB_RUN_DURATION, sub_runs=sub_runs) - plt.plot(sub_runs, durations) + # plt.plot(sub_runs, durations) if projectfile == 'HB2B_439.h5': np.testing.assert_equal(sub_runs, [1, 2, 3, 4]) @@ -103,6 +121,51 @@ def test_nexus_to_project(nexusfile, projectfile): os.remove(projectfile) +@pytest.mark.parametrize('mask_file_name, filtered_counts, histogram_counts', + [('data/HB2B_Mask_12-18-19.xml', (540461, 1635432, 1193309), (590.1, 1788.2, 1306.2)), + (None, (548953, 1661711, 1212586), (518.8, 1580.6, 1154.6))], + ids=('HB2B_1017_Masked', 'HB2B_1017_NoMask')) +def test_reduce_data(mask_file_name, filtered_counts, histogram_counts): + """Verify NeXus converters including counts and sample log values""" + SUBRUNS = (1, 2, 3) + CENTERS = (69.99525, 80., 97.50225) + + # reduce with PyRS/Python + hidra_ws = convertNeXusToProject('/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.ORIG.nxs.h5', + projectfile=None, skippable=True, mask_file_name=mask_file_name) + + # verify subruns + np.testing.assert_equal(hidra_ws.get_sub_runs(), SUBRUNS) + + for sub_run, total_counts in zip(hidra_ws.get_sub_runs(), filtered_counts): + counts_array = hidra_ws.get_detector_counts(sub_run) + np.testing.assert_equal(counts_array.shape, (1048576,)) + assert np.sum(counts_array) == total_counts, 'mismatch in subrun={} for filtered data'.format(sub_run) + + # Test reduction to diffraction pattern + reducer = ReductionApp(False) + reducer.load_hidra_workspace(hidra_ws) + reducer.reduce_data(sub_runs=None, instrument_file=None, calibration_file=None, mask=None) + + # plot the patterns + if DIAGNOSTIC_PLOTS: + for sub_run, angle in zip(SUBRUNS, CENTERS): + x, y = reducer.get_diffraction_data(sub_run) + plt.plot(x, y, label='SUBRUN {} at {:.1f} deg'.format(sub_run, angle)) + plt.legend() + plt.show() + + # check ranges and total counts + for sub_run, angle, total_counts in zip(SUBRUNS, CENTERS, histogram_counts): + assert_label = 'mismatch in subrun={} for histogrammed data'.format(sub_run) + x, y = reducer.get_diffraction_data(sub_run) + assert x[0] < angle < x[-1], assert_label + assert np.isnan(np.sum(y)), assert_label + np.testing.assert_almost_equal(np.nansum(y), total_counts, decimal=1, err_msg=assert_label) + + # TODO add checks for against golden version + + def test_split_log_time_average(): """(Integration) test on doing proper time average on split sample logs @@ -113,7 +176,7 @@ def test_split_log_time_average(): """ # Convert the NeXus to project - nexus_file = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1086.nxs.h5' + nexus_file = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1086.ORIG.nxs.h5' project_file = 'HB2B_1086.h5' convertNeXusToProject(nexus_file, project_file, skippable=True) @@ -166,6 +229,10 @@ def test_apply_mantid_mask(): # Convert the NeXus to file to a project without mask and convert to 2theta diffraction pattern no_mask_project_file = 'HB2B_938_no_mask.h5' + if os.path.exists(no_mask_project_file): + os.remove(no_mask_project_file) + + # Convert to NeXust no_mask_hidra_ws = convertNeXusToProject(nexus_file, no_mask_project_file, skippable=False, mask_file_name=None) @@ -181,6 +248,9 @@ def test_apply_mantid_mask(): # Convert the NeXus to file to a project with mask and convert to 2theta diffraction pattern project_file = 'HB2B_938_mask.h5' + if os.path.exists(project_file): + os.remove(project_file) + # Convert masked_hidra_ws = convertNeXusToProject(nexus_file, project_file, skippable=False, mask_file_name='data/HB2B_Mask_12-18-19.xml') mask_array = masked_hidra_ws.get_detector_mask(True) @@ -206,9 +276,15 @@ def test_apply_mantid_mask(): assert no_mask_data_set[0].min() <= masked_data_set[0].min() assert no_mask_data_set[0].max() >= masked_data_set[0].max() + # from matplotlib import pyplot as plt + # plt.plot(no_mask_data_set[0], no_mask_data_set[1], color='red') + # plt.plot(masked_data_set[0], masked_data_set[1], color='blue') + # plt.plot() + # plt.show() + def test_hidra_workflow(tmpdir): - nexus = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1060.nxs.h5' + nexus = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1060.ORIG.nxs.h5' mask = '/HFIR/HB2B/shared/CALIBRATION/HB2B_MASK_Latest.xml' calibration = '/HFIR/HB2B/shared/CALIBRATION/HB2B_Latest.json' project = os.path.basename(nexus).split('.')[0] + '.h5' @@ -221,5 +297,34 @@ def test_hidra_workflow(tmpdir): os.remove(project) +def test_reduce_with_calibration(): + """Test reduction with calibration file + + Returns + ------- + + """ + nexus = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5' + mask = '/HFIR/HB2B/shared/CALIBRATION/HB2B_MASK_Latest.xml' + calibration = '/HFIR/HB2B/shared/CALIBRATION/HB2B_Latest.json' + project = os.path.basename(nexus).split('.')[0] + '_WL.h5' + project = os.path.join(os.getcwd(), project) + try: + # convert from NeXus to powder pattern + _ = convertNeXusToProject(nexus, project, True, mask_file_name=mask) + addPowderToProject(project, calibration_file=calibration) + + # load file + verify_project = HidraProjectFile(project, HidraProjectFileMode.READONLY) + verify_workspace = HidraWorkspace('verify calib') + verify_workspace.load_hidra_project(verify_project, load_raw_counts=False, load_reduced_diffraction=True) + wave_length = verify_workspace.get_wavelength(True, True) + assert not np.isnan(wave_length) + + finally: + if os.path.exists(project): + os.remove(project) + + if __name__ == '__main__': pytest.main([__file__]) diff --git a/tests/performance/__init__.py b/tests/performance/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/performance/test_convert_nexus.py b/tests/performance/test_convert_nexus.py deleted file mode 100644 index 469d8f56b..000000000 --- a/tests/performance/test_convert_nexus.py +++ /dev/null @@ -1,75 +0,0 @@ -import os -from pyrs.split_sub_runs.load_split_sub_runs import load_split_nexus_python -import pytest -import datetime - - -@pytest.mark.parametrize('ipts_number, run_start, run_stop', - [(22731, 1102, 1155), - (22048, 1180, 1182)], - ids=('IPTS22731', 'IPTS22048')) -def test_convert_nexus(ipts_number, run_start, run_stop): - """Test performance on converting NeXus to HidraWorkspace - - Using IPTS-???? Run ??? to ???. - - Returns - ------- - - """ - # ipts_number = 22731 - # run_start = 1102 - # run_stop = 1155 - - ipts_dir = '/HFIR/HB2B/IPTS-{}/split_sub_runs'.format(ipts_number) - if os.path.exists(ipts_dir) is False: - pytest.skip('Performance test is skipped due to no access to {}'.format(ipts_dir)) - - # a record dictionary for reduction time, key = time (second) - time_run_dict = dict() - non_exist_nexuses = list() - for run_number in range(run_start, run_stop): - # Skip known problematic runs - # 1119: no count - # 1123: low count with zero scan index - if run_number in [1119, 1123]: - continue - - # create NeXus file name - nexus_name = os.path.join(ipts_dir, 'HB2B_{}.nxs.h5'.format(run_number)) - # skip non-existing NeXus - if not os.path.exists(nexus_name): - non_exist_nexuses.append((run_number, nexus_name)) - continue - else: - print('Reducing Run {} In ({}, {})'.format(run_number, run_start, run_stop)) - - # load and split - start_time = datetime.datetime.now() - try: - counts_dict, sample_log_dict, mask_array = load_split_nexus_python(nexus_name, None) - except RuntimeError as run_err: - if str(run_err).count('does to have counts') > 0: - # it is OK to fail - continue - if str(run_err).count('is not valid') > 0: - # it is OK to fail: sub index error - continue - else: - raise run_err - stop_time = datetime.datetime.now() - - time_run_dict[(stop_time - start_time).total_seconds()] = run_number, nexus_name, counts_dict.keys() - # END-FOR - - # Non-existing NeXus - if len(non_exist_nexuses) > 0: - print(non_exist_nexuses) - - # Sort by time - run_time_list = sorted(time_run_dict.keys(), reverse=True) - for i in range(min(10, len(run_time_list))): - run_i, nexus_i, sub_runs_i = time_run_dict[run_time_list[i]] - print('Run {}: Time = {}, Number of sub runs = {}'.format(run_i, run_time_list[i], len(sub_runs_i))) - - return diff --git a/tests/unit/test_hidra_project_file.py b/tests/unit/test_hidra_project_file.py index a0777ba01..a7d3005cf 100644 --- a/tests/unit/test_hidra_project_file.py +++ b/tests/unit/test_hidra_project_file.py @@ -66,7 +66,7 @@ def test_mask(): # Test to read all user detector mask user_mask_dict = dict() verify_project_file.read_user_masks(user_mask_dict) - assert user_mask_dict.keys()[0] == 'test' + assert list(user_mask_dict.keys())[0] == 'test' # Read solid angle mask & compare verify_solid_mask = verify_project_file.read_mask_solid_angle('test') @@ -114,6 +114,54 @@ def test_detector_efficiency(): return +def test_wave_length_rw(): + """Test writing and reading for wave length + + Returns + ------- + + """ + # Set up for testing + test_file_name = 'test_wave_length.h5' + # Create a detector mask + gold_wave_length = 1.23456 + + # Generate a HiDRA project file + test_project_file = HidraProjectFile(test_file_name, HidraProjectFileMode.OVERWRITE) + test_project_file.save(True) + test_project_file.close() + + # Open file + verify_project_file = HidraProjectFile(test_file_name, HidraProjectFileMode.READONLY) + + # Read wave length (not exist) + wave_length_test = verify_project_file.read_wavelengths() + assert np.isnan(wave_length_test), 'No wave length read out' + + # Close + verify_project_file.close() + + # Generate a HiDRA project file + test_project_file = HidraProjectFile(test_file_name, HidraProjectFileMode.READWRITE) + + # Write wave length + test_project_file.write_wavelength(gold_wave_length) + + # Save and close + test_project_file.save(True) + test_project_file.close() + + # Open file again to verify + verify_project_file2 = HidraProjectFile(test_file_name, HidraProjectFileMode.READONLY) + + # Read wave length (not exist) + wave_length_test = verify_project_file2.read_wavelengths() + assert wave_length_test == gold_wave_length + + # Clean + os.remove(test_file_name) + + def next_test_monochromator_setup(): """ Test methods to read and write monochromator setup including @@ -210,13 +258,13 @@ def test_peak_fitting_result_io(): # parameter values # print('DEBUG:\n Expected: {}\n Found: {}'.format(test_params_array, peak_info[3])) - peak_values, _ = peak_info.get_native_params() + peak_values, peak_errors = peak_info.get_native_params() assert_allclose_structured_numpy_arrays(test_params_array, peak_values) # np.testing.assert_allclose(peak_info[3], test_params_array, atol=1E-12) # parameter values # assert np.allclose(peak_info[4], test_error_array, 1E-12) - assert_allclose_structured_numpy_arrays(test_error_array, peak_info.parameters_errors) + assert_allclose_structured_numpy_arrays(test_error_array, peak_errors) # Clean os.remove(test_file_name) diff --git a/tests/unit/test_load_split.py b/tests/unit/test_load_split.py new file mode 100644 index 000000000..8196e5dc6 --- /dev/null +++ b/tests/unit/test_load_split.py @@ -0,0 +1,73 @@ +from __future__ import (absolute_import, division, print_function) # python3 compatibility +from pyrs.projectfile import HidraProjectFile +from pyrs.utilities import calibration_file_io +from pyrs.core import workspaces +import numpy as np +from pyrs.core.nexus_conversion import NeXusConvertingApp +import os +import pytest + +FILE_1017 = '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.ORIG.nxs.h5' + + +def test_calibration_json(): + """Test reduce data with calibration file (.json)""" + # Get simulated test data + project_file_name = 'data/HB2B_000.h5' + calib_file = 'data/HB2B_CAL_Si333.json' + + # Import file + calib_obj = calibration_file_io.read_calibration_json_file(calib_file) + shift, shift_error, wave_length, wl_error, status = calib_obj + + # Verify result + assert shift + assert shift_error + assert abs(wave_length - 1.4499332864) < 1E-8 + assert wl_error + assert status == 3 + + # Import project file + project_file = HidraProjectFile(project_file_name, 'r') + + # Reduce + test_workspace = workspaces.HidraWorkspace('test calibration') + test_workspace.load_hidra_project(project_file, load_raw_counts=True, load_reduced_diffraction=False) + + # Test set and get wave length + test_workspace.set_wavelength(wave_length, True) + wave_length_i = test_workspace.get_wavelength(True, True) + assert wave_length_i == wave_length + + +@pytest.mark.skipif(not os.path.exists(FILE_1017), reason='File {} is not accessible'.format(FILE_1017)) +def test_log_time_average(): + """Test the log time average calculation""" + SUBRUNS_EXP = np.array([1, 2, 3]) + + processor = NeXusConvertingApp(FILE_1017) + + sub_run_times, sub_run_numbers = processor._splitter.times, processor._splitter.subruns + + # verify splitting information + # 2theta is the motor that determines the first start time + exp_times = np.array(['2019-11-10T16:31:02.645235328-0500', '2019-11-10T16:41:02.771317813-0500', # scan_index=1 + '2019-11-10T16:41:14.238680196-0500', '2019-11-10T17:11:14.249705287-0500', # scan_index=2 + '2019-11-10T17:11:33.208056929-0500', '2019-11-10T17:31:33.218615767-0500'], # scan_index=3 + dtype='datetime64[ns]') + np.testing.assert_equal(sub_run_numbers, SUBRUNS_EXP, err_msg='subrun numbers') + np.testing.assert_equal(sub_run_times, exp_times, err_msg='subrun filtering') + + # previous calculations + exp_durations = np.array([600., 1800., 1200.]) + np.testing.assert_almost_equal(processor._splitter.durations, exp_durations, decimal=0) + + # split the sample logs + sample_logs = processor.split_sample_logs(SUBRUNS_EXP) + + # verify two of the properties + np.testing.assert_allclose(sample_logs['2theta'], [69.99525, 80., 97.50225]) + + +if __name__ == '__main__': + pytest.main([__file__]) diff --git a/tests/unit/test_peak_collection.py b/tests/unit/test_peak_collection.py index e0f3423b4..0384b1606 100644 --- a/tests/unit/test_peak_collection.py +++ b/tests/unit/test_peak_collection.py @@ -5,13 +5,34 @@ import pytest -def test_peak_shape_enum(): - assert PeakShape.getShape('gaussian') == PeakShape.GAUSSIAN - assert PeakShape.getShape('GAUSSIAN') == PeakShape.GAUSSIAN +def check_peak_shape_enum(peak_shape, num_native_params): + """check peak shape enum + + Parameters + ---------- + peak_shape : str + Peak shape + num_native_params : integer + number of native parameters + + Returns + ------- + + """ + assert PeakShape.getShape(peak_shape) == PeakShape[peak_shape.upper()] + assert PeakShape.getShape(peak_shape.upper()) == PeakShape[peak_shape.upper()] with pytest.raises(KeyError): PeakShape.getShape('non-existant-peak-shape') - assert len(PeakShape.getShape('gaussian').native_parameters) == 3 + assert len(PeakShape.getShape(peak_shape).native_parameters) == num_native_params + + +def test_peak_shape_enum_gaussian(): + check_peak_shape_enum('gaussian', 3) + + +def test_peak_shape_enum_pseudoVoigt(): + check_peak_shape_enum('PseudoVoigt', 4) def test_background_enum(): @@ -24,19 +45,49 @@ def test_background_enum(): assert len(BackgroundFunction.getFunction('linear').native_parameters) == 2 -def test_peak_collection(): - # create test data with two peaks - both peaks will be normalized gaussians - NUM_SUBRUN = 2 +def check_peak_collection(peak_shape, NUM_SUBRUN, target_errors, + wavelength=None, d_reference=None, target_d_spacing_center=np.nan, + target_d_spacing_center_error=np.nan, target_strain=np.nan, target_strain_error=np.nan): + """check the peak collection + + Parameters + ---------- + subruns : numpy.array + 1D numpy array for sub run numbers + peak_shape : str + Peak shape + target_errors : numpy.ndarray + numpy structured array for peak/background parameter fitted target error + wavelength : float + neutron wavelength + target_d_spacing_center : list + d spacing center target value + target_d_spacing_center_error : list + d spacing center target error + + Returns + ------- + + """ subruns = np.arange(NUM_SUBRUN) + 1 chisq = np.array([42., 43.]) - raw_peaks_array = np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype('Gaussian', 'Linear')) - raw_peaks_array['Height'] = [1, 2] - raw_peaks_array['PeakCentre'] = [3, 4] - raw_peaks_array['Sigma'] = np.array([4, 5], dtype=float) + raw_peaks_array = np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype(peak_shape, 'Linear')) + if peak_shape == 'PseudoVoigt': + raw_peaks_array['Intensity'] = [1, 2] + raw_peaks_array['FWHM'] = np.array([4, 5], dtype=float) + raw_peaks_array['Mixing'] = [0, 1] + else: + raw_peaks_array['Height'] = [1, 2] + raw_peaks_array['Sigma'] = np.array([4, 5], dtype=float) + raw_peaks_array['PeakCentre'] = [90., 91.] + # background terms are both zeros - raw_peaks_errors = np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype('Gaussian', 'Linear')) + raw_peaks_errors = np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype(peak_shape, 'Linear')) + if wavelength is None: + peaks = PeakCollection('testing', peak_shape, 'Linear') + else: + peaks = PeakCollection('testing', peak_shape, 'Linear', wavelength=wavelength) - peaks = PeakCollection('testing', 'Gaussian', 'Linear') # uncertainties are being set to zero peaks.set_peak_fitting_values(subruns, raw_peaks_array, raw_peaks_errors, chisq) @@ -53,13 +104,59 @@ def test_peak_collection(): # check effective parameters obs_eff_peaks, obs_eff_errors = peaks.get_effective_params() assert obs_eff_peaks.size == NUM_SUBRUN - np.testing.assert_equal(obs_eff_errors, np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype(effective=True))) + np.testing.assert_equal(obs_eff_errors, target_errors) np.testing.assert_equal(obs_eff_peaks['Center'], raw_peaks_array['PeakCentre']) - np.testing.assert_equal(obs_eff_peaks['Height'], raw_peaks_array['Height']) - np.testing.assert_equal(obs_eff_peaks['FWHM'], 2. * np.sqrt(2. * np.log(2.)) * raw_peaks_array['Sigma']) + if peak_shape == 'PseudoVoigt': + np.testing.assert_equal(obs_eff_peaks['Intensity'], raw_peaks_array['Intensity']) + np.testing.assert_equal(obs_eff_peaks['FWHM'], raw_peaks_array['FWHM']) + np.testing.assert_equal(obs_eff_peaks['Mixing'], raw_peaks_array['Mixing']) + else: + np.testing.assert_equal(obs_eff_peaks['Height'], raw_peaks_array['Height']) + np.testing.assert_equal(obs_eff_peaks['FWHM'], 2. * np.sqrt(2. * np.log(2.)) * raw_peaks_array['Sigma']) np.testing.assert_equal(obs_eff_peaks['A0'], NUM_SUBRUN * [0.]) np.testing.assert_equal(obs_eff_peaks['A1'], NUM_SUBRUN * [0.]) - # not checking integrated intensity + + # check d spacing + obs_dspacing, obs_dspacing_errors = peaks.get_dspacing_center() + np.testing.assert_allclose(obs_dspacing, target_d_spacing_center, atol=0.01) + np.testing.assert_allclose(obs_dspacing_errors, target_d_spacing_center_error, atol=0.01) + + # check strain + if d_reference is None: + peaks.set_d_reference() + else: + peaks.set_d_reference(values=d_reference) + strain, strain_error = peaks.get_strain() + np.testing.assert_allclose(strain, target_strain, atol=0.0001) + np.testing.assert_allclose(strain_error, target_strain_error, atol=0.0001) + + +def test_peak_collection_Gaussian(): + NUM_SUBRUN = 2 + # without wavelength + check_peak_collection('Gaussian', NUM_SUBRUN, np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype(effective=True))) + # with wavelength + check_peak_collection('Gaussian', NUM_SUBRUN, np.zeros(NUM_SUBRUN, dtype=get_parameter_dtype(effective=True)), + wavelength=1.53229, d_reference=1.08, target_d_spacing_center=[1.08, 1.07], + target_d_spacing_center_error=[0.0, 0.0], target_strain=[0.0032, -0.0054], + target_strain_error=[0.0, 0.0]) + + +def test_peak_collection_PseudoVoigt(): + NUM_SUBRUN = 2 + # without wavelength + check_peak_collection('PseudoVoigt', NUM_SUBRUN, + np.array([(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)], + dtype=get_parameter_dtype(effective=True))) + # with wavelength + check_peak_collection('PseudoVoigt', NUM_SUBRUN, + np.array([(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)], + dtype=get_parameter_dtype(effective=True)), + wavelength=1.53229, d_reference=1.08, target_d_spacing_center=[1.08, 1.07], + target_d_spacing_center_error=[0.0, 0.0], target_strain=[0.0032, -0.0054], + target_strain_error=[0.0, 0.0]) if __name__ == '__main__': diff --git a/tests/unit/test_peak_fit_engine.py b/tests/unit/test_peak_fit_engine.py index f368a83e1..d8ce52a8e 100644 --- a/tests/unit/test_peak_fit_engine.py +++ b/tests/unit/test_peak_fit_engine.py @@ -1,15 +1,17 @@ from __future__ import (absolute_import, division, print_function) # python3 compatibility +from matplotlib import pyplot as plt import numpy as np +import os from pyrs.peaks import FitEngineFactory as PeakFitEngineFactory from pyrs.core.workspaces import HidraWorkspace from pyrs.core.peak_profile_utility import pseudo_voigt, PeakShape, BackgroundFunction from pyrs.core.peak_profile_utility import Gaussian, PseudoVoigt -import pytest, os -from matplotlib import pyplot as plt +import pytest from collections import namedtuple from pyrs.core import pyrscore + def generate_test_gaussian(vec_x, peak_center_list, peak_range_list, peak_height_list): """ Generate Gaussian function for test @@ -299,7 +301,8 @@ def test_1_gaussian_1_subrun(setup_1_subrun, fit_domain): """ # initialize fit engine fit_engine = PeakFitEngineFactory.getInstance(setup_1_subrun['workspace'], peak_function_name='Gaussian', - background_function_name='Linear', out_of_plane_angle=None) + background_function_name='Linear', wavelength=np.nan, + out_of_plane_angle=None) # Fit peak_tag = 'UnitTestGaussian' @@ -357,7 +360,7 @@ def test_2_gaussian_1_subrun(setup_1_subrun, fit_domain): """ fit_engine = PeakFitEngineFactory.getInstance(setup_1_subrun['workspace'], peak_function_name='Gaussian', - background_function_name='Linear') + background_function_name='Linear', wavelength=np.nan) # Fit @@ -440,7 +443,7 @@ def test_2_gaussian_3_subruns(target_values): # Fit fit_engine = PeakFitEngineFactory.getInstance(test_hd_ws, peak_function_name='Gaussian', - background_function_name='Linear') + background_function_name='Linear', wavelength=np.nan) fit_result = fit_engine.fit_multiple_peaks(peak_tags=['Left', 'Right'], x_mins=(72.5, 77.5), x_maxs=(77.5, 82.5)) @@ -551,7 +554,7 @@ def test_3_gaussian_3_subruns(target_values): # Fit fit_engine = PeakFitEngineFactory.getInstance(test_hd_ws, peak_function_name='Gaussian', - background_function_name='Linear') + background_function_name='Linear', wavelength=np.nan) fit_result = fit_engine.fit_multiple_peaks(peak_tags=['Left', 'Middle', 'Right'], x_mins=(72.5, 77.5, 82.5), x_maxs=(77.5, 82.5, 87.5)) @@ -575,6 +578,8 @@ def test_3_gaussian_3_subruns(target_values): ' not {}'.format(fit_cost2_lp[2]) +# pseudo-Voigt peak fitting only works on mantid versions with https://github.com/mantidproject/mantid/pull/27809 +@pytest.mark.skipif(ON_TRAVIS and sys.version_info.major == 2, reason='Need mantid version > 4.2.20200128') @pytest.mark.parametrize("setup_1_subrun", [{'peak_profile_type': 'PseudoVoigt', 'min_x': 75., 'max_x': 85., 'num_x': 500, 'peak_center': [80.], 'peak_range': [10. * 0.25], 'peak_intensities':[100.]}], indirect=True) @@ -590,7 +595,7 @@ def test_1_pv_1_subrun(setup_1_subrun, fit_domain): # Generate test workspace and initialize fit engine fit_engine = PeakFitEngineFactory.getInstance(setup_1_subrun['workspace'], peak_function_name='PseudoVoigt', - background_function_name='Linear') + background_function_name='Linear', wavelength=np.nan) # Fit peak_tag = 'UnitTestPseudoVoigt' @@ -633,7 +638,8 @@ def test_1_pv_1_subrun(setup_1_subrun, fit_domain): if fit_costs[0] > 1.0: # Plot - model_x, model_y = fit_engine.calculate_fitted_peaks(1, None) + model_x = fit_result.fitted.readX(0) + model_y = fit_result.fitted.readY(0) data_x, data_y = setup_1_subrun['workspace'].get_reduced_diffraction_data(1, None) assert data_x.shape == model_x.shape assert data_y.shape == model_y.shape diff --git a/tests/unit/test_sample_logs.py b/tests/unit/test_sample_logs.py index fd6dd75d5..a89e9cbce 100644 --- a/tests/unit/test_sample_logs.py +++ b/tests/unit/test_sample_logs.py @@ -1,7 +1,6 @@ import numpy as np import pytest from pyrs.dataobjects import SampleLogs -from pyrs.core.nexus_conversion import calculate_log_time_average def test_reassign_subruns(): @@ -61,37 +60,5 @@ def test_multi(): np.testing.assert_equal(sample['variable1', [10]], [0., 50., 75., 100.]) -def test_time_average(): - """Test method to do time average - - Example from 1086 - - Returns - ------- - - """ - # sub run 1 - log1_times = np.array([1.5742809368379436e+18, 1.574280940122952e+18, - 1.5742809402252196e+18, 1.5742809634411576e+18]) - log1_value = np.array([90.995, 81.9955, 81.99525, 84.]) - - # splitter - splitter1_times = np.array([1.5742809368379436e+18, 1.5742809601445215e+18]) - splitter1_value = np.array([1, 0]) - - log1_average = calculate_log_time_average(log1_times, log1_value, splitter1_times, splitter1_value) - assert abs(log1_average - 83.26374511281652) < 1E-10, '{} shall be close to {}' \ - ''.format(log1_average, 83.26374511281652) - - # sub run 2 - log2_times = np.array([1.5742809634411576e+18, 1.5742809868603507e+18]) - log2_vlaue = np.array([84., 86.00025]) - - splitter2_times = np.array([1.5742809368379436e+18, 1.574280963555972e+18, 1.574280983560895e+18]) - splitter2_value = np.array([0, 1, 0]) - log2_average = calculate_log_time_average(log2_times, log2_vlaue, splitter2_times, splitter2_value) - assert abs(log2_average - 84. < 1E-10), '{} shall be close to 84'.format(log2_average) - - if __name__ == '__main__': pytest.main([__file__]) diff --git a/tests/unit/test_utilities.py b/tests/unit/test_utilities.py index 5603ef4e5..aa5e110a3 100644 --- a/tests/unit/test_utilities.py +++ b/tests/unit/test_utilities.py @@ -1,9 +1,15 @@ #!/usr/bin/python # Test utilities methods without GUI +from __future__ import (absolute_import, division, print_function) # python3 compatibility +import os from pyrs.interface import gui_helper +from pyrs.utilities import get_default_output_dir, get_input_project_file, get_ipts_dir, get_nexus_file import pytest +MISSING_RUNNUMBER = 112123260 + + def test_parse_integers(): """ test main @@ -15,10 +21,68 @@ def test_parse_integers(): try: int_list = gui_helper.parse_integers('3.2, 4') except RuntimeError as run_err: - print run_err + print(run_err) else: raise AssertionError('Shall be failed but get {0}'.format(int_list)) +@pytest.mark.skipif(not os.path.exists('/HFIR/HB2B/shared/'), reason='HFIR data archive is not mounted') +def test_get_ipts_dir(): + """Test to get IPTS directory from run number + + Returns + ------- + + """ + # Test good + assert get_ipts_dir(1060) == '/HFIR/HB2B/IPTS-22731/', 'IPTS directory is not correct for run 1060' + + # Test no such run + with pytest.raises(RuntimeError): + get_ipts_dir(MISSING_RUNNUMBER) + + # Test exception + with pytest.raises(TypeError): + get_ipts_dir(1.2) + with pytest.raises(ValueError): + get_ipts_dir('1.2') + with pytest.raises(ValueError): + get_ipts_dir('abc') + + +@pytest.mark.skipif(not os.path.exists('/HFIR/HB2B/shared/'), reason='HFIR data archive is not mounted') +def test_get_default_output_dir(): + assert get_default_output_dir(1060) == '/HFIR/HB2B/IPTS-22731/shared/manualreduce', 'Output directory is not '\ + 'correct for run 1060' + + # Test no such run + with pytest.raises(RuntimeError): + get_default_output_dir(MISSING_RUNNUMBER) + + +@pytest.mark.skipif(not os.path.exists('/HFIR/HB2B/shared/'), reason='HFIR data archive is not mounted') +def test_get_input_project_file(): + assert get_input_project_file(1060) == '/HFIR/HB2B/IPTS-22731/shared/manualreduce', 'Output directory is not '\ + 'correct for run 1060' + + # Test no such run + with pytest.raises(RuntimeError): + get_input_project_file(MISSING_RUNNUMBER) + + # Test bad preferred type + with pytest.raises(ValueError): + get_input_project_file(1060, preferredType='nonsense') + + +@pytest.mark.skipif(not os.path.exists('/HFIR/HB2B/shared/'), reason='HFIR data archive is not mounted') +def test_get_nexus_file(): + assert get_nexus_file(1060) == '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1060.nxs.h5' + assert get_nexus_file(1017) == '/HFIR/HB2B/IPTS-22731/nexus/HB2B_1017.nxs.h5' + + # Test no such run + with pytest.raises(RuntimeError): + get_nexus_file(MISSING_RUNNUMBER) + + if __name__ == '__main__': pytest.main()