dicompare 0.1.38__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.
- dicompare-0.1.38/LICENSE +21 -0
- dicompare-0.1.38/PKG-INFO +227 -0
- dicompare-0.1.38/README.md +191 -0
- dicompare-0.1.38/dicompare/__init__.py +18 -0
- dicompare-0.1.38/dicompare/cli/__init__.py +0 -0
- dicompare-0.1.38/dicompare/cli/main.py +222 -0
- dicompare-0.1.38/dicompare/config.py +197 -0
- dicompare-0.1.38/dicompare/data_utils.py +259 -0
- dicompare-0.1.38/dicompare/interface/__init__.py +15 -0
- dicompare-0.1.38/dicompare/interface/web_utils.py +287 -0
- dicompare-0.1.38/dicompare/io/__init__.py +76 -0
- dicompare-0.1.38/dicompare/io/dicom.py +773 -0
- dicompare-0.1.38/dicompare/io/dicom_generator.py +568 -0
- dicompare-0.1.38/dicompare/io/json.py +120 -0
- dicompare-0.1.38/dicompare/io/pro.py +1430 -0
- dicompare-0.1.38/dicompare/io/special_fields.py +304 -0
- dicompare-0.1.38/dicompare/processing/__init__.py +26 -0
- dicompare-0.1.38/dicompare/processing/parallel_utils.py +75 -0
- dicompare-0.1.38/dicompare/processing/progress_utils.py +158 -0
- dicompare-0.1.38/dicompare/schema/__init__.py +32 -0
- dicompare-0.1.38/dicompare/schema/build_schema.py +100 -0
- dicompare-0.1.38/dicompare/schema/tags.py +293 -0
- dicompare-0.1.38/dicompare/session/__init__.py +24 -0
- dicompare-0.1.38/dicompare/session/acquisition.py +500 -0
- dicompare-0.1.38/dicompare/session/mapping.py +389 -0
- dicompare-0.1.38/dicompare/tests/__init__.py +0 -0
- dicompare-0.1.38/dicompare/tests/fixtures/__init__.py +0 -0
- dicompare-0.1.38/dicompare/tests/fixtures/fixtures.py +203 -0
- dicompare-0.1.38/dicompare/tests/fixtures/ref_empty.py +0 -0
- dicompare-0.1.38/dicompare/tests/generate_test_dataset.py +262 -0
- dicompare-0.1.38/dicompare/tests/test_acquisition_assignment.py +180 -0
- dicompare-0.1.38/dicompare/tests/test_cli.py +162 -0
- dicompare-0.1.38/dicompare/tests/test_compliance.py +864 -0
- dicompare-0.1.38/dicompare/tests/test_compliance_series_fix.py +133 -0
- dicompare-0.1.38/dicompare/tests/test_compliance_unified.py +322 -0
- dicompare-0.1.38/dicompare/tests/test_data_utils.py +345 -0
- dicompare-0.1.38/dicompare/tests/test_dicom_factory.py +402 -0
- dicompare-0.1.38/dicompare/tests/test_dicom_generator.py +177 -0
- dicompare-0.1.38/dicompare/tests/test_generate_schema.py +243 -0
- dicompare-0.1.38/dicompare/tests/test_helpers.py +54 -0
- dicompare-0.1.38/dicompare/tests/test_integration_hybrid.py +280 -0
- dicompare-0.1.38/dicompare/tests/test_io.py +767 -0
- dicompare-0.1.38/dicompare/tests/test_multiband_extraction.py +109 -0
- dicompare-0.1.38/dicompare/tests/test_pro_files.py +266 -0
- dicompare-0.1.38/dicompare/tests/test_ref_dicom.py +19 -0
- dicompare-0.1.38/dicompare/tests/test_schema_integration.py +659 -0
- dicompare-0.1.38/dicompare/tests/test_schema_json_generator.py +293 -0
- dicompare-0.1.38/dicompare/tests/test_serialization.py +295 -0
- dicompare-0.1.38/dicompare/tests/test_tags.py +248 -0
- dicompare-0.1.38/dicompare/tests/test_utils_enhanced.py +207 -0
- dicompare-0.1.38/dicompare/tests/test_validation_dynamic.py +276 -0
- dicompare-0.1.38/dicompare/tests/test_visualization.py +18 -0
- dicompare-0.1.38/dicompare/tests/test_web_utils.py +103 -0
- dicompare-0.1.38/dicompare/utils.py +215 -0
- dicompare-0.1.38/dicompare/validation/__init__.py +61 -0
- dicompare-0.1.38/dicompare/validation/compliance.py +326 -0
- dicompare-0.1.38/dicompare/validation/core.py +458 -0
- dicompare-0.1.38/dicompare/validation/helpers.py +448 -0
- dicompare-0.1.38/dicompare.egg-info/PKG-INFO +227 -0
- dicompare-0.1.38/dicompare.egg-info/SOURCES.txt +65 -0
- dicompare-0.1.38/dicompare.egg-info/dependency_links.txt +1 -0
- dicompare-0.1.38/dicompare.egg-info/entry_points.txt +2 -0
- dicompare-0.1.38/dicompare.egg-info/requires.txt +13 -0
- dicompare-0.1.38/dicompare.egg-info/top_level.txt +1 -0
- dicompare-0.1.38/pyproject.toml +7 -0
- dicompare-0.1.38/setup.cfg +4 -0
- dicompare-0.1.38/setup.py +49 -0
dicompare-0.1.38/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ashley Stewart
|
|
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,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dicompare
|
|
3
|
+
Version: 0.1.38
|
|
4
|
+
Summary: A tool for checking DICOM compliance against a template
|
|
5
|
+
Home-page: https://github.com/astewartau/dicompare
|
|
6
|
+
Author: Ashley Stewart
|
|
7
|
+
Keywords: DICOM compliance validation medical imaging
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pydicom==2.4.4
|
|
15
|
+
Requires-Dist: pandas
|
|
16
|
+
Requires-Dist: tabulate
|
|
17
|
+
Requires-Dist: scipy
|
|
18
|
+
Requires-Dist: tqdm
|
|
19
|
+
Requires-Dist: nibabel
|
|
20
|
+
Requires-Dist: twixtools
|
|
21
|
+
Provides-Extra: interactive
|
|
22
|
+
Requires-Dist: curses; extra == "interactive"
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
36
|
+
|
|
37
|
+
# dicompare
|
|
38
|
+
|
|
39
|
+
[](https://dicompare-web.vercel.app/)
|
|
40
|
+
|
|
41
|
+
dicompare is a DICOM validation tool designed to ensure compliance with study-specific imaging protocols and domain-specific guidelines while preserving data privacy. It provides multiple interfaces, including support for validation directly in the browser at [dicompare-web.vercel.app](https://dicompare-web.vercel.app/), leveraging WebAssembly (WASM), Pyodide, and the underlying pip package `dicompare`. dicompare is suitable for multi-site studies and clinical environments without requiring software installation or external data uploads.
|
|
42
|
+
|
|
43
|
+
dicompare supports DICOM session validation against templates based on:
|
|
44
|
+
|
|
45
|
+
- **Reference sessions**: JSON schema files can be generated based on a reference MRI scanning session;
|
|
46
|
+
- **[TESTING] domain guidelines**: Flexible guidelines for specific domains (currently [QSM](https://doi.org/10.1002/mrm.30006));
|
|
47
|
+
- **[FUTURE] landmark studies**: Schema files based on landmark studies such as the [HCP](https://doi.org/10.1038/s41586-018-0579-z), [ABCD](https://doi.org/10.1016/j.dcn.2018.03.001), and [UK BioBank](https://doi.org/10.1038/s41586-018-0579-z) projects.
|
|
48
|
+
|
|
49
|
+
# Command-line interface (CLI) and application programming interface (API)
|
|
50
|
+
|
|
51
|
+
While you can run [dicompare](https://dicompare-web.vercel.app/) in your browser now without any installation, you may also use the underlying `dicompare` pip package if you wish to use the command-line interface (CLI) or application programming interface (API).
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install dicompare
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Command-line interface (CLI)
|
|
58
|
+
|
|
59
|
+
The package provides a unified `dicompare` command with two subcommands:
|
|
60
|
+
|
|
61
|
+
- **`dicompare build`**: Generate a JSON schema from a reference DICOM session
|
|
62
|
+
- **`dicompare check`**: Validate DICOM sessions against a JSON schema
|
|
63
|
+
|
|
64
|
+
### 1. Build a JSON schema from a reference session
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
dicompare build /path/to/dicom/session schema.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This creates a JSON schema describing the session based on default reference fields present in the data.
|
|
71
|
+
|
|
72
|
+
### 2. Check a DICOM session against a schema
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
dicompare check /path/to/dicom/session schema.json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The tool will output a compliance summary, indicating deviations from the schema.
|
|
79
|
+
|
|
80
|
+
### 3. Check with report output
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
dicompare check /path/to/dicom/session schema.json compliance_report.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This saves the compliance report to a JSON file.
|
|
87
|
+
|
|
88
|
+
### 4. Automatic acquisition mapping
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
dicompare check /path/to/dicom/session schema.json --auto-yes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `--auto-yes` or `-y` to automatically map acquisitions without interactive prompts.
|
|
95
|
+
|
|
96
|
+
## Python API
|
|
97
|
+
|
|
98
|
+
The `dicompare` package provides a comprehensive Python API for programmatic schema generation, validation, and DICOM processing.
|
|
99
|
+
|
|
100
|
+
### Loading DICOM data
|
|
101
|
+
|
|
102
|
+
**Load a DICOM session:**
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from dicompare import load_dicom_session
|
|
106
|
+
|
|
107
|
+
session_df = load_dicom_session(
|
|
108
|
+
session_dir="/path/to/dicom/session",
|
|
109
|
+
show_progress=True
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Load individual DICOM files:**
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from dicompare import load_dicom
|
|
117
|
+
|
|
118
|
+
dicom_data = load_dicom(
|
|
119
|
+
dicom_paths=["/path/to/file1.dcm", "/path/to/file2.dcm"],
|
|
120
|
+
show_progress=True
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Load Siemens .pro files:**
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from dicompare import load_pro_session
|
|
128
|
+
|
|
129
|
+
pro_session = load_pro_session(
|
|
130
|
+
session_dir="/path/to/pro/files",
|
|
131
|
+
show_progress=True
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Build a JSON schema
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from dicompare import load_dicom_session, build_schema, make_json_serializable
|
|
139
|
+
from dicompare.config import DEFAULT_SETTINGS_FIELDS
|
|
140
|
+
import json
|
|
141
|
+
|
|
142
|
+
# Load the reference session
|
|
143
|
+
session_df = load_dicom_session(
|
|
144
|
+
session_dir="/path/to/dicom/session",
|
|
145
|
+
show_progress=True
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Build the schema
|
|
149
|
+
json_schema = build_schema(session_df)
|
|
150
|
+
|
|
151
|
+
# Save the schema
|
|
152
|
+
serializable_schema = make_json_serializable(json_schema)
|
|
153
|
+
with open("schema.json", "w") as f:
|
|
154
|
+
json.dump(serializable_schema, f, indent=4)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Validate a session against a JSON schema
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from dicompare import (
|
|
161
|
+
load_schema,
|
|
162
|
+
load_dicom_session,
|
|
163
|
+
check_acquisition_compliance,
|
|
164
|
+
map_to_json_reference,
|
|
165
|
+
assign_acquisition_and_run_numbers
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Load the JSON schema
|
|
169
|
+
reference_fields, json_schema, validation_rules = load_schema(json_schema_path="schema.json")
|
|
170
|
+
|
|
171
|
+
# Load the input session
|
|
172
|
+
in_session = load_dicom_session(
|
|
173
|
+
session_dir="/path/to/dicom/session",
|
|
174
|
+
show_progress=True
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Assign acquisition and run numbers
|
|
178
|
+
in_session = assign_acquisition_and_run_numbers(in_session)
|
|
179
|
+
|
|
180
|
+
# Map acquisitions to schema
|
|
181
|
+
session_map = map_to_json_reference(in_session, json_schema)
|
|
182
|
+
|
|
183
|
+
# Check compliance for each acquisition
|
|
184
|
+
compliance_summary = []
|
|
185
|
+
for ref_acq_name, schema_acq in json_schema["acquisitions"].items():
|
|
186
|
+
if ref_acq_name not in session_map:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
input_acq_name = session_map[ref_acq_name]
|
|
190
|
+
acq_validation_rules = validation_rules.get(ref_acq_name) if validation_rules else None
|
|
191
|
+
|
|
192
|
+
results = check_acquisition_compliance(
|
|
193
|
+
in_session,
|
|
194
|
+
schema_acq,
|
|
195
|
+
acquisition_name=input_acq_name,
|
|
196
|
+
validation_rules=acq_validation_rules
|
|
197
|
+
)
|
|
198
|
+
compliance_summary.extend(results)
|
|
199
|
+
|
|
200
|
+
# Display results
|
|
201
|
+
for entry in compliance_summary:
|
|
202
|
+
print(entry)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Additional utilities
|
|
206
|
+
|
|
207
|
+
**Assign acquisition and run numbers:**
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from dicompare import assign_acquisition_and_run_numbers
|
|
211
|
+
|
|
212
|
+
session_df = assign_acquisition_and_run_numbers(session_df)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Get DICOM tag information:**
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from dicompare import get_tag_info, get_all_tags_in_dataset
|
|
219
|
+
|
|
220
|
+
# Get info about a specific tag
|
|
221
|
+
tag_info = get_tag_info("EchoTime")
|
|
222
|
+
print(tag_info) # {'tag': '(0018,0081)', 'name': 'Echo Time', 'type': 'float'}
|
|
223
|
+
|
|
224
|
+
# Get all tags in a dataset
|
|
225
|
+
all_tags = get_all_tags_in_dataset(dicom_metadata)
|
|
226
|
+
```
|
|
227
|
+
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# dicompare
|
|
2
|
+
|
|
3
|
+
[](https://dicompare-web.vercel.app/)
|
|
4
|
+
|
|
5
|
+
dicompare is a DICOM validation tool designed to ensure compliance with study-specific imaging protocols and domain-specific guidelines while preserving data privacy. It provides multiple interfaces, including support for validation directly in the browser at [dicompare-web.vercel.app](https://dicompare-web.vercel.app/), leveraging WebAssembly (WASM), Pyodide, and the underlying pip package `dicompare`. dicompare is suitable for multi-site studies and clinical environments without requiring software installation or external data uploads.
|
|
6
|
+
|
|
7
|
+
dicompare supports DICOM session validation against templates based on:
|
|
8
|
+
|
|
9
|
+
- **Reference sessions**: JSON schema files can be generated based on a reference MRI scanning session;
|
|
10
|
+
- **[TESTING] domain guidelines**: Flexible guidelines for specific domains (currently [QSM](https://doi.org/10.1002/mrm.30006));
|
|
11
|
+
- **[FUTURE] landmark studies**: Schema files based on landmark studies such as the [HCP](https://doi.org/10.1038/s41586-018-0579-z), [ABCD](https://doi.org/10.1016/j.dcn.2018.03.001), and [UK BioBank](https://doi.org/10.1038/s41586-018-0579-z) projects.
|
|
12
|
+
|
|
13
|
+
# Command-line interface (CLI) and application programming interface (API)
|
|
14
|
+
|
|
15
|
+
While you can run [dicompare](https://dicompare-web.vercel.app/) in your browser now without any installation, you may also use the underlying `dicompare` pip package if you wish to use the command-line interface (CLI) or application programming interface (API).
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install dicompare
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Command-line interface (CLI)
|
|
22
|
+
|
|
23
|
+
The package provides a unified `dicompare` command with two subcommands:
|
|
24
|
+
|
|
25
|
+
- **`dicompare build`**: Generate a JSON schema from a reference DICOM session
|
|
26
|
+
- **`dicompare check`**: Validate DICOM sessions against a JSON schema
|
|
27
|
+
|
|
28
|
+
### 1. Build a JSON schema from a reference session
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
dicompare build /path/to/dicom/session schema.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This creates a JSON schema describing the session based on default reference fields present in the data.
|
|
35
|
+
|
|
36
|
+
### 2. Check a DICOM session against a schema
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
dicompare check /path/to/dicom/session schema.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The tool will output a compliance summary, indicating deviations from the schema.
|
|
43
|
+
|
|
44
|
+
### 3. Check with report output
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
dicompare check /path/to/dicom/session schema.json compliance_report.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This saves the compliance report to a JSON file.
|
|
51
|
+
|
|
52
|
+
### 4. Automatic acquisition mapping
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
dicompare check /path/to/dicom/session schema.json --auto-yes
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Use `--auto-yes` or `-y` to automatically map acquisitions without interactive prompts.
|
|
59
|
+
|
|
60
|
+
## Python API
|
|
61
|
+
|
|
62
|
+
The `dicompare` package provides a comprehensive Python API for programmatic schema generation, validation, and DICOM processing.
|
|
63
|
+
|
|
64
|
+
### Loading DICOM data
|
|
65
|
+
|
|
66
|
+
**Load a DICOM session:**
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from dicompare import load_dicom_session
|
|
70
|
+
|
|
71
|
+
session_df = load_dicom_session(
|
|
72
|
+
session_dir="/path/to/dicom/session",
|
|
73
|
+
show_progress=True
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Load individual DICOM files:**
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from dicompare import load_dicom
|
|
81
|
+
|
|
82
|
+
dicom_data = load_dicom(
|
|
83
|
+
dicom_paths=["/path/to/file1.dcm", "/path/to/file2.dcm"],
|
|
84
|
+
show_progress=True
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Load Siemens .pro files:**
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from dicompare import load_pro_session
|
|
92
|
+
|
|
93
|
+
pro_session = load_pro_session(
|
|
94
|
+
session_dir="/path/to/pro/files",
|
|
95
|
+
show_progress=True
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Build a JSON schema
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from dicompare import load_dicom_session, build_schema, make_json_serializable
|
|
103
|
+
from dicompare.config import DEFAULT_SETTINGS_FIELDS
|
|
104
|
+
import json
|
|
105
|
+
|
|
106
|
+
# Load the reference session
|
|
107
|
+
session_df = load_dicom_session(
|
|
108
|
+
session_dir="/path/to/dicom/session",
|
|
109
|
+
show_progress=True
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Build the schema
|
|
113
|
+
json_schema = build_schema(session_df)
|
|
114
|
+
|
|
115
|
+
# Save the schema
|
|
116
|
+
serializable_schema = make_json_serializable(json_schema)
|
|
117
|
+
with open("schema.json", "w") as f:
|
|
118
|
+
json.dump(serializable_schema, f, indent=4)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Validate a session against a JSON schema
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from dicompare import (
|
|
125
|
+
load_schema,
|
|
126
|
+
load_dicom_session,
|
|
127
|
+
check_acquisition_compliance,
|
|
128
|
+
map_to_json_reference,
|
|
129
|
+
assign_acquisition_and_run_numbers
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Load the JSON schema
|
|
133
|
+
reference_fields, json_schema, validation_rules = load_schema(json_schema_path="schema.json")
|
|
134
|
+
|
|
135
|
+
# Load the input session
|
|
136
|
+
in_session = load_dicom_session(
|
|
137
|
+
session_dir="/path/to/dicom/session",
|
|
138
|
+
show_progress=True
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Assign acquisition and run numbers
|
|
142
|
+
in_session = assign_acquisition_and_run_numbers(in_session)
|
|
143
|
+
|
|
144
|
+
# Map acquisitions to schema
|
|
145
|
+
session_map = map_to_json_reference(in_session, json_schema)
|
|
146
|
+
|
|
147
|
+
# Check compliance for each acquisition
|
|
148
|
+
compliance_summary = []
|
|
149
|
+
for ref_acq_name, schema_acq in json_schema["acquisitions"].items():
|
|
150
|
+
if ref_acq_name not in session_map:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
input_acq_name = session_map[ref_acq_name]
|
|
154
|
+
acq_validation_rules = validation_rules.get(ref_acq_name) if validation_rules else None
|
|
155
|
+
|
|
156
|
+
results = check_acquisition_compliance(
|
|
157
|
+
in_session,
|
|
158
|
+
schema_acq,
|
|
159
|
+
acquisition_name=input_acq_name,
|
|
160
|
+
validation_rules=acq_validation_rules
|
|
161
|
+
)
|
|
162
|
+
compliance_summary.extend(results)
|
|
163
|
+
|
|
164
|
+
# Display results
|
|
165
|
+
for entry in compliance_summary:
|
|
166
|
+
print(entry)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Additional utilities
|
|
170
|
+
|
|
171
|
+
**Assign acquisition and run numbers:**
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from dicompare import assign_acquisition_and_run_numbers
|
|
175
|
+
|
|
176
|
+
session_df = assign_acquisition_and_run_numbers(session_df)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Get DICOM tag information:**
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from dicompare import get_tag_info, get_all_tags_in_dataset
|
|
183
|
+
|
|
184
|
+
# Get info about a specific tag
|
|
185
|
+
tag_info = get_tag_info("EchoTime")
|
|
186
|
+
print(tag_info) # {'tag': '(0018,0081)', 'name': 'Echo Time', 'type': 'float'}
|
|
187
|
+
|
|
188
|
+
# Get all tags in a dataset
|
|
189
|
+
all_tags = get_all_tags_in_dataset(dicom_metadata)
|
|
190
|
+
```
|
|
191
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
__version__ = "0.1.38"
|
|
2
|
+
|
|
3
|
+
# Import core functionalities
|
|
4
|
+
from .io import get_dicom_values, load_dicom, load_schema, load_dicom_session, async_load_dicom_session, load_nifti_session, load_pro_file, load_pro_session, generate_test_dicoms_from_schema, generate_test_dicoms_from_schema_json, load_pro_file_schema_format
|
|
5
|
+
from .validation import check_acquisition_compliance
|
|
6
|
+
from .session import assign_acquisition_and_run_numbers
|
|
7
|
+
from .session import map_to_json_reference, interactive_mapping_to_json_reference
|
|
8
|
+
from .validation import BaseValidationModel, ValidationError, ValidationWarning, validator, safe_exec_rule, create_validation_model_from_rules, create_validation_models_from_rules
|
|
9
|
+
from .config import DEFAULT_SETTINGS_FIELDS, DEFAULT_ACQUISITION_FIELDS, DEFAULT_DICOM_FIELDS
|
|
10
|
+
from .schema import get_tag_info, get_all_tags_in_dataset
|
|
11
|
+
|
|
12
|
+
# Import enhanced functionality for web interfaces
|
|
13
|
+
from .schema import build_schema
|
|
14
|
+
from .io import make_json_serializable
|
|
15
|
+
from .utils import detect_constant_fields, clean_string, make_hashable
|
|
16
|
+
from .interface import (
|
|
17
|
+
analyze_dicom_files_for_web,
|
|
18
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from dicompare.io import load_dicom_session, load_schema
|
|
10
|
+
from dicompare.io.json import make_json_serializable
|
|
11
|
+
from dicompare.schema import build_schema
|
|
12
|
+
from dicompare.validation import check_acquisition_compliance
|
|
13
|
+
from dicompare.validation.helpers import ComplianceStatus, create_compliance_record
|
|
14
|
+
from dicompare.session import map_to_json_reference, interactive_mapping_to_json_reference, assign_acquisition_and_run_numbers
|
|
15
|
+
|
|
16
|
+
# Set up logging
|
|
17
|
+
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_command(args) -> None:
|
|
22
|
+
"""Generate a JSON schema from a DICOM session."""
|
|
23
|
+
# Read DICOM session
|
|
24
|
+
session_data = load_dicom_session(
|
|
25
|
+
session_dir=args.dicoms,
|
|
26
|
+
show_progress=True
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Generate JSON schema
|
|
30
|
+
json_schema = build_schema(session_df=session_data)
|
|
31
|
+
|
|
32
|
+
# Write JSON to output file
|
|
33
|
+
serializable_schema = make_json_serializable(json_schema)
|
|
34
|
+
with open(args.schema, "w") as f:
|
|
35
|
+
json.dump(serializable_schema, f, indent=4)
|
|
36
|
+
logger.info(f"JSON schema saved to {args.schema}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def check_command(args) -> None:
|
|
40
|
+
"""Check a DICOM session against a schema for compliance."""
|
|
41
|
+
# Load the schema
|
|
42
|
+
reference_fields, json_schema, validation_rules = load_schema(json_schema_path=args.schema)
|
|
43
|
+
|
|
44
|
+
# Load the input session
|
|
45
|
+
in_session = load_dicom_session(
|
|
46
|
+
session_dir=args.dicoms,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Assign acquisition and series using canonical process
|
|
50
|
+
in_session = assign_acquisition_and_run_numbers(in_session)
|
|
51
|
+
|
|
52
|
+
# Map and perform compliance check
|
|
53
|
+
session_map = map_to_json_reference(in_session, json_schema)
|
|
54
|
+
if not args.auto_yes and sys.stdin.isatty():
|
|
55
|
+
session_map = interactive_mapping_to_json_reference(in_session, json_schema, initial_mapping=session_map)
|
|
56
|
+
|
|
57
|
+
# Check compliance for each acquisition
|
|
58
|
+
compliance_summary = []
|
|
59
|
+
for ref_acq_name, schema_acq in json_schema["acquisitions"].items():
|
|
60
|
+
if ref_acq_name not in session_map:
|
|
61
|
+
compliance_summary.append(create_compliance_record(
|
|
62
|
+
field="Acquisition Mapping",
|
|
63
|
+
message=f"Reference acquisition '{ref_acq_name}' is not mapped to any input acquisition.",
|
|
64
|
+
status=ComplianceStatus.ERROR,
|
|
65
|
+
expected=f"Acquisition '{ref_acq_name}' to be mapped"
|
|
66
|
+
))
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
input_acq_name = session_map[ref_acq_name]
|
|
70
|
+
acq_validation_rules = validation_rules.get(ref_acq_name) if validation_rules else None
|
|
71
|
+
results = check_acquisition_compliance(
|
|
72
|
+
in_session,
|
|
73
|
+
schema_acq,
|
|
74
|
+
acquisition_name=input_acq_name,
|
|
75
|
+
validation_rules=acq_validation_rules
|
|
76
|
+
)
|
|
77
|
+
compliance_summary.extend(results)
|
|
78
|
+
compliance_df = pd.DataFrame(compliance_summary)
|
|
79
|
+
|
|
80
|
+
# If compliance_df is empty, log message and exit
|
|
81
|
+
if compliance_df.empty:
|
|
82
|
+
logger.info("Session is fully compliant with the schema model.")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Inline summary output
|
|
86
|
+
for entry in compliance_summary:
|
|
87
|
+
if entry.get('input acquisition'):
|
|
88
|
+
acq_text = f"Acquisition: {entry.get('input acquisition')}"
|
|
89
|
+
if entry.get('reference acquisition'):
|
|
90
|
+
acq_text += f" ({entry.get('reference acquisition')})"
|
|
91
|
+
logger.info(acq_text)
|
|
92
|
+
# Handle 'field' (single) or derive from 'expected' keys
|
|
93
|
+
if entry.get('field'):
|
|
94
|
+
logger.info(f"Field: {entry.get('field')}")
|
|
95
|
+
elif entry.get('expected') and isinstance(entry.get('expected'), dict):
|
|
96
|
+
logger.info(f"Fields: {list(entry.get('expected').keys())}")
|
|
97
|
+
if entry.get('series'): logger.info(f"Series: {entry.get('series')}")
|
|
98
|
+
if entry.get('expected') is not None: logger.info(f"Expected: {entry.get('expected')}")
|
|
99
|
+
if entry.get('value') is not None: logger.info(f"Value: {entry.get('value')}")
|
|
100
|
+
if entry.get('message'): logger.info(f"Message: {entry.get('message')}")
|
|
101
|
+
logger.info("-" * 40)
|
|
102
|
+
|
|
103
|
+
# Save compliance summary to JSON
|
|
104
|
+
if args.report:
|
|
105
|
+
with open(args.report, "w") as f:
|
|
106
|
+
json.dump(compliance_summary, f, indent=4)
|
|
107
|
+
logger.info(f"Compliance report saved to {args.report}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main() -> None:
|
|
111
|
+
parser = argparse.ArgumentParser(
|
|
112
|
+
description="DICOM compliance validation tool",
|
|
113
|
+
prog="dicompare"
|
|
114
|
+
)
|
|
115
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
116
|
+
|
|
117
|
+
# Build subcommand
|
|
118
|
+
build_parser = subparsers.add_parser(
|
|
119
|
+
"build",
|
|
120
|
+
help="Build a JSON schema from a DICOM session"
|
|
121
|
+
)
|
|
122
|
+
build_parser.add_argument(
|
|
123
|
+
"dicoms",
|
|
124
|
+
nargs="?",
|
|
125
|
+
help="Directory containing DICOM files"
|
|
126
|
+
)
|
|
127
|
+
build_parser.add_argument(
|
|
128
|
+
"schema",
|
|
129
|
+
nargs="?",
|
|
130
|
+
help="Output path for the JSON schema"
|
|
131
|
+
)
|
|
132
|
+
build_parser.add_argument(
|
|
133
|
+
"--dicoms",
|
|
134
|
+
dest="dicoms_named",
|
|
135
|
+
metavar="PATH",
|
|
136
|
+
help="Directory containing DICOM files"
|
|
137
|
+
)
|
|
138
|
+
build_parser.add_argument(
|
|
139
|
+
"--schema",
|
|
140
|
+
dest="schema_named",
|
|
141
|
+
metavar="PATH",
|
|
142
|
+
help="Output path for the JSON schema"
|
|
143
|
+
)
|
|
144
|
+
build_parser.add_argument(
|
|
145
|
+
"--name-template",
|
|
146
|
+
default="{ProtocolName}",
|
|
147
|
+
help="Naming template for acquisitions (default: {ProtocolName})"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Check subcommand
|
|
151
|
+
check_parser = subparsers.add_parser(
|
|
152
|
+
"check",
|
|
153
|
+
help="Check a DICOM session against a schema"
|
|
154
|
+
)
|
|
155
|
+
check_parser.add_argument(
|
|
156
|
+
"dicoms",
|
|
157
|
+
nargs="?",
|
|
158
|
+
help="Directory containing DICOM files to check"
|
|
159
|
+
)
|
|
160
|
+
check_parser.add_argument(
|
|
161
|
+
"schema",
|
|
162
|
+
nargs="?",
|
|
163
|
+
help="Path to the JSON schema file"
|
|
164
|
+
)
|
|
165
|
+
check_parser.add_argument(
|
|
166
|
+
"report",
|
|
167
|
+
nargs="?",
|
|
168
|
+
help="Output path for the compliance report"
|
|
169
|
+
)
|
|
170
|
+
check_parser.add_argument(
|
|
171
|
+
"--dicoms",
|
|
172
|
+
dest="dicoms_named",
|
|
173
|
+
metavar="PATH",
|
|
174
|
+
help="Directory containing DICOM files to check"
|
|
175
|
+
)
|
|
176
|
+
check_parser.add_argument(
|
|
177
|
+
"--schema",
|
|
178
|
+
dest="schema_named",
|
|
179
|
+
metavar="PATH",
|
|
180
|
+
help="Path to the JSON schema file"
|
|
181
|
+
)
|
|
182
|
+
check_parser.add_argument(
|
|
183
|
+
"--report",
|
|
184
|
+
dest="report_named",
|
|
185
|
+
metavar="PATH",
|
|
186
|
+
help="Output path for the compliance report"
|
|
187
|
+
)
|
|
188
|
+
check_parser.add_argument(
|
|
189
|
+
"--auto-yes", "-y",
|
|
190
|
+
action="store_true",
|
|
191
|
+
help="Automatically map acquisitions without prompting"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
args = parser.parse_args()
|
|
195
|
+
|
|
196
|
+
if not args.command:
|
|
197
|
+
parser.print_help()
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
|
|
200
|
+
# Resolve positional vs named arguments
|
|
201
|
+
if args.command == "build":
|
|
202
|
+
args.dicoms = args.dicoms or args.dicoms_named
|
|
203
|
+
args.schema = args.schema or args.schema_named
|
|
204
|
+
|
|
205
|
+
if not args.dicoms or not args.schema:
|
|
206
|
+
build_parser.error("the following arguments are required: dicoms, schema")
|
|
207
|
+
|
|
208
|
+
build_command(args)
|
|
209
|
+
|
|
210
|
+
elif args.command == "check":
|
|
211
|
+
args.dicoms = args.dicoms or args.dicoms_named
|
|
212
|
+
args.schema = args.schema or args.schema_named
|
|
213
|
+
args.report = args.report or args.report_named
|
|
214
|
+
|
|
215
|
+
if not args.dicoms or not args.schema:
|
|
216
|
+
check_parser.error("the following arguments are required: dicoms, schema")
|
|
217
|
+
|
|
218
|
+
check_command(args)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
main()
|