Full Code of Retsediv/WIFI_CSI_based_HAR for AI

master 54bee76dcb00 cached
72 files
8.0 MB
2.1M tokens
561 symbols
1 requests
Download .txt
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": "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
Download .txt
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
Download .txt
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.

Copied to clipboard!