|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project |
| 6 | + |
| 7 | +Ptychodus is a ptychography data-analysis application that ingests instrument data, prepares it for processing, and dispatches it through several reconstruction libraries (PtyChi, PtychoPINN, PtychoPINN-Torch). It runs interactively as a PyQt5 GUI, headless via batch CLI, and as a streaming processor inside beamline pipelines (pvapy area-detector). Python ≥3.11. |
| 8 | + |
| 9 | +## Common Commands |
| 10 | + |
| 11 | +Project uses `uv` (preferred) and a developer install with extras. |
| 12 | + |
| 13 | +```sh |
| 14 | +# Dev install (preferred). Other available extras: docs, ptychopinn |
| 15 | +uv sync --extra globus --extra gui --extra ptychi |
| 16 | + |
| 17 | +# Launch GUI |
| 18 | +uv run ptychodus |
| 19 | + |
| 20 | +# Headless batch |
| 21 | +uv run ptychodus -b reconstruct -i <input_dir> -o <output_dir> |
| 22 | +uv run ptychodus -b train -i <input_dir> -o <output_dir> |
| 23 | +# Batch mode reads <input_dir>/settings.ini, <input_dir>/diffraction.h5, <input_dir>/product-in.h5 |
| 24 | +# (see StandardFileLayout in src/ptychodus/api/io.py) |
| 25 | + |
| 26 | +# Beamline data-prep CLI |
| 27 | +uv run ptychodus-bdp --product-name <name> --diffraction-input <h5> \ |
| 28 | + --probe-position-input <csv> --output-directory <dir> \ |
| 29 | + --settings <ini> |
| 30 | + |
| 31 | +# Other entry points (see [project.scripts] in pyproject.toml) |
| 32 | +uv run convert-to-ptychodus |
| 33 | +uv run ptychodus-system-check |
| 34 | +uv run ptychodus-iri-tokens # Genesis/IRI auth |
| 35 | +uv run ptychodus-transfer-tokens # AmSC data-transfer auth |
| 36 | +uv run ptychodus-ptychopinn-tf-test # PtychoPINN TensorFlow smoke check |
| 37 | +``` |
| 38 | + |
| 39 | +Tests, lint, types: |
| 40 | + |
| 41 | +```sh |
| 42 | +uv run pytest # full suite (tests/) |
| 43 | +uv run pytest tests/test_io.py # one file |
| 44 | +uv run pytest tests/test_io.py::test_name # one test |
| 45 | +uv run ruff check . # lint (rules: F, N, NPY) |
| 46 | +uv run ruff format . # format (single quotes, line-length 100) |
| 47 | +uv run mypy src/ptychodus # type check (py 3.11) |
| 48 | +``` |
| 49 | + |
| 50 | +CI (`.github/workflows/python-package.yml`) runs four jobs on push/PR to `main`: a `pip install` + `ptychodus --version` smoke test (Py 3.11/3.12/3.13), `pytest tests/`, `ruff check` + `ruff format --check`, and `mypy src/ptychodus`. Run the local equivalents before pushing — CI will block the PR otherwise. |
| 51 | + |
| 52 | +Container & docs: |
| 53 | + |
| 54 | +```sh |
| 55 | +podman build -f Dockerfile.cuda -t ptychodus:cuda13.0 . # also: Dockerfile.cpu / .rocm / .xpu |
| 56 | +docker build -f Dockerfile.cuda -t ptychodus:cuda13.0 . |
| 57 | +make -C docs html # Sphinx docs into docs/build/ |
| 58 | +``` |
| 59 | + |
| 60 | +## High-Level Architecture |
| 61 | + |
| 62 | +### Three-layer separation: api / model / view+controller |
| 63 | + |
| 64 | +- **`src/ptychodus/api/`** — pure-Python domain layer with **no** GUI or model dependencies. Defines core data structures (`Product`, `ProbeSequence`, `Object`, `ProbePositionSequence`, `DiffractionDataset`, `AssembledDiffractionData`), abstract interfaces (`DiffractionFileReader/Writer`, `ProductFileReader/Writer`, `Reconstructor`, `TrainableReconstructor`, `WorkflowAPI`), and infrastructure (`Observable`/`Observer`, `Parameter`/`ParameterGroup`, `SettingsRegistry`, `PluginRegistry`/`PluginChooser`). All other layers depend on this; this layer depends on nothing else in ptychodus. |
| 65 | +- **`src/ptychodus/model/`** — application logic. Each subpackage (`diffraction/`, `product/`, `processing/`, `reconstructor/`, `analysis/`, `fluorescence/`, `globus/`, `genesis/`, `automation/`, `agent/`, `visualization/`, `ptychi/`, `ptychopinn/`, `ptychopinn_torch/`) exposes a `*Core` class that owns its settings, repositories, and APIs. `model/core.py::ModelCore` is the composition root: it constructs every `*Core` in dependency order and wires them together. Used both by the GUI and by `__main__.py` batch mode. |
| 66 | +- **`src/ptychodus/view/`** (PyQt5 widgets, no logic) and **`src/ptychodus/controller/`** (mediates between widgets and model). Mirror the model package layout. `view/core.py::ViewCore` and `controller/core.py::ControllerCore` are the composition roots; the navigation toolbar order in `ViewCore` is the source of truth for the left/right stacked-panel indexes. |
| 67 | + |
| 68 | +The GUI is optional: `__main__.py` falls back to headless mode if PyQt5 is missing. `ptychodus_stream_processor.py` (the `PtychodusAdImageProcessor`) is the third entry mode and is only imported when `pvapy` is available. |
| 69 | + |
| 70 | +### Index-based pattern/position association |
| 71 | + |
| 72 | +Diffraction patterns and probe positions are paired by **integer scan index**, never by array order. This is what makes streaming and mismatched-rate ingest robust: patterns and positions can arrive from different files or different PV channels, with dropped or extra samples on either side, and the matcher still pairs them correctly. |
| 73 | + |
| 74 | +- Producers: `DiffractionArray.get_indexes()` on the pattern side (`api/diffraction.py`); `ProbePosition.index` on the position side (`api/probe_positions.py`). |
| 75 | +- Matcher: `AssembledDiffractionData.prepare_reconstruct_input` in `api/reconstructor.py` treats pattern indexes as authoritative — duplicate position indexes are averaged into anchors, pattern indexes inside the anchor range with no exact position are linearly interpolated, and pattern indexes outside the anchor range are dropped (no extrapolation). The `Product` is rebuilt from the resulting per-pattern positions. |
| 76 | +- Round-trip: HDF5 and NPZ product writers persist position indexes via `ProductFileKeys.PROBE_POSITION_INDEXES`. |
| 77 | + |
| 78 | +### Plugin system |
| 79 | + |
| 80 | +File-format support is dynamic. `api/plugins.py::PluginRegistry.load_plugins()` walks `ptychodus.plugins.*` with `pkgutil.iter_modules` and calls each module's `register_plugins(registry)` function. Module-load failures are logged and skipped — this is how optional-dependency plugins (e.g., LCLS, NSLS-II) silently disable themselves. |
| 81 | + |
| 82 | +To add a new file format: create a module under `src/ptychodus/plugins/`, implement the relevant `*FileReader`/`*FileWriter` from `ptychodus.api`, and define `register_plugins(registry)` calling the appropriate `registry.<category>.register_plugin(...)`. For product readers, prefer `register_product_file_reader_with_adapters` so the reader is also reachable as a probe/probe-position/object reader. |
| 83 | + |
| 84 | +### Settings & observers |
| 85 | + |
| 86 | +Settings flow through `api/parametric.py::Parameter[T]` and `ParameterGroup`. Each `*Core` calls `settings_registry.create_group(name)` and creates typed parameters on it. `SettingsRegistry` serializes the full tree as INI. Components react to settings changes through the `Observer`/`Observable` pattern in `api/observer.py`; `PluginChooser.synchronize_with_parameter` is the typical bridge for "settings string → currently selected plugin." When adding cross-component reactivity, hook observers rather than poll. |
| 87 | + |
| 88 | +### Reconstructor libraries |
| 89 | + |
| 90 | +Each reconstructor backend (`model/ptychi/`, `model/ptychopinn/`, `model/ptychopinn_torch/`) exposes a `*ReconstructorLibrary` class. They are constructed in `ModelCore` and passed as a list to `ProcessingCore`. Backends that aren't installed should fail cleanly at import inside their own `__init__.py` — keep the surface a stable `*ReconstructorLibrary` regardless. Reconstructors implement `Reconstructor`/`TrainableReconstructor` from `api/reconstructor.py` and yield `ReconstructOutput` per iteration so the GUI can stream progress. |
| 91 | + |
| 92 | +### Standard HDF5 layout |
| 93 | + |
| 94 | +`api/io.py::StandardFileLayout` is the canonical contract for batch mode and remote workflows: `diffraction.h5`, `product-in.h5`, `product-out.h5`, `fluorescence-in.h5`, `fluorescence-out.h5`, `settings.ini`. `load_diffraction_data` / `save_diffraction_data` and `load_product` / `save_product` round-trip these files; their key names live in `DiffractionFileKeys` / `ProductFileKeys` enums — change those carefully, they are an external interface. |
| 95 | + |
| 96 | +### Remote compute |
| 97 | + |
| 98 | +Two providers, both gated by optional dependencies and constructed by `ModelCore` even when disabled: |
| 99 | + |
| 100 | +- `model/globus/` — Globus Compute submission, used by the original APS workflow. |
| 101 | +- `model/genesis/` — IRI/AmSC HPC submission with facility adapters in `model/genesis/facility_adapters.py` and per-facility scripts under `src/ptychodus/scripts/genesis/{alcf,nersc,olcf}/`. |
| 102 | + |
| 103 | +`WorkflowAPI` (see `api/workflow.py` and `model/workflow.py::ConcreteWorkflowAPI`) is the unified façade that GUI, batch mode, and remote workflows all drive. |
| 104 | + |
| 105 | +## Conventions |
| 106 | + |
| 107 | +- Ruff is configured for **single-quoted** strings, 100-char lines, py311 target. The selected lint rules (F, N, NPY) flag pyflakes errors, PEP-8 naming, and NumPy-specific issues. NumPy/Qt-style names (e.g., `probe_energy_eV`, `set_value_from_string`) are accepted via `# noqa: N802/N806/N815` — preserve the unit suffixes on physical quantities; reviewers expect them. |
| 108 | +- Type hints are mandatory; `pyproject.toml` lists modules whose missing stubs are intentionally ignored. Keep new code typed and avoid widening that ignore list. |
| 109 | +- `model/core.py::ModelCore.is_developer_mode_enabled` is `True` whenever the effective log level ≤ DEBUG (`--log-level 10`). Some controllers gate features (Agent panel, probe-position analysis) behind it. |
| 110 | + |
| 111 | +## Repository Notes |
| 112 | + |
| 113 | +- The git CI workflow targets `main`; local development branches such as `amsc` are active — confirm the intended target before opening PRs. |
| 114 | +- Sample `.h5`/`.npy` data and the `dist/` build output in the working tree are typically **untracked** local artifacts — do not stage them in commits unless explicitly asked. |
0 commit comments