asl-mri-utils 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. asl_mri_utils-0.5.0/.github/workflows/publish.yml +48 -0
  2. asl_mri_utils-0.5.0/.gitignore +8 -0
  3. asl_mri_utils-0.5.0/CHANGELOG.md +19 -0
  4. asl_mri_utils-0.5.0/LICENSE +21 -0
  5. asl_mri_utils-0.5.0/PKG-INFO +93 -0
  6. asl_mri_utils-0.5.0/README.md +63 -0
  7. asl_mri_utils-0.5.0/docs/convert.md +46 -0
  8. asl_mri_utils-0.5.0/docs/dicom.md +38 -0
  9. asl_mri_utils-0.5.0/docs/discovery.md +21 -0
  10. asl_mri_utils-0.5.0/docs/figs/fft_forward_match.svg +1252 -0
  11. asl_mri_utils-0.5.0/docs/figs/fft_nyquist_caveat.svg +1323 -0
  12. asl_mri_utils-0.5.0/docs/figs/fft_round_trip.svg +1229 -0
  13. asl_mri_utils-0.5.0/docs/figs/gp_length_scale.svg +1891 -0
  14. asl_mri_utils-0.5.0/docs/figs/gp_uncertainty.svg +1552 -0
  15. asl_mri_utils-0.5.0/docs/figs/kalman_bidirectional.svg +1690 -0
  16. asl_mri_utils-0.5.0/docs/figs/kalman_holt_convergence.svg +1290 -0
  17. asl_mri_utils-0.5.0/docs/figs/subtract_methods_comparison.svg +1662 -0
  18. asl_mri_utils-0.5.0/docs/figs/windowed_sinc_edge.svg +1553 -0
  19. asl_mri_utils-0.5.0/docs/figs/windowed_sinc_ringing.svg +1447 -0
  20. asl_mri_utils-0.5.0/docs/interpolate.md +260 -0
  21. asl_mri_utils-0.5.0/docs/merge.md +32 -0
  22. asl_mri_utils-0.5.0/docs/order.md +35 -0
  23. asl_mri_utils-0.5.0/docs/subtract.md +108 -0
  24. asl_mri_utils-0.5.0/pyproject.toml +69 -0
  25. asl_mri_utils-0.5.0/scripts/plot_interpolation_methods.py +403 -0
  26. asl_mri_utils-0.5.0/src/asl_mri_utils/__init__.py +1 -0
  27. asl_mri_utils-0.5.0/src/asl_mri_utils/cli.py +224 -0
  28. asl_mri_utils-0.5.0/src/asl_mri_utils/convert.py +281 -0
  29. asl_mri_utils-0.5.0/src/asl_mri_utils/dicom.py +131 -0
  30. asl_mri_utils-0.5.0/src/asl_mri_utils/discovery.py +40 -0
  31. asl_mri_utils-0.5.0/src/asl_mri_utils/exceptions.py +69 -0
  32. asl_mri_utils-0.5.0/src/asl_mri_utils/interpolate.py +386 -0
  33. asl_mri_utils-0.5.0/src/asl_mri_utils/merge.py +108 -0
  34. asl_mri_utils-0.5.0/src/asl_mri_utils/order.py +143 -0
  35. asl_mri_utils-0.5.0/src/asl_mri_utils/subtract.py +387 -0
  36. asl_mri_utils-0.5.0/tests/conftest.py +12 -0
  37. asl_mri_utils-0.5.0/tests/integration/test_real_dataset.py +81 -0
  38. asl_mri_utils-0.5.0/tests/unit/test_convert.py +108 -0
  39. asl_mri_utils-0.5.0/tests/unit/test_dicom.py +106 -0
  40. asl_mri_utils-0.5.0/tests/unit/test_discovery.py +35 -0
  41. asl_mri_utils-0.5.0/tests/unit/test_interpolate.py +194 -0
  42. asl_mri_utils-0.5.0/tests/unit/test_merge.py +96 -0
  43. asl_mri_utils-0.5.0/tests/unit/test_order.py +106 -0
  44. asl_mri_utils-0.5.0/tests/unit/test_subtract.py +114 -0
