Showing preview only (8,430K chars total). Download the full file or copy to clipboard to get everything.
Repository: Retsediv/WIFI_CSI_based_HAR
Branch: master
Commit: 54bee76dcb00
Files: 72
Total size: 8.0 MB
Directory structure:
gitextract_opl2tjw6/
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── data_retrieval/
│ ├── README.md
│ ├── csi_extraction/
│ │ ├── __init__.py
│ │ ├── csi_calibration.py
│ │ ├── read_csi.py
│ │ └── read_log_file.py
│ ├── run_test_client.py
│ ├── run_visualization_server.py
│ └── window.ui
├── model/
│ ├── .gitignore
│ ├── data_calibration.py
│ ├── dataset.py
│ ├── label_activities.py
│ ├── label_human_boxes.py
│ ├── main.py
│ ├── metrics.py
│ ├── models/
│ │ ├── FCNBaseline.py
│ │ ├── InceptionTime.py
│ │ ├── LSTMClassifier.py
│ │ ├── SimpleLSTMClassifier.py
│ │ ├── __init__.py
│ │ └── utils.py
│ ├── notebooks/
│ │ ├── DataFilteringAndFeatureEngineering.ipynb
│ │ ├── DetectPersonBoxes.ipynb
│ │ ├── ExperimentsVisualization.ipynb
│ │ ├── HARSimpleLSTM.ipynb
│ │ ├── Inception.ipynb
│ │ ├── VisualizeCSI.ipynb
│ │ ├── fastai_timeseries/
│ │ │ ├── __init__.py
│ │ │ └── exp/
│ │ │ ├── __init__.py
│ │ │ ├── nb_ColorfulDim.py
│ │ │ ├── nb_ImageDataAugmentation.py
│ │ │ ├── nb_Initialization.py
│ │ │ ├── nb_NewDataAugmentation.py
│ │ │ ├── nb_Optimizers.py
│ │ │ ├── nb_TSBasicData.py
│ │ │ ├── nb_TSCallbacks.py
│ │ │ ├── nb_TSCharts.py
│ │ │ ├── nb_TSDataAugmentation.py
│ │ │ ├── nb_TSDatasets.py
│ │ │ ├── nb_TSImageData.py
│ │ │ ├── nb_TSTrain.py
│ │ │ ├── nb_TSUtilities.py
│ │ │ └── rocket_functions.py
│ │ └── torchtimeseries/
│ │ └── models/
│ │ ├── FCN.py
│ │ ├── InceptionTime.py
│ │ ├── ROCKET.py
│ │ ├── ResCNN.py
│ │ ├── ResNet.py
│ │ ├── __init__.py
│ │ └── layers.py
│ ├── schedulers/
│ │ ├── CycleLR.py
│ │ └── __init__.py
│ ├── test.py
│ ├── train_lstm.py
│ └── yolo_data/
│ ├── coco.names
│ └── yolov3.cfg
├── requirements.txt
└── router/
├── recvCSI/
│ ├── Makefile
│ ├── README.md
│ ├── bin/
│ │ └── .gitkeep
│ ├── csi_fun.c
│ ├── csi_fun.h
│ └── main.c
└── sendData/
├── Makefile
├── README.md
├── bin/
│ └── .gitkeep
├── sendData.c
└── sendData_con.c
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Data
dataset
./data_retrieval_and_analysis/dataset
./data_retrieval_and_analysis/experiments### C template
# Prerequisites
*.d
# Binary folders
./router/*/bin/*
./*/bin
./*/bin/*
# Object files
*/*.o
./*/*.o
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### Example user template template
### Example user template
# IntelliJ project files
.idea
*.iml
out
gen
### JupyterNotebooks template
# gitignore template for Jupyter Notebooks
# website: http://jupyter.org/
.ipynb_checkpoints
*/.ipynb_checkpoints/*
# IPython
profile_default/
ipython_config.py
# Remove previous ipynb_checkpoints
# git rm -r .ipynb_checkpoints/
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
*.pyc
*.DS_STORE
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent.
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
================================================
FILE: LICENSE.md
================================================
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
================================================
FILE: README.md
================================================
# Human Activity Recognition based on Wi-Fi CSI Data - A Deep Neural Network Approach
This is a repository with source code for the [paper "Human Activity Recognition based on Wi-Fi CSI Data - A Deep Neural Network Approach"](https://www.sciencedirect.com/science/article/pii/S1877050921024509)
and respective [thesis](https://s3.eu-central-1.amazonaws.com/ucu.edu.ua/wp-content/uploads/sites/8/2021/07/Zhuravchak-Andrii_188586_assignsubmission_file_Bachelor_Thesis_Human_Activity_Recognition_based_on_WiFi_CSI_data.pdf)
(it contains more details that are not covered in the paper).
Using Wi-Fi Channel State Information (CSI) is a novel way of sensing and human activity recognition (HAR). Such a
system can be used in medical institutions for their patients monitoring without privacy violence, as it could be with a
vision-based approach.
The main goal of this thesis was to explore current methods and systems which use Wi-Fi CSI, conduct experiments to
analyze how different hardware configurations affect the data and possibility to detect human activity, collect the
dataset and build the classification model for HAR task. 8 experiments were performed, the dataset in 3 different rooms
was collected, and LSTM-based classification model was build and trained. We’ve shown the full pipeline of building
Wi-Fi CSI based system.
## Repository structure
- `router` - contains source code for `sendData` and `recvCSI`.
They are used to send data packet from one router and calculate the CSI data on another.
Then `recvCSI` sends the data to a user computer via UDP connection for further processing.
- `data_retrieval` - contains a program (`run_visualization_server.py`) that listens to `recvCSI` program,
visualizes incoming data, and saves it to a file. Also, it has a script for a dummy server to emulate incoming data from
the router (`run_test_client.py`) and a sample CSI data in binary format as it is coming from the router
(`data/sample_csi_packet_big_endian.dat`).
- `model` - has all the code for building the model and training it, scripts that were used to label activities,
notebook for EDA, etc.
## Dataset
The dataset can be downloaded by the following [link](https://doi.org/10.6084/m9.figshare.14386892.v1).
## Authors
* **[Andrew Zhuravchak](https://github.com/Retsediv)** - Ukrainian Catholic University (UCU) former student
* **Oleh Kapshii** - supervisor
## License
This project is licensed under the GNU License - see the [LICENSE.md](LICENSE.md) file for details
## Acknowledgments
* This repository is a fork from
the [Atheros-CSI-Tool-UserSpace-APP](https://github.com/NovelSense/Atheros-CSI-Tool-UserSpace-APP)
which is based on [Atheros-CSI-Tool](https://github.com/xieyaxiongfly/Atheros-CSI-Tool).
* [RF-pose](http://rfpose.csail.mit.edu/) for inspiration for doing this work
* Cypress Semiconductor company, ASR Ukraine team and especially Oleh Kapshii for support and help
## Contributing
Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of
conduct, and the process for submitting pull requests to us.
================================================
FILE: data_retrieval/README.md
================================================
# CSI data retrieval, visualization and storing
This program is responsible for retrieving CSI data from the router (the one running `recvCSI` program),
visualizing it in a realtime and storing it in a filesystem.
***Sample Setup for visualize CSI***
The setup consists of three devices. Two SoC's and one PC/Notebook
1. The first SoC (A) acts as an access point (AP). The other SoC (B) and the PC connect to that AP
2. A starts the recv_csi tool from [HERE](https://github.com/Retsediv/WIFI_CSI_based_HAR/tree/master/router/recvCSI)
with the IP Address of the PC and the port 1234 `./recv_csi <PC IP> 1234`
3. The PC starts the `run_visualization_server.py` script from this folder
4. B starts the send_data tool from [HERE](https://github.com/Retsediv/WIFI_CSI_based_HAR/tree/master/router/sendData)
and starts sending packages. Those packages should now be received by the PC
5. As the result you should see a plot of the CSI packet like in the picture below.
<div align="center"><img src="images/screenshot.png" alt="CSI packet" width="400" height="auto"/></div>
*Troubleshooting*:
- If the packages are not received by the PC there might be issues with the firewall
- Also check if you set the correct UDP port in `recvCSI` program arguments
- Check if you have installed `pyqt`.
================================================
FILE: data_retrieval/csi_extraction/__init__.py
================================================
from .read_csi import *
from .read_log_file import *
from .csi_calibration import *
================================================
FILE: data_retrieval/csi_extraction/csi_calibration.py
================================================
import numpy as np
def calibrate_phase(phases):
"""
CSI phase calibration
Based on https://github.com/ermongroup/Wifi_Activity_Recognition/.../phase_calibration.m
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/sys031fp.pdf
"""
phases = np.array(phases)
difference = 0
calibrated_phase, calibrated_phase_final = np.zeros_like(phases), np.zeros_like(phases)
calibrated_phase[0] = phases[0]
phases_len = phases.shape[0]
for i in range(1, phases_len):
temp = phases[i] - phases[i - 1]
if abs(temp) > np.pi:
difference = difference + 1 * np.sign(temp)
calibrated_phase[i] = phases[i] - difference * 2 * np.pi
k = (calibrated_phase[-1] - calibrated_phase[0]) / (phases_len - 1)
b = np.mean(calibrated_phase)
for i in range(phases_len):
calibrated_phase_final[i] = calibrated_phase[i] - k * i - b
return calibrated_phase_final
def calibrate_amplitude(amplitudes, rssi): # Basic statistical normalization
amplitudes = np.array(amplitudes)
return ((amplitudes - np.min(amplitudes)) / (np.max(amplitudes) - np.min(amplitudes))) * rssi
================================================
FILE: data_retrieval/csi_extraction/read_csi.py
================================================
import io
import struct
from math import atan2
BITS_PER_BYTE = 8
BITS_PER_SYMBOL = 10
bitmask = (1 << BITS_PER_SYMBOL) - 1
DEBUG = 1
class csi_struct:
pass
def unpack_csi_struct(f, endianess='>'): # Big-Endian as Default Value
csi_inf = csi_struct()
csi_inf.field_len = struct.unpack(endianess + 'H', f.read(2))[0] # Block Length 1Byte
csi_inf.timestamp = struct.unpack(endianess + 'Q', f.read(8))[0] # TimeStamp 8Byte
csi_inf.csi_len = struct.unpack(endianess + 'H', f.read(2))[0] # csi_len 2Byte
csi_inf.channel = struct.unpack(endianess + 'H', f.read(2))[0] # tx 2Byte
csi_inf.err_info = struct.unpack(endianess + 'B', f.read(1))[0] # err_info 1Byte
csi_inf.rate = struct.unpack(endianess + 'B', f.read(1))[0] # noisefloor 1Byte
csi_inf.noise_floor = struct.unpack(endianess + 'B', f.read(1))[0] # rate 1Byte
csi_inf.bw = struct.unpack(endianess + 'B', f.read(1))[0] # bandWidth 1Byte
csi_inf.num_tones = struct.unpack(endianess + 'B', f.read(1))[0] # num_tones 1Byte
csi_inf.nr = struct.unpack(endianess + 'B', f.read(1))[0] # nr 1Byte
csi_inf.nc = struct.unpack(endianess + 'B', f.read(1))[0] # nc 1Byte
csi_inf.rssi = struct.unpack(endianess + 'B', f.read(1))[0] # rssi 1Byte
csi_inf.rssi1 = struct.unpack(endianess + 'B', f.read(1))[0] # rssi1 1Byte
csi_inf.rssi2 = struct.unpack(endianess + 'B', f.read(1))[0] # rssi2 1Byte
csi_inf.rssi3 = struct.unpack(endianess + 'B', f.read(1))[0] # rssi3 1Byte
csi_inf.payload_len = struct.unpack(endianess + 'H', f.read(2))[
0] # payload_len 2Byte Total: 27Byte + csi_len + payload_len
if csi_inf.csi_len > 0:
csi_buf = f.read(csi_inf.csi_len) # csi csi_len
csi_inf.csi = read_csi(csi_buf, csi_inf.num_tones, csi_inf.nc, csi_inf.nr, csi_inf.csi_len, endianess)
else:
csi_inf.csi = 0
if csi_inf.payload_len > 0:
payload_buf = f.read(csi_inf.payload_len) # payload_len payload_len
else:
payload_buf = 0
return csi_inf
def read_csi(csi_buf, num_tones, nc, nr, csi_len, endianess):
csi = []
buf = io.BytesIO(csi_buf)
bits_left = 16
cur_data = struct.unpack(endianess + 'B', buf.read(1))[0] # Read 16Bits at a Time
cur_data += (struct.unpack(endianess + 'B', buf.read(1))[0] << BITS_PER_BYTE)
print("read csi")
print("num_tones: ", num_tones)
print("nc: ", nc)
print("nr: ", nr)
print()
for i in range(0, num_tones):
tones = []
for nc_idx in range(0, nc):
A = []
for nr_idx in range(0, nr):
if (bits_left - BITS_PER_SYMBOL) < 0:
new_bits = struct.unpack(endianess + 'B', buf.read(1))[0]
new_bits += (struct.unpack(endianess + 'B', buf.read(1))[0] << BITS_PER_BYTE)
# print(new_bits)
cur_data += new_bits << bits_left
bits_left += 16
_imag = cur_data & bitmask
imag = signbit_convert(_imag, BITS_PER_SYMBOL)
bits_left -= BITS_PER_SYMBOL
cur_data = cur_data >> BITS_PER_SYMBOL
if (bits_left - BITS_PER_SYMBOL) < 0:
new_bits = struct.unpack(endianess + 'B', buf.read(1))[0]
new_bits += (struct.unpack(endianess + 'B', buf.read(1))[0] << BITS_PER_BYTE)
# print(new_bits)
cur_data += new_bits << bits_left
bits_left += 16
_real = cur_data & bitmask
real = signbit_convert(_real, BITS_PER_SYMBOL)
bits_left -= BITS_PER_SYMBOL
cur_data = cur_data >> BITS_PER_SYMBOL
# print("R:", real, "I:", imag)
A.append(complex(real, imag))
tones.append(A)
csi.append(tones)
# print("csi: ", csi)
return csi
def signbit_convert(data, maxbit):
if data & (1 << (maxbit - 1)):
data -= (1 << maxbit)
return data
def calc_frequency(basefrequency, c, num_tones):
per_side = num_tones / 2
step = 0.3125
if (c < per_side): # Carrier is on left side of centerfrequency
freq = basefrequency - (per_side - c) * step
else:
freq = basefrequency + (c + 1 - per_side) * step
return freq
def calc_phase_angle(iq, unwrap=0):
imag = iq.imag
real = iq.real
if unwrap == 1:
return atan2(imag, real)
return atan2(imag, real)
================================================
FILE: data_retrieval/csi_extraction/read_log_file.py
================================================
import os
from .read_csi import *
def read_log_file(filename, ignore_endian=0, endian="", check_tones=1):
f = open(filename, "rb")
f.seek(0, os.SEEK_END)
length = f.tell() # File Length
print("file length is ", length)
f.seek(0, 0) # Default Pos
if ignore_endian == 0:
if struct.unpack("B", f.read(1))[0] == 255:
print("Big-Endian Format")
endian = ">"
else:
print("Little-Endian Format") # 1 Byte Endian Format
endian = "<"
ret = []
tones = -1
while (f.tell() < length):
try:
csi_inf = unpack_csi_struct(f, endianess=endian)
if (tones == -1 and check_tones == 1):
tones = csi_inf.num_tones
if (check_tones == 1 and csi_inf.num_tones != tones):
continue # discarding packet with a different amount of tones
if (csi_inf.csi == 0):
print("Ignoring packet, no csi information found")
continue
ret.append(csi_inf)
except:
print("Corrupt Packet")
break
f.close()
return ret
if __name__ == "__main__":
file = read_log_file("../../Sandip_Tests/csi.dat")
print("Done reading")
for struct in file:
print(struct.channel)
================================================
FILE: data_retrieval/run_test_client.py
================================================
import os
import socket
import struct
import argparse
def run_client(filename: str, host: str, port: int) -> None:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if not os.path.exists(filename):
print(f"There is no such file: {filename}")
return
with open(filename, "rb") as f:
f.seek(0, os.SEEK_END)
length = f.tell()
print(f"File length: {length}")
f.seek(0, 0)
if struct.unpack("B", f.read(1))[0] == 255:
print("Data is in Big-Endian Format")
endian = ">"
else:
print("Data is in Little-Endian Format") # 1 Byte Endian Format
endian = "<"
while f.tell() < length:
prev = f.tell()
block_length = struct.unpack(endian + "H", f.read(2))[0]
block_length += 2
f.seek(prev, 0)
data = f.read(block_length)
sock.sendto(data, (host, port))
# time.sleep(0.1)
input("Press Enter to continue...")
# if(f.tell() == length): # Go to the beginning of the file and start again
# f.seek(0,0)
# f.read(1)
def init_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Dummy client that is sending CSI data packet to the server\n"
"Try data/sample_csi_packet_big_endian.day",
prog="python run_test_client.py"
)
parser.add_argument("filename", help="path to the file with a sample data", type=str)
parser.add_argument("--host", help="host ip to send data", default="127.0.0.1", type=str)
parser.add_argument("--port", help="host port", default=1234, type=int)
return parser
if __name__ == "__main__":
parser = init_argparse() # Initialize args parser
args = parser.parse_args() # Read arguments from command line
run_client(args.filename, args.host, args.port)
================================================
FILE: data_retrieval/run_visualization_server.py
================================================
import argparse
import csv
import io
import os
from copy import deepcopy
import cv2
import numpy as np
from PyQt6 import QtNetwork, QtWidgets, uic
from pyqtgraph.Qt import QtCore
from csi_extraction import calc_phase_angle, calibrate_amplitude, calibrate_phase, unpack_csi_struct
class UI(QtWidgets.QWidget):
def __init__(self, app: QtWidgets.QApplication, parent: QtWidgets.QWidget = None, is_5ghz: bool = True):
super(UI, self).__init__(parent)
self.app = app
self.is_5ghz = is_5ghz
# get and show object and layout
uic.loadUi('window.ui', self)
self.setWindowTitle(f"Visualize CSI data {'5GHz' if is_5ghz else '2.4GHz'}")
self.antenna_pairs, self.carrier, self.amplitude, self.phase = [], [], [], []
amp = self.box_amp
amp.setBackground('w')
amp.setWindowTitle('Amplitude')
amp.setLabel('bottom', 'Carrier', units='')
amp.setLabel('left', 'Amplitude', units='')
amp.setYRange(0, 1, padding=0) # for normalized amp values, prev range: 0, 90
amp.setXRange(0, 114 if is_5ghz else 57, padding=0)
self.penAmp0_0 = amp.plot(pen={'color': (200, 0, 0), 'width': 3})
self.penAmp0_1 = amp.plot(pen={'color': (200, 200, 0), 'width': 3})
self.penAmp1_0 = amp.plot(pen={'color': (0, 0, 200), 'width': 3})
self.penAmp1_1 = amp.plot(pen={'color': (0, 200, 200), 'width': 3})
phase = self.box_phase
phase.setBackground('w')
phase.setWindowTitle('Phase')
phase.setLabel('bottom', 'Carrier', units='')
phase.setLabel('left', 'Phase', units='')
phase.setYRange(-np.pi, np.pi, padding=0)
phase.setXRange(0, 114 if is_5ghz else 57, padding=0)
self.penPhase0_0 = phase.plot(pen={'color': (200, 0, 0), 'width': 3})
self.penPhase0_1 = phase.plot(pen={'color': (200, 200, 0), 'width': 3})
self.penPhase1_0 = phase.plot(pen={'color': (0, 0, 200), 'width': 3})
self.penPhase1_1 = phase.plot(pen={'color': (0, 200, 200), 'width': 3})
self.amp = amp
self.box_phase = phase
@QtCore.pyqtSlot()
def update_plots(self):
if len(self.amplitude) and len(self.amplitude[0]):
if len(self.antenna_pairs) > 0:
self.penAmp0_0.setData(self.carrier, self.amplitude[0])
self.penPhase0_0.setData(self.carrier, self.phase[0])
if len(self.antenna_pairs) > 1:
self.penAmp0_1.setData(self.carrier, self.amplitude[1])
self.penPhase0_1.setData(self.carrier, self.phase[1])
if len(self.antenna_pairs) > 2:
self.penAmp1_0.setData(self.carrier, self.amplitude[2])
self.penPhase1_0.setData(self.carrier, self.phase[2])
if len(self.antenna_pairs) > 3:
self.penAmp1_1.setData(self.carrier, self.amplitude[3])
self.penPhase1_1.setData(self.carrier, self.phase[3])
self.process_events() # force complete redraw for every plot
def process_events(self):
self.app.processEvents()
class UDPListener:
packet_counter = 0
def __init__(self, save_data_path: str, sock: QtNetwork.QUdpSocket,
form: UI, make_photo: bool = False):
self.save_data_path = save_data_path
os.makedirs(save_data_path, exist_ok=True)
self.make_photo = make_photo
self.cam = cv2.VideoCapture(0) if self.make_photo else None
self.sock = sock
sock.readyRead.connect(self.on_datagram_received)
self.form = form
def on_datagram_received(self):
while self.sock.hasPendingDatagrams():
print("Received new datagram")
datagram, host, port = self.sock.readDatagram(self.sock.pendingDatagramSize())
f = io.BytesIO(datagram)
csi_inf = unpack_csi_struct(f)
if csi_inf.csi != 0: # get csi from data packet, save and process for further visualization
raw_peak_amplitudes, raw_phases, carriers_indexes, antenna_pairs = self.get_csi_raw_data(csi_inf)
self.calc(raw_peak_amplitudes, raw_phases, carriers_indexes, antenna_pairs)
self.save_csi_to_file(raw_peak_amplitudes, raw_phases, carriers_indexes)
if self.make_photo:
self.make_photo_and_save()
self.form.update_plots()
self.packet_counter += 1
def get_csi_raw_data(self, csi_inf):
print("getting csi from the raw data")
channel = csi_inf.channel
carriers_num = csi_inf.num_tones
carriers = [] # just indexes from 0 to "csi_inf.num_tone"
print("channel: ", channel)
print("carriers_num: ", carriers_num)
num_of_antenna_pairs = csi_inf.nc * csi_inf.nr
antenna_pairs = [(tx_index, rx_index) for tx_index in range(csi_inf.nc) for rx_index in range(csi_inf.nr)]
raw_phases, raw_peak_amplitudes = [[] for _ in range(num_of_antenna_pairs)], \
[[] for _ in range(num_of_antenna_pairs)]
print("antenna_pairs: ", antenna_pairs)
for i in range(0, carriers_num):
for enum_index, (tr_i, rc_i) in enumerate(antenna_pairs):
# print("csi_len ", csi_inf.csi_len)
# print("csi_inf.csi[i]: ", csi_inf)
p = csi_inf.csi[i][tr_i][rc_i]
imag, real = p.imag, p.real
peak_amplitude = np.sqrt(np.power(real, 2) + np.power(imag, 2)) # calculate peak amplitude
phase_angle = calc_phase_angle(p) # calculate phase angle
raw_peak_amplitudes[enum_index].append(peak_amplitude)
raw_phases[enum_index].append(phase_angle)
carriers.append(i)
return raw_peak_amplitudes, raw_phases, carriers, antenna_pairs
def calc(self, raw_peak_amplitudes, raw_phases, carriers_indexes, antenna_pairs): # used for visualization only
amplitude, phase = deepcopy(raw_peak_amplitudes), deepcopy(raw_phases)
# Update form carriers indexes
self.form.carrier = carriers_indexes
# Calibrate amplitude and update form amplitude values
for i in range(len(amplitude)):
amplitude[i] = calibrate_amplitude(amplitude[i], 1).tolist()
self.form.amplitude = amplitude
# Calibrate phase and update form phase values
for i in range(len(phase)):
phase[i] = calibrate_phase(phase[i]).tolist()
self.form.phase = phase
self.form.antenna_pairs = antenna_pairs
def make_photo_and_save(self):
ret, frame = self.cam.read()
img_name = "opencv_frame_{}.png".format(UDPListener.packet_counter)
path_to_image = "{}/{}/{}".format(self.save_data_path, "images", img_name)
cv2.imwrite(path_to_image, frame)
print("{} written!".format(path_to_image))
def save_csi_to_file(self, raw_peak_amplitudes, raw_phases, carriers):
# print("Saving CSI data to .csv file...")
filename_csv = "data.csv"
path_to_csv_file = "{}/{}".format(self.save_data_path, filename_csv)
with open(path_to_csv_file, 'a', newline='\n') as csvfile:
writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
writer.writerow([*carriers, *raw_peak_amplitudes[0], *raw_peak_amplitudes[1], *raw_peak_amplitudes[2],
*raw_peak_amplitudes[3], *raw_phases[0], *raw_phases[1], *raw_phases[2], *raw_phases[3]])
def init_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Visualization server that listens to any incoming packets, plot them and store.\n"
"Only supports up to 4 antenna pairs at the moment.",
prog="python run_visualization_server.py"
)
parser.add_argument("-f", "--frequency", help="Frequency on which both routes are operating",
choices=['2400MHZ', '5000MHZ'], default='5000MHZ')
parser.add_argument("-s", "--save_path", help="path to the folder where to save the incoming data",
default="./tmp", type=str)
parser.add_argument("-p", "--port", help="port to listen to", default=1234, type=int)
parser.add_argument("--photo", help="make webcam photo for each data packet?", default=False, type=bool)
return parser
def run_app() -> None:
parser = init_argparse()
args = parser.parse_args()
app = QtWidgets.QApplication([])
try:
udp_socket = QtNetwork.QUdpSocket()
udp_socket.bind(QtNetwork.QHostAddress.SpecialAddress.Any, args.port)
form = UI(app=app, is_5ghz=(args.frequency == '5000MHZ'))
form.show()
listener = UDPListener(save_data_path=args.save_path, sock=udp_socket,
form=form, make_photo=args.photo)
app.exec()
except KeyboardInterrupt as e:
try:
listener.cam.release()
except Exception as e_cam:
pass
print("KeyboardInterrupt. Finishing the program...")
if __name__ == '__main__':
run_app()
================================================
FILE: data_retrieval/window.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="PlotWidget" name="box_amp">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>680</width>
<height>240</height>
</rect>
</property>
<property name="title">
<string>Amplitude</string>
</property>
</widget>
<widget class="PlotWidget" name="box_phase">
<property name="geometry">
<rect>
<x>10</x>
<y>250</y>
<width>681</width>
<height>240</height>
</rect>
</property>
<property name="title">
<string>Phase</string>
</property>
</widget>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QWidget</extends>
<header>pyqtgraph</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
================================================
FILE: model/.gitignore
================================================
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
*.manifest
*.spec
pip-log.txt
pip-delete-this-directory.txt
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
*.mo
*.pot
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
instance/
.webassets-cache
.scrapy
docs/_build/
target/
.ipynb_checkpoints
profile_default/
ipython_config.py
.python-version
__pypackages__/
celerybeat-schedule
celerybeat.pid
*.sage.py
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.spyderproject
.spyproject
.ropeproject
/site
.mypy_cache/
.dmypy.json
dmypy.json
.pyre/
.ipynb_checkpoints
*/.ipynb_checkpoints/*
./notebooks/.ipynb_checkpoints
*/.ipynb_checkpoints/*
profile_default/
ipython_config.py
.idea
*.iml
out
*.pdf
*.csv
gen
dataset/
dataset/*
saved_models/
saved_models/*
yolo_data/yolov3.weights
================================================
FILE: model/data_calibration.py
================================================
import pandas as pd
import numpy as np
import pywt
def calibrate_single_phase(phases):
"""
Calibrate phase data from the single time moment
Based on:
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/sys031fp.pdf
https://github.com/ermongroup/Wifi_Activity_Recognition/.../phase_calibration.m
:param phases: phase in the single time moment, np.array of shape(1, num of subcarriers)
:return: calibrate phase, np.array of shape(1, num of subcarriers)
"""
phases = np.array(phases)
difference = 0
calibrated_phase, calibrated_phase_final = np.zeros_like(phases), np.zeros_like(phases)
calibrated_phase[0] = phases[0]
phases_len = phases.shape[0]
for i in range(1, phases_len):
temp = phases[i] - phases[i - 1]
if abs(temp) > np.pi:
difference = difference + 1 * np.sign(temp)
calibrated_phase[i] = phases[i] - difference * 2 * np.pi
k = (calibrated_phase[-1] - calibrated_phase[0]) / (phases_len - 1)
b = np.mean(calibrated_phase)
for i in range(phases_len):
calibrated_phase_final[i] = calibrated_phase[i] - k * i - b
return calibrated_phase_final
def calibrate_phase(phases):
"""
Calibrate phase data based on the following method:
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/sys031fp.pdf
https://github.com/ermongroup/Wifi_Activity_Recognition/.../phase_calibration.m
:param phases: np.array of shape(data len, num of subcarries)
:return: calibrated phases: np.array of shape(data len, num of subcarriers)
"""
calibrated_phases = np.zeros_like(phases)
for i in range(phases.shape[0]):
calibrated_phases[i] = calibrate_single_phase(np.unwrap(phases[i]))
return calibrated_phases
def calibrate_amplitude(amplitudes, rssi=1):
"""
Simple amplitude normalization, that could be multiplied by rsii
((data - min(data)) / (max(data) - min(data))) * rssi
:param amplitudes: np.array of shape(data len, num of subcarriers)
:param rssi: number
:return: normalized_amplitude: np.array of shape(data len, num of subcarriers)
"""
amplitudes = np.array(amplitudes)
return ((amplitudes - np.min(amplitudes)) / (np.max(amplitudes) - np.min(amplitudes))) * rssi
def calibrate_amplitude_custom(amplitudes, min_val, max_val, rssi=1):
amplitudes = np.array(amplitudes)
return ((amplitudes - min_val) / (max_val - min_val)) * rssi
def dwn_noise(vals):
data = vals.copy()
threshold = 0.06 # Threshold for filtering
w = pywt.Wavelet('sym5')
maxlev = pywt.dwt_max_level(data.shape[0], w.dec_len)
coeffs = pywt.wavedec(data, 'sym5', level=maxlev)
for i in range(1, len(coeffs)):
coeffs[i] = pywt.threshold(coeffs[i], threshold * max(coeffs[i]))
datarec = pywt.waverec(coeffs, 'sym5')
return datarec
def hampel(vals_orig, k=7, t0=3):
# Make copy so original not edited
vals = pd.Series(vals_orig.copy())
# Hampel Filter
L = 1.4826
rolling_median = vals.rolling(k).median()
difference = np.abs(rolling_median - vals)
median_abs_deviation = difference.rolling(k).median()
threshold = t0 * L * median_abs_deviation
outlier_idx = difference > threshold
vals[outlier_idx] = rolling_median
# print("vals: ", vals.shape)
return vals.to_numpy()
================================================
FILE: model/dataset.py
================================================
import os
import torch
import numpy as np
import pandas as pd
from torch.utils.data import Dataset, DataLoader
from data_calibration import calibrate_amplitude_custom, calibrate_phase, calibrate_amplitude, dwn_noise, hampel
DATASET_FOLDER = "../dataset"
DATA_ROOMS = ["bedroom_lviv", "parents_home", "vitalnia_lviv"]
DATA_SUBROOMS = [["1", "2", "3", "4"], ["1"], ["1", "2", "3", "4", "5"]]
SUBCARRIES_NUM_TWO_HHZ = 56
SUBCARRIES_NUM_FIVE_HHZ = 114
PHASE_MIN, PHASE_MAX = 3.1389, 3.1415
AMP_MIN, AMP_MAX = 0.0, 577.6582
def read_csi_data_from_csv(path_to_csv, is_five_hhz=False, antenna_pairs=4):
"""
Read csi data(amplitude, phase) from .csv data
:param path_to_csv: string
:param is_five_hhz: boolean
:param antenna_pairs: integer
:return: (amplitudes, phases) => (np.array of shape(data len, num_of_subcarriers * antenna_pairs),
np.array of shape(data len, num_of_subcarriers * antenna_pairs))
"""
data = pd.read_csv(path_to_csv, header=None).values
if is_five_hhz:
subcarries_num = SUBCARRIES_NUM_FIVE_HHZ
else:
subcarries_num = SUBCARRIES_NUM_TWO_HHZ
# 1 -> to skip subcarriers numbers in data
amplitudes = data[:, subcarries_num * 1:subcarries_num * (1 + antenna_pairs)]
phases = data[:, subcarries_num * (1 + antenna_pairs):subcarries_num * (1 + 2 * antenna_pairs)]
return amplitudes, phases
def read_labels_from_csv(path_to_csv):
"""
Read labels(human activities) from csv file
:param path_to_csv: string
:return: labels, np.array of shape(data_len, 1)
"""
data = pd.read_csv(path_to_csv, header=None).values
labels = data[:, 1]
return labels
def read_all_data_from_files(paths, is_five_hhz=True, antenna_pairs=4):
"""
Read csi and labels data from all folders in the dataset
:return: amplitudes, phases, labels all of shape (data len, num of subcarriers)
"""
final_amplitudes, final_phases, final_labels = np.empty((0, antenna_pairs * SUBCARRIES_NUM_FIVE_HHZ)), \
np.empty((0, antenna_pairs * SUBCARRIES_NUM_FIVE_HHZ)), \
np.empty((0))
for index, path in enumerate(paths):
amplitudes, phases = read_csi_data_from_csv(os.path.join(path, "data.csv"), is_five_hhz, antenna_pairs)
labels = read_labels_from_csv(os.path.join(path, "label.csv"))
amplitudes, phases = amplitudes[:-1], phases[:-1] # fix the bug with the last element
final_amplitudes = np.concatenate((final_amplitudes, amplitudes))
final_phases = np.concatenate((final_phases, phases))
final_labels = np.concatenate((final_labels, labels))
return final_amplitudes, final_phases, final_labels
def read_all_data(is_five_hhz=True, antenna_pairs=4):
all_paths = []
for index, room in enumerate(DATA_ROOMS):
for subroom in DATA_SUBROOMS[index]:
all_paths.append(os.path.join(DATASET_FOLDER, room, subroom))
return read_all_data_from_files(all_paths, is_five_hhz, antenna_pairs)
class CSIDataset(Dataset):
"""CSI Dataset."""
def __init__(self, csv_files, window_size=32, step=1):
from sklearn import decomposition
self.amplitudes, self.phases, self.labels = read_all_data_from_files(csv_files)
self.amplitudes = calibrate_amplitude(self.amplitudes)
pca = decomposition.PCA(n_components=3)
# self.phases[:, 0 * SUBCARRIES_NUM_FIVE_HHZ:1 * SUBCARRIES_NUM_FIVE_HHZ] = calibrate_amplitude(calibrate_phase(
# self.phases[:, 0 * SUBCARRIES_NUM_FIVE_HHZ:1 * SUBCARRIES_NUM_FIVE_HHZ]))
# self.phases[:, 1 * SUBCARRIES_NUM_FIVE_HHZ:2 * SUBCARRIES_NUM_FIVE_HHZ] = calibrate_amplitude(calibrate_phase(
# self.phases[:, 1 * SUBCARRIES_NUM_FIVE_HHZ:2 * SUBCARRIES_NUM_FIVE_HHZ]))
# self.phases[:, 2 * SUBCARRIES_NUM_FIVE_HHZ:3 * SUBCARRIES_NUM_FIVE_HHZ] = calibrate_amplitude(calibrate_phase(
# self.phases[:, 2 * SUBCARRIES_NUM_FIVE_HHZ:3 * SUBCARRIES_NUM_FIVE_HHZ]))
# self.phases[:, 3 * SUBCARRIES_NUM_FIVE_HHZ:4 * SUBCARRIES_NUM_FIVE_HHZ] = calibrate_amplitude(calibrate_phase(
# self.phases[:, 3 * SUBCARRIES_NUM_FIVE_HHZ:4 * SUBCARRIES_NUM_FIVE_HHZ]))
self.amplitudes_pca = []
data_len = self.phases.shape[0]
for i in range(self.phases.shape[1]):
# self.phases[:data_len, i] = dwn_noise(hampel(self.phases[:, i]))[:data_len]
self.amplitudes[:data_len, i] = dwn_noise(hampel(self.amplitudes[:, i]))[:data_len]
for i in range(4):
self.amplitudes_pca.append(
pca.fit_transform(self.amplitudes[:, i * SUBCARRIES_NUM_FIVE_HHZ:(i + 1) * SUBCARRIES_NUM_FIVE_HHZ]))
self.amplitudes_pca = np.array(self.amplitudes_pca)
self.amplitudes_pca = self.amplitudes_pca.reshape((self.amplitudes_pca.shape[1], self.amplitudes_pca.shape[0] * self.amplitudes_pca.shape[2]))
self.label_keys = list(set(self.labels))
self.class_to_idx = {
"standing": 0,
"walking": 1,
"get_down": 2,
"sitting": 3,
"get_up": 4,
"lying": 5,
"no_person": 6
}
self.idx_to_class = {v: k for k, v in self.class_to_idx.items()}
self.window = window_size
if window_size == -1:
self.window = self.labels.shape[0] - 1
self.step = step
def __getitem__(self, idx):
if self.window == 0:
return np.append(self.amplitudes[idx], self.phases[idx]), self.class_to_idx[
self.labels[idx + self.window - 1]]
idx = idx * self.step
all_xs, all_ys = [], []
# idx = idx * self.window
for index in range(idx, idx + self.window):
all_xs.append(np.append(self.amplitudes[index], self.amplitudes_pca[index]))
# all_ys.append(self.class_to_idx[self.labels[index]])
return np.array(all_xs), self.class_to_idx[self.labels[idx + self.window - 1]]
# return np.array(all_xs), np.array(all_ys)
def __len__(self):
# return self.labels.shape[0] // self.window
# return (self.labels.shape[0] - self.window
return int((self.labels.shape[0] - self.window) // self.step) + 1
if __name__ == '__main__':
val_dataset = CSIDataset([
"./dataset/bedroom_lviv/4",
])
dl = DataLoader(val_dataset, batch_size=16, shuffle=False, num_workers=1)
for i in dl:
print(i[0].shape)
print(i[1].shape)
break
print(val_dataset[0])
================================================
FILE: model/label_activities.py
================================================
from enum import Enum
from os import path
import numpy as np
import pandas as pd
PATH_TO_DATASET = "./dataset/vitalnia_lviv/5"
DATA_FILENAME = "data.csv"
LABEL_FILENAME = "label.csv"
class Activity(Enum):
NO_PERSON = 0
STANDING = 1
WALKING = 2
SITTING = 3
LYING = 4
GET_UP = 5
GET_DOWN = 6
activity_to_string = {
Activity.NO_PERSON: "no_person",
Activity.STANDING: "standing",
Activity.WALKING: "walking",
Activity.SITTING: "sitting",
Activity.LYING: "lying",
Activity.GET_UP: "get_up",
Activity.GET_DOWN: "get_down",
}
def set_activity(activities, from_moment, to_moment, activity):
activities[from_moment:to_moment] = activity_to_string[activity]
def main():
data = pd.read_csv(path.join(PATH_TO_DATASET, DATA_FILENAME))
activities = np.empty(shape=(data.shape[0]), dtype=object)
# Manually defined labels
set_activity(activities, 0, 230, Activity.STANDING)
# ...
activities_df = pd.DataFrame(activities)
activities_df.to_csv(path.join(PATH_TO_DATASET, LABEL_FILENAME), index=True, header=False)
if __name__ == '__main__':
main()
================================================
FILE: model/label_human_boxes.py
================================================
import cv2
import numpy as np
import pandas as pd
import os
from os import listdir
from tqdm import tqdm
labels = open("./yolo_data/coco.names").read().strip().split("\n")
def detect_person_box(path_to_the_image):
#############################################################################
# Setup model
#############################################################################
CONFIDENCE = 0.5
SCORE_THRESHOLD = 0.5
IOU_THRESHOLD = 0.5
# the neural network configuration
config_path = "./yolo_data/yolov3.cfg"
# the YOLO net weights file
weights_path = "./yolo_data/yolov3.weights"
# loading all the class labels (objects)
labels = open("./yolo_data/coco.names").read().strip().split("\n")
# generating colors for each object for later plotting
colors = np.random.randint(0, 255, size=(len(labels), 3), dtype="uint8")
# load the YOLO network
net = cv2.dnn.readNetFromDarknet(config_path, weights_path)
#############################################################################
# Load image
#############################################################################
image = cv2.imread(path_to_the_image) # "../dataset/bedroom_lviv/1/images/opencv_frame_630.png"
h, w = image.shape[:2]
# create 4D blob
blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (416, 416), swapRB=True, crop=False)
# print("image.shape:", image.shape)
# print("blob.shape:", blob.shape) # should be blob.shape: (1, 3, 416, 416)
#############################################################################
# Making prediction
#############################################################################
# sets the blob as the input of the network
net.setInput(blob)
# get all the layer names
ln = net.getLayerNames()
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
# feed forward (inference) and get the network output
# measure how much it took in seconds
layer_outputs = net.forward(ln)
boxes, confidences, class_ids = [], [], []
# loop over each of the layer outputs
for output in layer_outputs:
# loop over each of the object detections
for detection in output:
# extract the class id (label) and confidence (as a probability) of
# the current object detection
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
# discard out weak predictions by ensuring the detected
# probability is greater than the minimum probability
if confidence > CONFIDENCE:
# scale the bounding box coordinates back relative to the
# size of the image, keeping in mind that YOLO actually
# returns the center (x, y)-coordinates of the bounding
# box followed by the boxes' width and height
box = detection[:4] * np.array([w, h, w, h])
(centerX, centerY, width, height) = box.astype("int")
# use the center (x, y)-coordinates to derive the top and
# and left corner of the bounding box
x = int(centerX - (width / 2))
y = int(centerY - (height / 2))
# update our list of bounding box coordinates, confidences,
# and class IDs
boxes.append([x, y, int(width), int(height)])
confidences.append(float(confidence))
class_ids.append(class_id)
# print("boxes: ", boxes)
# print("confidences: ", confidences)
# print("class_ids: ", class_ids) # 0 -> person
return boxes, class_ids
def class_id_to_string(id):
return labels[id]
def main():
DATALEN = 5228
human_boxes = [0] * DATALEN
portion = 10
data_path = "./dataset/bedroom_lviv/1/"
image_folder_path = os.path.join(data_path, "images")
output_csv_path = os.path.join(data_path, "label_boxes.csv")
files = listdir(image_folder_path)
files = [os.path.join(image_folder_path, f) for f in files] # add path to each file
files.sort(key=lambda x: os.path.getmtime(x)) # sort by date
start = 0
for file_path in tqdm(files, total=len(files)):
person_box = [0, 0, 0, 0]
boxes, class_ids = detect_person_box(file_path)
for i, class_id in enumerate(class_ids):
if class_id == 0: # this is person
person_box = boxes[i]
break
human_boxes[start*portion:(start+1)*portion] = [person_box] * portion
start += 1
coords_df = pd.DataFrame(human_boxes, columns=["x", "y", "width", "height"])
coords_df.to_csv(output_csv_path)
if __name__ == '__main__':
main()
================================================
FILE: model/main.py
================================================
================================================
FILE: model/metrics.py
================================================
import torch
from torch.nn import functional as F
from tqdm import tqdm
# Cuda support
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
def get_train_metric(model, dl, criterion, BATCH_SIZE):
model.eval()
correct, total, total_loss = 0, 0, 0
model.hidden = model.init_hidden(BATCH_SIZE)
for x_val, y_val in tqdm(dl, total=len(dl), desc="Validation epoch: "):
if x_val.size(0) != BATCH_SIZE:
continue
model.init_hidden(x_val.size(0))
x_val, y_val = x_val.double().to(device), y_val.double().to(device)
out = model(x_val)
# out = out.view(out.size(0) * out.size(1), out.size(2))
# y_val = y_val.view(y_val.size(0) * y_val.size(1))
# print("y_val.size(0): ", y_val.size())
loss = criterion(out, y_val.long())
total_loss += loss.item()
preds = F.log_softmax(out, dim=1).argmax(dim=1)
total += y_val.size(0)
correct += (preds == y_val).sum().item()
acc = correct / total
return total_loss, correct, total, acc
================================================
FILE: model/models/FCNBaseline.py
================================================
import torch
from torch import nn
from .utils import ConvBlock
class FCNBaseline(nn.Module):
"""A PyTorch implementation of the FCN Baseline
From https://arxiv.org/abs/1909.04939
Attributes
----------
sequence_length:
The size of the input sequence
num_pred_classes:
The number of output classes
"""
def __init__(self, in_channels: int, num_pred_classes: int = 1) -> None:
super().__init__()
# for easier saving and loading
self.input_args = {
'in_channels': in_channels,
'num_pred_classes': num_pred_classes
}
self.layers = nn.Sequential(*[
ConvBlock(in_channels, 128, 8, 1),
ConvBlock(128, 256, 5, 1),
ConvBlock(256, 128, 3, 1),
])
self.final = nn.Linear(256, num_pred_classes)
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
x = self.layers(x)
# return self.final(x.mean(dim=-1))
print("X: ", x.shape)
print("X: ", x.mean(dim=-1).shape)
print("X: ", x.mean(dim=0).shape)
print("X: ", x.mean(dim=1).shape)
return self.final(x)
================================================
FILE: model/models/InceptionTime.py
================================================
import torch
from torch import nn
import torch.nn.functional as F
from typing import cast, Union, List
class InceptionModel(nn.Module):
"""A PyTorch implementation of the InceptionTime model.
From https://arxiv.org/abs/1909.04939
Attributes
----------
num_blocks:
The number of inception blocks to use. One inception block consists
of 3 convolutional layers, (optionally) a bottleneck and (optionally) a residual
connector
in_channels:
The number of input channels (i.e. input.shape[-1])
out_channels:
The number of "hidden channels" to use. Can be a list (for each block) or an
int, in which case the same value will be applied to each block
bottleneck_channels:
The number of channels to use for the bottleneck. Can be list or int. If 0, no
bottleneck is applied
kernel_sizes:
The size of the kernels to use for each inception block. Within each block, each
of the 3 convolutional layers will have kernel size
`[kernel_size // (2 ** i) for i in range(3)]`
num_pred_classes:
The number of output classes
"""
def __init__(self, num_blocks: int, in_channels: int, out_channels: Union[List[int], int],
bottleneck_channels: Union[List[int], int], kernel_sizes: Union[List[int], int],
use_residuals: Union[List[bool], bool, str] = 'default',
num_pred_classes: int = 1
) -> None:
super().__init__()
# for easier saving and loading
self.input_args = {
'num_blocks': num_blocks,
'in_channels': in_channels,
'out_channels': out_channels,
'bottleneck_channels': bottleneck_channels,
'kernel_sizes': kernel_sizes,
'use_residuals': use_residuals,
'num_pred_classes': num_pred_classes
}
channels = [in_channels] + cast(List[int], self._expand_to_blocks(out_channels,
num_blocks))
bottleneck_channels = cast(List[int], self._expand_to_blocks(bottleneck_channels,
num_blocks))
kernel_sizes = cast(List[int], self._expand_to_blocks(kernel_sizes, num_blocks))
if use_residuals == 'default':
use_residuals = [True if i % 3 == 2 else False for i in range(num_blocks)]
use_residuals = cast(List[bool], self._expand_to_blocks(
cast(Union[bool, List[bool]], use_residuals), num_blocks)
)
self.blocks = nn.Sequential(*[
InceptionBlock(in_channels=channels[i], out_channels=channels[i + 1],
residual=use_residuals[i], bottleneck_channels=bottleneck_channels[i],
kernel_size=kernel_sizes[i]) for i in range(num_blocks)
])
# a global average pooling (i.e. mean of the time dimension) is why
# in_features=channels[-1]
self.linear = nn.Linear(in_features=channels[-1], out_features=num_pred_classes)
@staticmethod
def _expand_to_blocks(value: Union[int, bool, List[int], List[bool]],
num_blocks: int) -> Union[List[int], List[bool]]:
if isinstance(value, list):
assert len(value) == num_blocks, \
f'Length of inputs lists must be the same as num blocks, ' \
f'expected length {num_blocks}, got {len(value)}'
else:
value = [value] * num_blocks
return value
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
x = self.blocks(x).mean(dim=-1) # the mean is the global average pooling
return self.linear(x)
class InceptionBlock(nn.Module):
"""An inception block consists of an (optional) bottleneck, followed
by 3 conv1d layers. Optionally residual
"""
def __init__(self, in_channels: int, out_channels: int,
residual: bool, stride: int = 1, bottleneck_channels: int = 32,
kernel_size: int = 41) -> None:
super().__init__()
self.use_bottleneck = bottleneck_channels > 0
if self.use_bottleneck:
self.bottleneck = Conv1dSamePadding(in_channels, bottleneck_channels,
kernel_size=1, bias=False)
kernel_size_s = [kernel_size // (2 ** i) for i in range(3)]
start_channels = bottleneck_channels if self.use_bottleneck else in_channels
channels = [start_channels] + [out_channels] * 3
self.conv_layers = nn.Sequential(*[
Conv1dSamePadding(in_channels=channels[i], out_channels=channels[i + 1],
kernel_size=kernel_size_s[i], stride=stride, bias=False)
for i in range(len(kernel_size_s))
])
self.batchnorm = nn.BatchNorm1d(num_features=channels[-1])
self.relu = nn.ReLU()
self.use_residual = residual
if residual:
self.residual = nn.Sequential(*[
Conv1dSamePadding(in_channels=in_channels, out_channels=out_channels,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm1d(out_channels),
nn.ReLU()
])
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
org_x = x
if self.use_bottleneck:
x = self.bottleneck(x)
x = self.conv_layers(x)
if self.use_residual:
x = x + self.residual(org_x)
return x
class Conv1dSamePadding(nn.Conv1d):
"""Represents the "Same" padding functionality from Tensorflow.
See: https://github.com/pytorch/pytorch/issues/3867
Note that the padding argument in the initializer doesn't do anything now
"""
def forward(self, input):
return conv1d_same_padding(input, self.weight, self.bias, self.stride,
self.dilation, self.groups)
def conv1d_same_padding(input, weight, bias, stride, dilation, groups):
# stride and dilation are expected to be tuples.
kernel, dilation, stride = weight.size(2), dilation[0], stride[0]
l_out = l_in = input.size(2)
padding = (((l_out - 1) * stride) - l_in + (dilation * (kernel - 1)) + 1)
if padding % 2 != 0:
input = F.pad(input, [0, 1])
return F.conv1d(input=input, weight=weight, bias=bias, stride=stride,
padding=padding // 2,
dilation=dilation, groups=groups)
class ConvBlock(nn.Module):
def __init__(self, in_channels: int, out_channels: int, kernel_size: int,
stride: int) -> None:
super().__init__()
self.layers = nn.Sequential(
Conv1dSamePadding(in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=stride),
nn.BatchNorm1d(num_features=out_channels),
nn.ReLU(),
)
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
return self.layers(x)
================================================
FILE: model/models/LSTMClassifier.py
================================================
import torch
from torch import nn
from torch.autograd import Variable
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
class LSTMClassifier(nn.Module):
def __init__(self, in_dim, hidden_dim, num_layers, dropout, bidirectional, num_classes, batch_size):
super(LSTMClassifier, self).__init__()
self.arch = 'lstm'
self.hidden_dim = hidden_dim
self.batch_size = batch_size
self.num_dir = 2 if bidirectional else 1
self.num_layers = num_layers
self.lstm = nn.LSTM(
input_size=in_dim,
hidden_size=hidden_dim,
num_layers=num_layers,
dropout=dropout,
bidirectional=bidirectional,
batch_first=True
)
self.hidden2label = nn.Sequential(
nn.Linear(hidden_dim * self.num_dir, hidden_dim),
nn.ReLU(True),
nn.Dropout(p=dropout),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(True),
nn.Dropout(p=dropout),
nn.Linear(hidden_dim, num_classes),
)
self.hidden = None
def init_hidden(self, batch_size):
h0 = nn.Parameter(nn.init.xavier_uniform_(
torch.Tensor(self.num_dir * self.num_layers, batch_size, self.hidden_dim).type(torch.DoubleTensor)
), requires_grad=True).to(device)
c0 = nn.Parameter(nn.init.xavier_uniform_(
torch.Tensor(self.num_dir * self.num_layers, batch_size, self.hidden_dim).type(torch.DoubleTensor)
), requires_grad=True).to(device)
return h0, c0
def forward(self, x): # x is (batch_size, sequence_size, num_of_features)
if self.hidden is None:
self.hidden = self.init_hidden(x.size(0))
# See: https://discuss.pytorch.org/t/solved-why-we-need-to-detach-variable-which-contains-hidden-representation/1426/2
lstm_out, _ = self.lstm(x, self.hidden)
y = self.hidden2label(lstm_out[:, -1, :])
return y
================================================
FILE: model/models/SimpleLSTMClassifier.py
================================================
import torch
from torch import nn
# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
device = torch.device("cpu")
class SimpleLSTMClassifier(nn.Module):
"""Very simple implementation of LSTM-based time-series classifier."""
def __init__(self, input_dim, hidden_dim, num_layers_lstm, out_classes_num, dropout=0.0, bidirectional=False):
super().__init__()
self.hidden_dim = hidden_dim
self.layer_dim = num_layers_lstm
self.bidirectional = bidirectional
self.lstm = nn.LSTM(input_dim, hidden_dim,
num_layers=num_layers_lstm,
batch_first=True,
dropout=dropout,
bidirectional=bidirectional,
bias=True)
self.hidden2label = nn.Linear((hidden_dim * 2) if self.bidirectional else hidden_dim, out_classes_num)
self.hidden = None
def forward(self, x):
if self.hidden is None:
self.hidden = self.init_hidden(x.size(0))
out, _ = self.lstm(x, self.hidden)
out = self.hidden2label(out[:, -1, :])
return out
def init_hidden(self, batch_size):
h0 = nn.Parameter(nn.init.xavier_uniform_(
torch.Tensor(self.layer_dim, batch_size, self.hidden_dim).type(torch.DoubleTensor)
), requires_grad=True).to(device)
c0 = nn.Parameter(nn.init.xavier_uniform_(
torch.Tensor(self.layer_dim, batch_size, self.hidden_dim).type(torch.DoubleTensor)
), requires_grad=True).to(device)
return h0, c0
================================================
FILE: model/models/__init__.py
================================================
from .LSTMClassifier import LSTMClassifier
from .SimpleLSTMClassifier import SimpleLSTMClassifier
from .FCNBaseline import FCNBaseline
from .InceptionTime import InceptionModel
================================================
FILE: model/models/utils.py
================================================
import torch
from torch import nn
import torch.nn.functional as F
class Conv1dSamePadding(nn.Conv1d):
"""Represents the "Same" padding functionality from Tensorflow.
See: https://github.com/pytorch/pytorch/issues/3867
Note that the padding argument in the initializer doesn't do anything now
"""
def forward(self, input):
return conv1d_same_padding(input, self.weight, self.bias, self.stride,
self.dilation, self.groups)
def conv1d_same_padding(input, weight, bias, stride, dilation, groups):
# stride and dilation are expected to be tuples.
kernel, dilation, stride = weight.size(2), dilation[0], stride[0]
l_out = l_in = input.size(2)
padding = (((l_out - 1) * stride) - l_in + (dilation * (kernel - 1)) + 1)
if padding % 2 != 0:
input = F.pad(input, [0, 1])
return F.conv1d(input=input, weight=weight, bias=bias, stride=stride,
padding=padding // 2,
dilation=dilation, groups=groups)
class ConvBlock(nn.Module):
def __init__(self, in_channels: int, out_channels: int, kernel_size: int,
stride: int) -> None:
super().__init__()
self.layers = nn.Sequential(
Conv1dSamePadding(in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=stride),
nn.BatchNorm1d(num_features=out_channels),
nn.ReLU(),
)
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
return self.layers(x)
================================================
FILE: model/notebooks/DataFilteringAndFeatureEngineering.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.6.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
================================================
FILE: model/notebooks/DetectPersonBoxes.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import cv2\n",
"import numpy as np\n",
"\n",
"import time\n",
"import sys\n",
"import os"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Setup YOLO detection system"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"CONFIDENCE = 0.5\n",
"SCORE_THRESHOLD = 0.5\n",
"IOU_THRESHOLD = 0.5\n",
"\n",
"# the neural network configuration\n",
"config_path = \"../yolo_data/yolov3.cfg\"\n",
"\n",
"# the YOLO net weights file\n",
"weights_path = \"../yolo_data/yolov3.weights\"\n",
"\n",
"# loading all the class labels (objects)\n",
"labels = open(\"../yolo_data/coco.names\").read().strip().split(\"\\n\")\n",
"\n",
"# generating colors for each object for later plotting\n",
"colors = np.random.randint(0, 255, size=(len(labels), 3), dtype=\"uint8\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# load the YOLO network\n",
"net = cv2.dnn.readNetFromDarknet(config_path, weights_path)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"path_name = \"../dataset/bedroom_lviv/1/images/opencv_frame_630.png\"\n",
"\n",
"image = cv2.imread(path_name)\n",
"file_name = os.path.basename(path_name)\n",
"filename, ext = file_name.split(\".\")"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"# cv2.imshow(image)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"image.shape: (480, 640, 3)\n",
"blob.shape: (1, 3, 416, 416)\n"
]
}
],
"source": [
"h, w = image.shape[:2]\n",
"\n",
"# create 4D blob\n",
"blob = cv2.dnn.blobFromImage(image, 1/255.0, (416, 416), swapRB=True, crop=False)\n",
"\n",
"print(\"image.shape:\", image.shape)\n",
"print(\"blob.shape:\", blob.shape)\n",
"\n",
"# image.shape: (1200, 1800, 3)\n",
"# blob.shape: (1, 3, 416, 416)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Making predictions"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Time took: 0.85s\n"
]
}
],
"source": [
"# sets the blob as the input of the network\n",
"net.setInput(blob)\n",
"\n",
"# get all the layer names\n",
"ln = net.getLayerNames()\n",
"ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]\n",
"\n",
"# feed forward (inference) and get the network output\n",
"# measure how much it took in seconds\n",
"start = time.perf_counter()\n",
"layer_outputs = net.forward(ln)\n",
"\n",
"time_took = time.perf_counter() - start\n",
"\n",
"print(f\"Time took: {time_took:.2f}s\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"font_scale = 1\n",
"thickness = 1\n",
"\n",
"boxes, confidences, class_ids = [], [], []\n",
"\n",
"# loop over each of the layer outputs\n",
"for output in layer_outputs:\n",
"\n",
" # loop over each of the object detections\n",
" for detection in output:\n",
" # extract the class id (label) and confidence (as a probability) of\n",
" # the current object detection\n",
" scores = detection[5:]\n",
" class_id = np.argmax(scores)\n",
" confidence = scores[class_id]\n",
"\n",
" # discard out weak predictions by ensuring the detected\n",
" # probability is greater than the minimum probability\n",
" if confidence > CONFIDENCE:\n",
" # scale the bounding box coordinates back relative to the\n",
" # size of the image, keeping in mind that YOLO actually\n",
" # returns the center (x, y)-coordinates of the bounding\n",
" # box followed by the boxes' width and height\n",
" box = detection[:4] * np.array([w, h, w, h])\n",
" (centerX, centerY, width, height) = box.astype(\"int\")\n",
" \n",
" # use the center (x, y)-coordinates to derive the top and\n",
" # and left corner of the bounding box\n",
" x = int(centerX - (width / 2))\n",
" y = int(centerY - (height / 2))\n",
" \n",
" # update our list of bounding box coordinates, confidences,\n",
" # and class IDs\n",
" boxes.append([x, y, int(width), int(height)])\n",
" confidences.append(float(confidence))\n",
" class_ids.append(class_id)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(85,)\n"
]
}
],
"source": [
"print(detection.shape)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Drawing Detected Objects"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# loop over the indexes we are keeping\n",
"for i in range(len(boxes)):\n",
" # extract the bounding box coordinates\n",
" x, y = boxes[i][0], boxes[i][1]\n",
" w, h = boxes[i][2], boxes[i][3]\n",
" \n",
" # draw a bounding box rectangle and label on the image\n",
" color = [int(c) for c in colors[class_ids[i]]]\n",
" cv2.rectangle(image, (x, y), (x + w, y + h), color=color, thickness=thickness)\n",
" text = f\"{labels[class_ids[i]]}: {confidences[i]:.2f}\"\n",
" \n",
" # calculate text width & height to draw the transparent boxes as background of the text\n",
" (text_width, text_height) = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, fontScale=font_scale, thickness=thickness)[0]\n",
" text_offset_x = x\n",
" text_offset_y = y - 5\n",
" box_coords = ((text_offset_x, text_offset_y), (text_offset_x + text_width + 2, text_offset_y - text_height))\n",
" overlay = image.copy()\n",
" cv2.rectangle(overlay, box_coords[0], box_coords[1], color=color, thickness=cv2.FILLED)\n",
" \n",
" # add opacity (transparency to the box)\n",
" image = cv2.addWeighted(overlay, 0.6, image, 0.4, 0)\n",
" \n",
" # now put the text (label: confidence %)\n",
" cv2.putText(image, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, \n",
" fontScale=font_scale, color=(0, 0, 0), thickness=thickness)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Boxes: [[35, 6, 240, 463]]\n",
"class_ids: [0]\n"
]
}
],
"source": [
"print(\"Boxes: \", boxes)\n",
"print(\"class_ids: \", class_ids)\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"# cv2.imwrite(filename + \"_yolo3.\" + ext, image)\n",
"\n",
"# cv2.imshow('image', image)\n",
"# cv2.waitKey(0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"def detect_person_box(path_to_the_image):\n",
" #############################################################################\n",
" # Setup model\n",
" #############################################################################\n",
" CONFIDENCE = 0.5\n",
" SCORE_THRESHOLD = 0.5\n",
" IOU_THRESHOLD = 0.5\n",
"\n",
" # the neural network configuration\n",
" config_path = \"../yolo_data/yolov3.cfg\"\n",
"\n",
" # the YOLO net weights file\n",
" weights_path = \"../yolo_data/yolov3.weights\"\n",
"\n",
" # loading all the class labels (objects)\n",
" labels = open(\"../yolo_data/coco.names\").read().strip().split(\"\\n\")\n",
"\n",
" # generating colors for each object for later plotting\n",
" colors = np.random.randint(0, 255, size=(len(labels), 3), dtype=\"uint8\")\n",
" \n",
" # load the YOLO network\n",
" net = cv2.dnn.readNetFromDarknet(config_path, weights_path)\n",
" \n",
" \n",
" #############################################################################\n",
" # Load image\n",
" #############################################################################\n",
"# path_name = \"../dataset/bedroom_lviv/1/images/opencv_frame_630.png\"\n",
"\n",
" image = cv2.imread(path_to_the_image)\n",
" file_name = os.path.basename(path_name)\n",
" filename, ext = file_name.split(\".\")\n",
" \n",
" h, w = image.shape[:2]\n",
"\n",
" # create 4D blob\n",
" blob = cv2.dnn.blobFromImage(image, 1/255.0, (416, 416), swapRB=True, crop=False)\n",
"\n",
"# print(\"image.shape:\", image.shape)\n",
"# print(\"blob.shape:\", blob.shape) # should be blob.shape: (1, 3, 416, 416)\n",
"\n",
"\n",
" #############################################################################\n",
" # Making prediction\n",
" #############################################################################\n",
"\n",
" # sets the blob as the input of the network\n",
" net.setInput(blob)\n",
"\n",
" # get all the layer names\n",
" ln = net.getLayerNames()\n",
" ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]\n",
"\n",
" # feed forward (inference) and get the network output\n",
" # measure how much it took in seconds\n",
" layer_outputs = net.forward(ln)\n",
" \n",
" font_scale, thickness = 1, 1\n",
"\n",
" boxes, confidences, class_ids = [], [], []\n",
"\n",
" # loop over each of the layer outputs\n",
" for output in layer_outputs:\n",
"\n",
" # loop over each of the object detections\n",
" for detection in output:\n",
" # extract the class id (label) and confidence (as a probability) of\n",
" # the current object detection\n",
" scores = detection[5:]\n",
" class_id = np.argmax(scores)\n",
" confidence = scores[class_id]\n",
"\n",
" # discard out weak predictions by ensuring the detected\n",
" # probability is greater than the minimum probability\n",
" if confidence > CONFIDENCE:\n",
" # scale the bounding box coordinates back relative to the\n",
" # size of the image, keeping in mind that YOLO actually\n",
" # returns the center (x, y)-coordinates of the bounding\n",
" # box followed by the boxes' width and height\n",
" box = detection[:4] * np.array([w, h, w, h])\n",
" (centerX, centerY, width, height) = box.astype(\"int\")\n",
"\n",
" # use the center (x, y)-coordinates to derive the top and\n",
" # and left corner of the bounding box\n",
" x = int(centerX - (width / 2))\n",
" y = int(centerY - (height / 2))\n",
"\n",
" # update our list of bounding box coordinates, confidences,\n",
" # and class IDs\n",
" boxes.append([x, y, int(width), int(height)])\n",
" confidences.append(float(confidence))\n",
" class_ids.append(class_id)\n",
"\n",
" print(\"boxes: \", boxes)\n",
" print(\"confidences: \", confidences)\n",
" print(\"class_ids: \", class_ids) # 0 -> person\n",
" \n",
" return boxes"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"boxes: [[35, 6, 240, 463]]\n",
"confidences: [0.9965195059776306]\n",
"class_ids: [0]\n"
]
},
{
"data": {
"text/plain": [
"[[35, 6, 240, 463]]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"detect_person_box(\"../dataset/bedroom_lviv/1/images/opencv_frame_630.png\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.6.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
================================================
FILE: model/notebooks/ExperimentsVisualization.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"data": {
"text/plain": [
"DataTransformerRegistry.enable('default')"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"import altair as alt\n",
"\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib.backends.backend_pdf import PdfPages\n",
"\n",
"alt.data_transformers.enable('default', max_rows=None)"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"# Visualize CSI HAR experiments"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"## Supplementary functions"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"EXPERIMENTS_FOLDER = \"../experiments\"\n",
"exp_filename = \"data.csv\"\n",
"\n",
"SUBCARRIES_NUM_TWO_HHZ = 56\n",
"SUBCARRIES_NUM_FIVE_HHZ = 114"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"# https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/sys031fp.pdf\n",
"# Based on https://github.com/ermongroup/Wifi_Activity_Recognition/.../phase_calibration.m\n",
"def calibrate_single_phase(phases):\n",
" phases = np.array(phases)\n",
" difference = 0\n",
"\n",
" calibrated_phase, calibrated_phase_final = np.zeros_like(phases), np.zeros_like(phases) \n",
" calibrated_phase[0] = phases[0]\n",
" \n",
" phases_len = phases.shape[0]\n",
" \n",
" for i in range(1, phases_len):\n",
" temp = phases[i]- phases[i - 1]\n",
" \n",
" if abs(temp) > np.pi:\n",
" difference = difference + 1*np.sign(temp)\n",
" \n",
" calibrated_phase[i] = phases[i] - difference * 2 * np.pi\n",
" \n",
" k = (calibrated_phase[-1] - calibrated_phase[0]) / (phases_len - 1)\n",
" b = np.mean(calibrated_phase)\n",
" \n",
" for i in range(phases_len):\n",
" calibrated_phase_final[i] = calibrated_phase[i] - k * i - b\n",
" \n",
" return calibrated_phase_final\n",
"\n",
"\n",
"def calibrate_phase(phases):\n",
" calibated_phases = np.zeros_like(phases)\n",
" \n",
" for i in range(phases.shape[0]):\n",
" calibated_phases[i] = calibrate_single_phase(np.unwrap(phases[i]))\n",
" \n",
" return calibated_phases\n",
"\n",
"def calibrate_amplitude(amplitudes, rssi=1): # Basic statistical normalization\n",
" amplitudes = np.array(amplitudes) \n",
" return ((amplitudes - np.min(amplitudes)) / (np.max(amplitudes) - np.min(amplitudes))) * rssi"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"def read_csi_data_from_csv(path_to_csv, is_five_hhz=False, antenna_pairs=4):\n",
" data = pd.read_csv(path_to_csv, header=None).values\n",
"\n",
" if is_five_hhz:\n",
" subcarries_num = SUBCARRIES_NUM_FIVE_HHZ\n",
" else:\n",
" subcarries_num = SUBCARRIES_NUM_TWO_HHZ\n",
" \n",
" data_len = data.shape[0]\n",
" \n",
" amplitudes = data[:, subcarries_num*1:subcarries_num*(1 + antenna_pairs)]\n",
" phases = data[:, subcarries_num*(1 + antenna_pairs):subcarries_num*(1 + 2*antenna_pairs)]\n",
" \n",
" return amplitudes, phases\n",
"\n",
"# print(\"path_to_experiment_data: \", path_to_experiment_data)\n",
"# read_csi_data_from_csv(path_to_experiment_data)"
]
},
{
"cell_type": "code",
"execution_count": 161,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"source": [
"import pywt\n",
"\n",
"\n",
"def hampel(vals_orig, k=3, t0=123, outliers_return=False):\n",
" vals = pd.Series(vals_orig.copy())\n",
"\n",
" #Hampel Filter\n",
" L= 1.4826\n",
" \n",
" rolling_median=vals.rolling(k).median()\n",
" difference=np.abs(rolling_median-vals)\n",
" median_abs_deviation=difference.rolling(k).median()\n",
" threshold= t0 *L * median_abs_deviation\n",
" outlier_idx=difference>threshold\n",
" vals[outlier_idx]=rolling_median\n",
" \n",
" if outliers_return:\n",
" return vals, outlier_idx\n",
"\n",
" return vals\n",
"\n",
"def dwn_noise(vals, th=0.19):\n",
" threshold = th # Threshold for filtering\n",
"\n",
" data = vals.copy()\n",
"# print(\"data: \", data.shape)\n",
"\n",
" w = pywt.Wavelet('sym5')\n",
" maxlev = pywt.dwt_max_level(data.shape[0], w.dec_len)\n",
"# print(\"maximum level is \" + str(maxlev))\n",
" \n",
" # Decompose into wavelet components, to the level selected:\n",
" coeffs = pywt.wavedec(data, 'sym5', level=maxlev)\n",
" \n",
"# plt.figure()\n",
" for i in range(1, len(coeffs)):\n",
"# plt.subplot(maxlev, 1, i)\n",
"# plt.plot(coeffs[i])\n",
" coeffs[i] = pywt.threshold(coeffs[i], threshold*max(coeffs[i]))\n",
"# plt.plot(coeffs[i])\n",
"\n",
" datarec = pywt.waverec(coeffs, 'sym5')\n",
" \n",
" return datarec\n",
"\n",
"def noise_reduction_all_subcarriers(amplitudes):\n",
" data_len = amplitudes.shape[0]\n",
" \n",
" res = np.zeros_like(amplitudes)\n",
"\n",
" for i in range(amplitudes.shape[1]):\n",
" res[:data_len, i] = dwn_noise(hampel(amplitudes[:, i]))[:data_len]\n",
" \n",
" return res\n",
"\n",
"def hampel_all_subcarriers(amplitudes, k=3, t0=123):\n",
" data_len = amplitudes.shape[0]\n",
" \n",
" res = np.zeros_like(amplitudes)\n",
" outliers = []\n",
"\n",
" for i in range(amplitudes.shape[1]):\n",
" res[:data_len, i], out = hampel(amplitudes[:, i], k, t0, outliers_return=True)[:data_len]\n",
" outliers.append(out)\n",
" \n",
" return res, outliers\n",
"\n",
"def cwt_all_subcarriers(amplitudes, th=0.19):\n",
" data_len = amplitudes.shape[0]\n",
" \n",
" res = np.zeros_like(amplitudes)\n",
" \n",
" for i in range(amplitudes.shape[1]):\n",
" res[:data_len, i] = dwn_noise(amplitudes[:, i], th)[:data_len]\n",
" \n",
" return res\n",
"\n",
"\n",
"def annotation_line( ax, xmin, xmax, y, text, ytext=0, linecolor='black', linewidth=1, fontsize=12 ):\n",
"\n",
" ax.annotate('', xy=(xmin, y), xytext=(xmax, y), xycoords='data', textcoords='data', annotation_clip=False,\n",
" arrowprops={'arrowstyle': '|-|', 'color':linecolor, 'linewidth':linewidth})\n",
" \n",
" ax.annotate('', xy=(xmin, y), xytext=(xmax, y), xycoords='data', textcoords='data', annotation_clip=False,\n",
" arrowprops={'arrowstyle': '<->', 'color':linecolor, 'linewidth':linewidth})\n",
"\n",
" xcenter = xmin + (xmax-xmin)/2\n",
" if ytext == 0:\n",
" ytext = y + ( ax.get_ylim()[1] - ax.get_ylim()[0] ) / 20\n",
"\n",
" ax.annotate(text, xy=(xcenter,ytext), ha='center', va='center', fontsize=fontsize, annotation_clip=False)"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"## 1. Channels comparison"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"path_to_experiment_data: ../experiments/channels_comparison/2_channels(=3)/data.csv\n",
"path_to_experiment_data: ../experiments/channels_comparison/2_channels(=7)/data.csv\n",
"path_to_experiment_data: ../experiments/channels_comparison/2_channels(=10)/data.csv\n",
"path_to_experiment_data: ../experiments/channels_comparison/2_channels(=11)/data.csv\n"
]
}
],
"source": [
"experiments_data = []\n",
"\n",
"experiment_name = \"channels_comparison\"\n",
"subexperiment_names = [\"2_channels(=3)\", \"2_channels(=7)\", \"2_channels(=10)\", \"2_channels(=11)\"]\n",
"\n",
"for subexperiment_name in subexperiment_names:\n",
" path_to_experiment_data = os.path.join(EXPERIMENTS_FOLDER, experiment_name, subexperiment_name, exp_filename)\n",
" print(\"path_to_experiment_data: \", path_to_experiment_data)\n",
" \n",
" amplitudes, phases = read_csi_data_from_csv(path_to_experiment_data, False)\n",
" \n",
" experiments_data.append([amplitudes, phases])\n",
" \n",
"# calibrate_phase(phases[:, 0*SUBCARRIES_NUM_TWO_HHZ:1*SUBCARRIES_NUM_TWO_HHZ]).shape"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Amplitude plot"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/andrew/Bachelor_thesis/CSI_OpenWrt(C)_Visuals(Python)/model_for_har/venv/lib/python3.6/site-packages/ipykernel_launcher.py:2: MatplotlibDeprecationWarning: \n",
"The mpl_toolkits.axes_grid1.colorbar module was deprecated in Matplotlib 3.2 and will be removed two minor releases later. Use matplotlib.colorbar instead.\n",
" \n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABUMAAAJhCAYAAABrZqElAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+j8jraAAAgAElEQVR4nOy9f5wkVXnv/z5T1T+ne6Z2u3dnmN2FQVYgBCLGNRCFQBJ8gUTFRBOI5ofmJkQTk9wfJjG53gSv5Ide84pJTGKMRlCixpirUb8ICRoMSMRghIDIyiILuyw7u9M7vdu9/Wu65nz/OOdUna7tmV0U2Av7vF+vfnV31alznvOcp06d7q7+PEprjSAIgiAIgiAIgiAIgiAIwrOdieNtgCAIgiAIgiAIgiAIgiAIwtOBfBkqCIIgCIIgCIIgCIIgCMIJgXwZKgiCIAiCIAiCIAiCIAjCCYF8GSoIgiAIgiAIgiAIgiAIwgmBfBkqCIIgCIIgCIIgCIIgCMIJgXwZKgiCIAiCIAiCIAiCIAjCCYF8GSoIwjGhlLpGKXXD8bYji1LqVqXUzz+J9X1UKfXKJ6u+ZxJKqT9SSr3xeNshCIIgCILgI+vQZz9KqX9QSr30eNshCMKJgXwZKghCglLqNUqpu5RSbaXU40qpzymlLjjedn27KKWuUkptV0odVErtU0pdr5SaWqP89wDPA/7Rvv8RpdTtSqmmUmqvUur9SqnqmOPWK6X2K6Vu97adr5T6Z6XUAbvv75VSJ3n7f10pdZ9SqqWUelgp9etr2DWvlNJKqa9ltteVUgOl1E5v206l1CWZcq/zbVuDdwG/rZTKH0NZQRAEQRCEJ41n4Tr0vbYv7tFXSrXWKP+krUMz+3/HriMv8ba9Syn1oF2HPqCU+pk17LrYHv/JzPbn2e23etu0UmprptyxfpH9DuDaYygnCILwHSNfhgqCAIBS6r8D7wZ+H5gBTgb+ArjieNr1HfIl4MVa62ngOUDI2ousXwT+Vmut7ftpW34O+C5gE/B/xhz3DuAbmW3rgPcB88ApQAv4oLdfAT9jy10GvEkpddVR+lNWSp3tvX8N8PBRjjlmtNaPAw8Ar3iy6hQEQRAEQTgaz8Z1qNb6DVrrinsAHwX+fo1Dnsx1KABKqdOAHwcez+w6DLzctvGzwJ8opV60hm37ge9XStW8bT8LfHONY54QWuuvAFNKqW1PVp2CIAirIV+GCoKAUmoa+N/AL2ut/6/W+rDWellr/RmttX/HYl4p9SH7K/LX/cWKUuotSqmH7L77lVI/6u17nf1l+11KqSV7J+RLvf23KqXerpT6kj3+n5RSdW//+UqpO+wv4/copS4+ln5prXdprRe9TTGwdbXywEuBL3rHf0RrfZPWuqO1XgL+GnhxxncvAs5m9ItOtNaf01r/vdb6kNa6A7zHP1Zr/U6t9X9orYda6+2YuwBG6h7DhzELT8fPAB86yjEjKKWuHHOXwq1ekVuBH3kidQqCIAiCIHy7PFvXoZk+TgKvAq5fo9iTtg71+HPgN4GBv1Fr/bta6we01ita6zuB24DvX8O2AfAp4CrbbgBcCfztGsccgVLqNzLr0GWl1HVekVuRdaggCE8D8mWoIAhgFj9F4JNHKfcK4GNABHwa8wWf4yHgQswvzG8DblDe38KB84DtQB14J/ABpZTy9r8GeD2wEcgDbwZQSm0C/j/ML+Pr7fZ/UEptOJaOKaUuUEodxNyZ+SrMXQfjyk0Cp1obV+MHgK97xwQYH7wJ0KsdNO7YTNsK47ux+z1uAK5SSgVKqbOACnDnUY4ZQWv9d94dCnPAtzB3Kji+gfmLliAIgiAIwtPBs3Yd6vEqzN2V/zpu51OxDlVK/TjQ11rfuJZhSqkS8EKOvg79EOaHeIBLgfuAPUc5ZgR7M4Bbh34Xxid/5xWRdaggCE8L8mWoIAgANWBRaz08SrnbtdY3aq1jzF2KyWLF3gW5x/7C/HfAg8D3ecc+orX+a3vs9cBJmL9BOT6otf6m1roLfBw4127/KeBG2+6K1vqfgbuAy4+lY1rr2+3f5Ddj/lq0c5WikX0eq+WklHoJ5q7M3/E2/ypwp9b6q2vZoIwG1O8Aq+mCXoOZj1f7Vd+xG7NIvgSzGP3wKuU+Ze9eaCqlmpi/mWVtmgA+Atyqtf4rb1eL1BeCIAiCIAhPNc/adajHzwIf8v4Cn+VJXYcqoy36+8CvHYNt7wXuAW5eq5DW+g5gvVLqDNb+d9J/ZNahbxljXwlzp+mfaK0/5+2SdaggCE8L8mWoIAgADaCulAqPUm6v97oDFN0xSqmfUUrd7S18zsb8+n7EsfZv42DubFytbrfvFODHM4uqCzCL2GNGa/0YcBPmjoJxNO3zOGH68zFfHL5aa/1Nu20Oswj9n2u1a0XkPwf8mtb6tjH734RZUP6I1rp/DF35EPA64CdZ/cvQV2qtI/cAfmlMmd/D9PVXM9urpL4QBEEQBEF4qnlWr0OVUicDF7O2tNGTvQ69Bviw1nrnUWz7Pxhf/cQaX9T6fBhzJ+oPsvqdvN+bWYf+4ZgyHwC2a63fkdku61BBEJ4WjnbBEQThxODfgD7wSuATT/RgpdQpGB2jHwb+TWsdK6XuxiQJ+k7ZhVnM/cKTUFcInDZuh9b6sFLqIeB0zF92AFBKPR/zV6yf01p/3jvk+zAL4fvtv6xKQEkptRfYZH1wCnAL8Hat9RFfXCqlfg7za/kPaK13H2Mf/gHzl6ivaq0fVUqdfozH+e1ehfky9YVa6+XM7u/C3B0gCIIgCILwdPBsX4f+NPAlrfW3VivwZK9DMb7YrJRyP4hvAD6ulHqH+wJSKfU2jE7pRVrrQ8fYlw8DOzB3uXZGlQaODaXUW2w/LxyzW9ahgiA8LciXoYIgoLU+qJT6HeDPlVJD4J+AZczfsX9Qa/0bR6liEqNVtB9AKfV6zK/MTwY3AP+ulLoU88ViDjgf2HG0LxCVUq8FbrNfGp6CuRvy82scciNwESYLPcpkbr8J+BWt9WcyZT+HyRTvuBKjN3WFXYRvAr4AvEdr/d5VbPt9jH9XXRxnsYvlHwKWjvWYTLvPB/4MeInWev+YIhcB7/926hYEQRAEQXiiPFvXoR4/g8n4fjSezHXoD1tbHf8O/Hd7HEqp37LlL9RaN46xH2itH1ZKXYTRnH/C2MRVvwqcZyUJslyEkSYQBEF4SpG/yQuCAIDW+o8wi6S3YhaTuzB/g/nUMRx7P/BHmF/2F4BzsAu5J8GuXcAVwG97dv06xzZ/nQXcoZQ6bO3ZDqz1y/77gNd6gvr/A/NL+ge8rJdft3b1tdZ73QM4CCzb1wA/DzwHuMbPmum1dS1GI+vfvf1HfGk6Dq31XVrrh46l7BiuANYBt3vtuoXxSRifHXXMBUEQBEEQniyepetQlFLfj9Gt//tjKP6krUO11o3M/hhY0lq7tejvAycDO7y6f/tY+mT1+J9Q4iSPK22fvpFd/yqlXgi0tdZf+TbrFgRBOGbUsUmDCIIgnBgopT4CfFxrfcJ9IaiU+iPgIa31EQmXBEEQBEEQhKeWE3wd+g/AB7TWNx5vWwRBePYjX4YKgiAIgiAIgiAIgiAIgnBC8FT+Tf5vgH3AfavsV8CfYgSY/xP43qfQFkEQBEEQBEEQBEEQBEEQTnCeyi9DrwMuW2P/S4Hn2sfVwF8+hbYIgiAIgiAIgiAIgiAIgnCC81R+GfqvwIE19l8BfEgpdalS6vr5+fnnz83N/d5TaI8gCIIgCIIgJCilLlNKbVdK7VBKveV42yMIgiAIgiA89RzPbPKbWq3WY8CfAy/9xje+cfuhQ4d+XCl11nG0SRAEQRAEQTgBUEoF2HUocBbwk7IOFQRBEARBePYTHs/Gr7nmmu8CdmitvwXorVu3/tM999xzBXD/ascoVddsmDdvFoFZoA0UgBjoAwGwAfgWUAPKQAsYAhWgC+RthfuBaeAQMGkfOfte2fpC+1gGisDAttUBNgLalm3Y7XWgCUS2zY7dH1g7D1ubYqClYbOC3cAW26cupuFNBVNnyfZx2dq9yb7eDTwH+NYQ1odpWycBC8CUtXXS9vUQULXblLV9u233kC0T27qVrccdt79jGi+E6VfogbXH9T+w/iwCPevXvPV9wfbjsK3/JOuvAdDr2QpiU8lEwdhcsOOlgRU7Lh1bT2iL77Pb3b7A1tm1dqyztpTtmEzbMc1bWzdaOzbYdjq2zaatf719DdBegZkJ44+ytalLehbl7DhV7L4Q46u8HY+TbF0bMGO1Auyx49kH9vWhWDD7hjFsCEx93RVTUWjrKtj2XSzlra0V22YLwo0Dhgfy8Chwhm2rDzwCnL0CeyeMr9z5Urev99g6nqPhcWX6t8H6NGd9utuWrdixPd3GSGi3PWb9DuTW9VnuFuCbwFkaYmVUgjcC6zQ8qszrXdbGzXYcNFDV0Fem3cds3OSsvx4H5jQsKFiy5c80/qiuP0g7nqQQDOh9owxbQBVj9IHA+O2QrSsCFExPHWACzdJXlalosmbGfWBtqpHOBX075hPW51XrA2XHdGBjrGfHZqOto2jLH/bipWHrblv/N2w9FWufG9uOHYMS6TzVtm2HpHNPBEG0TLwcwm5l2pwCChqWFeRWIJ4w9pcw47AJ2GntOGy3T1h7J23fDtltVfs6j/HPHrtt0vNNZP2DjavY2tiFdZsadCnS60wSlJaJH8+ZfW4+dXNPYB/K9rM/hJND44cOJu67Nhbc3OPsc+fq0I7DOhtbZfvo2bITpHGq7PaKbW/Ss91NSw1bdgMmlp9jtwW2rhzw+DKEtk9zmHO+bP21Ym3K2zp3Wzvz1vex7Qu2vp718YS3b8HaMGF9OiT970XZ9tfNP3YqpWfHsmOP22vHeL3147LdV7TlW9amCeCg7YebE0p2m/PzJOn1xF0TBrb9RdLrYNf20cVV29ZXtPa3Mef9orWrYs97TLmgZGO6Z+ejKiZWG8DJpNcBbduMbd8UkNPQVcbuw9Z/Q8yccYp97lnblbXVjZmLsSHmOl1R6VzrYrNo++XmvyHp3NG3PnNzr7vmD6BW3cni4iLC8eeOO+7gmmuu4eabb34I4A/+4A8Avr7WMUrVYWLexMG4HKRqzDa3ZnFzlrvOZctrxh9/LGhMPK7YR+Btw25zdU/YfUNrm7Ml9uzN2j9cpd3V7B3nH7//2X6vcPRbNNz5pzH9c8c6XL+HpHO9stt9JrxtWTv999ntbm51tvrlVOaYcX10uGtMDjMP+224a9pa8ZXd7sZn3D5F+vmhgJmfnD2h99odNwX1LftoU2EdSwTELJMjIGZAnsXBBgCK+S695RJTuYMcakWm3gA2RHsZUAA0E2gmWKHx8AaoQLihzzSHaFGhwIAOZdaxRI8i7T1TVOcO0mpNU6keor13iuJshwJ9DnbXsb60yIA87cNTbJjcS54Bh5imQ5lJDlOhzZCQISElujSJONyqMlVtcqgbMVVqMiSgc6BKZf0h2q0p6EOpfpgpDlGgzwHWU6BPY3EDDKAw12Uj+1hiHQpNgT7LhBxcjpgIVlg5HJrrJqBmhujHwnQdl4fcZJ+AmOfyIAdYT0xAjyID8nSHJSbCFeKFXPp5IQRVitFxQLHQMX1cnqacaxOwQo5lAmK6lBgSssIEg26RicKQcGJIhTYtqhTo025NEVSWiR/JMT1/gC5lJlihH+eZClr0yVOgj2aCmIACfZQdrwlWKNHlAOs5zCRxNwehhkUFAZQ3tuitlIgmmvQoMsEKVQ4RE6KB/Y1Z1tcWOTCoUcm3KNOhR5EeBXsamZN3uBwShDFxN8eG8l40Eyg0A/KUOcwkHXYsb2V97gAxExzsrmd9aZESXQ4yjUYRM0GBARpFgR5F+jSoERMQeIurzoEq4fo+G1jkMJP0KFClTcgyAwosk0OjiGiynw0oNMsrOTZOmPEPiOn0KhSLh02cPVJg8pQWJ/E4O5a3wmCCwmSXk3mUIQETaAKGdCnTpcQKEwwJKdIjJqBFhSlaNImI4wn07pDglGXifg4mVuDxCaiCmhqSD5bpLxco5zrEBAziHEGwwnA5Rz23nyYRk7Q52FhvPkZPDVl5PDTrEX/O6to5YAGirWYBOSDHNAdpUGfQLJrPiWfCXGE3B1hHrz9JsWDaXW4VIIRK6RAxAd3lEuVch4CY1vKUmQu6EXOl3bSpMMEKGkWRHgPy9CgSE1CiA8BhKuRYZpLDTHKYR7qnQqCZye8FGycTrLDCBDmWmSBmSMgyeQ406jC1Ar0JVCmmFHYYkKdInyI9Oz9MMSDHJB2KdHm8v5lyoZ3EekBMjyKt3jSbio+yJ55jKmihUeRYZkCeji4zqQ4nxxxiin6cR/dCSpMmFlb0BBXVpk8BhaYXFzgleJRF6hToMySgTJeQIQeZ5lAvYmNxL0PC5HzbtzxDMdehwICAmDi5CKR+WMcSTSKWCSnRo0+BdnuKWmU/B4br0a0AQpistujEJZSCwkSfFSboD4pM5tvG7/ur8OhXF7XWG8ZcYU44jueXoY/1er0zMR9VATZ3Op1PYr7SWIN5eNVdxvL3A78I3G4208Z8wI+AnweuAl4ObANuwXzYugCT0mkes3B4L3Ch3X8ucD7mC9ZbMG3ssO8jzAfJMzEfZJvA3cBrSBeT19ntrwQ+C7zMtnm33R/Zdu+ybbWBW5bhv+bgLcBv2j7dDfAw/MqpcANwtu3jbsyH07dbW94MvBN4dQMur5m27gL+B/Bu4BJ7zDbb/k3AxXZbCLzJvv9fdh/Wpr12/1vttpuA937VGL+lZi7y2P5s9vpfsf7can18oe3vrfb5bGvfXkx/b8B8EfPA/ZhPjQeAKSg/F86zx1xg6+9hPujebeupW1vfY19vtnVHtn9ujK8CHrD+/pQdkx1233uBX7Lj9gbbzt2YGPisHcur7GuAWzvw0+U0Vtq27sj2vw582R7v7K1Y296FGd9PYWKzaY+/xm7fAbz7Ydh6qvHP4iG4csqM+90doJzGzzxmTLfa9uZtfefbNm+Fdb/2KPv/9mQzxh8nPTfeABP/dJiVP5w0tu209r/Ovn4rJt5v6MG1RePHN1ifzmLi/y22rvPt2H4cEyN14AINb1XwauOymVc9yO6vP9fE4o09aBfNGLwJeHUP3lQ0r99sbXyX9d0QuLgHO4swa+vca224VsO1Cq5Zhnfn4BO2/Kc0fFnxwtd+ljsOvohTp3fyjRd+L7wbwjMPsXzDVDoX7MacpyFcfOlHKTDg48p+gjrndWbcd1tbfsqO707b7/swX4I0Mf261e5ftMdcZuPiyza+2tZvTUyM1m08XYeZP263MXGdrfd8TF/d2N6NmefOtn4+3x5zsa0rxJxLr4TKy/ZycG8N3pwzbV4GzPdgb5GJzYdZaU6a+s7FjMO1tu0rbXtnWxvOtjF2q/VX0bZ3Cyaer7LHXmzLOd+8zPZ9iImrtn2+D17yB9dzP2dx31dfSOXsvRy8dtaM507rm6btd0R6Tt0O7GjAb9eM7+7GxP3djM49zj53ri7avr8a+K+2v9usbRVb/i0aLlXm+Acwc83ttpyzPbR2Xafhh1U6Z77Tjldk65oFrl2A+ozZdg1mDt5m7ezZvs3bOt9sPyXPYc69pu2L+8HuAevjirfvXXZb0Y77IvAx299zbX/d/OP8/oDnrwrwh3Zsr7Lb92L8erbdf6u1sWh9eY191G0bn7X9/5Rt52JMzBetHbvt9utsvz5r236drX+r9fGsff0x+/5dmGvfVcAFy7AjZ+qbhekzH+PA3hrssPPRBdbm6zBzeNP6ekg6t261fp7twX1Fc958GRMPi5g54y/s8w5re4ip/1w7Zg+QfqF7yzKcm0vn2tDWt9X26ybr7yZwOSamd1q/tu3rc9PX87VtCP9v8Nhjj7Fly5bk/ebNm7nzzjvXPkjNQ/mu1b8gHLeydrHk5ix3vmfLD/n2V+Zu/mgDbQ1VZba587Pn1V2x7/0f7rHHDq29vn110rVhltXsHecfv//Zfrsfc7Ll/Tbc+Tf0yvptuB+2FjE/QLgfiduZeiveNvcD/ji7h5ntTcwPMu6HJr9cmDlmXB/dPvej0gzpmty14a5pfv0+oWevO87NP+Nsdz9iz2HmrJ3e/rr32tV5GfzYH/8Jt3Ehr+ITrKPJHuaIaLKLLbzvkV8C4NRT7uUbj53F+Ztu5p++eEWynrzyineyk1MJGFJgQIkO1//0G+ECqP/it7iUm7mNH2Ceh7k7fj4/FnyC+zmL2//XS3jh2z/LFz7/Ms794X/m9ne8hFN/8z84jYf47Nd/nEu/+2/YxRZuv/MlXHneO9nCLm7mUu6On895wZ28iDtoUGOBGc7hXj7Dy/m3L/4QL77o//K5r/8Y53/3P9Ik4it/exEveO1NfPHzl8FOOP2/fJlLuIXT2MFHeC2nsYPrP/BG2Aknv/0efpm/4BO8mjx9TmUni9T4zMIrKFc6tO/aYK6bQyi8+QC9t643Y3ABsBlmznuQKi0+w2V8hNfQoso3OZ2HmefexvdQjVocePcmMw52LRGefYjlZpVTT/sa8+zkc49dztmb7qRCizkep0qLezmHBjU6lHnknjMpb91PbbLBi7iD27iQ03iIL37xMirn7+XgG2a5+IMf5W6eT5kODx+c58Lpz7OTeU5jBwMKNImYZyd5+pTpUqbDOdzLR3gNd8XbOHDfJqj34LoiRHD2L3+R+w+fxaWTn2E7p1OmyyXcQpOIIQHvuf43uPRn/4aPPvLTvOCUz/N8vsZ2zmA7ZxAwpGnvmGgs1KhELQ7eN8uVL3gnAwoExOxiC9u4ixdwF1csfJpLZ/6ODiX+8Z6f5NLn/Q3ncC83cykD8rSoMs9OYgJOYwdn8E0+yOtoUaVKi9D+wvOVv72I+mu/xX/h/XyF89jBVi7kX5lhHzuZZw9zxAS8nM/wV/wiefo8fniO10/+GZ/gVVRp8R/bL2DrGf/OIjX2/sJz+J6//gK/ze9xxcKnWdk5ycnn3cOf8yYWqVFgQI0Gd3Mu93IOHco0iTiD7SwRcRs/wCXcwif5UVoHK/TevJ7p9z7GgZ1zTFQ6rFw7CRdD4ZIDbJnexUMLWzlz5mu0qLLr4Baq0232P7aRKza9n0/zCl7El/jH638SIihfsp/2tRvMmsrNqz3MGmcr8C744c/cAMAutnA5N/JBXs8j/3im+cz3Kc0bT/t1/o4rue+hbZx6mml39+efC3V4wfNuoknEvQvncObM14ho8oXHLuHFm27kc/f8GG983pu5jQup0qJPgTPYzi62sJ0zaBJxLl8D4A5ezBx72MZdnMedXP31D0NlmZ865R0ANImo0qJFlZPYQ5UWDersYY4brv8FJi47zMp9k4TnHuLs2td4lC1s5SHOYDvz7OQWLkli6Szu55qH/pAzT/sSW3mIPAOqtNjO6Xxh+8v41TN+hd89+DYunP48A/KcxB52sYWv9rdxXuFOTuMhCvS5mUt5+OA8vQfWc/p5X6ZBjVa/yoWF23iI08gzYPvB07lm+o18kNczz8M0iTiHe5lhHzdyOZ/b/mO85ow/YIEZynSo0uLdj/03tm66l3l2UqVFhxKxneDdDxyv5hN8mlewj418F/ezk1P54pcu42Uv/ks+0ngty7dMwSx8z0Vf4GsHzyUMY06bfIgOJR585Cy+55TbAPi3v/oheIN6ZMzV5YTkeP5N/tPnnHPORfb1+cDBBx988OC4gkqpq5VSdyml7jK38wmCIAiCIAjCU8/73vc+tm3bxrZt20DLOlQQBEEQBOGZzlN5Z+hHSe9d2g38LuYPIWDu57ixVqu9/qKLLnoN5j7A1wMvwfx5cQSt9fuA9wEotU1TsbX2vDtx6qS/CLv32L+2uTuN2qR3Ivm/hLtjiqRl65l9/nPF25dtt0d6J2nk7St69brnIsYlba+uWewvu1NpWVf3bq99/+5M1tvnTP9du+6OhNDbVgTqyxDlUlv9O5NcuaSNKaB0ZP/d69Ar79px9fo2u3Hw7aNk/MCMeXb2z3rj4O60dA83Xn69/ni4fZszfvPH170PveNmM/X4seX3v+IdVxzTZ+c3dzcUXr3uGDfus3Z8mErHhqm0rPNJ1qf+czJuGiJFxBL7KydbPyxDO2fuCnNnvPOPsyWy54odk0rUoh2ZX4CZJb27xPnL933FxlEFivUlepX1ZvsQIprsqR9mpThJsdJhEMashOau1ErUoh0W0zhwbVgbK1GLdrFofiG1x7AVKPYhLDJRHLBSySVt5aIWy5Up1tGkUDS/+Dmbq1GLA9FU2h//fEjoAuvT+Bracpt75IoDlntTZswWSe/ecPHifnX1x8jdMeji3cVL5DWZHVc/Ft2dwDtJz2M/zudJ78CcNdVVCy3iekC7siGpc7re5OCwRlRv0gpjlutTFGcPmHGaz7Qfee3WTSwlfXXj5MdN9tx2z03SO4tsuSotMyazPaJCk4PF2dFYgiPP3bvt9llvu98ujN4Z4+agZL61ffDnjmTeUen84vzrz9P+fIY6cv7264oANh45L7hyPdI5zNXnx4cfG66s64+7ayl7/ruxd313seji29kym6mrwmiM1xkd+1mvzqxPsrHq7Hfx72wreuVDr81sW37M2HaKUYtefT30oLj5AFHQhFk4sGjvnqn3IDLzxkT9MCu9yTSO3N/t6xqKfdbPNjiwc5PZ7vofpm0l9g0Z9U8Fc1dncidXaMqfi7mzAq9cEXP3hbs7NNuvMPPaP/+F486mTZvYtWtX8n737t1s2rTpiHJXX301V199NQAqvy0dT/9uy7Vw5zak57S7Gy97N+Q4VvuLeraMm3fbjM5h7uFw+/zrr3/Hqr/N2d/03he9sqv13/Uze3fkVsyd19nj3Py/2h23zl7/9Tgb/PN4tfr8Navvp+ydqH7d7g5Tfw3cG1POvR+3zT1XSOdpZ+Naj3G2+/U7X2Tbcf2rZJ7vI/0niH+nrr2u1GiwhV3UaVCjQZ8CEUv0yVOMWoRhTJUWlahFjUb6OaMOM+yjRRWAMl1z3bfz4jqazLCPKi1m2EcUNIkwDypmv2l/EepmDTnDAkQ9Ipqm3rpmhn00qDPH4zwUbKVGgxqL9MnTp0AVa1cFNrLARP0wG9ln/nVeTosAACAASURBVDZdx9RpbXZlnV0hcXLNm2EfW9hFjUVCYmo0GBJQjVpEhSbtaEPiw2i6yd5offrZJTL2V2lR7zeICksEDKnRMHctRi2ioMmBaNPItbZWW6Rb6XASe6jRYLrepEaDiCZ1Fs262uqYfR93cn3lDPLFATMsUGORGg3j8wpmrVWZpU6DGRbIM6A1XWWGBVpUqduxBeNzdyev8YlpqxYscqBeM2vJ+iwUTXxUJk2ZOXvXWt7eCRkTwKzxcTFqJTbn6bOFXQysTl1MSFwPiIImB+u1EVs6lJhnp/H9jInFXWzh6uf9CRFNXsQd3Mn3EROSZ8AP8i/cz1nGXhap06Bg7/xLKEKdBnUabGQfi9So0uIsq8zXoUxMQMRSsl4dTBbYwqOso8lG9vHNzfvt3Y559kYmduZ4nKje5EAvn8RS3mpRRCwl4+G21WgQEFO3YxWxRFwJ6EUQBU069RLlSpcD0aSJoekm62gS1ZvUaZjzqtIlYolBPZ/UUaWdnGeVyZaJzWgZKqH53NQrwPkqOdc3ss/+Xb/KHHuoscgjNg4rs4s27hvkohZb2MUOtkIEE7OHiTCxXI1aRBj7krkgWmaOPUQ0KdGhRJcaDTqUqbFIwNCc72DngQVqdkwm6ofJF/tUaREQ27+LB8kYzbOTMl32MAcR5It9evVJqlGLjexjSGBjoGHjYJEmEaeznRoNivWl5O7qKsb2BjVy9UPMsI/6tIk1d240qLOxsGDnhwUCOwe0pqvsjtYn/QkKcXKul+hSnW5TY5Ez2M5GFijTtXOfqauyeT8RTfoUCOxE7Z/nEUvkqVq5EQgYUqbLPDvN3IWJ5TZViOBUdhLVmuwPp2CvnXem25TpJI/H600u5Dbu5tz0s48APLVfhv7kUfbrn/iJn7gK+KZS6hWYL0E/gPnjqCAIgiAIgiA8ZbzwhS/kwQcf5OGHH2bTpk187GMf4yMf+cjxNksQBEEQBEF4inkqvww9KlrroVLqTcDNGHnfv9FarylcLwiCIAiCIAjfKWEY8p73vIdLL72UOI75uZ/7Ob77u7/7eJslCIIgCIIgPMUc1y9DAbTWNwI3Hm87BEEQBEEQhBOLyy+/nMsvv/x4myEIgiAIgiA8jRzPBEqCIAiCIAiCIAiCIAiCIAhPG8f9ztAnjEtAUAS2zoyKlDuBd5eswE96kU0s5BL5+Elh/AQZRY5MqDD02sA71iVIcQLirvwsqSB8NmmEn0TBJdVwQuZ1YG8ttdVPUOK36ZJUoEaTiWSTC/lJLLx6JooDVoo5k1yigkk88RbgraQJL7CvXag4MXe/T0Nvm/OLa9/1Z7N9PODZkSROmcIkr7EJbCJb1vnUJRlw7dWB2R4sFsf30++/n+zEvd7r2erbUszUk01q4pKe+OX2en332/cToGSf/aRLrk87AHJHJiyI7HbXXn1MWy42KyTJhQoMkmMq9SZtIvjDHPyUN0aQJG+YqHRYKU4m/QvCeDRpgDsmtAlpPD8kcVSBQnFAz0v6kqfPyjCAIlSn27SAnkugNNmiXdyQ+sWdKzb5QzTZpB3VKVc6tKNJs78JGzbtY3/xZCOc/boy3KSgDXO1PTxSmSLPgHxhYITyi0BREw+D0WQE3hgaAfa2bbg2mvRmaEStq4UWuzeXTCIql5ALRhNuuaQV4xLNuHPcT/yTPaf9c9g/v1ximDpm/Ba9dsEkQivmkmQDw0mbQMkmmIkKTfpRnplggaAWsz+aMkL/xfU20YwaTapj7c1FLZajqdEEOpfY9qNlqOdG5z1/TssmYitixsUKyCf99OcmF2N+MqYiJPHvJ79xse8nfXNJybaSJCtySbWOGA9nq9++n/TGlXMk49GDsDh6TJJAaQiV3Pg5yMbuiGi5OwdnvX2R9a9LtlS077NzlIs332/O/myfxm33r2mVTFlvHp+YPTwyL4zEZfa6lE3W5CeY8v27CFxAmjgqk9yjOt2mV1kHRUUYmiQDgyDPgYpJ8laJWrQrxshq1OLg3klvLtSwWUFPMb25STVocSCbNCm57tu5zPnQH8s6Zk6u2/LYRGLu2uHPI+MSM2XPad/fRWAZ4ZmMYjQGjhV/jeQnrHHbxyXvIbPtWNooWiOzCZTca3/u8K+7rj8uqVMvc1wx895PvOOTLePj6hyXNCqZ88cc5/CTA2Xr9/uQTUCUHafiKq+zfRqXnCnrr3Hl3Lq1mNnmnoscOX9kx2m12HJj5Mq5bdk1m59AKbt2Pd+zw+/zTnPIHHv4JqdTpmPmYPKU6DCgwMz0Ph7ZfibRGU1qkw2b/GjZXP8qJhFPgxoAFVqU6Sbtu2RJLtlIxFKaJKkIJVu2TDdZ00Q0ma43KdAnokmxvkSePjN0aFDjkYfO4EdP+2SS+CQmTBLpUDFJWlxSli4liKBEJ/GNn0ylRsMka7HXyzIdajQo06VsEwt1KJEv2OQ89rpkynbTeT6EiegwEU0CYvK9FfYVZphjD3mMLeWgkyb6dLEfLZt6Cth+LBHYZFW+7+o06FJmF1uYqHSoBYts9JI9lelAxazBzZg0TGIXWjapTSfpd54BA/JUaVOgT4kO62gmya9m2Mfj0Ry1QoODldkkMdQ6a8cutnAaD1FnkT4Fk0ApNGNfnW6bZ2v/gAJlOsQEJmFREFCmw2+d8vbEloChtcccZ8ZgkQ4lkyQIaFAjoklMmMSFs2mdS8gFif8H5JN4cgmH6jZpVkAMwDwP06CeJNKKaNKhPOL3aDKt+8GKWdeW6ZAP+kl7EUtJH09iD48zxzw7WWCGAXnblzJ5TNKrKi0GQYEDoYnFciVK1y42fiPMeqZDmSptasGiOa5QSOJphoVkrbWOJntDyFW6ABSKfTrtmJUwD8MQQmViBHMuVGwbbv1SmuwmfXFJklxSrnKlQ0STkHgkNkuTXeoskqt0bQKjBgWMX1yZGfZRYEBIjEsIlmeQ+Ldc6VCZbCW+WWCGiCWbLG0PZTqpLVG6XiwHZnuTaKStio0fwMSUTWy0kX0U6Cd21mqLybnhxi6iSZkOBTvGyblsz5GJ6DBlugwoELJA3c57AXEyr8UE1GkQE7KRfWxkX2KPSyplUr4NqBbceWJiMyRmSMCAAjUWGdjEcC7majRoEpGbPZT4ZL/9bDN67jQYkKcyOTqOQorcGSoIgiAIgiAIgiAIgiAIwgmBfBkqCIIgCIIgCIIgCIIgCMIJgXwZKgiCIAiCIAiCIAiCIAjCCcEz88vQCjAP7NCpzpqvMedrKmZ1IOuZck57x9dfJFNf5B0frfLe1xl1+7P6o75NRa+tczlSYy2rpQajWkjuud5LfeLrs43zRWZ7NWodqQV3nW//sqdblANKR9ofZdr1dQCdv8HorvUYta+eqZsp8+zr0vk6odFyqncaxub9OL1QMvZlfVnEaNv5/s72w9fC88fAaSS691ktvqwuk6/Ll+hG6tF4S3T6SuN1CrP9yWoAulioLFOJrKYLnWQMKpMtKvVmctzczJ60fas9mS/2R8YtKjRHtXizGl9ePNVmGknZeBiY13XIbTtESMx0vWm1opYoV7pJXwoMbHv6SP2szUaLh2Lf6Jd6+5zeSqE4YKLSSfofECcaT/sfOcloQoVAOPRs7qWv7SMmMHpCVO1j1N9RoUmeAUXr22Sc68Ds8nh9RD8eN2diydeMHRcvvmblyLmv4RZvHOrLEGmjCWTrKNNJ9bkic45XrW6T06Ih0tRpQAjF+tJo2945XY1aJj7889GWyVW63jmjvRj0yvl+xvgZgKHR4jlizH3/RN7xTB15rq42n/rnagRRrTk631RI++Tq2uyNle//7PlZMfqxVIDNy0fOq+S8evVoXCRzmGf3kCPHvpK1YzmNF2er87WzG6/Pbt6uYOYZbB1+f53P6r3x/fV8lVwjnP2+//15Mxuz2fkwqbsHZ5LOidk5NjJaR7moZfTWJpuJnhTFPsya+czVFxWa5DYfSnw6UekY6db5/eQLg0QvbaRv1qacO5+z8ZPVQsyOz7i5PltHdj3ifOJrsgrPbLL6jk/k4R/jx8M4ncvsmu9Y6i1m6iuucnz2OnS0/ePsyR63Wl3Z/WuVO5Y6VmvbnaN4flirb1m/+XV8u3at9n7c9mMtczSfrdWvcXFy1yr77Nzs9PGclmCVFnWrr2f0KHuU6JCnT41Fsz4qAhVNGac36PQbl5L5vWQ1SMtWm8/pUubtetCsUXpmzh+aNU2JDqVCh41WyzEMY+Z4nBJm2/ef9i9WB7Lp6X8uWQ1PU1d1uk3ZaiM6LdXK1v0QkegBOo1AXzvR6ZlGmGuRqb+d6PEV60vJuBTop2Ng119OzzF3AKvVaXxTsfqETvvQX1tVaRF6uoNOW9RpIW609mxkH+fxFbN+B7awiyotoz+IuYbOsA+K2PFbpEA/0fqMaHISe0Z85rfj6zNWJlsj+qbJuNBJ/OG0DOfYA3WjoVqgTxmjIen0OY3moulnQExAzAwL/CsXEjCkToOSbXvLwb04fcot7OLPDv8KAUM2ssCPcCMRS9RoJD6p0eAk9hDRTPxrXneTdanpZ5uAmDIdtrCLLeyy+5rJsc4HczyO07d0dZasJiuQaD4SGu3Rgl2zVGgxoEAFp/XpdByNtqY7x9x4UbFaj4GJG39N5B5nsJ06i9RpUKfBuXyNOfYkY4DVTs8zSNbA5UqH0mTXrOf25kzMRp4WKW3qNGyuCfO5v5z0v0s+6Cf9nogOU5lssc76dCZIzw2n3VqudJLzo2TH3tkfWK3Qmh1jf36JWCJfHNj5Y8nb3kzaKNmYcp8hqrSYqHQSrc88ffL0R46v0uJkdnEW9yf+L9jzskA/ed2iavvVGWnbt6FmfV+lNaKl6taprr0CA9bRZJ6diS8ilpK5pDSZahDPsI/T2OHp+LaT+HFtuXnSxbmv+Vso9pO2Xcx8+uDLkzm2xmJSxy62GN/5n+eFZ+iXoYIgCIIgCIIgCIIgCIIgCE8Q+TJUEARBEARBEARBEARBEIQTAvkyVBAEQRAEQRAEQRAEQRCEEwL5MlQQBEEQBEEQBEEQBEEQhBOCZ+aXoUWMAPa5Kk1QYwWxXdKCyub9RyYsqWAS+bjXs6RJDfyEH/OMCtA7/MQXvlh5ZLf5iTiG3rOfZMG1kxWm7wG7SZOtOPv8JCHOHpe8A29btk7PFyN1eEkdSoXO6HF1r66hS5KybH2bA9R48fpxSSL8eiu2X37iGN+vLGMSKIWjbSTi9xoq2kvassyWmV1MFAdHtt1jdGyGmPF0Y+0n7XBJPPxx9PuVTQAS2n5USGPEL5MV7c+OiY2HJOnPrDb21pdNfeRG/eKSkrh6/Hpdu27cKkAYm2RDvh32vGg308RATqg+ITTJiPy68/TThC8uliuYJCY9z64mNBtRYt+WyV1Qh/VnPkZUs6LXhQH0oOWSEw1d95oQ2kQmvr8qaZmJMDbHe/aWPeHysvMlmCRIEQwJyFW6tKma+itdk9jJ1peMXwhEJqFSn7xttJSeg9YXAbER0J5ujvpj1vg8iQ0/yYqfPC2EiejwaHIffx5x5R3+/OLieRYjen5VL2ljojiAcEgQDk3SmDP3E9FMExVZHzmR+DwDmzhpOLI/ia1h2hYhBEFs5kusHV5ymKiWJkDKRa20H87ubDIaSMS812/eZ2zsMXq+ZucFl+jIJcJyjJt7xu2rwKCf985Tbca9bfffZMvuJT2Hskmw/MQ7FUwsDhlNpuWPnz3eCdSPzE+QzrFF0vnQv1YsevVVehSjFhOzh49MuuXHnLv+uVgrDshtPmTGpUc6T4bADSTzXq44ONKH/nUgJE1e5vsVr71sko9sAi0YGbuKO9dD0uRjIRDqJP6qtJLzdUhAjUUCl4itaROv2RA2521qTzUyCQQ67XIiTk9xOTXA+a7ozR1uDLx+J+XqjJ6Prg7XBzf+12Hi1SUKHJe8JGLEF8IzmAnGJ7XJbvMffvK0CkeeV0dLjsMa+/y49cnOqWHmOYtfT7bs0dr2H8NMPdnHTkbnwmy738kj23Y2md9qfhvnq3FjMDyG+o7mN3/7cMz+7zQOVuvXavWAmbdgZOyrmEQpQ4IkaUcBk6zn7E33JglWCgzS5JheMo8SHbv+6CZjUWBAn0KSSMWtrwKGyX7XNpFZr4bEhHYdU6ZDZdIkJNnGVwGSRE4hsU12s5QklMkVB0miFJPkxSSWOZWdRJNNgCQ5jqvfbjSfk2zdLmlLnj4BQxr9GiW6hGFs5v4IajRGPuflCyaJyiXcAo00oVCBATEhZUwSFf/cD8Jhsl5zfijZ9y45zIACMQFlm4yqUBzYRFVmPEySogHlSidJIuiS12xMkhgNkrViPkkmM7CJaLo2OdVCkvxmHU0z9vYab5JZtVlnkww1iRK7N7IPQpiziYxcYpuylyAmYJisTZ3PX8FnksRYzjc2JxERTQYUeNvk7zLHHgoM+E/OoWD9EtrETHn6xITk6SeJmAKGzLBgP//0bfKoJTayQJUWA/LEBMywz8aJqc/5341BhRYx4Ujiq7KN8ZiAidDEejU265wCA2oHD9qEVYtJnwNbt+tXkpTMjkWakAkItR2X7kiCqEVqBMTsYksaR45wSI3FkfmsTIcgjCmeeSDZ5mKqahM8mc9k5jOkS/rj9ofW7nyxn9jj/OISQNVo0Kdg1smY86HOok1o1EzOw9jOJ2W6xIQ2yVaHOg2CIE6SaBkf9r0ERktJOy4WB+QJrN9dwjYTqya+q7TJM2APcywRJTHvkh65pEwDCpzGjiRJWs0muPLPP3eOBHY+Mu12knnBnVPOZwExTSI6lG38d+mTT/xQo2HGCWgSsc6258ayTJchAQExc+yhQznxiZsnwSQVTRIoRZCbP8Qbpv9qZB4xn/8WeRF3mHEWRnhmfhkqCIIgCIIgCIIgCIIgCILwBJEvQwVBEARBEARBEARBEARBOCGQL0MFQRAEQRAEQRAEQRAEQTgheOZ9Gars82bgbqz2on3v6UAOh4Gnj6NTrSZfhxNSzS9fM8xpyjnNt0QvkVH90dDbN+vV52tJbvbej9OhA5jtmTLzts4e0CTV/HH9cnpQi7lEZ2i63kztgBHtuFRzk1E9TGvPOqzuX6hHNQNt+ULRaAOZukvmtfOp35ej6XaOs6uynNrMeqBLKsaU+noiOkyxvkSxvpRovE0UB3QoG3043xasD53NzibnS1+DM6MFmPgnHLMvq9vq62n5bWX1R/1Y6aV1rzQnoaIhHELdarP6fvL1+Fxc1e3Dxa8r5173jAZgqdCBIZToJuNZpmu0+q4xVXWyY+mfC/Z1gUGq6ei31yymPrX7arXFUT08+zzop9oo1E28VYNW0mbAEEKMPqA39OZc1MywQBAa/ZjknJy12qNWy7J93YbEt0OrQxkSEw+DRBclHgbkrW5iudJJ9XkrQC9VkjIN5FI77PjVrXZMma7RGHX6txWrgejix9eE88c+slqG2XlgJKasrqEfYy6OrO8LxQHcVDTnjiVX6Rqtrgq0F01wJxpUQ6NZ5bRrnOZXMTKaO4A51o2tF7u5+iGjseS0MN15c5cpE8dBMgbLvbx3bi8fGQv24XTCDuytJTpWCeP095KYKKfnefY8zOpo+mWKsKWwy9NGtfPZuZjz6JW23JmMzllZvVM3N2J1zIp2LLJ6oKTHFZwep68RmNX59PWr3bXlXPu6bfRtC8UBK8PAzNEjftLpmPTSY1ysxcPAxPo85vj3Ox/pZFwKxb7RhE5irTeqn1mEfNAf1Z6rpPuM7b0xuommzun5vaNxHEFpspv2uZlL/DNR6SR2BQwpV4y+ldHzKlCgb/RLe6R6W3Zuq7pzsAgHFyPY3CMI41QXydf19TT0qoXWqG6vm+ucfUNvG145f64H7zrmbfev1359/rVKeGbja9UPM9uyD0cv8+zrRfrlshyrfqRfp79O8eeprB5l9n3WLrxnMu9Xs8Gfw8fZfTaMyJYdi8al/xin2+nI6qb6a8+1fJe1fRz+nL4aWXvWamtcn7P1HKvvx7Xtl8vG6gWkvspeX4cww0Ki+bnODpbTT9zIPvbEcxQYJFqaQKJFXfY09hItO3v9cfp3bl3iNEidrmhEM9WWHpJo8rk1TJUWg7hAnj47mbe6gC3qVttyQJ6QONEAdXWbOoaJVv0iNbNmrBs9vaGtJyCmQznReTd6jF1KdOlSIrJ6hLVCI9FhdOdzTJBca3KzhyjTYUjAzVwKmGtbngERTWZYSPqeaJpXeoT2ulelxanspGC13p1uY5kuc+xJtDxLdKgWWpToJv1zfsoXB4m2oNMxNK/NGNVYtBqF3WSb0590eo5O57JEx3yuCIGKttuHiZblOdxrx2ExCT2nI+pix8VEYIPQaVI6DU5nn7MhIEb1SOJrhgXez89TpZ3YWLLxUbHHm5hbSrRTjZ6lXaxWlqnTSHRou5QpWX9uZCHRskzraSYxmvq/g9MTNfkVUk3GpJ+9Zcqx6VvOSnQaXVqjYWp0NM254/RKK7SSeCvYsaBuNPlTv8WJ5uUZfDPRFvW1PV0+gZgQKhCxRKlgtXuDDr3FdWbdP09y7pTpcAbbiQmMlnwFT+PTxJB7X650E61XF2/OtgF5o2FrNUAL9BOdy4AhF/Kv1Ggwx54kfpIYJs0d4bRr3bg2WccMC1RpE7HEH/PfTEwUNXPsMetd6wOnh+u0RZ1O7RZ2sZWHPA3bATMsJHkWkv5ZDVSnkbqRhcTHgdWldXblg/6I5m3Ekj2PTFzWaHAG29nCLjrqPVbntzuiL+piysW401B2tsywjxqL7GGOLewiosnF3Gr1Yxcp2XhJKGpqtUU+xpVJXeuspmmHMts53cRQ9jr5bbJVKT33HT6UUjc9OdZ8+6x1SRcEQRAEQRAEQRAEQRAEQaAD/OJ3WMc1oymEjwvPvDtDBUEQBEEQBEEQBEEQBEEQvg3ky1BBEARBEARBEARBEARBENZEsbqazbE+jtqGUkWl1FeUUvcopb6ulHqb3X6dUuphpdTd9nGu3a6UUn+qlNqhlPpPpdT3Hq0N+Zu8IAiCIAiCIAiCIAiCIAj/L9AHfkhr3VZK5YDblVKfs/t+XWv9iUz5lwLPtY/zgL+0z6vyzLwz1CV5ON++bwKLjAjQlytd8yI0AsQjAuG+CPswU6dLmNBL6xoRJ+9ljvfLuPcuoUI2KdFaX4P7gvBDAC9hhm8DmCQYtly/lz/Sxqx4u0vY4PpnX+cZwNAmrxiuUg7gYoBlY1M2KcBqAvRZ//n+6QG9NAlUynJqr024sTIMiIdeoSJJYo18YTDajktK4o9TnTSJlStT9/qQFfl3ZWC0P0WvPrfNT5Yxzh89b9sQk1ikR5osJ4xhCEE4HPWDG4eI0WQbLqGR31bbswcYxAUIoUspaTcmoN2swrXm+JHkSG1GkznY1070+giB/aIeTfzSg+bByJx/nv8O7NhEqdChRdWI1C+aOltx1ZSpwx7mIDTjeUQChWKfBWaIhwHdfnlkfPKY5DTxMKDyuv3J+BTs9hZVajMNmkTWvzGtZhV60O8VjkiQ0KVsyjIFlJLkSC4uAuJE6LsStVL/z3q21xmNIz8+rK1HxMq4JBX+OeGP99ZlDr55lspV+5N9QWhE3OOhSWa0fvO+NLmMPZ8Dhgwwc0SLKia5VJhsyweegL+zG5MUqUM5tdfNi5eYMrVgMUkclisOvD6FaUw6bLItI6ZeYiKMTcKqJqZsNtmI78MiYMXJR+I160d/biF9v8DM6vO0myNc+exc5ievs7QwcTRX2JPWN2ZezxcGRyZPGUKSCMnV78Y4BPYCD9iydTO++cKA9bMNk/zJn4fCYTqf2FjNzR9K4mJlGJhkaq6+EDNer1SZOSXn+cwI8/vJTxJh9mxSEduvnEsUNfKIoW2TGbVJHz2bgMq2NzF72NowWueAAsNhYM59ykkSCncOxQQjtsTO7p5NKNguUqst0iQiJmTCzrNJ3Ngx7VNIx6fJaKKkNrDVjENyTcmel9lrhostL1le6mdGr0HCM5sV+3wsY5ldV3gxmLz34+dYGDf/Za8n2Tazx/rrE3/fuPVtto61tmXbza4Zh5h5aZxKWGYuGEt23s6Wz/qhOGY/jPeTf/xqZK9vWbLrwXH716ovu35fjXG+yl5zxo1jOKZs9vpp11H7mKFEJ0l+2KJKnwINamwNdiTrjQ4lgsAmuAuHSZKXAQViAvrkbWJYRUxAkyhJHNOnkCZPKUKfPMNhYJIsFs083aKatDMkoBq0aFE1iVRpcQcvZoGZJDlRhRYdypzF/RSKJklNk4iQ2CQQHUKSBKdJknSpTz5J1LTSMzZ3KBETMCBPjQZduzZ2NsXDILk+usSUDNPrUkSTl/NpXHMxAS2qNIns2jL2xsEk+3R9XmAmSeg0sLaBSf7k1ugFm/wpYEiBQeLXKi2CwNYfOrOCJPGNs6NBPemfSQVTMmt1qrZcaMaCNOkpQ0VokxO5Pu1iC2DGax8bAZhjj1n/AzEhHUp27AuJPTEhMSH/k2u5mUvpUkr64PrbpUQJ83niV/jTJAGTW8e2qfJVttGlTEzIJ3h1Uoezz/g5TBL9uLEJGLJInSZR0kZMOBJ3rg8lOnQoJ+8dA1t2ZWhju5hjKbD9nMT6Mk1+5PzstrskVs7HMYEZ30VYblZtlATJ55AqbR7iNOu7gJ/4+mcIiXFJZFdsQiFnm6t3SMCG03Zhg4gynSSeF5gxMRMOk8+NfesnZ3OLKoNeus2MjUkh5CLHxWWfAkObmMj1rWvjeJG6l6RoaNZ8BOQZJP1355hbA7ptrg4zpyj2MEcQmnOkYJMSDcgztM/Gl6XEp2YOM31oEtGhRJkODWrJuLu4c7b1ydOgRkSTN/HnDGz6q9ieWEObCGlgjx2Qt34p8ShbaBKxTl9NlZYXAyXy9OnjJ/E1n5NcvLq5smvnuQY1GtT5Ki9IbAgY0jhcs7WY87R92MRwiQ4v4C76FJIkUK6dJwuFSTv86x0DsQAAIABJREFUnTyOhja4K6U7TK9xyBXAh+xxXwYipdRJa7XxzPwyVBAEQRAEQRAEQRAEQRCEZxp1pdRd3uPqbAGlVKCUuhvYB/yz1vpOu+v37F/h/1gp5b7p3QTs8g7fbbetivxNXhAEQRAEQRAEQRAEQRCENXGaod8hi1rrbWsV0FrHwLlKqQj4pFLqbOC3MP+pywPvA34T+N/fjgFyZ6ggCIIgCIIgCIIgCIIgCGvydPxN3kdr3QT+BbhMa/24/St8H/gg8H222GNgdTsMm+22VXnmfRk6waguF1gtQ69MiNGvsfuXe3mj3zXEPPuaOe44p+nl6zBmdTh9HbmsxqGvB+j0N7PbWaWevUXzOrJ2RECkTHlni9/fnkrqq063R/viyvn+cFp7Ga0qp52z4jTXmt6xs85HIdwCUErb8fH05ZJ9TlPSL+vrrmX9irKFc6M6a0Cx0qFc6VCudBMdviAcGg2QOBjVOvR12vyx3EuqA1XXR2otOVt9zTjnt6wel//sjo+847LjPI4wNpp/tu1CVncP0n5VMq/duPi22PFdbtsxajKiQ+Q0SrjG1NGKq0eMYzwMzLazzfuA4aiu2BCjC+r3zepiJvq8PaujZPUB24eraQNFo09TDVpJXXUaicbUERqmO4uJ5k2iy2mPK9BPtEbbzWrSdp88tE3f4zhItEWjWjOxu1DsJxqW9IBFxcPMW22gKZKpubicxENAzDqaFOgbW6K070EYp+PjtvvndtSDEPLFQdo3p0Ponzt7c6lOqfO30x3da7e1of2xDUbnsW3nNeefnhnDROPH01HNW60YoxXltHDMc6IJmdFJmwjjxM/Jo7KclG2yblQP1bU5VOmc5fxsx2cPc7SpstLLs0gtjaFx2mW+hjNlqPQyOqLec/YcJbW5zmLi4wkXR0U9Ovf78ezeZ3XXLAFDKMKe/txo265clDnOnbfY56FK97v55UyOvC7ZkI3jgAN7a6meZ9LPGKc57Npabpe8uSvVM0qO2wt8djmZD/u9gtEwtcUmil4s9Fx/R9sY8VXPmzew25oYW0OMvq7rv4sVT2trZfdkctxKLz96zQLyRaN/5DS8Ou0yRBhdpNDZZxu31658wfSheTAy+lcMWVmcNOV/3qs8O3f7OuEuNnaTrgt8rWbnCzLbfJ1q/73nrzW1EIUTj3FzH5lt4x5rkdVUzrY1TmPyWO3Ibl+r7nGsVd+x2Oevv8bZMq7+J7JtnA+y9q9Wz1rl1yqTXSdm55Rx/sjqpq423q7+zPV91fo935bpsJGFRFMwT99qG3ao0WBHvDXRtivTTdaModUyd8cETlvSrZmsRqLRBzSagU4/k56n5Ql2PTe05fsj+9bRTNYx53GnWa9g9DSdVul2zrD6o2YdZLoYW9sGyRo1b3U3y3SJCezaJ/0M2aGcaGw6nc8ZFoybrWY7Q3t9s+v0IDTlupS5lR+EgJFrcmQv8ommZ/j/s/f+sZJk133fp6fqVVXXdO/07nucNzuakUbWrpZaLh1KpkPJkQjJy4UMQ5YUMUCgyMlKSrAxYAORbRmmbSE/YMXZIJZFARZgLKTYBBTIMeKAFARDgshEkBOHjGmTEak1V1zKY81qZt7wvZ1edk91Vb2q6fxx7rl1q16/N0PucnZm93yBRnfXj1v3nnvurVs9bz7fBuKWtum4ntt+0Y2Lc+Hbfp6rVCS8xBOsqpyclSMp1p43qDGRusVBW2LfbzmFZw0mVKTUnOcqUxY+jiq/XpzIzVJZliDr74m7557lBjSy5tP8CWOv58QuF1J3XW1nFObAV1xfMWWbA/4uf40VOe+68gd8L7/t17h6Tk7Bf8z/ypgVjctPbR9x48tPqP16+TxXmTFnzsOeW6lrC41T7Pqyp1h4phENdZVwKpZrNVHE0vFGv7K9xZiCPc4iHMvUj4GxW5PPmXk+abifna7OqsJ5HJzlhl/T/5N3/TkaIu+bsJUJO5NG+lS5rCk1X37pG+X3ETfOE2oKcs5ztfOKIBynle8vYYq2Lj5SakUiz0aOmTllgbJp4yAPV+Se57vLnu8HZVmGOZJQU5Oyyx5TFmxzwFXO+/km5Lme52qPU6zMz9hnp1xDx09O4XNtxty1IWVMwWUueTarcHpTHxPtA50Hde7SSOg+jY1eV3NLc1C4obGLjdRy5saz9kUonSu1NQmVz2thqcZsnz5wYz6GuGF2es5/yT+gJeb/5X2euar9AjxQa9HRaPQO9xehjEajMfAM8AXlgI5GoxHww8Dn3Sm/BvxnzlX+O4HX1uv1tZOuYf9N3mQymUwmk8lkMplMJpPJZDKdqDfov8nfSY8CHxmNRhHyJ5H/ZL1e//poNPo/RqPRO1w1Pgv8BXf8PwP+LPAy4sD7E3e6gP0YajKZTCaTyWQymUwmk8lkMpnedK3X698Fvn3D9j99zPFr4C9+NdewH0NNJpPJZDKZTCaTyWQymUwm04lSZuiDrgePGXrbvT922DHalP8XsCM9FyFz7LIhd0fZc8PPWkbIgRy+oOPA6ecMQbSG+ybB8cfxfUrIHnu1Y3oqN3COMPpC9qiWo+y+WJhyPZbS8Odt3TajX1YW8F/otvmyljA7PXccm6DiWXDs8KXl7Gw4RtmWYT+ETEH2gIOu/jNg55A4bhmnBUnkmC+ZMIl2OJBtw37VPNB+nSGczYlj45Wjrr+0zvPgnFnwOeRADnmgWVD+fLAv7IN4w7kIn28yWwAwTot+3+j5mYvlkJE45NC5unjmX8gtbKAJmaElzKJ5v01fgPHpVY9rl7MKrrXu+JXLUdePjoFZKKs0gzGFr//49MoV0cJS6lGQy7k7wnYCaJu4a5/W69yauWtI20Q9tq+2p20isknh41G3KSAs3GI5pnY8oIO9beFr7ju+U8j5LeHgYMfV5RFg3MXP9VlES0PEmBXTdCHtnwDZWnIwZB1q3rgck/qthdGjeTenmyP03OH3cL7IIJstHEvY9ccPwJmdOW0TSdk78Nr+jJyVxDjIoyhg50S0pFnluYsdryq4rmu3Z9d45lks/OAYZtz080Wk7MqQZxYyzfZle0FORcqprPZ91eMLD7mZnsH5lX5MhuNrwtGxO5dyfSwmMJ0tOHXhFjQj2fbpdceSDsf08HPwPaY9Wo9w7nRjw7O6wvtKg7Bow/Edzlkz4BJ97iTSz/54V85WVkOZdvVfAsst6dOoZXJuX5hXeq2lK7+JfY6lWdUvVxXcD307wvYG7YwCPpzvA3esMEnpGLJNwPtsBmXRHadspbpMPcspohH279yxz9w5LbGMSVfm4rWJjA9fjYA3/I/o2KCxa5vGfknHY9b2D+8TE+Ax+vfSMEd0DOt9fMLGHLJ/gn4LSNG/d9OXQyZjqJCtPpzX9P24NehJGh4XrhE3HTP8fFw9jmvHprodx6nUfQPO/7HXP25du0nh3LqpvE3HHtfG4xSO6bu5xqbrhPuOO2ZT2zcdPzx3mE/Dfijpa1NsXd/UpEdYmy0RK8Y8Eb3U4wl6LigdX0+P99dt8Dy+8Dy9Bpms79pGmHo6p1YkxG4dpixAgO/hd6hIuMwld92GXW701juxZxwG7MVY6rV9+sCfp7xQZRJSpr7d+jyp7L2CMXvsSv2z+ui8HnyOHFeRGGoSXwddX7VELjYxNBFt42vODXZpiahJPPNP16OF83FIqBinBWnAVNXYNo5/6O/5joepx+hxyjxUhuE+2xSMHYNUyo2Vvenyaywrbc+tTB3nEeAGZwE8F1NjrMzVhJrUcVq1DgU5389vej6ilFnBI/jvemxEy+9d/GNEND4+7+ZzngOqz7b6roxUEMan5q30eezrKpzUhJa4x0vV3BHeoxIpY7+GXpHTNhG3l7kvb+KOLaLcl1c71mXBmJVjUkY0fo3TrWsi74FA3Of8K5dSY6L7wnHRNp1ngtbH89WD8a8xzSk4YFv62nkQhOXq+FROZxiT3LFZU5crBbnPw3Ac6vkrcv/sF/ZpTEvaVr2yO15n448Vdu+Eh5lDDPvs9DjFIetVePMSywVTrnCRiqTXHvVUkHmo8qzcbfb9uE+pPeP3F/lLLJhSB3mmx+k40X5SaRs1P7vYyfgJY6Jl6XiXI8bMHCM5pfa9reOncvODVKL1dRnGOGfFn+DTjFkdYfS/3WXLcpPJZDKZTCaTyWQymUwmk8l0ou4RM/TrrgfvL0NNJpPJZDKZTCaTyWQymUwmk+lr0FvhB12TyWQymUwmk8lkMplMJpPJ9HWUMUNNJpPJZDKZTCaTyWQymUwmk+kB0oP3Y6h6I5RbHQg4NOYZmpngTFP0mDLYFwLGA0Mjf2wIvg8VlhMC8K9zFHCux4UGCgOTjvKVR+D5oHw15mnifh18vWJvXlOXSb8NQyMTfV/iz9FXZ5ASmDAtu/OKNg/qsDp6nTAWYV+EBhlh3UuO1tH/bfJh13boG1Th4MqhmQcdGPpI/4V1a5B+KUf9vhjC5jVG+h62LWzLZfp9H5ahpgChYUbYdlUTcVv7LXZmRd9bdsY82v5s8B4axEA/lzUmri+9UZGvW+TbVjA+YnZTV4kYj3xBDvfHgBjODGMaxKYuU1/2Sq9birmXz7EhtP+6a3c5MEhSI7GlOFTcLhOqMulf3703TRTE1I2FpssLhX3fLhNYZhBDuezqp/19eP0hbuztAmMIAOFabuug/V7aB3HTbd9kVgEe7C1mNutuv+bVnQwnsn45Z37gOmpqVpVJZ2BTitFOwVhg2a4OES01CXNmHYS/ESy6tu1IzjtVJF3uLpEx5I5JHZydBg6X427szOn6ELp5ZwlzZiyYCmS+iWRc6nmDuA3jcEqh7sP5h+D4OWLWFMYz2F+VCbeDceDzetgHQ2243tl076ixWXB8E/RBLxbQb++ELg5z4BV8fkRxI+ZY4KHoWt92kPtHxoeqdGZLP+WuWw7arDFokNgMTCAqZ4hwXByG7e6MIBDTsvAeVAbmR0v6hkN6P3fjty5TP0eqWUFdJUfqUpOw/9q2b1M+WcF8RNvEFORH+yGYOyvS/n1g2RXPnM48T+NVIvOj5nc4rw/jv2n7ndYVpgdHozsf0tNx80zJ0Tl/01puUw4fd53QmC281vAzHM3L4WvO0Wse931T/Y6r61PHXHdY/nFtPu5ag3VNr33HadO1jrvepuPC13F9uanOm+bt4XwxrPuwbuH+k/pxcC/cqMFcHZoc5RQUzvAFYMyKq5wnZ0VCTU0ia70lbn0h56kJTUUSzNlqVSIvKTdhxVjWoqS0TeTvPTr/V6S0xDTOMGfBlBvOxGiPXX+82DEl3jxF1zstMQumctwyMKlx1xiuk4gbfy+XuqbOBDLx9iUVCYv51Lctwq1T9vv36Pfzz+G03HO0frpW86ZOzQjKLaoyRU1CNUad/ZNceYWY8uSs+GYus6pyv94N27+qcr/W0/I0/qEZTu2uUbt2rpwJjh7jjXn8M5fUZeUtYMbOfmhKTcpZbgD4bfpZj9V8kX6Va7Zt5OMj/STbZU3QxTI0h6rpYvVZ3uNNpSqXT5WLhbaRJvbndX2vBlUxRXCOmvWE5j1dDDvDmkJjsMx9O1fuuUeNbTTfW2c0JNfKe2OqdusRNcapSXvPTdoOgClLb+KjBl1VYPjUtdd9rhJnhlbxp/+9X+/FsnUmPTp+mmAdOGdGQe4Nm/QZXPu+a1/q+i71fVMEuVG5emq/xu65pA2Oa5zZlOZ5mCdLN5ZlzKbefEvmi4SqTH1ZmqNat7C/O/OryNdL3ysSdl3etr4OORf5Q5/HCyZ+DtHrqSlW5WJQuWutXNuv8I28zLewIndzUuyNl7TdOi61ffrcrGO1GyeJO2/sr6111fkOZzyq82p//tC5I3h+eJ1SZujred0PevB+DDWZTCaTyWQymUwmk8lkMplMpq9B98uPsiaTyWQymUwmk8lkMplMJpPpPpUxQ00mk8lkMplMJpPJZDKZTCaT6QHSPfsxdDQa/c+j0ejGaDT6fLDtkdFo9Fuj0eiL7v3huyosg+zcq/BZOqbigAHpWXiqkOM4ZDcpkzHkfDaD88KXXjM8f3YIlwbbQyDCJo6mOy678Cp8iI53FzAJe3zR4d/x6veQLXnc3/qGDMtB+7cmq6NtPaJxv6wwFtCPV9j2IU+PDccD8AjwUI+POdmZMz69coQPeRFDFLcBZye43mTwHaQvlbUZ8jzj4Njw+LD+5Ybjwj6c0Y/5gAXrv3sOlLB7stmCU1nN+PQKYoTD8unsaOwnh3KNCXAhKHvC0fi5OLeOiei5kXHA+YldncO4ue1VmQin09W5dlylHrtqyFTdkG+eMdhAsXTMlzaHWHiideU4NjsB01PZSiHzyvPOhB/I0l3rkjsvFo5m+UuP+H5qHC+1JvHMQGJgGfy7VZke7e853L5+GsnxEczgVFb7UxIqGiJyCp+D0vaWxLFfhvOPtiXJasgc6zbgM3r5XFn3x8pgvorith/rj8tbFDfkFBLP+dRVIfJ5InyijrGk40bZPxHt0XkphiQTAlBvfDTAd0udEqqALxx3ObJP1396zly+H7DNvJ1BOZI81f0hS3PjvBF3PMuw3CFTV+O2xHOXPRsngzSrhZ/p47refD+YI2zgcJ4A+CRMWUDc5W6vTpsUzgMxwv4M7xHKCQ7bnHX1jWhI0ro7PozLfNTNbUFcIhqiuPVc18gxZof3tChuITvsthHsd33uOc3DPnHHR/GGm1ojQMVTmrPB/UBYbO77dbdvgvTF5GhRbRv52V9jUJP0WN2tcq2bjqGdujGnHK4j96SYjkF60r0+ZPjFg+PY8B7G6rht9v9x3hT95E/+JGfPnuWpp57y21599VWeeeYZHn/8cZ555hlu3rz59avAkGcOfeb8SVxL1UlMyE1rlOP4lOF9NtuwXzVc392Jf7np+6bXJo7npmtsavdQm9ocjtc71WFYn5NiGt4Lh3Uc1uVu+nN47kn77hSfTceflA+byla52I0pPNcyCZj9c2Zss88B2543GdHARLjsF7lCwZgZc5QBGF6jCTjsOQUJtedDwmBN6BTROB5m6qiN3Rrt/fyOZzECvs5634hcwZGjCA7VHde6sivhWDcgfgW1P065pTFt37cAd29yuXS7iXqcR73/hfxPf44qOyTNunbEjnAYtkvbpue1RERxy5jCswuVo7mpjV1/1p57KNtjmuBeq3WMHZPSxzGmV07urvsePkNO4fiQjV8npu46MS3b7JNSBwxSYRjWJPx30X8j3Fgk7zQ/yjPd2rUi4S/z8yyYurh07Xw3nyOl9hxFpUMOYzxl2eOFFoyDdXHtz9EY6bbEZ1/drUlcLApyYZw3I89pXDrOpNQ7JWdF5fiSeoyyOGtSz5sN2bC6Ng7ZsmG7dXWknhpH1vPu8zgtPLPzX1XvpW3lWUlzMWSB6nm1Y3tqH1Uk7LMtz4p0HNkwv3R8KVNXYi/PhMoNzVn5/AxzUtsc+jNov09YeJ6mxi52bU2o/Tq0z2eNeyzONJgvQukYi2l5mW/xa/s9zgLw+zzh5qjK5UEl63LqI3NJ654TW2I3RqXtu9zgIld8vil7uBfzoP4aL41zFx9p0x/ncz7+Og/25oS2GxfdGEh78X6jpH8Z+npe94Pu5V+G/iPgzwy2fQj4xHq9fhz4hPtuMplMJpPJZDK9IfrxH/9xfuM3fqO37fnnn+fpp5/mi1/8Ik8//TTPP//8MWebTCaTyWQymd5qumc/hq7X698BXh1s/iHgI+7zR4Afvlf1MZlMJpPJZDK99fX+97+fRx55pLftYx/7GM8++ywAzz77LB/96EffjKqZTCaTyWQyPXDa9J+qvprX/aA3ux676/X6mvt8HdjddNBoNHoOeA6AU994b2pmMplMJpPJZHpLam9vj0cffRSAc+fOsbe3d+yxL7zwAi+88IJ8ab58L6pnMplMJpPJZPo66s3+MdRrvV6vR6PR+ph9LwAvAIy23rvxGJPJZDKZTCaT6avVaDRiNBodu/+5557juefk3+RH4/feq2qZTCaTyWQy3XcyN/k3Rnuj0ehRAPd+445njIAYylceETOP6wRGHmwEhqdZLZD4JWKOEWpC3ygGNptFxHRGF/PgehP3udzqn7/pPTTYCYuP2z7w3ZtubDAJaoC48dsiPUZNNAiOG8LSS3rXURh22wQw3SCGtQNCiw7p/XY+NJEYmkWEdRrWI9zuz1sAW13bJ2uSrPZQaG+gNDl0BkqNQJ9/HDEROQ6uD/AKXR/rNeN1V48hzD803AjNXZb0zZjCtg9jEZooqZqRi3fM7WUuxjeNmLKc+TPX5ZjQCKaJO2OdedBO6JuwOPVyQff5nGj7xw/yI1ZjFxePSk1KhsYfWnYQsySr/HU8UNqVGdESRZLfHrg9Aa4LOJ+YztRGzXA2GYFpO5YOOL2E5XzKqT9/y29PnelRQ8Shg3x3Rkzus5rJ7AT79oGXAR6SGJ9zAH9nwpMjBmM5hRjoBIY5OUVnQBHWW42vcEYyIDmn9QhfGZ3JUmj6FnxP0joYP63EoYlpm9iD17d3D8hZ9SDoHRw/6gG0ewrr5D7XZcqinXZ5ouPA5UPOSmIIPq99roSGQnpuKXVpmwiyNcv5tG/Epe/hHH5OK5hLjqh0LKvCa2keNcCczkiglHlODcZ8vZsNZWTAO+mPX3ecxlPL7I0jnTdK+oYRoeGG1p+j5/Typ5TxrCD6I2ZEwKkLt7pYD+axJK2P9vVgfmybqDMXaxBzsTAGDuZ/5P4RfK7VkKwJzssOjxqObDIoubAOyhr5e3NLJONvuUUeFd64oyrFnCIJjb0ITJwaqMoUrkvsCjX9O8YgZJwWXS6E80RonjQJ3t15vTI33fe0PzeZlBxnKGh6U7S7u8u1a/Kfk65du8bZs2e/fhfbtD45bs1ynO705wvDnAzP27Qu1TXOcA2n9Qrnqrup2/DaJ9V3/4R9b4Q23SOO053GZXh+OFcM9x9jRHSijsuLrzY3htcd9vdJ1wvnvAv4tcOch/19RFbjSWeSiazpGr+20HVHxJjC72+JOwMbFzsxApFyCnJvnEOs5p/OoKbpjJPUlCShomDMgmlg7pLK+oKoZ6yUUMs6ic6sqCWGCWxzwPLWVO61gaFPS8QBO7JeQ+6BBXnPLGVFzv5r2/J8ovfm2N2bNJalGD35tqedUUztjGD8+sSvs7ZkbeTqqmZIag6l9SvIKZzxEMi9fMG0V8+CnKpM5D7YdGvA0FiqGvSp9rHEUvotopV1L50ZjsZKpSZDuu0y3+zv54l7hqtImTsH19B8R+uj7e0bOEVkt/yh/vopFQU5v8P3ULsYFYxdWxIO2PEGNA2RN+9Rg6PQpCce9L0aJ4UmS107Kx83ACbSTyvGMN/ybV6RO6OklAO2iWh8vmo+SQ6kPgYr309xFyP3fNQZ38SE5lia8118pM7egMyNp1WVdyY8TZBzThUpK8ZdWXE3tptgTE1ZyrMiQ/OjyMdrzsMUbc6K3Bu41qTEzuBHDYo6I7F+eypSb8KkOb8i77X7b/F3uMlM1qAkvk4JlTN9k9zNWfn+nDPz8erMobp4VqSc55qvR86KgjFnueHnOIlTNzbU0EjVEvkcD7fVJFzlvG8LQN2mfm5bkVMw9jFR46fC5dEw1i/zmJgTB30WmmwVy84UrAryrGdc9XruL29Bvdk/hv4a8Kz7/CzwsTexLiaTyWQymUymt4F+8Ad/kI98RLD1H/nIR/ihH/qhN7lGJpPJZDKZTPe/3N8nvq7X/aB79mPoaDT6VeD/AZ4YjUavjEaj/xx4HnhmNBp9EfiA+24ymUwmk8lkMr0h+tEf/VG+67u+i5deeokLFy7wy7/8y3zoQx/it37rt3j88cf5+Mc/zoc+9KE3u5omk8lkMplMpnuke/aj7Hq9/tFjdj19r+pgMplMJpPJZHp76Vd/9Vc3bv/EJz5xj2tiMplMJpPJ9GDLmKFvlsIaK8NzAh7T4PhHEY6BOHGsMT3u3GHHxlEm3Mydt+M+K8toFrzPgmP1uspnmiF8GV/eYcca26H/98CeiYln5CWZY6Dp9TynbsDy1DIub0l9J7CdHvSZe8pG1e8hs21Oxz7LOlbK7flp2VYG7cTx1zIXLx5B0j4oM0O4fjP6zLsJXfu1PiXC7gzP1frNAMbAoT//1KRgO9rvWKE4zkUpw+5AgYW/MojNBde+MOaz4PPk0O1vNjMzMgIWYlBWyJYL46ltVObP9aAsZVM6Lu2ZS9ehhOlsweTcvvAHM2F6vHb5XBf7VzRGFWcu7PUZhu+kn4NBfD0LZuny37Hucgoms4X83fUOrKq8z8jyeXcI7wm+7+PZizTAh4NrzvFjZufMgedM6fV5rGS5P2NMwcHetr9ektZ+jLZEsI/wPQcMU+bKX2rIJ0UX8y84bk8pLM7bZSLlLaFY5tAIv4gvZMI5yoBZKexV4FTIntQx+LKUS/yQz9utkDOMlDljLozQ9x6SnXsVXsnY5kCOGfJhA01nC9euUT/nevNA1M9ZHRcX5Ji6kjnstU+eE37kD0A+KUizynOMvvzvHuWAbdo28jkyZcHYMU9nzP1Y2mcbJh03eDh2ZztzLkZXurEcI7n9UTynx+d9yJVz85Ln0OnYB/ZeOytzcTOCZdaVq30+5C+WwGPyOVLW65DtGXJtd5D5KIjhw8z9OU0TcXuZw7lSjn+vu4bOVSE/bxm8dP9T3bhaVXlXzw3/pLi6Ne6zdrWvy7Sbg0O0kLbHtZcZRFFL0ebMX5v1j1sKE/O2jhtc2901YlrqynHA3uO4nlrHH3cxWko8mJW+HVuap8pHnrixpPOnzot017qt91Ydv3Nkjs7oxq3mVYZwaJV9fH3Uzac9Rqzj7E4OmTHngB0iWmnHpeD6yByRBvfPwzKBHZkLdrkhPKd9d/yfp3dPjB1rt3ffC9cEr9DdM8P71oTuvq7x+ilkfJwbxCk8pwk+mx5sremzcr/aV6g42Dbk6t7p3KHPH8rpAAAgAElEQVTiDccN7636Wbm2Q1ZzeF624fhNdb1TnYflbzrnpLLYsP+444+L57CMkM+9HOzbVK9wPRi+Nxz1LDipHSfF8G4V1n3TucNrBDxw//n/Gmwb8srpGPAhB0/XGx2H0/EmK8fOa2LPSxzy88IyasfF68qLHG87giYW1mUp5SuzcMGUFTkxLe/md0l8KZVf2xyw7Xmaj/EyUdw4np4QCmtkzXiV80xOL3y5ykb0DMO5PO94xp9j+NWOS7l75kbHjnTxyymC9U/jOJYpH+cD3No+5XmJLZHncHrFQLZmK6uJaEioPTuxCeof0ZJSkVPQEPEZvp2qTHiYOdvsI7TGlS9W+0d5nAumntOZUwTtk3Zpv7ZE7LPtmY9adxljHXMz5FY2yqV0TELNA+m3MSm17wPlH0qeCdfxN/l+z2VfkTNnxmEq9+oDtpmy4Hn+OjeZ9Tj457nmrhsTO65sS+zrnFJLPsVrxhSeibpg6rmpymINmarK4NQ8LsjZZt8xMru4VqT+Oakg56bjU2pe1aTkFD1mpLa7cuzHhsj9diFjLqGCTJ7rKt83EUumXOU8MwcobYm4ynkKcmH1NtpNUe+5S9u0fPkd8qw46/qnYOz5tt7DBNjhwO/33Mngd4mQ/QtSXk5B20h+a18mVDQu54WTKRxLjYVm9qd4ny9r7vq4YMxVzvvtCTVTFiyZQgxjCqpSuLErx2LtXEYiz+y8yBW22e+xNDWHOi5vd86cGStypix8Tmu+aC7o3KVcez27djNT4uY5zVfli2ofh7mg427J1F9Px3xCzRUu+vF6iX9LEnB6Ixqfx5QjylceYeEWmhENOUVvTCyYbpzv3866X/67vslkMplMJpPJZDKZTCaTyWS6T6XM0AddD95fhppMJpPJZDKZTCaTyWQymUwm09egt8IPuiaTyWQymUwmk8lkMplMJpPp6yhjhppMJpPJZDKZTCaTyWQymUwm0wOkB/PH0AlihDOEhKthBR18mBgxWIC+wYmad6iBQxZ8V4MHLVcNIEJ5QwUx/Tk1KbrzFC49OewA9KFxkl7PlZNElTtm3f2t7sSVM0FMVEKQvdY1BKeHZiJa14bOAEVNH+iu72HL2WEfPB8D5yDNqgCU3QBBzPW4TTHZ9AqPKQfHZgAr4CFvKhLFrQdh5xT+RXZIFLXkFAJCjoMy1fhD46JmKEHfbU1WsOPeNRZqnqGGKft0xlCh2RZ0ZkI7dOZIgUFIz4DjUmCqBSzmU5hAsRwDeIOfbfa58MQXOzMX98omhRgOvYe+GVOYCwOTg8TlugLMdXtVdqD3tom6PtA2ASy3/Dk5KylX4xoaM4SGQUtYVFO/LVbjps9Lo1fkRHFngOUh+5qLE2dWtE8f6Bz0mQd2u3ooGDyKW/iVLZ9Lh0sB7s+ZwQU8rHorq6EcwdyZhWlfZ4jpSWiUo30XxFbg1FLWmBWnspo4bsne+Sorxp15zdAoLYY8KqjKRPpac16vB4EZgxtnoSFQ2R03TRe+n6cz+TxOC1fNEGgeyXxSAtmhjBlkrCtoO81qD1/3ddDruTq0jYDLfdwbF5cfpj+e9dzhXKT9N+n2tY07cS5182UPywtNpK7Le9vE/TkvNDUL5/HhvB18TrOardmCMzvzzjhoaHoVGv7oC/p13FTX0IQEGJ9eHTW2aICs6tdricR1h74Jx3UxYYqiVoD2ery7bj4punL9fLp2AH45XuZLZ3L09911/vG6b/IWGHd5k6ogJt6IMOzj4P5yKry3+nuQmJU1TXTkPhFFbX8+DecVN4/rWCNumTNz4PdgDtBYxGJwEMWtH1eT2cJfr2AsUHq9P+g1dV5Xhfe9JriGGtVpboX3snDbBDGX2+HouA7bGc4rpgdbt9370ODnJA3nmnDdot9DbVo/3Y2Oq89x24dj4aT9d7N9uH/TejC8z97pnKE2tWN47HHrz/Aa2YZtm84famhedNJ6d1P9wvk/fB9+3rRtaOIUbh/m4qbv4fz+nceU9YocqyYfnelH681GQuMbNXYBfGxCUx41y9E5syIhcqZAUq3Im5n49V1QZ1231IGJTELNl3iMqzzKgok3NVFzkIiWBVNe4gm/7lAjGF0/bqtTbok3dVGjl4imtybSNaAa59SkXD0QU5fDUu9Xg3V3I4Y+EQ0FOVWaOAOdsTfraYm9cY7cv8UwVA1UZsxdbGK/TftjyoKYVt7jzmgW4DxXySlIs9qbu2h/gd4bI9+m1lu1xL7PtH5Ar7+lbV2fqbHMwhm/1CQS21jyYJgrc2cupOdqe2oSPsDHAVnDa54tzmRULu/UyCilduY/Uv8rXPT1rpxhU2eeI8YyNSmUI2fek7i8ap2Zl+RPQuVMrxJ/XuOOUx2w0zONWjARQxo1cmXMnJl/aWwKcm9yo9dX+6+aFDU01n5ekUMJt5vI9z7IM4gakGlebnNwxJxJx09EK6aqdGPaBdmXqfFviPy6TftVzZMKxiyY0DaxP7YmIXVjRvOki8vUx1ivo58TZ3jWuLzT8t/N71JXCStyX2ZMS8HYmWRJ+T/Dz8r+Eq7dOs9hmXjjpHC+0utr7lzhojc16xssiQnWZS75saDGRS/xrT5GYtTWeHOk4djUPm2DdqmxUzjf6bhqgmtNWcjzsotVaEa2YkxEwz47VKR8lm8PxmhE3ab+WU+7WM2Vwjq0RLzIkz3Tq9crZYa+ntf9oAfzx1CTyWQymUwmk8lkMplMJpPJZPoqdb/8KGsymUwmk8lkMplMJpPJZDKZ7lMZM9RkMplMJpPJZDKZTCaTyWQymR4gPXg/hq6B2DEfQwZPyNrL8EwGMoS7iB7T9llFQ3ZZuC0bvJQhFjAiT2U1TA6F4eaYj1tZDZmrY1gvPTfulxs79uXWbNEx9q4LM5IY4SmGfCHlfyrXZ8B5I1v3uWx63oCpNGXpuFVbXbnKNNwX/uRktoAPrBGm5+FRpmK8oWxl5oRxmwGX6BhsQw4l3yAbXHzSTJgiOxwwZUFCxZQFpzKhe0xZdPyaEDwRMpJCNpR7aT9FcZ+d4uur78p1DGOr/Q79nBvmoV4zvEYMSSbsnp0zB0xOL3x9H2YubTlHxwWdQD5ZdeyiWbe9d90gv/JJIbxIlduXUEl7f0auNz696sdK+Zu6TfNK8+GcO+5DwXUD9muS1n5bhOPcPlV6nmCaCRO3IqUqEzn2escvOpxPO4bokH1VbnWsSdfXygE8nE978dVjKhLYh4NWCj18+aGOzQh97uo+su86Qb6Wkieai9TscOApQlHcMjm9IJ+shL2Z4dnBvj/ctcYUnD9zTZihk8Ojuerilk2K49lhDSRIjJ96+l9KDIGDgx3X9NYzhhNq6jaVNs23WDBlxViYRu7YKBY+jbbN9+cSuAzsrLtyQy5nMK4T6m4MKxs55NCGfDLX5raJKJe552kdyeNBTIjxPNZUWZvD8Q79MRcy1UL+mLK/4qaf6yDj/ziOnH53bc2RvPAM1xiZ1+iXobzO3tyR6b3BsaGVT72k4/Ze746dnF6QU7hcK7p4TGS8PnLhRscZ1vkmE5ZYktYyvkp3H3JtPaWs0QymZ5ZyP3T5Gyt7M0ZyIBZGUteX6/74z9ycNrj3nhrmcsDRzCk2c2Ina/855Fg1RP57klXwirCtNNciWuGQungu92fSP7MFKbVcT+dUZYUO+d9hbuv+crAtPBa6Nuu24f1Pj9k0HkwPvkav8/zhmmHT3L+JA8mGfcPXcM4Ojx+eq+vL4bWH19q070681JPqqPW7m3OPq8/dbD+pfieptzY9ptyT+JzHnbPpOifVIVwrfLXXG65Nh/14h/a1xCwdB/IoXy9x3MsxKXKvSdLalTlCeY8hJw/wuVmQk1L78lLH39tcj6MsROXlCRcv5l+0f8qz+rRuOQUVCYfLcY9lCMASrnFe1kpxn9NYk3KV8z5+CcrhjxFuquNsNpG7F6X9OIb+DHTPoi1xwI1MPQu164sRNDHFMvfbhU859mUpoxDwa7ruOnKvDFmCSVrL8XF3TBvcU1fOjaEN+IQNEVMWnsF50y1mtUdVwqvUdrQ+H7RPiUNGadd/yjbsWKGp57z+Gn+OOTPHN5RKx213XeWS5hQ8yYv8Kf6Fv/ZLPOHyqhLPA7rrt8SenRrGrXZ1uMJFrnJe1sN0+TAsR/nlPp9cXFeM/bqmdn0kfZ30uKmaYw2Rd8KoHH9S19Qhw1VyQ+I7pvDXP8sN9tmW+NDyn/7e/+bSSJm1Xd3B+UQMNZNYVCQ+H7T92mbtS80PHYchQzXkZva3C1f0gG0KcpZMPS+2YwqnPmeVvNk6Hq3GqyHiPNe4zCUqEhZM+Sl+vte+U46Zm1D72CuXd8HE51jYt1o/5XwWjDnLnl9zrhhTk3Cea6wY+/4KuZ4SCyF/an5pdrTEjCnY5YY8b7lrKx+1LjtebEHOnBkVqZ9jfN+58bUiZ8qClogneZHGtaEipViOOWDbj7vJpS/zD6ufcHk29nN2V15z8r3nq5AxQ00mk8lkMplMJpPJZDKZTCaT6QHS/fKjrMlkMplMJpPJZDKZTCaTyWS6T2XMUJPJZDKZTCaTyWQymUwmk8lkeoD04P0YegrhBSp7cMgadFK2W08xoOyMkNWk3LWQtaTMsCFfTDlLjid5u0w4ldXCoXPXb5sIJnC4HHdlhLy7kEMWC3ODzDEQlSN3YXC9LPhcuusrG3XI3itHR5lmYTtcGTWJMN5mt7ryly6m7vrFModPjoApKBelpF8nH9vB+6a/Ow65aSFTkC15ufOUaymclJqcFREtt5vIkWzqjqcJfa7mDkc5XEuOKoxPOYhxWHfNC+3LSXDOprI9HzZgE2YI+zITrkvqKB5MYMyKJ3hJjrvU1SOKWmEaNQhTMAPijq03rGtVpp6BVJH4/TGt8ADDNqvcMVHcChMwZO6GcRiyFAN+YdtGPhbKxjkVS195tkosx/l64LgzJ7AaPWelcRyc7wZeCesQdHCDsG8DXnDbRB3LUqW5q9edu9c+kvMzx3UEz2Ls82FazyrNo8KzN4GOSRiMj5CH48vU+mp9JpBm9ZF5we9v8Nyoz//ff1K4m3QcIJ0/WGbCswrnOFdn5XONWQlj0cW8IunzxM4B5YgoboU/FOb4EvhtOTanCMbBluzbxKsL8u32/LTMvzEQMkAHnM+w3T7uOr+GbNKwfN0ejmWESaTjsW0k//KoEEarzj3n3DnK7Szp8zGDeapFcuose12drw/agGNk6VwZzB++b/Seo/eScH4OuMxCBKrpaQLFctyPM0ATu1zJPceIy0GZMdxe5v7z6tZY+qOUc6uQfbYcuSpHHce6GfXjvElBf7TNYA5pXL7pPBve28pRry23y4RTccuqklyvSKjLdGN+fPlLF/sc8KXEWRhMid/G36V3//LzT3ifD/NH5/GQZ0vwOYxBHJzTBO/h/jjYZ3qwdZuTmZibmJeae8ftvxtG5d1wOofzKvTzL1SJ588f2RduO2nfsH6bjt9Uz+POv9tyj2NfDq8dD44bljcse1Nbj6t3ueHY4+q46Xqb4nSn2IXnHtfu4+IWbv/0hm3B+qgl8uts4eWNPb9OKaLQrfna1q3T4nWPk+m5z36NGBPR+LIKxo6V1/TmVF0zNa4eDVHH10d41le4SErF90S/47cr47DjlG7+u6VwbRrRuOsEzH1da6NrqMrXGeB247iR5QAgHCPryWB9umDS45lqmTGtsA99jo78GqElYsbcx6E7r/Hx0GeHYiks0iZot5In07BNehl07ZgE7M7EsyFDhd9j3DqsiQkZo1qm9nfr1o7VgH3YHdc/VvfVQRu07KjpuJ9RkBMViedaViR8Cy+7WE9dTnUMWWlb7JmcWmeNo3yuHGuya6+uuzUvpiwDhqysZbXO6j2h11sxZsnU8yvroO81R329gtyJaLsxM5CyT0N2a9jf6qdAE/u6h6zcuk05887rsp6e4xmbFSkJNQW5rLOaruzC8z1dvJvI96s8yx7Nryh4zgvzQyJT+Rhq74fHNi7/Y1rneZCzYOpZswkVH+YvS5800DSRH4vKw4yCzNIY6Hhr2z4/Veun/ZCzoiFi4nxJDtiW5yYaF/uOsAsdZ7V2vM9wnM6ZsWCKsFN3fJtTapomIpxPNOP1fH121PpPWTBnRkvE53i3r6/28zYHqI9KVaY9TmxYXy37ru4zdyH9y9DX87of9OD9GGoymUwmk8lkMplMJpPJZDKZTF+DjBlqMplMJpPJZDKZTCaTyWQyme6ot8IPifaXoSaTyWQymUwmk8lkMplMJpPpbaG3wg+6JpPJZDKZTCaTyWQymUwmk+nrqBGw9Xp/SXyD+KWvRw/eX4Y6pnU2KbyJUM/kAqDpjC5AQNv6s+8pNUdp8CZIQAcOHxhh9MwiBmYQlLA1WXF7/7QAg92+fFLAHLLZom+gERoHBeD1lAo+jJiKTBAQc+lMVcLraf1mazlu6UyQ1PwjNDAJP4dmJkE7FYJ8Ww1qYsSIYh9vvJGo0Qmrrg2bgPShQoD/PkcNJTT2+r0EOJTPzgijbTvgsQKaI1pOxR1s2J+r11NjqRBqvwl2HyNGPnpOOdg/jGE40Pc52p9DQxA1SVi6AybStulsATMBNRfkApEuxYzmJZ6QcvYRE6WZGAhFUSt9/R7ZtjVbdKYek8Mj9UuiEAAv8SjIO3OUEuoq6edHKWZAxI2vu5oXEQOTsjMH0bjEXfnekEdNSSaSN1tZzWUuMTs9hziom7tG6gDYW7OFmNBoLNXwhggmZTe2jphCtEdzUN8vlQKQ1jlCj5kdSo6paYSaJ827Nu1u7znzpUOYSJvUSCCngGZETMuMucC3MzevhHNHdghNZwC2r05vatITmnU18Nr+zJkg0e8b16aHkRg+9R/8S2/wdLuU8RvRwBImF74s3+POrEDh5wU5jQO2e80DkLaa9rj8LZZj5jzcxXTTzUrnz0nZj31oKBaaCM2B5RbMxWDLj5MwrzQmatrjxls0MCPrxTCsS88ECQ8UJ4PJ6QXj0wJHL+dTzlx3Sfeya3towtOM+gZKg/tLrvOhjr/Jujf+o2HAXJ8mWSXmRDrWw7aHcyId0D00etBreiMyjdMcMXJoZD5JqX3fHpZJYLpX+dxIssCYSc0ewrY2dMYXQ7MgV+c4HoDYgzpGOp9onEoXt/1+GT5f9sN4xdxe5lRl4o0ZkqyCuTMMczq4tS3zx2X5fubcAcSwmE9JNAaaG3/JnTSjP8eH98Wg7exs2Ddov/8emneF5TI4dmjoZnowFa6c7/ZB4JgxdGR+bTa8uMP+48x0Tjr2uGPCNdGd6nNc/U46twQ+e5f1Pq7c0IzquDYs2XyNTeVtWmffbTvDa2y6lx0Xb/28yVQrvG8ed+5x9QznoGH9Tup7VdD/ES1zHmaPs6zcGkJNP148eJKVW18A1GXi7vFiKFR5k5hU1iHuGqFpjH4WS9TAwK8ZdXWhM4HZZ6dnyqMGLytyZwYk94aCnJpU6tdIXRbVlAVTf52CvFsjAku3r9L6xQ00Ukc1Qmm9zUnEVlb32uXfNX7LLepKTHPU1Edj0RJRMKZw5jBhn+WTwpvy3OCsj5MYBjXUpK4usTcIut1Efpua87REzF+b0RD5Z0Xdrs8gUoZ8VjOlFWP+kIs+nitn0KJ9qnWtvZWtxFzMbsa+PDkmdRaQch9XMxxtt+ZBQ8TP3PpZcgrfBs2NKk18m0CeDeV77OsYtrsi4aP8hz5fw5jr85DmyAHbLJgyY05K7eofsXJ9UzurW83BBRPfl7q2VJMfNRlVYygxeZK+qlwcfAxdeRobcCZNsea7i2EMNLGzHEqdMZPku89v4Jff9Z/4POuNH5fbarBVlwnFMpexChyw7fNC8jMRk80SX15Ew6LqjKBuL7u80bZqLmib9PlB+7ci4TG+xIoxB+x4kyO9hpoY1aTUZdfXGq+UysdZ+1P7Rdor/Z5Q+/i0iIlmTBvktxgL6byk5S2YUleJK1uNp2JvwiUGaGoKJ+29yYzVrbHPJ22nvq+COqbUXOLf+nPnzLwRbk03R4Rt0ni3QR7VJM4gtWXl5pOWmHL/YW+upOOzKlPfRt8vrq4JdX/ON9lfhppMJpPJZDKZTCaTyWQymUymkzUaQWx/GWoymUwmk8lkMplMJpPJZDKZTA+G7C9DTSaTyWQymUwmk8lkMplMJtOJGo1gK7rzcfe7Hry/DB0BM4S3EAOxg4gqe8z9vDum8AyfhsjzdaazgIU4k/2K82NCxxLccd8n9Hlx+v2cvKdZBTMHaQh5mjHUZSrXCPlB2dEyI1r4KYQFqDzIfeF8HGGihtzRSdBO3dYgLMnwPOWJNnSMuRjP0/F1B879D3/grxVFLflk5RgUWxL8OLiO4/J5BpsyB7WNGcK/1Pju0Of9hcxXHpFruJjPorlnhY4pUOWTos/wCHmoWpeQJThZwytdzIUF6WKrbdH+KPG8Tl9myBGc0P9z7oCx6OMfMucyx0GdbwGOqbQPu+x1Zbhzz3NVznuqi2tOwWO8LHVyebq7veeuuxZmpuZbplUQZmfOytcnoZI8dXVL0rqfh5OAw+f6QzmU0k+u8A8DO4dd+1yfz7jpy2mJYA7lZx8B4Ft5ifmtGSyFQzo+7ep1CW664EbKKwx5eo7pNJktpO4af61b7MZLyLpcarfMoQzao301QWIW9mWGjI9gjDREJFktHNAYn4cz5jJWs0PGFFypLrLLDcCxIPUaM6CR3FTmTkTD1mTVH8P6OQaaqD/Gw/lm0sXk8y/9SbzilqpMhe/adFzNKGrdWFuzzT5jClIqHmYuHBrHXPLzRzgOXDziuBU+ahbsA/hu+T5jLu2cu/wI6z6Yi/32JZ4hfHuZSx3Pdc3pzZ/a7oYjOe7nk/B6Wr9Zv8w/+Gvv8u1riWlb4WVtTVa8tuMOfCcdxzLkY4b8NfqfP1e9u39d6OYiYFXlwZx06Mss9x+WOUnreh0Z8+eAx4B3rv21UipiWuFtNcFqYwmz03OK5birwwRO7dxyTOLIMaByuARndubw065uP5z5+S1k/YIbh2Hblbnq57XDfjsnLue0Xxwr7bZjIbVuDAB+XLRE3XgN4uXvH7i5KKugGbFz5oClG0N1mcKFPgN8dnou9XYxeO36dm8sVMqhmwO/hPC2X8Fx5JJ+XcK2ey4z/bniKbpxHub1z7pr7NC/Z4RlD+61pgdYa45nMZ7Ef4QuH8O5jeB47lDeSdK5d7htOJdtuqZ+D/N1WKdh3U6qa3nMfpA5d75hXzhOjit703hl8DmM7TLYdtwr5IuGrM4hz1PXvGFdwmP2B9838UAZfN7Ujju1bdjGTf04PD98dgDh0IcKGawljqcoN7gU4UuvyD2brm0ix1JvmTOTNbVbfyszu2NURt7nAGCOsCwrZP3ieeZLPAexQMrrmI8xKZUw/irhPD7GlzzDVHl757kKyNoroe6Y/emCmNbXZZc9FnNhLNakbHPAnBk5K6n/K7JuV4ZoQu3Zj0umRHHT4zz2+sLlsfIJU2rmPEzhmJTKFPQsS83BEpbXdzwzUVmKRZu7useuH8Y+rvIcFzFlwVUeJaFiysJz6lfKngzK074RRmfkqt+VL/tjVoxJqD1zsSbp1guOpV+RUDDm93nCxTLhKuc7fjvCKLzBLvtsEzvqakrl80QZ7N/Lb8u609U1pfLMQ339F9EvcZlLvMiT/DbfR8GYGTcDJmrMn+WfEdF6huaUhdzvJ5KbWqYySvc463mPytfXGAEcuFi2xD6/KheLhEpydQm8E+YHM5bKEQX3rCr8V+E9NlSOoZq6vloyZc5McobIPTMIr/3UpPD8Sq3fHrvMmJNQU5Oyxy4VqTyLlUB2yA123bNdQttE1FVCFLccfv4hv57UdeKcmW8jcQOZjJ/Y8eqTVKi18ltK4/tZY1nR8WjnzGib2O0b+5x6mW+hcWP1BrtSP2QuULZsQU4+KUiouMnMMy732EV5xTUJf5P/nj3OQizPKuoFc5VHfb7J+K88uzjsT61/48bimIJZOvdjXeKxzYqcCQtqUg7YZsmUv1L9PQpkPCZZ7ctUBq6ycbW+B2xzhYt8icccPzZhyoLJbOHzYMqCHfaZuWc0PU7nSO33CQsfYx0ROQVbs0X3O0kDh194iJ/Y/odEtPz7fIqKxI8rzykerhPe5rK/DDWZTCaTyWQymUwmk8lkMplMJ+oNYYbeB3rw/jLUZDKZTCaTyWQymUwmk8lkMpm+Br0Ffs81mUwmk8lkMplMJpPJZDKZTF9PjYCtt8AvifaXoSaTyWQymUwmk8lkMplMJpPpbaEH88fQ0LBGFZodQWeuQ2AmA1Rl0plkhOD00BQITobEq3mCqolIosqD1ZOshkbMfo7A1DlapgdoD9oWxQ0X/uoX5fsy2N+MPORdIc29eDRxv+5hbAKDkCnOTKpM/bHX/8Yf83Vr20hMf8I4h+B6rc88+HwSKD4sY9OxgemTB0gjcHOFBfeMRFRhfNWkRcubj3rXqdRYZ3iexn1O32hgE2R/CPkPzQYIj3E5uCMFTk4vvImImvJoHikomhgxU8kEzN0SSX3OAfFagPAZYjCix7t3bzS07OdUSyxwe1e3to08rF37IaUimy3gZdnWEvt83tr5inz+GaAUqLw3CkL6p2cG5kyNorjhMt/M7PQ82N94k54pC2dqFfdNcZru2GKZ9/ssdjD/Bg7LBP6jdZdLO4euPgIaj+JW2rmz7vdpaIAAnemBM0xZVbkYbbl4RbTezCah5pu+6UvUpFxMrzgTlkNSB/Du6tkI3JuWlYNtHy7HXZ8N5plTbs4A+nV139UE7tue+NfBeR24GwTWX5FKjN08oYYHES03mXlgvcLZ295gpmfK4HNIc38J/Lp898D4DDHWGZpMMPi+xBvXyDWa/jxA8D40FQHyqDg69sLjtE91/Dozrsf/p//P515dJSzmztAqNO3SsrT8GGdQdvR6OiclaT2Yx0a92OabLd0AACAASURBVCVpHYyv2Jd3alJ0x+q95GU6Ey/wc31oduDrGK8hE2OgVA3ElnLu7f3TsCSwGmjh5eCep31xp3/FjYFs7XO/N58NtJhP+30IHrDfM2RysWgZzD0a42AukvyVe9L+a9tEztjhdiPnLqqpv1TR5jJ/aJlx6+cGBfx7I6QSKEd9s7mhGcvQPEbnfNz7Z+nmC70nT4D/1r3P6YPpT7pPmB5cqYES9Oe+k16hSQ/HbH8jXqGBZDjuN30eHtcMPg/XaJsMhb6WOoZr8PA1Z3MdTyrnTnXVsXq3ZdxNG4f3OIJ3Nhy/adtx1wgNt4bfh8ctjzl2U9kMvn/2Du1F1nYtUWcSA34tcWn3Mgsmfv2RZJWP84qcFWMi2s6Qxq0XtJwVOTWJGMfQmXg2bs0b0fq1fEtE20bss+2NZK5wkU/xPgA+1b7PG8+o+cicmZjdLLeoSSja3F9L70X6jBbWY8GUJdPunuGur2ZSur4sl4ERiYuZN+xzcWwaeZJZMPWvgpyalJ5JT5izWSVmRW0qZlFMaZvImy2psYqe2z0nJexyg6W7jjcddP1ekAfmVOPOZNF9r0glTu64cFvinlf8eqAZufPH3riqCp7TFkx9jDW2CTUrZ96ihjkrcoo29yaRv8n3c8C2N8xSE53G25BG7r4uRljv4TPEYo3JizzZPUcF/aTtrEmhlN8DxKxnTOPaMGXhTIpSV+fUG+xIuxu/qjpgG+ieQ7RPdU6L4iYwvRn7ukxZUDBm6dovxkOpP7ZFnttrZ+JTk0AjhpQ1iTcSk7EzpmDMwhkv/c1/9fOubjtuHt2S56u5lOsNJd1asG1iuI4zHku8AZLkb+zzXs2NVrfGrMi5Ue1CE1NXiTfvaly8tJ9SKrf+Sn3u16TMmLNg6ueFMQUFY57gpV6simXu5obUGaZJ7LS8hoi/wt+T9dsSlvMpt8uE5a2pK0fmHp1jtN8k5xI/lnzModtH7OvREnGV8/w+T/hxN2ZFscydBVjkyhr7uaNgTEzr80ZzI6J1OdZtr8rU58EVLvKHXKRw/drloI5JidkBO75MNS4ryDlcSh0qlzMyHhOfNxqXnpHUG2XkOQKi1/m6D/Rg/hhqMplMJpPJZDKZTCaTyWQymUxfpd4C/9PfZDKZTCaTyWQymUwmk8lkMn1dNeIt8Uui/WWoyWQymUwmk8lkMplMJpPJZHpb6MH7MfQIq2nUZw257S2R36bsEEA4a8qCifFsOc8AckgZh4fpK+SbhXyguGX+2qzPAoyFZXGEf6evuNuWILzBwzLpMYbqMuWV//FxqZPWWevrylBejGd3Khct5AftA9fdfmXMlR1r8NSk6NVH1TaRsCYbgK3N7VANeaj6uRzs28ADFF6k8DR1f0otjFDHxIlohKXSxJ4ZpEyVXkwbuj7U8ifd98P5VDgsyoR0vL1eGWFObOJ8hWzSTbynRve5flgKnKNFWCwJtTD/EMZSTsG2wpF23PmZVt1xXbNDsp2bck62ZiurOeU4MFovz1PV9gaxn84Wvo5R5LiewSuiFY7MYxI/zzQFYV02wM8Ck9K1Cc9zSoJj9fOZd14njlue5EWKNvccvVjzVdmfS8fW9X219v0Y03K7iYQ9GFxTuSjZpIB/PAp4S1sdl/DzI+F+Zq4ftO+CePnx/Hk8K5UMxmkBwG3l6wRQEyHLVMyYc7U9zy43oNwSJs+s7Li3Ph5CbfHjVHMkZBSXwgXqje2QIYXLnSX8m099h8wTS2nvYZl4PtPy8juEX6NzUUPALsrJWTl+rLCGPDNmmOdLYet6zlc4330g+KxzyXKrPzeG8SV4V55iA54/qXy7ZXCefo+7/Z7FtelfIMN+1bE/5KTFMp+lWeWYsPHRsat9Mge+MDo6d5XdfFtXSTdON8yDdZUE20Z+LrjdBHywGBlD59zniTt2BlyS/Pe8Na3DUspR/qmP0wxO7dyCuGOy1SRwKbhWqBIWr026cpu4i8lS6+zKCOOwDD6rLvTbTrkFjTDVhsc2RNI+HQOaT5v6toTZmbnnjhHyooN6RHHT9cPLmcvD1LNGe2uDGP/d88/CuX6Yv5PB96eQ+oe8pTnwIfce8keH98ngvm16wLXmKMtxE9fxuBwYzvObjv9aXuE1WPfnpiHrPFyzwBtfl+NeJzE8N3FEw5iG7dtU37BtbNgWvkIe8KYy2XCuHrupvONiNyz/bvpw07a7aeud4h4e+85j2nlBPiqXTrlzNYnnXirTsCVm0Qq3sLz+iF8TVI49KfxH4Q3SAPsdw1EYdzJPX+O8528u3XrmgG1k+p4xZ8ZiPvU8z7aJ2OaAhIoFU94TfcZdb4eKxLMxX+JboenWQSDl0Uj7lvszv79jSyZyvGNDH7DDAdvUpBywzYIp++zA9exI7FKq3r2ybWJflzkzz0nVmHpuZ5jj1zPZ53iELRGVY2Arp1FZkQW5nN8I7/IKF2kc3/AGu9zW9WGDZ2Bq/ylDULmQyh/s+m7H+0ocsOM4ol1dO8aickAT9tlmn21hdccd01RZm9oP2hboWPflfMp7+TQJteeRtsSec6/8e5B795wZ/5z3M2fGTWZc5Ipnxe5xVvrXM1oTv5a5yqM+/ppPWn/hMY59DLTNcx7266opC8f7TLwvwJyZ3P/3O5bsTWZ+vIRcUOWUar2UXVoj6/qCnNWtccCfHfl6aX9JvsY+Fn/7T/x09zuHW0vr831dJRwux5JDZQqX3G8NEzyfVWOxYoyuVXXtOWdG00jeTNMFLOXZSvKo44W2RKyqnJvMoEy7+YEJES1f4jFf3wVTDtjx19XXnJk887m17x67VKRc5TyV6585D/O3+Dvss+3mtRTKLZom8uOrIfK82W6cyHNP00ispX8mnsGpdc1ZeaZtRcJFrqBr8Bd5kr++/bzP+baR9seuTV0c84BDGrHHWa66Oe4Gu1QkHM6nfvxXJN5fQvNR66v9o8/k+nvIATvu2Ak0MXMelrnG3V+1nE/xPhcDyevzXJXcUI+C1yv9y9DX87oP9OD9GGoymUwmk8lkMplMJpPJZDKZTF+D7pPfZE0mk8lkMplMJpPJZDKZTCbTfa23wC+J9pehJpPJZDKZTCaTyWQymUwmk+ltoXvyY+hoNLo4Go3+z9Fo9OJoNPq90Wj0X7ntj4xGo98ajUZfdO8P34v6mEwmk8lkMpneHrpy5Qrf933fx5NPPsm73vUufuEXfgGAV199lWeeeYbHH3+cZ555hps3b77JNTWZTCaTyWS6zzUCotf5ug90r/4ytAH+6nq9fhL4TuAvjkajJxHbgU+s1+vHgU+47ydrBGTwTd9wGb7gts1wJjyyj5kzR5jQNxnKgmP0c+zOdSYUPYj7LNjvTIy2dr7SmaRkMDm9YCur2T1zozsHOfYd33BD6qAGSOF7YF4zZQEZPHLuoGfOMNuZy3XU2EHNHCZrX9cpC9l+ydVzAkwOO/OHONwetGsCj3K1bwQxgNrO0rmY2+wMjtEYapmz4DNBDHVfHOwfnqcmIjzk+46J9FkeIKynLNjmgHxS8DBzclbSdjVZ0XaE5hxa37Lblu3Ig042Kbr9k6CMEojX/bqGZkNh3cN+1HpobHbg1OwWTA4lZyaur2byPuMmY1awAzPm/HE+1+9bYMqSnJUA7V/ZIp+sSKiZnNtnOluIKdIMmEl9p2eWHnieU/j6KoBc+8ebIyk83wG380kBr8jnhNq368y5Azn3+VJg1a59XJC2ptRu/Bz6NiZpTZLV5BTMojnM4GHm3ixMumoO5+RYbw7mYpBdepUZcyazBdMzy26Mn3PXywRO3zOFmIiBUUMk7VaDqX38WMgmBewc+jpInhwChRyzA3WbkqQ1p7IazkmbCnLGFCTUXHntInNmvCf6DBEt2blXSaJKDK1ixOxqJrmpaPAZc9mmY0TzxuXRqUlxNN+CsahxZeLakEl7s0nR7cvWTFmISZwzTdC+1/ecgumZJTvO/crPH5N+nfKJjL1eXRrg1+nGlptLTu3c6o7RuWBophS+MiBuuvGjfTscXw1+7sopum1afjgmw/EXjM+Y1pvrjE+viOKWWTonzaru/Pmg/TvAO9cyFrVslysax1k67+b6S3Rzhjt+nBbd98khZIdy/8jqvknbBXrGOlvnviJlvgJjZ6yWsyKfuL7YKb3J1/ndq/1cctI5QO8rbRPDr0i7Tn38lrR3JvPFqUzG0qlJIfOJi8OpnVswQeYojbHGNohJFLdizqd9HyPjMIYzev8K8iWl7t9jfxs/7vRY6bOq16YENx5LN1+43EmizrwNYOupr8A+nLmw1xm7aZ1/2vXTO6Wu4Rzp6xDmo5sLezl9nWC+oevLmH4e6/7h/UHbabrniuOYn/u5n+PFF1/kk5/8JL/4i7/Iiy++yPPPP8/TTz/NF7/4RZ5++mmef/75Oxe2HhY+eB1bia+5+ncvX4fR0evFm44b7IsH+zcdc9z+TdfaZJig899JbdhUz4Y7x3B4zNAk6E7Hh9vvdK7Wrwk+n3Tcps/HnTfsj6E2GSsdp9A4Se83X9iwP/jcEhHTeiMR3RbR0LaRf67KI7GDITv09VDToAkLQtPM4X0qomHBlBlzPx8nbj2ZuHtFQs2MOVHcOLsQub/VQZ2WTL3J6hz5mxo//7s8K5ZiWpNQwxz2OCv3FKeC3JmTtLI2dXXN3bpP5Z8ngdqb3NA9a86QNfVEzIG07Mtc4kle9MZBOQURrTNFpcvzSXeNnBUNEflk5Y2IEmq3lq6685sREQ3bHDizq1TiGbcSh0FuRDS+PG1zHLRL++cq52mImLIgoXZldsfUJJIPxHwrL9ESM2XBw86dRQ1gFkyZsmCHfd+2yhndtM7Q5m9809/mN/l+18bKm3StnLlNRUJKzYf/3U8D8G4+5+t/wA4v8QQtsX9u1L6KaHz+iq9T186WmAkLrnGem8xoiUipnXGTHKdWOvpMCrI207WlNz9yz5hbWU3BmFgNHJ3UpEvbE9G4c9NeXnXvja9zQs2UpY+N1N2NRaL+uW4+0romaZe7SVbBZfcllvWd5sFQCRWxy43WGZ6GRmRqcKx1rZzpVxqMFTVRKhhzkSvebCmlIqegJZK5AzVNbmCZubHY+DJSKna54fuuJeI816Bxz/TxmnwivwmMKVwerEip/DNYSyx94tqiz2Va39WtMTkrCsY+bwCucNHnyi57/Owf/dfsuHW5xqFy4y3sB50/Ab6Zy5xlj4iWMYXvczGQk+uoYdWYlZ9vdIxHtOyxS0pFS8RjfMlfryX241/7FeCffunHghyu3QiPeZnHumNNXvfkx9D1en1tvV7/a/d5Afwb4BuAHwI+4g77CPDD96I+JpPJZDKZTKa3hx599FG+4zu+A4DpdMq3fdu38Ud/9Ed87GMf49lnnwXg2Wef5aMf/eibWU2TyWQymUym+19vETf5e16N0Wh0Cfh24FPA7nq9vuZ2XQd2jznnOeA5ALa+8eteR5PJZDKZTCbTW0+XL1/mM5/5DO973/vY29vj0UcfBeDcuXPs7e1tPOeFF17ghRdekC/rL9+rqppMJpPJZDKZvk66pwZKo9FoAvxT4KfW6/VXwn3r9XrN0f98pPteWK/X712v1+8lesc9qKnJZDKZTCaT6a2k5XLJBz/4QT784Q/z0EMP9faNRiNGo9HG85577jk+/elP8+lPfxpGtg41mUwmk8n0NtZb5C9D79mPoaPRaAv5IfR/Wa/X/7vbvDcajR51+x8FbtxVYQ0s2qnQR0NWjjK6oMdP8IyX2DHONPjKllSGzhLPbNnYUZPgc8A3S7Oq28aAY7apHL3ugNeZRFV3XMifbAbf444B6bk4c46yMsPym2BbiIuIHUdkAxuqJeq4i6qQT7cJO6HlhxylMJ66P6zrsP8ciy+iZZc9z8CLaHx9lNPh+0uvoWWFnM+A66acybpMuzgN26bxzYBsfZRL2ATvzebr0MBtxwuK4gYyx3BxPKBYOS2NfE+oYQKTc/uePad8EDI4897r5FHBjDlNI5wd3zdx49vhmUVafyRHo7jt91cYH8fYrcoEHpP6K2/QjxngVNwK2xLXf3M6bm0GxK3n6OQU5FHBFS563mND1HH6MskvSmjbyMf31KSAuKEuU8YUXRvL7tXjFsVdO5TZqZyeqky6vHM5GcctaCz8+DiEgMmZR461k1Ww3AIc3xTJwSfO/D5P8BKxy08QJsvUcUJpYrlO07FvlW/l6xvmq/aLjolwvFyS48P5LA7msLYRDgzLjocLXbktsSNI1Z571LYRN5l1bCqtx3AOAc+T9XUH2MfnLpnLj5A7qtzEcDyE8+zclRPmYFj+8LqNm+fC2AzPGc4lbttYc8XFcJwWLNopy/mUd9z8w+5aMyBeB+U1vXKOrecm9luG5wkBUG517QVhqw3Pc/vaJur4lWj/VUSR5NOpWMZXj++lMQ7K8feF2LGA/wJQwu0y8f3k51OfS1EXq+G8Dx3HU18Ncu8L7x3BPadY5vL9Z3Bz2lrqNWQsD+MHoHVBeFkRjcynDdRVIn3m8jufrDwP+3D/IbggbZHZKOpy7++7sve7cnusvw33wB5PMGxjNjhPGYjDe3s8ON9QTW+qDg8P+eAHP8iP/diP8SM/8iMA7O7ucu2a/Aela9eucfbs2TsXdIqjeXEnhXP9YO33hkrn2eE1w+8hO3IT23nT97DcTfuHGrKiw9cX2Dz+w3NP2tdsOC5gN/auddJ1vlZtYGwe+fy1nH8333XbcK46Lo823RuB/5+9tw2WIzvv+35zu293z6CHaOwdAlgQEEHuSstXcRlvKDtmRaJoyY6siPpAU7Kd1KbCFJNU5MSSP0ixWbbsUil2VRzFcfwSuejKluxYppWypMgu2YyouCxFFLUqLkW9bbQUV8YSXGDvJYY7g5nuvt2YfHjOc87pwb0XWJKLfeHzr5q6Mz3dp895znNOn24Mfn9Z9zCMZYr8H71ILRlLpm6XxK+vekezWzRTx61LbzuHHufL7wLrUMrOPTsxZnFSuzWPu+dauHLWTJhT0dQ5V7jEgbtQPtk8REtORit1YupXPSBl5UUbzlHCOa5za37Kn2vIAg1rR+UianuVk0kp3MuYd+/XU1vq+4SGjAXTiLPYOzLhFq+xC+u9FWNax2IM8U88u1EZonot1HKV57irTNSoTjEfVHmXWqbSQyvmrJh41uJRTMkQK+nDz/IgE1asmUj9Oly/dv5cev44j5JUOIo/9qU/71ihScRUDFzYuB79Vp/sse/fe4anxp40MEBT4dhqucok3e5j7Wfts4Tefc59m7WvpJ655MESDudTz8YMzNNQfui7FGVyalzifvK80a2+0zVhrL/4yz/mcyPe18999a7cK3RRYR0sKGnJyFzrVkz8PNCThvzokjBub+PP9r5vui6RMggMTO3P3+Ytvn4dMh5iBmlc974PuaL3x9c4G+V/5vu4nsvcoGNEGaU9oS49iWeHat3mnGHJlN7dlyZpzxUukbq+1vnjAld9mZo3ypftOomJjBN5r23SOa1iTkPO3N2Yt26cUY+ivE6JczzmjernnMaX/RQP+Bjmjgkcc26lAiPifI7jus0H/oplBkp3p5H8U/tHgN/ZbDb/U/TVzwKPuvePAj9zL+pjMplMJpPJZPra0Gaz4YMf/CBvfvOb+YEf+AG//bu+67t47DFB1z/22GO8733ve6mqaDKZTCaTyWS6h7pXP1D9o8B/CnxmNBo94bb9ReCvAx8djUYfBP4A+MA9qo/JZDKZTCaT6WtAv/zLv8xP/MRP8Pa3v52HH34YgB/90R/lh37oh/jABz7ARz7yEV7/+tfz0Y9+9CWuqclkMplMJtPLXPrf5F/huidN2Gw2v4SE7Ci9917UwWQymUwmk8n0tad3v/vdCJr+dv3CL/zCPa6NyWQymUwmk+ml1j01UPqqaERgd6awM7sZGHzK7UphynLI9nL7KA8w5p4NOGjx9pgV51h1SRo4NpSOC5H2gc/hOKJUjm22zc7bPs8MDtiDSriDXsoQ7Y6ok3Le0oj9UBF4m/p9fJ4oZrpNz5cX7ZCZVgIXhYOS5e3RXDzP79uEeG6zh4g+x5zEOB7b5ZXafwsmrBwvbsi3yGiHDEai46voPI4jxwyfF7ly7+K6xe0Cdst14E4VTRT3qN5bfFC/Td9XUJQrdopW4lsEhssEYX8qIyaj4bM8AClUp+Y+VmO3HzOY5gvHH7lBXrRkeSuMlKi/EzrPVsxofR3HrJgmC19uQj9kVpYS77xofR7F+QxAuSErGmEJVm77eQKftILdoqVkMeCgPswTt/OGinBOCjcmXU5mRcNOIfzNnJZpvhAmSkV4ub6q9ua352Yp8S0e+aKwNf343/gxsKvt1BzRJChCvCas3PGS3xNW5LSk9FzlAismPMWDEUu0Z5ov3DzUMD61DpxYhFM0KdfDnInmhEm5OpoD9oz8zR1T9l0P/Ruyog31KlcDtlZPGsZsGthTWcQYSpI+tFfPo1xFF5NxvhrwXQd1mrm+c2Hz3EjdL2aGRuH1zLkCdrbnnJjHWCK5lSJ8x9QxmuIxRrS/H5ObEMNSqxvyuO1z1s2EC8lV7jt/wHPTrwv1rYFuFOaMp3cZyNU9dWPHx8bX33F7HXfasypToKx9HA7rDM+sLV25jhFM6ji1EVc1pwlMsTRwiP22ysVoifDP3DH+WlIg+aB9p0o3Qidy9U7SXvbrGMzpCZ0fc7tuHvO5UkEXsT39/FlIriZpdxu/eM3kdo6masDk7tmpbtL2+XD+qKL2OF7xF3/3dcNxVUNWtIEDp9dGX7a8lGXsz72dh9usz21WqObhDPgRwjWwjt7H7du+Fpteueo5mYl5FE9Tc+du9j1Od3uuu5Hufzc/iTiOW3lUve6mrOOOvROX9Che6FHnPa6+x9XxqDXucXWP3x/Fvj+q/sfV6aTtx9UlXmvfbRlH7fvg7tGxdH8bxz3stjiHPQlZ0vDMk19PQ8Y4X3kGnrZX+ZMg75uIsdg5hmDMqAT8ml6PWzKFuWyfsKLvhGGY05KkHQumrBizYMrl/GnPClVG3jXO8QBPQSdlrZYTxxIV5vRVLsi1OQ0cP+Vn+ri7eMTXoI5E+H/PRDHb3s99vtUJ12+1HLv6TnxMVkwcizIP3N5IfSdt7RGPgIbstlj592k4v7ZjwAXsttiOEctR2aXx8WvHXNTz6D1YQxa1NR30Ya9xAa5z1l1j1+SOR7l01EYJbc9zf3A/E9Y0tXA5/5vTf5c5Z/zaRuoQzuGZnY57GedmS85lngaEL7vPnlvFC7dTeIvCd1UW5MqxPDUOwjrNXHkZMZe18XHQyPU+rm2fs6CMfA1GjvWa+eMal1e3M0lzv0b3HP5YUQ5qX+hx0k+Od1pHjFG3f0Iv7W0ySDfkRUO1N+f8H//9wSmUvznnjGyo9fhuwKHsSFg3Ln/rYb/r2PPtqEcuvuH7lbuzSuhIXd4rm1PqIX1ADavlmDUTR/mUV8yvTenDGrhLZe3uytDcFJ5uWD8L8zOXNTiy/uviHu0Sz/fson7qXN2V7Umdh/mizj1PVOPkx04vOXaNsywouc65Ab9TNeDb9uG80sepj6nGV+cQSQ8Xh47AkY0Y4JonMZe4JQts2K+GzEDJZDKZTCaTyWQymUwmk8lkMpleOXqZPJM1mUwmk8lkMplMJpPJZDKZTC9rvUwc4b8S2S9DTSaTyWQymUwmk8lkMplMJtPXhOyXoSaTyWQymUwmk8lkMplMJpPpZL1K3ORfeb8Mdd4OWdJADbccDNfDi50WkUtB7kxlFN49gLZ2W8fq39goIto/TR042ZXnTYiic2S0YuKQtwF2HgPiVa78zJWR0Qygtj3p8FhX/k7ae6OPABHeKr+OjlFt10XP00W/cY7iktBJuwrwgU+jcpaIwdC2GYbGrSNAxo8yntjuBzWA6nRT4vHGOS0T1tLviHFGQgfvJ5iegBiP6Lm3+o5ajELo3F9VDBKu4XA5jvrfgd+XrrwZt+fOdhmuHq07dpyvfJ0pBYDcRNDkM+qWAqz6iY+vQqA5f0hHwoSVAM/Tnr4X4y6WYtQEkotqHJbReFOQCWv3+TAYh8SGIK7uWk9SV1cXuyxpIO1I017McjTO7jVlIefX7yo5R0LHZ3mAs1z39VBDIYU7D2LZyfjKioamzjyQOqOV+C8JnzuBTZ/+r571ZeydOwjd+Jv3hXILfJ6WpxZi7AJiQtRJz0DqjX8Uny6x6BizdrnYM2bNG/gcGQ3v5FMcsEd1ek5C5+DuMj4TOp8bY20zhLlGzXPctqbOBwZwgzHUhf0++Vvf7NrlgOF15gH89TP3CWj7S6Xvm7WDbbcRDD6hY8aBg8lnQ7MYF+d1MxEYvofCR7HsHMTbmc8kathGVIaO87h/l6Et02oxNKaJxqjfP3pNWIVxHhviaLw62CmdqZHmSrrxuUIHVTJnmi88LP38woHk911/FIfB/ObBLUcDN+/p3O7b2Umci2ohn12/6piQOSJHTc7KaiHjVfu6lON5Vsqrf/M+yctKD0/c3KfjJvEGRxmt1N2ZxhXnv+jjsmLiryvrm2P4H90+lTO+qkd0JOF6Fsvllofyu3Ye1lloeyn1nZTrEB/X3r4ToH+a9lK/H3axqkO5Psc1hsXGz7F6Tbs1P+Xn+5Q+zMWqQswS7nvT5+U8Ok5m8MWnXhf203zQusfXoNjsKP6r7dGci/M4zm19r8dpn+oYiE1z9O8S0ytdX62V81FGOCe9Xsjxd/r8Ql/1CeW90DZs/1VtrQeObetRMTkuTtume0ed/6R2nRT/9Ih9j6r/nco9qT7b+xy35jwuFse9nlqdmF99tOZUoyI1GFo3wYBjYHLnylGTl2Cwk/j8iU1BYgMbXT+pSU/j1jUNGfvscViLKVBDRr0M6xo1oVHTEzUYSej4DN8InVxTDpdj1s5EhRT22JfraS3rZT3Ol1FrW3LXNLkbadXY5Ij7cbVZcQAAIABJREFUADVFDTGV/ev9M77da2cic5uZSlTOmgldl5DRotdDNWpR46B/y38Y6uqMptQcRe4xcg7nU19/NQxSYxY9t27X2LRk3KAicWahc6qhsVQNLPHb9NhLXPHmMyULqGFOxbypvNFPE9WFetetxSQuf/MPftCb9qwYu/7NfC6Fe92RN8pSEx7tt9aZTHnjKRcfyd3c1yn+7gYV57jm12x6vraRtXXnjIL0r5ohab4uNMa6BukYmP9I/HJvzqT905HQ94n/Pjax6jr5TnOiRe6HGjJnKtz5fGv73I2rZNBHC6bhnrcesZxPObi2x7P/4o2ylopNl5A1tjfIqsP954Ipt+rM30tQyxovNhnqSchp6LtU+qML/eHN09yYDIZJIX+kfR2x0Y8qbpeaUjXkXOeczweQe6HY9EvHkc4RLbmYVTvzp5X7TmPQdYk3vlpH5lo9KSk9E1YsmPLhB/6S5HA/8XUK7QlzmxrVTlhzwIwpC1f3zI93NTfKaXz+NVvjSoyfgnmWGostnSHb2pkh3aBi0U8H80jfh/HektHW0Tg2DfQqeJ5rMplMJpPJZDKZTCaTyWQymV5U2S9DTSaTyWQymUwmk8lkMplMJpPpq6PRaFSMRqNPjkajT49Go98ajUZ/1W1/w2g0+tXRaPTUaDT6p6PRKHPbc/f5Kff95Tudwx6Gmkwmk8lkMplMJpPJZDKZTKaTpb8M/Uped1YDfOtms3kH8DDwJ0aj0R8G/gbwY5vN5kHgBvBBt/8HgRtu+4+5/U7Ui/0w9E8ATwJPAT90xPdfB/wi8CngN4DvuKtSKzwno6gcm035h47XMmHtmV0xx2XCWpgKqZRDimcFemalst+IykyFL5gor9N9r1xDcMcVeJ5H22RDHulWHfW7ihuwhDlnfPsohPcAePajyvMui4j5kYY67RZt2FnPFbcH2aZMwKbOh/vUwDPCvRuzEqYdo3COKIF30j6w1pSRdt7tVwAPHhHL+DxaH22nO27KgpTec0oyWs+DacmYIxwa/hHCaqmj8mKWDwiPz52zqTMoNsK/295vjmOCRtwf5WDGsezcvsqT22ZpudetLuFW5/hKUf9V3GDC2nM5E3rhapawl+xLG2YSgz0O2C3X5LSMWQkrNenJkoZJsoLUsflcnid0ns+p/ZQ4+gsudzPaYb6Xcq4Jax+/TBm4hYy1olqw/POvpTo1D/3vyshooYLxqbWUUcOV5pJjmAZ2ouTTWuI2qyXvYz5pAdWpOdPTS1LH3dzjQHiJBdGY66CCc8k1vvSPzvu+6XvhAVfM2XnwJvefugoFlA8+57mqE9bkRQulY0ymILP5ns+/PQJ7dKdoPZuoYs6EFVe54PpjzR4HnmerXMtptZBxWeD5LJ63o3m+xdXsuySMIc2VAngbwzmsqmXfdMNO2nsurfTFJvBoSunbzLGTMhoSeseHSgd8LM85rPCsysV8yoCvrPPlH8OPzwGLbYux+MY/+1thDtgeG6njKc8Ic298Hs1LGI7n+Pv4fSd/b9WOd6Rc324U+Dgpwj1rpsIYS3ueHb9R9nsYx3tKI2bbFkfZtUv5QCvH6dG6KfdK56BGWazKq3VssuV8Kn2mx86By8icWcHOgzeFgeTYaqm7vgTOUArLEWeYc9DshTh1bh5I8Qw15cGOT63hw7JP/cfu89e9CWvfxCTt/NxBunE5FrGwC+Gd+muha+eANx1z51J3XYnmCYpN4OduL4Tqkf8crmnCRvV8rkKY2jrOqWUumO+7IFSwe/55x4CVi65ei7UeO+XKM0t9H2rd4/6O8ygej/FY0Lyogb9OuH64OTU+72DB9yr4L0Vf8+oZssljrvL2S79bbh1zJz7k3byOOj5iM3udtH2bexl/t71t+3xfTv1T4E13aM9R5cIwbunWfkdJ231SPbf77qS+/Gr02XZZ2+Vtc7O363ZSO06KnUrjdnkyvIbHZTjm4IqJv8dRZuaKCU2dcd+bPi/r8TrzfEKVsvuUcdmSDeY9LVtZ+MrH8xxDrVOB5/rtpD377LFgyk7a05GwxwEJHSm95y5WzD0jUMuI1ZOEbfNd6Bxf0dWrIZfPbryENuSeUalltH0+uEbEbFTmQJe6tVvHgpI5lef8XeMsjWNBDuaQJZ6zeJX7hQ+4r5zL1MVqwiM8Hurq1tE3Ih7mgil0I3+dU/5mS+bbmdCzZkzgksq1VpmbC6aOVZkHJqIbw0pUVDbs4zxCS8YBM+E5uj5MUmFdHjDz925xXxwux7R9zode/3d5hF/3rE0grFn70PYPPPQYC6Y8wcM8zBNA6KOG3LFPswHnNKH3fab5sWLs2j/x51O2KcjaRliYrfcg6Un8mqxH1uyHteNARv2n/ay8Si1/xZgZ+4F3WmfktH4s6LqkrXNWy7FfZ3WuL3pSFpQyfvqJrOuT3t9rJPS+f3TM+TWaux+NvQqUj7liwj57vm1x3seMydUy3Mu2Ln+Ug5rQc1hn0o46sD51DZbS+7695nif2r/Sb3Jvove0GrMJKzIarnBp+LxF5Z7N1MtJlDedL1d5x8pcJRXaqeZHi9Q5TXt6Up8rmgcxk1b7zedV2vt+VqYyhPG7YMoBe37MSf9NXb3x7b5BJSzbLvH5qDmh805Dzj57NOTMHdNXuioJfVIHXuq3v/VnfCxkXGd0XeLLPvaa+TLURqRX8l332gDfCvyU2/4Y8N3u/fvcZ9z37x2NRqOTzvFiPgxNgL8D/EfAW4A/7f7G+jDwUeCdwPcCf/dFrI/JZDKZTCaTyWQymUwmk8lk+nKVfIUvmI1Go8ej14e2TzEajZLRaPQEcB34GPBZYL7ZbPSx7jOAOqe+DrgC4L7/ErB3UhNezN8ovAv5Raiz7OUnkae1vx3tswFe496fBq6+iPUxmUwmk8lkMplMJpPJZDKZTC+d9jebzSMn7bDZbHrg4dFoVAH/HPn/JV81vZgPQ/2TWadngG/a2ueHgX8N/DngFPKfME0mk8lkMplMJpPJZDKZTCbTy0n32E1+s9nMR6PRLwJ/BKhGo1Hqfv15Efi82+3zwCXgmdFolCI/tjw4skCnl9pA6U8D/zvSiO8AfoKXvk4mk8lkMplMJpPJZDKZTCaT6R5rNBq91v0ilNFoNAa+DfgdxHPo/W63R4Gfce9/1n3Gff/xzWazOekcL+aDR30yq4qf2qo+iDBDAX4Fbx1zgtwT6CQRMxhvXFEQDHSq4b4TVhH01wFmq00A2uu+BUPzpKgMimZo5OFMERI6srz1JjLeAKKDs/m1UDc9Lt2Ez66uc87A+WE9uYgY5MR1cOZF1V4wsfEwcDUrKSAvmqFRU2x81IX9pizEMGZvf/i9O+eEtUDIL7ptemxsXrK9PVaBmBexdZwaUOg5nQfOwJQCGLNmzIo99qm4QenMeDJaLnHF19+Xp32v9QUoDr3ZBqjJSBfMQGI5A52yWvhMLKvoHGp4onWcHYZ26Dkvh7rspD27RUuVz4MpUaFxzTjHNajgAlepmId4ziSmgrVeMa0W8j0CnJ6wIqf1dc6KFsrDgQmSN7hx5kiAmAjVzhRlSwm9bH9Gzt2T+nhcSIRe8fX/4NMCht4yBxGDo0MgQMWTVOp5mael3E7A5d5QCaQNsTlQGsDT5amFg1mLiVJs8JPTHmlqMk2krxJ6plU4z/LZmTeZqrjhzXsSNcrSnHH5M2HFjAOStOdWnXlQvCDkxYhowZQJK1oypizIaSQOFVT5XNpcIcZYICjyZHW7iVgl+TmtgomUH8sgZjBpiNG7X/dvGecr+KkRt+qMrAjnPX3xGhPWVKfnHuauQPzUgco1p+J88jmseV5LbNbNZDhXRcYOHtRewjRfhHa5v7//j98qc+k8SjIFzO9H+cMR7+MYaN9GJlt+H31/Xur02td/QcpWzHZxGObHlEHuVck8GKA943Ig7aK5upc53+W1n0eJ+iMyb/Jz7raBW7rh9GzObrmWPprNQ96p2dzcxWUOt545JdeIVPJwzNqNeWd2VDRQHTJmJfOKnk+N8GrYY5+UXq5JOIM+Z6Sx+/PPu2tcfds8oGN+p1xRzaRsb3g0iHto6zhfDfs+6s9pFeVFB3SOX+4MjHYu3gzHlId+Ozhjvm7IO98tbh/3PSmTcjXMz1ri3Lq897n958XUjvMEczPNtYqwfiiA2cYbIt6Wj7ERTInM1z+MzCHb37lYDMb2KwhcbzpG8a8h4nXNcUq3XttGN9vHb+9/t7+8SAnGXxweXwe4s7HX9nmLrf22/55U9+22P00wEYwVj42j2hzX6STTpKMMik6q4/b6ND7PUX1w1Fo33n5UGdv1Pyl+d4qDtumo/e40v2zH8Kh86GSdKGYbY38voAYjk3LNF3/zdfQklKcW/loTm+vA8Jqr87uaj+j1Rw1FMreu8yZErq6y3m1I0t6v+28txbREDYZiQ0h/j4e731sGUz7dD5yBYLkZ7Ns4U5WcZnAfqGY72q6eBJ6NjC9dfWMDGzFSciaOXerrkCDmT7qGXzE+tr91n53IFLclI6P1ZkF6XxG3zce3OBxc5ztnsjNh5Y1VxGQzpfMtTMTMxb3Xc/p7Dpef3qQpUu8MsVZMvEGPaspC1uYspM6dmr/03qxIzzdh7QxwZP2tJkDaxikLVkz4Vd7FiokYNkVt1DzVNnmDog4OttCBY1Zc4xwLpt40Z8WYrkv88ee47tZUMiYyXe8D1DlLprKOW+INsMQwJ+Tj2hlnXbl5Sfoc6Dpt70orH2IZGXqm7v4joWPCmtTFZE5F3yf899/8l8loxZzK5U9D7tfy4XqjhqIdLKXtahikORDPy2tXT7qUhpxDNXVC7md7EtY3x+58GXTOYCpVI08xqGrIuJ+rPiZqkNw4kzWQcfEED99mxKb9OWXpDYwSOplbon130t7f1+hYy52Vmub7Bb7g98+cKbGO6dVy4o2XMlpSFxU1/NL9Ere2zpKGQ3d/KH24DiZWUf0B7ucql3na95+aTMXqSFjMp75OOY03DWuicST31Z/zdWnJ/T37w6ee8GvPjoS+S2j7YPgE8C5+VUytvlrr0HvjJn8/8Iuj0eg3gF8DPrbZbH4O+EHgB0aj0VMIE/Qjbv+PAHtu+w9wtIH7QHe7xPpy9GvA1wNvQB6Cfi/wZ7b2+XfAe5Ffh74ZuRw/9yLWyWQymUwmk8lkMplMJpPJZDK9DLXZbH4DMVrf3v77iD/R9vYa+FMv5Bwv5sPQDvg+4F8hflH/EPgt4K8BjyM/Y/0LwD8Avh8xU/rP3F+TyWQymUwmk8lkMplMJpPJ9HLRCKIfnr5i9WI+DAX4l+4V6y9H738b+KMvch1MJpPJZDKZTCaTyWQymUwmk+kVaFbkOGxv4HPwRMTPjHlfacRuIbAzSPFMECBwxpTv5crYqW4O2JkUh8IrKxx/Y5sPpIoZSCnCctD99DzbPE8QdmTtmBsRn/LIusafla8TM4ZSx46LOVQxEwmG5XcIG7CO9nMMHOW/eAafnrcI57tVZ2F7XM/t+sbnjhg7g7or99MxL3MaUsfE6EnJaR1zY0XjaCC83x2jbELl722zsRxfqalzSHtWy8mwHstQLx+/2jFdtpleMatJOYjKEq3x8coKYfVovypzpydhzFrqv8UKOqMNKQOjZC/Z97GYsvBcksTx8CbJClKhqwjrqJa8KGs3FoSvkqbCh9TjfOxL2cf3hzJ3oxyblMKpmXngY9gno2W3XJPQCbOlgrcnn6FkMWAOem5JKvw/5S4KcxTHFb3BlAXrZsI5rnkOi69bGXiNY4a8Ql8O8KVn9yTejqWpx68RLszO7KZwHlPgQeBteKafcoGStB+wmqTvUs/cfJKHeIDPsmLiWUjKmdK+034TPk4eckbniuWuG7PZgMnp803z0uXEL/3Bt9D2bj7rUtLUlevKaMk8L4cUxzjtGLPy7JmYY5TQD9nJ7n3fJTJGtJ6PIDn+c7JbxQ2fJ55/o2NeuYiaSzrOlC38NscS25oH/Xs3x9O545eOK6bHd9G+GqcS2iYL2wtgvhs4WpXw86p8zooxC6ZcfO73PIdW6hJYPztpLy/lVMbjP2ZBlsK+7LpkOAequhF9l5Ck0sgk7eV9F+2n7Z/B7uXn2SlXwrSld/0ZWFp0CbvlOnDSotyo9oTz2TpW04oxFJLzmtuH3/kaOU/R0pFQnZLrZ5r2nu2bpD1tnbkxm/qY+7lQY9ARccQ2vr+VidrW2bBPceU5PuetZ06FeNW70djphO2Zbjz7qyfhcF/q3neJHxcTViz3K+GApnBYZ/5a0JA5JtlG8u/vS/y0Lply4ToGfDgQbup23W9jEMbjs9jaF8J1M46ZjhPTK1uKs73bnxPEa40t/rDftp1f26+7ka59OvDM0DuVWx/x3Zfz+aRznHTO+LjtuhxVZsrJdblTHeM63E1djys31hb/7dj9j9u2Xead2nrcubaPiaXXsKdOOH6JZ9Y35J7Rd42zfveehPl+Rfngc7RkLG9OZT2RhjIaf91KaXUedjHX74Tml/sylR0o7EHHm+wCe7DvEg6YyfVMWZwI11Dfd45jGW8LZaX+XMzhszwA3QhquUbGzM342tqSk9OyZsyCMvAnS7dui8azZ2RGMRUG6ogVY7f2KGkdgR6ia6jm5NKxT+ucFRN6ErkeRtI1XOf6R+/XelJHts9kXTjf9Wunxt1HbbNAV4wR1wu5p1p5svzEM0HDfUfn63kUp7WL1pik4vuQJIEbqTGIOYdEbdO6rVz/LZiyoKSpM9+nH/31R/1adu3iE69rNee0jfF2HaeaaxozzTu53wh819St4zUOGj1dl8i930hi8Qx+PaD7z6l8H2q/rJaTUMcujeqf+n681QnvUeeohtyzXXXsNO7eu60DTzLOPV3P9XHuKIfU/Y3zcHDv4PqmI2HVy3hrm0zWaQh7XfftusTnD6lwWjXOwlcV/ub/x0PuXl721zimbn3XkvEQT0InZTY+N/MoLySPlD0q99+BLa+MU+1/P3e4caLzgPaJ5o+2RecjyVPN2QmrfsKcioSe/+HTf42ETp6buLbHc1o8Hg7YoyNhzhkWTEMdfIwTf/6WXPj3Tsrt1edA4kux9m1RnnNGA/tS1hM3H5Yc3IePf/ZPAvBQ8iQrJtFcG/L7q6J7wwx90fXKexhqMplMJpPJZDKZTCaTyWQymUxfhl4mz2RNJpPJZDKZTCaTyWQymUwm08tar4InifbLUJPJZDKZTCaTyWQymUwmk8n0NaFXwfNck8lkMplMJpPJZDKZTCaTyfSi6lXiJv/K+2Woq/GYNQDrm+OhQdBSXh7ojTNWcUYeOe0QRF5uvU/h1nISmWrgwdu3AfBdeYK47XxZCvbNaIKxgpom1Hkw3XFl9A4untOG7c9A2+e8/i/8rhy3xJs9rG+OvdGJhzhrm2rEoCNlC6bPbSZAKWJ04U2htvZTox4ubsL2uP3AbrkempZ0t+/jDVrUe8eZeQyMJLZgujHMPHN2SYlD//Yk7LEvkOqfJJhSdFvlKig6NqFxStQsZRvimyLGOs7Iqe/S2//JwLdrd2g6FRusqCmJM1GK+1nNdbSsMWI+RIEYELkyMxpnfJOS0br9ejIHXM9oIpMOmY3GrKHOg3FRChN3HEgf7LMXjHGicTFmJWZCKWKU5EDcUxZkSUNGG0yAKsS0JIrrxI1JOniSb/DGVxmta6dg2SXnOqk/UQ4XkpMTVlzIr/IkDwHObKfCg/21jNQZKW2bg+U03HfxusQSMc1R+dhpvwBcdK+ZHK/w87bOuLWceCC2IuYv8zQJHY/wOAfsUXHDGdaI4VBGI33t8nDMio7EA819nQGKjYOcp0cbq5QEg6s5fPvr/yVZ0sB/smGnXNF1Dmq/lHxL6On7xOdk60wKYni2lxoLxPFzY/NWnXlDHgB+Kfo+NuXpon6Iyr3NUCaeW8rI7Mq1z49BrUs9Ctu6yCyrJhh86XFunvMmVPp9dSjnAah3acmZNxUTZ5fwzGu/Xsx29rltfrjVCcT+lhokzAljt3ZwejeP33rmlJiTaXzmYW4l3YjxUKQ03YpXFKsk7bg1P+WB6IpXT3x5vZgEEZmeubj2fTAMEPO7NcyH14ji578I+3A4n5LSCxwfAdYrqL3vEm+W5A0T3Hzm88TFfNFPXf+O/PbeHTs+tR7me7rxphihn13bi8NBvuykPXQj339i+lbLdTUXYzRKMT0oZ3N4Vo7bLVq4CF/ar8gduH+QS2kfGVukQ8ORaGjcWk5CPkbX8N1Hnpd5T3OvQObSH3ZtmRGMkuJry/Y6wPS1pZPMuL7cnyRsG+fE54n3Oaou8fHFMfttn+tOn7frcpIuMrxGxHE56VxHlX23dd8u+27qeSd9Ncrc7v+4jOOMmV7I+bbbf9n9ffrw9uPdvYcqmBTJOkhNPZK0Z/mbr2Vg8uruvUDWUGLGMw7mMG6O1TWIXqc6Z4yidfTXToBnxczkgD1v1qrmRj0JUxZ03owp82u32PyEzh2zHPntYuSU39b+1hkIxQa8aiSkhk9qvOLjpPcXHSEeUT+poczarTxCqGXNoIZR8atB1p6+D5aFN33R+ugaX6+pGo+W3JcdG4320Wqii0xj4jWh9veEFQum7HHgY9CRsFQjS92/T7xR4QF7zvBoKmvvWgxk1jfHYW3hjGK0D+ReeZfFXPrtF/v3MKfyZk6+Ha4Oes+zz96gn2cc+Hr0JPws3+VzYWBq4+5D9N5E7moyJqxkPeQMeFaM6buUBVNf5ooJN6gis5zQb3pvxxKYi/mN5oy+v845elLOnrvmTXQO62CCpdukXGeaFc0N1zjrzXritV/XSezV9EfH2T6z4dzizI/koBGU+Fjr+FsxHuTumok3cuqdqSUdUOfepKitczp9VrAsvBGUfn+ds6yYsMeB3Mu5MaH53JGQ0dCpWZCb89qt/vf3ZeT86LW/FMaorsOXExpnLKSmUGoyNL9ZMafiGuegHpHQDUySvKmRy+mUnowmGBUtx77fJW9Tb2ClBkjxM4uOxI2N3PetzxnC2jMYycn3y/nUzzFyv535MXONcxyw5/M5Hk9qLOzNny/Cux/4v30dM/f8pF5O+HX+UMhXk5f9MtRkMplMJpPJZDKZTCaTyWQynSx1k3+F65X3y1CTyWQymUwmk8lkMplMJpPJZPoy9Cp4nmsymUwmk8lkMplMJpPJZDKZXlTZL0NfIu0AacecCt4NWdEKp2uGcMQcW/ACV4VJVB0KJ7AEzsM5rgkzJ+0CXzJmhpWH7FYL2T7bQArF7AbTagEzmCaLwAsrhWc4Yc05rvvtF7gKF+W7nepm4OIVRGXj97+fq1A5NqIyxt4E55Jr/MFH3jTklpVQnlr4tp7lmpQTMU7zIjDVqKC8/Bw7s5uy7eKhbJ/BJa4MWVPKkDwPPCj8ygtclVipCgZl7+3th8/bDMDt91W0rTgc8tRgwG3cY5+MlpIFZ5hTMRd2JS0ZLdc5J1xLrbOWPUMYgJ4xOQp10/riuKAx003rhYuv8mbLiG2IxIUZ7Jy/GXJmmwvn2poVLeNTLjdA+DuO7XGOa9LfpfA5J6yhdH3i4jBhTU7LlAUP8BSX+RyXuMI5rjFjnzPM4fwhexywU7Q+RsXsBmeYc3o29zl6gass//pr4aI7h3LvCo33ATNXPwp4gM/6HD3LdWYcUDEPMSf07Yx99vb2qbjBZZ6GyzIGLnGFN/A5yaESznFd8rWA6eml1KPEb6PQ8bQio+U7+JdUru99/AupD1UYNxrvs1yHsuYs15kmC2HKFDBvKqpTc19+T0q9nMi2i8C3AO92fXs5sE+bOmenFM5pxZwJK/Y4YI8D3sDTVMx5gM8yZcke+1JPxz09yzVA+EQVc84wl3loxmAs7FaSA+f2Qgx8v+hYA+nrSvrpDHOK2Q0m5YpJKTlCCcv51HNYYx7MFImF9kmmfNgKShYhb2uEvTgD0l7yM54fLgLfLfVT9irnN5LXs9qxVzdwEf7I93w8jMuI50oFu+ef5wJXKS8+d/wcsTUfVczDHB+Pt4h3OlF+sc57s7nkWAWkGzIapvmCS1wJLFGdNzsoo7l5t2jZSXt2inYwfyvTN6EfzOFZ0YY2lm6sV0j+lGuZkwu5fmRaptPOxZvsXJb5pH72PrlmOGZwRsOUhTBKK3jt666zW7T0JDKuCHFIkh4qqWNCzx77fr7hfwbOHzI7feD2Fx5xlUhcq9OSo5QwrRZMTy8hhSlL3/bylMSnrBY+/tNEtu1UN6XelRxP4Xij2k+zDeX5fT8HynVx4/tzt1y7ftsw44CsaNidPe/H0ISVnFd5rS7WOa3wn915qj0ZJ6dn4ZpBsZGyvw/uO38AM7kG+jWC5tuD+HWCjkt/nXIxOHz6NYOY+3Hxw3imMSkhV7eZuNF1xvQK1i339264W/Fcts1GZuvv9vuTXsXWK2IsSzm7w+3bbOgtHu6g7O06bH9/1Oej6nLcq4zOGx9T3sWxd6pbvKaMxu5tdatOOMdxbYjL5Yj9TjrupLKO69vteJwU4+17me26qnzOHg7XsDAopyWjYu7Z1crfi7mYMdMvLl/ZmDEjU/nQfZ94hn1HQtvnzhegPXY8ZbSod4Py7lZMPBde2YOAZ362EVsvc4zvnsTX+TKfG/gJCGd75WiVzYAH6ddVrs4dwnOsl5PgAYGLR4rcg7i+aB2bdE7FmNUgJjmt8ANvG7ug913aXu/hAH794nmfqVznhW05lvtjF/OWHJbBq6Ahp+2FIZ84Rn/jOJ7K1lwxYc2YBVPPGU2VXR4xJfsuxPMP8XjgdBNiqnnQk3rvB42B1ikrGj7y6e8jSaQNMft1wZS2lvot3faclpac/4D/F4CrXBjwSL+Df+FztnUsUeWUxgxHzRHt0961EvBs9rXjSmoMBjnv1sxPXnsI6kNZPz8NV/pLPMk3cMCeZ0VOWbBizLO//kYX71w8RNAca33bUAZ9J+W3Teb5uJpnadpLvNOuNl31AAAgAElEQVTe+RSkVNzw40y59n59lEbeH8UhLAneEjBk+3biTbJiItzJTtikzEe+77Xv1H9DeZqxOhLPCn2ayyT0lCyYsvT809SNVR/XDloXF99v4LmeE1ZkRSM5rrm4BLqRYxTnflzoWlHX52Espd5HQfNM/SF6x8+fcyb4P4CfCz/0TX+LnkTW9F04T5xF4BirTnsc+PzplF/rYpjQsefur3eL1rNHg6eGlKltacl4kofcfND5PFxQ+pxV9V3Kp770MOCYq3Xu5wnTUNuXSZPJZDKZTCaTyWQymUwmk8lkul2vgieJr7xfhppMJpPJZDKZTCaTyWQymUwm05ehV8HzXJPJZDKZTCaTyWQymUwmk8n0omoEr4b/df+K/GVoUS2EBfYJuJBcHfJWHO+mUR5E6lgLqfA/lfVSVIvAZ1FunGPs5EXjuBsjSIVFN0lWUBwG1hwMOC09iWcTZbRQC9cj8DoQflvaufMdBjYHwp3JHBeOApg7JkfMNXWPries5XiULzLcZ5yvYB7Fos655bgfLANHSlmqVTIfcpzmCPsEqcP511+RDxrfSLcxgwbMG+0wYAbFxS8GRhUEdtq+21YhfLdKyp04co1yQjMax5MUjt6YVcSS3IQ6XMafR/l7gO+T3Zjvl3Ib52TC2vMOk7QP3KaZ23cp++1efP52llMVyhFupXBE6BznpxLGpvKJSIVFovzMjBbOH3qG7B777LHv+SYJnWM+rilZBFZoJezaKQvywsUrb117Vp6fSeraF+dMJeWOWcHTeF6U8u3knBLzyrErqZE8SQPHcspS2lTCN/FJ9tindLxKOhz3RHi2MbOxJfd8rIo593PVs1D22A8sXcfz07hNWPO+P/pPfP9c4gplJXzFeVN55uWl/IrPlT32yWkodQ45jzD/zoc+nDhGofJXlGNaMSej4bM8wIKpHO/6ZMJaYnP+kDErYS26Ovakns2kPB/NySTtoHDsnq05zLNwijBW//mXvpuMVvq4aMmSRpg6pfAkwY1n5HjlzkxYuVaNSej8vBFzeli6HE83lNVCcmBJqHM0ZqbKGi0kL4pyJeOtXEFZ8yu/+q3DQRUxzA5/6TWMWcnYUp6jjhvX/t3Z81Bt/DyW04YxHs+HygkukTk6/h5hAcmc1Pn8XTuu0Due+0Ro90XHFXJxn1YL/4rHuOZtxTziTG/k3NF+mWOOTcoVSdLLWHT8zixpQptTqGbzaJ7ZeD517uhWE1YS71p4a9NKGMDKtg3jWvJL65jSw9xxsn9Ydm3IIRWm5pSlH2c+D9x8OmM/zJtd2E7q4uTOqcy2JHXXusJdf3B9pvXrRjR17ufCrGigaHwM42tuQ+Y4q41ve0bj+8fP+64/srz1831OA2VN3yWetbSrfVge0ncJp88fAK6PNIeUqaX9XEp/7iqHNp7jdSx00bFE74/iNNYEXujdcCZNrwwd9XOCdGv7UfvoeIr4e1997Z5ch5O23426rffd1vaTyn6awVrptmOPOoeWWR+xfVvFMe/jetVHbDvpvdfmdt7ocTqpnsfFa7u+23UpCJzK+Nij1t7bdYzfX56E47bLKeRe6SoXmFN5xqIyIfsu8XxqCCw+PV75h2NWjs+XyXdu7awsyqP4kismsHR/SxxTMPP7JPRQy/e/ziOe6bhgytoxJvWebME0cDvBUbjzsM3Nz3oft88eDTlXueDjpDxG0P1SYW7O8cxHf+8Vr61dH63c/V1DzhW+zrVxzA0qGjK5JmouunI0Lue4Jm0o5d7zgD0SehpyrnPO1a+FucTmGmfpSelIWDP2bYbAXMxpyBJhHMoqWLiacRy13Ctc8vzRLmKWxsxQKVs4jSsmLn73+/5XXqMySDWOvs/dveyffsc/5J18yvMR1459umbCrfkpWnLHPZRz77HP/8O3+Hp/hrfTOgal8G0nYQ2Ci2kN++y5lUHiGKsTfx7t79bxPBvyAUt07Piqmgc6dm51CbD26+XFfOrb1zk26VUu+HxcM3ZjoiOlZ8xKPteuvzSvapefqZxPmaVjViyfnfl4ajtjHm2i/Fotpx6xWy2477s/L6zLrXlrzYS+TwbnhMDvbOt8cL+s4zVJu8Cu1TzTvHefGzLGrLjOWa5zjjVjMsfL1Wc1Gmc6XH9LTq5d/5zluuOv5vy3p/92WNu7+pJuXC91rBxpXmPfd4l/hqDx17q1uHXpcuRzWhi3Mn8k9LR1zpyKnoQf/+X/TvJmOYF6NOC49qRu/klp6syPuavc74mia8c0ZhnOf52zXOWCv+dcuXanjq+r9yz6uszTvs/mVFDL+520989wWjIO64x6OWHp5sSdcsVlnvZ5YwqyX4aaTCaTyWQymUwmk8lkMplMppNlbvImk8lkMplMJpPJZDKZTCaTyfTK0avgea7JZDKZTCaTyWQymUwmk8lkelFlvwy9e41Go2I0Gn1yNBp9ejQa/dZoNPqrbvsbRqPRr45Go6dGo9E/HY1G2b2oj8lkMplMJpPpa0N1XfOud72Ld7zjHbz1rW/lr/yVvwLA5z73Ob7pm76JBx98kO/5nu+hbds7lGQymUwmk8lkejXoXv03+Qb41s1m8w7gYeBPjEajPwz8DeDHNpvNg8AN4IN3LGlHQL4tGbwbrnEuMrIIu7VkzuxAoLV0Do6r0GMI4PBl/HnrEXcKSRIg352Cwh1kPGCY+wCVVjMlnEFKBC1v69wBf3uBsJcONHzeGdsoFH3mTji/PQQ9CTvOTKJ3hlAeJOxMgo6Eu3uzIZyhTRfMcmLQe4EYyoDDjW/dHNzJNGAbGu+g0vX+mQjkHplD6TlTKGY3/PEKF1ds8FTNeAiAdt6/CSB+hZarIVPqwNa6XUHpXeLh0DH0neoQagLsXRXv486TFQ2H8+lwH41DsYGyZsWEts+ZcwZmrp/n8leB7aQw7RcetKyGJmqIsmDKGeZMWLm/axcHAW9PypWYhiBgdDVPUeMcUpzh0op3/CUxjDnHtSFQv8aVu/Ttq5gPDJcGRllq8nOeQZy8KVIhdWnJyWnZYx9mBNMrN27OOBOaCwQTtM5Bqy/wBWdctGaPA2decuiPVZOaONc1DhBMXEgFmJ7Temi+Aq8TenbO3xTDrYvAbMPO7KaHSxelmPzo/qpv4Ekq5jzFA1TcIKV340TO74HczjQrp3HQ+2wY9+OMHBxQfmCkFOWY5kjfJQLyB4FxNxEo380FCpdX+LcC2BXgvWA6NHoBqMXsZtVPQu5v1WfF2NdnworcQfKTtBczpZLIkInh+HDGO1neQjw/RmYOChKnwpsDUI+Gc8vWHJfRhrGOGP2o2Rb1Lj0p1zhH5cbTpy/9YW/cRQ1tnfny1BhgoC4Y8HlzO6dVLyYJ3/5jPwNLNXyC1XLC4kulgOmRHO1JgwHZRfjiU6/jcP81YuDjrlOAv7L4/CsQ44Okvx2AHvWRvx45zangR6Cczb3pl5afRuZmWTTPD3I+1f3TYf7iclHzFWCJh+ln0Xikk5jeZrinZn5FO+jbvkvoOjE3iOH87Ify6MSUou+DeYdchwPQXud7uT7uhn5VkwqF7xfAsyH23vhQ67iVv357FIsjr7nxd2ac9JIqz3M+/vGP8+lPf5onnniCn//5n+cTn/gEP/iDP8j3f//389RTT3HmzBk+8pGP3H2hR/XpnUyRuiNeR5V3J3Oe47R9zPbn4wyFjtr3hZznhRz3IHdu/510nOnS9rbjjJ7upv7H1Wd+RFnH7XunNm3XYzsHtrfH69H0iG3FEfufFKvtOcrdS6jxo5rRqNHJigm3lhN4Fq5wCQiGOH/h0R+BZ/GGS2ry05D7ubbvEr9Wasi92cx2DDIamMs+Ykqr93CJu84EQ59nf+GN3iRpQUlDFgx/lrp+wK9+wNXd3W+1ZNygCmZP4K9NYspUemMWILqmjCQX1GhSY7wMsWybzJulrG+KEUp8TetJwzXG1UfitRuZQKXuuISWzF/HvXmvrk2QtdmcM/6eVNcbjTOyWTFh3Uyc6VHiTGn0Ti/38REDm7E/xtfZ1bMlo6lzH2ft816NcNz6c7Uce9Mh7a/GmXNKv+zSdyn/5N/85/57zbee4TpM29g445mGXO6znKlMgxjd/Gv+uDdHWjHhgJk3slJTUzWjAZxFzYo1Y6kzY6hHtE3mxkHn+6rvpQ80F1gCTxfANZ8Dh8sxz37+wiCnMhrWzcTntOTmLvvMgqmOb+hI1jFuPdvWci+sdW7J/VqxqXP+5v/2YVqyYJpUR+NE1zzlIYdPvYYvPv461Bx6zcT325iVjEXXv00t/X6rlvuJW8tJyOt65M2MevfMZNBXXVhfXuUCPSkzDny8da0ZrJEzZ8A19jHUvNL71+uc9cX/L1/6cyEXtQ/qEZkavKF1Gk6u+ixDDbRCPcRU7Ske9Dk9ME5Lez+/HHVtUXOk2OhN8jRnxZgpS5cHMpepkVY8t/YkMB9enNUMTvNe5rzO53bj+o5a6vDQuSf9PP7JX/jmQVkrZ0T227xF5tYt4+ivSMlX+HoZ6J48DN2I9JHjrnttgG8Ffsptfwz47ntRH5PJZDKZTCbT14ZGoxFlKf+CcXh4yOHhIaPRiI9//OO8//3vB+DRRx/lp3/6p1/KappMJpPJZDKZ7pHumYHSaDRKRqPRE8B14GPAZ4H5ZrPR5+zPAK875tgPjUajx0ej0eO0z92bCptMJpPJZDKZXhXq+56HH36Ys2fP8m3f9m088MADVFVFmsovQC5evMjnP//5I4/98R//cR555BEeeeQRwNahJpPJZDKZvoalzNCv5PUy0D17GLrZbPrNZvMw8h9S3wW86QUc++ObzeaRzWbzCNlrX7Q6mkwmk8lkMplefUqShCeeeIJnnnmGT37yk/zu7/7uXR/7oQ99iMcff5zHH38csHWoyWQymUwm0ytd9+xhqGqz2cyBXwT+CFCNRiN9LnwROPqf5Le0d+7Aszs9b7A4HLBvznIdCjh9/oAZB+xUNwcsxEm5hvPCIOSiHEMF5fl9xqfWwkMsa88/HLNip2iF63neHeOYgDGHjQLPqctoSdMha3FaLQJrzv0mdo99mBNYNaW8PP+jYMAnq5iTFQ0UCIMRoHTszCpi52lZwH0Xr7s21n77gJ9aEfh57jVmNeSFlggDcBbqVDEX1mYaHXueIYPwvJR//oHP+X8J2EmF5ePb53lCKVTCl6wcI1NZKqmjZ0xYc45rwuL86VFgvmpZZfjs+YWzjefpVbO5cFSKqJ4pMN+Fi65N2l/JInxfMvhXjNMXrw25cf69BDWnIUsCx1PzRMu/xBVIYbw8lH4sYcYBu+Xal3WBL5DRMmVBRkvFnISOM8x9OQk903zh2Sp7+YHjba4GbM3wuQ1xKqRvlMtK6drNYpB3JQsucFX4nVucqj322eOAPQ6kTo69UjH33E9qmLEvnNAUHuJJYTbNIv5rIe29xBVWjGnJqJhLvVIkLqXmnMTqLNd9OzJaylMLznKdC1z1Y11i1/h8nXEAyDjVcURVs1PK5ykLShb0XUpeCKGnI/HtucIlVkx4hF9nzhlXR2lDOZvLnOTaE/NhJ6xDHrnvJ66vPTeqQHjAbp/dtz3vxrTUM3dzUJa3JGlPkvTSVyVM8wW5o/DoXDBlwZQlexwwZsWKSWDbxHNFzA3dl3mqrbMwlksG+804gFLGcUIvjMVZTbU3p15OfPu8tPwK3vgnf8vVa+FyfRP2kWSjrBaOm0TIkaqWc168ORyLjpEzY39Q1yTpfft2Z8+7OeUGQMhjVyeWyLzv+KFfekZYZ4N5onBc5yLiIxWwU678PBGYxpKz02rB7PSBcKfTDR2J9JfmwbNw8aHf474HP89uteC1D/07YalePPS8pDTiea6bCW2TcYkrwgK9KHVXFvFOddPPF2NWlI88J8deFLbU7/+pt0ps0j4wmD1nVPitMW/XX9tSfOwmyWrwL7o7RUteNMLHLhCG59b1Z2d2k71zB4yRGE9PL2Vu1nlE9003wi/OF+RF68dSTkuWNPA2x32qNm4uuMFesr91zezJiohGVUhf8PehOjXnS/tV4LLptSzOpeJQ2vggVHvzQV6+/qHf9azvwbiIOH261rjt+5htbXpJVVUV73nPe/iVX/kV5vM5XSdJ+Mwzz/C61x35H5Ru1wv91UO87Sje43G/lHihv55QDu5xdd5mhW1/fiHni8dNurUtbuNxx26/Co6O2VHHFcfsc1SdjisvrutxdbztuNHwu+19Yx3Vnu26bJ87PtdJHNLteWSbC7rdH0dti/9qHnhW5piEzq05e1aMHTsygeUI3lT768OKyZCpTztgT/pzdHLtiXmNe3v7tI79GJfRk7r7lMQzsvfZk/el3Ps9yFN0JFx87++ROTb7Bb7gzusYf9E6R7iLwgZvyeBZ2aUhZ8rC8xMB+b+K7t4ip2XNhJSeNeOwLugI9z2FxMxvc8xQZTaumHivgh7hvS9vTj2PcsB9BUhlXTRlAfWIhI4xKzoSDthj4tZz2p6Unjln/H3SPnuDe6Oc1t9PaT0yWr9mEZ554rmbulZUdqzW2+cIMClXLJmyYsyneNhzwmN+ZFtLP8gdSzXIh20e/4IpcyoO2APker7Pnmdg9qS87Y//Gle4xBUu+bJWjvqpfMVv519JrvbCQ52wkjoVIR9bMnIav0bXddbK9bPcjwrf9gtccOcZ+3wZMFQBeN6z+tnfZSftafucA/Zc/SY0dWBKar9V3PAMyyPHtPMPWTPx9db4ZjTkRcMH/8v/dXhM6WKra44UqHclL5/Cc96VrbtiwoIpkzKs7ZQFSr3r5tsmynXJzQkr+k7qPmUR2LXunqUh92u4q1zw96wH7A3akbp7pBx371PWjq8p+aTs4ZXjudbzqZQVz1lLKbch97l84OYL5RIrq1P7cMGUxvFcdU7JC83hdMAJnrCiIefRb/57LJjKerfcoBz8dSP9o3OiPvvJacMYdzmka21dd+szJoqN56jqnCf3a7n3nuhJ+Qxv9+VlqH9Myu88+e9J+56Fd7z3E9Al7Do/h75PIN3wdn4jzI1fDdkvQ+9eo9HotaPRqHLvx8C3Ab+DPBR9v9vtUeBn7kV9TCaTyWQymUxfG3ruueeYz+VmbL1e87GPfYw3v/nNvOc97+GnfkrQ9Y899hjve9/7Xspqmkwmk8lkMpnuke7VM9n7gcdGo1GCPID96Gaz+bnRaPTbwE+ORqMfAT4FvAAbT5PJZDKZTCaT6WR94Qtf4NFHH6Xve27dusUHPvABvvM7v5O3vOUtfO/3fi8f/vCHeec738kHP/jBl7qqJpPJZDKZTC9v6S9DX+G6J03YbDa/AbzziO2/j/BDTSaTyWQymUymr7q+8Ru/kU996lO3bX/jG9/IJz/5yZegRiaTyWQymUyml1Kvgue5JpPJZDKZTCaTyWQymUwmk+lFV3LnXV7uuucGSl+xRtA2mRh4fEKA2AL8doY8DkLdkkEHK2fmcatLoI5g3hDAwAqc78RkQkCzCHjWAY17UrKikfPpeY4Cz0eqmNNtGXAouJouEQhx4eDApZjUMHPlPhW1DQKUOxXQtd+m561HflvrwMxax2pv7iHedA6qrOWUePDx4PUs3hRGDYB8+2o8ZN2DslME+N1F3x9lFOHqNSlX4fsCb7KUpJ2Pq4KJxw7drsB1hQ1PnOmQhzvHAH5nctHWOcwRMxYchLtLyBxUeACoL6X+GY03oPH1K6P9KjG+aepsCMF3fbRbrilKqbO2QQHgnA8mK2NWUMHuzQBdHrNyJluHDm4uaO2KuQPEd+69gM/Hp9beaEWNkloyB1HvXf+tyWj5lb/zrZA6CHicOymM3T4+doT2PMSTzDggo5FjY6OtIvTThJXk7Awe4XFv4jJlIeZkzgSKQkDWb+czIadcTmrdpyzE5EbjVLqcIZg7CXA7QL332Pew6qtccOB5MWVSILpKDVbyomX3/PPcd/6Aajbnwukv+JxO0tvdC1IHWleYtRpbAYO/uWtnMB8Q8Le2U8fJJHFtYyVjv3NzleuXw6deA50bix38mfz/AOC5v/V1gzpRhbzyWiroPWHFmPUWKJ46mkt0DOp81yViKKR5HcOulwF8nznYeJL07KRi0rVbtFAeDueTaJz8/r95q8PJr3yf+r5RMzWcEY2bD8WYS855q86CgUSBN9Px+atmbL0zXnBlux6ncTZTXi7PE6G/Qwn3Xb5K4ox4vJzRkJ/33ZxwqwumApqbLbm7nsh5Ejp2SsHot2ShnedhfrPii09foO8Snvv8WSlrf9cZ2DXBjKKEKp9T5XOucS6YB0ZmUrf2T/k+TelZPvFaMej7PjicT/n6f/Zp6bekoRF7IW/UIAdtQk5pn7hxrrHsXF0gMpvDAeNrKdvnbDTm+t6ZBLhh1XdikDGYb1w+xcYKMXxeDS9cJYOJgZv7cxpYFuwl+x5An6a9z5WGnNOzuYPXd8EEI7pm4I0DwnnUEOMPnnyTbNfrnK4ddJ/IHG1QdhxL0ytfI243rDnJ7AZuXy/C8UY5R/1M4W5/uqDjicOT9zvOTEfnbAgGLHD0eu64Np+kDjHxmB+xXcfW3Zajf7s7bD+qzIFZzQnbtsuN9zvOROkoo6aj+u+oMrbbc9T+xxkvnXRM/L4Dnn5+a/2Lv76DXOvU0GbB1JtuNHrtrHNuUNF1iZ+jxVwJb2QzcYY/8by/Woo5pl4Prl87R0Pm1y8tmS8jvnYDnHHrKZZwhUtc4xwLplw7OEdL7mw893xdehKYw4ISltD2uVyXNK9n0t4VYw6Y0ZGQ0Esb/TjCX0t0re1jWSN5vJT3Y9yaaY6/PjR17q83i/mUlkzMoo7rqw7WN8fQjbjGOX/f0zrTnp6Uijmfcv/ZMnXxWDFhxr4YK+r22hmn1PL9nMrfA/XOSLMnYc2ERTP1a6MFU29ctXZmpr5/O/z5FvMpCWIU9I18xq8xG3f/3ZFwq0vo+8TfD6gpE527fteh7T2JM4Jde8Mu/U5Mfkp+8//8970p6KKZeqOkYPKUhnWBkxhN5VLvfuLbo2v4q9zvzcHEZCv39WrIKVk4Y57W30v79eISty75orx3zwduzU/R1pmP+T4zfz/qx1Anxkg5Lat+4nNU8iCVsvYlhxJ6+l7iowbSYsqUD9oZj+Hb5tg5Yrrp3ue0Eq/emXwtJ2Fdgxt3Oicvi9C+ekTnTKCSVMyPbmznHWJAdI1zrBhzgatc5yw9ib8vUvMmnSO8iVqde8OsnkT62uUkAPUu1zkbnsW4uUXHpq4f1Tx3Uq78PKb7qmmw3DPnPlZNnfvz9qSk9BzWMiYSOh77hf/aGUelUI/cmMnJ8tYbmjVkdF2iFp5+7tT7W81/zYMVE5ZMnVFa71+9M0+K7yFXjLnM0z7Xc3S9HT0XuhiezRzWmdSxzqAbBWOqL+fa/SqW/TLUZDKZTCaTyWQymUwmk8lkMp2sVwkz9JX3y1CTyWQymUwmk8lkMplMJpPJZPoy9Cp4nmsymUwmk8lkMplMJpPJZDKZXlTZL0NfIo1gnK+EDVEKD5HC8T0d2yxmt00r4ZTsFi1UCIewPGSaLNidPS9llhvP+6r25lTJHMpDYQameHZbmvZyPj1PJcy0nIazXBPmnrLrCmGKTE87eEch55kkwsrcKVpwTMK38NtDfsMMeJO07Zv/7M9DCX/7J/4LzwpS1qEw0uTvzuymr9eUxYCBNmXBOBc+ZTmbC6u03Egs0sBkjOOnfLwxK9+eARctDfvslmtIYUc5oDFfMPo8YX37oNnirOVFC7NDxqw9FzR3DMaZZ0L2zKkCr66EolyFcqJzVLO55MfseccF7MnyVrh2Mdc0Yj15zmfKkEv6NjyrcJosmJTrIffUtTVmTUr8Gpi5vFiG7a1yaRT1WEl/TpIVp88fsMc+UxYD9uYZ5pxhTsWcids2cXHS/fY48Nt9HmpOlAhvV/l4jv9ZccOPGc2hndlNKIRnIn2x5hzX5VhlTxVwP1eZuHZOWUJZ8zkuU0V11TL1HBmN5F8nrE/txykLEnou8AUmrJixL3lzXniJ2h5mcIkr/GP+rM/1lD6KydqXuaAUltOc8H3i+gWZI/aSffaSfWbsk9GyZCq5iOSMcFcbz1bNaB3XdN+XWTEnSYUnOmHl8yencfNE6+cNqjB+SB170cWz0HGkY6PClxf3a5Y0jtMqTEStR07jz6PM2ZTe54WSM7U8n7/R+B7njsmreRKN593Lz0selMLO1TlwWgU+D2kf5tWYc1bBu7/5Y74d43wVeL9leGVFK+xcxy7KaSgqYc/uFNF8VCDHK3e1CPWdJCvPhaz2hLN7jmshD1M8t1ni53KmOGSSrKgSdy3Quj/u4jUL1xcq6S/tn7HjK/ucP70ko2XCmiTt/RiN59msaNmtFlSzOV//uicdP/aQGQeMWQ+OEWZZwyWuyLZojhU2b82YFee4RkbL6UeeJadlp7rJTrnijKuXzqnSjsbPs/J+7dvn59hC6uPPo/3lxk95aiGM2fMbKSdmZ7q5NUlkfHJeOGaX9z7nv9PrYlGu3Ny2ltxwsfRjupS5o5jdkHOzCPO1m9/Li8/RknOBq5xhTnlq4etRcUO43aVrW3kYXfc2kEJ5ft9ztAdjoHQc2wKY1e7vsI3Sn81tXGUfr2ITmF2mV670BiBmQd7tDcE2V/KFlLF9zFHHHseq3OKEn8i31L8VJ5/vuGPv9P2D3M4sOykGx533qNdJbM+T/t4pfke15bi6Hlf3o+q/vWaOyyiOOCbmZR9X9+NiVQBP67HTYT01bo513JDR9rnn2imz3l8n0+h9fLw/9RbDfEtZ0gijMe3DvhHjNaH3DH/lhHaO6UgX1pMpPYdPvMazCDPHQYTgn9CT3tYHMZda29c6xvrErReVe6nkP+X4dV1y+3giYmpG/af3A2sm3OoSX0bfSTn+vijqz/GpNRSHZDQDdqpyABvH+hww38GzBvV9nAe6FvRMfLdv45iEyonW12gAACAASURBVBHtHEu0cyxRjVPfJ55VqW2/5Y7JksYzJA/Y8+sMYadLW5U7m7qz+LU8wqj/zvf+Mxdl5ZOmtGSslSNJYIwK53RMlrf0fUJbZ57X2JLxf/EfC5O2k9VvzOicJCtHi8w8N1Vj2zim4yAXkXasGHtGprBWXU4pR1N5sTXCEF0Kf1IZn22TCV++O5prmiTD/qII63jNz95xKFN6WUsg+RX8IQKDP3X3Bd6nJN1IWU8j6/OteWod8VkppE8a9R9RRTmv/gnej8SpITBy8buHvNRYS/4GrwvdLvk1cpzMceCEIjm7ZMqHHvpbUt7W9Smhk3xw/dtoP7nY6HpWOZzKLR30Q9oF3jBy364eIGsmPPrevydjJu3cPNV5nqu2MZ774vfJVhxyWs8T1bjG8dFjZE7S+7vWcW2zwFllaz5Tj5IudF6S9pBu+P/Ze/tgSda7vu8zp/t098z26PbqDLt7V7tohS6REAhJlmKITcqg2MYxlJXYJIiKHbnMi0xMECQpiBOX81YxBamAiUkoXquUggJc4AJKQIxjYVfAvOQShAQSgoW7sKu996zOuTt7Z3amu0/3mfzxe37P8/TsnL3SBa7uSr9v1al5635efs/77Ozn2+iYMg30sW7fTCaTyWQymUwmk8lkMplMJtOnssxN3mQymUwmk8lkMplMJpPJZDKZHg3ZL0NNJpPJZDKZTCaTyWQymUwm08NlzFCTyWQymUwmk8lkMplMJpPJZHp09Eh+GRoDjmMYrfvQgXRjWK/72rpmAOP1KpoADe4DUFnzAAe+TSP4bbFxIOTEQ32p8UBdUgfHbYYA4gHU2sFtNU0P0nWAag933gKT+zrEsPFtIHL0XCDELq+0R42bNJ1eaOJDuXrkaraiacZmKgiUWI2mBjGro8cjYAm37z3u00nSPkDgNe8Ssrxlr2gfgHwn9CyY+lcKOgfJo+9cHSrgGg+U52Q5frD9t/81oxsN6xCrRoDqrv4rJgK8Vlj6ktBurl0HAPnt9BAgNilwHMylJs6wSqHyE9ZiTuPByT3j6L2exJsOqSmKAp21X804JqPhb7zzhwDE7GsH/N/3zVSg0aedgNsPnImRmiRtS41YclomrNhLez6PXwXEUEZB2t6wBTE/usE1KF2cUo1JywHHzKm8UVFCF9ou1VhtwliMyqxmQclWwBM6DyIfO5B2Sy6mNskiMqoKhgAKBo8h67lDrydRHmJqI+NEYeUN+aDNYyC2HxuEsb9iLG8UkKYRwL08CfNZ1F6vfOfvDPvXcgugrWMPfNlXjB34PfHl8+VUU4ZbEsvl/WmAzmuMl/gyJHRStij9ps4EiJ637KVR26gic66KOWPWTFgPze9c2Z+9dYGmzoIRD9DWUtasiOajyKhgoYYQRbgn3YpbR8KK8TBW5QnUrh4FApcnavfSBfLz3fX11vrSpZJeBL+PTQ80XknahfETfZYlDUnasVqOefr+ZZfHvhvTcn1O69aUxqeX0ksbO8j+jCPoEgf7n5DQc++3LjFhJcYNaT/ol1qWvTTMsdpu2t/i+fIBmL6WS+PQJ9CNPAQ/oxmYLfR9IiZyndT5TnNxaBwRKaGjJ3XjKuThl/JlMFXwhlS15LmcTzngyMPl2z5/IOYPKDbDwI2lMgb6u3Kl0ZiLjU/0Nc4EMFqbt8tu+iTQHg83wdn+LP7bZZZzlgnOx9pntvd+2/dtlzG+bqvvP/D6rPy289G0zvrsrPp9PHU/q25n7H2f976HXf+x3L9d31394GHp7novmm/UcHPw3lnXbu/Bdz0HeK0+joZlVl2SBz0zpPSs/Wo9kTmx3MAz+34/15PCUvbFenbpCKY5fh6tw/7Yz8u4fYgrq67Na8bQibFLvP9REx01wZnfr+CJE3qX34KSFjHU6Uhgid9DrpZjv6/xBqaujlrOhlz2Em6v3/ays14wZU4lJj21u1f3/e6xJxmefWqo51NIXR2XhTfi6buENJVzTbwnpHNrqatr4tb5eA+Z0vN6PsCCKUfM/OeHXHQmQrIH0DrLkXR4VlbDIH1saqn/upkwZcGSKR0JKb03aWmj/WJDBp3Eq+1z3sebfAxvcxnmug8Skx01gvFtXbs+UMPpcuLrJflkwz1zF/aZpLLXe4prrO+PaetggKT1XjAV06b5VM7ghL3M/H5F7+q1aKY05GIOGfV5PXevlhN3Cgl74b4LJyzfznOA56QfuHZnCSfPvMyfv6/lN2BZ+L2m7ys4Q6zemSupOZc+LvEGVL2Ld0cCdTivNs7wahWZTXVR+lKmkY8fkemRmnk1ZJzUmW/f0zoyr4rOt1pnNd467YJNazwG5Lyup7GED/I6n6fGT/vqtvlP3F7g5gJCH/je3//6cH30fYMaA/WuPNofV8uJ78O6f9fxvHYWxNou9TKYC8k8MqaeT90JL/dzoJ5HtI7aD9V0rF5KP2xcGW5ylcbNSxojLZM3wnVzaBwPHQd6So3nAHBpLMO8qG31oZ/5M9LmtdhU9V0Cy4d8x/FCtW0m+UL+XgJ6JL8MNZlMJpPJZDKZTCaTyWQymUymj1cvke9kTSaTyWQymUwmk8lkMplMJtNLViPMTd5kMplMJpPJZDKZTCaTyWQymR4VPXpfho4C943PDywJYMA/6kmggCQR7lpeNFAJK2K/DFxAYVYGvluWCIuRtBf2l2MaZhEPkOJE+JJF4BQqC410yGYZ5yt50gHdyHFWdrM7PWNtCZTC1vvln3irsExjFk0kZekIt3MjdVa+TiHpeO5nofn2UDTCWkslH89tS+UeLgn7IqMZcBRJEfbiNuthm/sWs6hKYLbh6mM3h+/H97o/YetJ/HJaLnObnGbAxhzIsQ7zonFpbAL/sEQ4HlHdlJPneXsx80nZgDCsnz53LCWWwvTJlfmpdXTspbxoyAshzcR55TQw2zBlwYS1MD62mHmZ425miXApJ45vqTxOiUHn09b6KLtW4+N5jy7tCWv/2TZHUd9T1q3y/vaL1vN5hHOUC79POa8pvr+VLBxZpyErGl9e5ZjqdTouj5nxRt4H4PuhMvrWjLnIIZWAeIQRWkpsqISvIu+v+K/4X3x/nHFEQu95KxOX7lVu+r4ydvf2JMJpSXoq5lzkjuRDzMDMaWrhG2nsOsc8mrDid3kNL79ds2Dq+aHTZOGZrtsx1jbzbR71S2VChrlh415LH9H8M8cr1TIl9AO2Z0fgMko9U39dSu/rr/lqe1AjvFDH6KzOSZ/z/COAmZTp5JmXCZtnuU+S9IHVGjOVQea3sh5yhmv4ud/8674NWzKZu6IyKbczTftwL44VqjHzbLaNcC6Xbh5TflCKZ84qC1rjMRnMZx3U++F1gedmNmSOsZn48utc7RlStcy9Kb3nB5GGPp3TUHFX8u5SYUvHUiZZnTMp11w9d1PGdFnTOg5U5kqijFdlaN6lemDu2CvaIdc3ddf9g4Ik7Xzc5SNhEmdFg6dgpYFTKvEKa6PGbdBXo/ZOkh7KOjA8tZ1KmJQrJsnKc7Zi7je4eDneso6lScQb9rpSB85sIdd4rpVbe8tqwTEzty43Uq5o3dHXE9bsuTkuVtclvj393FRLPV5/8H7pk93Z/xSurGFh/jJcS5RPbnr09fHysOL3yoekwxnPny+PB/hb6fDpWf8P7KFpPM9nu+7f9bjrnu1xsOveXc/P4vAOxln0XrHjmrPyehjHrNu6XveZZ5U91sPa86zrz2LMRmztBziw22nE+1uN2/WH5Be9r5y5uw62rlzCORXMR8JUdOcvndf1nDKnCtxK8Ew+kPlRGXsNGSfz6VYaaWCHxue5WvaNK8aQwm0e5328ifG5NaTCtVw5rmnjVv855wNLcOm44xo7fazDmVL5ft6rYQmL+dRz/oSxmXJaZ8KJ7AiPEdd+yFhMQ6xqqV/b53RdQtclwzXugfNdNzzj+ssCx37CyjMc5ayUhpiVUD02hzQw1Rvy0AaO+9iSc1JnrPoJTZ15rufyvtRd+0JD5s9NPSnM92WXVWdc4wYrJjTkniW7YArLwvNIV/2EFWPWzcTxY91amXYo41u5rMpXlDSiOruz1EXu8OZzvy51qnN/j+7RVsuJcNJrabPAUJ14puxqOWHtGK5yapE+rrzN04jRKTvolKbOaPvcn+WZuz86afclcAPP++1JWTPmw/f+Lc90bKKxobFWVqhnhtb7fv996uLUdbr/DMz/ts5J6d1ev/P3+D2pxljPxleAtPfc14GPRp0P+m08brfHzIqx+1yYsb3j80o6eF6nnnsuczucT1y9tf/5fqljfomPucq3KxP+5qu/P/BRBxzTjNVyPBgfPYkw8+lk7or4s3om1X71YV7DXtoHrq9noaYDhm3s/9BuxXC73BqnirveFyY+s7Vk3KWSsqWwvj/2HFKdd/TcKf1OuKWrfjJov1aZ/q6dXvklv+NiKZzQxnGOPSPVmKEDvUSKYTKZTCaTyWQymUwmk8lkMple0vok+Cbx0ftlqMlkMplMJpPJZDKZTCaTyWQyvQB9EnyfazKZTCaTyWQymUwmk8lkMpn+VKX/Tf4R1yP5y9AJa+FhRHzMabUQDpNjdCm3TPlnWSGciikL+k54gZNSOGV77jOKDaljDu4XrbD7Ii5ar3yXtBduaKrlEVoNV04cZ0zYagMWRwoUJ8JdS4UrueeYpDe5Klw11kMuUSTPZivwrBFhljpmZJci/LvwnjKAMpSDClUiHMb9okW5bcr9EbZnKO8F7jDjWFgWEdcz1AdKFsJWVcXX1NG13ShcU0KWt8O0XHo5LUnaSTs5ZkZG63mhMRNzEAfHkCPt4GgreFXIJ6X3PJuY8wl4DlzMXFR2jLJh9mfPCbcyWbFopqGO4NklyjztSVDSiPJlqUesGAcenuM1Zo4xKTxRJR8J3y+h54Bj/5g7RqP2M2U2KgdT6rj0DNCx658Z0t+0PJS1xKaSND6dm1KHmfBdcse3U8ZpxV25V1lVrv0SelemjhnHpKmwKRPCo/bVa9wQjhFzKd/MsW1cWygjFWTs5jQShw7PVrrAIRSN7xdxWQ44omLOE/x+NAeE8S3dQNpEY32Z21zmdsRkdRzMtGNSrnyfy5Exc+DYpG/m16kfE1ZpHrVB7ohVOj8p99HPB2kYGwPmVi1t4XnCJeyVwiyMea+Zi4nwQztfP+UG6ZjQpxc5pCFjzMpzfFaMw7iI2WRz2L/yHEf3DiRPxw/2Y/g9UF77KBVz9mb3Bzzf8bk1CR0Vd93cmnrurM5r5RMf5R1v+G4OOGbKQni4ek3Eu3n5pWNh3Lj3EnompcyPk3Lt51NwPKXCjVUtr5POe1nS+Hla21mC5uZyHZ+psEnH0RiMuZLalwPndhMyKwOnNndjTZXTMilXjFlznrnU85r0g4PkiKsXb7KYTznsLwrfbF482KapjAHlGKX0fNY7/z9ftgVTptWCltz3jVjTx5ahvZ0mrEjT3nOlZY3rPftMY6LX+jXX9dU5FUkqhKOchr20D2uc3lvA+NyaKQuZ51yss7wdtDkz4a/qONP5U7lTWp94LWzIOeDY55PQkxWtXyuEldz5z6cshB/r6nSqPNgSLr36KQlz2nuulY+9m59yWqhqimrheaiet+fK5NciVar3nzwQf9MjqtOt12fxL/X59t4p3fH+9mcfz982O7IAGA33bQXDPHcxKbev0/dLBpzwj6tMZ+VX7riHHXnoPLqrXLte676u2MrnrLo+Xwz0L+JXP3BfvI/crs+udt9Vfi13HJdqx3W72npXnLfLqWX4HIbabo+jcH2Wyx5LOexLpsI4dNctmAYOZ+rWYJe3cuxbsgfWog7hZfakUDSe2yjrRsRb72DVTwZcvAXTwX6v7xMKt09T9rVK12O/5yWwM723Qopbv9oBU1v7e17I+akhI6d1PNEUz85X6R5EfR/AMSJH4jNAyt6l+6wY+3OYxCl9sO+AZ5kfcAzLwIgH4f4dcOzPSDpGNAYNmeeML+9Lfces/J66c+v8hJVrn56iXNHWGX2XMqfiLpVnVC6YOs5oOuS4VifC1Oxk37vNP/VnNJdGkrjzW50NeJZ7RcvP/cxf931D2Yo+nwK3Q8m58o7fY8GUG1zj3xz/OWnncsUNXkVPSsWcL+afk6QdfZeQpj19n8i6X8h3BY6QLmcclK3fMVYGubZFndM20ufWjH3fAWG9+7aeuxv0dYdwQ29B2wiP9PJjT/t9vz//uXCtmwnTXPaqsv+P+oOT8uZbbVvw+9gfePfX0fdJ4OFrXyRKz/mccAt/1o29JCT4o4jDORp+hxHP/4WMGSnjDuat27Pr2Nd91Hnm8n0CDPqKMjJ9nV29nQsFR8w8NzOj4Yc+/NVyTlwSzuDKKe1S9Iy3YCrzyrJgznk597hsPfNUVQZ/iZyWNRN35j6CbuTjqXxZ3TtmtPQkLOfT6Mzfsec8FHpSz1DOkLku9pXRfeoBx4PvD3S8Z853QzmnOl4HSh2HVPueznnR90nqzRDPI6ag9PkvMZlMJpPJZDKZTCaTyWQymUyf0hpB/J35o6pH8pehJpPJZDKZTCaTyWQymUwmk8n08cp+GWoymUwmk8lkMplMJpPJZDKZHi5jhppMJpPJZDKZTCaTyWQymUwm06OjR+/L0BHBoIRgZNHWApeNTSsUIpvRCrjZGVxkRRMAsqkzX3LfbKtxhpczdZmyFMOHWO4zgfq2sNyHygHAnbnDLDJ3AIHfkoqhxGknpinXuAEdHpDrjaB8PhtJUw166HxZUgcBVxC0N6TpEMOGOB0HDhZzqM7HzhtdxBB2QjwEIBzu3zYGUSOMzBnu+Lw0zSNgCXPOOyh1BE+OTYyKEw+Gzmg88BsIMUbA0N4IKZW8fPunPVwJaQZjpd7XRw05BmXEGWmpKVUMnY+A/JNy5csw6A8aOwcF77vEmZj0wZiFBmqBSFfMmSplvZH3SMVYKPRt6YcVcxZMucxtbzpzkUNmHHGeuTeFUSMlBaOrkQlIP/2hH/vqAFouNsHcpsDBw9e+7SesvekYBBOlq9wMsalqKMVASD/PaMkKge5PWfDyO7WHfHvTmkqezxww2hugOGMlve6AYx7ntoCyyxNnXiKw7aKUfplHZlcClE+9MZLGVMf0XrniInd8/2rIqZhzgUMOOKYj8QB+HYu9A8Nf4JCDe/ecsVfqjYOKOzrHBEMnbQeWMhfMOPZ5Ukv/V7MCNT/ygP8OkqSXvtg5gxeCqcD/yX9K5fqPQrgTeqjgLlWA2Lt2nbCiIxmY0iiwe/CveW5cMoOT+ZSrj90Mc45+Lh0/GNQ4oxk1nZqw8vmoCVReNGIC4eqcFe3AqKDROU9NylKgrMmShpP5VF7PnOFW0g/L3EUvnAmCn5tKvKGYSkHnOr4GaRWR+VFkLgcBaK4GPAFuv4GioXos9FlNV9eXzMUjc+ZEPUkYZ53UW83idG4Ro7MT/1xMxNa+LdT8S825PCDdXaB9tCfl0hv+gAlryu/6KAC//NVvhZlcc8Cxj4U3FHNGFDpnaV/aL9eyXsT9BTFaSxK53sc1lptfJw5Xr2YROQ0Vd32ba1zVjE/7uBqweaMwZ2Ko6503dXN9LKOl7xIuc9v39fPOkI3OGSUkvV+DBgZfUXvnNDz2xDNc5DC0Vbrhw/1rYFmQpsF8Y9CXOjHX8yo3YWylBq3/pJH+GmLb3Gb7uRrTbOssE51or/jANR/LX3xvnAY7Pvfz7Y4yb5v1wNDY5ywzo7Pe2/78FmebF21fH5uKbq9ZH2tMdrXPw8rOjs+334OwlsefbRsaPSzGu+oSx3k7z/j5LMpjV1kf1u+2n4PEuQx/HQltIyuQnonGrAdrnRie9t78RM8eug8dGLC4eTTTswqyDhTlKqwdXTB+AaAOJjt6pkrd3uoyTzPjmL5LaOvc5+UNZAnrsD5OSjEYJXXGPHOJQUM+KG9CL+eWGlbLia+rriksRyFm4I1cciLzvC0z3PX9Mae3zpHTsmIsRi+71oTU7QGr+z5P3Z/0bo/ak4ghkVsbqWUtPOYANV1sndFV5ta4lpwVE9nhur1TRzBMrF09T2oxaEnpqZ95uY9JozF2Y1JMP/dl5a/F3KgnZUHJEQcSb7dnOKnF8LbvE2cmuRYzV2dSfLqc8Je/5Kd8CHoS2l5O+rFxzYQVt979mWLiBbzl4Em6Ts5il7lN3yccc8AxB/RdyulyImZRSS/9yBnU9KTexGtBML6Zc37Q90jlrB0bJ6VpH86bIOOlBniZP+tyHd8Pmvhat/9XE0wxtJVOtOonW/PbJoz5LuxbGzKOmUG5oSfhtEt4xzu+myTpB+Zj3mQ6edBsh1T2ympm5M2x9PNC8h+zGpoE617G70U76GRPNRjr7rOMlpacnIbbXCaj5SKH9KSsmfixqucpb86mezU3bvU8Jr2s5Wtf8+1S9q35rEfO3mqQNfF7zg0XOZSzZBHFZysueq7Rc8KKiYvjiS/HwOyJYGi0l/ZUzAfzlxqWqRFUT+rnGhmTGWpo2zoDpL5LaJ1BdE/i71Vp3pNkhTdo7pwxXO3icd1dHK1nk3INNXyQ13Hn/oVhu/5x9UL2Ktvr1SdYj96XoSaTyWQymUwmk8lkMplMJpPJ9AL0EvlO1mQymUwmk8lkMplMJpPJZDK9ZGVu8iaTyWQymUwmk8lkMplMJpPJ9OjokfwyNOZ9KhdDuSj65993LIq+l6+uOxLPaUnSfsCbADwPonecEQjcNhCWYuHYbsqo8PxLxwNRhlnumJHKqNhzrD2pQ8eeyz9zbD5leMZMDmqgcIxUxxKasPZ8O02v6xJh6Dlej3A3UlfMwLyLpcySMSvP8/m6v/Bt8AywlDJOWAfm2jbHCWUDCVcujeMZxVSfJ3SSTxnahxTh9twQLt2UhbB0HPdGeYyTiB+q+Xp+TeHaUut3FMo3YLuk4d42YnBIAB071rFKBv2ow5dV+UW7eCOxsqL1fUkZS63j3ChTxLMdj/GMvpjH1JN4dt6MIzJaDu7d84xQ5enFmnjGXhgjM45J6PmGL/8WSIW1RNEIdy/qUxV3fT2nzcJzdpVPKI+dxGMGZcTa1c8r7pIlDR/kdRxwzP3H9oQ56zihymzRcg44jI4fOmZF6eLg2bDKh3QxyovWMwU9z9X1kQlrbnPZ8xSXTKmYc+pYSMoQzV2/uszTPMF1F4O558XU8ynLubCC1kx8jF/P+31dNo8FJmlCR+c4NcruyWg930hZVfGco+3kGTQdg34eumf4pzfte6EfCdMmjdsdoNi4sbMeMB09U0u5Wdt8sqKhJw0cL2WSlbVnEMX5JPSe+XOBQzoS3pl/DyBsZGVjAmRJE/XdLjCHCykvKTIvAnQjn3+mLFTloPnyCnNMYx0YS9JPShYDHqTwU5vASQbhJ22NX+H6dKGeMUuuG167ay4I83NIQ1mrg/Z1c4xndyY6n4b3NMakjtHm0jjmILBEC0lnnK/c9Z3nUvUkLP/rTyOh4wu+71/ALRynSPp0kvZ+zpDnnc/Pc6fSzvdfP4+ngTmleXoVW+2BzKsyLjaepRqvu3rdNuct1KKHtPecOooozUjCsm18n2nIoYCX/4OP+Ll0wNNzjLy4HUsWnhUnazBQNDInOn6U1G0jr6M1ws9JKey5vkkqrO4944Z+cmjDg9yrXezGsz57Ph5l8ZC/7XTj55yR/64/5Vh2O9LaVf5dz+HBMrOVx660Bqz4M9Ivomvj+hVb12zfFzHeH5rHWa85o9zR2vJAOc66Pr7nrNjtek/r8LB0HXLeS9+vdqT3fO25o295hiCO4UjgclIDr5X3T+rM77V03YhWlLB3iZaHnJa+E35ev8X+bsmH3gNEZxrw8/kNrnHIRSBwSFvHrVQmpN/fuLQ6xxOkQ/j3XVy/7IH9tEq5fYO9dczcq0I6dEjbdMjZZos92pCRIgzLrovOj1EYEjpO62wQv9g3QRntujYKhzL1zMK+d+eOJcyPKs+p9Ne7sq4dlxBk39W5882KMQ05xaVnHfUw56Te4rsDFCd+36B7QOXGSzoT6Hhg3WvrbItF2fHz//ptPrYNOW2d+fbUc5nuWZSpuGJC36X0fcJ1Xk1bZ2S03m+BbkTfpbRNFtizvk3znczWjoifWe/TNpkvU09CU+c+Tj4ec4CXyXiN238J9XLi2ZD+M5S36fps95Cf1i2BTr+XSMO9aTfor32feH6khCwZ9Kld6ep5p3PeCKSbUKd65M+sLjChzdIoTt2ItsmGzPhoP6XcV50PIDBA4/QH36UUw/4q5QyVkbEaTSgp4bsXQt4x61UYnA8GJD5b3eZxklTSzeP9Yxq+j2gdu5ZOWL4JPQ0Zp53WUeYf3Vdr+rqL1XGiddbPOpId39GEGCRuzgDNM/d9Urw7jsP83UVnSq3/cgwlXOMp+b5s6xzzgrXNT38hfy8BPZJfhppMJpPJZDKZTCaTyWQymUwm08erl8h3siaTyWQymUwmk8lkMplMJpPpJSv9ZegjLvtlqMlkMplMJpPJZDKZTCaTyWT6lNAnwfe5JpPJZDKZTCaTyWQymUwmk+lPXeYm/wmQ4/te5BD+b2e+4kw9YiC4BwkjZhZZ0vivfr3BgnvPGymlHTkNUxZkRTDsiE2PgABdLsQsIacVoO6lOlxfEyDOrlyTciXmJakAxBX6rUYSUyEaS7mPnBFDGcGnXTpTFt50JkNMcCblGhz4N5h+hDpntB5KPK2CAY2WhQKY1XzXv/4mDwNXuLGa2Qzg7A7s7s1IYvDvLjhuqvXcQM3QnMU9TsoVU5YkqeKWO2cW0jozDGk3H+9I03zh4NXJADpfnlNznqHxkTe28sD8je8HsUFIbFzl+0oRAOYD2L2LT5L2ZIngjTNaSa8K7TLjGG+sVAHPOsOVUh4r5kxY+XaecUTFnCkL9u8L9PqAYyrmZLTR413GrF2MGt/XQPqS9oEpC/bS3tV34/uUb4/yhKQ7lfdK6R9alilLqe8yxOICdyKTq5YZRqfGCAAAIABJREFUx/xF/iUVd5ncPxWwc+H6mesL2pa+D7s4TliR0vPp3GTKwtezmN3lwKXTkzDOxbToe/i7vtxqEpQ44Lb2y7EzHCtncxI6KuYe5z1mxat4imvc8GN/yoKehKJacOkVt326+8/JWPggrwOg4i6jY7x5kh+X0RhUs6JcDYy0v22BsmPDq0wNYlL4zFd82LeB9LuAItd0dV66wKHrN0vXH7uov4uhjiDp24HBkzcvUlOZTkygVoxDAXUMuf4zYUVeNN6ESmMkxlQyPotqMTQHcFD1kgXnmbu4OUOh8sTPV5kzcFLDJp07dJ7MEmeYUzTsFy150UK58XVS4wkPEC+kjXT8ax8hlbl1v2jDvF1Kv544U6wJKw9UJ3Xj1KW3V67YL1oOnEEZBd4gTKHmqYOr57QkSTA98IYVV+CPuMqKMQf5MU9wnYvc4ZWvuOHHlJpN6VjReF/lJm5C8W2pxlcaW51HZGyt+cVv/EtwTa69yKE3H1Ko+zhfOVB8N/in0njNkz4ieco4a3w602rhYxQb3EGA5uPWvavc9J/rejbOV86qonVjauliIO1XVjI21XTL128mbb6g5Gpy05c7d/MiwLPf9YpBn5hxHOpUwvx+JfNILmNE1qMFzGTTsZf2VNxlv1xTnZs7Y5SRmLv90MYby01Y+/Uii0zq8qIRk76tsW96RHWWqdG2MU269XiWWdC2sc2u17sMb7bT3GVOFP+VW49npbX9uJ3uw+ocmyltv3dWfJ4vLlpmHnKv5nlWDHfFafvzkmHZd9VTy3FpK7+z2uthbVKe0Ra76hCXi+j5w/qh3rdt4vS+YAQ7eN+915Ixzlc0ZN4oT4z3xmKkkm7oSNhz5iIgZil00f4eWQPVBIcOZ5yUuX1+x8ly7AxmUr839OrcPtM9n7KQtcZ5uh5zAEBb596gJd639CTePCgujzcYcXOzGp0MDF5cLCbOMFfX8hXjEEtnmsszDOtduPej2Cdp781zVv0k1GtbqZq4Do1Qcxq/ho5ZuVUy44gDX9bG7e7AmcfE44GwBuduDR5H56hTNfGpxbSoJaP+rZezYErfSzvHbdOQwXJfPu8Sb1bTk3CXCubBUErTbp0plNa97xIXw33e+hfeQ+v2SrqfWDH2+7q+j/oesn9I6Oi7hGdvXeDT3X5oxZgFpTeb6bskGBQtxUhGjWmaOvcGOGFnLXWXNt7QOOMo3Xv3UV0AuIXrjxeCwZAaJS0lnh0Ji37qzaeC+ZCUt4sMm32/SLvBXCNGVV0wAUqDEbDuLxdMfVsPzuWxWY4aWpVqRNSTpmLIQzcK/VbnAjXMUlMoZ6Sktj6UUuk1Y0jx42/B1MdzyZQLHLJi4g2FYuMifT6n8nlkNKwZ0yFmn1MWvo0a8oGpLZ3ESc9baiwUz0PeuChVg9St8deJuXBby/hRs67bPO7vbxCjKDGn6n059WzrDZH6hJPl2I3RnAOOfToNuTdx61xdEqKzmmt/bVvtmypvmhRdw9KVd1d713JP7kyTPsDnhu/LTF4WDpPJZDKZTCaTyWQymUwmk8n0cBkz1GQymUwmk8lkMplMJpPJZDKZHh29qF+GjkajZDQa/cZoNHqPe/2q0Wj0q6PR6PpoNPqx0WiUvZjlMZlMJpPJZDJ9aqjve970pjfxpV/6pQA89dRTfN7nfR5PPPEEX/7lX07bts+TgslkMplMJtOnuPSXoX+cv5eAXuxfhr4L+FD0+luB79hsNk8Ad4GvfN4URsIJ60g8uxGE0RAzdZSpkHpqRR9ep33gFxaOW+IaxFND0t6zv3Iaxo5l6JX2g/Q9myJi7oDcG/OT4jRSz6gUhpxyeaiAa8N0lIUxYOpEHEZhQPQDRoyyRhN6z2TrEX5KlreeC/cAOyNiB+U0KKtvFxNp7Nh0EBiSgxjUwAyKK886RlwXeHJxWu7+noTZY8cuDHOucpMJyrFzbe3aNqOlfPtHodo4hgfsFa1wnFza2oZ7RTjgCEukgevAEY6BMpJ+4OO3CeXUulQIP8bxhpRX65kq2j4u7h1JYCa6WFJtmLJg7OpEDdwPfNjSMZGUDah9SzmY3A+8TWXrKF+1J3UMR+UFrl0bSd/NXF9M6AKXph5BcRKu17I2jp2abrjMbc9o9TzVTth6pJD30kfG/powNrok9G8fj1TZh2soNoFDSuB+rpgw6Vee29J3juOTbgZj0ZenUnZL48uqfJiETlgvhfIyW89zzWkpWXCBQ89laRxzqXXsJM8lBH+/53Y2+PdbchZMw3hyTV9x1zOKSAncna254oHnwO/9ofBJPZ/WxfeAI/9cxqc8fpjXcMNPHmE+8/Fwj1rHQZ5R3j7dAYtMyn/cHHjekl6rvK5jDsho+H6+ino5Gc6ZJYOYx2XaVk7DY7O5ZxV1Gr/OjekS9ouWJO0Y5ytIuyGTFcmrJYdUWGPK2lWW0d/4jR8iKxpOlmPHDO38+Bf+rYzDvGj9PKVjXliiPbljS8c8Xh3HgzgSmNO+zjWeB5v6MZz49tJ+ljJcv1R3qbjGU759dI3QfDS9MSvKf/BRuWmpeTa+HEm0TqSOM+r5rK5eWdEO1wnXLzLHE+5IyGkZ5yt/XcwszhyPFyIGtkrXIEVhuVjE41ReN2RFG/jGRH25lvJc5mlaMo6Zed6oj1kxHA8JvfDgXNs+fu52aGNgtZxI2kXjmW+fyweYVgt5PwXSDXvlCv7uSNbtpc6bPDCusyKwgE2fOH3nd34nn/VZn+Vff/M3fzPf+I3fyPXr1zl//jw/8AM/8PyJ7DHcD8WPZ7E1z+J0dtFnZ/Ehd/Em0x3P3T7F770gcOfizx/G71SdxQ7dvmfX55xxj372xI60zjoolTuuJYrbrjrErMSz+KO7+KH6utuRLjvufz4+6/NxPHe9r1vG7XhqGbT9rmzlUUbXKtMUHszPt/P+7vI6ZbQs709ZM/F70CZie/K+ET0pp0vH7ot4icoNDOT/cMYKLMfUcRNTf22chrIR+y7x7MkFU79PWzH255nTOnD1UnpWjGkjNp9nh3Ypq34CndvnHeE/99cT8feieHQktH0e1hPd99d4rwfPE+0Yngu6UO823nvF6rYeGZ7Nsmg/0JLzAT7X15cUx9sUruEkWfmYxWkpG1U1YL0uC/ouFW6ky48rJ7RNFjjtWj5tH5f+aZd4hmnMw9Qy7Dm+ZeP4mX2XDOtbSz0+9Kt/RnddNLXEWliWcsZdNxMe+9vPkCDpffDe6zh17M7r/RN0XYIyak+7wCvturDnivmyUs+GORVrJu6smXj2LfWItg6nhoSOrGhI017SVL5mBzCW/qS+HzoHLyWeSdKHfoH7zqKTeudFy/r+2PVRV9YuHV5PQt+l4UzurttLe773e97l6zKYwzu3H9H5wHlEhM9C//L9JWqXleOADs4r7nPh/LozU53JmNF7axlDPQnz48q1l8T9Dhd9no1LYx2xROMy+L7kyhdzQv0ZRlmmdejPeq+vF7Jf1rLFeXv2qeuv8VzSk3KeObizjqbfkst7UdvQyVynDF2AtYsBwKu44a/1Z3+0ClOOmIXvo4jPa5LnvKkc09+NtUbo+sqi9ecKxyX+wx9+rZ+DehJWy0nYn0btaBK9aF+GjkajK8CXAN/vXo+AtwI/7i55N/AfvFjlMZlMJpPJZDJ9aujWrVv8zM/8DF/1VV8FwGaz4b3vfS9f9mVfBsA73vEOfvInf/ITWUSTyWQymUyml77sl6Eft/4x8E3AqXt9AMw3m41+P30LeMWuG0ej0deMRqMnR6PRkzQf/dMvqclkMplMJpPpk0bf8A3fwLd927extydb3+PjY6qqIk1lR37lyhU+8pGP7Lz3e7/3e3nLW97CW97yFji1fajJZDKZTCbTo64X5cvQ0Wj0pcCdzWbz6y/k/s1m872bzeYtm83mLeSf9idcOpPJZDKZTCbTJ6ve8573cOHCBd785je/oPu/5mu+hieffJInn3wS9mwfajKZTCaT6VNcyR/z7yWgF+sHqn8e+Guj0eivIhSWlwHfCVSj0Sh1vw69Auz+J3mTyWQymUwmk+kF6Jd+6Zf46Z/+aX72Z3+Wuq557rnneNe73sV8PqfrOtI05datW7ziFTv/g5LJZDKZTCaT6ZNML8ovQzebzd/fbDZXNpvNNeDtwHs3m81/AvwC8GXusncAP/WxpBebvajxwiRZsT97zpsalM4wKJg5iIFObPISG8YIVFxMZjIakrT3hhqdg2tntN6cZS8yP9L3VArjnbDy8GcKMWAYsxLzGRrG58QI5JCLkYHHiQBwrwejkNNOzCkUfK7GHl4dLO45QrKD58YQe1++UuovJhedN19SuPlebIAUmVPFhk0ewOz+YoMhbw6xJECXS3msn3m51BNgvmWcUgLXBDDdk7BopmQ03OHiAMDeq0mHa7eWjOV7Pg3qkYeIn9YZzlsGUgeAroO504Lpgx3KwaHH+SrErxsFwPQSD1JvazHOyGgDFD4GTHeatZrWpD4PMSNpXNlzaZcCSAS0LFz6ngOOuczTHCBGUhNWXOBQ+kfPwDxHjEuawXhQQ5fYdGcc9dGclkkp/WD/0nOAmFXpWHrs0jGje1KWvXLFgqk3dPJj5rUMjEwaciasqZo5PQnv443+MzVumrLwpk4eQp+KMQolUEmbquHX9N5JMC4qGjd2Rt5YRvuDmhiIidPaj0mtjzc9SqQXqZHOzMVxwpopCwe7TlkzZsWEabXwY2fK4gEY/YQ1HON7KMB55gOzM8BDv6W/bPyY8IB5Ing5DkzuTN0+65UfCGO5hq/nn/j+owYzIc7toN6k/WB+iuevYw78XKLtwCVgJmZjsZGPmtPsF8GgSCHc4yhtNftK6cWkgAAxJ5X2lT6w8m04ZUFCz17R+vRrZ1zjje06uS82YaCDSbliUq7JadkrWhkXNR5qP2ElpltIPY85EHMBVmS0/MS//TdJU8mb2pnR1bKWPGAqB/BkeDplwbRa0HURgF3Vhf6tM5caR6nZkJqQxW0Xz209iStnE8qSyjjVmF/lJnPO+zynznwtdXHNnRkZqAFSx9u+70e8IVVGS0NOljSEGTb8+f5R4MeOjjNdJ7TeatqmdZTiBsOwnpSOhIq5mBbhAPiOaa/p6P3B2CGPzNt6WmeWMBnMZ64PF6Ffaiy8sWFx4tbrTsZnFa9rJ1DAcT/zaQzGZZ3DTMD2v8GbWC3HIf9UDBVi9lFPKmO8RMy3Chnzz966ICYKsbmJ6UXTt3zLt3Dr1i1u3LjBj/7oj/LWt76VH/7hH+aLvuiL+PEfF3T9u9/9bt72trc9f2IZuw2F2HoPghFLbFhQbV1L9FrviT/bxdba9VMGNfXRz2OTptjUI04jzmO7/LGZT7f1/nZ9t9N4WP1+a+u92EzlLJ5YnKYzTXzgM01j1/tn8cni94qt93fFvDvj+a77Hhbb7bS1reod12+XYTvdXXlt1+UBA6V0eJ2unVfkZU/Ccj4lo2VO5QxT3Fq3JJi8LkfyXjk0B2qcmY4ap+i54HR+Tt7rElmflyM6Na0p4zVl5Y3r1CSvJWPd6H4qpSEXI5tf2Xd55t5atCUT01bcXN/BiVs/ANbO5EnNR3rUfCfa+3RQnZtTMWd5X4xRVkyg2nhzRz8WCoIxio63uYvp0p1Nls6sqZY9XpqGtd23gfapZeFjzlLqHp/7Xs/7gXC21fq35CyaqTePOq0zqHGnxsyf+xpy5n3FonFnonTjjY36XoxgqPdZzKesmwmnkZEVSHnU1Iou8WerBVNv2NuQ+7on9N4cpqnl/U6NlAr4+X/5NiikDx03B5zUGQvZaUEH6/tjVssJ9/7xJd9H5DzWQ5dKf3IGTf9P8+/69qNLqOdTn+fp/ByAN2fNaVkzoXFnj4ZM9uxuPJy6frpiwpyKvktl39e5tj4C8cbZD3nqHt+l0ZJ7wyQ9Y2qsNW5Z0Q7nQXcGVcV7zIwWjiQ/NcDs+6EBke7h2l7G5P7sOdevwlldTYRkjGWh37o/b1i0fc5Nw5ihzum71OcXTIYyb5alZkreHIscNbpSyTyR+jLqeeyYA28U3Lh2ADGmHaxlaua1nND3MpbnVFKvuZi9HXLBmTuN/bzU48b0Em5yl
gitextract_opl2tjw6/
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── data_retrieval/
│ ├── README.md
│ ├── csi_extraction/
│ │ ├── __init__.py
│ │ ├── csi_calibration.py
│ │ ├── read_csi.py
│ │ └── read_log_file.py
│ ├── run_test_client.py
│ ├── run_visualization_server.py
│ └── window.ui
├── model/
│ ├── .gitignore
│ ├── data_calibration.py
│ ├── dataset.py
│ ├── label_activities.py
│ ├── label_human_boxes.py
│ ├── main.py
│ ├── metrics.py
│ ├── models/
│ │ ├── FCNBaseline.py
│ │ ├── InceptionTime.py
│ │ ├── LSTMClassifier.py
│ │ ├── SimpleLSTMClassifier.py
│ │ ├── __init__.py
│ │ └── utils.py
│ ├── notebooks/
│ │ ├── DataFilteringAndFeatureEngineering.ipynb
│ │ ├── DetectPersonBoxes.ipynb
│ │ ├── ExperimentsVisualization.ipynb
│ │ ├── HARSimpleLSTM.ipynb
│ │ ├── Inception.ipynb
│ │ ├── VisualizeCSI.ipynb
│ │ ├── fastai_timeseries/
│ │ │ ├── __init__.py
│ │ │ └── exp/
│ │ │ ├── __init__.py
│ │ │ ├── nb_ColorfulDim.py
│ │ │ ├── nb_ImageDataAugmentation.py
│ │ │ ├── nb_Initialization.py
│ │ │ ├── nb_NewDataAugmentation.py
│ │ │ ├── nb_Optimizers.py
│ │ │ ├── nb_TSBasicData.py
│ │ │ ├── nb_TSCallbacks.py
│ │ │ ├── nb_TSCharts.py
│ │ │ ├── nb_TSDataAugmentation.py
│ │ │ ├── nb_TSDatasets.py
│ │ │ ├── nb_TSImageData.py
│ │ │ ├── nb_TSTrain.py
│ │ │ ├── nb_TSUtilities.py
│ │ │ └── rocket_functions.py
│ │ └── torchtimeseries/
│ │ └── models/
│ │ ├── FCN.py
│ │ ├── InceptionTime.py
│ │ ├── ROCKET.py
│ │ ├── ResCNN.py
│ │ ├── ResNet.py
│ │ ├── __init__.py
│ │ └── layers.py
│ ├── schedulers/
│ │ ├── CycleLR.py
│ │ └── __init__.py
│ ├── test.py
│ ├── train_lstm.py
│ └── yolo_data/
│ ├── coco.names
│ └── yolov3.cfg
├── requirements.txt
└── router/
├── recvCSI/
│ ├── Makefile
│ ├── README.md
│ ├── bin/
│ │ └── .gitkeep
│ ├── csi_fun.c
│ ├── csi_fun.h
│ └── main.c
└── sendData/
├── Makefile
├── README.md
├── bin/
│ └── .gitkeep
├── sendData.c
└── sendData_con.c
SYMBOL INDEX (561 symbols across 42 files)
FILE: data_retrieval/csi_extraction/csi_calibration.py
function calibrate_phase (line 4) | def calibrate_phase(phases):
function calibrate_amplitude (line 36) | def calibrate_amplitude(amplitudes, rssi): # Basic statistical normaliz...
FILE: data_retrieval/csi_extraction/read_csi.py
class csi_struct (line 11) | class csi_struct:
function unpack_csi_struct (line 15) | def unpack_csi_struct(f, endianess='>'): # Big-Endian as Default Value
function read_csi (line 49) | def read_csi(csi_buf, num_tones, nc, nr, csi_len, endianess):
function signbit_convert (line 103) | def signbit_convert(data, maxbit):
function calc_frequency (line 109) | def calc_frequency(basefrequency, c, num_tones):
function calc_phase_angle (line 119) | def calc_phase_angle(iq, unwrap=0):
FILE: data_retrieval/csi_extraction/read_log_file.py
function read_log_file (line 6) | def read_log_file(filename, ignore_endian=0, endian="", check_tones=1):
FILE: data_retrieval/run_test_client.py
function run_client (line 7) | def run_client(filename: str, host: str, port: int) -> None:
function init_argparse (line 43) | def init_argparse() -> argparse.ArgumentParser:
FILE: data_retrieval/run_visualization_server.py
class UI (line 15) | class UI(QtWidgets.QWidget):
method __init__ (line 16) | def __init__(self, app: QtWidgets.QApplication, parent: QtWidgets.QWid...
method update_plots (line 55) | def update_plots(self):
method process_events (line 75) | def process_events(self):
class UDPListener (line 79) | class UDPListener:
method __init__ (line 82) | def __init__(self, save_data_path: str, sock: QtNetwork.QUdpSocket,
method on_datagram_received (line 95) | def on_datagram_received(self):
method get_csi_raw_data (line 113) | def get_csi_raw_data(self, csi_inf):
method calc (line 145) | def calc(self, raw_peak_amplitudes, raw_phases, carriers_indexes, ante...
method make_photo_and_save (line 162) | def make_photo_and_save(self):
method save_csi_to_file (line 171) | def save_csi_to_file(self, raw_peak_amplitudes, raw_phases, carriers):
function init_argparse (line 182) | def init_argparse() -> argparse.ArgumentParser:
function run_app (line 199) | def run_app() -> None:
FILE: model/data_calibration.py
function calibrate_single_phase (line 6) | def calibrate_single_phase(phases):
function calibrate_phase (line 42) | def calibrate_phase(phases):
function calibrate_amplitude (line 60) | def calibrate_amplitude(amplitudes, rssi=1):
function calibrate_amplitude_custom (line 74) | def calibrate_amplitude_custom(amplitudes, min_val, max_val, rssi=1):
function dwn_noise (line 79) | def dwn_noise(vals):
function hampel (line 96) | def hampel(vals_orig, k=7, t0=3):
FILE: model/dataset.py
function read_csi_data_from_csv (line 22) | def read_csi_data_from_csv(path_to_csv, is_five_hhz=False, antenna_pairs...
function read_labels_from_csv (line 47) | def read_labels_from_csv(path_to_csv):
function read_all_data_from_files (line 61) | def read_all_data_from_files(paths, is_five_hhz=True, antenna_pairs=4):
function read_all_data (line 85) | def read_all_data(is_five_hhz=True, antenna_pairs=4):
class CSIDataset (line 95) | class CSIDataset(Dataset):
method __init__ (line 98) | def __init__(self, csv_files, window_size=32, step=1):
method __getitem__ (line 147) | def __getitem__(self, idx):
method __len__ (line 163) | def __len__(self):
FILE: model/label_activities.py
class Activity (line 12) | class Activity(Enum):
function set_activity (line 33) | def set_activity(activities, from_moment, to_moment, activity):
function main (line 37) | def main():
FILE: model/label_human_boxes.py
function detect_person_box (line 12) | def detect_person_box(path_to_the_image):
function class_id_to_string (line 101) | def class_id_to_string(id):
function main (line 105) | def main():
FILE: model/metrics.py
function get_train_metric (line 9) | def get_train_metric(model, dl, criterion, BATCH_SIZE):
FILE: model/models/FCNBaseline.py
class FCNBaseline (line 7) | class FCNBaseline(nn.Module):
method __init__ (line 19) | def __init__(self, in_channels: int, num_pred_classes: int = 1) -> None:
method forward (line 35) | def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
FILE: model/models/InceptionTime.py
class InceptionModel (line 8) | class InceptionModel(nn.Module):
method __init__ (line 33) | def __init__(self, num_blocks: int, in_channels: int, out_channels: Un...
method _expand_to_blocks (line 73) | def _expand_to_blocks(value: Union[int, bool, List[int], List[bool]],
method forward (line 83) | def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
class InceptionBlock (line 88) | class InceptionBlock(nn.Module):
method __init__ (line 93) | def __init__(self, in_channels: int, out_channels: int,
method forward (line 123) | def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
class Conv1dSamePadding (line 134) | class Conv1dSamePadding(nn.Conv1d):
method forward (line 140) | def forward(self, input):
function conv1d_same_padding (line 145) | def conv1d_same_padding(input, weight, bias, stride, dilation, groups):
class ConvBlock (line 158) | class ConvBlock(nn.Module):
method __init__ (line 160) | def __init__(self, in_channels: int, out_channels: int, kernel_size: int,
method forward (line 173) | def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
FILE: model/models/LSTMClassifier.py
class LSTMClassifier (line 8) | class LSTMClassifier(nn.Module):
method __init__ (line 10) | def __init__(self, in_dim, hidden_dim, num_layers, dropout, bidirectio...
method init_hidden (line 39) | def init_hidden(self, batch_size):
method forward (line 50) | def forward(self, x): # x is (batch_size, sequence_size, num_of_featu...
FILE: model/models/SimpleLSTMClassifier.py
class SimpleLSTMClassifier (line 7) | class SimpleLSTMClassifier(nn.Module):
method __init__ (line 10) | def __init__(self, input_dim, hidden_dim, num_layers_lstm, out_classes...
method forward (line 26) | def forward(self, x):
method init_hidden (line 35) | def init_hidden(self, batch_size):
FILE: model/models/utils.py
class Conv1dSamePadding (line 6) | class Conv1dSamePadding(nn.Conv1d):
method forward (line 11) | def forward(self, input):
function conv1d_same_padding (line 16) | def conv1d_same_padding(input, weight, bias, stride, dilation, groups):
class ConvBlock (line 29) | class ConvBlock(nn.Module):
method __init__ (line 31) | def __init__(self, in_channels: int, out_channels: int, kernel_size: int,
method forward (line 44) | def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
FILE: model/notebooks/fastai_timeseries/exp/nb_ColorfulDim.py
function splitAtFirstParenthesis (line 13) | def splitAtFirstParenthesis(s,showDetails,shapeData):
class ActivationsHistogram (line 20) | class ActivationsHistogram(HookCallback):
method __init__ (line 24) | def __init__(self, learn:Learner, do_remove:bool=True,
method mkHist (line 46) | def mkHist(self, x, useClasses):
method on_train_begin (line 55) | def on_train_begin(self, **kwargs):
method on_epoch_begin (line 66) | def on_epoch_begin(self, **kwargs):
method on_batch_begin (line 69) | def on_batch_begin(self, train, **kwargs):
method hook (line 73) | def hook(self, m:nn.Module, i:Tensors, o:Tensors)->Rank0Tensor:
method on_batch_end (line 79) | def on_batch_end(self, train, **kwargs):
method on_epoch_end (line 90) | def on_epoch_end(self, **kwargs):
method on_train_end (line 113) | def on_train_end(self, **kwargs):
method get_color_value_from_map (line 119) | def get_color_value_from_map(idx:int, cmap='Reds', scale=1):
method getHistImg (line 123) | def getHistImg(act,useClasses):
method getMin (line 130) | def getMin(act,useClasses,zero_bin):
method computeXY (line 137) | def computeXY(l,hscale,perc,hshift=0):
method plotPerc (line 146) | def plotPerc(ax,l,hscale,perc,hshift=0,colorById=False,linewidth=1,add...
method plotActsHist (line 159) | def plotActsHist(self, cols=10, toDisplay=None, hScale = .05,
function telemetry (line 218) | def telemetry(learn:Learner, ah:Callable=None, modulesId:Union[bool, lis...
function noop (line 227) | def noop(x): return x
function get_layers (line 230) | def get_layers(model, cond=noop):
function get_layers_idx (line 246) | def get_layers_idx(learn, cond=noop):
FILE: model/notebooks/fastai_timeseries/exp/nb_ImageDataAugmentation.py
class CutMixCallback (line 8) | class CutMixCallback(LearnerCallback):
method __init__ (line 10) | def __init__(self, learn:Learner, α:float=1., stack_y:bool=True, true_...
method on_train_begin (line 14) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 17) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 42) | def on_train_end(self, **kwargs):
function rand_bbox (line 46) | def rand_bbox(last_input_size, λ):
function cutmix (line 67) | def cutmix(learn:Learner, α:float=1., stack_x:bool=False, stack_y:bool=T...
class RicapLoss (line 79) | class RicapLoss(nn.Module):
method __init__ (line 82) | def __init__(self, crit, reduction='mean'):
method forward (line 93) | def forward(self, output, target):
method get_old (line 104) | def get_old(self):
class RicapCallback (line 110) | class RicapCallback(LearnerCallback):
method __init__ (line 112) | def __init__(self, learn:Learner, stack_y:bool=True):
method on_train_begin (line 116) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 119) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 148) | def on_train_end(self, **kwargs):
function ricap (line 152) | def ricap(learn:Learner, stack_y:bool=True) -> Learner:
FILE: model/notebooks/fastai_timeseries/exp/nb_Initialization.py
class ListContainer (line 13) | class ListContainer():
method __init__ (line 14) | def __init__(self, items):
method __getitem__ (line 16) | def __getitem__(self, idx):
method __len__ (line 25) | def __len__(self): return len(self.items)
method __iter__ (line 26) | def __iter__(self): return iter(self.items)
method __setitem__ (line 27) | def __setitem__(self, i, o): self.items[i] = o
method __delitem__ (line 28) | def __delitem__(self, i): del(self.items[i])
method __repr__ (line 29) | def __repr__(self):
class Hook (line 34) | class Hook():
method __init__ (line 35) | def __init__(self, m, f):
method remove (line 37) | def remove(self): self.hook.remove()
method __del__ (line 38) | def __del__(self): self.remove()
class Hooks (line 40) | class Hooks(ListContainer):
method __init__ (line 41) | def __init__(self, ms, f): super().__init__([Hook(m, f) for m in ms])
method __enter__ (line 42) | def __enter__(self, *args): return self
method __exit__ (line 43) | def __exit__ (self, *args): self.remove()
method __del__ (line 44) | def __del__(self): self.remove()
method __delitem__ (line 45) | def __delitem__(self, i):
method remove (line 48) | def remove(self):
function noop (line 52) | def noop(x): return x
function is_layer (line 54) | def is_layer(*args):
function is_lin_layer (line 59) | def is_lin_layer(l):
function is_conv_lin_layer (line 62) | def is_conv_lin_layer(l):
function is_affine_layer (line 66) | def is_affine_layer(l):
function is_conv_layer (line 69) | def is_conv_layer(l):
function has_bias (line 73) | def has_bias(l):
function has_weight (line 76) | def has_weight(l):
function has_weight_or_bias (line 79) | def has_weight_or_bias(l):
function find_modules (line 82) | def find_modules(m, cond=noop):
function get_layers (line 87) | def get_layers(model, cond=noop):
function append_stat (line 95) | def append_stat(hook, mod, inp, out):
function zmuv_layer (line 103) | def zmuv_layer(model: Callable, m: Callable, xb: Tensor, max_attempts: i...
function zmuv (line 119) | def zmuv(learn: Learner, tol: float = 1e-5, exp_mean: float = 0.,
function svd_orthonormal (line 151) | def svd_orthonormal(w):
function orthogonal_weights_init (line 163) | def orthogonal_weights_init(m):
function ortho_w_init (line 179) | def ortho_w_init(learn: Learner) -> Learner:
function kaiming (line 187) | def kaiming(learn: Learner, normal: bool = True, a: float = 0,
function activations (line 202) | def activations(learn, thr=.1, cond=noop) -> Learner:
function layer_stats (line 285) | def layer_stats(hook, mod, inp, out, thr=.1):
FILE: model/notebooks/fastai_timeseries/exp/nb_NewDataAugmentation.py
class RicapLoss (line 14) | class RicapLoss(nn.Module):
method __init__ (line 17) | def __init__(self, crit, reduction='mean'):
method forward (line 28) | def forward(self, output, target):
method get_old (line 39) | def get_old(self):
class RicapCallback (line 45) | class RicapCallback(LearnerCallback):
method __init__ (line 50) | def __init__(self, learn:Learner, beta:float=.3, stack_y:bool=True):
method on_train_begin (line 54) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 57) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 97) | def on_train_end(self, **kwargs):
function ricap (line 101) | def ricap(learn:Learner, beta:float=.3, stack_y:bool=True) -> Learner:
class CutMixCallback (line 112) | class CutMixCallback(LearnerCallback):
method __init__ (line 118) | def __init__(self, learn:Learner, alpha:float=1., stack_y:bool=True):
method on_train_begin (line 122) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 125) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 154) | def on_train_end(self, **kwargs):
function rand_bbox (line 158) | def rand_bbox(last_input_size, λ):
function cutmix (line 181) | def cutmix(learn:Learner, alpha:float=1., stack_x:bool=False, stack_y:bo...
function get_x1_coords (line 190) | def get_x1_coords(x_size, n_patches, same_size=True):
function get_x1_rand_coords (line 211) | def get_x1_rand_coords(x_size, n_patches, w, h, same_size=True):
function get_x2_coords (line 226) | def get_x2_coords(x_size, bby1, bby2, bbx1, bbx2):
class BlendLoss (line 236) | class BlendLoss(nn.Module):
method __init__ (line 239) | def __init__(self, crit, reduction='mean'):
method forward (line 250) | def forward(self, output, target):
method get_old (line 262) | def get_old(self):
class BlendCallback (line 269) | class BlendCallback(LearnerCallback):
method __init__ (line 271) | def __init__(self, learn:Learner,
method on_train_begin (line 298) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 301) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 374) | def on_train_end(self, **kwargs):
function blend (line 378) | def blend(learn:Learner, size:tuple=(.1, .1), alpha:float=1., fixed_prob...
class TfmScheduler (line 392) | class TfmScheduler(LearnerCallback):
method __init__ (line 394) | def __init__(self,
method on_train_begin (line 431) | def on_train_begin(self, n_epochs: int, epoch: int, **kwargs: Any):
method on_batch_begin (line 460) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 475) | def on_train_end(self, **kwargs):
class MyScheduler (line 479) | class MyScheduler():
method __init__ (line 481) | def __init__(self, total_iters:int, sch_val:StartOptEnd, sch_iter:Opti...
method restart (line 497) | def restart(self): self.n = 0
method step (line 499) | def step(self)->Number:
function cosine_annealing (line 507) | def cosine_annealing(start:Number, end:Number, pct:float, pct_start=.3, ...
function inv_annealing_poly (line 514) | def inv_annealing_poly(start:Number, end:Number, pct:float, degree:Numbe...
function inv_annealing_cos (line 518) | def inv_annealing_cos(start:Number, end:Number, pct:float, **kwargs)->Nu...
function tuplify (line 523) | def tuplify(a):
function get_fn (line 529) | def get_fn(a):
function show_multi_img_tfms (line 537) | def show_multi_img_tfms(learn, rows=3, cols=3, figsize=(8, 8)):
function show_single_img_tfms (line 559) | def show_single_img_tfms(learn, rows=3, cols=3, figsize=(8, 8)):
function show_multi_img_tfms (line 568) | def show_multi_img_tfms(learn, rows=3, cols=3, figsize=(8, 8)):
FILE: model/notebooks/fastai_timeseries/exp/nb_Optimizers.py
class RAdam (line 11) | class RAdam(Optimizer):
method __init__ (line 13) | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weig...
method __setstate__ (line 18) | def __setstate__(self, state):
method step (line 21) | def step(self, closure=None):
class LAMB (line 85) | class LAMB(Optimizer):
method __init__ (line 103) | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-6,
method step (line 118) | def step(self, closure=None):
class LARS (line 190) | class LARS(Optimizer):
method __init__ (line 210) | def __init__(self, params, lr=1e-3, momentum=.9,
method step (line 228) | def step(self, epoch=None, closure=None):
class NovoGrad (line 287) | class NovoGrad(optim.Optimizer):
method __init__ (line 288) | def __init__(self, params, grad_averaging=False, lr=0.01, betas=(0.9, ...
method step (line 299) | def step(self, closure=None):
class Lookahead (line 363) | class Lookahead(Optimizer):
method __init__ (line 364) | def __init__(self, base_optimizer, alpha=0.5, k=6):
method update_slow (line 380) | def update_slow(self, group):
method sync_lookahead (line 392) | def sync_lookahead(self):
method step (line 396) | def step(self, closure=None):
method state_dict (line 406) | def state_dict(self):
method load_state_dict (line 420) | def load_state_dict(self, state_dict):
function LookaheadAdam (line 446) | def LookaheadAdam(params, alpha=0.5, k=6, *args, **kwargs):
class AdaBound (line 456) | class AdaBound(Optimizer):
method __init__ (line 475) | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), final_lr=0.1, ...
method __setstate__ (line 495) | def __setstate__(self, state):
method step (line 500) | def step(self, closure=None):
class AdaBoundW (line 570) | class AdaBoundW(Optimizer):
method __init__ (line 589) | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), final_lr=0.1, ...
method __setstate__ (line 610) | def __setstate__(self, state):
method step (line 615) | def step(self, closure=None):
class RALAMB (line 692) | class RALAMB(Optimizer):
method __init__ (line 694) | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weig...
method __setstate__ (line 699) | def __setstate__(self, state):
method step (line 702) | def step(self, closure=None):
class Ranger (line 796) | class Ranger(Optimizer):
method __init__ (line 798) | def __init__(self, params, lr=1e-3, alpha=0.5, k=6, N_sma_threshhold=5...
method __setstate__ (line 847) | def __setstate__(self, state):
method step (line 851) | def step(self, closure=None):
function Over9000 (line 938) | def Over9000(params, alpha=0.5, k=5, *args, **kwargs):
FILE: model/notebooks/fastai_timeseries/exp/nb_TSBasicData.py
class TSItem (line 19) | class TSItem(ItemBase):
method __init__ (line 22) | def __init__(self, item, *args, **kwargs):
method __str__ (line 30) | def __str__(self):
method clone (line 34) | def clone(self):
method apply_tfms (line 37) | def apply_tfms(self, tfms=None, **kwargs):
method reconstruct (line 43) | def reconstruct(self, item):
method show (line 46) | def show(self, ax=None, title=None, **kwargs):
class TimeSeriesItem (line 66) | class TimeSeriesItem(TSItem): pass
class TSDataBunch (line 70) | class TSDataBunch(DataBunch):
method scale (line 72) | def scale(self, scale_type='standardize', scale_by_channel=False, scal...
method cw (line 179) | def cw(self)->None: return self._get_cw(self.train_dl)
method dbtype (line 182) | def dbtype(self)->str: return '1D'
method _get_cw (line 184) | def _get_cw(self, train_dl):
function show_counts (line 193) | def show_counts(databunch):
class TSPreProcessor (line 204) | class TSPreProcessor(PreProcessor):
method __init__ (line 206) | def __init__(self, ds: ItemList): self.ds = ds
method process (line 208) | def process(self, ds: ItemList):
class TimeSeriesList (line 214) | class TimeSeriesList(ItemList):
method __init__ (line 221) | def __init__(self, items, *args, mask=None, tfms=None, **kwargs):
method get (line 227) | def get(self, i):
method show_xys (line 233) | def show_xys(self, xs, ys, figsize=(10, 10), **kwargs):
method show_xyzs (line 242) | def show_xyzs(self, xs, ys, zs, figsize=(10, 10), **kwargs):
method from_array (line 269) | def from_array(cls, ts, **kwargs):
method from_df (line 273) | def from_df(cls, df, path='.', cols=None, feat=None, processor=None, *...
class TSList (line 293) | class TSList(TimeSeriesList): pass
class MixedTimeSeriesList (line 297) | class MixedTimeSeriesList(ItemList):
method __init__ (line 304) | def __init__(self, items, *args, mask=None, tfms=None, **kwargs):
method get (line 310) | def get(self, i):
method show_xys (line 316) | def show_xys(self, xs, ys, figsize=(10, 10), **kwargs):
method show_xyzs (line 325) | def show_xyzs(self, xs, ys, zs, figsize=(10, 10), **kwargs):
method from_array (line 352) | def from_array(cls, ts, processor=None, **kwargs):
method from_df (line 356) | def from_df(cls, df, path='.', cols=None, feat=None, processor=None, *...
function df2array (line 376) | def df2array(df, feat=None):
FILE: model/notebooks/fastai_timeseries/exp/nb_TSCallbacks.py
class CutMixCallback (line 15) | class CutMixCallback(LearnerCallback):
method __init__ (line 17) | def __init__(self, learn:Learner, alpha:float=1., alpha2:float=0., sta...
method on_train_begin (line 28) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 31) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 67) | def on_train_end(self, **kwargs):
function rand_bbox (line 72) | def rand_bbox(last_input_size, λ):
function cutmix (line 97) | def cutmix(learn:Learner, alpha:float=1., stack_y:bool=True) -> Learner:
function cutout (line 102) | def cutout(learn:Learner, alpha:float=1., stack_y:bool=True, out:bool=Tr...
function cutmixup (line 107) | def cutmixup(learn:Learner, alpha:float=1., alpha2:float=1., stack_y:boo...
class RicapLoss (line 125) | class RicapLoss(nn.Module):
method __init__ (line 128) | def __init__(self, crit, reduction='mean'):
method forward (line 139) | def forward(self, output, target):
method get_old (line 150) | def get_old(self):
class RicapCallback (line 156) | class RicapCallback(LearnerCallback):
method __init__ (line 161) | def __init__(self, learn:Learner, beta:float=.3, stack_y:bool=True):
method on_train_begin (line 165) | def on_train_begin(self, **kwargs):
method on_batch_begin (line 168) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 208) | def on_train_end(self, **kwargs):
function ricap (line 212) | def ricap(learn:Learner, beta:float=.3, stack_y:bool=True) -> Learner:
function get_fn (line 221) | def get_fn(a):
function show_tfms (line 227) | def show_tfms(learn, rows=3, cols=3, figsize=(8, 8)):
function show_tfms_db (line 297) | def show_tfms_db(data, rows=3, cols=3, figsize=(8, 8)):
class OverSamplingCallback (line 327) | class OverSamplingCallback(LearnerCallback):
method __init__ (line 328) | def __init__(self,learn:Learner,weights:torch.Tensor=None):
method on_train_begin (line 332) | def on_train_begin(self, **kwargs):
function oversampling (line 342) | def oversampling(learn: Learner) -> Learner:
class ReduceLROnPlateau (line 352) | class ReduceLROnPlateau(TrackerCallback):
method __init__ (line 354) | def __init__(self, learn:Learner, monitor:str='valid_loss', mode:str='...
method on_train_begin (line 360) | def on_train_begin(self, **kwargs:Any)->None:
method on_epoch_end (line 365) | def on_epoch_end(self, epoch, **kwargs:Any)->None:
function reduce_lr_on_plateau (line 378) | def reduce_lr_on_plateau(learn: Learner, monitor='valid_loss', mode='auto',
class TfmScheduler (line 391) | class TfmScheduler(LearnerCallback):
method __init__ (line 393) | def __init__(self,
method on_train_begin (line 430) | def on_train_begin(self, n_epochs: int, epoch: int, **kwargs: Any):
method on_batch_begin (line 459) | def on_batch_begin(self, last_input, last_target, train, **kwargs):
method on_train_end (line 474) | def on_train_end(self, **kwargs):
class MyScheduler (line 478) | class MyScheduler():
method __init__ (line 480) | def __init__(self, total_iters:int, sch_val:StartOptEnd, sch_iter:Opti...
method restart (line 496) | def restart(self): self.n = 0
method step (line 498) | def step(self)->Number:
function cosine_annealing (line 506) | def cosine_annealing(start:Number, end:Number, pct:float, pct_start=.3, ...
function inv_annealing_poly (line 513) | def inv_annealing_poly(start:Number, end:Number, pct:float, degree:Numbe...
function inv_annealing_cos (line 517) | def inv_annealing_cos(start:Number, end:Number, pct:float, **kwargs)->Nu...
function tuplify (line 522) | def tuplify(a):
function get_fn (line 528) | def get_fn(a):
FILE: model/notebooks/fastai_timeseries/exp/nb_TSCharts.py
function splitAtFirstParenthesis (line 12) | def splitAtFirstParenthesis(s,showDetails,shapeData):
class ActivationsHistogram (line 19) | class ActivationsHistogram(HookCallback):
method __init__ (line 23) | def __init__(self, learn:Learner, do_remove:bool=True,
method mkHist (line 44) | def mkHist(self, x, useClasses):
method on_train_begin (line 53) | def on_train_begin(self, **kwargs):
method on_epoch_begin (line 64) | def on_epoch_begin(self, **kwargs):
method on_batch_begin (line 67) | def on_batch_begin(self, train, **kwargs):
method hook (line 71) | def hook(self, m:nn.Module, i:Tensors, o:Tensors)->Rank0Tensor:
method on_batch_end (line 77) | def on_batch_end(self, train, **kwargs):
method on_epoch_end (line 88) | def on_epoch_end(self, **kwargs):
method on_train_end (line 111) | def on_train_end(self, **kwargs):
method get_color_value_from_map (line 116) | def get_color_value_from_map(idx:int, cmap='Reds', scale=1):
method getHistImg (line 120) | def getHistImg(act,useClasses):
method computeXY (line 127) | def computeXY(l,hscale,perc,hshift=0):
method plotPerc (line 136) | def plotPerc(ax,l,hscale,perc,hshift=0,colorById=False,linewidth=1,add...
method plotActsHist (line 149) | def plotActsHist(self, cols=3, figsize=(20,10), toDisplay=None, hScale...
FILE: model/notebooks/fastai_timeseries/exp/nb_TSDataAugmentation.py
function shuffle_HLs (line 18) | def shuffle_HLs(ts, **kwargs):
function get_diff (line 34) | def get_diff(a):
class TSTransform (line 40) | class TSTransform():
method __init__ (line 44) | def __init__(self, func:Callable, order:Optional[int]=None):
method __call__ (line 53) | def __call__(self, *args:Any, p:float=1., is_random:bool=True, use_on_...
method calc (line 58) | def calc(self, x:TSItem, *args:Any, **kwargs:Any)->Image:
method name (line 64) | def name(self)->str: return self.__class__.__name__
method __repr__ (line 66) | def __repr__(self)->str: return f'{self.name}({self.func.__name__})'
class TSRandTransform (line 70) | class TSRandTransform():
method __call__ (line 82) | def __call__(self, x:TSItem, *args, **kwargs) -> TSItem:
function random_curve_generator (line 126) | def random_curve_generator(ts, magnitude=.1, order=4, noise=None):
function random_cum_curve_generator (line 134) | def random_cum_curve_generator(ts, magnitude=.1, order=4, noise=None):
function random_cum_noise_generator (line 142) | def random_cum_noise_generator(ts, magnitude=.1, noise=None):
function _magnoise (line 151) | def _magnoise(x, magnitude=.1, add=True):
function _timewarp (line 171) | def _timewarp(x, magnitude=.1, order=4):
function _magwarp (line 188) | def _magwarp(x, magnitude=.1, order=4):
function _magscale (line 201) | def _magscale(x, magnitude=.1):
function _dimmagscale (line 215) | def _dimmagscale(x, magnitude=.1):
function _timenoise (line 231) | def _timenoise(x, magnitude=.1):
function _zoomin (line 247) | def _zoomin(x, magnitude=.2):
function _zoomout (line 269) | def _zoomout(x, magnitude=.2):
function _randomzoom (line 289) | def _randomzoom(x, magnitude=.2):
function _randtimestep (line 297) | def _randtimestep(x, magnitude=.1):
function _lookback (line 316) | def _lookback(x, magnitude=.2):
function _dimout (line 333) | def _dimout(ts, magnitude=.2):
function _cutout (line 353) | def _cutout(x, magnitude=.1):
function _timestepout (line 373) | def _timestepout(x, magnitude=.1):
function _timestepzero (line 390) | def _timestepzero(x, magnitude=.1):
function _crop (line 408) | def _crop(x, magnitude=.1):
function _randomcrop (line 428) | def _randomcrop(x, magnitude=.2):
function _centercrop (line 450) | def _centercrop(x, magnitude=.2):
function _maskout (line 472) | def _maskout(x, magnitude=.1):
function TS_geometric_tfms (line 488) | def TS_geometric_tfms(**kwargs):
function TS_erasing_tfms (line 505) | def TS_erasing_tfms(**kwargs):
function TS_tfms (line 519) | def TS_tfms(**kwargs):
function all_TS_tfms (line 523) | def all_TS_tfms(**kwargs):
class RandAugment (line 529) | class RandAugment():
method __init__ (line 530) | def __init__(self, tfms, N=1, **kwargs):
method __call__ (line 542) | def __call__(self, x):
function randaugment (line 549) | def randaugment(learn:Learner, tfms:list=TS_tfms(), N:int=1, **kwargs)->...
FILE: model/notebooks/fastai_timeseries/exp/nb_TSDatasets.py
function decompress_from_url (line 24) | def decompress_from_url(url, target_dir=None, verbose=False):
function get_UCR_univariate_list (line 67) | def get_UCR_univariate_list():
function get_UCR_multivariate_list (line 107) | def get_UCR_multivariate_list():
function get_UCR_univariate (line 120) | def get_UCR_univariate(sel_dataset, parent_dir='data/UCR', verbose=False...
function get_UCR_multivariate (line 164) | def get_UCR_multivariate(sel_dataset, parent_dir='data/UCR', verbose=Fal...
function get_UCR_data (line 222) | def get_UCR_data(dsid, parent_dir='data/UCR', verbose=False, check=True):
function create_UCR_databunch (line 236) | def create_UCR_databunch(dsid, bs=64, scale_type='standardize', scale_by...
function create_seq_optimized (line 247) | def create_seq_optimized(n_samples=1000, seq_len=100, channels=True, see...
function get_translation_invariance_data (line 255) | def get_translation_invariance_data(n_samples, seq_len, seed):
FILE: model/notebooks/fastai_timeseries/exp/nb_TSImageData.py
function GADF_encoder (line 35) | def GADF_encoder(ts, size=None, sample_range=None, overlapping=False,
function GASF_encoder (line 50) | def GASF_encoder(ts, size=None, sample_range=None, overlapping=False,
function MTF_encoder (line 65) | def MTF_encoder(ts,
function RP_encoder (line 83) | def RP_encoder(ts,
function Spectro_encoder (line 106) | def Spectro_encoder(ts,
function Scalo_encoder (line 144) | def Scalo_encoder(ts,
function AddCoordConv (line 164) | def AddCoordConv(arr, **kwargs):
function get_plot_fig (line 180) | def get_plot_fig(ts, size, yrange=(-1, 1), dpi=72):
function fig2img (line 192) | def fig2img(fig, size, return_img=True):
function plot (line 200) | def plot(ts, size=None, yrange=(-1, 1), dpi=72, **kwargs):
function norm (line 245) | def norm(tensor):
function apply_cmap (line 249) | def apply_cmap(tensor, cmap=None, **kwargs):
function ToImage (line 257) | def ToImage(tensor, size=None, cmap=None, **kwargs):
function resize_tensor (line 271) | def resize_tensor(tensor, size):
function add_dim (line 277) | def add_dim(tensor, **kwargs):
function _repeat_ch (line 281) | def _repeat_ch(tensor, **kwargs):
function _add_zero_ch (line 289) | def _add_zero_ch(tensor, **kwargs):
class TS2Image (line 305) | class TS2Image():
method __init__ (line 308) | def __init__(self,
method __call__ (line 350) | def __call__(self, ts):
function get_fill_between_fig (line 370) | def get_fill_between_fig(data, size=224, yrange=(-1, 1), dpi=72):
function plot_fill_between (line 382) | def plot_fill_between(data, size=224, yrange=(-1, 1), dpi=DPI, return_im...
function get_fill_between_plot (line 422) | def get_fill_between_plot(data, sel_TCs=None, sel_channels=None, size=No...
class TS2ImageList (line 440) | class TS2ImageList(ItemList):
method __init__ (line 444) | def __init__(self, items, *args, **kwargs):
method get (line 451) | def get(self, i):
method from_array (line 456) | def from_array(cls, ts, **kwargs):
method from_df (line 460) | def from_df(cls, df, path='.', cols=0, processor=None,
method from_csv (line 479) | def from_csv(cls, path, csv_name, header='infer', **kwargs) -> 'ItemLi...
method reconstruct (line 485) | def reconstruct(self, t):
method show_xys (line 488) | def show_xys(self, xs, ys, imgsize=4, figsize=None, **kwargs):
method show_xyzs (line 498) | def show_xyzs(self, xs, ys, zs, imgsize=4, figsize=None, **kwargs):
FILE: model/notebooks/fastai_timeseries/exp/nb_TSTrain.py
function run_UCR_test (line 18) | def run_UCR_test(iters, epochs, datasets, arch,
function FlatCosAnnealScheduler (line 61) | def FlatCosAnnealScheduler(learn, lr:float=4e-3, epochs:int=1, moms:Floa...
function fit_fc (line 81) | def fit_fc(learn:Learner, epochs:int, lr:float=4e-3,
FILE: model/notebooks/fastai_timeseries/exp/nb_TSUtilities.py
function get_dpi (line 34) | def get_dpi():
function get_elements (line 48) | def get_elements(arr, idx):
function cloning (line 52) | def cloning(list1):
function scale (line 58) | def scale(arr,
function scale_data (line 146) | def scale_data(X_train,
function cap_outliers (line 183) | def cap_outliers(y, lower=None, verbose=False):
function get_y_range (line 196) | def get_y_range(y, problem_type):
function get_stratified_train_val_test_idxs (line 207) | def get_stratified_train_val_test_idxs(y,
function check_overlap (line 268) | def check_overlap(a, b):
function leakage_finder (line 275) | def leakage_finder(train, val, test=None):
function oversampled_idxs (line 289) | def oversampled_idxs(y, idx, seed=1, verbose=False):
function split_data (line 299) | def split_data(X, y, train_idx, valid_idx, test_idx, train_add_idx=None):
function count_classes (line 316) | def count_classes(y):
function get_class_weights (line 320) | def get_class_weights(target):
function get_weighted_sampler (line 328) | def get_weighted_sampler(target):
function history_output (line 335) | def history_output(learn, max_lr, epochs, t0, t1):
function model_summary (line 397) | def model_summary(model, data, find_all=False, print_mod=False):
function get_batch (line 405) | def get_batch(dl, learn):
function conv (line 412) | def conv(ni, nf, ks=3, stride=1, bias=False):
function noopr (line 415) | def noopr(x, **kwargs):
function ToTensor (line 420) | def ToTensor(arr, **kwargs):
function ToArray (line 428) | def ToArray(arr):
function To3dTensor (line 437) | def To3dTensor(arr):
function To2dTensor (line 447) | def To2dTensor(arr):
function To1dTensor (line 456) | def To1dTensor(arr):
function To3dArray (line 465) | def To3dArray(arr):
function To2dArray (line 474) | def To2dArray(arr):
function To1dArray (line 482) | def To1dArray(arr):
function ToDevice (line 490) | def ToDevice(ts, **kwargs):
function mape (line 507) | def mape(pred, targ):
class MAPE (line 514) | class MAPE(RegMetrics):
method on_epoch_end (line 516) | def on_epoch_end(self, last_metrics, **kwargs):
class BPR (line 522) | class BPR(CMScores):
method __init__ (line 526) | def __init__(self, alpha=1, beta=1):
method on_epoch_end (line 530) | def on_epoch_end(self, last_metrics, **kwargs):
class FocalLoss (line 535) | class FocalLoss(nn.Module):
method __init__ (line 536) | def __init__(self, α=.25, γ=2., weight=None):
method forward (line 542) | def forward(self, inputs, targets, **kwargs):
function get_model_hp (line 549) | def get_model_hp(tsmodel, kwargs=[{}]):
function get_outcome_stats (line 560) | def get_outcome_stats (learn, y_outcome, problem_type, train, valid, tes...
function get_last_pos (line 631) | def get_last_pos(arr, val, ndigits=5):
function plot_weights (line 636) | def plot_weights(learn):
function load_params (line 645) | def load_params(m, path):
function noop (line 650) | def noop(x): return x
function get_layers (line 652) | def get_layers(model, cond=noop):
function count_params (line 656) | def count_params(model):
function nb_auto_export (line 665) | def nb_auto_export():
FILE: model/notebooks/fastai_timeseries/exp/rocket_functions.py
function generate_kernels (line 15) | def generate_kernels(input_length, num_kernels, kss=[7, 9, 11], pad=True...
function apply_kernel (line 37) | def apply_kernel(X, weights, length, bias, dilation, padding):
function apply_kernels (line 59) | def apply_kernels(X, kernels):
FILE: model/notebooks/torchtimeseries/models/FCN.py
class FCN (line 16) | class FCN(nn.Module):
method __init__ (line 17) | def __init__(self,c_in,c_out,layers=[128,256,128],kss=[7,5,3]):
method forward (line 25) | def forward(self, x):
FILE: model/notebooks/torchtimeseries/models/InceptionTime.py
function noop (line 11) | def noop(x):
function shortcut (line 14) | def shortcut(c_in, c_out):
class Inception (line 18) | class Inception(nn.Module):
method __init__ (line 19) | def __init__(self, c_in, bottleneck=32, ks=40, nb_filters=32):
method forward (line 37) | def forward(self, x):
class InceptionBlock (line 49) | class InceptionBlock(nn.Module):
method __init__ (line 50) | def __init__(self,c_in,bottleneck=32,ks=40,nb_filters=32,residual=True...
method forward (line 73) | def forward(self, x):
class InceptionTime (line 84) | class InceptionTime(nn.Module):
method __init__ (line 85) | def __init__(self,c_in,c_out,bottleneck=32,ks=40,nb_filters=32,residua...
method forward (line 92) | def forward(self, x):
FILE: model/notebooks/torchtimeseries/models/ROCKET.py
class ROCKET (line 10) | class ROCKET(nn.Module):
method __init__ (line 11) | def __init__(self, c_in, seq_len, n_kernels=10000, kss=[7, 9, 11]):
method forward (line 37) | def forward(self, x):
FILE: model/notebooks/torchtimeseries/models/ResCNN.py
class Block (line 14) | class Block(nn.Module):
method __init__ (line 15) | def __init__(self, ni, nf, ks=[7, 5, 3], act_fn='relu'):
method forward (line 25) | def forward(self, x):
class ResCNN (line 36) | class ResCNN(nn.Module):
method __init__ (line 37) | def __init__(self, c_in, c_out):
method forward (line 47) | def forward(self, x):
FILE: model/notebooks/torchtimeseries/models/ResNet.py
class ResBlock (line 17) | class ResBlock(nn.Module):
method __init__ (line 18) | def __init__(self, ni, nf, ks=[7, 5, 3], act_fn='relu'):
method forward (line 28) | def forward(self, x):
class ResNet (line 38) | class ResNet(nn.Module):
method __init__ (line 39) | def __init__(self,c_in, c_out):
method forward (line 49) | def forward(self, x):
FILE: model/notebooks/torchtimeseries/models/layers.py
function noop (line 16) | def noop(x): return x
class LambdaPlus (line 18) | class LambdaPlus(Module):
method __init__ (line 19) | def __init__(self, func, *args, **kwargs): self.func,self.args,self.kw...
method forward (line 20) | def forward(self, x): return self.func(x, *self.args, **self.kwargs)
function get_act_layer (line 23) | def get_act_layer(act_fn, **kwargs):
function same_padding1d (line 35) | def same_padding1d(seq_len,ks,stride=1,dilation=1):
class ZeroPad1d (line 45) | class ZeroPad1d(nn.ConstantPad1d):
method __init__ (line 46) | def __init__(self, padding):
class ConvSP1d (line 49) | class ConvSP1d(nn.Module):
method __init__ (line 51) | def __init__(self,c_in,c_out,ks,stride=1,padding='same',dilation=1,bia...
method forward (line 59) | def forward(self, x):
function convlayer (line 63) | def convlayer(c_in,c_out,ks=3,padding='same',bias=True,stride=1,
class Flatten (line 78) | class Flatten(nn.Module):
method forward (line 79) | def forward(self, x): return x.view(x.size(0), -1)
class Squeeze (line 81) | class Squeeze(nn.Module):
method __init__ (line 82) | def __init__(self, dim=-1):
method forward (line 85) | def forward(self, x): return x.squeeze(dim=self.dim)
class Unsqueeze (line 87) | class Unsqueeze(nn.Module):
method __init__ (line 88) | def __init__(self, dim=-1):
method forward (line 91) | def forward(self, x): return x.unsqueeze(dim=self.dim)
class YRange (line 93) | class YRange(nn.Module):
method __init__ (line 94) | def __init__(self, y_range:tuple):
method forward (line 97) | def forward(self, x):
class Mult (line 103) | class Mult(nn.Module):
method __init__ (line 104) | def __init__(self, mult, trainable=True):
method forward (line 108) | def forward(self, x):
class Exp (line 112) | class Exp(nn.Module):
method __init__ (line 113) | def __init__(self, exp, trainable=True):
method forward (line 117) | def forward(self, x):
function get_cls (line 121) | def get_cls(model, c_in, seq_len, c_out, **kwargs):
class FTSwishPlus (line 130) | class FTSwishPlus(nn.Module):
method __init__ (line 131) | def __init__(self, threshold=-.25, sub=-.1, **kwargs):
method forward (line 135) | def forward(self, x):
class Swish (line 141) | class Swish(nn.Module):
method __init__ (line 142) | def __init__(self, inplace=False):
method forward (line 146) | def forward(self, x):
class GeneralRelu (line 154) | class GeneralRelu(nn.Module):
method __init__ (line 155) | def __init__(self, leak=None, sub=0., maxv=None, **kwargs):
method forward (line 159) | def forward(self, x):
class Mish (line 170) | class Mish(nn.Module):
method __init__ (line 171) | def __init__(self):
method forward (line 173) | def forward(self, x):
function get_act_fn_norm (line 177) | def get_act_fn_norm(act_fn):
class AFN (line 185) | class AFN(nn.Module):
method __init__ (line 186) | def __init__(self, act_fn=F.relu, trainable=True, **kwargs):
method forward (line 193) | def forward(self, x):
FILE: model/schedulers/CycleLR.py
class CyclicLR (line 6) | class CyclicLR(_LRScheduler):
method __init__ (line 8) | def __init__(self, optimizer, schedule, last_epoch=-1):
method get_lr (line 13) | def get_lr(self):
function cosine (line 17) | def cosine(t_max, eta_min=0):
FILE: model/train_lstm.py
function load_data (line 40) | def load_data():
function train (line 67) | def train():
FILE: router/recvCSI/csi_fun.c
function is_big_endian (line 36) | bool is_big_endian() {
function bit_convert (line 45) | int bit_convert(int data, int maxbit) {
function fill_csi_matrix (line 53) | void fill_csi_matrix(u_int8_t* csi_addr, int nr, int nc, int num_tones, ...
function open_csi_device (line 118) | int open_csi_device() {
function close_csi_device (line 123) | void close_csi_device(int fd) {
function read_csi_buf (line 128) | int read_csi_buf(unsigned char* buf_addr, int fd, int BUFSIZE) {
function record_status (line 140) | void record_status(unsigned char* buf_addr, int cnt, csi_struct* csi_sta...
function record_csi_payload (line 182) | void record_csi_payload(unsigned char* buf_addr, csi_struct* csi_status,...
function process_csi (line 205) | void process_csi(unsigned char* data_buf, csi_struct* csi_status, COMPLE...
FILE: router/recvCSI/csi_fun.h
type COMPLEX (line 27) | typedef struct {
type csi_struct (line 32) | typedef struct {
FILE: router/recvCSI/main.c
function sig_handler (line 54) | void sig_handler(int signo) {
function main (line 59) | int main(int argc, char* argv[]) {
FILE: router/sendData/sendData.c
function main (line 53) | int main(int argc, char* argv[]) {
FILE: router/sendData/sendData_con.c
function main (line 54) | int main(int argc, char* argv[]) {
Condensed preview — 72 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (8,450K chars).
[
{
"path": ".gitignore",
"chars": 3053,
"preview": "# Data\ndataset\n./data_retrieval_and_analysis/dataset\n./data_retrieval_and_analysis/experiments### C template\n\n# Prerequi"
},
{
"path": "CONTRIBUTING.md",
"chars": 938,
"preview": "# Contributing\n\nWhen contributing to this repository, please first discuss the change you wish to make via issue,\nemail,"
},
{
"path": "LICENSE.md",
"chars": 17992,
"preview": "\t\t GNU GENERAL PUBLIC LICENSE\n\t\t Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc."
},
{
"path": "README.md",
"chars": 3128,
"preview": "# Human Activity Recognition based on Wi-Fi CSI Data - A Deep Neural Network Approach\n\nThis is a repository with source "
},
{
"path": "data_retrieval/README.md",
"chars": 1305,
"preview": "# CSI data retrieval, visualization and storing\n\nThis program is responsible for retrieving CSI data from the router (th"
},
{
"path": "data_retrieval/csi_extraction/__init__.py",
"chars": 84,
"preview": "from .read_csi import *\nfrom .read_log_file import *\nfrom .csi_calibration import *\n"
},
{
"path": "data_retrieval/csi_extraction/csi_calibration.py",
"chars": 1172,
"preview": "import numpy as np\n\n\ndef calibrate_phase(phases):\n \"\"\"\n CSI phase calibration\n Based on https://github.com/ermo"
},
{
"path": "data_retrieval/csi_extraction/read_csi.py",
"chars": 4629,
"preview": "import io\nimport struct\nfrom math import atan2\n\nBITS_PER_BYTE = 8\nBITS_PER_SYMBOL = 10\nbitmask = (1 << BITS_PER_SYMBOL) "
},
{
"path": "data_retrieval/csi_extraction/read_log_file.py",
"chars": 1314,
"preview": "import os\n\nfrom .read_csi import *\n\n\ndef read_log_file(filename, ignore_endian=0, endian=\"\", check_tones=1):\n f = ope"
},
{
"path": "data_retrieval/run_test_client.py",
"chars": 1946,
"preview": "import os\nimport socket\nimport struct\nimport argparse\n\n\ndef run_client(filename: str, host: str, port: int) -> None:\n "
},
{
"path": "data_retrieval/run_visualization_server.py",
"chars": 9194,
"preview": "import argparse\nimport csv\nimport io\nimport os\nfrom copy import deepcopy\n\nimport cv2\nimport numpy as np\nfrom PyQt6 impor"
},
{
"path": "data_retrieval/window.ui",
"chars": 1210,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>Widget</class>\n <widget class=\"QWidget\" name=\"Widget\">"
},
{
"path": "model/.gitignore",
"chars": 1026,
"preview": "__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdi"
},
{
"path": "model/data_calibration.py",
"chars": 3401,
"preview": "import pandas as pd\nimport numpy as np\nimport pywt\n\n\ndef calibrate_single_phase(phases):\n \"\"\"\n Calibrate phase dat"
},
{
"path": "model/dataset.py",
"chars": 6654,
"preview": "import os\n\nimport torch\nimport numpy as np\nimport pandas as pd\n\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom d"
},
{
"path": "model/label_activities.py",
"chars": 1139,
"preview": "from enum import Enum\nfrom os import path\n\nimport numpy as np\nimport pandas as pd\n\nPATH_TO_DATASET = \"./dataset/vitalnia"
},
{
"path": "model/label_human_boxes.py",
"chars": 4803,
"preview": "import cv2\nimport numpy as np\nimport pandas as pd\n\nimport os\nfrom os import listdir\nfrom tqdm import tqdm\n\nlabels = open"
},
{
"path": "model/main.py",
"chars": 0,
"preview": ""
},
{
"path": "model/metrics.py",
"chars": 1073,
"preview": "import torch\nfrom torch.nn import functional as F\nfrom tqdm import tqdm\n\n# Cuda support\ndevice = torch.device(\"cuda:0\" i"
},
{
"path": "model/models/FCNBaseline.py",
"chars": 1181,
"preview": "import torch\nfrom torch import nn\n\nfrom .utils import ConvBlock\n\n\nclass FCNBaseline(nn.Module):\n \"\"\"A PyTorch impleme"
},
{
"path": "model/models/InceptionTime.py",
"chars": 7225,
"preview": "import torch\nfrom torch import nn\nimport torch.nn.functional as F\n\nfrom typing import cast, Union, List\n\n\nclass Inceptio"
},
{
"path": "model/models/LSTMClassifier.py",
"chars": 2021,
"preview": "import torch\nfrom torch import nn\nfrom torch.autograd import Variable\n\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_a"
},
{
"path": "model/models/SimpleLSTMClassifier.py",
"chars": 1630,
"preview": "import torch\nfrom torch import nn\n\n# device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\ndevice = to"
},
{
"path": "model/models/__init__.py",
"chars": 177,
"preview": "from .LSTMClassifier import LSTMClassifier\nfrom .SimpleLSTMClassifier import SimpleLSTMClassifier\nfrom .FCNBaseline impo"
},
{
"path": "model/models/utils.py",
"chars": 1645,
"preview": "import torch\nfrom torch import nn\nimport torch.nn.functional as F\n\n\nclass Conv1dSamePadding(nn.Conv1d):\n \"\"\"Represent"
},
{
"path": "model/notebooks/DataFilteringAndFeatureEngineering.ipynb",
"chars": 555,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "model/notebooks/DetectPersonBoxes.ipynb",
"chars": 13668,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n "
},
{
"path": "model/notebooks/ExperimentsVisualization.ipynb",
"chars": 7758456,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {\n \"pycharm\": {\n \"name\": \"#%%\\n"
},
{
"path": "model/notebooks/HARSimpleLSTM.ipynb",
"chars": 82588,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"markdown\",\n \"metadata\": {},\n \"source\": [\n \"# Simple LSTM for HAR time series c"
},
{
"path": "model/notebooks/Inception.ipynb",
"chars": 56068,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n "
},
{
"path": "model/notebooks/VisualizeCSI.ipynb",
"chars": 148678,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {\n \"pycharm\": {\n \"name\": \"#%%\\n"
},
{
"path": "model/notebooks/fastai_timeseries/__init__.py",
"chars": 18,
"preview": "from .exp import *"
},
{
"path": "model/notebooks/fastai_timeseries/exp/__init__.py",
"chars": 442,
"preview": "from .nb_TSDatasets import *\nfrom .nb_TSBasicData import *\nfrom .nb_TSUtilities import *\nfrom .nb_TSDataAugmentation imp"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_ColorfulDim.py",
"chars": 10800,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./ColorfulDim.ipynb (unless otherwise specified)\n\nfrom fastai.torch_core impo"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_ImageDataAugmentation.py",
"chars": 6252,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./ImageDataAugmentation.ipynb (unless otherwise specified)\n\nfrom fastai.torch"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_Initialization.py",
"chars": 10433,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./Initialization.ipynb (unless otherwise specified)\n\n# To develop .zmuv() I'v"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_NewDataAugmentation.py",
"chars": 24908,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./NewDataAugmentation.ipynb (unless otherwise specified)\n\nfrom fastai.torch_c"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_Optimizers.py",
"chars": 39677,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./Optimizers.ipynb (unless otherwise specified)\n\n#from fastai.torch_core impo"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSBasicData.py",
"chars": 15264,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSBasicData.ipynb (unless otherwise specified)\nimport fastai\nfrom fastai.to"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSCallbacks.py",
"chars": 21602,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSCallbacks.ipynb (unless otherwise specified)\ntry: from exp.nb_TSBasicData"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSCharts.py",
"chars": 8690,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSCharts.ipynb (unless otherwise specified)\n\nfrom fastai.vision import Lear"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSDataAugmentation.py",
"chars": 16857,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSDataAugmentation.ipynb (unless otherwise specified)\n\nimport copy\nimport n"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSDatasets.py",
"chars": 11520,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSDatasets.ipynb (unless otherwise specified)\nfrom pathlib import Path\nimpo"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSImageData.py",
"chars": 17218,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSImageData.ipynb (unless otherwise specified)\n\nimport torch\nimport fastai\n"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSTrain.py",
"chars": 4268,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSTrain.ipynb (unless otherwise specified)\n\nfrom fastai.callbacks import *\n"
},
{
"path": "model/notebooks/fastai_timeseries/exp/nb_TSUtilities.py",
"chars": 24043,
"preview": "#AUTOGENERATED! DO NOT EDIT! file to edit: ./TSUtilities.ipynb (unless otherwise specified)\n\nimport fastai\nfrom fastai.b"
},
{
"path": "model/notebooks/fastai_timeseries/exp/rocket_functions.py",
"chars": 2759,
"preview": "# Angus Dempster, Francois Petitjean, Geoff Webb\n\n# Dempster A, Petitjean F, Webb GI (2019) ROCKET: Exceptionally fast a"
},
{
"path": "model/notebooks/torchtimeseries/models/FCN.py",
"chars": 1269,
"preview": "# This is an unofficial PyTorch implementation by Ignacio Oguiza - oguiza@gmail.com based on:\n\n# Wang, Z., Yan, W., & Oa"
},
{
"path": "model/notebooks/torchtimeseries/models/InceptionTime.py",
"chars": 3652,
"preview": "# This is an unofficial PyTorch implementation by Ignacio Oguiza - oguiza@gmail.com based on:\n\n# Fawaz, H. I., Lucas, B."
},
{
"path": "model/notebooks/torchtimeseries/models/ROCKET.py",
"chars": 2219,
"preview": "# This is an unofficial Multivariate, GPU (PyTorch) implementation by Ignacio Oguiza - oguiza@gmail.com based on:\n\n# Dem"
},
{
"path": "model/notebooks/torchtimeseries/models/ResCNN.py",
"chars": 1808,
"preview": "# This is an unofficial PyTorch implementation by Ignacio Oguiza - oguiza@gmail.com based on:\n\n# Zou, X., Wang, Z., Li, "
},
{
"path": "model/notebooks/torchtimeseries/models/ResNet.py",
"chars": 2014,
"preview": "# This is an unofficial PyTorch implementation by Ignacio Oguiza - oguiza@gmail.com based on:\n\n# Wang, Z., Yan, W., & Oa"
},
{
"path": "model/notebooks/torchtimeseries/models/__init__.py",
"chars": 135,
"preview": "from .FCN import *\nfrom .ResNet import *\nfrom .ResCNN import *\nfrom .InceptionTime import *\nfrom .layers import *\nfrom ."
},
{
"path": "model/notebooks/torchtimeseries/models/layers.py",
"chars": 6704,
"preview": "\n#original FTSwish = https://arxiv.org/abs/1812.06247\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as "
},
{
"path": "model/schedulers/CycleLR.py",
"chars": 575,
"preview": "import numpy as np\n\nfrom torch.optim.lr_scheduler import _LRScheduler\n\n\nclass CyclicLR(_LRScheduler):\n\n def __init__("
},
{
"path": "model/schedulers/__init__.py",
"chars": 37,
"preview": "from .CycleLR import CyclicLR, cosine"
},
{
"path": "model/test.py",
"chars": 1560,
"preview": "import logging\n\nimport torch\nfrom torch.utils.data import DataLoader\n\nfrom dataset import CSIDataset\nfrom metrics import"
},
{
"path": "model/train_lstm.py",
"chars": 4756,
"preview": "import logging\n\nimport torch\nfrom torch import nn\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom torch.util"
},
{
"path": "model/yolo_data/coco.names",
"chars": 626,
"preview": "person\nbicycle\ncar\nmotorbike\naeroplane\nbus\ntrain\ntruck\nboat\ntraffic light\nfire hydrant\nstop sign\nparking meter\nbench\nbir"
},
{
"path": "model/yolo_data/yolov3.cfg",
"chars": 8343,
"preview": "[net]\n# Testing\nbatch=1\nsubdivisions=1\n# Training\n# batch=64\n# subdivisions=16\nwidth=416\nheight=416\nchannels=3\nmomentum="
},
{
"path": "requirements.txt",
"chars": 140,
"preview": "torch==2.8.0\nnumpy==1.23.1\npandas==1.4.3\nopencv-python==4.8.1.78\nPyQt6==6.3.1\npyqtgraph==0.12.4\naltair==4.2.0\nmatplotlib"
},
{
"path": "router/recvCSI/Makefile",
"chars": 251,
"preview": "OBJS = csi_fun.o main.o\nCC = mips-openwrt-linux-gcc # gcc\n\nrecv_csi: $(OBJS)\n\t$(CC) $(OBJS) -o recv_csi\n\ncsi_fun.o: csi_"
},
{
"path": "router/recvCSI/README.md",
"chars": 117,
"preview": "Create the recvCSI tool by using `make`.\n\nUsage:\n\n* recvCSI: `./recv_csi <udp server(IP)> <udp port> <output_file>`\n"
},
{
"path": "router/recvCSI/bin/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "router/recvCSI/csi_fun.c",
"chars": 7512,
"preview": "/*\n * =====================================================================================\n * Filename: ath9k_csi"
},
{
"path": "router/recvCSI/csi_fun.h",
"chars": 2432,
"preview": "/*\n * =====================================================================================\n * Filename: csi_fun."
},
{
"path": "router/recvCSI/main.c",
"chars": 6665,
"preview": "/*\n * =====================================================================================\n * Filename: main.c\n "
},
{
"path": "router/sendData/Makefile",
"chars": 170,
"preview": "CC = mips-openwrt-linux-gcc # gcc\n\nsend_data: sendData.c\n\t$(CC) -o send_Data sendData.c\n\t$(CC) -o send_Data_con sendData"
},
{
"path": "router/sendData/README.md",
"chars": 209,
"preview": "Create the sendData tool by using `make`.\n\nUsage:\n\n* sendData: `./sendData <Interface Name> <HW Address> <Package count>"
},
{
"path": "router/sendData/bin/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "router/sendData/sendData.c",
"chars": 6134,
"preview": "/*\n * =====================================================================================\n * Filename: sendData"
},
{
"path": "router/sendData/sendData_con.c",
"chars": 6116,
"preview": "/*\n * =====================================================================================\n * Filename: sendData"
}
]
About this extraction
This page contains the full source code of the Retsediv/WIFI_CSI_based_HAR GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 72 files (8.0 MB), approximately 2.1M tokens, and a symbol index with 561 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.