Initial commit
This commit is contained in:
0
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__init__.py
vendored
Executable file
0
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__init__.py
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/__init__.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/__init__.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/codify.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/codify.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/main.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/main.cpython-313.pyc
vendored
Executable file
Binary file not shown.
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/show.cpython-313.pyc
vendored
Executable file
BIN
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/__pycache__/show.cpython-313.pyc
vendored
Executable file
Binary file not shown.
29
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/codify.py
vendored
Executable file
29
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/codify.py
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
# Copyright 2020 pydicom authors. See LICENSE file for details.
|
||||
"""Pydicom command line interface program for codify"""
|
||||
|
||||
import argparse
|
||||
|
||||
import pydicom.util.codify
|
||||
|
||||
|
||||
default_exclude_size = 100
|
||||
|
||||
|
||||
def add_subparser(subparsers: argparse._SubParsersAction) -> None:
|
||||
codify_parser = subparsers.add_parser(
|
||||
"codify",
|
||||
description=(
|
||||
"Read a DICOM file and produce the pydicom (Python) "
|
||||
"code which can create that file"
|
||||
),
|
||||
epilog=(
|
||||
"Binary data (e.g. pixels) larger than --exclude-size "
|
||||
f"(default {default_exclude_size} bytes) is not included. "
|
||||
"A dummy line with a syntax error is produced. "
|
||||
"Private data elements are not included by default."
|
||||
),
|
||||
)
|
||||
|
||||
# Codify existed before as a stand-alone before, re-use it here
|
||||
pydicom.util.codify.set_parser_arguments(codify_parser, default_exclude_size)
|
||||
codify_parser.set_defaults(func=pydicom.util.codify.do_codify)
|
||||
232
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/main.py
vendored
Executable file
232
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/main.py
vendored
Executable file
@@ -0,0 +1,232 @@
|
||||
# Copyright 2020 pydicom authors. See LICENSE file for details.
|
||||
"""Pydicom command line interface program
|
||||
|
||||
Each subcommand is a module within pydicom.cli, which
|
||||
defines an add_subparser(subparsers) function to set argparse
|
||||
attributes, and calls set_defaults(func=callback_function)
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from importlib.metadata import entry_points
|
||||
import re
|
||||
import sys
|
||||
from typing import cast, Any
|
||||
from collections.abc import Callable
|
||||
|
||||
from pydicom import dcmread
|
||||
from pydicom.data.data_manager import get_charset_files, get_testdata_file
|
||||
from pydicom.dataset import Dataset
|
||||
|
||||
|
||||
subparsers: argparse._SubParsersAction | None = None
|
||||
|
||||
|
||||
# Restrict the allowed syntax tightly, since use Python `eval`
|
||||
# on the expression. Do not allow callables, or assignment, for example.
|
||||
re_kywd_or_item = (
|
||||
r"\w+" # Keyword (\w allows underscore, needed for file_meta)
|
||||
r"(\[(-)?\d+\])?" # Optional [index] or [-index]
|
||||
)
|
||||
|
||||
re_file_spec_object = re.compile(re_kywd_or_item + r"(\." + re_kywd_or_item + r")*$")
|
||||
|
||||
filespec_help = (
|
||||
"File specification, in format [pydicom::]filename[::element]. "
|
||||
"If `pydicom::` prefix is present, then use the pydicom "
|
||||
"test file with that name. If `element` is given, "
|
||||
"use only that data element within the file. "
|
||||
"Examples: "
|
||||
"path/to/your_file.dcm, "
|
||||
"your_file.dcm::StudyDate, "
|
||||
"pydicom::rtplan.dcm::BeamSequence[0], "
|
||||
"yourplan.dcm::BeamSequence[0].BeamNumber"
|
||||
)
|
||||
|
||||
|
||||
def eval_element(ds: Dataset, element: str) -> Any:
|
||||
try:
|
||||
return eval("ds." + element, {"ds": ds})
|
||||
except AttributeError:
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"Data element '{element}' is not in the dataset"
|
||||
)
|
||||
except IndexError as e:
|
||||
raise argparse.ArgumentTypeError(f"'{element}' has an index error: {e}")
|
||||
|
||||
|
||||
def filespec_parts(filespec: str) -> tuple[str, str, str]:
|
||||
"""Parse the filespec format into prefix, filename, element
|
||||
|
||||
Format is [prefix::filename::element]
|
||||
|
||||
Note that ':' can also exist in valid filename, e.g. r'c:\temp\test.dcm'
|
||||
"""
|
||||
|
||||
*prefix_file, last = filespec.split("::")
|
||||
|
||||
if not prefix_file: # then only the filename component
|
||||
return "", last, ""
|
||||
|
||||
prefix = "pydicom" if prefix_file[0] == "pydicom" else ""
|
||||
if prefix:
|
||||
prefix_file.pop(0)
|
||||
|
||||
# If list empty after pop above, then have pydicom::filename
|
||||
if not prefix_file:
|
||||
return prefix, last, ""
|
||||
|
||||
return prefix, "".join(prefix_file), last
|
||||
|
||||
|
||||
def filespec_parser(filespec: str) -> list[tuple[Dataset, Any]]:
|
||||
"""Utility to return a dataset and an optional data element value within it
|
||||
|
||||
Note: this is used as an argparse 'type' for adding parsing arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filespec: str
|
||||
A filename with optional `pydicom::` prefix and optional data element,
|
||||
in format:
|
||||
[pydicom::]<filename>[::<element>]
|
||||
If an element is specified, it must be a path to a data element,
|
||||
sequence item (dataset), or a sequence.
|
||||
Examples:
|
||||
your_file.dcm
|
||||
your_file.dcm::StudyDate
|
||||
pydicom::rtplan.dcm::BeamSequence[0]
|
||||
pydicom::rtplan.dcm::BeamSequence[0].BeamLimitingDeviceSequence
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[Tuple[Dataset, Any]]
|
||||
Matching pairs of (dataset, data element value)
|
||||
This usually is a single pair, but a list is returned for future
|
||||
ability to work across multiple files.
|
||||
|
||||
Note
|
||||
----
|
||||
This function is meant to be used in a call to an `argparse` library's
|
||||
`add_argument` call for subparsers, with name="filespec" and
|
||||
`type=filespec_parser`. When used that way, the resulting args.filespec
|
||||
will contain the return values of this function
|
||||
(e.g. use `ds, element_val = args.filespec` after parsing arguments)
|
||||
See the `pydicom.cli.show` module for an example.
|
||||
|
||||
Raises
|
||||
------
|
||||
argparse.ArgumentTypeError
|
||||
If the filename does not exist in local path or in pydicom test files,
|
||||
or if the optional element is not a valid expression,
|
||||
or if the optional element is a valid expression but does not exist
|
||||
within the dataset
|
||||
"""
|
||||
prefix, filename, element = filespec_parts(filespec)
|
||||
|
||||
# Get the pydicom test filename even without prefix, in case user forgot it
|
||||
try:
|
||||
pydicom_filename = cast(str, get_testdata_file(filename))
|
||||
except ValueError: # will get this if absolute path passed
|
||||
pydicom_filename = ""
|
||||
|
||||
# Check if filename is in charset files
|
||||
if not pydicom_filename:
|
||||
try:
|
||||
char_filenames = get_charset_files(filename)
|
||||
if char_filenames:
|
||||
pydicom_filename = char_filenames[0]
|
||||
except NotImplementedError: # will get this if absolute path passed
|
||||
pass
|
||||
|
||||
if prefix == "pydicom":
|
||||
filename = pydicom_filename
|
||||
|
||||
# Check element syntax first to avoid unnecessary load of file
|
||||
if element and not re_file_spec_object.match(element):
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"Component '{element}' is not valid syntax for a "
|
||||
"data element, sequence, or sequence item"
|
||||
)
|
||||
|
||||
# Read DICOM file
|
||||
try:
|
||||
ds = dcmread(filename, force=True)
|
||||
except FileNotFoundError:
|
||||
extra = (
|
||||
(f", \nbut 'pydicom::{filename}' test data file is available")
|
||||
if pydicom_filename
|
||||
else ""
|
||||
)
|
||||
raise argparse.ArgumentTypeError(f"File '{filename}' not found{extra}")
|
||||
except Exception as e:
|
||||
raise argparse.ArgumentTypeError(f"Error reading '{filename}': {e}")
|
||||
|
||||
if not element:
|
||||
return [(ds, None)]
|
||||
|
||||
data_elem_val = eval_element(ds, element)
|
||||
|
||||
return [(ds, data_elem_val)]
|
||||
|
||||
|
||||
def help_command(args: argparse.Namespace) -> None:
|
||||
if subparsers is None:
|
||||
print("No subcommands are available")
|
||||
return
|
||||
|
||||
subcommands: list[str] = list(subparsers.choices.keys())
|
||||
if args.subcommand and args.subcommand in subcommands:
|
||||
subparsers.choices[args.subcommand].print_help()
|
||||
else:
|
||||
print("Use pydicom help [subcommand] to show help for a subcommand")
|
||||
subcommands.remove("help")
|
||||
print(f"Available subcommands: {', '.join(subcommands)}")
|
||||
|
||||
|
||||
SubCommandType = dict[str, Callable[[argparse._SubParsersAction], None]]
|
||||
|
||||
|
||||
def get_subcommand_entry_points() -> SubCommandType:
|
||||
subcommands = {}
|
||||
for entry_point in entry_points(group="pydicom_subcommands"):
|
||||
subcommands[entry_point.name] = entry_point.load()
|
||||
|
||||
return subcommands
|
||||
|
||||
|
||||
def main(args: list[str] | None = None) -> None:
|
||||
"""Entry point for 'pydicom' command line interface
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : List[str], optional
|
||||
Command-line arguments to parse. If ``None``, then :attr:`sys.argv`
|
||||
is used.
|
||||
"""
|
||||
global subparsers
|
||||
|
||||
py_version = sys.version.split()[0]
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="pydicom",
|
||||
description=f"pydicom command line utilities (Python {py_version})",
|
||||
)
|
||||
subparsers = parser.add_subparsers(help="subcommand help")
|
||||
|
||||
help_parser = subparsers.add_parser("help", help="display help for subcommands")
|
||||
help_parser.add_argument(
|
||||
"subcommand", nargs="?", help="Subcommand to show help for"
|
||||
)
|
||||
help_parser.set_defaults(func=help_command)
|
||||
|
||||
# Get subcommands to register themselves as a subparser
|
||||
subcommands = get_subcommand_entry_points()
|
||||
for subcommand in subcommands.values():
|
||||
subcommand(subparsers)
|
||||
|
||||
ns = parser.parse_args(args)
|
||||
if not vars(ns):
|
||||
parser.print_help()
|
||||
else:
|
||||
ns.func(ns)
|
||||
162
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/show.py
vendored
Executable file
162
dist/dicom2pacs.app/Contents/Resources/lib/python3.13/pydicom/cli/show.py
vendored
Executable file
@@ -0,0 +1,162 @@
|
||||
# Copyright 2019 pydicom authors. See LICENSE file for details.
|
||||
"""Pydicom command line interface program for `pydicom show`"""
|
||||
|
||||
import argparse
|
||||
from collections.abc import Callable
|
||||
|
||||
from pydicom.dataset import Dataset
|
||||
from pydicom.cli.main import filespec_help, filespec_parser
|
||||
|
||||
|
||||
def add_subparser(subparsers: argparse._SubParsersAction) -> None:
|
||||
subparser = subparsers.add_parser(
|
||||
"show", description="Display all or part of a DICOM file"
|
||||
)
|
||||
subparser.add_argument("filespec", help=filespec_help, type=filespec_parser)
|
||||
subparser.add_argument(
|
||||
"-x",
|
||||
"--exclude-private",
|
||||
help="Don't show private data elements",
|
||||
action="store_true",
|
||||
)
|
||||
subparser.add_argument(
|
||||
"-t", "--top", help="Only show top level", action="store_true"
|
||||
)
|
||||
subparser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
help="Only show basic information",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
subparser.set_defaults(func=do_command)
|
||||
|
||||
|
||||
def do_command(args: argparse.Namespace) -> None:
|
||||
if len(args.filespec) != 1:
|
||||
raise NotImplementedError("Show can only work on a single DICOM file input")
|
||||
|
||||
ds, element_val = args.filespec[0]
|
||||
if not element_val:
|
||||
element_val = ds
|
||||
|
||||
if args.exclude_private:
|
||||
ds.remove_private_tags()
|
||||
|
||||
if args.quiet and isinstance(element_val, Dataset):
|
||||
show_quiet(element_val)
|
||||
elif args.top and isinstance(element_val, Dataset):
|
||||
print(element_val.top())
|
||||
else:
|
||||
print(str(element_val))
|
||||
|
||||
|
||||
def SOPClassname(ds: Dataset) -> str | None:
|
||||
class_uid = ds.get("SOPClassUID")
|
||||
if class_uid is None:
|
||||
return None
|
||||
return f"SOPClassUID: {class_uid.name}"
|
||||
|
||||
|
||||
def quiet_rtplan(ds: Dataset) -> str | None:
|
||||
if "BeamSequence" not in ds:
|
||||
return None
|
||||
|
||||
plan_label = ds.get("RTPlanLabel")
|
||||
plan_name = ds.get("RTPlanName")
|
||||
line = f"Plan Label: {plan_label} "
|
||||
if plan_name:
|
||||
line += f"Plan Name: {plan_name}"
|
||||
lines = [line]
|
||||
|
||||
if "FractionGroupSequence" in ds: # it should be, is mandatory
|
||||
for fraction_group in ds.FractionGroupSequence:
|
||||
fraction_group_num = fraction_group.get("FractionGroupNumber", "")
|
||||
descr = fraction_group.get("FractionGroupDescription", "")
|
||||
fractions = fraction_group.get("NumberOfFractionsPlanned")
|
||||
fxn_info = f"{fractions} fraction(s) planned" if fractions else ""
|
||||
lines.append(f"Fraction Group {fraction_group_num} {descr} {fxn_info}")
|
||||
num_brachy = fraction_group.get("NumberOfBrachyApplicationSetups")
|
||||
lines.append(f" Brachy Application Setups: {num_brachy}")
|
||||
for refd_beam in fraction_group.ReferencedBeamSequence:
|
||||
ref_num = refd_beam.get("ReferencedBeamNumber")
|
||||
dose = refd_beam.get("BeamDose")
|
||||
mu = refd_beam.get("BeamMeterset")
|
||||
line = f" Beam {ref_num} "
|
||||
if dose or mu:
|
||||
line += f"Dose {dose} Meterset {mu}"
|
||||
lines.append(line)
|
||||
|
||||
for beam in ds.BeamSequence:
|
||||
beam_num = beam.get("BeamNumber")
|
||||
beam_name = beam.get("BeamName")
|
||||
beam_type = beam.get("BeamType")
|
||||
beam_delivery = beam.get("TreatmentDeliveryType")
|
||||
beam_radtype = beam.get("RadiationType")
|
||||
line = (
|
||||
f"Beam {beam_num} '{beam_name}' {beam_delivery} "
|
||||
f"{beam_type} {beam_radtype}"
|
||||
)
|
||||
|
||||
if beam_type == "STATIC":
|
||||
cp = beam.ControlPointSequence[0]
|
||||
if cp:
|
||||
energy = cp.get("NominalBeamEnergy")
|
||||
gantry = cp.get("GantryAngle")
|
||||
bld = cp.get("BeamLimitingDeviceAngle")
|
||||
couch = cp.get("PatientSupportAngle")
|
||||
line += f" energy {energy} gantry {gantry}, coll {bld}, couch {couch}"
|
||||
|
||||
wedges = beam.get("NumberOfWedges")
|
||||
comps = beam.get("NumberOfCompensators")
|
||||
boli = beam.get("NumberOfBoli")
|
||||
blocks = beam.get("NumberOfBlocks")
|
||||
|
||||
line += f" ({wedges} wedges, {comps} comps, {boli} boli, {blocks} blocks)"
|
||||
|
||||
lines.append(line)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def quiet_image(ds: Dataset) -> str | None:
|
||||
if "SOPClassUID" not in ds or "Image Storage" not in ds.SOPClassUID.name:
|
||||
return None
|
||||
|
||||
results = [
|
||||
f"{name}: {ds.get(name, 'N/A')}"
|
||||
for name in [
|
||||
"BitsStored",
|
||||
"Modality",
|
||||
"Rows",
|
||||
"Columns",
|
||||
"SliceLocation",
|
||||
]
|
||||
]
|
||||
return "\n".join(results)
|
||||
|
||||
|
||||
# Items to show in quiet mode
|
||||
# Item can be a callable or a DICOM keyword
|
||||
quiet_items: list[Callable[[Dataset], str | None] | str] = [
|
||||
SOPClassname,
|
||||
"PatientName",
|
||||
"PatientID",
|
||||
# Images
|
||||
"StudyID",
|
||||
"StudyDate",
|
||||
"StudyTime",
|
||||
"StudyDescription",
|
||||
quiet_image,
|
||||
quiet_rtplan,
|
||||
]
|
||||
|
||||
|
||||
def show_quiet(ds: Dataset) -> None:
|
||||
for item in quiet_items:
|
||||
if callable(item):
|
||||
result = item(ds)
|
||||
if result:
|
||||
print(result)
|
||||
else:
|
||||
print(f"{item}: {ds.get(item, 'N/A')}")
|
||||
Reference in New Issue
Block a user