-
Notifications
You must be signed in to change notification settings - Fork 12
/
__init__.py
184 lines (159 loc) · 6.73 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""
Welcome to the hazen Command Line Interface
The following Tasks are available:
- ACR phantom:
acr_snr | acr_slice_position | acr_slice_thickness | acr_spatial_resolution | acr_uniformity | acr_ghosting | acr_geometric_accuracy
- MagNET Test Objects:
snr | snr_map | slice_position | slice_width | spatial_resolution | uniformity | ghosting
- Caliber phantom:
relaxometry
All tasks can be run by executing 'hazen <task> <folder>'. Optional flags are available for the Tasks; see the General
Options section below. The 'acr_snr' and 'snr' Tasks have additional optional flags, also detailed below.
Usage:
hazen <task> <folder> [options]
hazen snr <folder> [--measured_slice_width=<mm>] [--coil=<head or body>] [options]
hazen acr_snr <folder> [--measured_slice_width=<mm>] [--subtract=<folder2>] [options]
hazen relaxometry <folder> --calc=<T1> --plate_number=<4> [options]
hazen -h | --help
hazen --version
General Options: available for all Tasks
--report Whether to generate visualisation of the measurement steps.
--output=<path> Provide a folder where report images are to be saved.
--verbose Whether to provide additional metadata about the calculation in the result (slice position and relaxometry tasks)
--log=<level> Set the level of logging based on severity. Available levels are "debug", "warning", "error", "critical", with "info" as default.
acr_snr & snr Task options:
--measured_slice_width=<mm> Provide a slice width to be used for SNR measurement, by default it is parsed from the DICOM (optional for acr_snr and snr)
--subtract=<folder2> Provide a second folder path to calculate SNR by subtraction for the ACR phantom (optional for acr_snr)
relaxometry Task options:
--calc=<n> Choose 'T1' or 'T2' for relaxometry measurement (required)
--plate_number=<n> Which plate to use for measurement: 4 or 5 (required)
"""
import sys
import json
import inspect
import logging
import importlib
from docopt import docopt
from hazenlib.utils import get_dicom_files
from hazenlib._version import __version__
"""
Hazen is designed to measure the same parameters from multiple images.
While some tasks require a set of multiple images (within the same folder),
such as slice position, SNR and all ACR tasks,
the majority of the calculations are performed on a single image at a time,
and bulk processing all images in the input folder with the same task.
In Sep 2023 a design decision was made to pass the minimum number of files
to the task.run() functions.
Below is a list of the single image tasks where the task.run() will be called
on each image in the folder, while other tasks are being passed ALL image files.
"""
single_image_tasks = [
"ghosting",
"uniformity",
"spatial_resolution",
"slice_width",
"snr_map",
]
def init_task(selected_task, files, report, report_dir, **kwargs):
"""Initialise object of the correct HazenTask class
Args:
selected_task (string): name of task script/module to load
files (list): list of filepaths to DICOM images
report (bool): whether to generate report images
report_dir (string): path to folder to save report images to
kwargs: any other key word arguments
Returns:
an object of the specified HazenTask class
"""
task_module = importlib.import_module(f"hazenlib.tasks.{selected_task}")
try:
task = getattr(task_module, selected_task.capitalize())(
input_data=files, report=report, report_dir=report_dir, **kwargs
)
except:
class_list = [
cls.__name__
for _, cls in inspect.getmembers(
sys.modules[task_module.__name__],
lambda x: inspect.isclass(x) and (x.__module__ == task_module.__name__),
)
]
if len(class_list) == 1:
task = getattr(task_module, class_list[0])(
input_data=files, report=report, report_dir=report_dir, **kwargs
)
else:
raise Exception(
f"Task {task_module} has multiple class definitions: {class_list}"
)
return task
def main():
"""Main entrypoint to hazen"""
arguments = docopt(__doc__, version=__version__)
files = get_dicom_files(arguments["<folder>"])
# Set common options
log_levels = {
"critical": logging.CRITICAL,
"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
}
if arguments["--log"] in log_levels.keys():
level = log_levels[arguments["--log"]]
logging.getLogger().setLevel(level)
else:
# logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)
report = arguments["--report"]
report_dir = arguments["--output"] if arguments["--output"] else None
verbose = arguments["--verbose"]
# Parse the task and optional arguments:
if arguments["snr"] or arguments["<task>"] == "snr":
selected_task = "snr"
task = init_task(
selected_task,
files,
report,
report_dir,
measured_slice_width=arguments["--measured_slice_width"],
coil=arguments["--coil"],
)
result = task.run()
elif arguments["acr_snr"] or arguments["<task>"] == "acr_snr":
selected_task = "acr_snr"
task = init_task(
selected_task,
files,
report,
report_dir,
subtract=arguments["--subtract"],
measured_slice_width=arguments["--measured_slice_width"],
)
result = task.run()
elif arguments["relaxometry"] or arguments["<task>"] == "relaxometry":
selected_task = "relaxometry"
task = init_task(selected_task, files, report, report_dir)
result = task.run(
calc=arguments["--calc"],
plate_number=arguments["--plate_number"],
verbose=arguments["--verbose"],
)
else:
selected_task = arguments["<task>"]
if selected_task in single_image_tasks:
# Ghosting, Uniformity, Spatial resolution, SNR map, Slice width
for file in files:
task = init_task(selected_task, [file], report, report_dir)
result = task.run()
result_string = json.dumps(result, indent=2)
print(result_string)
return
else:
# Slice Position task, all ACR tasks except SNR
task = init_task(selected_task, files, report, report_dir, verbose=verbose)
result = task.run()
result_string = json.dumps(result, indent=2)
print(result_string)
if __name__ == "__main__":
main()