This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Educational materials for applied signal processing and time-series classification in HCI / ubiquitous computing (a University of Washington course, part of the Makeability Lab "physcomp" curriculum). The artifacts are Jupyter notebooks backed by small supporting Python packages. There is no build; work is done interactively in notebooks. Tests do exist (added after the v2 modernization): pytest unit tests for the helper packages plus nbmake headless notebook smoke tests, run in GitHub Actions CI — see the Testing section below. The tests live entirely outside the .ipynb files (nothing was added inside the notebooks).
Top-level layout:
Tutorials/— standalone teaching notebooks (NumPy, Matplotlib, Python, and signals: sampling/quantization, frequency analysis, comparing signals). Supported by themakelab/package.Projects/GestureRecognizer/— accelerometer gesture classification. The most substantial project; supported by thegesturerec/package.Projects/StepTracker/— step-counting from accelerometer data (a single teaching notebook,StepTracker-Exercises.ipynb, that keeps the exercise prompts and shows worked "Example solution" cells).- Per issue #6, the published notebooks are now complete teaching artifacts: each keeps its exercise prompt / "your turn" framing but adds a clearly-labeled
### Example solution(markdown heading + worked code) immediately below, so the notebook runs top-to-bottom and serves as both lesson and answer key. New graded assignments are authored separately. (Historically there was an*-Exercises.ipynb/*-WithExampleSolution.ipynb/*-Private.ipynbsplit; the StepTracker pair has been consolidated, and*-Private.ipynbfiles remain gitignored.)
Setup (Python 3.12; see README.md): python -m venv .venv → activate → pip install -r requirements.txt. This also installs the repo itself editable (pip install -e .), which is what makes the helper packages importable.
The two helper packages (makelab in Tutorials/makelab/, gesturerec in Projects/GestureRecognizer/gesturerec/) are mapped to top-level import names by the root pyproject.toml, so after the editable install they import from any working directory, no sys.path hacks:
import gesturerec.data as grdata
import makelab.signal
However, data loading is still CWD-relative. Notebooks use paths like ./GestureLogs, ./Logs, and data/audio/..., so a notebook's kernel must run with its own folder as the working directory (JupyterLab/VS Code do this automatically; nbconvert --execute does too). Don't "fix" those into absolute paths — they must stay repo-relative to work in Colab/Binder (textbook contract).
Pinned dependencies live in requirements.txt / environment.yml: NumPy, SciPy, scikit-learn, pandas, Matplotlib, seaborn, librosa, JupyterLab. The .venv/ and a registered signals Jupyter kernel are local-only (gitignored).
Notebook files contain non-ASCII characters (arrows, curly quotes). When parsing a .ipynb with a script, open as UTF-8 and run Python with PYTHONUTF8=1 — the default Windows cp1252 codec will raise UnicodeDecodeError.
Install the test stack with pip install -e ".[test]" (pytest + nbmake + pytest-xdist). Two layers, both outside the notebooks:
- Unit tests (
tests/): pure-function tests for the helper packages —makelab.signal,gesturerec.signalproc/utility/data/experiments. Run withpytest tests/.tests/fixtures/TestGestures/is a tiny synthetic gesture corpus (3 trial CSVs + a*_fulldatastream_*exclusion file) so thedata.pyparser tests don't depend on the large realGestureLogs/. It deliberately includes aMidair Zorro _Z__*.csvfile to exercise the Windows double-underscore filename quirk. - Notebook smoke tests (
nbmake):pytest --nbmake <paths>executes notebooks headless and fails on any uncaught error. Intentional teaching errors are already taggedraises-exception(notebooks 2 and 5) and are honored — no notebook edits needed. nbmake runs each notebook in its own dir, so the CWD-relative data loads work.
[tool.pytest.ini_options].testpaths = ["tests"] keeps a bare pytest fast and notebook-free; the notebook sweeps are invoked explicitly by path. CI (.github/workflows/) runs units + the fast notebooks (Tutorials + StepTracker) on every push/PR; the slow gesture notebooks run only when a change touches them (Projects/GestureRecognizer/** or the deps), plus a monthly drift-canary cron and on-demand (workflow_dispatch). If you add a helper function, add a unit test; if a dependency bump breaks a notebook, the nbmake job is what catches it (this automates the manual Pass 2–4 "Restart & Run All" sweeps).
This package abstracts the data loading and experiment bookkeeping so notebook code can focus on the classification algorithm itself. The data flow is:
Raw logs → data structures → classifier → results.
Data structures (gesturerec/data.py):
SensorData— one sensor's signal as parallel NumPy arrays (time,sensor_time,x,y,z, plus computedmag). Holds*_pplaceholders for processed (filtered) versions produced by notebook preprocessing. Also exposessampling_rateandlength_in_secs.Trial— one recorded gesture (a single CSV). The constructor parses the CSV (np.genfromtxt) and builds theSensorData. Each trial knows its ground-truthgesture_name.GestureSet— all trials for one participant, loaded from a directory of CSVs. Usage is always construct →load()→ preprocess in the notebook. Filenames encodeGestureName_endTimeMs_numRows.csv; trials are ordered by end-timestamp. Note the Windows quirk: Windows replaced'in gesture names with_, producing__double-underscore filenames, which the parser special-cases.- Module-level
get_gesture_set*/get_all_gesture_sets*helpers operate on amapof gesture-set-name →GestureSet(one entry per participant directory underGestureLogs/).
Experiment bookkeeping (gesturerec/experiments.py):
TrialClassificationResult— result of classifying one test trial: holds the n-best list of (template_trial, score) tuples sorted by score, the chosenclosest_trial, andis_correct.ClassificationResults— aggregates results for a whole experiment: accuracy, per-gesture correctness, confusion matrix (via scikit-learn), n-best-list performance curves, timing. This is the object a matching algorithm returns.Experiments— a collection ofClassificationResults, sortable by accuracy / compute time, for comparing approaches.
Two classification paradigms are taught (each in its own notebook), both running over gesturerec data structures:
- Shape-based (
GestureRecognizer-ShapeBased.ipynb) — template/similarity matching between time-series (e.g. Euclidean / DTW-style distance) on pre-segmented trials. - Feature-based (
GestureRecognizer-FeatureBased.ipynb,Feature Selection and Hyperparameter Tuning.ipynb) — extract features (mean, variance, dominant frequency, …) and train a scikit-learn model (SVM in aStandardScaler→classifierPipeline), evaluated withStratifiedKFoldcross-validation.
Other modules:
gesturerec/signalproc.py—compute_fftandget_top_n_frequency_peaks, the shared FFT/feature primitives.gesturerec/gesturestream.py—GestureStream(the full continuousfulldatastream.csvfor offline segmentation work, vs. the pre-segmented per-trial CSVs) andEvent(a segment cut from that stream).GestureStream.load()auto-cleans malformed rows, renaming the original to*_old_<ms>.csvand rewriting a cleaned file.gesturerec/vis.py— plotting helpers (plot_confusion_matrix, signal plots).gesturerec/utility.py— file-handling helpers;find_csv_filenamesdeliberately excludes files containingfulldatastreamso trial loading and stream loading don't collide.
Two data corpora exist: GestureLogs/ (per-participant *Gestures/ dirs, the primary set) and ADXL335GestureLogs/ (an alternate sensor). Notebooks switch between them by changing root_gesture_log_path.
makelab/signal.py and makelab/audio.py provide signal-generation and analysis helpers (sine/cosine generators, FFT, distance metrics, audio via librosa) used by the tutorial notebooks. Same import rule: run notebooks from Tutorials/.
- Code is written to be read by students — verbose docstrings, inline citations to Stack Overflow / library docs, and explanatory comments are intentional. Match that style; don't strip the pedagogy.
- Loaders are tolerant of messy real-world logs (anomalous timestamps, empty columns, OS-specific filename mangling). Preserve those guards when touching parsing code.
int64casts inSensorDataare deliberate (Windowslongis 32-bit) — keep them.