qradiomics 0.9.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 (104) hide show
  1. qradiomics-0.9.0/LICENSE +21 -0
  2. qradiomics-0.9.0/PKG-INFO +585 -0
  3. qradiomics-0.9.0/README.md +532 -0
  4. qradiomics-0.9.0/pyproject.toml +83 -0
  5. qradiomics-0.9.0/qradiomics/__init__.py +13 -0
  6. qradiomics-0.9.0/qradiomics/atomic/__init__.py +34 -0
  7. qradiomics-0.9.0/qradiomics/atomic/features.py +80 -0
  8. qradiomics-0.9.0/qradiomics/atomic/hu_correct.py +64 -0
  9. qradiomics-0.9.0/qradiomics/atomic/io.py +77 -0
  10. qradiomics-0.9.0/qradiomics/atomic/preprocess.py +118 -0
  11. qradiomics-0.9.0/qradiomics/atomic/registration.py +159 -0
  12. qradiomics-0.9.0/qradiomics/cli/__init__.py +5 -0
  13. qradiomics-0.9.0/qradiomics/cli/commands/__init__.py +22 -0
  14. qradiomics-0.9.0/qradiomics/cli/commands/analyze.py +387 -0
  15. qradiomics-0.9.0/qradiomics/cli/commands/anonymize.py +220 -0
  16. qradiomics-0.9.0/qradiomics/cli/commands/config_cmd.py +73 -0
  17. qradiomics-0.9.0/qradiomics/cli/commands/convert.py +374 -0
  18. qradiomics-0.9.0/qradiomics/cli/commands/delta.py +100 -0
  19. qradiomics-0.9.0/qradiomics/cli/commands/extract.py +213 -0
  20. qradiomics-0.9.0/qradiomics/cli/commands/hu_correct.py +48 -0
  21. qradiomics-0.9.0/qradiomics/cli/commands/lidc.py +117 -0
  22. qradiomics-0.9.0/qradiomics/cli/commands/ml.py +411 -0
  23. qradiomics-0.9.0/qradiomics/cli/commands/pacs.py +606 -0
  24. qradiomics-0.9.0/qradiomics/cli/commands/preprocess.py +149 -0
  25. qradiomics-0.9.0/qradiomics/cli/commands/register.py +79 -0
  26. qradiomics-0.9.0/qradiomics/cli/commands/results.py +109 -0
  27. qradiomics-0.9.0/qradiomics/cli/commands/shape.py +119 -0
  28. qradiomics-0.9.0/qradiomics/cli/commands/tcia.py +363 -0
  29. qradiomics-0.9.0/qradiomics/cli/commands/workflow.py +188 -0
  30. qradiomics-0.9.0/qradiomics/cli/config_io.py +49 -0
  31. qradiomics-0.9.0/qradiomics/cli/main.py +92 -0
  32. qradiomics-0.9.0/qradiomics/cli/pattern/__init__.py +6 -0
  33. qradiomics-0.9.0/qradiomics/cli/pattern/list.py +28 -0
  34. qradiomics-0.9.0/qradiomics/cli/pattern/match.py +39 -0
  35. qradiomics-0.9.0/qradiomics/data/pyradiomics/ct_default.yaml +26 -0
  36. qradiomics-0.9.0/qradiomics/data/pyradiomics/ct_original_only.yaml +12 -0
  37. qradiomics-0.9.0/qradiomics/data/pyradiomics/nsclc_ct.yaml +36 -0
  38. qradiomics-0.9.0/qradiomics/data/pyradiomics/pet_default.yaml +15 -0
  39. qradiomics-0.9.0/qradiomics/data/schema/pattern_schema.yaml +394 -0
  40. qradiomics-0.9.0/qradiomics/data/templates/ct_default.yaml +93 -0
  41. qradiomics-0.9.0/qradiomics/data/templates/nsclc_survival.yaml +192 -0
  42. qradiomics-0.9.0/qradiomics/data/templates/standard_radiomics.yaml +227 -0
  43. qradiomics-0.9.0/qradiomics/data/templates/survival_analysis.yaml +247 -0
  44. qradiomics-0.9.0/qradiomics/data_model/__init__.py +37 -0
  45. qradiomics-0.9.0/qradiomics/data_model/core.py +174 -0
  46. qradiomics-0.9.0/qradiomics/data_model/enums.py +39 -0
  47. qradiomics-0.9.0/qradiomics/data_model/io.py +180 -0
  48. qradiomics-0.9.0/qradiomics/delta/__init__.py +21 -0
  49. qradiomics-0.9.0/qradiomics/delta/compute.py +158 -0
  50. qradiomics-0.9.0/qradiomics/extractor.py +172 -0
  51. qradiomics-0.9.0/qradiomics/io/__init__.py +5 -0
  52. qradiomics-0.9.0/qradiomics/io/dicom/__init__.py +28 -0
  53. qradiomics-0.9.0/qradiomics/io/dicom/pet_suv.py +229 -0
  54. qradiomics-0.9.0/qradiomics/io/dicom/preamble.py +40 -0
  55. qradiomics-0.9.0/qradiomics/io/dicom/rtstruct.py +194 -0
  56. qradiomics-0.9.0/qradiomics/io/dicom/segmentations.py +134 -0
  57. qradiomics-0.9.0/qradiomics/io/dicom/series.py +77 -0
  58. qradiomics-0.9.0/qradiomics/io/lidc/__init__.py +47 -0
  59. qradiomics-0.9.0/qradiomics/io/lidc/extract.py +283 -0
  60. qradiomics-0.9.0/qradiomics/io/lidc/parse_xml.py +230 -0
  61. qradiomics-0.9.0/qradiomics/io/lidc/staple.py +91 -0
  62. qradiomics-0.9.0/qradiomics/io/pacs/__init__.py +36 -0
  63. qradiomics-0.9.0/qradiomics/io/pacs/base.py +157 -0
  64. qradiomics-0.9.0/qradiomics/io/pacs/config.py +142 -0
  65. qradiomics-0.9.0/qradiomics/io/pacs/dicomweb.py +304 -0
  66. qradiomics-0.9.0/qradiomics/io/pacs/dimse.py +375 -0
  67. qradiomics-0.9.0/qradiomics/io/pacs/factory.py +58 -0
  68. qradiomics-0.9.0/qradiomics/io/pacs/orthanc.py +361 -0
  69. qradiomics-0.9.0/qradiomics/manifest/__init__.py +20 -0
  70. qradiomics-0.9.0/qradiomics/manifest/io.py +159 -0
  71. qradiomics-0.9.0/qradiomics/pattern_loader.py +869 -0
  72. qradiomics-0.9.0/qradiomics/shape/__init__.py +62 -0
  73. qradiomics-0.9.0/qradiomics/shape/ahsn.py +112 -0
  74. qradiomics-0.9.0/qradiomics/shape/detection.py +110 -0
  75. qradiomics-0.9.0/qradiomics/shape/hessian.py +101 -0
  76. qradiomics-0.9.0/qradiomics/shape/mesh_utils.py +133 -0
  77. qradiomics-0.9.0/qradiomics/shape/phantoms.py +568 -0
  78. qradiomics-0.9.0/qradiomics/shape/spiculation.py +390 -0
  79. qradiomics-0.9.0/qradiomics/shape/wall_elim.py +62 -0
  80. qradiomics-0.9.0/qradiomics/verification/__init__.py +59 -0
  81. qradiomics-0.9.0/qradiomics/verification/harness.py +192 -0
  82. qradiomics-0.9.0/qradiomics/verification/image_parity.py +111 -0
  83. qradiomics-0.9.0/qradiomics/workflow.py +768 -0
  84. qradiomics-0.9.0/qradiomics.egg-info/PKG-INFO +585 -0
  85. qradiomics-0.9.0/qradiomics.egg-info/SOURCES.txt +102 -0
  86. qradiomics-0.9.0/qradiomics.egg-info/dependency_links.txt +1 -0
  87. qradiomics-0.9.0/qradiomics.egg-info/entry_points.txt +4 -0
  88. qradiomics-0.9.0/qradiomics.egg-info/requires.txt +30 -0
  89. qradiomics-0.9.0/qradiomics.egg-info/top_level.txt +1 -0
  90. qradiomics-0.9.0/setup.cfg +4 -0
  91. qradiomics-0.9.0/tests/test_analyze_classify.py +136 -0
  92. qradiomics-0.9.0/tests/test_analyze_importance.py +217 -0
  93. qradiomics-0.9.0/tests/test_analyze_survival.py +97 -0
  94. qradiomics-0.9.0/tests/test_atomic.py +204 -0
  95. qradiomics-0.9.0/tests/test_data_model.py +171 -0
  96. qradiomics-0.9.0/tests/test_delta.py +99 -0
  97. qradiomics-0.9.0/tests/test_image_parity.py +132 -0
  98. qradiomics-0.9.0/tests/test_lidc.py +249 -0
  99. qradiomics-0.9.0/tests/test_pacs.py +442 -0
  100. qradiomics-0.9.0/tests/test_parity_golden.py +70 -0
  101. qradiomics-0.9.0/tests/test_pet_suv.py +104 -0
  102. qradiomics-0.9.0/tests/test_preamble.py +82 -0
  103. qradiomics-0.9.0/tests/test_results_merge.py +137 -0
  104. qradiomics-0.9.0/tests/test_verification_harness.py +87 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Choi Lab, Thomas Jefferson University
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,585 @@
1
+ Metadata-Version: 2.4
2
+ Name: qradiomics
3
+ Version: 0.9.0
4
+ Summary: Radiomics research CLI — extract, merge, analyze in one Unix-style pipeline.
5
+ Author: Pradeep Bhetwal
6
+ Author-email: Wookjin Choi <wookjin.choi@jefferson.edu>
7
+ License: MIT
8
+ Project-URL: Homepage, https://qradiomics.com/
9
+ Project-URL: Repository, https://github.com/choilab-jefferson/qradiomics
10
+ Project-URL: Documentation, https://qradiomics.com/
11
+ Project-URL: Bug Tracker, https://github.com/choilab-jefferson/qradiomics/issues
12
+ Keywords: radiomics,pyradiomics,cli,tcia,lung-cancer,medical-imaging
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Healthcare Industry
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: click>=8.0.0
27
+ Requires-Dist: httpx>=0.24.0
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: pydantic>=2.0.0
30
+ Requires-Dist: rich>=13.0.0
31
+ Requires-Dist: pyradiomics>=3.1.0
32
+ Requires-Dist: pydicom>=2.4
33
+ Requires-Dist: SimpleITK>=2.2.0
34
+ Requires-Dist: numpy>=1.24.0
35
+ Requires-Dist: pandas>=1.5.0
36
+ Requires-Dist: scikit-learn>=1.2.0
37
+ Requires-Dist: scikit-image>=0.21.0
38
+ Requires-Dist: lifelines>=0.27.0
39
+ Requires-Dist: statsmodels>=0.14.0
40
+ Requires-Dist: scipy>=1.10.0
41
+ Provides-Extra: shap
42
+ Requires-Dist: shap>=0.41.0; extra == "shap"
43
+ Provides-Extra: rtstruct
44
+ Requires-Dist: rt-utils>=1.2.7; extra == "rtstruct"
45
+ Provides-Extra: prefect
46
+ Requires-Dist: prefect>=2.10; extra == "prefect"
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
49
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
50
+ Requires-Dist: black>=23.0.0; extra == "dev"
51
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
52
+ Dynamic: license-file
53
+
54
+ # qradiomics
55
+
56
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
57
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
58
+ [![Version](https://img.shields.io/badge/version-0.9.0-orange.svg)](https://github.com/choilab-jefferson/qradiomics/releases)
59
+
60
+ ## Kick-off
61
+
62
+ Single backend (`scripts/kickoff.sh`) for both flows. It clones the
63
+ repo (if not already), creates a `.venv`, `pip install -e .`, runs
64
+ `qr info`, and runs the smoke tests.
65
+
66
+ **For users — one-liner:**
67
+
68
+ ```bash
69
+ curl -sSL https://raw.githubusercontent.com/choilab-jefferson/qradiomics/main/scripts/kickoff.sh | bash
70
+ ```
71
+
72
+ **For AI coding agents — drop-in prompt:**
73
+
74
+ > You are working on qradiomics, a lung-radiomics CLI toolkit at
75
+ > `github.com/choilab-jefferson/qradiomics`. Do this:
76
+ >
77
+ > 1. Run the kick-off (clones, installs, verifies):
78
+ > ```
79
+ > curl -sSL https://raw.githubusercontent.com/choilab-jefferson/qradiomics/main/scripts/kickoff.sh | bash
80
+ > ```
81
+ > 2. `cd qradiomics` and read `AGENTS.md` for operations (conventions,
82
+ > layout, don'ts, citations). Do **not** log PHI, do **not** push
83
+ > to remotes, do **not** invent new public dataset URLs — use only
84
+ > the ones documented in AGENTS.md.
85
+ > 3. Ask the user what they want to build.
86
+
87
+ Env knobs accepted by the script: `QR_REPO_URL`, `QR_REPO_DIR`,
88
+ `QR_BRANCH`, `QR_PYTHON`, `QR_VENV` (set to `-` to skip the venv),
89
+ `QR_SKIP_SMOKE=1` to skip pytest.
90
+
91
+ ---
92
+
93
+ > **Active successor for three earlier Choi Lab radiomics codebases.** The
94
+ > C++/MATLAB pipelines in
95
+ > [taznux/radiomics-tools](https://github.com/taznux/radiomics-tools),
96
+ > [taznux/lung-image-analysis](https://github.com/taznux/lung-image-analysis), and
97
+ > [choilab-jefferson/LungCancerScreeningRadiomics](https://github.com/choilab-jefferson/LungCancerScreeningRadiomics)
98
+ > are **superseded** by this repo. The feature extractors are now in
99
+ > `qradiomics.feature.rtools` (Python ITK port, numerically exact to the
100
+ > C++ binary). New work should land here.
101
+
102
+ **Radiomics research CLI.** `qr` does two things equally well:
103
+
104
+ 1. **Atomic tasks** — convert DICOM, extract features, merge clinical, fit
105
+ a model. Each is a single command, files in / files out.
106
+ 2. **Workflow assembly** — generate, mutate, scaffold, and run multi-step
107
+ pipelines from those atomic tasks. Default executor is **Nextflow**
108
+ (per-patient parallel + cache + HPC); **Prefect** is the secondary
109
+ executor; `inline` is the small-cohort fallback.
110
+
111
+ The canonical radiomics data flow has four stages — `data → image →
112
+ features → modeling` — and one `qr workflow plan` call instantiates the
113
+ whole chain:
114
+
115
+ ```bash
116
+ # Atomic tasks
117
+ qr convert dicom-series / rtstruct / manifest-from-dir
118
+ qr extract -m manifest.csv -p <pattern> -o features.csv
119
+ qr results merge -f features.csv -c clinical.csv -o analysis_ready.csv
120
+ qr analyze {survival,classify,importance} -i analysis_ready.csv ...
121
+ qr ml {train,predict,evaluate} ...
122
+
123
+ # Workflow assembly
124
+ qr workflow plan -t dicom_to_ml -d <cohort> -c <clinical> -o plan.json
125
+ qr workflow scaffold -p plan.json -e nextflow -o pipeline.nf
126
+ qr workflow run plan.json --executor nextflow # default
127
+ ```
128
+
129
+ ## Background — three earlier projects, unified
130
+
131
+ `qradiomics` is the modern Python successor of three earlier Choi Lab
132
+ radiomics codebases. The MATLAB pipelines, the ITK / Ruffus C++ tools,
133
+ and the Docker-based screening workflow are distilled here into a single
134
+ Click CLI built on PyRadiomics, scikit-learn, and lifelines:
135
+
136
+ | Earlier project | Stack | Role | This repo |
137
+ |---|---|---|---|
138
+ | [taznux/lung-image-analysis](https://github.com/taznux/lung-image-analysis) | MATLAB · MIT | LIDC-IDRI nodule detection / segmentation / characterization | superseded |
139
+ | [taznux/radiomics-tools](https://github.com/taznux/radiomics-tools) | C++/Python (ITK, Ruffus) · MIT | DICOM tools, GrowCut segmentation, feature extraction pipeline | superseded |
140
+ | [choilab-jefferson/LungCancerScreeningRadiomics](https://github.com/choilab-jefferson/LungCancerScreeningRadiomics) | MATLAB / Python · GPL-3.0 | LIDC + LUNGx end-to-end screening workflow with AutoML | superseded (this repo re-implements the open subset under MIT using PyRadiomics) |
141
+
142
+ The AHSN shape descriptor pipeline (CMPB 2014) and the spiculation
143
+ quantification pipeline (CMPB 2021, companion to
144
+ [choilab-jefferson/CIR](https://github.com/choilab-jefferson/CIR))
145
+ are re-integrated in `qradiomics.shape` (see Shape Analysis below).
146
+ The longitudinal CBCT / delta-radiomics workflows (ASTRO / AAPM 2026)
147
+ will be released here after publication.
148
+
149
+ ## Install
150
+
151
+ ```bash
152
+ pip install -e . # core CLI + library
153
+ pip install -e .[rtstruct] # plus rt-utils for `qr convert rtstruct`
154
+ ```
155
+
156
+ Python 3.11 or newer is required. PyRadiomics, SimpleITK, lifelines,
157
+ scikit-learn, statsmodels, scipy, and pandas are pulled in as
158
+ dependencies.
159
+
160
+ After install, `qr`, `qradiomics`, and `qrdx` are available on `$PATH`
161
+ and point at `qradiomics.cli.main:cli`.
162
+
163
+ ## DICOM Conversion
164
+
165
+ Many TCIA cohorts ship as DICOM (CT/PET/MR series + RTSTRUCT). Two
166
+ helpers convert into the NRRD form the rest of the pipeline consumes:
167
+
168
+ ```bash
169
+ # 1. CT/PET/MR DICOM series → single NRRD volume
170
+ qr convert dicom-series \
171
+ -i <dataset_root>/<patient>/<study>/CT/ \
172
+ -o <out>/<patient>_CT.nrrd
173
+
174
+ # 2. RTSTRUCT contour → binary label NRRD (same geometry as the reference CT)
175
+ qr convert rtstruct \
176
+ -d <dataset_root>/<patient>/<study>/CT/ \
177
+ -r <dataset_root>/<patient>/<study>/RTSeries/RS.<uid>.dcm \
178
+ --roi GTV \
179
+ -o <out>/<patient>_GTV-label.nrrd
180
+
181
+ # 3. (Optional) build a manifest by globbing image/mask pairs in a tree
182
+ qr convert manifest-from-dir \
183
+ -d <out>/ \
184
+ --image-glob '*_CT.nrrd' \
185
+ --mask-glob '*-label.nrrd' \
186
+ -o manifest.csv
187
+ ```
188
+
189
+ RTSTRUCT conversion uses `rt-utils` (install via
190
+ `pip install qradiomics[rtstruct]`). ROI lookup is case-insensitive (a
191
+ `--roi Heart` request matches a structure set with `heart`). The mask
192
+ is auto-reshaped to the CT geometry, with a ±1-slice z-axis trim/pad
193
+ when the structure set references slices outside the series.
194
+
195
+ ## End-to-end Example — TCIA NSCLC-Radiomics (Lung1) from scratch
196
+
197
+ Starts from nothing — pulls DICOM straight from TCIA, converts, extracts,
198
+ joins clinical, and reports the Cox PH ranking. The same chain runs for
199
+ any TCIA collection by swapping the first argument.
200
+
201
+ ```bash
202
+ # 0. One-time: install + workspace
203
+ pip install -e .[rtstruct]
204
+ export USER_DATA=/data/$USER # ≥ 30 GB free for Lung1 (~422 patients)
205
+ mkdir -p $USER_DATA/{Lung1,Lung1-out}
206
+
207
+ # 1. DICOM pull from TCIA (CT + RTSTRUCT for the same series instance UIDs)
208
+ qr tcia download \
209
+ --collection NSCLC-Radiomics --modality CT \
210
+ -o $USER_DATA/Lung1 -j 16
211
+ qr tcia download \
212
+ --collection NSCLC-Radiomics --modality RTSTRUCT \
213
+ -o $USER_DATA/Lung1 -j 16
214
+
215
+ # 2. DICOM → NRRD per patient: CT volume + GTV-1 binary mask
216
+ for pat in $USER_DATA/Lung1/*/; do
217
+ pid=$(basename "$pat")
218
+ qr convert dicom-series \
219
+ -i "$pat"*/CT \
220
+ -o "$USER_DATA/Lung1-out/${pid}_CT.nrrd"
221
+ qr convert rtstruct \
222
+ -d "$pat"*/CT \
223
+ -r "$pat"*/RTSeries/*.dcm \
224
+ --roi GTV-1 \
225
+ -o "$USER_DATA/Lung1-out/${pid}_GTV-label.nrrd"
226
+ done
227
+
228
+ # 3. Manifest from the converted tree
229
+ qr convert manifest-from-dir \
230
+ -d "$USER_DATA/Lung1-out" \
231
+ --image-glob '*_CT.nrrd' \
232
+ --mask-glob '*_GTV-label.nrrd' \
233
+ -o "$USER_DATA/Lung1-out/manifest.csv"
234
+
235
+ # 4. PyRadiomics extraction under the nsclc-survival pattern
236
+ # (Original + LoG + Wavelet + Square + SquareRoot + Logarithm ×
237
+ # {firstorder, shape, glcm, glrlm, glszm, gldm, ngtdm} ≈ 1130 features)
238
+ qr extract \
239
+ -m "$USER_DATA/Lung1-out/manifest.csv" \
240
+ -p nsclc-survival \
241
+ -o "$USER_DATA/Lung1-out/features.csv"
242
+
243
+ # 5. Join with the published clinical table (download once)
244
+ # Header has Survival.time + deadstatus → results merge auto-renames.
245
+ curl -sLo "$USER_DATA/Lung1-out/clinical.csv" \
246
+ "https://www.cancerimagingarchive.net/wp-content/uploads/NSCLC-Radiomics-Lung1.clinical-version3-Oct-2019.csv"
247
+ qr results merge \
248
+ -f "$USER_DATA/Lung1-out/features.csv" \
249
+ -c "$USER_DATA/Lung1-out/clinical.csv" \
250
+ --clinical-id-col PatientID \
251
+ --time-col Survival.time --event-col deadstatus.event \
252
+ -o "$USER_DATA/Lung1-out/analysis_ready.csv"
253
+
254
+ # 6. Univariate Cox PH on every radiomic feature → ranked CSV
255
+ qr analyze survival \
256
+ -i "$USER_DATA/Lung1-out/analysis_ready.csv" \
257
+ --outcome OS_months --event OS_event \
258
+ -o "$USER_DATA/Lung1-out/cox_results.csv"
259
+ ```
260
+
261
+ Expected outcome on Lung1 (≈ 420 patients): `original_ngtdm_Busyness`
262
+ ranks at the top (HR ≈ 1.23, p < 1e-4) — replicating the headline
263
+ finding from the Aerts 2014 *Nature Communications* radiomics paper.
264
+
265
+ A 422-patient run takes ≈ 1 h on a 16-core workstation (≈ 30 min
266
+ download, 20 min DICOM→NRRD, 15 min extraction, seconds for the rest).
267
+ For a 5-patient smoke run on the same chain, see
268
+ `scripts/smoke.py` (synthetic NRRD; no TCIA download required).
269
+
270
+ The exact same sequence is bundled per cohort under `pipelines/lung1/`,
271
+ `pipelines/lidc_idri/`, `pipelines/nsclc_cetuximab/`, and
272
+ `pipelines/acrin_heart/` with Nextflow / Prefect / inline executors —
273
+ see the **Deployable Pipelines** section below.
274
+
275
+ ### Alternative starting point — manifest you already have
276
+
277
+ If your data is already in NRRD form with a manifest CSV (canonical
278
+ lowercase columns: `patient_id, modality, image_path, mask_path`), skip
279
+ steps 0-3 and start at step 4 above.
280
+
281
+ Browse the bundled patterns with `qr pattern list` and `qr pattern search <kw>`.
282
+
283
+ ## Deployable Pipelines
284
+
285
+ For each TCIA-public cohort, [`pipelines/`](pipelines/) ships a ready-
286
+ to-run bundle: `plan.json` + `main.nf` + `prefect_flow.py` +
287
+ `nextflow.config` + `deploy.sh`. Run any cohort end-to-end with:
288
+
289
+ ```bash
290
+ cd pipelines/lung1/
291
+ cp /path/to/your/clinical.csv clinical/clinical.csv
292
+ ./deploy.sh # nextflow (per-patient parallel, default)
293
+ EXECUTOR=prefect ./deploy.sh # via Prefect 2.x
294
+ EXECUTOR=inline ./deploy.sh # sequential subprocess (smoke tests)
295
+ ```
296
+
297
+ Available bundles: `lung1/`, `nsclc_cetuximab/`, `lidc_idri/`, `acrin_heart/`.
298
+
299
+ ## Workflow Assembly — agents compose, qr executes
300
+
301
+ The canonical four-stage data flow is encoded in the **template
302
+ library** that `qr workflow plan` draws from:
303
+
304
+ | Template | Stages covered | When to use |
305
+ |---|---|---|
306
+ | `nrrd_survival` | data → features → modeling | cohort already in NRRD form |
307
+ | `dicom_survival` | data → image → features → modeling | cohort ships as DICOM + RTSTRUCT |
308
+ | `dicom_to_ml` | data → image → features → modeling (ML) | full end-to-end DICOM → trained model + CV metrics + held-out evaluation |
309
+
310
+ ```bash
311
+ # 1. Generate a plan
312
+ qr workflow plan -t dicom_to_ml \
313
+ -d /data/cohort -c clinical.csv \
314
+ --roi GTV --pattern nsclc-survival \
315
+ -o plan.json
316
+
317
+ # 2. (Optional) scaffold a Nextflow file for inspection / editing
318
+ qr workflow scaffold -p plan.json -e nextflow -o pipeline.nf
319
+
320
+ # 3. Run — default executor is Nextflow (per-patient parallel + cache)
321
+ qr workflow run plan.json
322
+ qr workflow run plan.json --executor inline # small interactive
323
+ qr workflow run plan.json --executor prefect # Prefect-orchestrated
324
+ ```
325
+
326
+ The plan is plain JSON/YAML — agents can read, mutate (add a stage,
327
+ swap a pattern, change the executor), and re-run without re-templating.
328
+ Per-patient steps are marked in the plan and fanned out automatically
329
+ by the Nextflow / Prefect scaffolders.
330
+
331
+ ## Validated Cohorts
332
+
333
+ The pipeline has been validated end-to-end on three TCIA public cohorts:
334
+
335
+ | Cohort | Format on TCIA | Conversion path |
336
+ |---|---|---|
337
+ | [NSCLC-Radiomics (LUNG1)](https://www.cancerimagingarchive.net/collection/nsclc-radiomics/) | DICOM CT + RTSTRUCT (or pre-converted NRRD via the published companion pack) | `qr convert dicom-series` + `qr convert rtstruct --roi GTV-1`, or feed NRRD directly |
338
+ | [NSCLC-Cetuximab](https://www.cancerimagingarchive.net/collection/nsclc-cetuximab/) | DICOM CT + RTSTRUCT | `qr convert dicom-series` + `qr convert rtstruct --roi PTV` |
339
+ | [ACRIN-NSCLC-FDG-PET](https://www.cancerimagingarchive.net/collection/acrin-nsclc-fdg-pet/) | DICOM CT/PET + RTSTRUCT | `qr convert dicom-series` + `qr convert rtstruct --roi Heart` (case-insensitive) |
340
+
341
+ All three flow cleanly through `convert → extract → results merge → analyze`.
342
+ Ready-to-run shell scripts for each cohort (plus LIDC-IDRI and the IBSI
343
+ phantom) live in [`examples/`](examples/README.md).
344
+
345
+ ## Verified End-to-End Pipelines
346
+
347
+ Each row is a published study whose protocol has been reproduced end-to-end
348
+ in this repo. Pick the row matching your study, run the listed command, and
349
+ the result will be within reported tolerance of the paper.
350
+
351
+ | Pipeline | Paper anchor | Entry point | Verified outcome |
352
+ |---|---|---|---|
353
+ | **LIDC AHSN nodule detection** | Choi & Choi, *Comput Methods Programs Biomed* 2014;113(1):37–54 ([doi](https://doi.org/10.1016/j.cmpb.2013.08.015)) | `pipelines/lidc_idri/ahsn_proxy.py` · `pipelines/lidc_idri/ahsn_hardneg.py` | 180-D AHSN descriptor on LIDC nodule vs non-nodule |
354
+ | **LIDC + LUNGx malignancy radiomics (RM)** | Choi et al., *Med Phys* 2018;45(4):1537–1549 ([doi](https://doi.org/10.1002/mp.12820)) | `pipelines/lidc_idri/reproduce_papers.py` · `pipelines/lidc_idri/methods_compare.py` | LIDC RM AUC ≈ 0.816 ± 0.006 · LUNGx ext+cal ≈ 0.756 (paper: 0.80–0.85 / 0.76) |
355
+ | **LUNGx interpretable spiculation (PM, RM+spic)** | Choi et al., *Comput Methods Programs Biomed* 2021;200:105839 ([doi](https://doi.org/10.1016/j.cmpb.2020.105839)) | `pipelines/lidc_idri/methods_compare.py` (PM) · `qradiomics.shape.spiculation_from_voxel` | radiomics+spic PM ≈ 0.868 ± 0.039 (paper: 0.85) |
356
+ | **HeartToxicity FDG uptake 3-class (TJU prePT → ACRIN/HeartCB postPT)** | Choi et al., *JCO Clin Cancer Inform* 2024 Appendix C ([doi](https://doi.org/10.1200/CCI.23.00241)) | qradiomics-dev `pipelines/heart_local/` (private cohort extension; protocol reproduced with `qradiomics.feature.rtools` postPT-auto + pyradiomics two-source concat) | TJU prePT → ACRIN/HeartCB postPT external acc ≈ 0.80 / 0.78 (Step 1) — matches paper anchor |
357
+
358
+ Each entry point writes a JSON/CSV report next to the script; no manual
359
+ gluing required.
360
+
361
+ ## Command Reference
362
+
363
+ | Command | Stage | Purpose |
364
+ |-----------------------------|------------|---------------------------------------------------------------|
365
+ | `qr tcia download` | data | Bulk-download a TCIA collection (multi-process + progress) |
366
+ | `qr anonymize` | data | Strip PHI from a DICOM tree (DICOM PS3.15 Annex E) |
367
+ | `qr convert dicom-series` | data/image | DICOM CT/MR → NRRD; PT auto-routes through SUV conversion |
368
+ | `qr convert rtstruct` | data/image | DICOM RTSTRUCT contour → label NRRD (case-insensitive ROI) |
369
+ | `qr convert manifest-from-dir` | data | Glob image+mask pairs into a manifest CSV |
370
+ | `qr preprocess` | image | bbox crop + isotropic resample per (image, mask) row |
371
+ | `qr register` | image | Rigid Mattes-MI/LBFGSB moving→fixed (Scenario C mask transfer)|
372
+ | `qr hu-correct` | image | Histogram-match CBCT to a reference CT |
373
+ | `qr extract` | features | PyRadiomics → features.csv (manifest + pattern) |
374
+ | `qr shape extract` | features | AHSN + spiculation shape descriptors |
375
+ | `qr delta` | features | DeltaPair (A - B) + trend slope per patient across timepoints |
376
+ | `qr results merge` | features | features.csv + clinical.csv → analysis_ready.csv |
377
+ | `qr analyze survival` | modeling | Univariate Cox proportional hazards |
378
+ | `qr analyze classify` | modeling | Univariate logistic regression |
379
+ | `qr analyze importance` | modeling | Random-forest + permutation (+ optional SHAP) |
380
+ | `qr ml train` | modeling | CV Cox / logistic + leakage-safe corr/univariate selection |
381
+ | `qr ml predict` | modeling | Apply a trained model to new features |
382
+ | `qr ml evaluate` | modeling | Hold-out evaluation report (c-index / AUC) |
383
+ | `qr workflow plan` | assembly | Generate a multi-step plan from a template |
384
+ | `qr workflow show` | assembly | Inspect a plan's steps and variables |
385
+ | `qr workflow scaffold` | assembly | Render a plan as shell / nextflow / prefect |
386
+ | `qr workflow run` | assembly | Execute a plan (default executor: nextflow) |
387
+ | `qr pattern list / search` | meta | Browse bundled pattern templates |
388
+ | `qr config get / set` | meta | User preferences in `~/.qradiomics/config.yaml` |
389
+
390
+ ## Python API — atomic core
391
+
392
+ Every CLI command is a thin wrapper around a re-usable Python API.
393
+ External libraries (e.g. longitudinal CBCT orchestrators) consume the
394
+ atomic layer directly instead of shelling out.
395
+
396
+ ```python
397
+ from qradiomics.atomic import (
398
+ load_image_and_mask, preprocess_pair,
399
+ build_extractor, run_extractor, extract_features,
400
+ register_pair, resample_to_fixed, histogram_match_hu,
401
+ )
402
+ from qradiomics.data_model import (
403
+ Cohort, Patient, TreatmentCourse, Study,
404
+ ImageSeries, RTStructureSet, ROI,
405
+ AtomicUnit, Modality, StudyType,
406
+ save_cohort, load_cohort,
407
+ )
408
+ from qradiomics.manifest import flatten_cohort, read_manifest, write_manifest
409
+ from qradiomics.delta import DeltaPair, compute_delta, compute_trend
410
+ from qradiomics.io.dicom import read_pet_suv
411
+
412
+ # Single atomic unit: one image, one mask → ≈1409 features
413
+ image, mask = load_image_and_mask("planCT.nrrd", "Heart-label.nrrd")
414
+ cropped_img, cropped_msk = preprocess_pair(image, mask, pad_mm=20, resample_mm=1.0)
415
+ extractor = build_extractor(image_types=["Original", "LoG", "Wavelet"])
416
+ features = run_extractor(extractor, cropped_img, cropped_msk)
417
+ ```
418
+
419
+ ### Hierarchical cohort model
420
+
421
+ `qradiomics.data_model` mirrors the canonical 5–6 level hierarchy used
422
+ across the Choi-Lab ecosystem:
423
+
424
+ ```
425
+ Cohort → Patient → TreatmentCourse → Study → ImageSeries / RTStructureSet → ROI
426
+ (optional)
427
+ ```
428
+
429
+ Diagnostic-only cohorts omit `TreatmentCourse` and attach `Study`
430
+ directly to `Patient`. `flatten_cohort()` walks the tree and produces a
431
+ list of `AtomicUnit`s — one per (image, mask) pair — which becomes the
432
+ manifest CSV consumed by `qr extract`.
433
+
434
+ ```python
435
+ cohort = Cohort(cohort_id="lng-cbct")
436
+ patient = Patient(patient_id="P001")
437
+ course = TreatmentCourse(course_id="rt1", fractions=30, prescription_dose_gy=60.0)
438
+ study = Study(study_id="S-week4", timepoint="week4", relative_day=28)
439
+ study.series["CBCT"] = ImageSeries(series_id="CBCT-w4",
440
+ image_path="/data/CBCT_w4.nrrd", modality=Modality.CBCT, image_tag="CBCT-w4")
441
+ rs = RTStructureSet(rtstruct_id="rs", referenced_series_uid="...")
442
+ rs.rois["GTV"] = ROI(roi_id="GTV", mask_path="/data/GTV-label.nrrd",
443
+ mask_tag="manual", mask_image_tag="CBCT-w4")
444
+ study.structure_sets["rs"] = rs
445
+ course.studies[study.study_id] = study
446
+ patient.treatment_courses[course.course_id] = course
447
+ cohort.patients[patient.patient_id] = patient
448
+
449
+ units = flatten_cohort(cohort) # list[AtomicUnit]
450
+ write_manifest(units, "manifest.csv") # canonical 10-column schema
451
+ save_cohort(cohort, "cohort.yaml") # full graph persistence
452
+ ```
453
+
454
+ The manifest is the bridge: anyone (the CLI, Nextflow, JeffLungRadiomics,
455
+ external scripts) can consume it without needing the Python model.
456
+
457
+ ## Shape Analysis — `qradiomics.shape`
458
+
459
+ Python re-implementations of two published Choi-Lab pipelines, used as
460
+ a library (no CLI yet — call as functions):
461
+
462
+ **2014 CMPB — AHSN pulmonary nodule detection**
463
+
464
+ ```python
465
+ from qradiomics.shape import (
466
+ surface_elements, # Hessian eigendecomp + per-voxel normals (§2.2.1)
467
+ detect_candidates, # Multi-scale Sato/Li dot enhancement (§2.2.2)
468
+ ahsn, AHSNConfig, # Angular Histogram of Surface Normals (§2.3.1)
469
+ wall_eliminate, # Iterative wall detection / elimination (§2.3.2)
470
+ make, make_all, # Synthetic 3D lung phantoms for testing
471
+ )
472
+ ```
473
+
474
+ **2021 CMPB — Spiculation quantification** (companion to
475
+ [CIR](https://github.com/choilab-jefferson/CIR))
476
+
477
+ ```python
478
+ from qradiomics.shape import (
479
+ voxel_to_mesh, # marching cubes → triangular mesh
480
+ spherical_parameterization, # cotangent-Laplacian → unit sphere
481
+ area_distortion, # per-vertex log-area distortion
482
+ detect_peaks, # negative-distortion peaks = spike candidates
483
+ spiculation_features, # Na / Nl / Na_att / s1 / s2 features
484
+ spiculation_from_voxel, # one-shot mask → SpiculationFeatures
485
+ )
486
+ ```
487
+
488
+ See `tests/shape/` for end-to-end usage on analytic shapes
489
+ (sphere / spiked-sphere / phantoms).
490
+
491
+ ## Repository Layout
492
+
493
+ ```
494
+ qradiomics/
495
+ ├── __init__.py # exposes PatternLoader, RadiomicsExtractor, __version__
496
+ ├── cli/ # Click CLI (qr / qradiomics / qrdx)
497
+ │ ├── main.py
498
+ │ ├── config_io.py
499
+ │ ├── commands/ # extract, results, analyze, config_cmd
500
+ │ └── pattern/ # list, match
501
+ ├── pattern_loader.py # YAML pattern templates → Pydantic models
502
+ ├── extractor.py # PyRadiomics wrapper
503
+ ├── shape/ # Published shape pipelines (re-implementation)
504
+ │ ├── hessian.py # 2014 §2.2.1 — Hessian + surface elements
505
+ │ ├── detection.py # 2014 §2.2.2 — multi-scale Sato/Li dot filter
506
+ │ ├── ahsn.py # 2014 §2.3.1 — AHSN descriptor
507
+ │ ├── wall_elim.py # 2014 §2.3.2 — iterative wall elimination
508
+ │ ├── mesh_utils.py # 2021 — voxel → mesh + geometry primitives
509
+ │ ├── spiculation.py # 2021 — spherical param + Na/Nl/Na_att/s1/s2
510
+ │ └── phantoms.py # Synthetic 3D lung phantoms for testing
511
+ └── data/
512
+ ├── templates/ # pattern YAMLs (ct_default, nsclc_survival, ...)
513
+ ├── pyradiomics/ # per-pattern PyRadiomics extractor configs
514
+ └── schema/ # pattern-template JSON schema
515
+
516
+ tests/ # pytest: analyze + results.merge (19 tests)
517
+ LICENSE # MIT
518
+ pyproject.toml
519
+ ```
520
+
521
+ ## Bundled Pattern Templates
522
+
523
+ | `pattern_id` | Description |
524
+ |----------------------|-------------------------------------------------------|
525
+ | `ct-default` | Plain CT, single timepoint, multi image-type baseline |
526
+ | `standard-radiomics` | Multi-modality generic radiomics |
527
+ | `survival-analysis` | Cox + RSF + KM, time-to-event task |
528
+ | `nsclc-survival` | NSCLC CT GTV, LoG+Wavelet+Square/Sqrt/Log image types |
529
+
530
+ Drop a new `*.yaml` into `qradiomics/data/templates/` to add a study;
531
+ `qr pattern list` picks it up automatically.
532
+
533
+ ## Citing
534
+
535
+ If you use this CLI in published work, please cite the relevant
536
+ upstream papers. PyRadiomics and the NSCLC-Radiomics cohort are the
537
+ two essential citations for any qradiomics-derived feature analysis:
538
+
539
+ - **PyRadiomics** — van Griethuysen JJM, Fedorov A, Parmar C, et al.
540
+ *Computational Radiomics System to Decode the Radiographic
541
+ Phenotype.* Cancer Research 2017; 77(21):e104-e107.
542
+ doi:[10.1158/0008-5472.CAN-17-0339](https://doi.org/10.1158/0008-5472.CAN-17-0339)
543
+ - **NSCLC-Radiomics (TCIA LUNG1)** — Aerts HJWL, Velazquez ER, Leijenaar
544
+ RTH, et al. *Decoding tumour phenotype by noninvasive imaging using a
545
+ quantitative radiomics approach.* Nature Communications 2014; 5:4006.
546
+ doi:[10.1038/ncomms5006](https://doi.org/10.1038/ncomms5006)
547
+
548
+ If you build on the lung-screening lineage that this CLI grew out of,
549
+ please additionally cite:
550
+
551
+ - Choi W, Oh JH, Riyahi S, Liu C-J, Jiang F, Chen W, White C, Rimner A,
552
+ Mechalakos JG, Deasy JO, Lu W. *Radiomics analysis of pulmonary
553
+ nodules in low-dose CT for early detection of lung cancer.* Medical
554
+ Physics 2018; 45(4):1537-1549.
555
+ doi:[10.1002/mp.12820](https://doi.org/10.1002/mp.12820)
556
+ - Choi W, Nadeem S, Riyahi S, Deasy JO, Tannenbaum A, Lu W.
557
+ *Reproducible and Interpretable Spiculation Quantification for Lung
558
+ Cancer Screening.* Computer Methods and Programs in Biomedicine 2021;
559
+ 200:105839.
560
+ doi:[10.1016/j.cmpb.2020.105839](https://doi.org/10.1016/j.cmpb.2020.105839)
561
+ - Choi WJ, Choi TS. *Automated pulmonary nodule detection based on
562
+ three-dimensional shape-based feature descriptor.* Computer Methods
563
+ and Programs in Biomedicine 2014; 113(1):37-54.
564
+ doi:[10.1016/j.cmpb.2013.08.015](https://doi.org/10.1016/j.cmpb.2013.08.015)
565
+ - Choi W, Werner-Wasik M, Siglin J, et al. *Heart Radiomics for the
566
+ Early Detection of Cardiac Toxicity in Non–Small-Cell Lung Cancer.*
567
+ JCO Clinical Cancer Informatics 2024; 8:e2300241.
568
+ doi:[10.1200/CCI.23.00241](https://doi.org/10.1200/CCI.23.00241)
569
+ — Appendix C 3-class FDG uptake-pattern protocol (TJU prePT →
570
+ ACRIN/HeartCB postPT external validation) is reproduced in the
571
+ qradiomics-dev private extension on top of the public
572
+ `qradiomics.feature.rtools` postPT-auto extractor.
573
+
574
+ ## Authors and Acknowledgements
575
+
576
+ - [**Wookjin Choi**](https://github.com/taznux) — overall architecture,
577
+ CLI design, pattern templates
578
+ - [**Pradeep Bhetwal**](https://github.com/Pradeepbhetwal) — survival
579
+ analysis on the LUNG1 cohort
580
+ - Choi Lab, Department of Radiation Oncology, Sidney Kimmel Medical
581
+ College at Thomas Jefferson University
582
+
583
+ ## License
584
+
585
+ MIT — see [LICENSE](LICENSE).