cmpl 0.2.2__tar.gz → 0.2.4__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.
- {cmpl-0.2.2/src/cmpl.egg-info → cmpl-0.2.4}/PKG-INFO +454 -156
- cmpl-0.2.4/README.md +794 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/pyproject.toml +4 -1
- cmpl-0.2.4/src/cmpl/cli/__init__.py +3 -0
- cmpl-0.2.4/src/cmpl/cli/dicom_to_nifti.py +113 -0
- cmpl-0.2.4/src/cmpl/cli/t2star.py +469 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/dicom/enhanced_dicom.py +205 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/quantitative_MRI/mapping.py +23 -6
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/utilities/io.py +745 -23
- {cmpl-0.2.2 → cmpl-0.2.4/src/cmpl.egg-info}/PKG-INFO +454 -156
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl.egg-info/SOURCES.txt +5 -0
- cmpl-0.2.4/src/cmpl.egg-info/entry_points.txt +3 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/tests/test_qmr.py +6 -1
- cmpl-0.2.4/tests/test_t2star_cli.py +159 -0
- cmpl-0.2.2/README.md +0 -496
- {cmpl-0.2.2 → cmpl-0.2.4}/LICENSE +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/setup.cfg +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/_version.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/dicom/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/dicom/geometry.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/dicom/metadata.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/quantitative_MRI/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/grappa/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/grappa/grappa_1D.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/grappa/grappa_2D.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/grappa/utils.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/sense/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/reconstruction/sense/cg.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/segmentation/MRISegmentationTool.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/segmentation/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/segmentation/tools.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/utilities/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/utilities/df_build.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/utilities/numerical.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/utilities/utils.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/visualization/__init__.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl/visualization/visualization.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl.egg-info/dependency_links.txt +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl.egg-info/requires.txt +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/src/cmpl.egg-info/top_level.txt +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/tests/test_data.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/tests/test_installation.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/tests/test_io.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/tests/test_reconstruction.py +0 -0
- {cmpl-0.2.2 → cmpl-0.2.4}/tests/test_visualization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmpl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: CMRR MRI Processing Libraries
|
|
5
5
|
Author: Eisa Hedayati
|
|
6
6
|
License: Copyright (c) 2025 Eisa Hedayati
|
|
@@ -71,23 +71,32 @@ Dynamic: license-file
|
|
|
71
71
|
|
|
72
72
|
# CMPL — CMRR MRI Processing Libraries
|
|
73
73
|
|
|
74
|
-
[](https://pypi.org/project/cmpl/)
|
|
75
|
-
[](https://pypi.org/project/cmpl/)
|
|
74
|
+
[](https://pypi.org/project/cmpl/)
|
|
75
|
+
[](https://pypi.org/project/cmpl/)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
**PyPI:** https://pypi.org/project/cmpl/
|
|
78
|
+
**GitHub:** https://github.com/ehedayati/cmpl
|
|
78
79
|
|
|
79
|
-
CMPL is
|
|
80
|
+
CMPL is a Python package for MRI processing workflows developed at CMRR. It provides tools for MRI reconstruction, quantitative MRI, visualization, DICOM/NIfTI conversion and I/O, and supporting numerical and data utilities.
|
|
81
|
+
|
|
82
|
+
The package is modular by design: the base installation remains lightweight, while larger or domain-specific dependencies are installed only when the corresponding functionality is needed.
|
|
80
83
|
|
|
81
84
|
## Highlights
|
|
82
85
|
|
|
86
|
+
- Geometry-aware conventional and Enhanced DICOM to NIfTI conversion
|
|
87
|
+
- JSON metadata sidecars with acquisition and source-geometry information
|
|
88
|
+
- Multi-echo DICOM support with 4D NIfTI output ordered by echo time
|
|
89
|
+
- Packaged `cmpl-dicom-to-nifti` command-line converter
|
|
90
|
+
- Packaged `cmpl-t2star` command-line T2* and S0 mapper
|
|
91
|
+
- DICOM geometry and acquisition-metadata utilities
|
|
83
92
|
- Parallel MRI reconstruction with 1D/2D GRAPPA and conjugate-gradient SENSE
|
|
84
93
|
- Quantitative MRI tools for T2* fitting, signal reconstruction, and fitting-error analysis
|
|
85
|
-
- MRI visualization utilities for 2D comparisons
|
|
86
|
-
-
|
|
94
|
+
- MRI visualization utilities for 2D comparisons and 3D volume browsing
|
|
95
|
+
- Conventional and Enhanced DICOM, NIfTI, HDF5, and SimpleITK utilities
|
|
87
96
|
- Lightweight numerical utilities shared across CMPL
|
|
88
97
|
- Optional pandas-based indexing for CMPL-style medical-data directory structures
|
|
89
|
-
- Lazy
|
|
90
|
-
-
|
|
98
|
+
- Lazy imports so unrelated optional dependencies are not loaded unnecessarily
|
|
99
|
+
- Convenient aliases such as `cmpl.recon`, `cmpl.qmr`, `cmpl.vis`, and `cmpl.io`
|
|
91
100
|
|
|
92
101
|
## Requirements
|
|
93
102
|
|
|
@@ -98,7 +107,7 @@ CMPL requires:
|
|
|
98
107
|
- SciPy >= 1.13, < 2
|
|
99
108
|
- tqdm >= 4.66
|
|
100
109
|
|
|
101
|
-
Additional
|
|
110
|
+
Additional functionality is provided through optional dependency groups.
|
|
102
111
|
|
|
103
112
|
## Installation
|
|
104
113
|
|
|
@@ -116,23 +125,29 @@ Install only the functionality you need:
|
|
|
116
125
|
| `data` | pandas-based data indexing |
|
|
117
126
|
| `viz` | Matplotlib and Jupyter visualization |
|
|
118
127
|
| `torch` | PyTorch-based reconstruction and quantitative MRI |
|
|
119
|
-
| `all` | All optional functionality
|
|
128
|
+
| `all` | All optional CMPL functionality |
|
|
120
129
|
| `dev` | Testing, linting, build, and release tools |
|
|
121
130
|
|
|
122
131
|
Examples:
|
|
123
132
|
|
|
124
133
|
```bash
|
|
125
134
|
python -m pip install "cmpl[io]"
|
|
126
|
-
python -m pip install "cmpl[viz]"
|
|
127
135
|
python -m pip install "cmpl[torch]"
|
|
128
|
-
python -m pip install "cmpl[
|
|
136
|
+
python -m pip install "cmpl[viz]"
|
|
137
|
+
python -m pip install "cmpl[io,torch]"
|
|
129
138
|
python -m pip install "cmpl[all]"
|
|
130
139
|
```
|
|
131
140
|
|
|
132
|
-
|
|
141
|
+
For NIfTI-based T2* mapping:
|
|
133
142
|
|
|
134
143
|
```bash
|
|
135
|
-
python -m pip install "cmpl[torch
|
|
144
|
+
python -m pip install "cmpl[io,torch]"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Matplotlib is only required when plotting is explicitly requested:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python -m pip install "cmpl[viz]"
|
|
136
151
|
```
|
|
137
152
|
|
|
138
153
|
## Quick start
|
|
@@ -149,109 +164,262 @@ CMPL exposes convenient aliases for commonly used subpackages:
|
|
|
149
164
|
cmpl.recon # reconstruction
|
|
150
165
|
cmpl.qmr # quantitative MRI
|
|
151
166
|
cmpl.vis # visualization
|
|
152
|
-
cmpl.utils # utilities
|
|
153
167
|
cmpl.io # I/O utilities
|
|
168
|
+
cmpl.dicom # DICOM metadata and geometry utilities
|
|
169
|
+
cmpl.utils # utilities
|
|
154
170
|
```
|
|
155
171
|
|
|
156
|
-
|
|
172
|
+
These aliases are resolved lazily so `import cmpl` does not require every optional dependency to be installed or imported.
|
|
157
173
|
|
|
158
|
-
|
|
174
|
+
---
|
|
159
175
|
|
|
160
|
-
|
|
176
|
+
## DICOM and NIfTI I/O
|
|
177
|
+
|
|
178
|
+
Install the I/O extra:
|
|
161
179
|
|
|
162
180
|
```bash
|
|
163
|
-
python -m pip install "cmpl[
|
|
181
|
+
python -m pip install "cmpl[io]"
|
|
164
182
|
```
|
|
165
183
|
|
|
166
|
-
###
|
|
184
|
+
### Convert a DICOM series to NIfTI
|
|
185
|
+
|
|
186
|
+
CMPL supports direct conversion of both conventional and Enhanced DICOM series to NIfTI.
|
|
187
|
+
|
|
188
|
+
The converter:
|
|
189
|
+
|
|
190
|
+
- detects the DICOM representation automatically
|
|
191
|
+
- preserves spatial geometry
|
|
192
|
+
- supports single-echo and multi-echo acquisitions
|
|
193
|
+
- writes a NIfTI image
|
|
194
|
+
- writes a matching JSON metadata sidecar
|
|
195
|
+
- orders multi-echo volumes by echo time
|
|
167
196
|
|
|
168
197
|
```python
|
|
169
|
-
|
|
198
|
+
import cmpl
|
|
170
199
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
alpha=0.5,
|
|
175
|
-
direction="sagittal",
|
|
176
|
-
cmap="gray",
|
|
177
|
-
vmin=0,
|
|
178
|
-
vmax=1,
|
|
179
|
-
dpi=300,
|
|
200
|
+
metadata = cmpl.io.dicom_to_nifti(
|
|
201
|
+
"/path/to/dicom_series",
|
|
202
|
+
"output.nii.gz",
|
|
180
203
|
)
|
|
181
204
|
```
|
|
182
205
|
|
|
183
|
-
|
|
206
|
+
This creates:
|
|
184
207
|
|
|
208
|
+
```text
|
|
209
|
+
output.nii.gz
|
|
210
|
+
output.json
|
|
211
|
+
```
|
|
185
212
|
|
|
186
|
-
|
|
213
|
+
If the output path does not end in `.nii` or `.nii.gz`, CMPL appends `.nii.gz`.
|
|
214
|
+
|
|
215
|
+
For a single echo, the output is a 3D image. For a multi-echo acquisition, CMPL writes a 4D NIfTI with echoes in the last dimension:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
x, y, z, echo
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The matching JSON sidecar contains acquisition metadata and source-geometry information. For multi-echo data, echo times are stored in milliseconds under:
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"Acquisition": {
|
|
226
|
+
"EchoTimes": [2.5, 5.0, 7.5, 10.0],
|
|
227
|
+
"TimeUnit": "ms"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
If a directory contains multiple DICOM series, select a specific `SeriesInstanceUID`:
|
|
187
233
|
|
|
188
234
|
```python
|
|
189
|
-
|
|
235
|
+
metadata = cmpl.io.dicom_to_nifti(
|
|
236
|
+
"/path/to/dicom_directory",
|
|
237
|
+
"output.nii.gz",
|
|
238
|
+
series_id="1.2.840...",
|
|
239
|
+
)
|
|
240
|
+
```
|
|
190
241
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
242
|
+
### Command-line DICOM conversion
|
|
243
|
+
|
|
244
|
+
The I/O extra installs:
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
cmpl-dicom-to-nifti
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Convert a DICOM series with automatic conventional/Enhanced-DICOM detection:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
cmpl-dicom-to-nifti /path/to/dicom_series
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The same CLI can be invoked as a Python module:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
python -m cmpl.cli.dicom_to_nifti /path/to/dicom_series
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
If the output path is omitted, CMPL writes the NIfTI and JSON sidecar to the current directory using the DICOM directory name:
|
|
263
|
+
|
|
264
|
+
```text
|
|
265
|
+
./<series_directory_name>.nii.gz
|
|
266
|
+
./<series_directory_name>.json
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Specify an explicit output path if needed:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
cmpl-dicom-to-nifti \
|
|
273
|
+
/path/to/dicom_series \
|
|
274
|
+
/path/to/output.nii.gz
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Progress output is enabled by default. Disable it with:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
cmpl-dicom-to-nifti /path/to/dicom_series --no-verbose
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The same command handles conventional single-frame DICOM and Enhanced multi-frame DICOM.
|
|
284
|
+
|
|
285
|
+
### Read a NIfTI file
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from cmpl.utilities.io import nifti_read
|
|
289
|
+
|
|
290
|
+
nifti_image, data = nifti_read("image.nii.gz")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Replace NIfTI data while preserving geometry
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from cmpl.utilities.io import update_nifti_data
|
|
297
|
+
|
|
298
|
+
updated = update_nifti_data(
|
|
299
|
+
"reference.nii.gz",
|
|
300
|
+
new_data,
|
|
301
|
+
output_path="updated.nii.gz",
|
|
196
302
|
)
|
|
197
303
|
```
|
|
198
304
|
|
|
199
|
-
###
|
|
305
|
+
### Save a scalar map using reference NIfTI geometry
|
|
200
306
|
|
|
201
307
|
```python
|
|
202
|
-
from cmpl.
|
|
308
|
+
from cmpl.utilities.io import save_scalar_map_like
|
|
203
309
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
dimension="axial",
|
|
310
|
+
save_scalar_map_like(
|
|
311
|
+
reference_image,
|
|
312
|
+
scalar_map,
|
|
313
|
+
"map.nii.gz",
|
|
209
314
|
)
|
|
210
315
|
```
|
|
211
316
|
|
|
212
|
-
|
|
317
|
+
This is useful for quantitative maps such as T2* and S0 because the spatial geometry of the source NIfTI is preserved.
|
|
213
318
|
|
|
214
|
-
|
|
319
|
+
### Load a DICOM directory as a NumPy array
|
|
215
320
|
|
|
216
321
|
```python
|
|
217
|
-
cmpl.
|
|
322
|
+
from cmpl.utilities.io import load_dicom_scan_from_dir
|
|
323
|
+
|
|
324
|
+
volume = load_dicom_scan_from_dir(
|
|
325
|
+
"/path/to/dicom_directory",
|
|
326
|
+
reshape=True,
|
|
327
|
+
)
|
|
218
328
|
```
|
|
219
329
|
|
|
220
|
-
|
|
330
|
+
For multi-echo data, the loader can return:
|
|
221
331
|
|
|
222
|
-
```
|
|
223
|
-
|
|
332
|
+
```text
|
|
333
|
+
x, y, z, echo
|
|
224
334
|
```
|
|
225
335
|
|
|
226
|
-
|
|
336
|
+
depending on the acquisition metadata and requested reshaping behavior.
|
|
337
|
+
|
|
338
|
+
### Read a DICOM series as SimpleITK
|
|
227
339
|
|
|
228
340
|
```python
|
|
229
|
-
|
|
341
|
+
from cmpl.utilities.io import dicom_to_SimpleITK
|
|
230
342
|
|
|
231
|
-
|
|
343
|
+
image = dicom_to_SimpleITK("/path/to/dicom_directory")
|
|
344
|
+
```
|
|
232
345
|
|
|
233
|
-
|
|
234
|
-
s0 = np.full((64, 64, 8), 100.0, dtype=np.float32)
|
|
235
|
-
echo_times = np.array([0.0, 5.0, 10.0, 15.0], dtype=np.float32)
|
|
346
|
+
The returned image is 3D for single-echo data and 4D when multiple echoes are detected.
|
|
236
347
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
348
|
+
### Write a SimpleITK image as NIfTI
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
from cmpl.utilities.io import itk_to_nifti
|
|
352
|
+
|
|
353
|
+
output_path = itk_to_nifti(
|
|
354
|
+
image,
|
|
355
|
+
"output.nii.gz",
|
|
243
356
|
)
|
|
357
|
+
```
|
|
244
358
|
|
|
245
|
-
|
|
246
|
-
|
|
359
|
+
### DICOM geometry and metadata helpers
|
|
360
|
+
|
|
361
|
+
CMPL separates DICOM geometry and acquisition-metadata handling into dedicated modules under `cmpl.dicom`.
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from cmpl.dicom import (
|
|
365
|
+
extract_slice_geometry,
|
|
366
|
+
get_slice_position,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
geometry = extract_slice_geometry("slice001.dcm")
|
|
370
|
+
position = get_slice_position("slice001.dcm")
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Enhanced-DICOM helpers are also available:
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
from cmpl.dicom.enhanced_dicom import (
|
|
377
|
+
get_slice_thickness,
|
|
378
|
+
get_spacing_between_slices,
|
|
379
|
+
voxel_sizes_detailed,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
details = voxel_sizes_detailed(dataset)
|
|
247
383
|
```
|
|
248
384
|
|
|
249
|
-
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Quantitative MRI
|
|
388
|
+
|
|
389
|
+
Quantitative MRI functionality is available under:
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
cmpl.qmr
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Install PyTorch support:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
python -m pip install "cmpl[torch]"
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
For NIfTI-based quantitative MRI workflows:
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
python -m pip install "cmpl[io,torch]"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Signal model
|
|
408
|
+
|
|
409
|
+
The current two-parameter T2* implementation uses the mono-exponential signal model:
|
|
250
410
|
|
|
251
411
|
```text
|
|
252
412
|
S(TE) = S0 * exp(-TE / T2*)
|
|
253
413
|
```
|
|
254
414
|
|
|
415
|
+
where:
|
|
416
|
+
|
|
417
|
+
- `S0` is the extrapolated signal at TE = 0
|
|
418
|
+
- `T2*` is the transverse relaxation time
|
|
419
|
+
- TE and T2* must use the same time unit
|
|
420
|
+
|
|
421
|
+
CMPL conventionally uses milliseconds for T2* workflows.
|
|
422
|
+
|
|
255
423
|
### Fit a 3D two-parameter T2* model
|
|
256
424
|
|
|
257
425
|
```python
|
|
@@ -271,7 +439,133 @@ t2_star_map = result["T2_star_map"]
|
|
|
271
439
|
s0_map = result["S0_map"]
|
|
272
440
|
```
|
|
273
441
|
|
|
274
|
-
|
|
442
|
+
The expected image layout is:
|
|
443
|
+
|
|
444
|
+
```text
|
|
445
|
+
x, y, z, echo
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
If CUDA is available and no device is supplied, the fitter can select CUDA automatically.
|
|
449
|
+
|
|
450
|
+
### Command-line 3D T2* mapping
|
|
451
|
+
|
|
452
|
+
CMPL includes a command-line interface for calculating T2* and S0 maps directly from a 4D multi-echo NIfTI file and its JSON metadata sidecar.
|
|
453
|
+
|
|
454
|
+
Install the required dependencies:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
python -m pip install "cmpl[io,torch]"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Given:
|
|
461
|
+
|
|
462
|
+
```text
|
|
463
|
+
multi_echo.nii.gz
|
|
464
|
+
multi_echo.json
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
run:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
cmpl-t2star multi_echo.nii.gz
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The JSON sidecar is detected automatically when it has the same basename as the NIfTI file.
|
|
474
|
+
|
|
475
|
+
The CLI reads echo times from:
|
|
476
|
+
|
|
477
|
+
```json
|
|
478
|
+
{
|
|
479
|
+
"Acquisition": {
|
|
480
|
+
"EchoTimes": [2.5, 5.0, 7.5, 10.0],
|
|
481
|
+
"TimeUnit": "ms"
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
The input NIfTI must be 4D:
|
|
487
|
+
|
|
488
|
+
```text
|
|
489
|
+
x, y, z, echo
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
and the number of entries in `EchoTimes` must match the number of volumes in the fourth dimension.
|
|
493
|
+
|
|
494
|
+
The command writes:
|
|
495
|
+
|
|
496
|
+
```text
|
|
497
|
+
multi_echo_T2star.nii.gz
|
|
498
|
+
multi_echo_S0.nii.gz
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
The T2* map is written in milliseconds. The S0 map retains the signal-intensity units of the input data. Output maps preserve the spatial geometry of the source NIfTI.
|
|
502
|
+
|
|
503
|
+
Specify a different JSON file:
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
cmpl-t2star multi_echo.nii.gz \
|
|
507
|
+
--json metadata.json
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Specify a custom output prefix:
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
cmpl-t2star multi_echo.nii.gz \
|
|
514
|
+
-o results/subject01
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
This creates:
|
|
518
|
+
|
|
519
|
+
```text
|
|
520
|
+
results/subject01_T2star.nii.gz
|
|
521
|
+
results/subject01_S0.nii.gz
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Request CUDA explicitly:
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
cmpl-t2star multi_echo.nii.gz --device cuda
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
If no device is specified, CMPL uses CUDA when available and otherwise uses CPU.
|
|
531
|
+
|
|
532
|
+
Optimization settings can also be adjusted:
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
cmpl-t2star multi_echo.nii.gz \
|
|
536
|
+
--device cuda \
|
|
537
|
+
--iterations 10000 \
|
|
538
|
+
--lr 0.01 \
|
|
539
|
+
--initial-t2star 20
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
If echo times are present but not ordered, the CLI sorts the echo times and their corresponding NIfTI volumes together before fitting.
|
|
543
|
+
|
|
544
|
+
### Reconstruct a multi-echo signal from T2* and S0 maps
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
import numpy as np
|
|
548
|
+
|
|
549
|
+
from cmpl.quantitative_MRI import reconstruct_images
|
|
550
|
+
|
|
551
|
+
t2_star = np.full((64, 64, 8), 20.0, dtype=np.float32)
|
|
552
|
+
s0 = np.full((64, 64, 8), 100.0, dtype=np.float32)
|
|
553
|
+
echo_times = np.array(
|
|
554
|
+
[0.0, 5.0, 10.0, 15.0],
|
|
555
|
+
dtype=np.float32,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
images = reconstruct_images(
|
|
559
|
+
t2_star,
|
|
560
|
+
s0,
|
|
561
|
+
echo_times,
|
|
562
|
+
device="cpu",
|
|
563
|
+
return_numpy=True,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
print(images.shape)
|
|
567
|
+
# (64, 64, 8, 4)
|
|
568
|
+
```
|
|
275
569
|
|
|
276
570
|
### Calculate normalized fitting error
|
|
277
571
|
|
|
@@ -286,7 +580,11 @@ rmse_pct, rse_pct = calculate_rmse_percentage_s0(
|
|
|
286
580
|
)
|
|
287
581
|
```
|
|
288
582
|
|
|
289
|
-
CMPL also contains
|
|
583
|
+
CMPL also contains additional 2D/3D T2* fitting functions.
|
|
584
|
+
|
|
585
|
+
Plotting is optional. Matplotlib is imported only when plotting is requested.
|
|
586
|
+
|
|
587
|
+
---
|
|
290
588
|
|
|
291
589
|
## Reconstruction
|
|
292
590
|
|
|
@@ -296,7 +594,7 @@ Reconstruction functionality is available under:
|
|
|
296
594
|
cmpl.recon
|
|
297
595
|
```
|
|
298
596
|
|
|
299
|
-
PyTorch
|
|
597
|
+
Install PyTorch support:
|
|
300
598
|
|
|
301
599
|
```bash
|
|
302
600
|
python -m pip install "cmpl[torch]"
|
|
@@ -316,7 +614,7 @@ reconstructed_kspace = grappa_1d_recon(
|
|
|
316
614
|
)
|
|
317
615
|
```
|
|
318
616
|
|
|
319
|
-
|
|
617
|
+
The current implementation expects coil-resolved k-space in the order:
|
|
320
618
|
|
|
321
619
|
```text
|
|
322
620
|
frequency, phase, slice, coils
|
|
@@ -338,7 +636,7 @@ reconstructed_kspace = grappa_2d_recon(
|
|
|
338
636
|
### Conjugate-gradient SENSE
|
|
339
637
|
|
|
340
638
|
```python
|
|
341
|
-
from cmpl.reconstruction.sense import CG_sense_2D
|
|
639
|
+
from cmpl.reconstruction.sense.cg import CG_sense_2D
|
|
342
640
|
|
|
343
641
|
reconstructed_image = CG_sense_2D(
|
|
344
642
|
undersampled_image_space,
|
|
@@ -348,87 +646,53 @@ reconstructed_image = CG_sense_2D(
|
|
|
348
646
|
|
|
349
647
|
Inputs to the current SENSE implementation are PyTorch tensors.
|
|
350
648
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
Install the I/O extra:
|
|
354
|
-
|
|
355
|
-
```bash
|
|
356
|
-
python -m pip install "cmpl[io]"
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### Read a NIfTI file
|
|
360
|
-
|
|
361
|
-
```python
|
|
362
|
-
from cmpl.utilities.io import nifti_read
|
|
649
|
+
---
|
|
363
650
|
|
|
364
|
-
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### Replace NIfTI data while preserving geometry
|
|
651
|
+
## Visualization
|
|
368
652
|
|
|
369
|
-
|
|
370
|
-
from cmpl.utilities.io import update_nifti_data
|
|
653
|
+
Install the visualization extra:
|
|
371
654
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
new_data,
|
|
375
|
-
output_path="updated.nii.gz",
|
|
376
|
-
)
|
|
655
|
+
```bash
|
|
656
|
+
python -m pip install "cmpl[viz]"
|
|
377
657
|
```
|
|
378
658
|
|
|
379
|
-
###
|
|
659
|
+
### Browse or display a 3D MRI volume
|
|
380
660
|
|
|
381
661
|
```python
|
|
382
|
-
from cmpl.
|
|
662
|
+
from cmpl.visualization import plot_3D_mri
|
|
383
663
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
664
|
+
plot_3D_mri(
|
|
665
|
+
volume,
|
|
666
|
+
slice_number=volume.shape[2] // 2,
|
|
667
|
+
alpha=0.5,
|
|
668
|
+
direction="sagittal",
|
|
669
|
+
cmap="gray",
|
|
670
|
+
vmin=0,
|
|
671
|
+
vmax=1,
|
|
672
|
+
dpi=300,
|
|
387
673
|
)
|
|
388
674
|
```
|
|
389
675
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
```text
|
|
393
|
-
x, y, z, echo
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
depending on the acquisition metadata and requested reshaping behavior.
|
|
397
|
-
|
|
398
|
-
### DICOM to SimpleITK
|
|
399
|
-
|
|
400
|
-
```python
|
|
401
|
-
from cmpl.utilities.io import dicom_to_SimpleITK
|
|
402
|
-
|
|
403
|
-
image = dicom_to_SimpleITK("/path/to/dicom_directory")
|
|
404
|
-
```
|
|
676
|
+
If an interactive Matplotlib backend is available, `plot_3D_mri` can use interactive controls. Otherwise it falls back to static redraw mode.
|
|
405
677
|
|
|
406
|
-
###
|
|
678
|
+
### Compare images side by side
|
|
407
679
|
|
|
408
680
|
```python
|
|
409
|
-
from cmpl.
|
|
681
|
+
from cmpl.visualization import side_by_side_view
|
|
410
682
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
683
|
+
side_by_side_view(
|
|
684
|
+
image_a,
|
|
685
|
+
image_b,
|
|
686
|
+
titles=["Reference", "Reconstruction"],
|
|
687
|
+
color_palette="gray",
|
|
414
688
|
)
|
|
415
689
|
```
|
|
416
690
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
```python
|
|
420
|
-
from cmpl.dicom.enhanced_dicom import (
|
|
421
|
-
get_slice_thickness,
|
|
422
|
-
get_spacing_between_slices,
|
|
423
|
-
voxel_sizes_detailed,
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
details = voxel_sizes_detailed(dataset)
|
|
427
|
-
```
|
|
691
|
+
---
|
|
428
692
|
|
|
429
693
|
## Numerical utilities
|
|
430
694
|
|
|
431
|
-
Lightweight numerical helpers are kept separate from heavier I/O modules
|
|
695
|
+
Lightweight numerical helpers are kept separate from heavier I/O modules.
|
|
432
696
|
|
|
433
697
|
```python
|
|
434
698
|
from cmpl.utilities.numerical import resize_matrix
|
|
@@ -439,15 +703,17 @@ resized = resize_matrix(
|
|
|
439
703
|
)
|
|
440
704
|
```
|
|
441
705
|
|
|
442
|
-
`resize_matrix` accepts NumPy arrays and PyTorch tensors. PyTorch is imported
|
|
706
|
+
`resize_matrix` accepts NumPy arrays and PyTorch tensors. PyTorch is imported only when a Torch tensor is actually passed.
|
|
443
707
|
|
|
444
|
-
For backward compatibility
|
|
708
|
+
For backward compatibility:
|
|
445
709
|
|
|
446
710
|
```python
|
|
447
711
|
from cmpl.utilities.utils import resize_matrix
|
|
448
712
|
```
|
|
449
713
|
|
|
450
|
-
|
|
714
|
+
continues to work.
|
|
715
|
+
|
|
716
|
+
---
|
|
451
717
|
|
|
452
718
|
## Data indexing
|
|
453
719
|
|
|
@@ -457,7 +723,7 @@ Install the data extra:
|
|
|
457
723
|
python -m pip install "cmpl[data]"
|
|
458
724
|
```
|
|
459
725
|
|
|
460
|
-
CMPL includes a pandas-based utility for indexing
|
|
726
|
+
CMPL includes a pandas-based utility for indexing directory trees that follow the CMPL medical-data convention:
|
|
461
727
|
|
|
462
728
|
```python
|
|
463
729
|
from cmpl.utilities.df_build import build_medical_data_frame
|
|
@@ -465,28 +731,26 @@ from cmpl.utilities.df_build import build_medical_data_frame
|
|
|
465
731
|
df = build_medical_data_frame("/path/to/root")
|
|
466
732
|
```
|
|
467
733
|
|
|
468
|
-
The
|
|
734
|
+
The expected structure is similar to:
|
|
469
735
|
|
|
470
736
|
```text
|
|
471
737
|
root/
|
|
472
738
|
├── Study001/
|
|
473
739
|
│ ├── Dicoms/
|
|
474
740
|
│ │ └── <contrast>/
|
|
475
|
-
│
|
|
476
|
-
│
|
|
477
|
-
│ └── Segmentations/
|
|
478
|
-
│ └── <contrast>/
|
|
479
|
-
│ └── <group>/
|
|
480
|
-
│ └── <segmentation>.nii.gz
|
|
741
|
+
│ └── h5_files/
|
|
742
|
+
│ └── <contrast>.h5
|
|
481
743
|
└── Study002/
|
|
482
744
|
└── ...
|
|
483
745
|
```
|
|
484
746
|
|
|
485
|
-
This utility is convention-specific rather than a general filesystem indexer.
|
|
747
|
+
This utility is convention-specific rather than a general-purpose filesystem indexer.
|
|
748
|
+
|
|
749
|
+
---
|
|
486
750
|
|
|
487
751
|
## Lazy loading and optional dependencies
|
|
488
752
|
|
|
489
|
-
CMPL is designed so
|
|
753
|
+
CMPL is designed so unrelated optional packages are not imported simply because the top-level package is imported.
|
|
490
754
|
|
|
491
755
|
For example:
|
|
492
756
|
|
|
@@ -494,9 +758,13 @@ For example:
|
|
|
494
758
|
import cmpl
|
|
495
759
|
```
|
|
496
760
|
|
|
497
|
-
does not immediately
|
|
761
|
+
does not immediately import PyTorch, Matplotlib, pandas, nibabel, pydicom, SimpleITK, h5py, or the Jupyter visualization stack.
|
|
762
|
+
|
|
763
|
+
Optional functionality is loaded only when the corresponding module or function is accessed.
|
|
498
764
|
|
|
499
|
-
|
|
765
|
+
Within quantitative MRI, Matplotlib is also loaded lazily: non-plotting T2* workflows do not require Matplotlib.
|
|
766
|
+
|
|
767
|
+
---
|
|
500
768
|
|
|
501
769
|
## Development
|
|
502
770
|
|
|
@@ -506,40 +774,68 @@ Clone the project and install it in editable mode with development dependencies:
|
|
|
506
774
|
python -m pip install -e ".[dev]"
|
|
507
775
|
```
|
|
508
776
|
|
|
509
|
-
For development across the
|
|
777
|
+
For development across the main optional feature groups:
|
|
510
778
|
|
|
511
779
|
```bash
|
|
512
780
|
python -m pip install -e ".[dev,io,data,viz,torch]"
|
|
513
781
|
```
|
|
514
782
|
|
|
515
|
-
Run the test suite:
|
|
783
|
+
Run the full test suite:
|
|
516
784
|
|
|
517
785
|
```bash
|
|
518
786
|
python -m pytest tests/ -v
|
|
519
787
|
```
|
|
520
788
|
|
|
521
|
-
The test suite
|
|
789
|
+
The test suite covers:
|
|
522
790
|
|
|
523
|
-
- lazy-import and dependency-boundary
|
|
524
|
-
- qMRI numerical
|
|
525
|
-
-
|
|
791
|
+
- lazy-import and dependency-boundary behavior
|
|
792
|
+
- qMRI numerical fitting
|
|
793
|
+
- T2* CLI validation and NIfTI output
|
|
794
|
+
- GRAPPA and SENSE reconstruction
|
|
526
795
|
- visualization smoke tests
|
|
527
|
-
- synthetic NIfTI, DICOM, and SimpleITK I/O
|
|
528
|
-
-
|
|
796
|
+
- synthetic NIfTI, DICOM, and SimpleITK I/O
|
|
797
|
+
- conventional and Enhanced multi-echo DICOM-to-NIfTI conversion
|
|
798
|
+
- JSON sidecar generation
|
|
799
|
+
- DICOM geometry and metadata
|
|
800
|
+
- data indexing
|
|
529
801
|
|
|
530
802
|
### Build the package
|
|
531
803
|
|
|
804
|
+
Build distributions:
|
|
805
|
+
|
|
532
806
|
```bash
|
|
807
|
+
rm -rf build dist *.egg-info
|
|
533
808
|
python -m build
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Validate them:
|
|
812
|
+
|
|
813
|
+
```bash
|
|
534
814
|
python -m twine check dist/*
|
|
535
815
|
```
|
|
536
816
|
|
|
817
|
+
Before publishing, it is also useful to install the built wheel into a clean environment and verify the packaged CLI commands:
|
|
818
|
+
|
|
819
|
+
```bash
|
|
820
|
+
cmpl-dicom-to-nifti --help
|
|
821
|
+
cmpl-t2star --help
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
537
826
|
## Package layout
|
|
538
827
|
|
|
539
828
|
```text
|
|
540
829
|
src/cmpl/
|
|
830
|
+
├── _version.py
|
|
831
|
+
├── cli/
|
|
832
|
+
│ ├── __init__.py
|
|
833
|
+
│ ├── dicom_to_nifti.py
|
|
834
|
+
│ └── t2star.py
|
|
541
835
|
├── dicom/
|
|
542
|
-
│
|
|
836
|
+
│ ├── enhanced_dicom.py
|
|
837
|
+
│ ├── geometry.py
|
|
838
|
+
│ └── metadata.py
|
|
543
839
|
├── quantitative_MRI/
|
|
544
840
|
│ └── mapping.py
|
|
545
841
|
├── reconstruction/
|
|
@@ -558,6 +854,8 @@ src/cmpl/
|
|
|
558
854
|
└── visualization.py
|
|
559
855
|
```
|
|
560
856
|
|
|
857
|
+
---
|
|
858
|
+
|
|
561
859
|
## License
|
|
562
860
|
|
|
563
861
|
See the `LICENSE` file included with the project for licensing terms.
|