mri-normalization-tools 0.4.1__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 (50) hide show
  1. mri_normalization_tools-0.4.1/LICENSE +21 -0
  2. mri_normalization_tools-0.4.1/PKG-INFO +372 -0
  3. mri_normalization_tools-0.4.1/README.md +328 -0
  4. mri_normalization_tools-0.4.1/mnts/__init__.py +9 -0
  5. mri_normalization_tools-0.4.1/mnts/filters/__init__.py +9 -0
  6. mri_normalization_tools-0.4.1/mnts/filters/data_node.py +119 -0
  7. mri_normalization_tools-0.4.1/mnts/filters/geom/__init__.py +3 -0
  8. mri_normalization_tools-0.4.1/mnts/filters/geom/geom_mask_crop.py +276 -0
  9. mri_normalization_tools-0.4.1/mnts/filters/geom/reorient_filter.py +49 -0
  10. mri_normalization_tools-0.4.1/mnts/filters/geom/spatial_norm.py +89 -0
  11. mri_normalization_tools-0.4.1/mnts/filters/intensity/__init__.py +7 -0
  12. mri_normalization_tools-0.4.1/mnts/filters/intensity/hist_piecewise.py +498 -0
  13. mri_normalization_tools-0.4.1/mnts/filters/intensity/in_wrapper.py +19 -0
  14. mri_normalization_tools-0.4.1/mnts/filters/intensity/intensity_base.py +52 -0
  15. mri_normalization_tools-0.4.1/mnts/filters/intensity/linear_rescale.py +75 -0
  16. mri_normalization_tools-0.4.1/mnts/filters/intensity/linear_z_score.py +45 -0
  17. mri_normalization_tools-0.4.1/mnts/filters/intensity/masking.py +167 -0
  18. mri_normalization_tools-0.4.1/mnts/filters/intensity/n4_bias_field_correction.py +97 -0
  19. mri_normalization_tools-0.4.1/mnts/filters/intensity/range_rescale.py +87 -0
  20. mri_normalization_tools-0.4.1/mnts/filters/intensity/si_rebinning.py +65 -0
  21. mri_normalization_tools-0.4.1/mnts/filters/mnts_filters.py +112 -0
  22. mri_normalization_tools-0.4.1/mnts/filters/mnts_filters_graph.py +737 -0
  23. mri_normalization_tools-0.4.1/mnts/filters/mpi_wrapper.py +37 -0
  24. mri_normalization_tools-0.4.1/mnts/io/__init__.py +1 -0
  25. mri_normalization_tools-0.4.1/mnts/io/data_formatting.py +737 -0
  26. mri_normalization_tools-0.4.1/mnts/io/dixon.py +93 -0
  27. mri_normalization_tools-0.4.1/mnts/mnts_logger.py +688 -0
  28. mri_normalization_tools-0.4.1/mnts/scripts/__init__.py +3 -0
  29. mri_normalization_tools-0.4.1/mnts/scripts/console_entry.py +39 -0
  30. mri_normalization_tools-0.4.1/mnts/scripts/dicom2nii.py +140 -0
  31. mri_normalization_tools-0.4.1/mnts/scripts/dicom_anon.py +10 -0
  32. mri_normalization_tools-0.4.1/mnts/scripts/dicom_tag_printer.py +377 -0
  33. mri_normalization_tools-0.4.1/mnts/scripts/normalization.py +188 -0
  34. mri_normalization_tools-0.4.1/mnts/scripts/organize_nifti.py +162 -0
  35. mri_normalization_tools-0.4.1/mnts/utils/__init__.py +4 -0
  36. mri_normalization_tools-0.4.1/mnts/utils/dcm_anonymize.py +125 -0
  37. mri_normalization_tools-0.4.1/mnts/utils/dicom_tag_printer.py +1532 -0
  38. mri_normalization_tools-0.4.1/mnts/utils/filename_globber.py +245 -0
  39. mri_normalization_tools-0.4.1/mnts/utils/histogram_analysis.py +149 -0
  40. mri_normalization_tools-0.4.1/mnts/utils/preprocessing.py +194 -0
  41. mri_normalization_tools-0.4.1/mnts/utils/sequence_check.py +209 -0
  42. mri_normalization_tools-0.4.1/mnts/utils/utils.py +184 -0
  43. mri_normalization_tools-0.4.1/mri_normalization_tools.egg-info/PKG-INFO +372 -0
  44. mri_normalization_tools-0.4.1/mri_normalization_tools.egg-info/SOURCES.txt +49 -0
  45. mri_normalization_tools-0.4.1/mri_normalization_tools.egg-info/dependency_links.txt +1 -0
  46. mri_normalization_tools-0.4.1/mri_normalization_tools.egg-info/entry_points.txt +6 -0
  47. mri_normalization_tools-0.4.1/mri_normalization_tools.egg-info/requires.txt +14 -0
  48. mri_normalization_tools-0.4.1/mri_normalization_tools.egg-info/top_level.txt +1 -0
  49. mri_normalization_tools-0.4.1/setup.cfg +69 -0
  50. mri_normalization_tools-0.4.1/setup.py +3 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Alabamagan
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,372 @@
1
+ Metadata-Version: 2.4
2
+ Name: mri-normalization-tools
3
+ Version: 0.4.1
4
+ Summary: A pacakge dedicated for normalization and processing of MRI images.
5
+ Home-page:
6
+ Author: ML, Wong
7
+ Author-email: mat.lun.wong@gmail.com
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
25
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.7
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: SimpleITK<3,>=2.1.0
31
+ Requires-Dist: networkx<4,>=2.5
32
+ Requires-Dist: cachetools<6,>=4.2.2
33
+ Requires-Dist: netgraph<5,>=4.0.5
34
+ Requires-Dist: pyyaml>=5.3.1
35
+ Requires-Dist: tqdm>=4.0.0
36
+ Requires-Dist: pandas<3,>=1.5.0
37
+ Requires-Dist: rich<15,>=13.0
38
+ Requires-Dist: rich-tools>=0.5.1
39
+ Requires-Dist: click<9,>=8.0.0
40
+ Provides-Extra: dicom
41
+ Requires-Dist: pydicom; extra == "dicom"
42
+ Requires-Dist: pydicom-seg; extra == "dicom"
43
+ Dynamic: license-file
44
+
45
+ # MRI Normalization Tools
46
+
47
+ [![Python Version](https://img.shields.io/badge/python-3.7%2B-blue.svg)](https://www.python.org/downloads/)
48
+ [![PyPI version](https://badge.fury.io/py/mri-normalization-tools.svg)](https://badge.fury.io/py/mri-normalization-tools)
49
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
50
+ [![GitHub issues](https://img.shields.io/github/issues/alabamagan/mri_normalization_tools.svg)](https://github.com/alabamagan/mri_normalization_tools/issues)
51
+ [![GitHub stars](https://img.shields.io/github/stars/alabamagan/mri_normalization_tools.svg)](https://github.com/alabamagan/mri_normalization_tools/stargazers)
52
+
53
+ ## Introduction
54
+
55
+ Quantitative analysis of MRI is complicated, often with a specific set of steps that are complicated and cannot be
56
+ easily reproduced. This project aims to allow one-click reproducibility based on a config file.
57
+
58
+ ## Features
59
+
60
+ - **Bias Field Correction**: N4ITK bias field correction for improved image quality
61
+ - **Spatial Normalization**: Resampling and orientation correction
62
+ - **Intensity Normalization**: Multiple algorithms including Nyul, Z-score, and histogram matching
63
+ - **Graph-based Pipeline**: Flexible filter chaining with automatic dependency management
64
+ - **Training Support**: Built-in training workflows for normalization algorithms requiring training
65
+ - **MPI Support**: Parallel processing capabilities for large datasets
66
+ - **YAML Configuration**: Define normalization pipelines using YAML files
67
+ - **Console Interface**: Command-line tools for training and inference workflows
68
+
69
+ ## Key Functions
70
+
71
+ This repo aims to maximize the repeatability of the image normalization pipeline, with a focus of MRI. Normalization
72
+ generally consist of the following steps:
73
+
74
+ 1. Bias field correction
75
+ 2. Align image spacing
76
+ 3. Outlier removal
77
+ 4. Intensity normalization
78
+ 5. Binning
79
+
80
+ # Requirements
81
+
82
+ - SimpleITK >= 2.1.0
83
+ - networkx >= 2.5
84
+ - decorator >= 5.0.7
85
+ - cachetools >=4.2.2
86
+ - netgraph >= 0.7.0
87
+
88
+ # Installation
89
+
90
+ ## PyPI Installation
91
+
92
+ ```bash
93
+ pip install mri-normalization-tools
94
+
95
+ # OR, if you need to run scripts like dcm2nii
96
+ pip install mri-normalization-tools[pydicom]
97
+ ```
98
+
99
+ ## Development branch Installation
100
+
101
+ ```bash
102
+ git clone https://github.com/alabamagan/mri_normalization_tools.git
103
+ cd mri_normalization_tools
104
+ pip install -e .
105
+
106
+ # perform unittest
107
+ pip install pytest
108
+ cd mri_normalization_tools/
109
+ pytest unit_test/
110
+ ```
111
+
112
+ ## Quick Start
113
+
114
+ ```python
115
+ from mnts.filters.geom import SpatialNorm
116
+ from mnts.filters.intensity import N4ITKBiasFieldCorrection, NyulNormalizer
117
+ from mnts.filters.mnts_filters_graph import MNTSFilterGraph
118
+
119
+ # Create normalization graph
120
+ G = MNTSFilterGraph()
121
+ G.add_node(SpatialNorm(out_spacing=[1, 1, 0]))
122
+ G.add_node(N4ITKBiasFieldCorrection(), [0])
123
+ G.add_node(NyulNormalizer(), [1], is_exit=True)
124
+
125
+ # Process an image
126
+ result = G.execute("path/to/your/image.nii.gz")
127
+ ```
128
+
129
+ # Examples
130
+
131
+ ## General Example
132
+
133
+ ![Graph](./img/05_graph.png)
134
+ Caption: Green node is the input node, blue node is the output node.
135
+
136
+ ```python
137
+ from pathlib import Path
138
+ from mnts.filters.geom import *
139
+ from mnts.filters.intensity import *
140
+ from mnts.filters.mnts_filters_graph import MNTSFilterGraph
141
+ import matplotlib.pyplot as plt
142
+ import SimpleITK as sitk
143
+
144
+ from mnts.utils import repeat_zip
145
+ from mnts.filters import mpi_wrapper
146
+ from mnts.filters.intensity import NyulNormalizer
147
+
148
+ import pprint
149
+
150
+ # If this protector is absent, windows python might go into recursive import loop.
151
+ if __name__ == '__main__':
152
+ # Create the normalization graph.
153
+ G = MNTSFilterGraph()
154
+
155
+ # Add filter nodes to the graph.
156
+ G.add_node(SpatialNorm(out_spacing=[1, 1, 0]))
157
+ G.add_node(OtsuThresholding(), 0) # Use mask to better match teh histograms
158
+ G.add_node(N4ITKBiasFieldCorrection(), [0, 1])
159
+ G.add_node(NyulNormalizer(), [2, 1])
160
+ G.add_node(RangeRescale(0, 5000), 3, is_exit=True)
161
+ G.add_node(SignalIntensityRebinning(num_of_bins=256), 3, is_exist=True)
162
+
163
+ # Plot the graph
164
+ G.plot_graph()
165
+ plt.show()
166
+
167
+ # Borrow the trained features, please run example 04 if this reports error.
168
+ state_path = Path(r'./example_data/output/.EG_04_temp/EG_04_States/2_NyulNormalizer.npz')
169
+ G.load_node_states(3, state_path) # 3 for NyulNormalizer node index
170
+
171
+ # Write output images
172
+ image_folder = Path(r'./example_data')
173
+ images = [f for f in image_folder.iterdir() if f.name.find('nii') != -1]
174
+ output_save_dir = Path(r'./example_data/output/EG_05')
175
+ output_save_dir.mkdir(parents=True, exist_ok=True)
176
+ for im in images:
177
+ save_im = G.execute(im)
178
+ fname = output_save_dir.joinpath(im.name).resolve().__str__()
179
+ print(f"Saving to {fname}")
180
+ sitk.WriteImage(save_im[4], fname) # RangeRescale output at node index 3
181
+ ```
182
+
183
+ ## Using normalization graph API
184
+
185
+ Some normalization method require training. For example, most piecewise linear intensity normalization algorithm requries establishing feature points on a graph prior to usage. This package offers API for training these nodes.
186
+
187
+ ### Identifying nodes that require training
188
+
189
+ For nodes that requires training, it would be a child class of `MNTSFilterRequireTraining`. You can identify this by using `isinstance(node, MNTSFilterRequireTraining)`.
190
+
191
+ ### Training example
192
+
193
+ You can see [example 4](./examples/EG04_using_filters_that_require_train.py) for a more detailed implementation of how to build and train a normalization graph that requires training.
194
+
195
+ ```python
196
+ from mnts.filters.mnts_filters_graph import MNTSFilterGraph
197
+ from mnts.utils import repeat_zip
198
+
199
+ G = MNTSFilterGraph("/path/to/graph")
200
+
201
+ # * Prepare the upstream data for nodes that require training
202
+ image_folder = Path("...")
203
+ temp_output_folder = Path("...")
204
+ images = [f for f in image_folder.iterdir() if f.name.find('nii') != -1]
205
+ out_names = [f.name for f in images]
206
+
207
+ # this prepares the data from nodes that does not require training and are upstream of node X
208
+ z = ([X], out_names, [temp_output_folder], images)
209
+ for args in repeat_zip(*z):
210
+ G.prepare_training_files(*args)
211
+
212
+ # Train node number X
213
+ G.train_node(X, temp_output_folder, temp_output_folder.joinpath("trained_states"))
214
+ ```
215
+
216
+ ### Inference Example
217
+
218
+ ```python
219
+ from mnts.filters.mnts_filters_graph import MNTSFilterGraph
220
+ from mnts.utils import repeat_zip
221
+
222
+ G = MNTSFilterGraph("/path/to/graph")
223
+ output_save_dir = Path(r'./example_data/output/EG_04')
224
+ output_save_dir.mkdir(parents=True, exist_ok=True)
225
+
226
+ G.load_node_states(2, temp_output_folder.joinpath("trained_states"))
227
+ for im in images:
228
+ save_im = G.execute(im)
229
+ fname = output_save_dir.joinpath(im.name).resolve().__str__()
230
+ print(f"Saving to {fname}")
231
+ sitk.WriteImage(save_im[3], fname)
232
+
233
+ ```
234
+
235
+ ## Creating graph from yaml file
236
+
237
+ ### Example YAML file
238
+
239
+ ![Img](./img/07_graph.png)
240
+
241
+ ```yaml
242
+ SpatialNorm: # This layer should have the same name as the filter name
243
+ out_spacing: [0.5, 0.5, 0] # All kwargs arguments can be specified in this format
244
+
245
+ HuangThresholding:
246
+ closing_kernel_size: 10
247
+ _ext: # The argument of the method MNTSFilterGraph.add_node(), must be specified with _ext key
248
+ upstream: 0 # Keyword upstream is also necessary, otherwise, the node will be see as an input node.
249
+ is_exit: True
250
+
251
+ N4ITKBiasFieldCorrection:
252
+ _ext:
253
+ upstream: [0, 1]
254
+
255
+ NyulNormalizer:
256
+ _ext:
257
+ upstream: [2, 1]
258
+ is_exit: True
259
+ ```
260
+
261
+ ### Python script
262
+
263
+ ```python
264
+ from pathlib import Path
265
+ from mnts.filters.mnts_filters_graph import MNTSFilterGraph
266
+
267
+ yaml_file = '_test_graph.yaml'
268
+
269
+ if __name__ == '__main__':
270
+ G = MNTSFilterGraph.CreateGraphFromYAML('_test_graph.yaml')
271
+ print(G)
272
+ Path('default.log').unlink() # Remove useless log file
273
+ ```
274
+
275
+ ### Utility scripts
276
+
277
+ #### `mnts-dicom2nii` — DICOM → NIfTI conversion
278
+
279
+ ```bash
280
+ mnts-dicom2nii -i /data/raw -o /data/nifti --use-top-level-fname
281
+ mnts-dicom2nii -i /data/raw -o /data/nifti -g '[A-Z]{2}[0-9]{4}' # ID from path regex
282
+ mnts-dicom2nii -i /data/raw -o /data/nifti --idlist "PT001, PT002" # subset of subjects
283
+ mnts-dicom2nii -i /data/raw -o /data/nifti --check-image-type-tag # DIXON scans
284
+ mnts-dicom2nii -i /data/raw -o /data/nifti --add-scan-time # multiple sessions
285
+ ```
286
+
287
+ #### `mnts-dcm-tagprint` — print DICOM tags to table / CSV / Excel / SQLite
288
+
289
+ ```bash
290
+ mnts-dcm-tagprint /data/raw -t 0008|103e # series description
291
+ mnts-dcm-tagprint /data/raw -t default # common tag preset
292
+ mnts-dcm-tagprint /data/raw -t mri # full MRI parameters
293
+ mnts-dcm-tagprint /data/raw -t default -f csv -o tags.csv
294
+ mnts-dcm-tagprint /data/raw -t default -f sqlite -o study.db -c Cohort_A
295
+ ```
296
+
297
+ Common tags: `0008|103e` Series Description · `0010|0020` Patient ID · `0008|0020` Study Date ·
298
+ `0018|0080` TR · `0018|0081` TE · `0018|0087` Field Strength
299
+
300
+ #### `mnts-organize` — sort NIfTI files into per-modality subdirectories
301
+
302
+ Expects filenames like `PT001-T1+001_tra.nii.gz` (`PatientID-Modality+SeqID`).
303
+
304
+ ```bash
305
+ mnts-organize /data/nifti # in-place
306
+ mnts-organize /data/nifti --target-dir /data/organized
307
+ mnts-organize /data/nifti --dry-run # preview only
308
+ ```
309
+
310
+ # TODO
311
+
312
+ - [X] Training required filters
313
+ - [X] Intensity normalization ignores segmentation (UInt8 image won't be processed, might need `force` option?)
314
+ - [ ] Image registration
315
+ - [X] Graph label the filter names
316
+ - [X] Overflow protection for some function
317
+ - [X] MRI bias field correction
318
+ - [ ] Support processing labels together with images (for spatial operations only)
319
+ - [X] Finish pipeline implementation
320
+ - [X] MPI examples
321
+ - [X] Better documents for usage of dicom2nii
322
+ - [ ] Better document for scripts
323
+ - [ ] Incorporate Bash-based steps
324
+ - [ ] Add version and version check for saving graphs
325
+
326
+ # Example Data
327
+
328
+ The example data was obtained through the openneuro initiative, accessed [here](https://openneuro.org/datasets/ds000105/versions/00001) [1-3]. The data was not matched with any diagnosis or pathology here. A subset of T1-weighted images were extracted from the original public domain data, which were renamed into the followings:
329
+
330
+ ```
331
+ .
332
+ └── examples/
333
+ └── example_data/
334
+ ├── MRI_01.nii.gz
335
+ ├── MRI_02.nii.gz
336
+ └── MRI_03.nii.gz
337
+ ```
338
+
339
+ ## Reference
340
+
341
+ [1] Haxby, J.V., Gobbini, M.I., Furey, M.L., Ishai, A., Schouten, J.L.,Pietrini, P. (2001). Distributed and overlapping representations of faces and objects in ventral temporal cortex. Science, 293(5539):2425-30
342
+
343
+ [2] Hanson, S.J., Matsuka, T., Haxby, J.V. (2004). Combinatorial codes in ventral temporal lobe for object recognition: Haxby (2001) revisited: is there a "face" area? Neuroimage. 23(1):156-66 O'Toole, A.J., Jiang, F.,
344
+
345
+ [3] Abdi, H., Haxby, J.V. (2005). Partially distributed representations of objects and faces in ventral temporal cortex. J Cogn Neurosci, 17(4):580-90
346
+
347
+ ## License of usage
348
+
349
+ ### This repo
350
+
351
+ MIT License
352
+
353
+ ### Unit test data
354
+
355
+ This dataset is made available under the Public Domain Dedication and License v1.0, whose full text can be found at
356
+ [http://www.opendatacommons.org/licenses/pddl/1.0/](http://www.opendatacommons.org/licenses/pddl/1.0/). We hope that all users will follow the ODC
357
+ Attribution/Share-Alike Community Norms ([http://www.opendatacommons.org/norms/odc-by-sa/](http://www.opendatacommons.org/norms/odc-by-sa/)); in particular, while
358
+ not legally required, we hope that all users of the data will acknowledge the OpenfMRI project and NSF Grant OCI-1131441
359
+ (R. Poldrack, PI) in any publications.
360
+
361
+ To acquire the dataset, run `cd uni_test; python download_sample_data.py`. This will download both the dataset for
362
+ unittest and the dataset for examples from openneuro.
363
+
364
+ #### NIfTI sample
365
+
366
+ The NIfTI sample file (`unit_test/sample_data/nifti/example4d.nii.gz`) is taken from the [nibabel](https://github.com/nipy/nibabel) test suite and is distributed under the MIT License.
367
+
368
+ #### DICOM sample
369
+
370
+ The DICOM sample series (`unit_test/sample_data/sample1/`) is derived from `MR2_J2KI.dcm`, part of the [pydicom-data](https://github.com/pydicom/pydicom-data) repository and distributed under the MIT License.
371
+
372
+ Run `python unit_test/download_sample_data.py` to download all sample data before executing the unit tests.