|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with |
| 4 | +code in this repository. |
| 5 | + |
| 6 | +## Project Overview |
| 7 | + |
| 8 | +`openlifu` is the core Python library for Openwater's Low Intensity Focused |
| 9 | +Ultrasound (LIFU) platform. It provides beamforming, k-Wave acoustic simulation, |
| 10 | +treatment planning, a file-based database, and hardware I/O. Licensed under MIT. |
| 11 | + |
| 12 | +The primary downstream consumer is **SlicerOpenLIFU** (`../SlicerOpenLIFU`), a |
| 13 | +3D Slicer extension that wraps `openlifu` objects with a GUI, VTK visualization, |
| 14 | +and MRML persistence. |
| 15 | + |
| 16 | +Python 3.10-3.12 on Windows or Linux. |
| 17 | + |
| 18 | +## Build and Development Commands |
| 19 | + |
| 20 | +```bash |
| 21 | +# Install (editable, with test deps) |
| 22 | +pip install -e '.[test]' |
| 23 | + |
| 24 | +# Install (editable, with dev deps) |
| 25 | +pip install -e '.[dev]' |
| 26 | + |
| 27 | +# Run all tests |
| 28 | +pytest |
| 29 | + |
| 30 | +# Run a single test file |
| 31 | +pytest tests/test_database.py |
| 32 | + |
| 33 | +# Run a single test function |
| 34 | +pytest tests/test_database.py::test_function_name -v |
| 35 | + |
| 36 | +# Run tests with coverage |
| 37 | +pytest -ra --cov --cov-report=term --durations=20 |
| 38 | + |
| 39 | +# Lint (runs pre-commit hooks) |
| 40 | +pipx run nox -s lint |
| 41 | +# or if nox is installed: |
| 42 | +nox -s lint |
| 43 | + |
| 44 | +# Run ruff directly |
| 45 | +ruff check src/ |
| 46 | + |
| 47 | +# Run pylint |
| 48 | +nox -s pylint |
| 49 | + |
| 50 | +# Build docs |
| 51 | +nox -s docs |
| 52 | +``` |
| 53 | + |
| 54 | +## Linting and Code Style |
| 55 | + |
| 56 | +- **Ruff** is the primary linter, configured in `pyproject.toml` with many rule |
| 57 | + sets enabled (bugbear, isort, pylint, etc.). |
| 58 | +- **Every file must start with `from __future__ import annotations`** — enforced |
| 59 | + by isort via `isort.required-imports` in `pyproject.toml`. |
| 60 | +- **No auto-formatter** is currently enabled (ruff-format is commented out in |
| 61 | + `.pre-commit-config.yaml`). |
| 62 | +- **Pre-commit hooks** include: ruff (with `--fix`), codespell, shellcheck, |
| 63 | + prettier (YAML/MD/JSON), blacken-docs, and a custom check disallowing |
| 64 | + `PyBind|Numpy|Cmake|CCache|Github|PyTest` capitalization. |
| 65 | +- **PyLint** runs in CI via nox. MyPy is disabled. |
| 66 | +- Warnings are treated as errors in pytest, with specific exceptions listed in |
| 67 | + `pyproject.toml`. |
| 68 | + |
| 69 | +## CI |
| 70 | + |
| 71 | +CI runs on push to main and on PRs (`.github/workflows/ci.yml`): |
| 72 | + |
| 73 | +1. **Commit message check** (PRs only): every commit must reference a GitHub |
| 74 | + issue (`#123` or full URL). Exceptions: Bump, Merge, Revert, fixup!, squash!, |
| 75 | + amend! commits. |
| 76 | +2. **Pre-commit + PyLint**: linting on ubuntu. |
| 77 | +3. **Tests**: matrix of Python {3.10, 3.12} x {ubuntu, windows, macos} with |
| 78 | + coverage uploaded to Codecov. |
| 79 | + |
| 80 | +## Commit Guidelines |
| 81 | + |
| 82 | +Every commit must reference a relevant GitHub issue number in the title or body |
| 83 | +(e.g. `Fix target placement crash (#42)` or `Fixes #42` in the body). CI |
| 84 | +enforces this on PRs. |
| 85 | + |
| 86 | +## Architecture |
| 87 | + |
| 88 | +### Subpackages |
| 89 | + |
| 90 | +| Package | Purpose | |
| 91 | +| -------- | ----------------------------------------------------------------------------------------------- | |
| 92 | +| `plan/` | `Protocol` and `Solution` — treatment planning, beamforming orchestration, solution analysis | |
| 93 | +| `bf/` | Beamforming primitives: `Pulse`, `Sequence`, `FocalPattern`, `DelayMethod`, `ApodizationMethod` | |
| 94 | +| `sim/` | k-Wave acoustic simulation integration (`SimSetup`, `run_simulation`) | |
| 95 | +| `db/` | File-based JSON database (`Database`, `User`, `Subject`, `Session`, `Run`) | |
| 96 | +| `xdc/` | Transducer hardware definitions (`Transducer`, `Element`, `TransducerArray`) | |
| 97 | +| `seg/` | Tissue segmentation and material properties (`Material`, `SegmentationMethod`) | |
| 98 | +| `geo.py` | `Point` class — 3D coordinates with metadata and VTK visualization | |
| 99 | +| `io/` | Hardware communication (`LIFUInterface`) — USB, serial, voltage safety | |
| 100 | +| `nav/` | Photogrammetry (`Photoscan`), Meshroom pipeline integration, MODNet portrait matting | |
| 101 | +| `virtual_fit.py` | Virtual fit algorithm — transducer positioning optimization (top-level module, not a subpackage) | |
| 102 | +| `cloud/` | REST API client for cloud-based session sync, WebSocket support | |
| 103 | +| `util/` | Shared infrastructure: `DictMixin`, `PYFUSEncoder`, unit conversion, volume conversion | |
| 104 | + |
| 105 | +### Core Domain Model |
| 106 | + |
| 107 | +``` |
| 108 | +Protocol (treatment plan template) |
| 109 | + ├── Pulse, Sequence, FocalPattern |
| 110 | + ├── DelayMethod, ApodizationMethod |
| 111 | + ├── SegmentationMethod |
| 112 | + └── SimSetup |
| 113 | + │ |
| 114 | + ▼ Protocol.calc_solution(target, transducer, volume) |
| 115 | +Solution (computed beam parameters: delays[], apodizations[], foci[]) |
| 116 | + ├── simulate() → xarray.Dataset (pressure fields) |
| 117 | + ├── analyze() → SolutionAnalysis (pressure/safety metrics) |
| 118 | + └── scale() → adjust voltage to target pressure |
| 119 | +
|
| 120 | +Session |
| 121 | + ├── Subject, Protocol, Transducer, Volume |
| 122 | + ├── targets[] (Point), markers[] |
| 123 | + └── virtual_fit_results, transducer_tracking_results |
| 124 | +``` |
| 125 | + |
| 126 | +### Serialization Patterns |
| 127 | + |
| 128 | +Three patterns are used consistently: |
| 129 | + |
| 130 | +1. **`DictMixin`** (`util/dict_conversion.py`): automatic |
| 131 | + `to_dict()`/`from_dict()` from dataclass fields. Used by simpler classes |
| 132 | + (Sequence, Pulse, SimSetup, Material, VirtualFitOptions, Subject). |
| 133 | + |
| 134 | +2. **Manual `to_dict`/`from_dict`/`to_json`/`from_json`/`to_file`/`from_file`**: |
| 135 | + used by Protocol, Solution, Point, User, Run. |
| 136 | + |
| 137 | +3. **Polymorphic factory with class name lookup**: abstract base classes store |
| 138 | + `{'class': 'ClassName', ...}` in dicts and reconstruct via module |
| 139 | + introspection. Used by DelayMethod, ApodizationMethod, FocalPattern, |
| 140 | + SegmentationMethod. |
| 141 | + |
| 142 | +All JSON encoding uses `PYFUSEncoder` (`util/json.py`) which handles numpy |
| 143 | +types, datetime, and domain objects. |
| 144 | + |
| 145 | +### Database File Layout |
| 146 | + |
| 147 | +Each top-level resource has an index JSON listing its members (`users.json`, |
| 148 | +`protocols.json`, `subjects.json`, `transducers.json`); nested collections have |
| 149 | +their own index files alongside. |
| 150 | + |
| 151 | +``` |
| 152 | +{db_root}/ |
| 153 | + users/users.json |
| 154 | + users/{user_id}/... → User record + password hash |
| 155 | + protocols/protocols.json |
| 156 | + protocols/{protocol_id}/... → Protocol JSON |
| 157 | + subjects/subjects.json |
| 158 | + subjects/{subject_id}/{subject_id}.json → Subject record |
| 159 | + subjects/{subject_id}/volumes/volumes.json |
| 160 | + subjects/{subject_id}/volumes/{volume_id}/... |
| 161 | + subjects/{subject_id}/sessions/sessions.json |
| 162 | + subjects/{subject_id}/sessions/{session_id}/{session_id}.json |
| 163 | + subjects/{subject_id}/sessions/{session_id}/runs/runs.json |
| 164 | + subjects/{subject_id}/sessions/{session_id}/solutions/solutions.json |
| 165 | + subjects/{subject_id}/sessions/{session_id}/solutions/{solution_id}/... → Solution JSON + .nc |
| 166 | + subjects/{subject_id}/sessions/{session_id}/photoscans/photoscans.json |
| 167 | + subjects/{subject_id}/sessions/{session_id}/photocollections/photocollections.json |
| 168 | + transducers/transducers.json |
| 169 | + transducers/{transducer_id}/{transducer_id}.json → Transducer JSON |
| 170 | + transducers/{transducer_id}/{transducer_id}_gridweights_{hash}.h5 → Precomputed grid weights |
| 171 | +``` |
| 172 | + |
| 173 | +### Parameter Constraints |
| 174 | + |
| 175 | +`ParameterConstraint` provides validation with operators (`<`, `<=`, `>`, `>=`, |
| 176 | +`within`, `inside`, `outside`) and warning/error thresholds. `TargetConstraints` |
| 177 | +validates spatial bounds per dimension. Both used during |
| 178 | +`Protocol.calc_solution()` and `Solution.analyze()`. |
| 179 | + |
| 180 | +### Simulation Pipeline |
| 181 | + |
| 182 | +`Protocol.calc_solution()` flow: |
| 183 | + |
| 184 | +1. Validate target against `TargetConstraints` |
| 185 | +2. Create simulation params from volume + `SegmentationMethod` |
| 186 | +3. For each focal point: compute delays (`DelayMethod`) and apodizations |
| 187 | + (`ApodizationMethod`) |
| 188 | +4. Create `Solution` with stacked delay/apodization arrays |
| 189 | +5. `Solution.simulate()` → k-Wave via `run_simulation()` → xarray Dataset |
| 190 | +6. `Solution.scale()` → adjust voltage for target pressure |
| 191 | +7. `Solution.analyze()` → `SolutionAnalysis` (PNP, ISPPA, ISPTA, MI, TIC, |
| 192 | + beamwidths, temperature) |
| 193 | + |
| 194 | +## Testing |
| 195 | + |
| 196 | +- Tests use **pytest** (not Slicer's test framework — that's SlicerOpenLIFU). |
| 197 | +- No shared `conftest.py` — fixtures are local to each test file. |
| 198 | +- `tests/helpers.py` provides `dataclasses_are_equal()` for deep equality of |
| 199 | + nested dataclass/numpy structures. |
| 200 | +- Test data lives in `tests/resources/` (includes `example_db/` and a DICOM |
| 201 | + file). |
| 202 | +- The full sample database is published as its own repo (see "Sample Data" |
| 203 | + below); it is not pulled in by the test suite. |
| 204 | + |
| 205 | +## Sample Data |
| 206 | + |
| 207 | +The official sample database lives in |
| 208 | +[`openlifu-sample-database`](https://github.com/OpenwaterHealth/openlifu-sample-database) |
| 209 | +and is tracked with Git LFS. Tags on that repo (e.g. `openlifu-v0.20.0`) pin |
| 210 | +sample-data versions to specific `openlifu` releases, and downstream releases |
| 211 | +(SlicerOpenLIFU, openlifu-app) link to the README section below for the |
| 212 | +compatible version. |
| 213 | + |
| 214 | +```bash |
| 215 | +git clone --depth 1 --branch openlifu-v0.20.0 https://github.com/OpenwaterHealth/openlifu-sample-database.git |
| 216 | +cd openlifu-sample-database |
| 217 | +git lfs pull |
| 218 | +``` |
| 219 | + |
| 220 | +The legacy DVC flow (`db_dvc/`, `db_dvc.dvc`, `dvc[gdrive]` in the `dev` extra, |
| 221 | +Google Drive remote with `gdrive_client_secret`) is still present in the repo |
| 222 | +but is not the path users or downstream consumers should be pointed to. Direct |
| 223 | +customers and downstream projects to the README section "Getting Sample Data" |
| 224 | +instead. |
0 commit comments