@@ -0,0 +1,48 @@
1
+ name: publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ build:
10
+ name: Build distribution
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install build tool
20
+ run: python3 -m pip install --upgrade build
21
+
22
+ - name: Build wheel and sdist
23
+ run: python3 -m build
24
+
25
+ - name: Upload build artefacts
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ name: Publish to PyPI
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment:
36
+ name: pypi
37
+ url: https://pypi.org/project/asl-mri-utils/
38
+ permissions:
39
+ id-token: write # required for PyPI trusted publishing (OIDC); no API token stored
40
+ steps:
41
+ - name: Download build artefacts
42
+ uses: actions/download-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+
47
+ - name: Publish to PyPI
48
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ build/
6
+ dist/
7
+ *.nii.gz
8
+ *.dcm
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Versioning follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## 0.5.0 -- initial release
7
+
8
+ - **Volume-order analysis** (`asl-mri-utils order`): recovers the true LABEL/CONTROL/M0/TI acquisition
9
+ order of a Siemens enhanced-DICOM ASL series directly from per-frame DICOM tags.
10
+ - **DICOM-to-NIfTI conversion** (`asl-mri-utils convert`): wraps a local `dcm2niix`/`dcm2niix_afni`
11
+ install, merging per-echo output into an oxasl-ready `*_asl.nii.gz`/`*_mzero.nii.gz` with a
12
+ matching `*_aslcontext.tsv` and `*_oxasl_params.json`.
13
+ - **Perfusion subtraction** (`asl-mri-utils subtract`): standard pairwise, surround, and sinc
14
+ subtraction (Liu & Wong 2005), with `--recommend` for design-dependent method selection.
15
+ - **Alternative sinc estimators** (`--sinc-interp`): windowed-sinc, FFT fractional delay,
16
+ Gaussian process regression, and a Kalman filter/RTS smoother, each vectorised across the voxel
17
+ axis via shared weight matrices.
18
+ - **Series discovery** (`asl-mri-utils discover`): finds candidate DICOM series directories under a root.
19
+ - Full documentation with equations, citations, and worked visual examples under `docs/`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sriranga Kashyap
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.5
2
+ Name: asl-mri-utils
3
+ Version: 0.5.0
4
+ Summary: Utilities for Siemens arterial spin labeling (ASL) DICOM volume-order analysis and DICOM-to-NIfTI conversion
5
+ Project-URL: Homepage, https://github.com/srikash/asl-mri-utils
6
+ Project-URL: Repository, https://github.com/srikash/asl-mri-utils
7
+ Project-URL: Issues, https://github.com/srikash/asl-mri-utils/issues
8
+ Project-URL: Documentation, https://github.com/srikash/asl-mri-utils/tree/main/docs
9
+ Author-email: Sriranga Kashyap <srikashmri@gmail.com>
10
+ Maintainer-email: Sriranga Kashyap <srikashmri@gmail.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: ASL,DICOM,MRI,NIfTI,Siemens,arterial spin labeling,neuroimaging,perfusion
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
24
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: nibabel>=5.0
27
+ Requires-Dist: numpy>=1.26
28
+ Requires-Dist: pydicom>=3.0
29
+ Description-Content-Type: text/markdown
30
+
31
+ # asl-mri-utils
32
+
33
+ A repository of utilities for ASL data post-processing.
34
+
35
+ Utilities for Siemens arterial spin labeling (ASL) DICOM processing:
36
+
37
+ - **Volume-order analysis** (`asl-mri-utils order`): recovers the true LABEL/CONTROL/M0/TI acquisition order
38
+ of a multi-TI, single- or multi-echo Siemens enhanced-DICOM ASL series directly from per-frame DICOM
39
+ tags (ASL Context, Siemens private ordering tag), independent of filenames or protocol names.
40
+ - **DICOM-to-NIfTI conversion** (`asl-mri-utils convert`): wraps a locally installed `dcm2niix`/`dcm2niix_afni`
41
+ binary to convert a Siemens ASL series directory to NIfTI, then merges the per-echo output into a
42
+ single, oxasl-ready `*_asl.nii.gz` (label/control volumes, chronologically ordered) and
43
+ `*_mzero.nii.gz` (M0 volumes), with a matching `*_aslcontext.tsv` and an `*_oxasl_params.json`
44
+ recording the `(iaf, order, tis, tes, rpts)` shape for `oxasl`/`oxasl_multite`. Volume counts are
45
+ checked against the DICOM-derived order at every step.
46
+ - **Perfusion subtraction** (`asl-mri-utils subtract`): computes CONTROL-LABEL difference images per
47
+ TI using standard pairwise, surround, or sinc subtraction (Liu & Wong 2005, *NeuroImage*
48
+ 24:207-215). `--recommend block|event_related` picks a single method per the paper's
49
+ design-dependent guidance instead of listing `--methods` by hand. `--sinc-interp` swaps the
50
+ sinc method's estimator for an alternative bidirectional interpolator (windowed-sinc, FFT,
51
+ Gaussian process regression, or a Kalman filter/RTS smoother; see
52
+ [docs/interpolate.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/interpolate.md)).
53
+ - **Series discovery** (`asl-mri-utils discover`): finds candidate DICOM series directories under a root.
54
+
55
+ Siemens-only by design: this package relies on the standard ASL DICOM tags plus a Siemens private tag for
56
+ TI ordering, and fails loudly (specific exceptions) rather than guessing when expected tags are absent.
57
+
58
+ ## Contents
59
+
60
+ - [docs/dicom.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/dicom.md) -- Siemens tag reading, ASL Context classification rule
61
+ - [docs/order.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/order.md) -- ordering/pairing logic, `order.tsv` schema
62
+ - [docs/discovery.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/discovery.md) -- series discovery, DICM sniffing
63
+ - [docs/convert.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/convert.md) -- dcm2niix wrapping, `oxasl_params.json` fields
64
+ - [docs/merge.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/merge.md) -- chronological concatenation into oxasl-ready output
65
+ - [docs/subtract.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/subtract.md) -- Liu & Wong (2005) equations for standard/surround/sinc subtraction
66
+ - [docs/interpolate.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/interpolate.md) -- windowed-sinc, FFT, Gaussian process, and Kalman/RTS interpolation, with visual examples
67
+
68
+ ## Requirements
69
+
70
+ A local `dcm2niix` or `dcm2niix_afni` installation is required for `asl-mri-utils convert` (not bundled).
71
+ Locate it via `--dcm2niix-path`, the `ASL_MRI_UTILS_DCM2NIIX` environment variable, or `PATH`.
72
+
73
+ ## Install
74
+
75
+ ```
76
+ pip install asl-mri-utils
77
+ ```
78
+
79
+ For local development, from a clone of this repository:
80
+
81
+ ```
82
+ pip install -e .
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ ```
88
+ asl-mri-utils order <dicom_series_dir> [<dicom_series_dir> ...] [--outdir DIR]
89
+ asl-mri-utils order --all [ROOT] [--outdir DIR]
90
+ asl-mri-utils convert <dicom_series_dir> --outdir DIR [--dcm2niix-path PATH] [--overwrite]
91
+ asl-mri-utils subtract <nii> <order_tsv> [--ti N ...] [--methods standard surround sinc] [--recommend block|event_related] [--sinc-interp truncated|windowed_sinc|fft|gp|kalman] [--outdir DIR]
92
+ asl-mri-utils discover [ROOT]
93
+ ```
@@ -0,0 +1,63 @@
1
+ # asl-mri-utils
2
+
3
+ A repository of utilities for ASL data post-processing.
4
+
5
+ Utilities for Siemens arterial spin labeling (ASL) DICOM processing:
6
+
7
+ - **Volume-order analysis** (`asl-mri-utils order`): recovers the true LABEL/CONTROL/M0/TI acquisition order
8
+ of a multi-TI, single- or multi-echo Siemens enhanced-DICOM ASL series directly from per-frame DICOM
9
+ tags (ASL Context, Siemens private ordering tag), independent of filenames or protocol names.
10
+ - **DICOM-to-NIfTI conversion** (`asl-mri-utils convert`): wraps a locally installed `dcm2niix`/`dcm2niix_afni`
11
+ binary to convert a Siemens ASL series directory to NIfTI, then merges the per-echo output into a
12
+ single, oxasl-ready `*_asl.nii.gz` (label/control volumes, chronologically ordered) and
13
+ `*_mzero.nii.gz` (M0 volumes), with a matching `*_aslcontext.tsv` and an `*_oxasl_params.json`
14
+ recording the `(iaf, order, tis, tes, rpts)` shape for `oxasl`/`oxasl_multite`. Volume counts are
15
+ checked against the DICOM-derived order at every step.
16
+ - **Perfusion subtraction** (`asl-mri-utils subtract`): computes CONTROL-LABEL difference images per
17
+ TI using standard pairwise, surround, or sinc subtraction (Liu & Wong 2005, *NeuroImage*
18
+ 24:207-215). `--recommend block|event_related` picks a single method per the paper's
19
+ design-dependent guidance instead of listing `--methods` by hand. `--sinc-interp` swaps the
20
+ sinc method's estimator for an alternative bidirectional interpolator (windowed-sinc, FFT,
21
+ Gaussian process regression, or a Kalman filter/RTS smoother; see
22
+ [docs/interpolate.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/interpolate.md)).
23
+ - **Series discovery** (`asl-mri-utils discover`): finds candidate DICOM series directories under a root.
24
+
25
+ Siemens-only by design: this package relies on the standard ASL DICOM tags plus a Siemens private tag for
26
+ TI ordering, and fails loudly (specific exceptions) rather than guessing when expected tags are absent.
27
+
28
+ ## Contents
29
+
30
+ - [docs/dicom.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/dicom.md) -- Siemens tag reading, ASL Context classification rule
31
+ - [docs/order.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/order.md) -- ordering/pairing logic, `order.tsv` schema
32
+ - [docs/discovery.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/discovery.md) -- series discovery, DICM sniffing
33
+ - [docs/convert.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/convert.md) -- dcm2niix wrapping, `oxasl_params.json` fields
34
+ - [docs/merge.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/merge.md) -- chronological concatenation into oxasl-ready output
35
+ - [docs/subtract.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/subtract.md) -- Liu & Wong (2005) equations for standard/surround/sinc subtraction
36
+ - [docs/interpolate.md](https://github.com/srikash/asl-mri-utils/blob/main/docs/interpolate.md) -- windowed-sinc, FFT, Gaussian process, and Kalman/RTS interpolation, with visual examples
37
+
38
+ ## Requirements
39
+
40
+ A local `dcm2niix` or `dcm2niix_afni` installation is required for `asl-mri-utils convert` (not bundled).
41
+ Locate it via `--dcm2niix-path`, the `ASL_MRI_UTILS_DCM2NIIX` environment variable, or `PATH`.
42
+
43
+ ## Install
44
+
45
+ ```
46
+ pip install asl-mri-utils
47
+ ```
48
+
49
+ For local development, from a clone of this repository:
50
+
51
+ ```
52
+ pip install -e .
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ```
58
+ asl-mri-utils order <dicom_series_dir> [<dicom_series_dir> ...] [--outdir DIR]
59
+ asl-mri-utils order --all [ROOT] [--outdir DIR]
60
+ asl-mri-utils convert <dicom_series_dir> --outdir DIR [--dcm2niix-path PATH] [--overwrite]
61
+ asl-mri-utils subtract <nii> <order_tsv> [--ti N ...] [--methods standard surround sinc] [--recommend block|event_related] [--sinc-interp truncated|windowed_sinc|fft|gp|kalman] [--outdir DIR]
62
+ asl-mri-utils discover [ROOT]
63
+ ```
@@ -0,0 +1,46 @@
1
+ # `asl_mri_utils.convert`
2
+
3
+ Wraps a local `dcm2niix`/`dcm2niix_afni` install to convert a Siemens ASL DICOM series directory to
4
+ NIfTI, and attaches ASL ordering metadata on top.
5
+
6
+ ## Two layers of output
7
+
8
+ - Per echo, alongside dcm2niix's own NIfTI + JSON: `"<stem>_echo<N>_order.tsv"` and
9
+ `"<stem>_echo<N>_aslcontext.tsv"` (the explicit `_echo<N>` tag avoids a filename collision with
10
+ the series-level merged output for single-echo series, where dcm2niix's own stem already equals
11
+ the series-level prefix). These describe dcm2niix's output as-is, M0 and any trailing unpaired
12
+ block included.
13
+ - Series-level, via `asl_mri_utils.merge`: a single combined, oxasl-ordered `"<prefix>_asl.nii.gz"` and
14
+ `"<prefix>_mzero.nii.gz"`, built by concatenating each echo's own volumes in true chronological
15
+ order (M0 and the trailing unpaired block dropped, since oxasl requires an exact volume count),
16
+ plus `"<prefix>_oxasl_params.json"` recording the `(iaf, order, ntis, ntes, rpts)` shape and the
17
+ merged file paths, ready to hand to `oxasl`/`oxasl_multite` directly.
18
+
19
+ ## `oxasl_params.json` shape, by protocol
20
+
21
+ | Shape | ntis | ntes | order (fastest→slowest) |
22
+ |---|---|---|---|
23
+ | single-TI, single-TE | 1 | 1 | `lr` |
24
+ | multi-TI, single-TE | >1 | 1 | `tlr` |
25
+ | multi-TI, multi-TE | >1 | >1 | `etlr` |
26
+ | single-TI, multi-TE | 1 | >1 | `elr` |
27
+
28
+ `iaf` is always `"tc"` (tag-then-control) for this acquisition pattern: the sequence always
29
+ acquires a LABEL block before its paired CONTROL block.
30
+
31
+ ## Error handling
32
+
33
+ - `NotAslSeriesError` / `NotSiemensError` / `MissingAslTagsError` before touching dcm2niix at all,
34
+ if the series doesn't qualify (see `asl_mri_utils.dicom`'s classification rule).
35
+ - `Dcm2niixError` if the dcm2niix subprocess itself fails (non-zero exit).
36
+ - `VolumeCountMismatchError` if a converted NIfTI's volume count disagrees with the DICOM-derived
37
+ order rows for that echo.
38
+
39
+ `resolve_dcm2niix_path` checks, in order: an explicit `--dcm2niix-path`, the
40
+ `ASL_MRI_UTILS_DCM2NIIX` environment variable, then `PATH` for `dcm2niix` and `dcm2niix_afni` in turn.
41
+ The binary is never bundled or installed by this package.
42
+
43
+ Output filenames are never predicted from a regex: the same study can produce two different
44
+ dcm2niix postfix conventions (`_1`/`_2` for raw ASL series, `_e1`/`_e2` for Siemens-derived
45
+ perfusion maps), so `convert.py` parses dcm2niix's own stdout (`"Convert N DICOM as <path> (...)"`)
46
+ to enumerate the actual output files.
@@ -0,0 +1,38 @@
1
+ # `asl_mri_utils.dicom`
2
+
3
+ Per-frame Siemens enhanced-DICOM tag reading for ASL series.
4
+
5
+ ## Classification rule for `read_frame_info`'s return value
6
+
7
+ - If `PerFrameFunctionalGroupsSequence` is absent, or ASL Technique Sequence (0018,9251) is absent
8
+ from the first frame, the series does not self-identify as ASL at all (localizer, B0 map,
9
+ MoCoSeries, a derived Perfusion_Weighted/relCBF map, and so on). This is **not** an error:
10
+ `read_frame_info` returns `None` and callers should skip the series silently.
11
+ - If ASL Technique Sequence **is** present (the series claims to be ASL) but ASL Context
12
+ (0018,9257), Frame Content Sequence (0020,9111), or Temporal Position Index (0020,9128) is then
13
+ missing, that is a malformed/unexpected ASL series, not a derived map with no context.
14
+ `MissingAslTagsError` is raised naming the specific missing tag.
15
+ - Echo Number (0018,0086) absence is **not** an error: it falls back to echo 1 (single-echo
16
+ protocols don't carry it). `ti_index` being `None` after `parse_ti_index` is also **not** an
17
+ error: some protocols may legitimately lack the Siemens private ordinal tag; downstream code
18
+ treats `ti_index=None` rows as "ungrouped by TI".
19
+
20
+ ## Siemens private tags used
21
+
22
+ - `PRIV_PERFRAME_SEQ = (0x0021, 0x11FE)`, `PRIV_ORDER_TAG = (0x0021, 0x1106)`: the per-frame
23
+ private sequence and the ordinal string it carries, e.g. `"X_1_1_2_3_1_1_1_1_1_1_1_40"`, parsed
24
+ by `parse_ti_index` into the fourth underscore-separated field (the TI ordinal).
25
+ - Shared block: creator `(0x0021, 0x0010)` = `"SIEMENS MR SDS 01"`, data sequence
26
+ `(0x0021, 0x10FE)`. Per-frame block: creator `(0x0021, 0x0011)` = `"SIEMENS MR SDI 02"`, data
27
+ sequence `(0x0021, 0x11FE)`. These are two separate private blocks at different tag numbers, not
28
+ one shared creator used in both places.
29
+ - `verify_siemens_private_block(ds)` checks `Manufacturer` (0008,0070) and one of the two private
30
+ creator strings above, raising `NotSiemensError` if neither matches. Call once per series (the
31
+ first frame suffices) before trusting any TI-index parse.
32
+
33
+ ## Public functions
34
+
35
+ - `parse_ti_index(private_order_string) -> int | None`
36
+ - `verify_siemens_private_block(ds)`
37
+ - `read_frame_info(ds) -> dict | None` -- returns
38
+ `{"acq_dt", "tpi", "context", "echo", "ti_index"}` per the classification rule above.
@@ -0,0 +1,21 @@
1
+ # `asl_mri_utils.discovery`
2
+
3
+ Series-folder discovery: finds candidate DICOM series directories under a root, matching the
4
+ `"NNN-SeriesDescription"` folder-naming convention used throughout this study and verifying each
5
+ candidate actually contains DICOM files.
6
+
7
+ ## Public functions
8
+
9
+ - `is_dicom(path) -> bool`: checks the `"DICM"` magic bytes at offset 128 (the standard DICOM
10
+ preamble).
11
+ - `discover_series(root, *, require_dcm_extension=True) -> list[Path]`: finds series directories
12
+ directly under `root` whose name matches `"NNN-SeriesDescription"` and that contain at least one
13
+ DICOM file. With `require_dcm_extension=True` (default, fast path), a directory qualifies if it
14
+ contains at least one `*.dcm` file. With `False`, every file in a name-matching directory is
15
+ sniffed for the DICM magic bytes instead, so directories work even if files haven't been given a
16
+ `.dcm` extension.
17
+
18
+ The scan root is always an explicit argument, never inferred from the package's own install
19
+ location: an earlier version of the underlying script hardcoded the scanning directory to wherever
20
+ the script itself lived, which silently broke once packaged (the install directory is not the
21
+ user's data directory).