dicompare 0.1.8__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.
@@ -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,122 @@
1
+ Metadata-Version: 2.1
2
+ Name: dicompare
3
+ Version: 0.1.8
4
+ Summary: A tool for checking DICOM compliance against a reference model using Pydantic
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.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pydicom==3.0.1
15
+ Requires-Dist: pandas
16
+ Requires-Dist: tabulate
17
+ Requires-Dist: scipy
18
+ Provides-Extra: interactive
19
+ Requires-Dist: curses; extra == "interactive"
20
+
21
+ # dicompare
22
+
23
+ [![](img/button.png)](https://astewartau.github.io/dicompare/)
24
+
25
+ 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, leveraging WebAssembly (WASM), Pyodide, and the underlying pip package [`dicompare`](#dicompare). dicompare is suitable for multi-site studies and clinical environments without requiring software installation or external data uploads.
26
+
27
+ dicompare supports DICOM session validation against:
28
+
29
+ - **Session schemas**: JSON schema files that can be generated based on a reference session;
30
+ - **[UNDER CONSTRUCTION] 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;
31
+ - **[UNDER CONSTRUCTION] domain guidelines**: Flexible guidelines for domains such as [QSM](https://doi.org/10.1002/mrm.30006).
32
+
33
+ # Command-line interface (CLI) and application programming interface (API)
34
+
35
+ While you can run [dicompare](https://astewartau.github.io/dicompare/) 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).
36
+
37
+ ```bash
38
+ pip install dicompare
39
+ ```
40
+
41
+ ## Command-line interface (CLI)
42
+
43
+ The package provides the following CLI entry points:
44
+
45
+ - `dcm-gen-session`: Generate JSON schemas for DICOM validation.
46
+ - `dcm-check-session`: Validate DICOM sessions against predefined schemas.
47
+
48
+ 1. Generate a JSON Reference Schema
49
+
50
+ ```bash
51
+ dcm-gen-session \
52
+ --in_session_dir /path/to/dicom/session \
53
+ --out_json_ref schema.json \
54
+ --acquisition_fields ProtocolName SeriesDescription \
55
+ --reference_fields EchoTime RepetitionTime
56
+ ```
57
+
58
+ This will create a JSON schema describing the session based on the specified fields.
59
+
60
+ 2. Validate a DICOM Session
61
+
62
+ ```bash
63
+ dicompare-session \
64
+ --in_session /path/to/dicom/session \
65
+ --json_ref schema.json \
66
+ --out_json compliance_report.json
67
+ ```
68
+
69
+ The tool will output a compliance summary, indicating deviations from the reference schema.
70
+
71
+ ## Python API
72
+
73
+ The `dicompare` package provides a Python API for programmatic schema generation and validation.
74
+
75
+ **Generate a schema:**
76
+
77
+ ```python
78
+ from dicompare.io import read_dicom_session
79
+
80
+ reference_fields = ["EchoTime", "RepetitionTime"]
81
+ acquisition_fields = ["ProtocolName", "SeriesDescription"]
82
+
83
+ session_data = read_dicom_session(
84
+ session_dir="/path/to/dicom/session",
85
+ acquisition_fields=acquisition_fields,
86
+ reference_fields=reference_fields
87
+ )
88
+
89
+ # Save the schema as JSON
90
+ import json
91
+ with open("schema.json", "w") as f:
92
+ json.dump(session_data, f, indent=4)
93
+ ```
94
+
95
+ **Validate a session:**
96
+
97
+ ```python
98
+ from dicompare.io import read_json_session, read_dicom_session
99
+ from dicompare.compliance import check_session_compliance
100
+
101
+ # Load the schema
102
+ acquisition_fields, reference_fields, ref_session = read_json_session(json_ref="schema.json")
103
+
104
+ # Read the input session
105
+ in_session = read_dicom_session(
106
+ session_dir="/path/to/dicom/session",
107
+ acquisition_fields=acquisition_fields,
108
+ reference_fields=reference_fields
109
+ )
110
+
111
+ # Perform compliance check
112
+ compliance_summary = check_session_compliance(
113
+ in_session=in_session,
114
+ ref_session=ref_session,
115
+ series_map=None # Optional: map series if needed
116
+ )
117
+
118
+ # Print compliance summary
119
+ for entry in compliance_summary:
120
+ print(entry)
121
+ ```
122
+
@@ -0,0 +1,102 @@
1
+ # dicompare
2
+
3
+ [![](img/button.png)](https://astewartau.github.io/dicompare/)
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, leveraging WebAssembly (WASM), Pyodide, and the underlying pip package [`dicompare`](#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:
8
+
9
+ - **Session schemas**: JSON schema files that can be generated based on a reference session;
10
+ - **[UNDER CONSTRUCTION] 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;
11
+ - **[UNDER CONSTRUCTION] domain guidelines**: Flexible guidelines for domains such as [QSM](https://doi.org/10.1002/mrm.30006).
12
+
13
+ # Command-line interface (CLI) and application programming interface (API)
14
+
15
+ While you can run [dicompare](https://astewartau.github.io/dicompare/) 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 the following CLI entry points:
24
+
25
+ - `dcm-gen-session`: Generate JSON schemas for DICOM validation.
26
+ - `dcm-check-session`: Validate DICOM sessions against predefined schemas.
27
+
28
+ 1. Generate a JSON Reference Schema
29
+
30
+ ```bash
31
+ dcm-gen-session \
32
+ --in_session_dir /path/to/dicom/session \
33
+ --out_json_ref schema.json \
34
+ --acquisition_fields ProtocolName SeriesDescription \
35
+ --reference_fields EchoTime RepetitionTime
36
+ ```
37
+
38
+ This will create a JSON schema describing the session based on the specified fields.
39
+
40
+ 2. Validate a DICOM Session
41
+
42
+ ```bash
43
+ dicompare-session \
44
+ --in_session /path/to/dicom/session \
45
+ --json_ref schema.json \
46
+ --out_json compliance_report.json
47
+ ```
48
+
49
+ The tool will output a compliance summary, indicating deviations from the reference schema.
50
+
51
+ ## Python API
52
+
53
+ The `dicompare` package provides a Python API for programmatic schema generation and validation.
54
+
55
+ **Generate a schema:**
56
+
57
+ ```python
58
+ from dicompare.io import read_dicom_session
59
+
60
+ reference_fields = ["EchoTime", "RepetitionTime"]
61
+ acquisition_fields = ["ProtocolName", "SeriesDescription"]
62
+
63
+ session_data = read_dicom_session(
64
+ session_dir="/path/to/dicom/session",
65
+ acquisition_fields=acquisition_fields,
66
+ reference_fields=reference_fields
67
+ )
68
+
69
+ # Save the schema as JSON
70
+ import json
71
+ with open("schema.json", "w") as f:
72
+ json.dump(session_data, f, indent=4)
73
+ ```
74
+
75
+ **Validate a session:**
76
+
77
+ ```python
78
+ from dicompare.io import read_json_session, read_dicom_session
79
+ from dicompare.compliance import check_session_compliance
80
+
81
+ # Load the schema
82
+ acquisition_fields, reference_fields, ref_session = read_json_session(json_ref="schema.json")
83
+
84
+ # Read the input session
85
+ in_session = read_dicom_session(
86
+ session_dir="/path/to/dicom/session",
87
+ acquisition_fields=acquisition_fields,
88
+ reference_fields=reference_fields
89
+ )
90
+
91
+ # Perform compliance check
92
+ compliance_summary = check_session_compliance(
93
+ in_session=in_session,
94
+ ref_session=ref_session,
95
+ series_map=None # Optional: map series if needed
96
+ )
97
+
98
+ # Print compliance summary
99
+ for entry in compliance_summary:
100
+ print(entry)
101
+ ```
102
+
@@ -0,0 +1,7 @@
1
+ __version__ = "0.1.8"
2
+
3
+ # Import core functionalities
4
+ from .io import get_dicom_values, load_dicom, load_json_session, load_dicom_session, load_python_session
5
+ from .compliance import check_session_compliance_with_json_reference, check_session_compliance_with_python_module, check_dicom_compliance, is_session_compliant, is_dicom_compliant
6
+ from .mapping import map_to_json_reference, interactive_mapping_to_json_reference, interactive_mapping_to_python_reference
7
+ from .validation import BaseValidationModel, ValidationError, validator
File without changes
@@ -0,0 +1,93 @@
1
+ import sys
2
+ import json
3
+ import argparse
4
+ import pandas as pd
5
+
6
+ from dicompare.io import load_json_session, load_python_session, load_dicom_session
7
+ from dicompare.compliance import check_session_compliance_with_json_reference, check_session_compliance_with_python_module
8
+ from dicompare.mapping import map_to_json_reference, interactive_mapping_to_json_reference, interactive_mapping_to_python_reference
9
+
10
+ def main():
11
+ parser = argparse.ArgumentParser(description="Generate compliance summaries for a DICOM session.")
12
+ parser.add_argument("--json_ref", help="Path to the JSON reference file.")
13
+ parser.add_argument("--python_ref", help="Path to the Python module containing validation models.")
14
+ parser.add_argument("--in_session", required=True, help="Directory path for the DICOM session.")
15
+ parser.add_argument("--out_json", default="compliance_report.json", help="Path to save the JSON compliance summary report.")
16
+ parser.add_argument("--auto_yes", action="store_true", help="Automatically map acquisitions to series.")
17
+ args = parser.parse_args()
18
+
19
+ if not (args.json_ref or args.python_ref):
20
+ raise ValueError("You must provide either --json_ref or --python_ref.")
21
+
22
+ # Load the reference models and fields
23
+ if args.json_ref:
24
+ acquisition_fields, reference_fields, ref_session = load_json_session(json_ref=args.json_ref)
25
+ elif args.python_ref:
26
+ ref_models = load_python_session(module_path=args.python_ref)
27
+ acquisition_fields = ["ProtocolName"]
28
+
29
+ # Load the input session
30
+ in_session = load_dicom_session(
31
+ session_dir=args.in_session,
32
+ acquisition_fields=acquisition_fields,
33
+ )
34
+
35
+ if args.json_ref:
36
+ # Group by all existing unique combinations of reference fields
37
+ in_session = (
38
+ in_session.groupby(reference_fields)
39
+ .apply(lambda x: x.reset_index(drop=True))
40
+ .reset_index(drop=True) # Reset the index to avoid index/column ambiguity
41
+ )
42
+
43
+ # Assign unique group numbers for each combination of reference fields
44
+ in_session["Series"] = (
45
+ in_session.groupby(reference_fields, dropna=False).ngroup().add(1).apply(lambda x: f"Series {x}")
46
+ )
47
+
48
+ if args.json_ref:
49
+ session_map = map_to_json_reference(in_session, ref_session)
50
+ if not args.auto_yes and sys.stdin.isatty():
51
+ session_map = interactive_mapping_to_json_reference(in_session, ref_session, initial_mapping=session_map)
52
+ else:
53
+ session_map = interactive_mapping_to_python_reference(in_session, ref_models)
54
+
55
+
56
+ # Perform compliance check
57
+ if args.json_ref:
58
+ compliance_summary = check_session_compliance_with_json_reference(
59
+ in_session=in_session,
60
+ ref_session=ref_session,
61
+ session_map=session_map
62
+ )
63
+ else:
64
+ compliance_summary = check_session_compliance_with_python_module(
65
+ in_session=in_session,
66
+ ref_models=ref_models,
67
+ session_map=session_map
68
+ )
69
+ compliance_df = pd.DataFrame(compliance_summary)
70
+
71
+ # If compliance_df is empty, print message and exit
72
+ if compliance_df.empty:
73
+ print("Session is fully compliant with the reference model.")
74
+ return
75
+
76
+ # Inline summary output
77
+ for entry in compliance_summary:
78
+ if entry.get('acquisition'): print(f"Acquisition: {entry.get('acquisition')}")
79
+ if entry.get('field'): print(f"Field: {entry.get('field')}")
80
+ if entry.get('value'): print(f"Value: {entry.get('value')}")
81
+ if entry.get('rule'): print(f"Rule: {entry.get('rule')}")
82
+ if entry.get('message'): print(f"Message: {entry.get('message')}")
83
+ if entry.get('passed'): print(f"Passed: {entry.get('passed')}")
84
+ print("-" * 40)
85
+
86
+ # Save compliance summary to JSON
87
+ if args.out_json:
88
+ with open(args.out_json, "w") as f:
89
+ json.dump(compliance_summary, f)
90
+
91
+ if __name__ == "__main__":
92
+ main()
93
+
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python
2
+
3
+ import argparse
4
+ import json
5
+ import pandas as pd
6
+ from dicompare.io import load_dicom_session
7
+ from dicompare.utils import clean_string
8
+
9
+ def make_hashable(value):
10
+ """
11
+ Convert a value into a hashable format.
12
+ Handles lists, dictionaries, and other non-hashable types.
13
+ """
14
+ if isinstance(value, list):
15
+ return tuple(value)
16
+ elif isinstance(value, dict):
17
+ return tuple((k, make_hashable(v)) for k, v in value.items())
18
+ elif isinstance(value, set):
19
+ return tuple(sorted(make_hashable(v) for v in value))
20
+ return value
21
+
22
+
23
+ def create_json_reference(session_df, acquisition_fields, reference_fields, name_template="{ProtocolName}"):
24
+ """
25
+ Create a JSON reference from the session DataFrame.
26
+
27
+ Args:
28
+ session_df (pd.DataFrame): DataFrame of the DICOM session.
29
+ acquisition_fields (List[str]): Fields to uniquely identify each acquisition.
30
+ reference_fields (List[str]): Fields to include in JSON reference.
31
+ name_template (str): Naming template for acquisitions/series.
32
+
33
+ Returns:
34
+ dict: JSON structure representing the reference.
35
+ """
36
+ # Ensure all values in the DataFrame are hashable
37
+ for col in session_df.columns:
38
+ session_df[col] = session_df[col].apply(make_hashable)
39
+
40
+ json_reference = {"acquisitions": {}}
41
+
42
+ # Group by acquisition
43
+ for acquisition_name, group in session_df.groupby("Acquisition"):
44
+ acquisition_entry = {"fields": [], "series": []}
45
+
46
+ # Add acquisition-level fields
47
+ for field in acquisition_fields:
48
+ unique_values = group[field].dropna().unique()
49
+ if len(unique_values) == 1:
50
+ acquisition_entry["fields"].append({"field": field, "value": unique_values[0]})
51
+
52
+ # Group by series within each acquisition
53
+ series_fields = list(set(reference_fields) - set(acquisition_fields))
54
+ if series_fields:
55
+ series_groups = group.groupby(series_fields, dropna=False)
56
+
57
+ for i, (series_key, series_group) in enumerate(series_groups, start=1):
58
+ series_entry = {
59
+ "name": f"Series {i}",
60
+ "fields": [{"field": field, "value": series_key[j]} for j, field in enumerate(series_fields)]
61
+ }
62
+ acquisition_entry["series"].append(series_entry)
63
+
64
+ # Exclude reference fields from acquisition-level fields if they appear in series
65
+ acquisition_entry["fields"] = [
66
+ field for field in acquisition_entry["fields"] if field["field"] not in reference_fields
67
+ ]
68
+
69
+ # Add to JSON reference
70
+ json_reference["acquisitions"][clean_string(acquisition_name)] = acquisition_entry
71
+
72
+ return json_reference
73
+
74
+
75
+
76
+ def main():
77
+ parser = argparse.ArgumentParser(description="Generate a JSON reference for DICOM compliance.")
78
+ parser.add_argument("--in_session_dir", required=True, help="Directory containing DICOM files for the session.")
79
+ parser.add_argument("--out_json_ref", required=True, help="Path to save the generated JSON reference.")
80
+ parser.add_argument("--acquisition_fields", nargs="+", required=True, help="Fields to uniquely identify each acquisition.")
81
+ parser.add_argument("--reference_fields", nargs="+", required=True, help="Fields to include in JSON reference with their values.")
82
+ parser.add_argument("--name_template", default="{ProtocolName}", help="Naming template for each acquisition series.")
83
+ args = parser.parse_args()
84
+
85
+ # Read DICOM session
86
+ session_data = load_dicom_session(
87
+ session_dir=args.in_session_dir,
88
+ acquisition_fields=args.acquisition_fields,
89
+ )
90
+
91
+ # Filter fields in DataFrame
92
+ relevant_fields = set(args.acquisition_fields + args.reference_fields)
93
+ session_data = session_data[list(relevant_fields.intersection(session_data.columns)) + ["Acquisition"]]
94
+
95
+ # Generate JSON reference
96
+ json_reference = create_json_reference(
97
+ session_df=session_data,
98
+ acquisition_fields=args.acquisition_fields,
99
+ reference_fields=args.reference_fields,
100
+ name_template=args.name_template,
101
+ )
102
+
103
+ # Write JSON to output file
104
+ with open(args.out_json_ref, "w") as f:
105
+ json.dump(json_reference, f, indent=4)
106
+ print(f"JSON reference saved to {args.out_json_ref}")
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,18 @@
1
+ import os
2
+ import webbrowser
3
+ from importlib.resources import files
4
+
5
+ def main():
6
+ # Get the package base directory using `importlib.resources`
7
+ package_dir = files("dicompare").joinpath("docs", "index.html")
8
+
9
+ # Convert the resource path to an absolute file path
10
+ docs_path = str(package_dir)
11
+
12
+ # Check if the file exists
13
+ if not os.path.exists(docs_path):
14
+ print(f"Error: Documentation not found at {docs_path}.")
15
+ return
16
+
17
+ # Open the documentation in the default web browser
18
+ webbrowser.open(f"file://{docs_path}")