gaitutils technical documentation
Overview
This document is intended to provide technical information about the gaitutils package for developers and advanced users. It assumes basic knowledge about gait analysis and related concepts (e.g. trials, gait cycles and gait events.)
gaitutils provides the following core functionality:
automatically process gait data using Vicon Nexus
load data from C3D files or Vicon Nexus
plot gait curves, EMG data, time-distance parameters etc.
create reports based on the plots
A lot of the functionality is based on the Trial
class, which handles data
normalization etc. The package functionality can be accessed in scripts via
(import gaitutils
) or from a PyQt5-based GUI.
Installation and dependencies
gaitutils depends on a lot of other packages, including PyQt, SciPy, NumPy,
dash, plotly, matplotlib, btk
etc. To facilitate the installation, a Conda
environment.yml
file is provided, which should install all the dependencies.
One potentially problematic dependency is the btk
package (Biomechanical
ToolKit), which is no longer maintained. However, an unofficial Python 3
compatible release of btk
is available at
https://anaconda.org/conda-forge/btk.
The Vicon Nexus Python API and btk
are “soft” dependencies. It’s not
necessary to have Vicon Nexus installed to run gaitutils. Without Nexus, you can
still read data from C3D files, but obviously autoprocessing will not work. Even
though btk
is technically a soft dependency, the package cannot do very much
without it.
The Vicon Nexus Python API is currently (as of Nexus 2.12 and later) provided as
a pip
package in the Vicon Nexus installation. After creating the conda
environment, the API package still needs to be installed as follows (edit the
Nexus path as necessary).
C:\>cd "C:\Program Files\Vicon\Nexus2.13\SDK\Win64\Python"
pip install ./viconnexusapi
This may need superuser privileges.
Misc dependencies
The package depends on two other packages by the author: ulstools
and
configdot
. ulstools
contains various functionality shared by the
applications used in the Helsinki gait lab configdot
provides an extended
mechanism for reading and writing INI files.
Installation for development
The default installation mechanism will install a snapshot of the package from the
latest GitHub master branch. It can then be updated with pip
. However,
developers and advanced users will wish to install in “git mode” instead (i.e.
run the package directly from a cloned repository). This can be done as follows:
Clone the repository from GitHub
Perform a normal install using the conda environment. You may want to use the development version of the conda environment specification –
environment_dev.yml
for Windows orenvironment_linux_dev.yml
for Linux. The development versions of the specification do not specify package versions. This means that conda will try to install the most recent versions of all the package requirements, potentially leading to problems.Activate the new environment
Remove the pip install using
pip uninstall gaitutils
Change directory to root of cloned repository
Run
pip install -e .
(previouslypython setup.py develop
which is now deprecated)
Desktop shortcuts
To facilitate use of the GUI, a desktop shortcut mechanism is included. You can
run gaitmenu_make_shortcut
from the activated environment to create a
shortcut. This should create a shortcut that activates the correct conda
environment and runs the GUI.
Documentation
The documentation is written in RST using Sphinx. It is available online at https://gaitutils.readthedocs.io/en/latest/#. There is a commit hook on GitHub that automatically updates the online documentation when the master branch is updated. Locally, the documentation can be recreated by running
sphinx-build -b html docs docs-html
in the project’s root directory. The resulting html documentation will appear in
docs-html
directory.
To build the documentation, you need to have the sphinx_rtd_theme
package
installed. It can be installed using pip
.
Unit tests
The package includes unit tests written with pytest
. The tests can be run
from the tests/
directory by typing
python -m pytest --runslow --runnexus
The --runnexus
option runs also tests that require a working installation of
Vicon Nexus. Leave it out if you don’t have Nexus. --runslow
runs additional
tests that are marked as slow (e.g. report creation.)
Many of the tests require test data that is currently not distributed with the package, so they can currently be run only at the Helsinki gait lab.
Configuration
The package is configured via an INI file. The user-specific INI file
.gaitutils.cfg
file is located in the users’ home directory. Initially it is
a copy of data/default.cfg
from the package. The user can modify it either
from a text editor or via the GUI configuration interface.
The configuration INI files are parsed and written by the configdot
package
written by the author. The idea of configdot
is to support direct definition
of Python objects in config files, among other features not provided by standard
packages such as ConfigObj
.
During module import, a config object called cfg
is created in config.py
by reading the default configuration values and overriding them with any
user-specified values. After importing the config (from gaitutils import
cfg
), the items defined in the config are accessible as cfg.section.item
.
For example, print(cfg.autoproc.crop_margin)
will print 10
in the
default configuration. The values can be set using similar syntax, i.e.
cfg.autoproc.crop_margin = 15
. The configuration is package-global, i.e. any
changes will immediately be reflected in any other code that uses cfg
New config items can be defined simply by inserting the item and its default
value into a section in gaitutils/data/default.cfg
.
The GUI has dialog under File/Options that will automatically display all
configuration items defined in default.cfg
on a tabbed page and allow the
user to edit them.
Algorithms
Two important algorithms in gaitutils are
automatic detection of gait events
automatic detection of forceplate contacts
These are defined in utils.py
.
Event detection
See gaitutils.utils.automark_events()
.
gaitutils can detect gait events (foot strikes and toeoffs) based purely on marker data. The algorithm is based on velocity thresholding. At the frame where the velocity of the foot falls below a certain threshold, a foot strike event is created. When the velocity rises above another threshold, a toeoff event is created. The foot velocity is computed as the average velocity of the foot markers (ankle, toe and heel).
The velocity thresholds can be determined based on heuristics. The default heuristic is that the foot strike and toeoff occur at 20% and 45% of the subject’s peak foot velocity during the trial, respectively. This gives surprisingly good results for most subjects. However, more accurate thresholds can be determined based on the forceplate data. That is, if a valid forceplate contact is available for the trial, the foot velocity is determined at the moment of foot strike and toeoff, and those values are used as thresholds. When processing a whole gait session, it would be possible to use all trials to determine the velocity threshold. However, this doesn’t work in cases where there is a lot of intertrial variance (the threshold doesn’t generalize across trials).
Evaluation of forceplate contacts
Detection of forceplate contacts is necessary for kinetic models, for which we only want to consider cycles where valid forceplate contact occurs. For such cycles, we will be able know the reaction force for the duration of the cycle. From this force, various kinetic values can be computed, such as the moment at the knee joint.
Nexus stores forceplate contact information in the Eclipse database (see below). However for each plate, only the context (Right, Left or Invalid) is stored. Nexus does not e.g. store the frames where the forceplate contacts occur. Thus, we have to detect them ourselves.
“Valid” forceplate contact means that 1) the foot is completely inside the
forceplate area and 2) the contralateral foot does not contact the same plate
during the gait cycle (which would be a “double contact” and invalidate the
force data). In gaitutils, the foot is modelled as a simple triangle. The
vertices of the triangle are estimated from marker data. The position of the
heel marker is used as the heel vertex. For the other two vertices (“big toe”
and “little toe”) there are no markers available. Thus, the code attempts to
estimate their positions. If explicit information about foot length is
available, the accuracy will be improved. Foot length can be supplied as an
extra model parameter in Nexus (RightFootLen
and LeftFootLen
).
For the implementation, see gaitutils.utils.detect_forceplate_events()
.
The Trial object
Trial objects store trial data and related metadata. They also provide normalization of data to gait cycles. Creation of Trial() instances goes roughly as follows:
Trial metadata (events, subject information etc.) is read from the source (Nexus or C3D).
Forceplate contacts are detected automatically. This needs to be done at trial creation, since we need to know which gait cycles have valid forceplate contact (see above). By default, the detection of forceplate contacts is affected by the Eclipse database (see next section).
Model, marker and EMG data is read, mostly lazily (not at trial creation, but later when the data is needed). For C3D-based trial objects, the laziness is largely inconsequential. For trials that read directly from Nexus, this presents a potential problem, as the underlying Nexus data may change between the API calls. gaitutils tries to keep track of when the underlying data has changed. However, for this and other reasons (e.g. speed), it is recommended to create trial objects from C3D files instead.
Finally, gait cycles are created based on the events.
The Eclipse database
Vicon includes a database component called ProEclipse (or simply Eclipse) with
Nexus. It is used to store various trial- and session-related metadata. Eclipse
stores the information in INI-style files with .enf
extension, one file for
each trial.
gaitutils makes use of Eclipse in two ways: reading and writing forceplate-related metadata, and looking for tags in the Eclipse “notes” or “description” fields.
Forceplate data
The Eclipse forceplate metadata simply indicates which forceplates have a valid foot contact. For example, “FP1=Right” means that valid right foot contact has occurred on the 1st forceplate. The values can be set manually, and they affect the calculations performed by Plug-in Gait. Apparently, “Auto” means that Plug-in Gait should try to perform autodetection of the foot contact, “Invalid” means that the contact will not be taken into account, and “Left” and “Right” can be used to force a valid contact.
The Eclipse forceplate information is also used by default when loading trials.
This can be used to force kinetics data to be available for given cycles. For
example, if there is poor forceplate contact for plate 1 (e.g. partial contact)
but you still want to see kinetics for that plate, you can set the Eclipse key
for forceplate 1 to “Right” in Vicon Nexus. When the trial is loaded in
gaitutils, the Eclipse info will be read and corresponding cycle will have
kinetics available. This behavior can be disabled by setting the configuration
item cfg.trial.use_eclipse_fp_info
to False
.
Autoprocessing
gaitutils can automatically process gait data in conjuction with Vicon Nexus. This includes:
Running preprocessing pipelines in Nexus (typically reconstruct, label, gap fill, filter)
Detection of forceplate contacts
Identification of trials with good data
Automatic creation of gait events (foot strikes and toeoffs)
Running various gait models (e.g. Plug-in Gait)
In addition to Nexus pipelines, autoprocessing relies on the two algorithms described above. Some notes:
The autoprocessing operation deletes C3D files for the dynamic trials to be processed. This is the only way to ensure that the data in Nexus is unmodified (and not e.g. cropped). When C3D file for a trial is not available, Nexus loads the original data from the X1D and X2D files.
By default, the autoprocessing operation does its own detection of forceplate contacts and writes the Eclipse forceplate keys as either “Right”, “Left” or “Invalid”, according to the detected contacts. The keys can also be reset by setting
cfg.autoproc.write_eclipse_fp_info
toreset
. This will set the keys to “Auto” so that autodetection occurs in Plug-in Gait.
Reporting
Gait reports can be generated from processed data. Interactive (web browser -based) and hardcopy (PDF) reports are supported.
Contributing
Code guidelines
I have tried to adhere to the following guidelines (not always successfully):
Use NumPy-style docstrings. This is also assumed by the API documentation generator.
Properly document at least the functions intended for API.
Functions not intended for API are prefixed with underscore.
Add unit tests for functions, especially API ones.
Avoid writing lots of classes, especially thin ones that don’t provide much functionality. Classes are great, but they also introduce hidden “magic” that can make it difficult for others to reason about the code.
Code formatting
From time to time, all the code has been formatted with black
, using the
-S
option (no string normalization, i.e. both single and double quotes are
preserved and can be used as preferred). New code can be formatted in-place by
running
black -S .
in the root package directory. Various IDEs such as VS Code also support formatting with black.
Version control
The code is stored at a public GitHub repository at
https://github.com/NCH-Motion-Laboratory/gaitutils. In the past, PyPi packages for
gaitutils were actively created for gaitutils, but currently the philosophy is
to install directly from the latest GitHub master branch. Thus, the PyPi
packages are likely to be out of date. pip
can install directly from GitHub
master using a URL specifier such as
https://github.com/NCH-Motion-Laboratory/gaitutils/archive/master.zip.
Miscellaneous technical notes
Exception handling
The package defines one custom exception class: GaitDataError
. It is used to
signify a general problem with the gait data that is usually non-fatal. Several
API functions raise GaitDataError
when there is “something wrong” with the
data (the exact meaning depends on the function).
GUI
The GUI is currently written for PyQt5. With very minor modifications, it should also work with PySide2 and PyQt6.
Threads are used to keep the GUI responsive during long running operations. The
method gaitutils.gui._gaitmenu.Gaitmenu._run_in_thread()
is used to run a
long-running operation in a worker thread. It’s recommended to use this for any
operation that is expected to take longer than a second or two. The point is not
for the user to be able to run several operations in parallel, but just to keep
the GUI (e.g. the progress meter and the cancel button) responsive. In fact, by
default _run_in_thread()
disables the elements of the main UI window, so that
the user cannot start multiple operations at the same time. _run_in_thread()
also handles any exceptions raised during the operation and reports them via a
GUI window, without terminating the program.
Long-running Vicon Nexus operations (typically Nexus pipelines) require special
care. Seemingly, it should be enough to run the operation in a worker thread, as
described above. However, Python has a restriction known as the Global
Interpreter Lock (GIL): only one thread of a process can execute Python bytecode
at a time. It appears that the Nexus API does not release the GIL until the
Nexus operation is finished. Thus, starting a Nexus operation in a thread
freezes all other threads, potentially for a long time. A simple workaround is
to run any Nexus pipeline operations in a separate process instead of a thread
(i.e. a new interpreter is started for the operation). This is accomplished by
gaitutils.nexus._run_pipelines_multiprocessing()
.
For GUI operations that are not started via _run_in_thread()
, you must catch
and handle any exceptions yourself, otherwise they will cause a termination.
Such unhandled exceptions are propagated to a custom exception hook
(my_excepthook()
) that will display a message and terminate the GUI.
The GUI includes a logging window that will display any messages emitted via the
standard Python logging module. This is implemented via a special logging
handler QtHandler()
. The logging level can be set in the configuration.
Wishlist/TODO
Ideas on how to improve the package.
Reading accelerometer data is supported, but there is no specific support for plotting it it (or other non-EMG analog data, such as raw forceplate data). Something similar to EMG handling should be implemented (i.e. list of accelerometer channel names, etc.)
Some of the configuration values are mandatory to be adjust for each particular lab (such as EMG channel names) and others can be left as they are. It would be nice to have a list of “critical” config values and maybe a GUI wizard that would allow the user to set them easily.
The configuration GUI is a bit half-baked. It doesn’t know about the potential types of config items, thus it has to use a “universal” line input widget for most items. This enables the user to input basically any Python type. However, if we know that a certain value is e.g. numerical only (and can’t be None or a string etc.), we could use a spinbox, which would be neater. This would require a system for declaring types for config items. There could be a separate file that would list the allowable types (and possibly e.g. ranges of values) for each config type.
Description of modules and other files
This is a list of modules and other files included in the package. It is not 100% complete yet, but should contain the most important components.
autoprocess.py
Automatically process gait data using Vicon Nexus.
c3d.py
Load data from C3D files. Mostly wrappers around the btk library.
config.py
Read and write package configuration data.
eclipse.py
Read and write Vicon database (Eclipse) files.
emg.py
Handle EMG data.
envutils.py
Functionality related to the operating system and environment.
models.py
Definitions for various gait models, such as Plug-in Gait.
nexus.py
Communicate with Vicon Nexus. Mostly wrappers around the Nexus API.
normaldata.py
Load and save normal (reference) data.
numutils.py
Utilities for numerical computation.
read_data.py
Data reader functions intended for the end user. They delegate to either C3D or Nexus readers as needed.
sessionutils.py
Utilities for handling gait sessions, e.g. for finding trials of interest.
stats.py
Aggregate gait data into NumPy arrays and perform statistics.
timedist.py
Handle gait parameters (time-distance data).
trial.py
Defines the
Trial
class and related functionality.utils.py
Utility functions related to gait data, e.g. for recognizing gait events and extrapolating marker data.
videos.py
Facilities for handling gait videos.
assets/
Miscellaneous data used by the web report.
data/
Package data. Includes some reference data and default configuration etc.
gui/
The PyQt5 GUI and related functionality.
gui/_gaitmenu.py
Main code for the PyQt5 GUI.
gui/gaitmenu.py
Launches the PyQt5 GUI.
gui/gaitmenu.ui
UI file for the GUI, created in Qt Designer.
gui/_tardieu.py
A GUI for Tardieu tests (not actively maintained, may not work).
gui/_windows.py
GUI functionality specific to Microsoft Windows.
gui/qt_dialogs.py
gui/qt_widgets.py
Various custom Qt components.
report/
Web and PDF-based reports.
report/web.py
Web report based on the Dash package.
report/pdf.py
PDF report based on matplotlib.
report/text.py
Text reports.
report/translations.py
Provides simple translations.
thirdparty/
Modules and executables provided by third parties.
thirdparty/ffmpeg2theora.exe
Used to provide conversion from Nexus AVI video files to Theora. The web report needs this in order to show videos.
viz/
Visualization functions.
viz/plot_common.py
Common functions shared by all backends.
viz/plot_matplotlib.py
Plot using the matplotlib library.
viz/plot_misc.py
Utility functions.
viz/plot_plotly.py
Plot using the Plotly library.
viz/plots.py
The API to plotting trial data (e.g. gait curves and EMG).
viz/timedist.py
The API to time-distance plots.
docs/
This (and other) documentation.
tests/
Unit tests.