{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Utilities for reading h5USID files\n", "\n", "**Suhas Somnath**\n", "\n", "4/18/2018\n", "\n", "**This document illustrates the many handy functions in sidpy.hdf.hdf_utils and pyUSID.hdf_utils that significantly simplify reading data\n", "and metadata in Universal Spectroscopy and Imaging Data (USID) HDF5 files (h5USID files)**\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note**: Most of the functions demonstrated in this notebook have been moved out of ``pyUSID.hdf_utils`` and into ``sidpy.hdf``\n", "
\n", "\n", "## Introduction\n", "The USID model uses a data-centric approach to data analysis and processing meaning that results from all data analysis\n", "and processing are written to the same h5 file that contains the recorded measurements. **Hierarchical Data Format\n", "(HDF5)** files allow data, whether it is raw measured data or results of analysis, to be stored in multiple datasets within\n", "the same file in a tree-like manner. Certain rules and considerations have been made in pyUSID to ensure\n", "consistent and easy access to any data.\n", "\n", "The h5py python package provides great functions to create, read, and manage data in HDF5 files. In\n", "``pyUSID.hdf_utils``, we have added functions that facilitate scientifically relevant, or USID specific\n", "functionality such as checking if a dataset is a Main dataset, reshaping to / from the original N dimensional form of\n", "the data, etc. Due to the wide breadth of the functions in ``hdf_utils``, the guide for hdf_utils will be split in two\n", "parts - one that focuses on functions that facilitate reading and one that facilitate writing of data. The following\n", "guide provides examples of how, and more importantly when, to use functions in ``pyUSID.hdf_utils`` for various\n", "scenarios.\n", "\n", "## Recommended pre-requisite reading\n", "* [Universal Spectroscopic and Imaging Data (USID) model](https://pycroscopy.github.io/USID/usid_model.html)\n", "* [Crash course on HDF5 and h5py](./h5py_primer.html)\n", "\n", "\n", "## Import all necessary packages\n", "\n", "Before we begin demonstrating the numerous functions in ``pyUSID.hdf_utils``, we need to import the necessary\n", "packages. Here are a list of packages besides pyUSID that will be used in this example:\n", "\n", "* ``h5py`` - to open and close the file\n", "* ``wget`` - to download the example data file\n", "* ``numpy`` - for numerical operations on arrays in memory\n", "* ``matplotlib`` - basic visualization of data\n", "* ``sidpy`` - basic scientific hdf5 capabilities" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function, division, unicode_literals\n", "import os\n", "# Warning package in case something goes wrong\n", "from warnings import warn\n", "import subprocess\n", "import sys\n", "\n", "\n", "def install(package):\n", " subprocess.call([sys.executable, \"-m\", \"pip\", \"install\", package])\n", "# Package for downloading online files:\n", "\n", "try:\n", " # This package is not part of anaconda and may need to be installed.\n", " import wget\n", "except ImportError:\n", " warn('wget not found. Will install with pip.')\n", " import pip\n", " install(wget)\n", " import wget\n", "import h5py\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "# import sidpy - supporting package for pyUSID:\n", "try:\n", " import sidpy\n", "except ImportError:\n", " warn('sidpy not found. Will install with pip.')\n", " import pip\n", " install('sidpy')\n", " import sidpy\n", "\n", "# Finally import pyUSID.\n", "try:\n", " import pyUSID as usid\n", "except ImportError:\n", " warn('pyUSID not found. Will install with pip.')\n", " import pip\n", " install('pyUSID')\n", " import pyUSID as usid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to demonstrate the many functions in hdf_utils, we will be using a h5USID file containing real\n", "experimental data along with results from analyses on the measurement data\n", "\n", "### This scientific dataset\n", "\n", "For this example, we will be working with a **Band Excitation Polarization Switching (BEPS)** dataset acquired from\n", "advanced atomic force microscopes. In the much simpler **Band Excitation (BE)** imaging datasets, a single spectrum is\n", "acquired at each location in a two dimensional grid of spatial locations. Thus, BE imaging datasets have two\n", "position dimensions (``X``, ``Y``) and one spectroscopic dimension (``Frequency`` - against which the spectrum is recorded).\n", "The BEPS dataset used in this example has a spectrum for **each combination of** three other parameters (``DC offset``,\n", "``Field``, and ``Cycle``). Thus, this dataset has three new spectral dimensions in addition to ``Frequency``. Hence,\n", "this dataset becomes a 2+4 = **6 dimensional dataset**\n", "\n", "### Load the dataset\n", "First, let us download this file from the pyUSID Github project:\n", "\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Working on:\n", "temp.h5\n" ] } ], "source": [ "url = 'https://raw.githubusercontent.com/pycroscopy/pyUSID/master/data/BEPS_small.h5'\n", "h5_path = 'temp.h5'\n", "_ = wget.download(url, h5_path, bar=None)\n", "\n", "print('Working on:\\n' + h5_path)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, lets open this HDF5 file in read-only mode. Note that opening the file does not cause the contents to be\n", "automatically loaded to memory. Instead, we are presented with objects that refer to specific HDF5 datasets,\n", "attributes or groups in the file\n", "\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "h5_path = 'temp.h5'\n", "h5_f = h5py.File(h5_path, mode='r')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, ``h5_f`` is an active handle to the open file\n", "\n", "## Inspect HDF5 contents\n", "\n", "The file contents are stored in a tree structure, just like files on a contemporary computer. The file contains\n", "groups (similar to file folders) and datasets (similar to spreadsheets).\n", "There are several datasets in the file and these store:\n", "\n", "* The actual measurement collected from the experiment\n", "* Spatial location on the sample where each measurement was collected\n", "* Information to support and explain the spectral data collected at each location\n", "* Since the USID model stores results from processing and analyses performed on the data in the same h5USID file,\n", " these datasets and groups are present as well\n", "* Any other relevant ancillary information\n", "\n", "### print_tree()\n", "Soon after opening any file, it is often of interest to list the contents of the file. While one can use the open\n", "source software HDFViewer developed by the HDF organization, ``pyUSID.hdf_utils`` also has a very handy function -\n", "``print_tree()`` to quickly visualize all the datasets and groups within the file within python.\n", "\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Contents of the H5 file:\n", "/\n", "├ Measurement_000\n", " ---------------\n", " ├ Channel_000\n", " -----------\n", " ├ Bin_FFT\n", " ├ Bin_Frequencies\n", " ├ Bin_Indices\n", " ├ Bin_Step\n", " ├ Bin_Wfm_Type\n", " ├ Excitation_Waveform\n", " ├ Noise_Floor\n", " ├ Position_Indices\n", " ├ Position_Values\n", " ├ Raw_Data\n", " ├ Raw_Data-SHO_Fit_000\n", " --------------------\n", " ├ Fit\n", " ├ Guess\n", " ├ Spectroscopic_Indices\n", " ├ Spectroscopic_Values\n", " ├ Spatially_Averaged_Plot_Group_000\n", " ---------------------------------\n", " ├ Bin_Frequencies\n", " ├ Mean_Spectrogram\n", " ├ Spectroscopic_Parameter\n", " ├ Step_Averaged_Response\n", " ├ Spatially_Averaged_Plot_Group_001\n", " ---------------------------------\n", " ├ Bin_Frequencies\n", " ├ Mean_Spectrogram\n", " ├ Spectroscopic_Parameter\n", " ├ Step_Averaged_Response\n", " ├ Spectroscopic_Indices\n", " ├ Spectroscopic_Values\n", " ├ UDVS\n", " ├ UDVS_Indices\n" ] } ], "source": [ "print('Contents of the H5 file:')\n", "sidpy.hdf_utils.print_tree(h5_f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, ``print_tree()`` presents a clean tree view of the contents of the group. In this mode, only the group names\n", "are underlined. Alternatively, it can print the full paths of each dataset and group, with respect to the group / file\n", "of interest, by setting the ``rel_paths``\n", "keyword argument. ``print_tree()`` could also be used to display the contents of and HDF5 group instead of complete HDF5\n", "file as we have done above. Lets configure it to print the relative paths of all objects within the ``Channel_000``\n", "group:\n", "\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/Measurement_000/Channel_000\n", "Bin_FFT\n", "Bin_Frequencies\n", "Bin_Indices\n", "Bin_Step\n", "Bin_Wfm_Type\n", "Excitation_Waveform\n", "Noise_Floor\n", "Position_Indices\n", "Position_Values\n", "Raw_Data\n", "Raw_Data-SHO_Fit_000\n", "Raw_Data-SHO_Fit_000/Fit\n", "Raw_Data-SHO_Fit_000/Guess\n", "Raw_Data-SHO_Fit_000/Spectroscopic_Indices\n", "Raw_Data-SHO_Fit_000/Spectroscopic_Values\n", "Spatially_Averaged_Plot_Group_000\n", "Spatially_Averaged_Plot_Group_000/Bin_Frequencies\n", "Spatially_Averaged_Plot_Group_000/Mean_Spectrogram\n", "Spatially_Averaged_Plot_Group_000/Spectroscopic_Parameter\n", "Spatially_Averaged_Plot_Group_000/Step_Averaged_Response\n", "Spatially_Averaged_Plot_Group_001\n", "Spatially_Averaged_Plot_Group_001/Bin_Frequencies\n", "Spatially_Averaged_Plot_Group_001/Mean_Spectrogram\n", "Spatially_Averaged_Plot_Group_001/Spectroscopic_Parameter\n", "Spatially_Averaged_Plot_Group_001/Step_Averaged_Response\n", "Spectroscopic_Indices\n", "Spectroscopic_Values\n", "UDVS\n", "UDVS_Indices\n" ] } ], "source": [ "sidpy.hdf_utils.print_tree(h5_f['/Measurement_000/Channel_000/'], rel_paths=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, ``print_tree()`` can also be configured to only print USID Main datasets besides Group objects using the ``main_dsets_only`` option. \n", "\n", "**Note**: only ``pyUSID`` has this capability unlike ``sidpy``:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/\n", "├ Measurement_000\n", " ---------------\n", " ├ Channel_000\n", " -----------\n", " ├ Raw_Data\n", " ├ Raw_Data-SHO_Fit_000\n", " --------------------\n", " ├ Fit\n", " ├ Guess\n", " ├ Spatially_Averaged_Plot_Group_000\n", " ---------------------------------\n", " ├ Spatially_Averaged_Plot_Group_001\n", " ---------------------------------\n" ] } ], "source": [ "usid.hdf_utils.print_tree(h5_f, main_dsets_only=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing Attributes\n", "\n", "HDF5 datasets and groups can also store metadata such as experimental parameters. These metadata can be text,\n", "numbers, small lists of numbers or text etc. These metadata can be very important for understanding the datasets\n", "and guide the analysis routines.\n", "\n", "While one could use the basic ``h5py`` functionality to access attributes, one would encounter a lot of problems when\n", "attempting to decode attributes whose values were strings or lists of strings due to some issues in ``h5py``. This problem\n", "has been demonstrated in our `primer to HDF5 and h5py <./plot_h5py.html>`_. Instead of using the basic functionality of ``h5py``, we recommend always\n", "using the functions in pyUSID that reliably and consistently work for any kind of attribute for any version of\n", "python:\n", "\n", "### get_attributes()\n", "\n", "``get_attributes()`` is a very handy function that returns all or a specified set of attributes in an HDF5 object. If no\n", "attributes are explicitly requested, all attributes in the object are returned:\n", "\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "current_position_x : 4\n", "experiment_date : 26-Feb-2015 14:49:48\n", "data_tool : be_analyzer\n", "translator : ODF\n", "project_name : Band Excitation\n", "current_position_y : 4\n", "experiment_unix_time : 1503428472.2374\n", "sample_name : PZT\n", "xcams_id : abc\n", "user_name : John Doe\n", "comments : Band Excitation data\n", "Pycroscopy version : 0.0.a51\n", "data_type : BEPSData\n", "translate_date : 2017_08_22\n", "project_id : CNMS_2015B_X0000\n", "grid_size_y : 5\n", "sample_description : Thin Film\n", "instrument : cypher_west\n", "grid_size_x : 5\n" ] } ], "source": [ "for key, val in sidpy.hdf_utils.get_attributes(h5_f).items():\n", " print('{} : {}'.format(key, val))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "``get_attributes()`` is also great for only getting selected attributes. For example, if we only cared about the user\n", "and project related attributes, we could manually request for any that we wanted:\n", "\n" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "user_name : John Doe\n", "project_name : Band Excitation\n", "project_id : CNMS_2015B_X0000\n" ] } ], "source": [ "proj_attrs = sidpy.hdf_utils.get_attributes(h5_f, ['project_name', 'project_id', 'user_name'])\n", "for key, val in proj_attrs.items():\n", " print('{} : {}'.format(key, val))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### get_attr()\n", "\n", "If we are sure that we only wanted a specific attribute, we could instead use ``get_attr()`` as:\n", "\n" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "John Doe\n" ] } ], "source": [ "print(sidpy.hdf_utils.get_attr(h5_f, 'user_name'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### check_for_matching_attrs()\n", "Consider the scenario where we are have several HDF5 files or Groups or datasets and we wanted to check each one to\n", "see if they have the certain metadata / attributes. ``check_for_matching_attrs()`` is one very handy function that\n", "simplifies the comparision operation.\n", "\n", "For example, let us check if this file was authored by ``John Doe``:\n", "\n" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "print(sidpy.hdf.prov_utils.check_for_matching_attrs(h5_f, \n", " new_parms={'user_name': 'John Doe'}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Finding datasets and groups\n", "\n", "There are numerous ways to search for and access datasets and groups in H5 files using the basic functionalities\n", "of h5py. pyUSID.hdf_utils contains several functions that simplify common searching / lookup operations as part of\n", "scientific workflows.\n", "\n", "### find_dataset()\n", "\n", "The ``find_dataset()`` function will return all datasets that whose names contain the provided string. In this case, we\n", "are looking for any datasets containing the string ``UDVS`` in their names. If you look above, there are two datasets\n", "(UDVS and UDVS_Indices) that match this condition:\n", "\n" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n" ] } ], "source": [ "udvs_dsets_2 = usid.hdf_utils.find_dataset(h5_f, 'UDVS')\n", "for item in udvs_dsets_2:\n", " print(item)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you might know by now, h5USID files contain three kinds of datasets:\n", "\n", "* ``Main`` datasets that contain data recorded / computed at multiple spatial locations.\n", "* ``Ancillary`` datasets that support a main dataset\n", "* Other datasets\n", "\n", "For more information, please refer to the documentation on the USID model.\n", "\n", "### check_if_main()\n", "``check_if_main()`` is a very handy function that helps distinguish between ``Main`` datasets and other objects\n", "(``Ancillary`` datasets, other datasets, Groups etc.). Lets apply this function to see which of the objects within the\n", "``Channel_000`` Group are ``Main`` datasets:\n", "\n" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Main Datasets:\n", "----------------\n", "Raw_Data\n", "\n", "Objects that were not Main datasets:\n", "--------------------------------------\n", "Bin_FFT\n", "Bin_Frequencies\n", "Bin_Indices\n", "Bin_Step\n", "Bin_Wfm_Type\n", "Excitation_Waveform\n", "Noise_Floor\n", "Position_Indices\n", "Position_Values\n", "Raw_Data-SHO_Fit_000\n", "Spatially_Averaged_Plot_Group_000\n", "Spatially_Averaged_Plot_Group_001\n", "Spectroscopic_Indices\n", "Spectroscopic_Values\n", "UDVS\n", "UDVS_Indices\n" ] } ], "source": [ "h5_chan_group = h5_f['Measurement_000/Channel_000']\n", "\n", "# We will prepare two lists - one of objects that are ``main`` and one of objects that are not\n", "\n", "non_main_objs = []\n", "main_objs = []\n", "for key, val in h5_chan_group.items():\n", " if usid.hdf_utils.check_if_main(val):\n", " main_objs.append(key)\n", " else:\n", " non_main_objs.append(key)\n", "\n", "# Now we simply print the names of the items in each list\n", "\n", "print('Main Datasets:')\n", "print('----------------')\n", "for item in main_objs:\n", " print(item)\n", "print('\\nObjects that were not Main datasets:')\n", "print('--------------------------------------')\n", "for item in non_main_objs:\n", " print(item)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above script allowed us to distinguish Main datasets from all other objects only within the Group named\n", "``Channel_000``.\n", "\n", "### get_all_main()\n", "What if we want to quickly find all ``Main`` datasets even within the sub-Groups of ``Channel_000``? To do this, we have a\n", "very handy function called - ``get_all_main()``:\n", "\n" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "located at: \n", "\t/Measurement_000/Channel_000/Raw_Data \n", "Data contains: \n", "\tCantilever Vertical Deflection (V) \n", "Data dimensions and original shape: \n", "Position Dimensions: \n", "\tX - size: 5 \n", "\tY - size: 5 \n", "Spectroscopic Dimensions: \n", "\tFrequency - size: 87 \n", "\tDC_Offset - size: 64 \n", "\tField - size: 2 \n", "\tCycle - size: 2\n", "Data Type:\n", "\tcomplex64\n", "--------------------------------------------------------------------\n", "\n", "located at: \n", "\t/Measurement_000/Channel_000/Raw_Data-SHO_Fit_000/Fit \n", "Data contains: \n", "\tSHO parameters (compound) \n", "Data dimensions and original shape: \n", "Position Dimensions: \n", "\tX - size: 5 \n", "\tY - size: 5 \n", "Spectroscopic Dimensions: \n", "\tDC_Offset - size: 64 \n", "\tField - size: 2 \n", "\tCycle - size: 2\n", "Data Fields:\n", "\tPhase [rad], R2 Criterion, Quality Factor, Amplitude [V], Frequency [Hz]\n", "--------------------------------------------------------------------\n", "\n", "located at: \n", "\t/Measurement_000/Channel_000/Raw_Data-SHO_Fit_000/Guess \n", "Data contains: \n", "\tSHO parameters (compound) \n", "Data dimensions and original shape: \n", "Position Dimensions: \n", "\tX - size: 5 \n", "\tY - size: 5 \n", "Spectroscopic Dimensions: \n", "\tDC_Offset - size: 64 \n", "\tField - size: 2 \n", "\tCycle - size: 2\n", "Data Fields:\n", "\tPhase [rad], R2 Criterion, Quality Factor, Amplitude [V], Frequency [Hz]\n", "--------------------------------------------------------------------\n" ] } ], "source": [ "main_dsets = usid.hdf_utils.get_all_main(h5_chan_group)\n", "for dset in main_dsets:\n", " print(dset)\n", " print('--------------------------------------------------------------------')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The datasets above show that the file contains three main datasets. Two of these datasets are contained in a HDF5\n", "Group called ``Raw_Data-SHO_Fit_000`` meaning that they are results of an operation called ``SHO_Fit`` performed on the\n", "``Main`` dataset - ``Raw_Data``. The first of the three main datasets is indeed the ``Raw_Data`` dataset from which the\n", "latter two datasets (``Fit`` and ``Guess``) were derived.\n", "\n", "The USID model allows the same operation, such as ``SHO_Fit``, to be performed on the same dataset (``Raw_Data``),\n", "multiple\n", "times. Each time the operation is performed, a new HDF5 Group is created to hold the new results. Often, we may\n", "want to perform a few operations such as:\n", "\n", "* Find the (source / main) dataset from which certain results were derived\n", "* Check if a particular operation was performed on a main dataset\n", "* Find all groups corresponding to a particular operation (e.g. - ``SHO_Fit``) being applied to a Main dataset\n", "\n", "``hdf_utils`` has a few handy functions for many of these use cases.\n", "\n", "### find_results_groups()\n", "First, lets show that ``find_results_groups()`` finds all Groups containing the results of a ``SHO_Fit`` operation applied\n", "to ``Raw_Data``:\n", "\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Instances of operation \"SHO_Fit\" applied to dataset named \"/Measurement_000/Channel_000/Raw_Data\":\n", "[]\n" ] } ], "source": [ "# First get the dataset corresponding to Raw_Data\n", "h5_raw = h5_chan_group['Raw_Data']\n", "\n", "operation = 'SHO_Fit'\n", "print('Instances of operation \"{}\" applied to dataset named \"{}\":'.format(operation, h5_raw.name))\n", "h5_sho_group_list = usid.hdf_utils.find_results_groups(h5_raw, operation)\n", "print(h5_sho_group_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected, the ``SHO_Fit`` operation was performed on ``Raw_Data`` dataset only once, which is why\n", "``find_results_groups()`` returned only one HDF5 Group - ``SHO_Fit_000``.\n", "\n", "### check_for_old()\n", "\n", "Often one may want to check if a certain operation was performed on a dataset with the very same parameters to\n", "avoid recomputing the results. ``hdf_utils.check_for_old()`` is a very handy function that compares parameters (a\n", "dictionary) for a new / potential operation against the metadata (attributes) stored in each existing results group\n", "(HDF5 groups whose name starts with ``Raw_Data-SHO_Fit`` in this case). Before we demonstrate ``check_for_old()``, lets\n", "take a look at the attributes stored in the existing results groups:\n", "\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Parameters already used for computing SHO_Fit on Raw_Data in the file:\n", "machine_id : mac109728.ornl.gov\n", "timestamp : 2017_08_22-15_02_08\n", "SHO_guess_method : pycroscopy BESHO\n", "SHO_fit_method : pycroscopy BESHO\n" ] } ], "source": [ "print('Parameters already used for computing SHO_Fit on Raw_Data in the file:')\n", "for key, val in sidpy.hdf_utils.get_attributes(h5_chan_group['Raw_Data-SHO_Fit_000']).items():\n", " print('{} : {}'.format(key, val))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let us check for existing results where the ``SHO_fit_method`` attribute matches an existing value and a new value:\n", "\n" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Checking to see if SHO Fits have been computed on the raw dataset:\n", "\n", "Using \"pycroscopy BESHO\":\n", "[]\n", "\n", "Using \"alternate technique\"\n", "[]\n" ] } ], "source": [ "print('Checking to see if SHO Fits have been computed on the raw dataset:')\n", "print('\\nUsing \"pycroscopy BESHO\":')\n", "print(usid.hdf_utils.check_for_old(h5_raw, 'SHO_Fit',\n", " new_parms={'SHO_fit_method': 'pycroscopy BESHO'}))\n", "print('\\nUsing \"alternate technique\"')\n", "print(usid.hdf_utils.check_for_old(h5_raw, 'SHO_Fit',\n", " new_parms={'SHO_fit_method': 'alternate technique'}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Clearly, while find_results_groups() returned any and all groups corresponding to ``SHO_Fit`` being applied to\n", "``Raw_Data``, ``check_for_old()`` only returned the group(s) where the operation was performed using the same specified\n", "parameters (``sho_fit_method`` in this case).\n", "\n", "Note that ``check_for_old()`` performs two operations - search for all groups with the matching nomenclature and then\n", "compare the attributes. ``check_for_matching_attrs()`` is the handy function, that enables the latter operation of\n", "comparing a giving dictionary of parameters against attributes in a given object.\n", "\n", "### get_source_dataset()\n", "``hdf_utils.get_source_dataset()`` is a very handy function for the inverse scenario where we are interested in finding\n", "the source dataset from which the known result was derived:\n", "\n" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Datagroup containing the SHO fits:\n", "\n", "\n", "Dataset on which the SHO Fit was computed:\n", "\n", "located at: \n", "\t/Measurement_000/Channel_000/Raw_Data \n", "Data contains: \n", "\tCantilever Vertical Deflection (V) \n", "Data dimensions and original shape: \n", "Position Dimensions: \n", "\tX - size: 5 \n", "\tY - size: 5 \n", "Spectroscopic Dimensions: \n", "\tFrequency - size: 87 \n", "\tDC_Offset - size: 64 \n", "\tField - size: 2 \n", "\tCycle - size: 2\n", "Data Type:\n", "\tcomplex64\n" ] } ], "source": [ "h5_sho_group = h5_sho_group_list[0]\n", "print('Datagroup containing the SHO fits:')\n", "print(h5_sho_group)\n", "print('\\nDataset on which the SHO Fit was computed:')\n", "h5_source_dset = usid.hdf_utils.get_source_dataset(h5_sho_group)\n", "print(h5_source_dset)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the source dataset is always a ``Main`` dataset, ``get_source_dataset()`` results a ``USIDataset`` object instead of\n", "a regular ``HDF5 Dataset`` object.\n", "\n", "Note that ``hdf_utils.get_source_dataset()`` and ``find_results_groups()`` rely on the USID rule that results of an\n", "operation be stored in a Group named ``Source_Dataset_Name-Operation_Name_00x``.\n", "\n", "### get_auxiliary_datasets()\n", "\n", "The association of datasets and groups with one another provides a powerful mechanism for conveying (richer) information. One way to associate objects with each other is to store the reference of an object as an attribute of another. This is precisely the capability that is leveraged to turn Central datasets into USID Main Datasets or ``USIDatasets``. USIDatasets need to have four attributes that are references to the ``Position`` and ``Spectroscopic``\n", "``ancillary`` datasets. Note, that USID does not restrict or preclude the storage of other relevant datasets as attributes of another dataset. For example, the ``Raw_Data`` dataset appears to contain several attributes whose keys / names match the names of datasets we see above and values all appear to be HDF5 object references:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bin_Frequencies : \n", "Position_Indices : \n", "Excitation_Waveform : \n", "out_of_field_Plot_Group : \n", "Bin_Indices : \n", "Spectroscopic_Indices : \n", "UDVS : \n", "Bin_Wfm_Type : \n", "units : V\n", "Bin_Step : \n", "Bin_FFT : \n", "Spectroscopic_Values : \n", "UDVS_Indices : \n", "Position_Values : \n", "in_field_Plot_Group : \n", "Noise_Floor : \n", "quantity : Cantilever Vertical Deflection\n" ] } ], "source": [ "for key, val in sidpy.hdf_utils.get_attributes(h5_raw).items():\n", " print('{} : {}'.format(key, val))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As the name suggests, these HDF5 object references are references or addresses to datasets located elsewhere in the\n", "file. Conventionally, one would need to apply this reference to the file handle to get the actual HDF5 Dataset / Group\n", "object.\n", "\n", "``get_auxiliary_datasets()`` simplifies this process by directly retrieving the actual Dataset / Group associated with\n", "the attribute. Thus, we would be able to get a reference to the ``Bin_Frequencies`` Dataset via:\n", "\n" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "True\n" ] } ], "source": [ "h5_obj = sidpy.hdf_utils.get_auxiliary_datasets(h5_raw, 'Bin_Frequencies')[0]\n", "print(h5_obj)\n", "# Lets prove that this object is the same as the 'Bin_Frequencies' object that can be directly addressed:\n", "print(h5_obj == h5_f['/Measurement_000/Channel_000/Bin_Frequencies'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing Ancillary Datasets\n", "One of the major benefits of h5USID is its ability to handle large multidimensional datasets at ease. ``Ancillary``\n", "datasets serve as the keys or legends for explaining the dimensionality, reshape-ability, etc. of a dataset. There are\n", "several functions in hdf_utils that simplify many common operations on ancillary datasets.\n", "\n", "Before we demonstrate the several useful functions in hdf_utils, lets access the position and spectroscopic ancillary\n", "datasets using the ``get_auxiliary_datasets()`` function we used above:\n", "\n" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "dset_list = sidpy.hdf_utils.get_auxiliary_datasets(h5_raw, ['Position_Indices', 'Position_Values',\n", " 'Spectroscopic_Indices', 'Spectroscopic_Values'])\n", "h5_pos_inds, h5_pos_vals, h5_spec_inds, h5_spec_vals = dset_list" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As mentioned above, this is indeed a six dimensional dataset with two position dimensions and four spectroscopic\n", "dimensions. The ``Field`` and ``Cycle`` dimensions do not have any units since they are dimensionless unlike the other\n", "dimensions.\n", "\n", "### get_dimensionality()\n", "Now lets find out the number of steps in each of those dimensions using another handy function called\n", "``get_dimensionality()``:\n", "\n" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Size of each Position dimension:\n", "X : 5\n", "Y : 5\n", "\n", "Size of each Spectroscopic dimension:\n", "Frequency : 87\n", "DC_Offset : 64\n", "Field : 2\n", "Cycle : 2\n" ] } ], "source": [ "pos_dim_sizes = usid.hdf_utils.get_dimensionality(h5_pos_inds)\n", "spec_dim_sizes = usid.hdf_utils.get_dimensionality(h5_spec_inds)\n", "pos_dim_names = sidpy.hdf_utils.get_attr(h5_pos_inds, 'labels')\n", "spec_dim_names = sidpy.hdf_utils.get_attr(h5_spec_inds, 'labels')\n", "\n", "print('Size of each Position dimension:')\n", "for name, length in zip(pos_dim_names, pos_dim_sizes):\n", " print('{} : {}'.format(name, length))\n", "print('\\nSize of each Spectroscopic dimension:')\n", "for name, length in zip(spec_dim_names, spec_dim_sizes):\n", " print('{} : {}'.format(name, length))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### get_sort_order()\n", "\n", "In a few (rare) cases, the spectroscopic / position dimensions are not arranged in descending order of rate of change.\n", "In other words, the dimensions in these ancillary matrices are not arranged from fastest-varying to slowest.\n", "To account for such discrepancies, ``hdf_utils`` has a very handy function that goes through each of the columns or\n", "rows in the ancillary indices matrices and finds the order in which these dimensions vary.\n", "\n", "Below we illustrate an example of sorting the names of the spectroscopic dimensions from fastest to slowest in\n", "the BEPS data file:\n", "\n" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rate of change of spectroscopic dimensions: [0 2 1 3]\n", "\n", "Spectroscopic dimensions arranged as is:\n", "['Frequency' 'DC_Offset' 'Field' 'Cycle']\n", "\n", "Spectroscopic dimensions arranged from fastest to slowest\n", "['Frequency' 'Field' 'DC_Offset' 'Cycle']\n" ] } ], "source": [ "spec_sort_order = usid.hdf_utils.get_sort_order(h5_spec_inds)\n", "print('Rate of change of spectroscopic dimensions: {}'.format(spec_sort_order))\n", "print('\\nSpectroscopic dimensions arranged as is:')\n", "print(spec_dim_names)\n", "sorted_spec_labels = np.array(spec_dim_names)[np.array(spec_sort_order)]\n", "print('\\nSpectroscopic dimensions arranged from fastest to slowest')\n", "print(sorted_spec_labels)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### get_unit_values()\n", "\n", "When visualizing the data it is essential to plot the data against appropriate values on the X, Y, or Z axes.\n", "Recall that by definition that the values over which each dimension is varied, are repeated and tiled over the entire\n", "position or spectroscopic dimension of the dataset. Thus, if we had just the bias waveform repeated over two cycles,\n", "spectroscopic values would contain the bias waveform tiled twice and the cycle numbers repeated as many times as the\n", "number of points in the bias waveform. Therefore, extracting the bias waveform or the cycle numbers from the ancillary\n", "datasets is not trivial. This problem is especially challenging for multidimensional datasets such as the one under\n", "consideration. Fortunately, ``hdf_utils`` has a very handy function for this as well:\n", "\n" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Position unit values:\n", "Y : [0. 1. 2. 3. 4.]\n", "X : [0. 1. 2. 3. 4.]\n" ] } ], "source": [ "pos_unit_values = usid.hdf_utils.get_unit_values(h5_pos_inds, h5_pos_vals)\n", "print('Position unit values:')\n", "for key, val in pos_unit_values.items():\n", " print('{} : {}'.format(key, val))\n", "spec_unit_values = usid.hdf_utils.get_unit_values(h5_spec_inds, h5_spec_vals)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the spectroscopic dimensions are quite complicated, lets visualize the results from ``get_unit_values()``:\n", "\n" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcwAAAHJCAYAAAAIFgk1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzs3XmcFNW5//HPl31RHBVEGVCMEoxLAB0R1F9ijAtyVYhRg3GNRtQkN+s1wSyKibkxMdHodcE17gsaRVQS1KgxKovguCGiuMKgAWURZYfn90edjm1P90zN1lXd/bxfr35N96nq6tPVU/30OfXUOTIznHPOOdewdklXwDnnnCsFHjCdc865GDxgOuecczF4wHTOOedi8IDpnHPOxeAB0znnnIvBA2YFkzRa0pOSFktaLekdSZMkjUhBvX6cZB2KSdITkp5ohe0cIMmybqslLZQ0RdK3JXXK8xyTNL6lr50USeMl+bVxrijk12FWJknfBy4FbgAmAZ8AOwH/BbxmZj9NsG43AgeZWd+k6lBMknYFMLNXWridA4DHge8DzwIdgT7AwcCJwFzgYDNbkvWcYcBCM1vYktdOiqS+QF8zm550XVz584BZoSS9C8w2s6/lWdbOzDYlUK3M699IzIApqbOZrW37WqVfVsA82MwezVk2HHgMeNTMjkiges6VPO+SrVxbAe/nW5AdLCWdErrtvhS6az+W9KGkKyR1zX6epG6Sfi/pLUnrwt9fSGqXs14vSVdKWiBpbfh7i6TOIVieDFRndS2+HZ6X6XI8StK1kpYA/87a7ghJ00JX5IpQ34E5r32opGfC8o8lzZN0bs46gyTdF97n6rDOOVnLJelHoXydpPckXS6pR852TNJvwz5YGLb1pKTBOevV65JtaB/l+8waY2bTgAnA4ZJ2yqnj+KzH40PZLpKmSvpE0ruSvhWWnyjp1bDvHs/eVtY2xkp6QdIaSR9Iul7SVnn2zQWSvh/+T1ZK+qek3XLWa/DzytclK6lH+DwWhX03L3xeylon8790ZFj3g3C7VVJVzvZ+IGlu+PyWSZolqd4PTVf+OiRdAZeYmcDJkt4E7jez1xpZ/1ZgInAlMBQ4F+gOnAIgqQMwFdgV+A3wEjAM+BVRcP5JWG9L4JlQdgHwIrANMAroFJ7bC9gbODK8dm4L8v+AvxF1M3YJ2x0BPETUivoGsBnwa+ApSYPNrE7S54DJwD1h2TpgAPC5zIYlDQWeAOYDPwIWhnW+mPX6vwXOAa4AHsh6z4MkfTmndX4S8C7wPaBzeN1/SBpgZkvz7egY+6i5LeopwA+B/YA3Gln3buBa4I/Ad4AbJA0ADgDGEXX3XgrcDuyTVfcLiT7ry4CzgerwHnaXtK+Zbcx6jROAecAPwvu6CLhf0i5mtiHO55VL0Y+zh4A9if5HXyI6zXAx0f/Vz3OecinwIPBNYCDwB2Aj0Y82JB0P/Cm8/r+ArkT/C1vhKo+Z+a0Cb8Dnib6ILdw+AO4ADslZ75SwfEJO+S+Ivlg+Hx6fGNb7Up711gHbhMe/Ds8b0kDdbiQ6r5ZbfkB4jfvyLJsFvA50yCrbEVgPXBweHx2e36OB134SWAB0K7B8K6KAdWNO+Qlh20dmlWX2a/essv6hTr/JKnsCeCLrcaP7qEDdMvvnoALLB4blP8up4/isx+ND2UlZZVsCG4APs/cd0blSA3bIem8bgXNzXne/sN7onNd9HeiYVZb5fPZtwuc1HrCsx4eH55ySs9514XPrmbOvbspZ73JgDZ+erroceK6Yx6bf0nvzLtkKZVGLcgjwZaIW0/PA14Cpkn6Z5ykTcx7fSdSlPzQ8HgG8AzwjqUPmBjxM1BoZFtY7BHjWzGpbUP37sh9I6k7UorjLzDZkys3sLeBpovcI0XtcD9wp6WhJ2+RspxvRl/ttZraqwGsPI2oN3ZpTfidRUPlyTvkUM/skq05vA9OB4Q28v9bYR/lkuiTjJC78LXPHzJYBi4HpZvZR1jqvhr/9wt+Dif4nbsv5H5gBrAS+lPMaj5jZ+qzHL4W/24e/DX5eBXwJ2ETU8s12K9HnlrvfH8p5/BJRT0Dv8PhZYLCk/5N0UPgfcRXKA2YFM7ONZvakmf3SzA4i6up6CTgvdAtm+3eBx9Xh7zbADkRfcNm3mWH51ll/W5qR+V7O4y2JgkFuOUTnabcCMLP5wKFE//e3AO9Lmi7py1nbaddI/TJdcZ95rRCoP6R+V13ufsuUVecpz2iNfZRPJrDl20+5luU8XlegDEK3ONH/AETd2bn/B5vz6f9ARm6XdKaruQvE+rzy2QpYambrcsrfz1oeuw7AzcBZRN3OU4Glku6V1L+BOrgy5QHT/YeZLSLquupAdK4oW+8Cj+vC3w+Bt4jOPea7PRDW+4CGg0WsquY8XhbKts2z7rZkfSma2eNmNgKoAg4iahU+JKln2M6mRuqX2dZnXiu0pLam/hdw7n7LlNXlKc9ojX2Uz3+Fv0+1wbYh+h+AqIWc739gfFM32Mjnlc9SYCvVv+Z026zlTXl9M7OrzWwo0JPo3OZQ4K6mbMeVBw+YFUrSdgUW7RL+5mbQHpvzeAxRcJkRHv+dqAXzsZnNynP7IKz3MDBU0qAGqreWKLkiltDlORs4RlL7TLmkHYB9ic4R5j5nrZk9RpTk0R3YMXTDPgWcoJwM4CzTiVpWY3LKv0H0QyP3tUaGLuNMnfoTdetOa+AtxdlHTaLospIzgUmhq7otPEL0P7F9gf+BZr9uvs+rwKr/JPpeOyan/Hiiz62h/d5YHZaZ2V1Epyd2b+52XOnyLNnK9bKkR4kyJ98CegAjib5UJ5rZuznrj5R0EeHLHDgPuNnMXg/LbwO+RZQB+ifgBaJzRjsRZbuODgHpEqKMxEclXUDUBdyTKAP0TDNbCbxC1Eo4iyiZZ42ZZc5vFfIrovNRD0q6kihL9nxgBVGWI5LOJDrHNYUosacnUbbrIuDlsJ3/IfrSnRbex0KirurBZvbfZrY0lJ8j6ZOwrS8QZYI+Rf1zYquBh8O+6xzq9FHYD4XE2UcN+YKkj4mO7+2IWnwnEu3X0xt5brOZ2RuSfg9cruhynn8SJdD0Izq/eZ2ZPR53ezE/r1x/I/ocJkjqBcwh+r/+NvC7rB9ucetwDdH512lE53E/T7QvH27KdlyZSDrryG/J3IgC42SiRJ01RCP91AI/BTplrXcKIfsVuB/4mKhb6wqga842uxB1u71K1EpcSpQ0MZ7PZq9uA1xDdC5tHdGX4U1A57C8O1HGbqar9e1QfgANZ4GOIPpiW00UKO8HBmYtHx7KFoT6vUd0+cTAnO0MIepCXh629SqfzSwV0SUn80L93wv7o0fOdowooernRIF3DdGlCYNz1nuCrCzZOPuowPvP7J/MbQ1R1+8U4LTszzWnjuOzHo8PZR1y1nsbuLXA6x2UU34iUUv8k/D/Mpco27RvzutekPO8/mRluMb5vMjJkg1lPcLrZfbda+HzUoy6nxLK+4fHJ4fPZ3Gow1tEP2gKZu76rXxvPtKPa5CkU4C/AAMsSsJwMYUL6n9rZvmyjp1zJcbPYTrnnHMxeMB0zjnnYvAuWeeccy4Gb2E655xzMXjAdM4552LwgOmcc87F4AHTOeeci8EDpnPOOReDB0znnHMuBg+YzjnnXAweMJ1zzrkYPGA655xzMXjAdM4552LwgOmcc87F4AHTOeeci8EDpnPOOReDB0znnHMuBg+YzjnnXAweMJ1zzrkYPGA655xzMXjAdM4552LwgFniJL0tabWkj7NufZKul3OudUkaKOl5SSslfV9SV0kPSFoh6e6k61cJPGCWhyPMbLOs26LshZI6JFUx58pF1o/TlZKWS3pG0pmS2mWtM1TSlLB8qaSZkr4VY9tVkq6S9L6kVZJeyvO8nwKPm9nmZnYZcDTQG9jazI5pwfu6UdIFzX1+JfGAWYYk9Zdkkk6T9C7wWCgfFg7y5ZJekHRA1nN2lPTP8GXwiKTLJd0alh0gaWHOa7wt6aBwv52kcZLekPShpImStsqpy8mS3pX0gaRfZG2nvaSfh+eulDRbUj9JV0j6U85rTpb0o7bab87FcISZbQ7sAFwI/Ay4HkDScKJj7Z/AzsDWwFnAYQ1tUFIn4NGwzeHAFsDZwIWSfpy16g7AnJzHr5nZhpa/LReLmfmthG/A28BBOWX9AQNuBroDXYFq4ENgJNEPpYPD417hOdOAi4HOwJeAlcCtYdkBwMJCrwv8AJgO9A3Pvxq4I6cu14Z6DALWAl8Iy88GXgIGAgrLtwaGAouAdmG9nsAqoHfS+9xvlXkrcKwNBTYBuwNPAVc0Y7unAYuB7jnl3wA+BnoQBeKNwJpQdgewDlgfHp9GFKT/CawAPgDuytrWLsAjwFJgHnBsKB8btrEubOeBpPdzmm+JV8BvLfwAo4P4Y2B5uE3KClKfy1rvZ8AtOc+dCpwMbA9syD5ggdubEDDnAl/NWrZdOAg7ZNWlb9bymcCYcH8eMKrAe5sLHBzufw+YkvT+9lvl3vIFzFD+LvDdENC+0ozt3gnclKe8QzguDw2PnwC+nbV8fOYYDY/vAH5B9IO4C7B/KO8OLAC+FbY5JATUXcPyG4ELkt6/pXDzLtnyMNrMqsJtdFb5gqz7OwDHhO7Y5ZKWA/sTBbc+wDIz+yRr/Xea8Po7APdlbXcu0ZdH76x13s+6vwrYLNzvB7xRYLs3ASeE+ycAtzShTs4VyyKgiihQvdeM5/fM9zyLulo/CMvjWE90LPYxszVm9lQoPxx428z+YmYbzKwW+CvQ7POelcoDZnmzrPsLiFqYVVm37mZ2IdHBuqWk7lnrb591/xOgW+aBpPZAr5xtH5az7S5mVhejjguAnQosuxUYJWkQ8AWi1rNzaVNN1LuziegHaFN9kO95IVmvZ1gex0+JTmvMlDRH0qmhfAdgn5wfy8cD2zajrhXNA2bluBU4QtKhIdGmS0jm6Wtm7wCzgPMldZK0P3BE1nNfA7pI+i9JHYFfEp2rzJgA/FbSDgCSekkaFbNe1wG/kTRAkS9K2hrAzBYCzxK1LP9qZqtb8P6da3WS9iYKmE8S5QF8vRmbeRQ4LOcHK2Fba4nyAxplZu+b2elm1gc4A7hS0s5EP0r/mfODdjMzOyvz1GbUuSJ5wKwQZrYAGAX8HFhCdBCdzaf/A98E9iFKCjiPKGEo89wVwHeIglsdUYszO2v2UmAy8LCklUQH+D4xq3YxMBF4GPiIKOOwa9bym4A98O5YlyKSekg6nOj8461m9hJRC+8USWdnfvRJGiTpzkY2dwvR8XR3yCrvKOlQ4DJgfDj+4tTpGEl9w8NlRIFwE/Ag8HlJJ4Ztd5S0t6QvhHX/DXwu/ruvXAonfZ37DEnjgZ3N7ITG1m3jenyJqHW8g/k/q0uQpLeJzstvIApErxD9b04ws41hnaHA+cC+ROfxXyfKnL053zaztr0V8DtgNFFW7JvAJWZ2XdY6TxAF5+vC4/FkHaOS/kDU1boFURD8vZldE5YNJPpxOpToR/ILwI/N7HlJA4C7iRL0nsjJg3BZPGC6vNIQMEP3753AC2b266Tq4Zxz4F2yLqVCd9FyomSIPydcHeec8xamc861NUk/J8ofyPUvM2twJCCXHh4wnXPOuRjKblDunj17Wv/+/ZOuhnOtYvbs2R+YWa/G1ywuP85cOYl7nJVdwOzfvz+zZs1KuhrOtQpJTRlxqWj8OHPlJO5x5kk/zjnnXAweMJ1zzrkYPGA655xzMZTdOUznSsGk2joumjqPRctX06eqK2cfOpDRQ6qTrlZF8H3vmssDpnNFNqm2jnPufYnV6zcCULd8Nefc+xKAf3G3Md/3riW8S9a5IppUW8dPJr7wny/sjNXrN3LR1HkJ1apyXDR1nu9712weMJ0rkkzrZmOBwUIWLffZy9paoX3s+97F4QHTuSIo1LLM1qeqa8FlzSFpoKTns24fSfphzjoHSFqRtc65rVqJlCm0j1t737vy5OcwnWtDk2rrGD95DstXr29wva4d23P2oQNb9bXNbB4wGEBSe6K5TO/Ls+q/zOzwVn3xFJpUW8cna/N/DnXLV7PfhY95ApBrkAdM59pIboJJIe0lfnfUHm39Rf1V4A0zS+XIQW0tzmfhCUCuMd4l61wbiNMFC1HL8k/HDirGF/QY4I4Cy4ZLekHS3yTt1tYVSUK+ZJ98PAHINcQDpnOtrLHknowitSyR1Ak4Erg7z+LngB3MbBDwf8CkBrYzVtIsSbOWLFnSNpVtI01J6vEEIFeIB0znWtn5D8xJU8sS4DDgOTP7d+4CM/vIzD4O96cAHSX1zLcRM7vGzGrMrKZXr9RNoJLXpNo69rvwMZoyiaEB+134GJNq69qqWq5E+TlM51rRpNo6lq1qOMFny24dOe+I3Yp5nuw4CnTHStoW+LeZmaShRD+iPyxWxdpSY+ctO7YTCNZvrB9O/Xymy8cDpnOtJHPespD2UjFblQBI6g4cDJyRVXYmgJlNAI4GzpK0AVgNjLEymVW+ofOW1WFIvMx6dXm6YTPnMz1gugwPmM61UNxLR4odLAHM7BNg65yyCVn3LwcuL2qliqTQuUgBT4878D+PRw+pZsdxD+XttvXzmS6bn8N0rgUy3X6NBcuqrh29pVJkTRmkwAc0cHF4wHSumZpy6cj4I8vyao3UKjRIQaEBIs4+dCBdO7avV75q3QZP/nH/4V2yzjVD2i4dcZ8qlOzTULJVpiy3a33ZqvWe/OP+w1uYzjVRCgclcFkKJft069Shwc9i9JBquneu34bwwQxchrcwnWuCuC3LBC4dcUFLZiTx2UxcQzxgOhdTpmXZULBM4tIR91nbbtGF91asqVceJ4GnT1XXvJeYePKPA++Sda5Rk2rrGHz+w/zwrucbDJbeBZuszKg++YJl3NlgCiX/ZGYz8QSgyhY7YEpqL6lW0oPh8Y6SZkiaL+muMF4lkjqHx/PD8v5Z2zgnlM+TdGhW+YhQNl/SuKzyvK/hXLHEvWzEk3uSlfmcsluHCn+rq7rG/mxGD6nmd0ftQXWeFmVm9B8PmpWrKS3MHwBzsx7/HrjEzHYGlgGnhfLTgGWh/JKwHpJ2JZoxYTdgBHBlCMLtgSuIxrvcFTgurNvQazjX5jy5p3TkS/QxomD59LgDm/TZjB5SzdPjDswbND0BqLLFCpiS+gL/BVwXHgs4ELgnrHITMDrcHxUeE5Z/Naw/CrjTzNaa2VvAfGBouM03szfNbB1wJzCqkddwrk35ZSOlpS2SdTwByOWK28L8M/BTYFN4vDWw3Mw2hMcLgcw3RjWwACAsXxHW/095znMKlTf0Gs61GW9Zlp62GKnHR/9xuRoNmJIOBxab2ewi1KdZSnmePpcuTblsxFuWycsk+uTLbI2b6FOIj/7jcsW5rGQ/4EhJI4EuQA/gUqBKUofQAuwLZP6D6oB+wEJJHYAtiKYLypRnZD8nX/mHDbzGZ5jZNcA1ADU1NWUx04IrPr9spLQ0NH1XZjaSlnxOPvqPy9VoC9PMzjGzvmbWnyhp5zEzOx54nGhqIICTgfvD/cnhMWH5Y2G6oMnAmJBFuyMwAJgJPAsMCBmxncJrTA7PKfQazrUav2ykNBUa0ac5iT6F+Og/LltLrsP8GfBjSfOJzjdeH8qvB7YO5T8GxgGY2RxgIvAK8Hfgu2a2MbQevwdMJcrCnRjWbeg1nGsVftlI6SpWUo4n/7iMJo30Y2ZPAE+E+28SZbjmrrMGOKbA838L/DZP+RRgSp7yvK/hXGs5/4E5sZJ7SjVYSnobWAlsBDaYWU3OchGdYhkJrAJOMbPnil3PpppUW0c7KW+PQGsn5RQa/ceA/S58rMVdv650+Eg/rmJNqq1j2aqKaFl+xcwG5wbL4DCi0yMDgLHAVUWtWTM0lJjV0kSffAol/4APZlBpPGC6ipRJ8GlIhZyzHAXcbJHpRIl22yVdqYYUOnfZVj9uGhr9B/x8ZiXxgOkqTpxLR8roshEDHpY0W9LYPMsLXQddT1ou3yp07nCTWZt9XpnRf1RguZ/PrAw+W4mrKHEuHanq2pHacw8pYq3a1P5mVidpG+ARSa+a2ZPN2VBaLt9KckYRn82ksnkL01WEplw6Mv7I3YpYs7ZlZnXh72LgPuon0TV0fXSqtOUgBXH5bCaVzQOmK3uVeumIpO6SNs/cBw4BXs5ZbTJwkiLDgBVm9l6Rq9qofLORZDRlNpKW8tlMKpsHTFfWKnxc2N7AU5JeIBok5CEz+7ukMyWdGdaZArxJNBnCtcB3kqlqw4oxSEFcPptJ5fJzmK5sVfqMI+E65kF5yidk3Tfgu8WsV3OkcfCANNbJtS1vYbqyVOEty7KTxplD0lgn17Y8YLqy4zOOlJdJtXV8vLb++ediJfoU4rOZVB7vknVlxWccKS+FZiTZsltHzjtit0Q/Q5/NpPJ4C9OVjTgtS++CLS2Fkn26deqQis/QZzOpLN7CdGUhbsvSu2BLSykk1pRCHV3r8IDpStqk2rp6XWL5lPKMI5Ws1+adWbxybb3yNCXW+Og/lcO7ZF3JqtQBCSpBZlSffMEy6WSfXD76T+XwFqYrSXG6YMFblqUoX6KPiEaRr67qmrr5JzN1uWjqvHotzczoP9nrudLlLUxXcip9QIJyly/RJxMsiz2qT1w++k9l8BamKynesix/pZxEU8p1d43zFqYrGT4gQXnLnLcs9OmWQhJNoTq2k/xcZhnwFqYrGec/MKfBoe58QILSVWiAgoy0JfoUcvahA/O+j41mfi6zDHgL06VeZi7LZasKZ8P6gASlrdAABVDc6btaKjP9V3up3jI/l1n6PGC6VItz6Ygn95S+Quf4BKlN9Clk9JBqNhU4beDnMkubB0yXWnFnHPGWZX6S+kl6XNIrkuZI+kGedQ6QtELS8+F2bhJ1LbeZP8rt/biIB0yXSnETfKq6dvRgWdgG4CdmtiswDPiupF3zrPcvMxscbr8ubhWjz/qTtRvqlZfKect8fDCD8uRJPy51mnLpyPgjdytSrUqPmb0HvBfur5Q0F6gGXkm0YlnSPBtJS/hgBuXJW5guVfzSkbYhqT8wBJiRZ/FwSS9I+pukov4CSftsJC3hgxmUH29hutTwuSzbhqTNgL8CPzSzj3IWPwfsYGYfSxoJTAIGFNjOWGAswPbbb98qdauEC/0r4T1WikZbmJK6SJoZfoHOkXR+KD9Q0nOSXpZ0k6QOoVySLpM0X9KLkvbM2tbJkl4Pt5OzyveS9FJ4zmVSlJMtaStJj4T1H5G0ZevvApe0zGUjP7zreZ/LspVJ6kgULG8zs3tzl5vZR2b2cbg/BegoqWe+bZnZNWZWY2Y1vXr1apX6bVfVJW95OSXHeAJQ+YjTJbsWONDMBgGDgRGS9gVuAsaY2e7AO0AmAB5G9At1ANGv0asgCn7AecA+wFDgvKwAeBVwetbzRoTyccA/zGwA8I/w2JURn3Gk7YQfntcDc83s4gLrbJv1A3Uo0XfCh21dt8yoPouWr6m3rJSTffLxBKDy0WjAtMjH4WHHcNsIrDOz10L5I8DXw/1RwM3hedOBKknbAYcCj5jZUjNbFp4zIizrYWbTzcyAm4HRWdu6Kdy/KavclYG4l414y7LZ9gNOBA7MumxkpKQzJZ0Z1jkaeFnSC8BlRD+CGz6B3EKZH0nZyTCZy/xLaZCCuDKDGeQ7l5lJAPKgWRpincOU1B6YDewMXAHMBDpIqjGzWUQHXb+wejWwIOvpC0NZQ+UL85QD9A6ZfgDvA73jvS2Xdj7jSNszs6f4NBYVWudy4PLi1CjS2Gwk5Wj0kGpGD6lmvwsfq5c1m0kA8v/x9IuVJWtmG81sMNCXqDt1N2AMcImkmcBKolZnmwm/evN+u0oaK2mWpFlLlixpy2q4VuAty8pWyUkwlfzey0GTLisxs+XA48AIM5tmZv/PzIYCTwKZ7tk6Pm1tQhRk6xop75unHODfocuW8HdxgXq1ejKCaxt+2Yir5CSYSn7v5SBOlmwvSVXhflfgYOBVSduEss7Az4AJ4SmTgZNCtuwwYEXoVp0KHCJpy5DscwgwNSz7SNKwkHxwEnB/1rYyyUQnZ5W7EhSnZdle4s/fGEztuYd4sCxDk2rr+Gj1unrl5ZboU0ihBKBV6zb4ecwSEOcc5nbATeE8Zjtgopk9KOkiSYeHsqvM7LGw/hRgJDAfWAV8C8DMlkr6DfBsWO/XZrY03P8OcCPQFfhbuAFcCEyUdBpRJu6xzX6nLjGTausYP3lOo5mwPulzeSvXUX2aIvMec4+HZavW++g/JUBtnBBXdDU1NTZr1qykq+GCxuY5zPABCfKTNNvMapKuR67mHGf5El6gvJN9CvF9kS5xjzMfGs+1qcYmfQZP7qkUnvDyKd8XpckDpmszk2rrGpz0GfyykUqQGaSgUF9WJSa8FHrPBj6YQYp5wHRtIpPg0xBvWZa/fIMUZKuUZJ9chZJ/wAczSDMPmK7Vxbl0xC8bqQyFZiOB8hzVJ66GRv8Bn80krXy2Eteq4sw4UtW1I7XnHlLEWrmkFDonJ6j45JbM6D87jnsob3e1n89MH29hulYTp2Xpkz5XFr9Qv3G+j0qHB0zXKuIOSlCpXXCVJpPok+/cZaWetyzEBzMoHd4l61rEByVwuRq69ra6qitnHzrQ/w+y+GAGpcNbmK7ZfC5Ll0+hRJ/MRfn+f1Df6CHVdO9cv/3iyT/p4i1M1yxxknvAW5aVyC/Kbx7fb+nnLUzXZD6XZemQNELSPEnzJY3Ls7yzpLvC8hmS+rf0NT2JpXl8v6WfB0zXJD6XZekIEyZcARwG7AocJ2nXnNVOA5aZ2c7AJcDvW/Kak2rr+GRt/S56T/RpXKHkn7rlq330n5TwgOli87ksS85QYL6ZvWlm64A7gVE564wCbgr37wG+GqbZa7JPz2lv+Ey5/z/E09BgBj76Tzp4wHSx+FyWJakaWJD1eGEoy7uOmW0AVgBbN+fFCiX7dOvUwf8fYho9pJqnxx2YN2h6AlDyPOnHNcgvG3EZksYCYwG23377ess9aaX1+L5MJ29huoL8spGSVwf0y3rcN5TlXUdSB2AL4MN8GzOza8ysxsxqevXqVW+5J620Ht9XXIYjAAAgAElEQVSX6eQB0+XlyT1l4VlggKQdJXUCxgCTc9aZDJwc7h8NPGbNnFU+X9KKJ/s0jycAtY7MiFM7jnuoVfabd8m6evyykfJgZhskfQ+YCrQHbjCzOZJ+Dcwys8nA9cAtkuYDS4mCarNk/g8umjqPRctX08dH9Wm27H2ZO7xgJgEoez1XX+6IU62x39TMH5OpVVNTY7NmzUq6GiXLByRIF0mzzawm6Xrk8uOseAqNyZsZOcnl15T9Fvc48xam+4+mXDZy3hG7ebB0rgg8Aah52mK/ecB0/3H+A3MavWzEz1c6V1x9qrrmbSm1k5hUW+fHY45JtXVcNHVe3jlGoWWJU57045hUW8fg8x9m2arC2bCe3ONcMgolAG0088EMcmR6yfL9wICWJ6F5wKxwcS4d8eQe55KTGQGofZ4BmHwwg88qNHgGROcuW/o95gGzgsW9dMRbls4la/SQajYVyC3wc5mfKrQvBK0ytZwHzAoVN8GnqmtHD5bOpYAPZtC4tt5HHjArUFMGJRh/5G5FqpVzriE+mEHDijFTjmfJVhi/dMS50uSDGRSWO0hBRmt/jzXawpTURdJMSS9ImiPp/FD+VUnPSXpe0lOSdg7lBSeklXROKJ8n6dCs8ryT3IYhvWaE8rvC8F6umXzGEedKm89mkl+xZsqJ0yW7FjjQzAYBg4ERkoYBVwHHm9lg4Hbgl2H9vBPSholrxwC7ASOAKyW1b2SS298Dl4RtLQvbdk2UuWzkh3c932DL0i8dca40+GAGn1Ws/dFowLTIx+Fhx3CzcOsRyrcAFoX7hSakHQXcaWZrzewtYD7RBLd5J7kNzzkwbIOwzdHNfqcVymccca78eALQZ/Xu0SVveWvvj1hJP6El+DywGHjEzGYA3wamSFoInAhcGFYvNCFtoclsC5VvDSwP28gudzH5jCPOlSdPAIpkZiN5/6M19Za1xUw5sQKmmW0MXa99gaGSdgd+BIw0s77AX4CLW7VmTSBprKRZkmYtWbIkqWqkis844lz5ygxmkO9cZiYBqNyDZr5RfTJDO7TGIAX5NOmyEjNbDjxOdL5xUGhpAtwF7BvuF5qQttBktoXKPwSqwjayy/PVq8GJbSuNtyydK3+VngCUL9HH+HQ2krb4XouTJdtLUlW43xU4GJgLbCHp82G1TBkUnpB2MjAmZNHuCAwAZlJgktvwnMfDNgjbvL9F77YCNOWyEW9ZOlf6KjUBKIn3Hec6zO2Am0I2aztgopk9KOl04K+SNhFlsJ4a1s87IW2YuHYi8AqwAfiumW0EyDfJbdjWz4A7JV0A1IZtuwLizGXpM45UBkkXAUcA64A3gG+FHqLc9d4GVgIbgQ1pnHvTNazSZjNpy9lIGuMTSJeBSbV1jJ88p9FMWJ/0ufQ0dwJpSYcQ9e5skPR7ADP7WZ713gZqzOyDpmy/Eo+ztCp00T6U3zHf0HuF5r/fuMeZD41X4vyyEZePmT2clWE+nSgHwJWhSprNpK1nI2mMB8wS5sk9LqZTgb8VWGbAw5JmSxrb0EY8Gz29KmU2k7aejaQxHjBLlF824iQ9KunlPLdRWev8gihn4LYCm9nfzPYkynz/rqQvFXo9z0ZPt0oYzCDp9+gBs0Sd/8Acb1lWODM7yMx2z3O7H0DSKcDhRENY5v1lZWZ14e9i4D6ikbdcCSr3wQyi2Ug21CtviwEKCvGAWYIm1daxbFXD5yz9spHKJmkE8FPgSDNbVWCd7pI2z9wHDgFeLl4tXWsq58EMCuVqFPt7zgNmicmctyzEZxtxweXA5sAjYUahCQCS+kiaEtbpDTwl6QWia6IfMrO/J1Nd1xrKdTCDYs1G0hifD7OExDlv6V2wDiDM8JOvfBEwMtx/ExhUzHq54ii3wQzS8n68hVki4mTEVnXt6MHSOZd4ckxr61NVnNlIGuMBM+WaMpfl+CN3K2LNnHNpVSgBaNW6DSV1HjMzG0nd8uLMRtIY75JNscZGtcjwS0ecc9ky3wW5I4AtW7Wec+596TPrpFVD33/VVV05+9CBRX8P3sJMKR+UwDnXEqOHVNO9c/02Uakk/xRK9GnL2Uga4wEzhXxQAudca0hLskxzpLHuHjBTxluWzrnWUsrJP2msuwfMFPG5LJ1zralUR/+ZVFvHyjX1B2dJItEnmyf9pITPZemca22Z74qLps6rN2dmZvSf7PXSoFCyz5bdOnLeEbslWldvYSasKZeNeLB0zjVVqY3+k5ZRffLxFmaC/LIR51yxpDGJJp8019NbmAnx5B7nXDEVSpYxSMX5zMwgBYX62dKQqOQBMwF+2YhzrtgKJQBB8rOZZL4Tc8+zZiSd7JPhAbPIvGXpnEtCQ9N/QbLnMwudt4RooIK0NBz8HGYRNeWykaSzwZxz5Wf0kGpGD6lmx3EP5e36TOo8YaHXFfD0uAOLW5kGeAuzSOK0LH0uS9daJI2XVBfmwnxe0sgC642QNE/SfEnjil1Pl4y0DQqQtvoU4gGzCH456SV+5JeNuOK7xMwGh9uU3IWS2gNXAIcBuwLHSdq12JV0xZeW2Uw+nY2kfgszLects3mXbBubVFvHbdPfLZj5BZ7c4xIzFJgfJpJG0p3AKOCVRGvl2lwaZjNJ42wkjfEWZhvKdMM2FCy9Zena0PckvSjpBklb5lleDSzIerwwlLkKkPRsJmmcjaQxHjDbSJwEH29ZupaQ9Kikl/PcRgFXATsBg4H3gD+1wuuNlTRL0qwlS5a0dHMuBZIcJCDNAxQU4l2ybSDOuLACb1m6FjGzg+KsJ+la4ME8i+qAflmP+4ayQq93DXANQE1NTcOp3q4k9Knqmvf8YTGSbZJ87eZqtIUpqYukmZJekDRH0vmh/F9ZGXiLJE0K5ZJ0Wci6e1HSnlnbOlnS6+F2clb5XpJeCs+5TJJC+VaSHgnrP1KgWylV4rQsBRw/bHsPlq7NSNou6+HXgJfzrPYsMEDSjpI6AWOAycWon0uHpGYzmVRbxydr0zcbSWPidMmuBQ40s0FE3TsjJA0zs/+XycADpgH3hvUPAwaE21iiriEkbQWcB+xDlGxwXlYAvAo4Pet5I0L5OOAfZjYA+Ed4nFpxLx255BuDuWD0HkWsmatAfwg/Ql8EvgL8CEBSH0lTAMxsA/A9YCowF5hoZnOSqrArvoYGM2ir0X8yjYrlqzd8prwUpi1stEvWzAz4ODzsGG7/aT5J6gEcCHwrFI0Cbg7Pmy6pKvzaPQB4xMyWhuc9QhR8nwB6mNn0UH4zMBr4W9jWAWG7NwFPAD9r3lttO5Nq6+plm+XTtWP71P9DuPJgZicWKF8EjMx6PAWod8mJqxyZwQzyXd6RSQBqze+sNM9G0phYST+S2kt6HlhMFPRmZC0eTdQK/Cg8LpR511D5wjzlAL3N7L1w/32gd5z6FtOnv5YaDpae4OOcS7NiJeGUYrJPRqyAaWYbQ9drX2CopN2zFh8H3NEWlcupg0H+KzSSyt7zcWGdc+WiWKPtbLtFl6K8Tlto0mUlZrYceJxwjlFST6LzkQ9lrVYo866h8r55ygH+nUleCH8XF6jXNWZWY2Y1vXr1aspbajafccQ5V07aevSfzKg+761YU29Z2pN9MuJkyfaSVBXudwUOBl4Ni48GHjSz7D0wGTgpZMsOA1aEbtWpwCGStgzJPocAU8OyjyQNC9mxJwH3Z20rk017clZ5orxl6ZwrN5kEoKquHT9Tnhn9pyVBM9/0XQp/0zQbSWPitDC3Ax4P2XbPEp3DzFzTNYb63bFTgDeB+cC1wHcAQrLPb8I2ngV+nUkACutcF57zBlHCD8CFwMGSXgcOCo8T1ZQZR0rln8A556DtRv/Jl+hjpHtUn3ziZMm+CAwpsOyAPGUGfLfA+jcAN+QpnwXsnqf8Q+CrjdWxWOIMSNBe8lalc65ktUVSTikn+mTzofFiitOy9C5Y51ypK5R8Y9CswQwm1dbRTsq7rBQSfbJ5wIwh7oAE3gXrnCt1hZJ/oOmDGTTU0CiVRJ9sPpZsA3xAAudcpcl8j100dV7esV6bMphBoUEKSrWB4S3MAnxAAudcpRo9pJqnxx1I/o7U+OceC623yawkvzM9YObhl40451zLBzPYroQHKcjHA2YOH5DAOecizZ3NJDNIwaISHqQgHz+HmeP8B+bEall6sHTOlbuGzmdmEoCy14NPGx3Z36Pi0+suzz50YMl+d3oLM8uk2jqWrWr4nKUPSOCcqySZ85n5pgDLN6BBuQxSkI+3MIPMectCfEAC51wlizv4QLkMUpBPxQfMuJeOeLB0pUTSXUDmRFEVsDzMOJS73tvASmAjsMHMaopWSVdS+lR1zXuZSW4CT9z1SlFFB8x8fe35VHXt6MHSlRQz+0bmvqQ/ASsaWP0rZvZB29fKlbKzDx2Y9/syezaTQtdulnKiT7aKDZhxxoWF6IMef+RuRaqVc60rzAB0LHBg0nVxpS3TaMjtkVu2aj1n3/0CCNZvrP99WuqJPtkqMunHLx1xFeT/Af82s9cLLDfgYUmzJY0tYr1cCSo0m8n6TVYwWJZ6ok+2imthNqVl6cHSpZmkR4Ft8yz6hZll5o49jvpT8GXb38zqJG0DPCLpVTN7ssDrjQXGAmy//fYtqLkrZU1J3imHRJ9sFRUwmzKX5XlH7ObB0qWamR3U0HJJHYCjgL0a2EZd+LtY0n3AUCBvwDSza4BrAGpqaho+iFzZKpTUU2jdclIxXbJxZxz58zcGU3vuIR4sXTk4CHjVzBbmWyipu6TNM/eBQ4CXi1g/V4Iams0kW7kk+mSriIDpc1m6CjWGnO5YSX0kTQkPewNPSXoBmAk8ZGZ/L3IdXYkZPaSa3x21R96BDDLKdYCXiuiSLTTFTIYn97hyZGan5ClbBIwM998EBhW5Wq4MjB5Szegh1ex34WN5u2e7depQlt+nFdHCbOjEs7csnXOuecp5VJ98KiJgFjrx7C1L55xrvpZO/1VqKiJg5jtJ7S1L55xrmULfreWW7JNREecws6eoWbR8NX3KaOQJ55xLSqV9t1ZEwIRPT1I755xrPZX03VoRXbLOOedcS3nAdM4552KQNTJMXKmRtAR4p4FVegJpnMrI69U0aa0XtG7ddjCzXq20rVZTwsdZc/n7SbeWvp9Yx1nZBczGSJqVxklyvV5Nk9Z6QbrrVizltg/8/aRbsd6Pd8k655xzMXjAdM4552KoxIB5TdIVKMDr1TRprReku27FUm77wN9PuhXl/VTcOUznnHOuOSqxhemcc841WUUFTEkjJM2TNF/SuATr0U/S45JekTRH0g9C+VaSHpH0evi7ZUL1ay+pVtKD4fGOkmaE/XaXpE4J1KlK0j2SXpU0V9LwNOwvST8Kn+HLku6Q1CUN+yspaTnGmivtx2ZzpfGYbq4kvwsqJmBKag9cARwG7AocJ2nXhKqzAfiJme0KDAO+G+oyDviHmQ0A/hEeJ+EHwNysx78HLjGznYFlwGkJ1OlS4O9mtgvRHI5zSXh/SaoGvg/UmNnuQHuiSZvTsL+KLmXHWHOl/dhsrjQe082V3HeBmVXEDRgOTM16fA5wTtL1CnW5HzgYmAdsF8q2A+YlUJe+4R/uQOBBQEQXBHfItx+LVKctgLcI59yzyhPdX0A1sADYimhc5geBQ5PeX0nd0nyMteA9pebYbMF7SN0x3YL3kuh3QcW0MPn0yy1jYShLlKT+wBBgBtDbzN4Li94HeidQpT8DPwU2hcdbA8vNbEN4nMR+2xFYAvwldCtdJ6k7Ce8vM6sD/gi8C7wHrABmk/z+Skoqj7HmSuGx2VxpPKabK9HvgkoKmKkjaTPgr8APzeyj7GUW/VQqagqzpMOBxWY2u5ivG0MHYE/gKjMbAnxCTpdLQvtrS2AU0UHcB+gOjChmHVzbSNux2VwpPqabK9HvgkoKmHVAv6zHfUNZIiR1JDogbzOze0PxvyVtF5ZvBywucrX2A46U9DZwJ1EXzqVAlaTMVHBJ7LeFwEIzmxEe30N00CS9vw4C3jKzJWa2HriXaB8mvb+SkqpjrLlSemw2V1qP6eZK9LugkgLms8CAkB3WiSg5Y3ISFZEk4HpgrpldnLVoMnByuH8y0fmTojGzc8ysr5n1J9o/j5nZ8cDjwNEJ1ut9YIGkzDTuXwVeIeH9RdQVO0xSt/CZZuqV6P5KUGqOseZK67HZXGk9ppsr8e+CpE/iFvmE8UjgNeAN4BcJ1mN/oi6DF4Hnw20k0bmFfwCvA48CWyVYxwOAB8P9zwEzgfnA3UDnBOozGJgV9tkkYMs07C/gfOBV4GXgFqBzGvZXgv83qTjGWlD/1B+bLXhvqTqmW/A+Evsu8JF+nHPOuRgqqUvWOeecazYPmM4551wMHjCdc865GDxgOuecczF4wHTOOedi8IDpnHPOxeAB0znnnIvBA6ZzzjkXgwdM55xzLgYPmM4551wMHjCdc865GDxgOuecczF4wHR5Sdpe0seS2sdY9xRJTzWw/AlJ327dGjpXuSS9LemgpOtRaTxguszBtzoEyI8lfQxsMLPNzGxj0vVzrtRJ+qakWeH4ek/S3yTtn3S9XNN4wHQZR4QAmbktSrpCzpUDST8G/gz8L9Ab2B64EhiVZL1c03nAdHlJ6i/JJHUIj7eQdH34dVwn6YJC3bWSDpb0qqQVki4HVNTKO5cSkrYAfg1818zuNbNPzGy9mT0A/EnSKklbZ62/p6QlkjqGx6dLmitppaRXJO2Z5zXaSRon6Q1JH0qaKGmror3JCuIB08V1I7AB2BkYAhwC1DsvKakncC/wS6An8AawX9Fq6Vy6DAe6APflLjCz94EngGOzik8E7jSz9ZKOAcYDJwE9gCOBD/O8xn8Do4EvA32AZcAVrfYO3H94wHQZkyQtD7dJ2Qsk9QZGAj8Mv5AXA5cAY/JsZyQwx8zuMbP1RF1R77d15Z1Lqa2BD8xsQ4HlNwEnAIQem+OAW8KybwN/MLNnLTLfzN7Js40zgV+Y2UIzW0sUZI/O9A651uM71GWMNrNHMw8k9c9atgPQEXhP+k/vajtgQZ7t9MkuNzOTlG895yrBh0BPSR0KBM37gQmSdgQGAivMbGZY1o+oh6YxOwD3SdqUVbaR6HxpXfOr7nJ5wHRxLADWAj0b+KWc8R7RgQ6Aogjbr/DqzpW1aUTHzmjgntyFZrZG0kSiVuYufNq6hOi42ynGaywATjWzp1teXdcQ75J1jTKz94CHiZIUeoQkg50kfTnP6g8Bu0k6KnQJfR/Ytpj1dS4tzGwFcC5whaTRkrpJ6ijpMEl/CKvdDJxCdI4yO2BeB/yPpL0U2VnSDnleZgLw28wySb0keQZuG/CA6eI6CegEvEKUVHAPsF3uSmb2AXAMcCFRd9QAwH/5uoplZn8CfkyUCLeEqEX4PWBSWP40sAl4LvscpZndDfwWuB1YGdbPl/16KTAZeFjSSmA6sE9bvZ9KJjNLug7OOVfRJD0G3G5m1yVdF1eYB0znnEuQpL2BR4B+ZrYy6fq4wrxL1jnnEiLpJuBRoku2PFimnLcwnXPOuRi8hemcc87F4AHTOeeci6HsBi7o2bOn9e/fP+lqONcqZs+e/YGZ9Uq6Hrn8OHPlJO5xVnYBs3///syaNSvpajjXKiTlGzs0cX6cuXIS9zjzLlnnnHMuBg+YzjnnXAyJBUxJN0haLOnlAssl6TJJ8yW9mG/iVOdcw/w4c671JHkO80bgcqKBh/M5jGgc0gFE4yJeRQvGR5xUW8dFU+exaPlq+lR15exDBzJ6SHVzN+dcqbiRIh5nzqVJa3/vJ9bCNLMngaUNrDIKuDlMnDodqJJUb7DvOCbV1nHOvS9Rt3w1BtQtX805977EpFqfKs6Vt2IeZ86lSVt876f5HGY1n52geGEoa7KLps5j9fqNnylbvX4jF02d1/zaOVceWu04cy5N2uJ7P80BMzZJYyXNkjRryZIl9ZYvWr467/MKlTvn6mvsOHMuTera4Hs/zQGzDuiX9bhvKKvHzK4xsxozq+nVq/61p32quuZ9ga0369QK1XSupLXaceZcWtwze2HBZYXiQRxpDpiTgZNCFt8wYIWZvdecDZ196EC6dmz/mTIByz5Zx99fbtYmnSsXrXacOZcG1/3rTf7n7hf4/Dab0aXjZ0Nc147tOfvQgc3edpKXldwBTAMGSloo6TRJZ0o6M6wyBXgTmA9cC3ynua81ekg1vztqD6qruiKguqorv/3a7gzqV8V3bnuOO2e+29K341wqFfM4cy5JZsZFU1/lgofmMnKPbXng+/tz4VFf/Mz3/u+O2qNFWbJlN71XTU2NxR2ya9W6DZx563M8+doSxh22C2d+eac2rp1zTSNptpnVJF2PXE05zpxraxs3Gb+6/2Vun/Euxw3txwWj96B9O8V+ftzjLM1dsm2uW6cOXHdSDYd/cTsu/Nur/G7KXMrtB4RzzpWzdRs28f07a7l9xrucdcBO/O/XmhYsm6LsBl9vqk4d2nHpmCFs0bUjVz/5JstXree3X9udDu0r+reEc86l3idrN3DmrbP51+sf8PORuzD2S23bS1jxAROgfTtxwejd2ap7J/7vsfmsWL2eP48ZTJecRCHnnHPpsHzVOr5147O8sGA5fzj6ixxb06/xJ7WQN6MCSfzkkIH86vBd+fuc9zn1xmf5eO2GpKvlnHMux/sr1nDs1dOYU/cRVx6/V1GCJXjArOe0/XfkT8cMYsZbS/nmtdNZ+sm6pKvknHMueOuDTzh6wjPULVvNjafuzYjdty3aa3vAzOPre/Vlwgl78er7KzlmwjM+IpBzzqXAnEUrOGbCM6xat5E7xg5j3516FvX1PWAWcPCuvbn51KEs/mgtR1/1DG8s+TjpKjnnXMWa+dZSxlw9nU7t2zHxjOF8sW9V0evgAbMBwz63NXeMHcbaDZs4ZsI0Xlq4IukqOedcxfnH3H9z4vUz6NWjM3eftS87b7NZIvXwgNmI3au34O4zh9O1Y3uOu3Y60974MOkqOedcxbivdiFjb5nNwG035+4zhlPdgrFgW8oDZgyf67UZ95w1nO226MLJf5nJ1DnvJ10l55wre395+i1+dNcLDO2/FbefPoytN+ucaH08YMa03RZdmXjGcL6wXQ/OunU2d89a0PiTnHPONZmZcfEjr3H+A69wyK69+cu39mazzskPG+ABswm27N6J27+9D/vu1JOz73mR6/71ZtJVcs65srJpk3He5Dlc9o/XObamL1cev2dqBpHxgNlE3Tt34PpTahi5x7Zc8NBc/vD3V338WeecawXrNmzih3c9z83T3mHslz7H77/+xVQNU5p8G7cEde7Qnv87bk+26PoSVz7xBstWreeC0bu32YC/zjlX7lav28hZt83miXlL+NmIXTjrgPTNHuUBs5natxP/+7U9qOrWiaueeIOPVq/n4m8MonOHdHQdOOdcqVixaj2n3vQste8u43dH7cFxQ7dPukp5ecBsAUn8bMQubNmtI/875VU+WrOeCSfsRfcUnJx2zrlSsPijNZx0w0zeXPIJl39zT0busV3SVSooPZ3DJWzsl3biD0d/kafnf8Dx181gmY8/65xzjXr3w1UcPWEa7y5dxQ2n7J3qYAkeMFvNsTX9uPL4vXhl0Ucce/U03l+xJukqOedcas197yO+PuEZPlqznttPH8b+A4o7LmxzeMBsRSN235YbT92bRctX8/WrnuGtDz5JukrOOZc6s95eyjeunkZ7ibvPGM7gfsUfF7Y5PGC2sn136skdY4exev1GjpnwDC/X+fizzjmX8fi8xZxw/Qy23qwz95w1nAG9N0+6SrF5wGwDX+xbxcQzhtOpfTuOu2Y6M9708Wedc+7+5+s4/aZZ7NRrM+4+czh9t+yWdJWaJNGAKWmEpHmS5ksal2f59pIel1Qr6UVJI5OoZ3PsvM1m3H3WvvTq0ZmTbpjJo6/8O+kquQpUzseYKy23THubH971PHvusCV3jB1Gz4THhW2OxAKmpPbAFcBhwK7AcZJ2zVntl8BEMxsCjAGuLG4tW6a6qit3nzGcgdtuzhm3zube5xYmXSVXQSrhGHPpZ2Zc+ujr/Or+OXx1l2ie4R5dOiZdrWZJsoU5FJhvZm+a2TrgTmBUzjoG9Aj3twAWFbF+rWLrzTpz++nDGNp/K3488QVueOqtpKvkKkdFHGMuvTZtMs5/4BUuefQ1jtqzmgknpGdc2OZIMmBWA9lTfiwMZdnGAydIWghMAf67OFVrXZt17sBfvrU3h+zam18/+AoXPzzPx591xVAxx5hLn/UbN/GTu1/gxmfe5tT9duSPRw9K1biwzZH22h8H3GhmfYGRwC2S6tVZ0lhJsyTNWrJkSdErGUeXju258vg9ObamL5c9Np9z75/Dpk0eNF3iYh1jUBrHmUuHNes3cuYts7mvto7/OeTz/OrwL9CuDMbaTjJg1gH9sh73DWXZTgMmApjZNKALUO/qVjO7xsxqzKymV69ebVTdluvQvh2///oXGfulz3HL9Hf4wV3Ps27DpqSr5cpXqx1jYXlJHGcuWStWr+ek62fy2LzFXDB6d7534ACk0g+WkGzAfBYYIGlHSZ2IEg4m56zzLvBVAElfIDqYS/qnrSR+PvIL/GzELjzwwiJOv3kWq9ZtSLparjxV5DHmkrNk5VrGXDOd2gXLuGzMEE4YtkPSVWpViQVMM9sAfA+YCswlytSbI+nXko4Mq/0EOF3SC8AdwClWJif/zjpgJ3531B786/UlnHj9TFasWp90lVyZqfRjzBXXgqWrOGbCM7z9wSdcd/LeHDGoT9JVanUqt2OjpqbGZs2alXQ1Ypvy0nv88M7n2bFnd245bSjb9OiSdJVcikiabWY1SdcjV6kdZ65tzXt/JSfdMIM16zdxwyl7s9cOWyZdpSaJe5ylPemn7I3cYztuOGVvFixbxdcnPMM7H/r4s8650vHcu8s49uppmMHEM4aXXFa8WeQAAB94SURBVLBsCg+YKbD/gJ7cfvowVq7ZwNETpjH3vY+SrpJzzjXqydeWcPy1M6jq1pG/nrUvA7ctnXFhm8MDZkoM7lfF3WcMp73EsVdPY9bbS5OuknPOFfTgi4s47aZn6d+zO3efOZx+W5XWuLDN4QEzRQb03px7zhpOz806c8L1M3j81cVJV8k55+q5bcY7/PcdtQzuV8WdY4exzeaVkXvhATNl+m7ZjbvPHM5OvTbj9Jtncf/zuZfNOedcMsyMKx6fzy/ue5mvDNyGm0/dhy26lua4sM3hATOFem7WmTvGDmPPHbbkh3c9z83T3k66Ss65Crdpk/Hbh+Zy0dR5jB7ch6tP3IuunUp3XNjm8ICZUj26dOTmU4fy1V16c+79c7j00dd9/FnnXCI2bNzE2fe8yHVPvcUp+/bn4mMH07HEx4Vtjsp7xyWkS8f2TDhhT47as5pLHn2N8x94xcefdc4V1Zr1Gznrtuf463ML+dFBn+e8I3Yti3Fhm6ND0hVwDevQvh1/PHoQVV07ccPTb7F81TouOmZQRf66c84V18o16/n2TbOY8dZSzj9yN07et3/SVUqUB8wS0K6d+NXhX2Cr7h3548Ov8dGaDVzxzT0r7vyBc654Pvh4Laf8ZSavvreSS8cMZtTg3JnhKo83U0qEJL534AAuGL07j89bzEk3zGDFah9/1jnX+hYuW8WxE6Yxf/HHXHtSjQfLwANmiTlh2A5cNmYIzy9YzphrprN45Zqkq+ScKyPzF6/kmAnTWPLxWm45bR++sss2SVcpNTxglqAjBvXhupP35u0PPuGYCdNYsHRV0lVyzpWB5xcs55gJ01i/0Zh4xnD27r9V0lVKFQ+YJerLn+/Frd/eh+Wr1vP1q55h3vsrk66Sc66EPT3/A7557XQ269KBv541nC9s1yPpKqWOB8wSttcOWzLxjOEAHHv1NGa/syzhGjnnStHfX36Pb/3lWfpt2Y17ztyXHbbunnSVUskDZokbuO3m/PWsfanq1pETrpvBP19bknSVnHMl5M6Z7/Kd255j9+oeTDxjOL19Tt6CPGCWgX5bRePP9u/ZnW/f9CwPvLAo6So550rAhH++wbh7X+L/DYhO8WzRrXLGhW0OD5hlYpvNu3Dn2GEM7lfF9++s5dbp7yRdJedcSpkZv5sylwv/9ipHDOrDtSfV0K2TX5bfGA+YZWSLrh25+dR9+MrAbfjlpJe5/DEff9Y591kbNm5i3F9f4uon3+SEYdvz528MplMHDwVx+F4qM107tefqE/di9OA+/PHh17jgobk+/qxzDojGhf3e7bXcNWsB3z9wZ34zanfaV+i4sM3hbfAy1LF9Oy4+djBV3Tpx/VNvsXzVen7/9T3o4OPPOlexPl67gbE3z+KZNz7k3MN35dT9d0y6SiUn0W9QSSMkzZM0X9K4AuscK+kVSXMk3V7sOpaqdu3EeUfsyo8O+jx/fW4hZ976HGvWb0y6Wq7I/BhzAEs/Wcc3r53OjLeWcvGxgzxYNlNiLUxJ7YErgIOBhcCzkiab2StZ6wwAzgH2M7NlknyMpiaQxA8OGkBVt46cN3kOJ98wk2tPrqFHF8+EqwR+jDmARctXc+L1M1i4bDVXn7AXB+3aO+kqlawkW5hDgflm9qaZrQPuBEblrHM6cIWZLQMws8VFrmNZOHnf/lw6ZjCz/3979x0edZ3uffx9pyeU0EILICCg9DaEJD6u67queDgruhB6kWbC0aPncY/76HGrrrrqurqumhCKVEECHmV3VXxUWNZNIQkgvSQUSWihJLSEtO/5I6MbOSADTOY75X5d11zXlG/4fWaSm3vKb+7fwdOMS8/mxLmLtiMpz9AaC3CFJecYlZrJ8TMXWTQtTpvlDbLZMGOBQ/UuFzmvq68H0ENE/iEi2SIyzGPp/MyIAbHMmeygsOQcSWlZFJ3W+bMBQGssgG0tKiMpLYvKmlqWPRTP0K4tbUfyed6+F0gI0B34PjAOmCMizS5dJCIPiUieiOSVlOikmyu589bWLJ4+lBPnLjIqNYu9x3T+rHKtxkDrzJdkFZ5k3JxsIkODyUhJpE9stO1IfsFmwywGOta73MF5XX1FwGpjTJUxZj+wh7ri/hZjTLoxxmGMccTExDRYYH8wpHMLViQnUF1rSJqdxeZDpbYjqYbjthoDrTNfsWb7Uaa8vYF20RGsmpVIl1Y6F9ZdbDbMXKC7iHQRkTBgLLD6kjXvU/fMFxFpRd3bR/s8GdIf9WzXlFWzEmgSEcL4Odl8sfeE7UiqYWiNBZiMvEPMWpJPr3Z1c2HbRutcWHey1jCNMdXAI8AaYCewwhizXUSeEZH7nMvWACdFZAewFnjCGHPSTmL/clPLRqxKSaRTiyimLcjlo61HbEdSbqY1Fljm/n0fT6zcwm3dWrF0xlCaNwqzHcnviL+NTnM4HCYvL892DJ9RdqGKaQtz2fTVaZ57oC/j4jrZjqTqEZF8Y4zDdo5LaZ15D2MML6/ZzVvrChnetx1/GNOf8JBg27F8iqt15u07/agGFh0VyuLpcdzePYan3ttK6rpC25GUUi6qqTX8139v4611hYwf2onXxw3UZtmAtGEqosJCmDPZwX392/Pix7t44cOdOrRdKS93sbqGR5dtYtmGr3j4zpt57n6dC9vQdJasAiAsJIjXxgwgOjKU2ev3cfpCJc8/oPNnlfJG5y9Wk7Ikn7/vPcHPh/dkxu1dbUcKCNow1TeCgoRnRvSmeaMwXv9sL2XlVfxx7EAiQvUtHqW8xenzlUxdkMvW4jJeHtWPJEfHq/+Qcgt9+aC+RUR4/O4e/OrHvViz/RjTFuRy7mK17VhKKeBoWQWjZ2ex48gZUicM0mbpYdow1WVNva0Lr47pT87+U4yfk81JnT+rlFX7T5xnZGomR8oqWDg1jh/1bms7UsDRhqmu6IGBHUifNJjdR8+SNDuLw6XltiMpFZC2FZeRlJZJeVUNy2bGk3CzzoW1QRum+k539WzDomlxlJy5yKjUTAqOn7MdSamAkrPvJOPSswkPCSYjJYG+HXQurC3aMNVVDe3akuXJ8VTW1DJ6dhZbinT+rFKe8OmOY0yev4HWTcPJSEng5pjGtiMFNG2YyiW920eTkZJIZGgw49KzySzU+bNKNaT3NhaRvCSfW9s2ISMlkfbNIm1HCnjaMJXLurRqxKpZicQ2j+TB+bms2X7UdiSl/NL8L/bz+Iovie/agqUz42mhc2G9gjZMdU3aRkewIjmBXu2bMmtJPivyDl39h5RSLjHG8IdPdvPMX3YwrHdb5j84hMbh+nV5b6ENU12zZlFhLJ0xlNu6teJnK7cwZ70eDUqpG1Vba/jlB9t5/fMCxjg68uaEQToX1stow1TXpVF4CHOnOBjetx3PfbiTFz/epfNnlbpOldW1PPbuZhZnHyT5jq78bmRfnQvrhfS1vrpu4SHBvD5uINFRoaSuK6T0QiW/vV8LXalrcaGymllLNvK3PSU8ee+tpNxxs+1I6gq0YaobEhwkPHd/H5pHhfLm2kLKyqt4dcwAfStJKRfUPx7tiyP7MmaIHo/Wm2nDVDdMRHjinltpHhXGb/+6k7MVeaRNHEwj3VlBqSs6fqaCSfM2sP/Eed6aMIhhfdrZjqSuQj/DVG4z4/auvDyqH5mFJxk/N4fT5yttR1LKKx08eZ6RaZkUnb7A21OHaLP0EdowlVslOTqSOmEQO4+cYfTsLI6WVdiOpJRX2XnkDKPSsjhXUc07M+O5rVsr25GUi7RhKrf7Ue+2LJwax5GyCkamZrKvROfPKgWQd+AUo2dnERIkZKQk0L9jM9uR1DXQhqkaRMLNLVk2M57yqhqS0rLYVlxmO5JSVq3ddZyJ83KIaRzOylmJdGvdxHYkdY20YaoG07dDNBkpCUQ458/m7DtpO5JSVnywuZiZi/Lo1roxGSkJxOpcWJ9ktWGKyDAR2S0iBSLy5HesGykiRkQcnsynbtzNMXX/QbRuGs7k+Rv4dMcx25ECitaYfYuyDvAf727G0bk5y2bG07JxuO1I6jpZa5giEgy8CdwL9ALGiUivy6xrAjwG5Hg2oXKX9s0iyUhJ5Na2TUheks+q/CLbkQKC1phdxhj++OlefvnBdn7Ysw0LpsbRJCLUdix1A2y+wowDCowx+4wxlcByYMRl1j0LvAjo7pY+rEWjMJbOjCe+awt+mvEl877YbztSINAas6S21vCbP+/g1U/3MGpwB1InDCIiVId5+DqbDTMWqH+oiyLndd8QkUFAR2PMXz0ZTDWMxuEhzH9wCMN6t+XZv+zglU926/zZhqU1ZkFVTS2Pr9jMgswDzPg/XXhpZD9CgnV3EX/gtb9FEQkC/gD81IW1D4lInojklZSUNHw4dd3CQ4J5c8Igxjg68qfPC/jFB9uoqdWmacO11JhzvdbZVZRX1pC8OJ/3Nx/miXtu4enhPQnS2cp+w2bDLAY61rvcwXnd15oAfYB1InIAiAdWX26nBGNMujHGYYxxxMTENGBk5Q7BQcLvRvYl+Y6uLMn+iseWb6KyutZ2LH/kthoDrbOrKSuvYvL8HNbuPs7zD/Tl4Tu7IaLN0p/YHPaZC3QXkS7UFfFYYPzXNxpjyoBvRmCIyDrgP40xeR7OqRqAiPDUvT1pHhXG7z7axZmKatImDiIqTOfPupHWmIccP1vBlPm5FBw/yxvjBjG8n46680fWXmEaY6qBR4A1wE5ghTFmu4g8IyL32cqlPCvljpt5cWRfvthbwsS5OZRe0Pmz7qI15hmHTl0gKS2LAyfOM2/KEG2Wfkz8bacLh8Nh8vL0CbKv+XjbER5dtpkurRqxaHocbZpG2I7kFUQk3xjjdd+N1Dqrs/voWSbNy+FidS1vTx3CoE7NbUdS18HVOvPanX5UYBnWpx1vTx1C0ekLjErL5MCJ87YjKfWd8g+eZvTsLEQgIyVBm2UA0IapvMZt3Vrxzsx4zlVUMyotix2Hz9iOpNRl/W1P3UcIzaNCWZmSSI82Ohc2EGjDVF6lf8dmZKQkEBosjEnPIvfAKduRlPqWP395mBkLc+nSqhEZKYl0bBFlO5LyEG2Yyut0a92ElbMSiWkczqR5Oazdddx2JKUAWJJ9kEeXb2Jgx+YsT44nponOhQ0k2jCVV4ptFklGSgLdWjdm5qI83t9UfPUfUqqBGGN44/O9/Pz9bfzgltYsmh5HU50LG3C0YSqv1bJxOMtmxuPo3Jz/eHczCzMP2I6kAlBtreG3f93J7z/ZwwMDY0mbNFjnwgYobZjKqzWJCGXB1Dju7tWGX63ezmuf7tH5s8pjqmtqeWLlFuZ9sZ+pt3XmlaT+hOpc2IClv3nl9SJCg0mdMIhRgzvw2qd7+fXq7dTq/FnVwCqqakhZspFVG4t4/O4e/PJfe+lc2ACnc8iUTwgJDuKlkf1oFhnK3C/2U1pexe/12b5qIGcqqpi5MI8NB07x7IjeTErobDuS8gLaMJXPCAoSnh7ek+aNwnh5zW7OlFfx1oTBRIbp50nKfU6cu8iU+RvYffQsr40ZwIgBsVf/IRUQ9Om58ikiwsN3duP5B/qybk8Jk+fnUFZeZTuW8hNFp+vmwhaWnGPuFIc2S/Ut2jCVTxo/tBNvjBvE5kOljJmdxfGzFbYjKR+399hZRqVmcfLcRZbOGMr3b2ltO5LyMtowlc8a3q8d86YM4eDJulcFh05dsB1J+ajNh0pJmp1FjTG8m5zA4Jta2I6kvJA2TOXTvtcjhqUzh1J6oYqRqZnsOqrzZ9W1+WLvCcbPyaZpRCirUhLp2a6p7UjKS2nDVD5vUKfmZKQkIAKj07LIP3jadiTlIz7aeoRpC3Lp1CKKlSkJdGqpc2HVlWnDVH6hR5smrExJpEWjMCbOzeFve0psR1JebtmGr3j4nY306xDNuw8l0FqPwaquQhum8hsdW0SRkZJIl1aNmLEwlz9/edh2JOWlUtcV8tR7W/lejxgWTx9KdJTOhVVXpw1T+ZWYJuEsT45nYMfmPLp8E4uzD9qOpLyIMYYXPtzJix/vYsSA9syZ7NDv8SqXacNUfqdpRCiLpsfxg1ta84v3t/Gnz/bq/FlFdU0t/2/VFmav38fkhJt4dfQAnRSlron+tSi/FBEaTNqkwTwwMJZX/v8env3LTp0/G8Aqqmp4+J2NrMgr4rG7uvOb+3rrXFh1zXQ0nvJbocFBvJLUn2ZRocz/x35Kyyt5cWQ/fVURYM5drOahRXlkFp7kVz/uxdTbutiOpHyU1f85RGSYiOwWkQIRefIytz8uIjtEZIuIfCYiN9nIqXxXUJDwy3/txeN39+C9jcXMWpJPRVWN7VgeE+g1dvLcRcbPySZn/yleGzNAm6W6IdYapogEA28C9wK9gHEi0uuSZZsAhzGmH7ASeMmzKZU/EBEevas7z47ozWe7jjN5/gbOVPj//NlAr7HDpeUkzc5i99GzzJk8mPsH6lxYdWNsvsKMAwqMMfuMMZXAcmBE/QXGmLXGmK/nnWUDHTycUfmRSQmdeW3MADYePM249GxOnLtoO1JDC9gaKzh+jlGpmZScucji6UP5wa1tbEdSfsBmw4wFDtW7XOS87kqmAx81aCLl90YMiGXuFAeFJecCYf5sQNbYlqJSRs/OorLGsDw5nrguOhdWuYdP7P0gIhMBB/DyFW5/SETyRCSvpEQnvKjv9v1bWrN0xlBOnrtIUloWe4+dtR3JuqvVmHON19dZZuEJxqVnExUWzMqUBHq3j7YdSfkRmw2zGOhY73IH53XfIiI/BJ4G7jPGXPY9NGNMujHGYYxxxMTENEhY5V8G39SCd5MTqDGGpNlZbPrKL+fPuq3GwPvrbM32ozw4P5fY5pGsmpVI51aNbEdSfsZmw8wFuotIFxEJA8YCq+svEJGBwGzqCvm4hYzKj/Vs15RVKYk0jQhlwtwc/r7XO1813YCAqbEVeYeYtSSf3rFNWZGcQBudC6sagLWGaYypBh4B1gA7gRXGmO0i8oyI3Odc9jLQGMgQkc0isvoK/5xS16VTS+dRKlpEMW1BLh9uPWI7ktsESo3NWb+Pn63cwm3dWrF0xlCaRYXZjqT8lPjbyDCHw2Hy8vJsx1A+puxCFdMX5pL/1Wmef6Av4+I62Y4EgIjkG2MctnNcyhvqzBjDS2t2k7qukOH92vHq6AGEhfjEbhnKy7haZ/rXpRQQHRXK4ulDuaNHDE+9t5W31hXo/FkvVlNr+K//3krqukLGD+3E62MHarNUDU7/wpRyigwLZs5kByMGtOelj3fzwke7tGl6oYvVNfz7so0s23CIR+7sxnP39yFY58IqD9BZskrVExocxKujBxAdGUr6+n2cPl/JCz/pS4jOn/UK5y9Wk7Ikn7/vPcHPh/dkxu1dbUdSAUQbplKXCAoSfnNfb5pHhfHHz/ZSVl7F6+MGEhGqx0206fT5Sh5ckMu24jJ+n9SfUYP9YiiR8iH6tFmpyxAR/u/dPfjVj3vxyY5jTH07l7MBMH/WWx0tq2D07Cx2HjlD2sTB2iyVFdowlfoOU2/rwmtjBrDhwCnGz8nhpP/Pn/U6+0rOMTI1kyNlFSyaFsfdvXQurLJDG6ZSV3H/wFjmTB7MnmNnSZqdRXFpue1IAWNbcRlJaVlUVNWw/KF44ru2tB1JBTBtmEq54Ae3tmHx9KGUnLnIqNRMCo6fsx3J7+XsO8m49GwiQoPJSEmgT6zOhVV2acNUykVxXVqwPDmeqhpDUlomW4pKbUfyW5/uOMbk+RtoEx3BylkJdI1pbDuSUtowlboWvdtHszIlgUbhIYxLzyaz4ITtSH5nVX4RyUvyubVtE1YkJ9AuOtJ2JKUAbZhKXbPOrRqxalYisc0jefDtXD7edtR2JL8x74v9/DTjS+K7tmDpzHhaNNK5sMp7aMNU6jq0aRrBiuQEesc25d+W5rMi99DVf0hdkTGGVz7ZzbN/2cG9fdoy/8EhNA7Xr4kr76INU6nr1CwqjKUzhnJbt1b8bNUW0tcX2o7kk2pqDb/4YBt/+ryAsUM68sb4QYSH6JAI5X20YSp1A6LCQpg3ZQjD+7Xj+Q938TudP3tNKqtreWz5JpZkf0XKHTfzwk/66lxY5bX0PQ+lblBYSBCvjx1IdGQoaX8rpPRCJc89oP/xX82FympSlmxk/Z4Snrr3VpLvuNl2JKW+kzZMpdwgOEh47v4+tIgK4421BZSVV/Ha2AH61uIVlF6oZNqCXDYfKuWlkf0YPaSj7UhKXZW+JauUm4gI/3nPLfx8eE8+2naU6QvyOH+x2nYsr3PsTAVjZmezrfgMb00YrM1S+QxtmEq52Yzbu/L7pP5k7TvJ+Lk5nD5faTuS1zhw4jyj0jIpOn2BBVOHMKxPW9uRlHKZNkylGsCowR1ImziYnUfOkDQ7iyNlOn92x+EzjErL4lxFNe/MjCexWyvbkZS6JtowlWogd/dqw6JpcRwtq2BUahb7SgJ3/mzugVOMSc8iNFjISEmkf8dmtiMpdc20YSrVgOK7tmT5Q/FUVNWQlJbFtuIy25E8bu2u40yal0NMk3BWzkqkW2udC6t8k9WGKSLDRGS3iBSIyJOXuT1cRN513p4jIp09n1KpG9MnNpqMlAQiQoMZm55N9r6THtu27Rp7f1MxMxfl0b11EzKSE4htpnNhle+y9rUSEQkG3gTuBoqAXBFZbYzZUW/ZdOC0MaabiIwFXgTGeD6tUjema0xjVs5KYNK8DUyev4FJ8Tfx8bajHC4tp32zSJ645xbuHxjr1m3aqLH3NxXz8prdHC4tp2lkKGXlVSR0bUn65ME0iQi9kbujlHU2X2HGAQXGmH3GmEpgOTDikjUjgIXO8yuBu0REvw2ufFK76EhWJCfQpkk4877YT3FpOQYoLi3nqfe28v6mYndv0qM19v6mYp56b+s396usvIoggZ8MitVmqfyCzYYZC9SfWF3kvO6ya4wx1UAZoIdcVz6rRaMwqmv/9+i88qoaXl6z292b82iNvbxmN+VVNd+6rtbAa5/uvZ5/Timv4xc7/YjIQyKSJyJ5JSUltuMo9Z2OllVc9vrDpd791ZOr1dmV8nv7/VLKVTYbZjFQf8RHB+d1l10jIiFANPC/9pgwxqQbYxzGGEdMTEwDxVXKPdpfYceXK11/A9xWY3D1OvPg/VLKCpsNMxfoLiJdRCQMGAusvmTNamCK8/wo4HOjh4JQPu6Je24hMvTbM2YjQ4N54p5b3L0pj9aYB++XUlZY20vWGFMtIo8Aa4BgYL4xZruIPAPkGWNWA/OAxSJSAJyiruCV8mlf7w379d6kDbWXrKdrzFP3SylbxN9esDkcDpOXl2c7hlJuISL5xhiH7RyX0jpT/sTVOvOLnX6UUkqphqYNUymllHKBNkyllFLKBX73GaaIlAAHv2NJK+CEh+JoBu/dvq9kuMkY43XfldI684ntawbXM7hUZ37XMK9GRPJs70ShGexvXzM0LG+4X7Yz2N6+ZnB/Bn1LVimllHKBNkyllFLKBYHYMNNtB0AzeMP2QTM0JG+4X7Yz2N4+aIavuSVDwH2GqZRSSl2PQHyFqZRSSl0zv22YIjJMRHaLSIGIPHmZ28NF5F3n7Tki0tlChsdFZIeIbBGRz0TkJk9uv966kSJiRMTte7K5kkFERjsfh+0i8o6nM4hIJxFZKyKbnL+Lf3Hz9ueLyHER2XaF20VEXnfm2yIig9y5/YZku85s15grGeqt0zrz9TozxvjdibpB04VAVyAM+BLodcmafwPSnOfHAu9ayHAnEOU8P8udGVzZvnNdE2A9kA04LDwG3YFNQHPn5dYWMqQDs5znewEH3Jzhe8AgYNsVbv8X4CNAgHggx53bb6iT7TqzXWOuZnCu0zrzgzrz11eYcUCBMWafMaYSWA6MuGTNCGCh8/xK4C4REU9mMMasNcZccF7Mpu54hR7bvtOzwIvA5Y9q3PAZZgJvGmNOAxhjjlvIYICmzvPRwGF3BjDGrKfuSCBXMgJYZOpkA81EpJ07MzQQ23Vmu8ZcyuCkdeYHdeavDTMWOFTvcpHzusuuMcZUA2VASw9nqG86dc9+PLZ951sSHY0xf3Xjdq8pA9AD6CEi/xCRbBEZZiHDr4GJIlIEfAj8u5szXM21/q14C9t1ZrvGXMqgdfaNX+PjdWbteJjqn0RkIuAA7vDgNoOAPwAPemqbVxBC3dtF36fu2f96EelrjCn1YIZxwAJjzCsikkDd8SH7GGNqPZhBNSAbNebcrtbZP/l8nfnrK8xioGO9yx2c1112jYiEUPcWwUkPZ0BEfgg8DdxnjLnowe03AfoA60TkAHXv6a928w4JrjwGRcBqY0yVMWY/sIe6wvZkhunACgBjTBYQQd3sSU9x6W/FC9muM9s15koGrbN/8v06c+eHrt5you7Z1D6gC//8ALr3JWse5ts7I6ywkGEgdR+Ud7fxGFyyfh3u3xnBlcdgGLDQeb4VdW+ZtPRwho+AB53ne1L32Yq4+bHozJV3RhjOt3dG2ODuv4eGONmuM9s15mqGS9ZrnRnfrTO3/wF5y4m6PaL2OIvlaed1z1D3LBPqnt1kAAXABqCrhQyfAseAzc7Tak9u/5K1bi9kFx8Doe4tqx3AVmCshQy9gH84i3wz8CM3b38ZcASoou6Z/nQgBUip9xi86cy3tSF+Dw11sl1ntmvMlQyXrNU68+E600k/SimllAv89TNMpZRSyq20YSqllFIu0IaplFJKuUAbplJKKeUCbZhKKaWUC7RhKqWUUi7QhqmUUkq5QBumUkop5YL/AbH9Sx+mxE5YAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig, axes = plt.subplots(ncols=2, nrows=2, figsize=(6.5, 6))\n", "for axis, name in zip(axes.flat, spec_dim_names):\n", " axis.set_title(name)\n", " axis.plot(spec_unit_values[name], 'o-')\n", "\n", "fig.suptitle('Spectroscopic Dimensions', fontsize=16, y=1.05)\n", "fig.tight_layout()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reshaping Data\n", "\n", "### reshape_to_n_dims()\n", "\n", "The USID model stores N dimensional datasets in a flattened 2D form of position x spectral values. It can become\n", "challenging to retrieve the data in its original N-dimensional form, especially for multidimensional datasets such as\n", "the one we are working on. Fortunately, all the information regarding the dimensionality of the dataset are contained\n", "in the spectral and position ancillary datasets. ``reshape_to_n_dims()`` is a very useful function that can help\n", "retrieve the N-dimensional form of the data using a simple function call:\n", "\n" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Succeeded in reshaping flattened 2D dataset to N dimensions\n", "Shape of the data in its original 2D form\n", "(25, 22272)\n", "Shape of the N dimensional form of the dataset:\n", "(5, 5, 87, 64, 2, 2)\n", "And these are the dimensions\n", "['X' 'Y' 'Frequency' 'DC_Offset' 'Field' 'Cycle']\n" ] } ], "source": [ "ndim_form, success, labels = usid.hdf_utils.reshape_to_n_dims(h5_raw, get_labels=True)\n", "if success:\n", " print('Succeeded in reshaping flattened 2D dataset to N dimensions')\n", " print('Shape of the data in its original 2D form')\n", " print(h5_raw.shape)\n", " print('Shape of the N dimensional form of the dataset:')\n", " print(ndim_form.shape)\n", " print('And these are the dimensions')\n", " print(labels)\n", "else:\n", " print('Failed in reshaping the dataset')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### reshape_from_n_dims()\n", "The inverse problem of reshaping an N dimensional dataset back to a 2D dataset (let's say for the purposes of\n", "multivariate analysis or storing into h5USID files) is also easily solved using another handy\n", "function - ``reshape_from_n_dims()``:\n", "\n" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape of flattened two dimensional form\n", "(25, 22272)\n" ] } ], "source": [ "two_dim_form, success = usid.hdf_utils.reshape_from_n_dims(ndim_form, h5_pos=h5_pos_inds, h5_spec=h5_spec_inds)\n", "if success:\n", " print('Shape of flattened two dimensional form')\n", " print(two_dim_form.shape)\n", "else:\n", " print('Failed in flattening the N dimensional dataset')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Close and delete the h5_file\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h5_f.close()\n", "os.remove(h5_path)" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.5" } }, "nbformat": 4, "nbformat_minor": 1 }