Skip to content

Python Version for 2.0 - #40

Open
ironictoo wants to merge 286 commits into
masterfrom
dev
Open

ironictoo wants to merge 286 commits into
masterfrom
dev

Conversation

@ironictoo

Copy link
Copy Markdown
Member

Port ROCKETSHIP to python

lsaca05 and others added 30 commits April 29, 2024 08:44
…ner VFA input filename, add dce.json for test data
…o many start frames or if B fails for a similar reason
…ity curve csv to RUNB, TR to ms conversion in CLI script, blood_t1 to s in script prefs
…ltihance not magnevist pre-oct 2017, chop less baseline imgs in case current chop removes baseline
ironictoo and others added 11 commits August 23, 2026 14:37
Found by walking the CLI, batch and GUI as a new user would on a non-BIDS
dataset. All three broke plausible first invocations, and all three were
invisible to the suite.

run_dce_bids_batch's hardcoded stage_overrides still carried
"aif_curve_mode": "raw", retired in 2c81dcc when the key collapsed onto
top-level aif_mode. The typo guard rejects it, so every no-template batch
session failed at validate() before doing any work. The two existing tests
built a config and inspected its keys but never called validate(), which is
where the guard lives -- they stayed green while the path they cover was
dead. Both now validate, and a third test covers the no-template
invocation directly, since that is the block that can drift out of
dce_defaults.json.

Stage D raised "relaxivity has no default" for runs whose relaxivity came
from the image sidecar and which Stage A had already completed with it.
The call spelled the fallback as stage_a.get("relaxivity",
_stage_override(config, "relaxivity")); Python evaluates that default
argument eagerly, so the raising branch ran whether or not Stage A had
reported a value. Resolved only when Stage A genuinely reported none.
Ktrans maps are bit-identical across the fix (max abs diff 0.0) -- it
only ever raised or did nothing.

--set KEY=VALUE reached the pipeline as strings, so any boolean meant its
opposite: --set write_param_maps=false is the truthy string "false" and
wrote all seven maps. Four entry points had each grown their own parser
and drifted -- the batch drivers coerced JSON, the CLIs did not, and only
run_parametric_bids_batch mapped none/null. python/cli_overrides.py is now
the one parser, shared by both CLIs, both batch drivers and the GUI
override table, so a --set value and its JSON equivalent finally agree.
Values parse as JSON where that succeeds and stay text otherwise, which
keeps bare words and paths (tv, Dyn-1, ./sub-01_DCE.nii.gz) intact.
Keeping the none/null mapping is safe: _normalize_steady_state_auto_method
turns None into "none", the same as the string.

_write_param_maps compounded that with a bare bool() on the resolved
override, so even a correctly typed False from a config file wrote maps
anyway. It is now _to_bool like every other boolean read; it was the only
bare bool() among the five boolean defaults.

test_dce_cli's test_main_applies_set_overrides asserted == "123" and
== "1600" -- it had pinned the defect as intended behaviour. Those now
expect integers, plus a boolean and a bare word. tests/python/
test_cli_overrides.py pins the coercion table and asserts all four CLIs
share one function object, which is the drift that caused this.

Also, per-scan values now say where they came from. relaxivity and
hematocrit resolve sidecar -> run config -> defaults, the inverse of every
other key, so a relaxivity set in a run config and then outranked by the
sidecar was silently ignored. dce_config.resolve_scan_value_with_source
reports the winning source, Stage A logs it and records it in its payload,
and a run config that lost gets one line saying so. Not a warning -- a
study-wide fallback in a batch template outranked by a session that
carries its own value is the correct idiom, it just should not be
invisible.

The example configs exercised only the run-config route: all four set
relaxivity in stage_overrides. dce_run_example.json and dce_run_tiny.json
now take it from their sidecars (sub-02downsample's sidecar gains the
5.7 it was missing), dceprep_run_example.json keeps the study-wide
fallback, and dce_run_tiny.json's entry was inert already -- its sidecar
supplied the same 3.6. Each file's _doc says which route it shows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three interfaces read the same JSON and disagreed about it: where its
relative paths pointed, whether it had to describe a BIDS session, and
whether its file lists were honoured at all. Found by running each one as
a new user would, on data in and out of BIDS layout.

Relative paths in a config now resolve against the directory holding that
config, not the process cwd. Three rules were in play: the DCE CLI used
the cwd, the DCE GUI used the repository root, and parametric already used
the config's directory. The shipped example only ran from the repository
root -- from anywhere else it looked for /wherever/you/stood/tests/data
and died -- while the parametric config beside it ran from anywhere.
Paths typed on the command line (--output-dir, --set dce_metadata_path=)
still follow the cwd, since that is where they were typed. Two anchors,
each matching where the text was written.

