Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
279 commits
Select commit Hold shift + click to select a range
ee3503e
Handle coordinates without steady state frames
cbmorrell Aug 23, 2024
30ef842
Animation documentation
cbmorrell Aug 23, 2024
ac127c3
Add heatmap visualization
cbmorrell Aug 23, 2024
a751cbb
Add string parameters for common metadata operations
cbmorrell Aug 26, 2024
124a1dd
Merge pull request #71 from LibEMG/common-metadata-operations
ECEEvanCampbell Aug 26, 2024
60249b0
ninapro db2 dataglove support added
A-R-Hariri Aug 27, 2024
5d92b81
Merge branch 'feature-extractor-rework' into online-model-feature-queue
cbmorrell Aug 27, 2024
29e677c
Revert "Revert "Revert "Rework FeatureExtractor"""
cbmorrell Aug 27, 2024
8aaf19d
Add online standardization method
cbmorrell Aug 27, 2024
80969d8
Use proper FeatureExtractor interface
cbmorrell Aug 27, 2024
3466dad
Fix heatmap feature extraction
cbmorrell Sep 4, 2024
5f20583
Add clarification to install_standardization docstring
cbmorrell Sep 4, 2024
2c10637
Fix online regressor visualize lag
cbmorrell Sep 4, 2024
7c86fa3
Add docstring to OnlineEMGRegressor.visualize
cbmorrell Sep 4, 2024
5bfe048
Skeleton for Controller class
cbmorrell Sep 4, 2024
6f5e72d
Check for correct MacOS string
cbmorrell Sep 5, 2024
318fc0c
ninapro db2
A-R-Hariri Sep 6, 2024
24c1951
Remove unnecessary pop of oldest window
cbmorrell Sep 6, 2024
9aa3f4e
Create SocketController
cbmorrell Sep 6, 2024
f18ec91
Add comment idea
cbmorrell Sep 6, 2024
48beed0
Add abstract timestamp parsing function
cbmorrell Sep 6, 2024
7ce9bc1
RegressorController
cbmorrell Sep 6, 2024
8254942
Merge pull request #79 from LibEMG/oymotion-streamer-incorrect-os-check
ECEEvanCampbell Sep 9, 2024
95e1111
Merge pull request #73 from LibEMG/online-model-feature-queue
ECEEvanCampbell Sep 9, 2024
25460b3
updates sifi streamer to accept bridge version. forked sifi bridge in…
ECEEvanCampbell Sep 9, 2024
deb3a98
Remove model from RegressorController
cbmorrell Sep 9, 2024
8a20f1b
db2 dataglove support limited to NinaproDB2 class
A-R-Hariri Sep 9, 2024
fb41e9a
ClassifierController implementation
cbmorrell Sep 9, 2024
edb395d
Fix online regressor visualize lag
cbmorrell Sep 4, 2024
19769b5
Add option to parse timestamp
cbmorrell Sep 9, 2024
030d6b2
Remove OnlineEMGRegressor.parse_output and references
cbmorrell Sep 9, 2024
6e436a9
documentation for NinaproDB2
A-R-Hariri Sep 9, 2024
c943fff
Merge pull request #80 from AmirRezaHariri/ninapro_regression
cbmorrell Sep 9, 2024
68942af
Merge pull request #81 from LibEMG/sifi-breakout
cbmorrell Sep 9, 2024
7d0a7d6
Remove duplicate method
cbmorrell Sep 13, 2024
5073d27
Throw error when streaming stops during SGT
cbmorrell Sep 13, 2024
e38e66c
Update .gitignore
cbmorrell Sep 13, 2024
2b49cf7
Convert methods to private
cbmorrell Sep 13, 2024
2142233
Regression Fitts task
cbmorrell Sep 13, 2024
e2357e5
Added sifi-bridge-py to dependencies. Renamed executable to sifibridge
Sep 24, 2024
6965053
cleanup init and sifibridge configurations
Sep 24, 2024
190818c
Change raw writes to sbp methods
Sep 24, 2024
c547681
Updated some redundant parameters and docstrings
Sep 24, 2024
29aafb2
Remove executable from repo
Sep 24, 2024
45c8034
Fixed sifibridge process launch, added sifibridge to gitignore
Sep 24, 2024
a7a839f
Fix gitignore
Sep 24, 2024
ef60d25
Fixed cleanup
Sep 24, 2024
48f907b
make streamer function more in-line with sifi-breakout
Sep 27, 2024
62b0064
docstring, change 'device' to 'name'
Sep 27, 2024
9ae16eb
Remove metric calculation functions
cbmorrell Oct 4, 2024
38031de
Implement classifier controller for visualization
cbmorrell Oct 4, 2024
633f173
Add check for data not being received
cbmorrell Oct 4, 2024
063b965
Add EMG hero legacy code
cbmorrell Oct 4, 2024
e71897c
Replace deque with multiprocessing Queue
cbmorrell Oct 5, 2024
e3b27e5
Fix online ClassifierController parsing
cbmorrell Oct 5, 2024
f846b0b
Fix OnlineEMGClassifier.visualize
cbmorrell Oct 5, 2024
e5e1c26
Automatically start Controller in Environment
cbmorrell Oct 5, 2024
2db5c59
Classifier working in FittsLawTest
cbmorrell Oct 5, 2024
7fb7dd6
Rename FittsLawTest to IsoFitts
cbmorrell Oct 5, 2024
bc16d76
Move common functionality to run method
cbmorrell Oct 5, 2024
f40619c
Update OnlineEMGClassifier visualize docstring
cbmorrell Oct 6, 2024
a697bd1
Hide pygame welcome message
cbmorrell Oct 6, 2024
92756ea
Move functionality to Environment class and Fitts parameters
cbmorrell Oct 6, 2024
5812e7a
Rework prediction map
cbmorrell Oct 6, 2024
b022309
Convert EMGHero to Environment
cbmorrell Oct 6, 2024
f7a126f
Rework _get_action for queue
cbmorrell Oct 6, 2024
d687ec9
Remove _parse_timestamp from Controller class
cbmorrell Oct 6, 2024
a6435c8
Fix prediction map for IsoFitts
cbmorrell Oct 6, 2024
a729a9b
Implemented KeyboardController
cbmorrell Oct 6, 2024
5a7721c
Properly receive None in EMGHero
cbmorrell Oct 6, 2024
4e1fb29
Split environments.py into multiple files
cbmorrell Oct 7, 2024
459c1a2
Add environments to __init__
cbmorrell Oct 7, 2024
c2bf8f5
Fix environment imports
cbmorrell Oct 7, 2024
f8ae722
Hide pygame welcome message
cbmorrell Oct 7, 2024
31d5a46
IsoFitts direction fix
cbmorrell Oct 7, 2024
87ff27e
Convert IsoFitts methods to hidden methods
cbmorrell Oct 7, 2024
277cb14
Environments documentation
cbmorrell Oct 7, 2024
d056439
Rework SocketController
cbmorrell Oct 7, 2024
fad6227
Add wait in OnlineEMGRegressor.visualize
cbmorrell Oct 7, 2024
9822c14
Merge pull request #83 from LibEMG/duplicate-method-in-ninaprodb2
ECEEvanCampbell Oct 8, 2024
d28d0f6
Merge pull request #84 from LibEMG/handle-sgt-streaming-interruption
ECEEvanCampbell Oct 8, 2024
391ab53
Merge pull request #74 from LibEMG/online-regressor-visualization-lag
ECEEvanCampbell Oct 8, 2024
b6121a9
Merge branch 'latest' into game-environments
ECEEvanCampbell Oct 8, 2024
a8367ee
Merge pull request #89 from LibEMG/game-environments
ECEEvanCampbell Oct 8, 2024
bf252a2
fixed windows compilation target
Oct 8, 2024
b483dcf
Override terminate method
cbmorrell Oct 8, 2024
157dc45
Allow non-callable dtypes
cbmorrell Oct 8, 2024
ff61e87
Convert Manager to SharedMemory
cbmorrell Oct 8, 2024
265e482
Add proportional control flag to IsoFitts
cbmorrell Oct 8, 2024
75a71ea
Automatically create parent directories when saving coordinates
cbmorrell Oct 9, 2024
278c202
Update Fitts example with regression
cbmorrell Oct 9, 2024
2ffb798
added sifi bridge to setup
ECEEvanCampbell Oct 10, 2024
938d797
Launch the subprocess in run method for pickling issues
gabrielpgagne Oct 10, 2024
f2c0d5a
Merge branch 'latest' into feature/sifibridge-1.1
ECEEvanCampbell Oct 10, 2024
8dce28b
Merge pull request #93 from gabrielpgagne/feature/sifibridge-1.1
ECEEvanCampbell Oct 10, 2024
d51fe42
Merge pull request #91 from LibEMG/socket-controller-shared-memory
ECEEvanCampbell Oct 10, 2024
ac9ce2f
Update streamers.py
ECEEvanCampbell Oct 10, 2024
4a73b76
removed version from sifi bridge import
ECEEvanCampbell Oct 16, 2024
2fb22a1
added more checks for creating variables
ECEEvanCampbell Oct 16, 2024
b4c42e6
SocketController Lock Fix
cbmorrell Oct 16, 2024
9410fff
Add pygame to setup.py
cbmorrell Oct 16, 2024
22cdcf1
Merge pull request #94 from LibEMG/socket-controller-shared-memory-lo…
ECEEvanCampbell Oct 17, 2024
d1ff3ee
Merge pull request #92 from LibEMG/regression-documentation
ECEEvanCampbell Oct 17, 2024
e58b1cd
Simplify SocketController
cbmorrell Oct 17, 2024
0d95a50
Make socket non-blocking
cbmorrell Oct 17, 2024
f9a8644
Replace classifier_smm_writes with model_smm_writes
cbmorrell Oct 17, 2024
67dc160
Pass model timestamp instead of current time
cbmorrell Oct 17, 2024
d6b0486
Fixed saving of IMU data
ulysseTM Oct 18, 2024
c48b3f8
Remove Delsys pasted docstring
cbmorrell Oct 18, 2024
81727d0
Merge pull request #97 from ulysseTM/patch-1
ECEEvanCampbell Oct 20, 2024
4b04b99
Add numpy import to Delsys API streamer
cbmorrell Oct 23, 2024
8e6ad11
Add check for num_channels in delsys_api_streamer
cbmorrell Oct 23, 2024
1e02c49
Add support for .json when logging environment
cbmorrell Oct 24, 2024
25492e4
Add target and distance radii parameters
cbmorrell Oct 24, 2024
96f7e07
Add option to redo final rep
cbmorrell Oct 29, 2024
960d6f9
Show "Rep X of Y" during data collection
cbmorrell Oct 29, 2024
6e32c11
Automatically calculate rep time for videos
cbmorrell Oct 30, 2024
cf1e181
Find labels files and move to corresponding data directory
cbmorrell Oct 30, 2024
1bbff78
Throw ConnectionError during start callback
cbmorrell Oct 30, 2024
08b0428
GUI docstring update
cbmorrell Oct 30, 2024
a27ca1f
Merge pull request #98 from LibEMG/sgt-gui-improvements
eeddy Oct 31, 2024
f5bf631
Merge pull request #96 from LibEMG/simplify-controllers
eeddy Oct 31, 2024
dfb2c69
Add game time parameter
cbmorrell Nov 12, 2024
3113b50
Removed unused dependencies in sifibridge streamer. Set EDA to 0Hz by…
Nov 12, 2024
97be849
Merge pull request #100 from gabrielpgagne/bugfix/sifi-bridge-py-1.2
ECEEvanCampbell Nov 13, 2024
5a89b2d
Add regression info to docstring
cbmorrell Nov 13, 2024
dc296cf
Add timestamps to KeyboardController
cbmorrell Nov 13, 2024
510fd1f
Draw cursor for RotationalIsoFitts
cbmorrell Nov 13, 2024
fb3f835
WIP: converting to using polygons
cbmorrell Nov 20, 2024
56dfe95
Rename isofitts module to fitts
cbmorrell Nov 20, 2024
5617bfd
Rename IsoFitts class to ISOFitts
cbmorrell Nov 20, 2024
a245816
Create PolarFitts
cbmorrell Nov 20, 2024
959caf7
Fitts base class
cbmorrell Nov 20, 2024
d89af62
Remove duplicated code from ISOFitts
cbmorrell Nov 20, 2024
a087460
Inherit from Fitts instead of ISOFitts for PolarFitts
cbmorrell Nov 20, 2024
adb2695
Remove dead code
cbmorrell Nov 20, 2024
e81d6e6
Create Fitts targets in uniform circle
cbmorrell Nov 21, 2024
39dc79a
Handle different target sizes
cbmorrell Nov 21, 2024
fe9ae77
Custom directions for PolarFitts prediction_map
cbmorrell Nov 21, 2024
5bf193e
Set minimum target size based on cursor
cbmorrell Nov 21, 2024
c028774
Reimplement feature parameters online
cbmorrell Nov 21, 2024
1ca93c4
Only add to smm_items list if tag has not already been provided
cbmorrell Nov 21, 2024
bfeacaf
Fix check for cursor being on new goal target
cbmorrell Nov 21, 2024
1a2f147
Keep radius on screen
cbmorrell Nov 21, 2024
b168f98
Fix incorrect polar to cartesian action map
cbmorrell Nov 21, 2024
ad08df5
Restrict PolarFitts to a single side
cbmorrell Nov 21, 2024
1799048
Removed unused dependencies in sifibridge streamer. Set EDA to 0Hz by…
Nov 12, 2024
125ed3a
Stop targets from generating in the center of the screen
cbmorrell Nov 24, 2024
1110a3f
Set theta pointed at bottom of screen instead of right
cbmorrell Nov 24, 2024
02cb852
Docstring for PolarFitts
cbmorrell Nov 24, 2024
2cd4b73
Initialize theta to pi/2
cbmorrell Nov 25, 2024
9a8fc8f
Remap theta direction to correspond to prediction direction
cbmorrell Nov 25, 2024
a7bfbc4
Fix visualize_heatmap throwing error for feature lists of length 1
cbmorrell Nov 25, 2024
143f0a5
WIP: Functional conversion from Cartesian to Polar space
cbmorrell Nov 26, 2024
2b16984
Remove dead code
cbmorrell Nov 26, 2024
ea1a5dd
Pass in rect when drawing to polar space
cbmorrell Nov 26, 2024
84694d5
Add fill parameter for drawing polar circle
cbmorrell Nov 26, 2024
b387b2b
Fix logical error when checking if new target is on cursor
cbmorrell Nov 26, 2024
9557de0
Combine PolarFitts into parent Fitts class
cbmorrell Nov 26, 2024
65e7699
Fix check for cursor remaining on screen
cbmorrell Nov 26, 2024
9ce1a0e
Update Fitts and ISOFitts docstrings
cbmorrell Nov 26, 2024
0a73901
Add option to draw radius in polar Fitts
cbmorrell Nov 26, 2024
1c5f514
Extract origin to variable
cbmorrell Nov 26, 2024
154496c
Improve check for cursor staying in bounds
cbmorrell Nov 27, 2024
e4ed8b3
Add check in ISOFitts for target distance radius and screen size
cbmorrell Nov 27, 2024
ee00681
Improve error handling for small screen size error
cbmorrell Nov 27, 2024
5fe02b8
Fix fstring formatting in error message
cbmorrell Nov 27, 2024
47f1837
Use full screen size when possible
cbmorrell Nov 27, 2024
3275158
Switch polar Fitts to vertical layout instead of horizontal
cbmorrell Nov 27, 2024
569ec35
Change default num_targets in ISOFitts
cbmorrell Nov 27, 2024
b617a4a
Add parameters for target and cursor color
cbmorrell Nov 27, 2024
f3d9e46
Extract Fitts parameters to config dataclass
cbmorrell Nov 27, 2024
d25b360
Merge pull request #103 from LibEMG/visualize-heatmap-fix
eeddy Nov 28, 2024
9356b44
Update colors
cbmorrell Nov 28, 2024
8da18f8
Make OnlineDataHandler processes daemons
cbmorrell Nov 28, 2024
eb9187e
Dataset Updates (#85)
eeddy Nov 29, 2024
defc993
Fixed setup issue
eeddy Nov 29, 2024
bf52580
Remove incorrect docstring
cbmorrell Dec 3, 2024
d672145
Reset dwell timer in _get_new_goal_target
cbmorrell Dec 3, 2024
57cdf2f
Add HyserMVC Dataset (#105)
cbmorrell Dec 5, 2024
e1e5d1e
Wait to start timers until data have been received
cbmorrell Dec 5, 2024
ea17e42
Add cursor_radius parameter to FittsConfig
cbmorrell Dec 5, 2024
46d7d86
Updated the normalization in the feature extractor
eeddy Dec 7, 2024
7b4a727
Modify FittsConfig default parameters
cbmorrell Dec 13, 2024
71845f9
Update _delsys_streamer.py
ECEEvanCampbell Jan 15, 2025
afe7b5e
Added option for dictionary list
eeddy Jan 25, 2025
f40e3ef
Added option for dictionary list
eeddy Jan 25, 2025
2f161f0
Added a fix feature errors to feature extractor
eeddy Jan 28, 2025
7e7808a
Changed feature_extractor to default to fix errors is false
eeddy Jan 28, 2025
a45013d
Changed dataset names
eeddy Jan 29, 2025
aeea667
Updated feature dictionary
eeddy Jan 30, 2025
33a5177
Updates
eeddy Jan 30, 2025
265d0ae
Merge pull request #104 from LibEMG/isofitts-improvements
ECEEvanCampbell Feb 11, 2025
f64e1ca
intermediate step of subbing in composing functions
ECEEvanCampbell Feb 17, 2025
49902f1
added output_writer
ECEEvanCampbell Feb 17, 2025
0a93000
Modified onlinestreamers to composed run() method. Added output_writers
ECEEvanCampbell Feb 17, 2025
e3f7705
added output_writer (to be used with onlinestreamers)
ECEEvanCampbell Feb 17, 2025
379db4e
made dictionary more succinct
ECEEvanCampbell Feb 17, 2025
bdeb058
fixed internal import
ECEEvanCampbell Feb 18, 2025
a1a6efb
Remove gui parameter from DataCollectionPanel
cbmorrell Mar 27, 2025
7323834
Remove final reference to GUI in DataCollectionPanel
cbmorrell Mar 27, 2025
f811b4e
Remove old call to controller.start
cbmorrell Mar 27, 2025
562ccc0
Remove call to controller.start
cbmorrell Mar 27, 2025
9beb15f
Centralize prediction flow
cbmorrell Mar 27, 2025
8c864e6
Add rejection back in
cbmorrell Mar 27, 2025
ceef479
Add comments
cbmorrell Mar 27, 2025
201d7bd
Centralize EMGClassifier predictions and postprocessing
cbmorrell Mar 27, 2025
a58a718
Remove unused window parameter from default_on_prediction method
cbmorrell Mar 27, 2025
31d608b
Move private method with others
cbmorrell Mar 27, 2025
01259b8
Remove unused window parameter from default_on_prediction method
cbmorrell Mar 28, 2025
3012617
Revert "Remove unused window parameter from default_on_prediction met…
cbmorrell Mar 28, 2025
4a588ef
Move private method with others
cbmorrell Mar 28, 2025
9ab88b3
Revert "Move private method with others"
cbmorrell Mar 28, 2025
e71b2e5
Pass probabilities through
cbmorrell Mar 28, 2025
0c85139
removed different os oymotion streamer
ECEEvanCampbell Mar 30, 2025
caef253
Rename SMM variables
cbmorrell Mar 31, 2025
4dcc458
fixed oymotion memory leak on p.kill(), use p.signal.set()
ECEEvanCampbell Apr 1, 2025
3ef3ae4
_match_nus_uuid doesnt work for repeated connects/disconnects
ECEEvanCampbell Apr 7, 2025
f432a01
added tags to outputwriters
ECEEvanCampbell Apr 7, 2025
2582cab
moved default compose functions from online streamer to the child str…
ECEEvanCampbell Apr 7, 2025
ea34e53
changed encoder
ECEEvanCampbell Apr 29, 2025
2661ea7
no longer need windows speific streamer, make general
ECEEvanCampbell May 1, 2025
1104fdf
old windows streamer now works for all OS's
ECEEvanCampbell May 1, 2025
99932f3
add adaptation module
ECEEvanCampbell May 1, 2025
1d18e66
added Event to OnlineStreamers
ECEEvanCampbell May 1, 2025
29b9598
added MethoType to mod_fn
ECEEvanCampbell May 1, 2025
00f5df0
updates oymotion streamer to general streamer
ECEEvanCampbell May 1, 2025
c9b8ab1
removed windows specific oymotion streamer
ECEEvanCampbell May 1, 2025
e6c70d0
added module
ECEEvanCampbell May 1, 2025
41e8ef2
added edil adaptation objects function & placeholders
ECEEvanCampbell May 1, 2025
5b35ed4
added base memory class
ECEEvanCampbell May 1, 2025
31c0e38
initial commit -- not working yet
ECEEvanCampbell May 1, 2025
92db12f
added AdaptationManager comments & smm creation in both AM and MM
ECEEvanCampbell May 1, 2025
77812aa
accomodated non-pickleable types for adaptation mode load
ECEEvanCampbell May 23, 2025
37dc7ce
fixed flag and flag_counter function handles
ECEEvanCampbell May 23, 2025
cd3faab
fixed smow for memory and adaptation manager (no longer dictionary)
ECEEvanCampbell May 23, 2025
09cc0ad
moved init so it can be run in process
ECEEvanCampbell May 23, 2025
edf8388
added a run_loop function for single frame pass
ECEEvanCampbell May 23, 2025
bcbc843
moved initialization so its possible to run in background
ECEEvanCampbell May 23, 2025
6792baa
added curricular fitts
ECEEvanCampbell May 23, 2025
ffede95
added WiP curricular fitts
ECEEvanCampbell May 23, 2025
6c966af
removed environment folder
ECEEvanCampbell May 23, 2025
4cea550
incremental learning update
ECEEvanCampbell Jun 19, 2025
48a6427
Jul24 IL fix
ECEEvanCampbell Jul 24, 2025
613bae1
updated sifi streamer
ECEEvanCampbell Jul 2, 2026
66e338a
upgraded numpy > 2.0
ECEEvanCampbell Jul 2, 2026
05ee2ec
reversed chunks to be written (required)
ECEEvanCampbell Jul 2, 2026
1f01dc7
added adaptation explanation to docs
ECEEvanCampbell Jul 5, 2026
9bcd569
shared memory items for <count> now shares the same lock as its data.
ECEEvanCampbell Jul 5, 2026
2688ce4
Bump doc toolchain for Python 3.13 so docs build
ECEEvanCampbell Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,27 @@ test_*.py
*.csv
.vscode/*
test_delsys_api.py
resources/
resources/
*.csv
*.txt
ContinuousTransitions/*
FORS-EMG/*
MyoDisCo/*
NinaProDB1/*
*.zip
libemg/_datasets/__pycache__/*
CIILData/*
EMGEPN612.pkl
OneSubjectMyoDataset/
_3DCDataset/
ContractionIntensity/
CIILData/
*.pkl
LimbPosition/
CNN.py
MLP.py
__pycache__/
MLPR.py
docs/Makefile
sifibridge-*
*.pyc
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Ethan Eddy, Evan Campbell, Angkoon Phinyomark, Scott Bateman, and Erik Scheme'

# The full version, including alpha/beta/rc tags
release = '1.0.0'
release = '2.0.0'


# -- General configuration ---------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions docs/source/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ The goal of this library is to provide an easy to use and feature-rich API for d
$ pip install libemg
```

# Interactive API Walkthrough

Interactive offline and online walkthroughs were created as part of a `LibEMG` workshop we presented at [MEC24](https://www.unb.ca/ibme/mec/index.html), hosted by the Institute of Biomedical Engineering. These walkthroughs step through much of the core functionality of `LibEMG`, and offer visuals to explain pieces of the API. The offline example is formatted as a jupyter notebook, allowing you to see visuals for each code snippet without running the code yourself. Please check out out the workshop GitHub repository to see these interactive API walkthroughs: <https://github.com/LibEMG/LibEMG_MEC24_Workshop>.

# Questions
Check out our discord server if you have questions, comments, or feature requests: https://discord.gg/NeqTTXmM4F

Expand Down
4 changes: 4 additions & 0 deletions docs/source/documentation/adaptation/adaptation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Online Adaptation
------------------------------
.. include:: adaptation_doc.md
:parser: myst_parser.sphinx_
160 changes: 160 additions & 0 deletions docs/source/documentation/adaptation/adaptation_doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
[View Source Code](https://github.com/ECEEvanCampbell/CoAdaptUnderCurriculum)

The offline performance of a myoelectric model (e.g., $R^2$, mean absolute error on screen-guided training data) does not necessarily reflect how usable it is once a person is actually in the loop. A model that looks excellent on a calibration set can still drift or feel unresponsive online. `LibEMG` provides an **adaptation suite** that keeps improving a model *while the user operates it*, using the ongoing interaction as a source of labels. This is the mechanism behind context-informed incremental learning (see [Context-Informed Incremental Learning Improves Throughput and Reduces Drift in Regression-Based Myoelectric Control, Morrell et al. 2025](https://github.com/ECEEvanCampbell/CoAdaptUnderCurriculum)).

This tutorial builds the minimal pipeline: a **within-subject initialization** from screen-guided training (SGT) data, followed by an **online environment that adapts the model** as the user plays through it.

# Architecture
Adaptation runs as **four cooperating processes** that communicate over shared memory. The `libemg.adaptation._base.get_edil_adaptation_objects` helper pre-builds every shared-memory item and `OutputWriter` needed to wire them together (each modality buffer and its sample counter are paired on a shared lock so readers always see a consistent snapshot), so you never allocate them by hand.

1. **`OnlineEMGRegressor`** — the live model. It windows the incoming EMG, extracts features, predicts, and publishes each feature vector (`model_input`) with a timestamp to shared memory. It also watches an `adapt_flag`; when the adaptation manager raises it, the regressor hot-swaps in the newly adapted model.
2. **Environment** (`CurricularFitts`) — the task the user performs. Every frame it turns the current cursor/target geometry into a *pseudo-label* through a `feedback_handle`, and writes that label (`environment_feedback`) with the same timestamp and trial number.
3. **`MemoryManager`** — joins each `environment_feedback` row to the `model_input` row with the matching timestamp, appends the pair to a `Memory`, and saves one memory slice (`memory_<trial>.pkl`) at the end of every trial.
4. **`AdaptationManager`** — loads memory slices (seeded by the SGT data for stability), calls `model.adapt(memory)`, saves the updated model (`mdl<N>.pkl`), and — if `notify=True` — sets the `adapt_flag` so the live model reloads it.

The loop is therefore: **predict → act → pseudo-label → remember → adapt → reload → predict …**, all without pausing the user.

**You supply three things.** The suite is model-agnostic; it only assumes:
- a **model** with `predict(features)`, `adapt(memory)`, `save(path)`, and `load(path)` (the reference repository uses a small MLP / Transformer),
- a **memory** subclassing `libemg.adaptation.memory.Memory` (implementing `append`, `reset`, `save`, `load`, and `__add__`),
- a **feedback function** mapping game state to a pseudo-label — `libemg.adaptation._base.produce_tciil_feedback` is a ready-made regression example.

**Note:** The snippets below use the `Myo Armband` and a 2-DOF wrist regressor (flexion/extension and radial/ulnar deviation). Any hardware works by switching the `streamer`, `window_size`, and `window_increment`.

# Step 1 — Within-Subject Initialization
First, record a short screen-guided training session and fit an initial model to it. This is the same collection flow used elsewhere in `LibEMG`; for regression we prompt the four wrist directions and record continuous labels.

```Python
import libemg
from libemg.streamers import myo_streamer

# Stream from the device and collect calibration (SGT) data.
streamer, sm_items = myo_streamer()
odh = libemg.data_handler.OnlineDataHandler(shared_memory_items=sm_items)

gui = libemg.gui.GUI(odh, args={
'media_folder': 'media/', # regression prompts (wrist flexion/extension, radial/ulnar)
'data_folder': 'data/sgt/',
'num_reps': 5,
'rep_time': 5,
'auto_advance': True,
})
gui.start_gui()
streamer.stop()
```

Next, parse those recordings with an `OfflineDataHandler`, fit your model, and — crucially — save the SGT data **as a memory slice**. That slice seeds the adaptation manager so early online updates don't wander away from a known-good starting point.

```Python
# Parse the SGT recordings.
offdh = libemg.data_handler.OfflineDataHandler()
offdh.get_data('data/sgt/', regex_filters, metadata_fetchers, delimiter=',')

# Fit the initial within-subject model (implements predict/adapt/save/load).
model = MyModel(...)
model.calibrate(offdh)

# Seed the adaptation with the SGT data as the first memory slice.
initial_memory = MyMemory(...) # subclass of libemg.adaptation.memory.Memory
initial_memory.load_from_odh(offdh)
initial_memory.save('data/adapt/sgt_memory.pkl')
```

The same `model` object is handed to both the live regressor and the adaptation manager below; because they run in separate processes each gets its own copy.

# Step 2 — Launch the Online Adaptive Environment
Now assemble the four processes. Start by requesting the pre-wired shared-memory items and output writers.

```Python
from libemg.adaptation._base import get_edil_adaptation_objects, produce_tciil_feedback
from libemg.adaptation.managers import MemoryManager, AdaptationManager
from libemg.environments.controllers import RegressorController
from libemg.environments.curricular_fitts import (
CurricularFitts, CurricularFittsConfig, RadiusTargetGenerator,
)

NUM_FEATURES, NUM_DOFS, NUM_TRIALS = 64, 2, 80
ADAPT_DIR = 'data/adapt/' # adapted models (mdl<N>.pkl) AND memory slices (memory_<trial>.pkl)

(model_smi, model_ow,
environment_smi, environment_ow,
adaptation_manager_smi, adaptation_manager_ow,
memory_manager_smi, memory_manager_ow) = get_edil_adaptation_objects(
num_features=NUM_FEATURES, num_outputs=NUM_DOFS)
```

**The live model.** Pass `model_ow`/`model_smi` so it publishes `model_input` and exposes the `active_flag`/`adapt_flag`. Its `file_path` is where it reloads adapted models from.

```Python
streamer, sm_items = myo_streamer()
odh = libemg.data_handler.OnlineDataHandler(shared_memory_items=sm_items)

emg_regressor = libemg.emg_predictor.EMGRegressor(model) # the SGT-trained model
online_regressor = libemg.emg_predictor.OnlineEMGRegressor(
offline_regressor = emg_regressor,
online_data_handler = odh,
window_size = 100, window_increment = 40,
features = ['WENG'],
output_writers = model_ow, # publishes model_input (features + timestamp)
smm = True, smm_items = model_smi, # exposes active_flag / adapt_flag / model_input
file_path = ADAPT_DIR, # reloads mdl<N>.pkl from here when adapt_flag is set
)
online_regressor.run(block=False)
```

**The environment.** The `feedback_handle` converts cursor/target geometry into a pseudo-label; `environment_ow` writes it out for the memory manager.

```Python
controller = RegressorController()
config = CurricularFittsConfig(
feedback_handle = produce_tciil_feedback,
num_trials = NUM_TRIALS,
controller_map = [1, -1],
)
env = CurricularFitts(
controller, config,
target_generator = RadiusTargetGenerator(config, F=0, P=0),
environment_ow = environment_ow, # writes environment_feedback (label + timestamp + trial)
save_file = ADAPT_DIR,
)
```

**The memory and adaptation managers.** Two path linkages must line up: the adaptation manager's `load_dir` equals the memory manager's `save_dir` (where slices are written), and its `save_dir` equals the online regressor's `file_path` (where adapted models are read back). Setting `notify=True` is what closes the loop by raising the `adapt_flag`.

```Python
memory_manager = MemoryManager(
memory = MyMemory(...), # empty memory of the same type as the SGT seed
smi = memory_manager_smi,
ow = memory_manager_ow,
save_dir = ADAPT_DIR, # writes memory_<trial>.pkl
)

adaptation_manager = AdaptationManager(
model = model, # adapts its own copy of the SGT model
smi = adaptation_manager_smi,
ow = adaptation_manager_ow,
initial_memory_loc = ADAPT_DIR + 'sgt_memory.pkl', # SGT seed for stability
load_dir = ADAPT_DIR, # reads memory_<trial>.pkl (== MemoryManager.save_dir)
save_dir = ADAPT_DIR, # writes mdl<N>.pkl (== OnlineEMGRegressor.file_path)
stop_condition = lambda n: n >= NUM_TRIALS - 1,
notify = True, # raise adapt_flag so the live model hot-swaps
)
```

Finally, launch them. Run the environment and memory manager in the background and block on the adaptation manager; it returns once `stop_condition` is met. Then tear everything down.

```Python
env.run_helper(block=False) # spawn the game
memory_manager.run_helper(block=False) # spawn the memory assembler
adaptation_manager.run_helper(block=True) # adapt until stop_condition; blocks here

# Cleanup once adaptation ends.
env.process.join()
memory_manager.signal.set(); memory_manager.join()
online_regressor.odh.stop_all()
online_regressor.stop_running()
streamer.stop()
```

# Result
As the user completes trials, memory slices accumulate, the adaptation manager retrains on them (seeded by the SGT slice), and the live model is swapped out mid-session — so control quality improves *during* use rather than only between sessions. To make the model non-adaptive for a baseline comparison, build everything identically but pass `notify=False` to the `AdaptationManager`: memories are still collected, but the `adapt_flag` is never raised and the live model stays fixed.
4 changes: 4 additions & 0 deletions docs/source/documentation/animation/animation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Animation
------------------------------
.. include:: animation_doc.md
:parser: myst_parser.sphinx_
75 changes: 75 additions & 0 deletions docs/source/documentation/animation/animation_doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## Use Case

The `Animator` class offers some simple functionality to create animations in multiple video formats (e.g., .gif and .mp4). Specific `Animator` classes have been created to help create specific visual prompts, such as bar plots and cartesian plots. These prompts are primarily used for training of regression-based myoelectric control systems, but the `Animator` class can also be used to generate these prompts for any purpose. For fully custom animations, you can inherit from the `Animator` class and pass a set of frames to `save_video()` (see other `Animators` for examples). If you'd rather implement a custom plotting animation that isn't supported, you can inherit from `PlotAnimator` instead.

For all further examples, we will use a generated set of coordinates to illustrate the difference between plots.

```Python
import numpy as np

fps = 24
coordinates = np.concatenate((
np.linspace(0, 1, num=fps), # each movement is 24 frames -> 1 second
np.ones(2 * fps), # steady state
np.linspace(1, -1, num=2 * fps),
np.ones(2 * fps) * -1,
np.linspace(-1, 0, num=fps)
))
coordinates = np.hstack((
np.expand_dims(coordinates, 1),
np.zeros((coordinates.shape[0], 1))
))
```

## Bar Plots

One type of `Animator` built into `LibEMG` creates bar plot animations. Pass in an array of coordinates to the `plot_icon` method to create a bar plot visualization (see Figure 1).

```Python
from libemg.animator import BarPlotAnimator

animator = BarPlotAnimator(['Open', 'Close'], fps=fps)
animator.save_plot_video(coordinates)
```

![alt text](bar.gif)
<p align="center">Figure 1: Simple bar plot animation.</p>

Additional information can also be shown during these animations, such as the next destination and a countdown for steady states (see Figure 2).

```Python
animator = BarPlotAnimator(['Open', 'Close'], fps=fps, show_countdown=True, show_direction=True)
animator.save_plot_video(coordinates)
```

![alt text](bar-info.gif)
<p align="center">Figure 2: Bar plot animation with added information.</p>

Parameters such as the time per unit distance, figure size, and more can also be modified. See the `BarPlotAnimator` API for more details.

## Scatter Plots

`LibEMG` also provides a helper class to animate scatter plots (see Figure 3).

```Python
from libemg.animator import ScatterPlotAnimator

animator = ScatterPlotAnimator(['Open', 'Close'], fps=fps)
animator.save_plot_video(coordinates)
```

![alt text](scatter.gif)
<p align="center">Figure 3: Simple scatter plot animation.</p>

Similar to the bar plot animation, extra information can be added such as next destination, a countdown, and a unit circle boundary (see Figure 4).

```Python

animator = ScatterPlotAnimator(['Open', 'Close'], fps=fps, show_countdown=True, show_direction=True, show_boundary=True)
animator.save_plot_video(coordinates)
```

![alt text](scatter-info.gif)
<p align="center">Figure 4: Scatter plot animation with added information.</p>

Parameters such as the time per unit distance, figure size, and more can also be modified. See the `ScatterPlotAnimator` API for more details.
Binary file added docs/source/documentation/animation/bar-info.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/documentation/animation/bar.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/documentation/animation/scatter.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading