dcmspec 0.2.1__py3-none-any.whl

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 (45) hide show
  1. dcmspec/__init__.py +0 -0
  2. dcmspec/apps/__init__.py +1 -0
  3. dcmspec/apps/cli/__init__.py +0 -0
  4. dcmspec/apps/cli/dataelements.py +89 -0
  5. dcmspec/apps/cli/iodattributes.py +125 -0
  6. dcmspec/apps/cli/iodmodules.py +92 -0
  7. dcmspec/apps/cli/modattributes.py +265 -0
  8. dcmspec/apps/cli/tdwiicontent.py +365 -0
  9. dcmspec/apps/cli/uidvalues.py +84 -0
  10. dcmspec/apps/cli/upsdimseattributes.py +109 -0
  11. dcmspec/apps/cli/upsioddimseattributes.py +330 -0
  12. dcmspec/apps/ui/iod_explorer/README.md +33 -0
  13. dcmspec/apps/ui/iod_explorer/__init__.py +9 -0
  14. dcmspec/apps/ui/iod_explorer/config/README.md +171 -0
  15. dcmspec/apps/ui/iod_explorer/config/iod_explorer_config.json +4 -0
  16. dcmspec/apps/ui/iod_explorer/config/iod_explorer_config_debug.json +4 -0
  17. dcmspec/apps/ui/iod_explorer/config/iod_explorer_config_example.json +4 -0
  18. dcmspec/apps/ui/iod_explorer/config/iod_explorer_config_minimal_logging.json +4 -0
  19. dcmspec/apps/ui/iod_explorer/iod_explorer.py +989 -0
  20. dcmspec/config.py +90 -0
  21. dcmspec/csv_table_spec_parser.py +85 -0
  22. dcmspec/doc_handler.py +214 -0
  23. dcmspec/dom_table_spec_parser.py +831 -0
  24. dcmspec/dom_utils.py +116 -0
  25. dcmspec/iod_spec_builder.py +444 -0
  26. dcmspec/iod_spec_printer.py +59 -0
  27. dcmspec/json_spec_store.py +110 -0
  28. dcmspec/module_registry.py +51 -0
  29. dcmspec/pdf_doc_handler.py +451 -0
  30. dcmspec/progress.py +232 -0
  31. dcmspec/service_attribute_defaults.py +124 -0
  32. dcmspec/service_attribute_model.py +231 -0
  33. dcmspec/spec_factory.py +451 -0
  34. dcmspec/spec_merger.py +536 -0
  35. dcmspec/spec_model.py +461 -0
  36. dcmspec/spec_parser.py +37 -0
  37. dcmspec/spec_printer.py +131 -0
  38. dcmspec/spec_store.py +50 -0
  39. dcmspec/ups_xhtml_doc_handler.py +117 -0
  40. dcmspec/xhtml_doc_handler.py +182 -0
  41. dcmspec-0.2.1.dist-info/METADATA +139 -0
  42. dcmspec-0.2.1.dist-info/RECORD +45 -0
  43. dcmspec-0.2.1.dist-info/WHEEL +4 -0
  44. dcmspec-0.2.1.dist-info/entry_points.txt +11 -0
  45. dcmspec-0.2.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,365 @@
1
+ """CLI for extracting and printing the TDW-II content specifications from the IHE-RO Supplement.
2
+
3
+ IHE profiles define additional requirements for DICOM Services and Objects.
4
+ IOD specifications (which modules are required for each IOD) are defined in section 7.3, "IOD Definitions."
5
+ Module specifications (which attributes are defined within each module) are found in section 7.4, "Module Definitions."
6
+ In IHE-RO profiles, module specifications are organized into two parts:
7
+ - a base definition, which applies to all transactions or content definitions where the module is used
8
+ - an extension, which applies only to a particular transaction or content definition.
9
+
10
+ This CLI tool focuses on the IHE-RO TDW-II workflow profile and extract the content definitions relevant to each
11
+ transaction.
12
+
13
+ Features:
14
+ - Download and parse TDW-II module base and extension definition tables from the IHE-RO Supplement in PDF format.
15
+ - Extract and print the module definition as a table and a tree.
16
+ - Cache the resulting model as a JSON file for future runs and as a structured representation of the table.
17
+ - Supports configuration, caching, and command-line options for flexible workflows.
18
+
19
+ Usage:
20
+ poetry run python -m src.dcmspec.apps.cli.tdwiimoddefinition [options]
21
+
22
+ For more details, use the --help option.
23
+ """
24
+
25
+ import argparse
26
+ from copy import deepcopy
27
+ import logging
28
+
29
+ from anytree import Node
30
+
31
+ from dcmspec.json_spec_store import JSONSpecStore
32
+ from dcmspec.pdf_doc_handler import PDFDocHandler
33
+ from dcmspec.csv_table_spec_parser import CSVTableSpecParser
34
+ from dcmspec.spec_factory import SpecFactory
35
+ from dcmspec.spec_model import SpecModel
36
+ from dcmspec.spec_printer import SpecPrinter
37
+
38
+ # Configurations for each content definition
39
+ CONTENT_DEFINITION_CONFIGS = {
40
+ "ups_scheduled": {
41
+ "page_numbers": [57, 58],
42
+ "table_indices": [(57, 1), (58, 0)],
43
+ "table_id": "tdwii_ups_scheduled_info",
44
+ "column_to_attr": {0: "elem_name", 1: "elem_tag", 2: "elem_type", 3: "elem_description"},
45
+ },
46
+ "ups_performed": {
47
+ "page_numbers": [61, 62],
48
+ "table_indices": [(61, 0), (62, 0)],
49
+ "table_id": "tdwii_ups_performed_info",
50
+ "column_to_attr": {0: "elem_name", 1: "elem_tag", 2: "elem_type", 3: "elem_description"},
51
+ },
52
+ "ups_progress": {
53
+ "page_numbers": [64],
54
+ "table_indices": [(64,0)],
55
+ "table_header_rowspan": {(64,0): 1},
56
+ "table_id": "tdwii_ups_progress_info",
57
+ "column_to_attr": {0: "elem_name", 1: "elem_tag", 2: "elem_type", 3: "elem_description"},
58
+ },
59
+ "ups_relationship": {
60
+ "page_numbers": [60],
61
+ "table_indices": [(60, 1)],
62
+ "table_id": "tdwii_ups_relationship_info",
63
+ "column_to_attr": {0: "elem_name", 1: "elem_tag", 2: "elem_type", 3: "elem_description"},
64
+ },
65
+ "ups_query": {
66
+ "page_numbers": [63],
67
+ "table_indices": [(63, 0)],
68
+ "table_header_rowspan": {(63, 0): 2},
69
+ "table_id": "tdwii_ups_query_info",
70
+ "column_to_attr": {
71
+ 0: "elem_name",
72
+ 1: "elem_tag",
73
+ 2: "key_matching_scu",
74
+ 3: "key_matching_scp",
75
+ 4: "key_return_scu",
76
+ 5: "key_return_scp",
77
+ },
78
+ },
79
+ "rt_bdi": {
80
+ "page_numbers": [55, 56, 57],
81
+ "table_indices": [(55, 1), (56, 0), (57, 0)],
82
+ "table_id": "tdwii_bdi_instruction_info",
83
+ "column_to_attr": {0: "elem_name", 1: "elem_tag", 2: "elem_type", 3: "elem_description"},
84
+ },
85
+ }
86
+
87
+ def _extract_module_definition(
88
+ pdf_file,
89
+ url,
90
+ page_numbers,
91
+ table_indices,
92
+ table_header_rowspan,
93
+ table_id,
94
+ column_to_attr,
95
+ logger,
96
+ force_parse=False,
97
+ ):
98
+ """Extract and return the TDW-II module definition model."""
99
+ factory = SpecFactory(
100
+ input_handler=PDFDocHandler(logger=logger, extractor="pdfplumber"),
101
+ table_parser=CSVTableSpecParser(logger=logger),
102
+ column_to_attr=column_to_attr,
103
+ name_attr="elem_name",
104
+ logger=logger
105
+ )
106
+
107
+ handler_kwargs = {
108
+ "page_numbers": page_numbers,
109
+ "table_indices": table_indices,
110
+ "table_header_rowspan": table_header_rowspan,
111
+ "table_id": table_id,
112
+ }
113
+
114
+ return factory.create_model(
115
+ url=url,
116
+ cache_file_name=pdf_file,
117
+ table_id=table_id,
118
+ force_parse=force_parse,
119
+ json_file_name=f"{table_id}.json",
120
+ handler_kwargs=handler_kwargs,
121
+ )
122
+
123
+ def _parse_args():
124
+ """Parse and return command-line arguments for the TDW-II CLI."""
125
+ parser = argparse.ArgumentParser(description="Extract and print TDW-II UPS module information tables.")
126
+ parser.add_argument(
127
+ "content_definition",
128
+ choices=[
129
+ "ups_create",
130
+ "ups_performed",
131
+ "ups_progress",
132
+ "ups_query",
133
+ "rt_bdi"
134
+ # Add more content_definition names here as you add configs
135
+ ],
136
+ help=(
137
+ "Select which content definition to extract: "
138
+ "'ups_create', 'ups_query', 'ups_progress', 'ups_performed', 'rt_bdi'"
139
+ )
140
+ )
141
+ parser.add_argument("-d", "--debug", action="store_true", help="Enable debug logging")
142
+ parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
143
+ return parser.parse_args()
144
+
145
+ def _setup_logger(debug=False, verbose=False):
146
+ """Set up and return a logger with the appropriate log level."""
147
+ if debug:
148
+ log_level = logging.DEBUG
149
+ elif verbose:
150
+ log_level = logging.INFO
151
+ else:
152
+ log_level = logging.WARNING
153
+
154
+ logging.basicConfig(
155
+ level=log_level,
156
+ format="%(asctime)s - %(levelname)s - %(message)s"
157
+ )
158
+
159
+ # Suppress PDFMiner debug logs
160
+ logging.getLogger("pdfminer").setLevel(logging.WARNING)
161
+
162
+ logger = logging.getLogger("tdwiimoddefinition")
163
+ logger.setLevel(log_level) # <-- This ensures the logger is always at the correct level
164
+
165
+ return logger
166
+
167
+ def _extract_ups_create_model(pdf_file, url, logger):
168
+ """Extract and combine UPS Scheduled and Relationship module definition."""
169
+ base_config = CONTENT_DEFINITION_CONFIGS["ups_scheduled"]
170
+ base_config_part2 = CONTENT_DEFINITION_CONFIGS["ups_relationship"]
171
+
172
+ scheduled_model = _extract_module_definition(
173
+ pdf_file=pdf_file,
174
+ url=url,
175
+ page_numbers=base_config["page_numbers"],
176
+ table_indices=base_config["table_indices"],
177
+ table_header_rowspan=base_config.get("table_header_rowspan", None),
178
+ table_id=base_config["table_id"],
179
+ column_to_attr=base_config["column_to_attr"],
180
+ logger=logger,
181
+ force_parse=False,
182
+ )
183
+ relationship_model = _extract_module_definition(
184
+ pdf_file=pdf_file,
185
+ url=url,
186
+ page_numbers=base_config_part2["page_numbers"],
187
+ table_indices=base_config_part2["table_indices"],
188
+ table_header_rowspan=base_config.get("table_header_rowspan", None),
189
+ table_id=base_config_part2["table_id"],
190
+ column_to_attr=base_config_part2["column_to_attr"],
191
+ logger=logger,
192
+ force_parse=False,
193
+ )
194
+ # Combine the two models under a new root node
195
+ combined_root = Node("content")
196
+ # Attach all children of scheduled_model.content
197
+ for child in list(scheduled_model.content.children):
198
+ child.parent = combined_root
199
+ # Attach all children of relationship_model.content
200
+ for child in list(relationship_model.content.children):
201
+ child.parent = combined_root
202
+
203
+ # Use metadata from scheduled_model, but set a new table_id for the combined model
204
+ combined_metadata = deepcopy(scheduled_model.metadata)
205
+ combined_metadata.table_id = "tdwii_ups_create_info"
206
+ return SpecModel(
207
+ metadata=combined_metadata,
208
+ content=combined_root,
209
+ logger=logger,
210
+ )
211
+
212
+ def _apply_hard_coded_extensions(model, content_definition_name):
213
+ """Apply hard-coded extensions to the base model for specific modules."""
214
+ for node in model.content.children:
215
+ elem_name = getattr(node, "elem_name", "").strip()
216
+
217
+ if content_definition_name == "ups_create":
218
+ if elem_name == "Scheduled Workitem Code Sequence":
219
+ for child in node.children:
220
+ child_name = getattr(child, "elem_name", "").strip()
221
+ if child_name == ">Code Value":
222
+ child.elem_description = 'shall be equal to "121726"'
223
+ elif child_name == ">Coding Scheme Designator":
224
+ child.elem_description = 'shall be equal to "DCM"'
225
+ elif child_name == ">Code Meaning":
226
+ child.elem_description = 'shall be equal to "RT Treatment with Internal Verification"'
227
+ continue
228
+ if elem_name == "Input Information Sequence":
229
+ node.elem_description = (
230
+ "Shall contain at least 2 Referenced DICOM Instances:"
231
+ "\nRT Plan Storage or RT Ion Plan Storage retrieved from OST."
232
+ "\nRT Beams Delivery Instruction Storage retrieved from TMS."
233
+ "\nShall contain more Referenced DICOM Instances if Treatment Delivery Type is equal to "
234
+ "CONTINUATION:"
235
+ "\nRT Beams Treatment Record Storage or RT Ion Beams Treatment Record Storage retrieved from OST."
236
+ "\nMay contain additional items for other reasons, which are out of scope for this profile"
237
+ )
238
+ continue
239
+ if elem_name == "Scheduled Processing Parameters Sequence":
240
+ node.elem_description = (
241
+ "Shall include 4 Content Items per Template:"
242
+ '\nEV (121740, DCM, "Treatment Delivery Type"), VT:TEXT (TREAMENT or CONTINUATION).'
243
+ '\nEV (2018001, 99IHERO2018, "Plan Label"), VT:TEXT (RT Plan Label (300A,0002) value).'
244
+ '\nEV (2018002, 99IHERO2018, "Current Fraction Number"), VT:NUMERIC '
245
+ "(Current Fraction Number (3008,0022) value)."
246
+ '\nEV (2018003, 99IHERO2018, "Number of Fractions Planned"), VT:NUMERIC '
247
+ "(Number of Fractions Planned (300A,0078) value)."
248
+ )
249
+ continue
250
+
251
+ if content_definition_name == "ups_performed":
252
+ if elem_name == ">Performed Workitem Code Sequence":
253
+ for child in node.children:
254
+ child_name = getattr(child, "elem_name", "").strip()
255
+ if child_name == ">Code Value":
256
+ child.elem_description = 'shall be equal to "121726"'
257
+ elif child_name == ">Coding Scheme Designator":
258
+ child.elem_description = 'shall be equal to "DCM"'
259
+ elif child_name == ">Code Meaning":
260
+ child.elem_description = 'shall be equal to "RT Treatment with Internal Verification"'
261
+ continue
262
+ if elem_name == ">Output Information Sequence":
263
+ node.elem_description = (
264
+ "Shall contain at least 1 item if any therapeutic treatment was delivered to the patient:"
265
+ "\nRT Beams Treatment Record Storage (1.2.840.10008.5.1.4.1.1.481.4) or "
266
+ "RT Ion Beams Treatment Record Storage (1.2.840.10008.5.1.4.1.1.481.9) stored to OST."
267
+ "\nMay be present otherwise."
268
+ )
269
+ continue
270
+
271
+ if content_definition_name == "ups_progress" and elem_name == "Procedure Step Progress Information Sequence":
272
+ # Add missing Procedure Step Progress Parameters Sequence to the model
273
+ Node(
274
+ "procedure_step_progress_parameters_sequence",
275
+ parent=node,
276
+ elem_name=">Procedure Step Progress Parameters Sequence",
277
+ elem_tag="(0074,1007)",
278
+ elem_type="R+*",
279
+ elem_description=(
280
+ "Shall include 1 Content Item per Template:"
281
+ '\nEV (2018004, 99IHERO2018, "Referenced Beam Number"), VT:NUM '
282
+ "(Beam Number (300A,00C0) value of beam in progress)."
283
+ ),
284
+ )
285
+ continue
286
+
287
+ def main():
288
+ """CLI for extracting and printing the TDW-II Content Definitions from the IHE-RO Supplement.
289
+
290
+ This CLI downloads, parses, and prints the TDW-II Content Definitions from the IHE-RO Supplement (PDF).
291
+ The tool extracts the relevant table(s) from the PDF, parses the content definition, and outputs the result as a
292
+ table and a tree.
293
+
294
+ The resulting model is cached as a JSON file. The primary purpose of this cache file is to provide a structured,
295
+ machine-readable representation of the content definition, which can be used for further processing or integration
296
+ in other tools. As a secondary benefit, the cache file is also used to speed up subsequent runs of the CLI scripts.
297
+
298
+ Usage:
299
+ poetry run python -m src.dcmspec.apps.cli.tdwiicontent <content_definition> [options]
300
+
301
+ Where <content_definition> is one of:
302
+ - ups_create: content of scheduled UPS creation (combined UPS Scheduled and Relationship definitions).
303
+ - ups_query: content of C-FIND identifier for UPS Query transaction.
304
+ - ups_progress: content of N-SET dataset for UPS Progress Update transaction.
305
+ - ups_performed: content of N-SET dataset for UPS Final Status Update transaction.
306
+ - rt_bdi: content of RT Beam Delivery Instruction Module.
307
+
308
+ Options:
309
+ -d, --debug: Enable debug logging.
310
+ -v, --verbose: Enable verbose output.
311
+
312
+ Example:
313
+ poetry run python -m src.dcmspec.apps.cli.tdwiicontent ups_progress --debug
314
+
315
+ For more details, use the --help option.
316
+
317
+ """
318
+ args = _parse_args()
319
+ logger = _setup_logger(debug=args.debug, verbose=args.verbose)
320
+
321
+ logger.info(f"Extracting TDW-II module definition for: {args.content_definition}")
322
+ logger.debug(f"Arguments: {args}")
323
+
324
+ # TDW-II Supplement PDF and URL (shared for all module extractions)
325
+ pdf_file = "IHE_RO_Suppl_TDW_II.pdf"
326
+ url = "https://www.ihe.net/uploadedFiles/Documents/Radiation_Oncology/IHE_RO_Suppl_TDW_II.pdf"
327
+
328
+ # Special handling for ups_create: combine ups_scheduled and ups_relationship models
329
+ if args.content_definition == "ups_create":
330
+ base_model = _extract_ups_create_model(pdf_file, url, logger)
331
+ else:
332
+ # Select config based on argument
333
+ base_config = CONTENT_DEFINITION_CONFIGS[args.content_definition]
334
+
335
+ # Extract base and extension models
336
+ base_model = _extract_module_definition(
337
+ pdf_file=pdf_file,
338
+ url=url,
339
+ page_numbers=base_config["page_numbers"],
340
+ table_indices=base_config["table_indices"],
341
+ table_header_rowspan=base_config.get("table_header_rowspan", None),
342
+ table_id=base_config["table_id"],
343
+ column_to_attr=base_config["column_to_attr"],
344
+ logger=logger,
345
+ force_parse=False,
346
+ )
347
+
348
+ # Apply hard-coded extensions for specific modules when the extension specifications is not structured
349
+ # as a single table in the PDF document
350
+ _apply_hard_coded_extensions(base_model, args.content_definition)
351
+
352
+ # Cache the final model (base or combined, with hard-coded extensions)
353
+ json_store = JSONSpecStore(logger=logger)
354
+ cache_file_name = f"{getattr(base_model.metadata, 'table_id', 'unknown')}.json"
355
+ cache_path = f"./cache/model/{cache_file_name}"
356
+ json_store.save(base_model, cache_path)
357
+ logger.info(f"Final model saved as JSON to {cache_path}")
358
+
359
+ # Print the model
360
+ printer = SpecPrinter(base_model, logger=logger)
361
+ printer.print_table(colorize=True)
362
+ printer.print_tree(attr_names=["elem_name", "elem_tag"], colorize=True)
363
+
364
+ if __name__ == "__main__":
365
+ main()
@@ -0,0 +1,84 @@
1
+ """CLI for extracting, caching, and printing DICOM Unique Identifiers (UIDs) from Part 6.
2
+
3
+ Features:
4
+ - Download and parse DICOM UID Values table from Part 6 of the DICOM standard.
5
+ - Cache the model as a JSON file for future runs and as a structured representation of the standard.
6
+ - Print the resulting UID Values as a table.
7
+ - Supports caching, configuration files, and command-line options for flexible workflows.
8
+
9
+ Usage:
10
+ poetry run python -m src.dcmspec.apps.cli.uidvalues [options]
11
+
12
+ For more details, use the --help option.
13
+ """
14
+
15
+ import os
16
+ import argparse
17
+ from dcmspec.config import Config
18
+
19
+ from dcmspec.spec_factory import SpecFactory
20
+ from dcmspec.spec_printer import SpecPrinter
21
+
22
+
23
+ def main():
24
+ """CLI for parsing, caching, and printing DICOM Unique Identifiers (UIDs) from Part 6.
25
+
26
+ This CLI downloads, caches, and prints the list of DICOM UIDs from Part 6 of the DICOM standard.
27
+
28
+ The tool parses the UID Values table to extract UID values, names, keywords, types, and additional information.
29
+ The output can be printed as a table.
30
+
31
+ The resulting model is cached as a JSON file. The primary purpose of this cache file is to provide a structured,
32
+ machine-readable representation of the DICOM UIDs, which can be used for further processing or integration in other
33
+ tools. As a secondary benefit, the cache file is also used to speed up subsequent runs of the CLI scripts.
34
+
35
+ Usage:
36
+ poetry run python -m src.dcmspec.apps.cli.uidvalues [options]
37
+
38
+ Options:
39
+ --config (str): Path to the configuration file.
40
+
41
+ Example:
42
+ poetry run python -m src.dcmspec.apps.cli.uidvalues
43
+
44
+ """
45
+ # Parse command-line arguments
46
+ parser = argparse.ArgumentParser()
47
+ parser.add_argument("--config", help="Path to the configuration file")
48
+ args = parser.parse_args()
49
+
50
+ # Determine config file location
51
+ config_file = args.config or os.getenv("DCMSPEC_CONFIG", None)
52
+
53
+ # Initialize configuration
54
+ config = Config(app_name="dcmspec", config_file=config_file)
55
+
56
+ url = "https://dicom.nema.org/medical/dicom/current/output/html/part06.html"
57
+ cache_file_name = "Part6.xhtml"
58
+ model_file_name = "UIDValues.json"
59
+
60
+ table_id = "table_A-1"
61
+
62
+ # Create the factory
63
+ factory = SpecFactory(
64
+ column_to_attr={0: "uid_value", 1: "uid_name", 2: "uid_keyword", 3: "uid_type", 4: "uid_part"},
65
+ name_attr="uid_value",
66
+ config=config
67
+ )
68
+
69
+ # Download, parse, and cache the model
70
+ model = factory.create_model(
71
+ url=url,
72
+ cache_file_name=cache_file_name,
73
+ json_file_name=model_file_name,
74
+ table_id=table_id,
75
+ force_download=False,
76
+ )
77
+
78
+ # Print the model as a table
79
+ printer = SpecPrinter(model)
80
+ printer.print_table(colorize=True)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()
@@ -0,0 +1,109 @@
1
+ """CLI for extracting, caching, and printing DICOM UPS DIMSE Service Attribute tables from Part 4.
2
+
3
+ Features:
4
+ - Download and parse UPS DIMSE Service Attribute table from Part 4 of the DICOM standard.
5
+ - Select a specific DIMSE service and role for filtering.
6
+ - Cache the model as a JSON file for future runs and as a structured representation of the standard.
7
+ - Print the resulting attributes as a table.
8
+ - Supports caching, configuration files, and command-line options for flexible workflows.
9
+
10
+ Usage:
11
+ poetry run python -m src.dcmspec.apps.cli.upsdimseattributes [options]
12
+
13
+ For more details, use the --help option.
14
+ """
15
+
16
+ import os
17
+ import argparse
18
+ from dcmspec.config import Config
19
+
20
+ from dcmspec.service_attribute_model import ServiceAttributeModel
21
+ from dcmspec.spec_factory import SpecFactory
22
+ from dcmspec.spec_printer import SpecPrinter
23
+ from dcmspec.ups_xhtml_doc_handler import UPSXHTMLDocHandler
24
+ from dcmspec.service_attribute_defaults import UPS_DIMSE_MAPPING, UPS_COLUMNS_MAPPING, UPS_NAME_ATTR
25
+
26
+
27
+ def main():
28
+ """CLI for parsing, caching, and printing DICOM UPS DIMSE Service Attribute tables from Part 4.
29
+
30
+ This CLI downloads, caches, and prints the attributes for the UPS DIMSE services from Part 4 of the DICOM standard.
31
+
32
+ The tool parses the UPS Service Attribute table and allows selection of a specific DIMSE service (e.g., N-CREATE,
33
+ N-SET, N-GET, C-FIND, FINAL) and role (SCU or SCP). The output can be printed as a table.
34
+
35
+ The resulting model is cached as a JSON file. The primary purpose of this cache file is to provide a structured,
36
+ machine-readable representation of the UPS DIMSE service attributes, which can be used for further processing or
37
+ integration in other tools. As a secondary benefit, the cache file is also used to speed up subsequent runs of the
38
+ CLI scripts.
39
+
40
+ Usage:
41
+ poetry run python -m src.dcmspec.apps.cli.upsdimseattributes [options]
42
+
43
+ Options:
44
+ --config (str): Path to the configuration file.
45
+ --dimse (str): DIMSE service to select (ALL_DIMSE, N-CREATE, N-SET, N-GET, C-FIND, FINAL).
46
+ --role (str): Role to select (SCU or SCP). Only valid if --dimse is not ALL_DIMSE.
47
+
48
+ Example:
49
+ poetry run python -m src.dcmspec.apps.cli.upsdimseattributes --dimse N-CREATE --role SCU
50
+
51
+ """
52
+ # Parse command-line arguments
53
+ parser = argparse.ArgumentParser()
54
+ parser.add_argument("--config", help="Path to the configuration file")
55
+ parser.add_argument(
56
+ "--dimse",
57
+ choices=["ALL_DIMSE", "N-CREATE", "N-SET", "N-GET", "C-FIND", "FINAL"],
58
+ default="ALL_DIMSE",
59
+ help="DIMSE service to select (default: ALL_DIMSE)",
60
+ )
61
+ parser.add_argument(
62
+ "--role",
63
+ choices=["SCU", "SCP"],
64
+ help="Role to select (SCU or SCP)",
65
+ )
66
+ args = parser.parse_args()
67
+
68
+ # Determine config file location
69
+ config_file = args.config or os.getenv("DCMSPEC_CONFIG", None)
70
+
71
+ # Initialize configuration
72
+ config = Config(app_name="dcmspec", config_file=config_file)
73
+
74
+ url = "https://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_CC.2.5.html"
75
+ cache_file_name = "UPSattributes.xhtml"
76
+ table_id = "table_CC.2.5-3"
77
+
78
+ # Create the factory with UPSXHTMLDocHandler for UPS-specific table patching
79
+ factory = SpecFactory(
80
+ model_class=ServiceAttributeModel,
81
+ input_handler=UPSXHTMLDocHandler(config=config),
82
+ column_to_attr=UPS_COLUMNS_MAPPING,
83
+ name_attr=UPS_NAME_ATTR,
84
+ config=config
85
+ )
86
+
87
+ # Download, parse, and cache the model
88
+ model = factory.create_model(
89
+ url=url,
90
+ cache_file_name=cache_file_name,
91
+ table_id=table_id,
92
+ force_download=False,
93
+ model_kwargs={"dimse_mapping": UPS_DIMSE_MAPPING},
94
+ )
95
+
96
+ if args.dimse!= "ALL_DIMSE":
97
+ model.select_dimse(args.dimse)
98
+ if args.role:
99
+ if args.dimse == "ALL_DIMSE":
100
+ parser.error("--role option can only be used if --dimse is not ALL_DIMSE")
101
+ model.select_role(args.role)
102
+
103
+ # Print the model as a table
104
+ printer = SpecPrinter(model)
105
+ printer.print_table(colorize=True)
106
+
107
+
108
+ if __name__ == "__main__":
109
+ main()