Path-valued stage_overrides move with them. Anchoring the image lists but
not dce_metadata_path would have left a config resolving its images
relative to itself and its metadata relative to the cwd, which is worse
than either rule applied consistently. dce_config.PATH_VALUED_KEYS names
the five; one resolve_override_paths serves all four callers.

The shipped configs were written for cwd=repo-root, so their paths are
re-anchored to their own location. test_dce_pipeline_contracts was
leaning on the same assumption: it absolutized the tiny config into a
temp dir but skipped the metadata path inside stage_overrides, which only
worked because pytest runs from the repository root.

subject_source_path and subject_tp_path are now optional. A config
without them died on KeyError: 'subject_source_path' -- naming a BIDS key
to someone whose data is a folder of NIfTIs. Neither is consulted when
reading images: the first enables sidecar discovery by convention, the
second is recorded in the run summary. output_dir is the one genuinely
required key and its absence now says so. dce_cli validates before it
creates anything, so a bad config prints one line and exits 2 rather than
opening an event log and then raising.

Empty file lists are filled from the dceprep naming convention under
subject_tp_path. run_dce_bids_batch and the GUI's auto-find always did
this; the pipeline did not, so the same BIDS config meant different things
depending on which interface read it. Only empty lists are filled, so
naming a file still wins and a config can take the convention for most
inputs and override one; drift_files is never discovered, having no
convention; and every file found is logged, so a config that lists
nothing still records what it ran on. validate()'s AIF error no longer
says pipeline AIF discovery is unavailable, because it is not.

The GUI now lets the config decide whether auto-find owns the file lists.
It was checked by default and overwrote them on load, so a BIDS config's
explicit lists were already dead there -- verified with a decoy config,
which the GUI silently replaced. Worse for non-BIDS: with no derivatives
folder the file fields sat locked read-only with browse disabled under a
red "Set the BIDS Derivatives Folder", and nothing said the fix was
unchecking a box. Auto-find is now on only when the config names session
folders and no files, which is what asking for the convention looks like.

Two helpers the GUI kept privately are now shared, since a second copy of
"which files count" is how these interfaces drifted apart to begin with:
session_from_paths and DISCOVERABLE_FILE_LISTS live in
dce_file_discovery.py beside the naming patterns.

The examples are now a matched pair, one per layout, and each is a worked
run rather than a syntactically valid one:

  dce_run_example_bids.json     two session folders and no files at all;
                                images found by convention, TR/FA/time
                                resolution/relaxivity from the sidecar
  dce_run_example_nonbids.json  tests/data/BBB data p19, a flat folder;
                                every file named, every parameter stated

The BIDS example carried a redundant dce_metadata_path -- with a session
folder set the sidecar is found anyway -- so dropping it leaves the two
differing in exactly the thing they exist to show. The non-BIDS values
are the ones that study was actually processed with in MATLAB, from
processed/User Inputs Log.txt, and reproduce its result: 25,498 non-zero
Ktrans voxels at median 0.0257 against MATLAB's 25,510 and 0.0263. Guard
tests hold each example to its job -- adding a session path to the
non-BIDS one, or file lists to the BIDS one, fails.

run_parametric_bids_batch had the same template-anchoring gap as the DCE
batch (template values copied verbatim, then resolved against the cwd)
and is fixed with it.

Documented in docs/dce_options.md (a File paths section and a BIDS and
non-BIDS data section), both READMEs, AGENTS.md, and the CLI --help
epilog, which now names both examples.

Also records a pre-existing parametric batch failure in TODO.md, found
while sweeping the entry points: sub-02downsample pairs a 2-frame
preprocessed VFA image with three raw flip sidecars. It fails identically
at c0d27d1 and no test covers it, since the batch tests build synthetic
trees rather than using the committed fixture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pipelines already emitted a structured event stream; the CLIs printed
it raw, one JSON object per line, which is what made the terminal and the
GUI's "CLI output" an unreadable wall of text. The stream is unchanged and
still lands in full in <output_dir>/*_events.jsonl. run_reporting.py is
the other half: one renderer, shared by the CLIs, both batch drivers and
both GUI log views, so every interface describes a run identically.

Verbosity (quiet/normal/detailed/debug, -q/-v/-vv) selects what is
rendered, never what is recorded. normal is the new default; the old
default is --events on, which the GUI already passed explicitly. Batch
runs now describe the queue before running it -- the preflight only
reports, so the run loop still attempts every session and records the same
failures. Timings come from each event's own timestamp, so a GUI reading
down a pipe and a replayed log show true stage durations.

Version moves to 2.0.rc, and version.build_identity() writes it plus the
git revision into the cli_config and run_start events and the run summary
JSON. Recording is not gated on verbosity: result files outlive the
terminal that made them. Readers take the version from the event rather
than importing it, so a GUI rendering a subprocess reports the build that
produced the run, not the one drawing the header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
python/parametric_default.json was two files wearing one name: the defaults
the pipeline falls back to, and a runnable config pointing at fixtures in
tests/data. Editing it to change a default also edited an example run, so
in practice nobody edited it -- the real defaults lived in
parametric_pipeline.py as 10 dataclass field values and 11 .get() fallbacks.

parametric_defaults.json now holds preferences only, in the same shape
dce_defaults.json uses: _schema, required, optional, defaults, _units.
parametric_config.py resolves against it with the dce_config API --
load_defaults, default_for, resolve_with_source, validate_keys -- so a value
in neither the run config nor the file raises instead of being guessed, and
a misspelled key stops the run rather than being ignored. Input paths
(output_dir, vfa_files, the two masks) stay in a run config, since they
describe one study rather than how the software behaves.

parametric_run_example.json is what remains of the old file: its inputs and
its output label, nothing else. A test asserts it never restates a
defaults-file value, which is the drift that created this.

Two parity tests built ParametricT1Config by hand and so resolved defaults
from source. They go through from_dict now, because the pipeline and the
tests reading the same file is the point of having one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The parametric pipeline emitted no figures at all -- only the three NIfTI
maps -- so there was nothing for a QC tab to show and no way to see whether
a fit had gone wrong without loading the maps somewhere else.

parametric_qc.py answers the three questions a person asks after a T1 fit:
are the values physiological (T1 histogram, median marked), how much of the
volume did the R^2 threshold reject (R^2 histogram, threshold marked), and
does the map look like anatomy rather than noise (slice montage on one
shared window). They go out as artifact_type=figure, the same type the DCE
stages use, so the GUI tab, the reporter and the event log need no
parametric special case. write_qc_figures in the defaults file turns them
off.

Figures are decorative, and the code says so: no matplotlib, a map with
nothing fitted, or one plot raising all degrade to "no figure" rather than
failing a run that produced numbers.

The R^2 plot needed care. R^2 is negative wherever the fit is worse than a
flat line and reaches -267 on the example data, so the first version cropped
the axis to [0,1] and drew a plot claiming to reject 5,479 voxels while
showing almost nothing below the threshold. Values below zero are now
clipped into a labelled first bin, so the caption and the bars agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The DCE and parametric windows had grown 20 methods with the same names,
about half of them already identical -- _on_process_output differed by one
token, _stop_run_hard by one call, _base_dir only in its docstring. Two
copies of "how a GUI drives a CLI" is how the four --set parsers happened.

gui_common.py takes the parts that are the same by nature: the window
palette and stylesheet, path resolution against the config that holds a
path, browse dialogs, the log view and its reporter, the run bar, the
figures panel, and the QProcess lifecycle including the event demux. What
stays in each GUI is what genuinely differs -- which settings exist, how a
payload is assembled, and what an event means for the progress bar.

This commit only moves code and points dce_gui at it; the parametric GUI is
rebuilt on it separately, so a regression here bisects to one commit. The
window still builds its four tabs and all 112 override rows, and a full run
driven through it end to end finishes with the log rendered rather than
dumped. dce_gui.py loses 268 lines.

There were no GUI tests at all, which is how a cross-file refactor kills a
code path quietly. test_gui_common.py now covers path resolution, the shared
widgets, and that the DCE window builds -- plus a guard that these methods
have not been copied back into the window.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It was a two-pane splitter with a plain log box, no styling, no figures, no
results viewer, and a list of output paths as text. It now has the same four
tabs as the DCE window -- Inputs, CLI Output, QC Figures, Results -- the same
palette, the same run bar, and the same follow-the-run behaviour, all from
gui_common.

Results reuses the DCE volume viewer unchanged: discover_result_volumes was
already "every NIfTI under these directories" despite its docstring, so a
finished run shows its T1 and R-squared maps beside the VFA images they came
from. QC Figures needs no parametric special case either, because the
pipeline emits artifact_type=figure.

backend is reachable at last -- it was a validated config field with no
control in any interface, JSON only. fit_type becomes a combo of the three
values validate() accepts, instead of free text that turned a typo into a
ValueError several seconds into a run.

The advanced table is not the DCE one. With 15 flat keys a second editable
grid would just be the form again, so this is a read-only Resolved Settings
view: what the run will use, and whether each value came from the config, the
defaults file, or an edit made here. That is the source column, which is the
part of the DCE table a flat config actually benefits from -- and it only
became possible once the defaults moved out of source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/parametric_options.md is the field reference the parametric side never
had. It follows dce_options.md's structure -- configuration files, path
anchoring, verbosity, precedence, then the keys -- and is wired into the site
nav and the GUI's Open Options Doc button, which until now pointed at a file
that did not exist.

Writing it turned up two errors in what had just been built. An unset
b1_map_file does not mean nominal flip angles: the pipeline looks for
B1_scaled_FAreg.nii beside the VFA images and uses it if present, so absent
means auto-detect, not off. And the mask threshold is > 0, not non-zero. The
doc says both correctly and a GUI tooltip was corrected to match.

AGENTS.md gains the rule for the parametric defaults file and a GUI structure
section saying what gui_common owns and that a window must not re-add a local
copy of a mixin method. Both READMEs described a parametric GUI that no longer
exists, down to a script-preferences field that was never a config key.

TODO loses two items: the blocking "Update parametric GUI, check CLI, add
wrapper", and the single-source-defaults follow-up. COMPLETED records the four
phases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
install.py wrote a single rocketship.sh taking [dce|parametric]. The
parametric GUI therefore existed only as an argument, and the installer's own
next-steps output printed just "Launch the GUI: ./rocketship.sh" -- so the
second interface was reachable but undiscoverable, which is the actual defect
here rather than the file count.

It now writes rocketship_dce.sh and rocketship_parametric.sh (.bat on Windows)
from one template parameterised by GUI and label, and prints both, aligned,
under "Launch a GUI". Each script launches its own interface, so the
subcommand dispatch is gone and arguments pass straight through -- previously
a first argument named "dce" would have been eaten.

rocketship.sh was days old with no users, so it is removed rather than kept as
an alias. A re-install deletes a stale copy, since a script that still
dispatches on a subcommand would keep working while pointing at a shape the
installer no longer produces. It stays in .gitignore so an un-regenerated copy
is not accidentally committed.

Tests cover both launchers being written, each targeting its own GUI (they
share a template, so a substitution bug points both at one), no placeholder
surviving, the legacy file being removed, and both scripts actually running.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lsaca05 and others added 4 commits August 27, 2026 12:18
- Introduced new SVG logo and favicon for DCEasy branding.
- Updated mkdocs.yml to set the favicon for project differentiation.
- Modified docs.yml to prevent cancellation of concurrent deployments.
- Enhanced index.md with inline SVG for the DCEasy pipeline diagram.
- Added CSS styles for the pipeline diagram and hero band.
ironictoo and others added 5 commits August 31, 2026 07:05
Project notes, roadmaps and archived project records are working documents for
the development team, not user documentation. They were kept off the site by
exclude_docs in mkdocs.yml, which works under MkDocs -- but Zensical has no
exclude_docs, no not_in_nav and no draft support, so migrating the build would
have silently published all 18 pages and put 307 matches into the site search.

Move them to the repository root instead. Placement rather than configuration is
what keeps them unpublished: anything under docs/ publishes, full stop. That
survives a change of build tool, and it cannot be defeated by someone adding a
second internal directory and forgetting to exclude it.

No published page linked into the directory, so the site is unaffected. The
references updated here are in AGENTS.md, the Python and MATLAB sources, and the
tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The banner read DCE-MRI KINETIC MODELLING while the prose under it said
modelling in seven places and modeling in two. Part of a family-wide sweep
across the three DCEasy sites; see petmri/petmri.github.io for the hub.

Titles of published work keep their spelling as printed: Sourbron & Buckley's
"Tracer kinetic modelling in MRI" in the three model citations, and Manning et
al.'s "Verification of a generalised framework for tracer kinetic..." in the
OSIPI verification notes.

MATLAB and Python sources are untouched. Renaming identifiers there is a code
change with test consequences, and none of it is user-facing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Material for MkDocs reaches end of life on 5 November 2026. Zensical reads the
same mkdocs.yml and inherits the shared DCEasy theme unchanged, so the switch is
confined to the build: 20 pages in, 20 pages out, --strict clean, with MathJax,
arithmatex and the Material icon set all intact.

This depends on the preceding commit moving project-management out of docs/;
Zensical has no exclude_docs to keep it unpublished.

setup-python goes to v7 so every action in the workflow is on node24 before
GitHub retires node20 under them. The dev branch stays in the trigger: docs
still live there, and this repository has no main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zensical derives a page title from the first h1 and falls back to the filename.
This page's h1 is the inline banner SVG, so there is no text to derive from and
the home page came out as "Index - ROCKETSHIP" in the browser tab, in bookmarks
and in search results. MkDocs used to fall back to site_name here.

Set it explicitly in front matter, as the hub already does. The h1 stays the
banner and the nav label stays "Home".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zensical defaults to its "modern" look, which does not paint the header from
--md-primary-fg-color and so rendered this site with a white header instead of
the DCEasy navy. dceasy-base.yml now pins variant: classic for all three sites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants