/usr/lib/python2.7/dist-packages/dicom/util/codify.py is in python-dicom 0.9.9-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | # codify.py
"""Produce runnable python code which can recreate DICOM objects or files.
Can run as a script to produce code for an entire file,
or import and use specific functions to provide code for pydicom DICOM classes
"""
# Copyright (c) 2013 Darcy Mason
# This file is part of pydicom, released under an MIT license.
# See the file license.txt included with this distribution, also
# available at http://pydicom.googlecode.com
# Run this from the same directory as a "base" dicom file and
# this code will output to screen the dicom parameters like:
# ds.PatientName = 'TEST'
# etc for all parameters in the file.
# This can then be pasted into a python file and parameters edited as necessary
# to create a DICOM file from scratch
import sys
import dicom
from dicom.datadict import dictionary_keyword
import re
line_term = "\n"
# Helper functions first
# Precompiled search patterns for camel_to_underscore()
first_cap_re = re.compile('(.)([A-Z][a-z]+)')
all_cap_re = re.compile('([a-z0-9])([A-Z])')
byte_VRs = ['OB', 'OW', 'OW/OB', 'OW or OB', 'OB or OW',
'US or SS or OW', 'US or SS']
def camel_to_underscore(name):
"""Convert name from CamelCase to lower_case_with_underscores"""
# From http://stackoverflow.com/questions/1175208
s1 = first_cap_re.sub(r'\1_\2', name)
return all_cap_re.sub(r'\1_\2', s1).lower()
def tag_repr(tag):
"""String of tag value as (0xgggg, 0xeeee)"""
return "(0x{group:04x}, 0x{elem:04x})".format(group=tag.group,
elem=tag.element)
def default_name_filter(name):
"""Callable to reduce some names in code to more readable short form
:arg name: a sequence variable name or sequence item name
:return: a shorter version of name if a known conversion,
else return original name
"""
name = camel_to_underscore(name)
name = name.replace("control_point", "cp")
name = name.replace("reference", "ref")
name = name.replace("fraction_group", "frxn_gp")
return name
# Functions to produce python code
def code_imports():
"""Code the import statements needed by other codify results
:return: a string of import statement lines
"""
line1 = "import dicom"
line2 = "from dicom.dataset import Dataset"
line3 = "from dicom.sequence import Sequence"
return line_term.join((line1, line2, line3))
def code_dataelem(dataelem, dataset_name="ds", exclude_size=None,
include_private=False):
"""Code lines for a single DICOM data element
:arg dataelem: the DataElement instance to turn into code
:arg dataset_name: variable name of the Dataset containing dataelem
:arg exclude_size: if specified, values longer than this (in bytes)
will only have a commented string for a value,
causing a syntax error when the code is run,
and thus prompting the user to remove or fix that line.
:return: a string containing code to recreate the data element
If the data element is a sequence, calls code_sequence
"""
if dataelem.VR == "SQ":
return code_sequence(dataelem, dataset_name, exclude_size,
include_private)
# If in DICOM dictionary, set using the keyword
# If not (e.g. is private element), set using add_new method
have_keyword = True
try:
keyword = dictionary_keyword(dataelem.tag)
except KeyError:
have_keyword = False
valuerep = repr(dataelem.value)
if exclude_size:
if dataelem.VR in byte_VRs and len(dataelem.value) > exclude_size:
valuerep = "# XXX Array of %d bytes excluded" % len(dataelem.value)
if have_keyword:
format_str = "{ds_name}.{keyword} = {valuerep}"
line = format_str.format(ds_name=dataset_name,
keyword=keyword,
valuerep=valuerep)
else:
format_str = "{ds_name}.add_new({tag}, '{VR}', {valuerep})"
line = format_str.format(ds_name=dataset_name,
tag=tag_repr(dataelem.tag),
VR=dataelem.VR,
valuerep=valuerep)
return line
def code_sequence(dataelem, dataset_name="ds",
exclude_size=None, include_private=False,
name_filter=default_name_filter):
"""Code lines for recreating a Sequence data element
:arg dataelem: the DataElement instance of the Sequence
:arg dataset_name: variable name of the dataset containing the Sequence
:arg exclude_size: if specified, values longer than this (in bytes)
will only have a commented string for a value,
causing a syntax error when the code is run,
and thus prompting the user to remove or fix that line.
:arg include_private: If True, private data elements will be coded.
If False, private elements are skipped
:arg name_filter: a callable taking a sequence name or sequence item name,
and returning a shorter name for easier code reading
:return: a string containing code lines to recreate a DICOM sequence
"""
lines = []
seq = dataelem.value
seq_name = dataelem.name
seq_item_name = seq_name.replace(' Sequence', '')
seq_keyword = dictionary_keyword(dataelem.tag)
# Create comment line to document the start of Sequence
lines.append('')
lines.append("# " + seq_name)
# Code line to create a new Sequence object
if name_filter:
seq_var = name_filter(seq_keyword)
lines.append(seq_var + " = Sequence()")
# Code line to add the sequence to its parent
lines.append(dataset_name + "." + seq_keyword + " = " + seq_var)
# Code lines to add sequence items to the Sequence
for i, ds in enumerate(seq):
# Determine index to use. If seq item has a data element with 'Index',
# use that; if one with 'Number', use that, else start at 1
index_keyword = seq_keyword.replace("Sequence", "") + "Index"
number_keyword = seq_keyword.replace("Sequence", "") + "Number"
if index_keyword in ds:
index_str = str(getattr(ds, index_keyword))
elif number_keyword in ds:
index_str = str(getattr(ds, number_keyword))
else:
index_str = str(i + 1)
# Code comment line to mark start of sequence item
lines.append('')
lines.append("# " + seq_name + ": " + seq_item_name + " " + index_str)
# Determine the variable name to use for the sequence item (dataset)
ds_name = seq_var.replace("_sequence", "") + index_str
# Code the sequence item
code_item = code_dataset(ds, ds_name, exclude_size, include_private)
lines.append(code_item)
# Code the line to append the item to its parent sequence
lines.append(seq_var + ".append(" + ds_name + ")")
# Join the lines and return a single string
return line_term.join(lines)
def code_dataset(ds, dataset_name="ds", exclude_size=None,
include_private=False):
"""Return python code lines for import statements needed by other code
:arg exclude_size: if specified, values longer than this (in bytes)
will only have a commented string for a value,
causing a syntax error when the code is run,
and thus prompting the user to remove or fix that line.
:arg include_private: If True, private data elements will be coded.
If False, private elements are skipped
:return: a list of code lines containing import statements
"""
lines = []
lines.append(dataset_name + " = Dataset()")
for dataelem in ds:
# If a private data element and flag says so, skip it and go to next
if not include_private and dataelem.tag.is_private:
continue
# Otherwise code the line and add it to the lines list
code_line = code_dataelem(dataelem, dataset_name, exclude_size,
include_private)
lines.append(code_line)
# Add blank line if just coded a sequence
if dataelem.VR == "SQ":
lines.append('')
# If sequence was end of this dataset, remove the extra blank line
if len(lines) and lines[-1] == '':
lines.pop()
# Join all the code lines and return them
return line_term.join(lines)
def code_file(filename, exclude_size=None, include_private=False):
"""Write a complete source code file to recreate a DICOM file
:arg filename: complete path and filename of a DICOM file to convert
:arg exclude_size: if specified, values longer than this (in bytes)
will only have a commented string for a value,
causing a syntax error when the code is run,
and thus prompting the user to remove or fix that line.
:arg include_private: If True, private data elements will be coded.
If False, private elements are skipped
:return: a string containing code lines to recreate entire file
"""
lines = []
ds = dicom.read_file(filename, force=True)
# Code a nice header for the python file
lines.append("# Coded version of DICOM file '{0}'".format(filename))
lines.append("# Produced by pydicom codify utility script")
# Code the necessary imports
lines.append(code_imports())
lines.append('')
# Code the file_meta information
lines.append("# File meta info data elements")
code_meta = code_dataset(ds.file_meta, "file_meta", exclude_size, include_private)
lines.append(code_meta)
lines.append('')
# Code the main dataset
lines.append("# Main data elements")
code_ds = code_dataset(ds, exclude_size=exclude_size,
include_private=include_private)
lines.append(code_ds)
lines.append('')
# Add the file meta to the dataset, and set transfer syntax
lines.append("ds.file_meta = file_meta")
lines.append("ds.is_implicit_VR = " + str(ds.is_implicit_VR))
lines.append("ds.is_little_endian = " + str(ds.is_little_endian))
# Return the complete code string
return line_term.join(lines)
if __name__ == "__main__":
default_exclude_size = 100 # bytes
try:
import argparse
except ImportError:
print "The argparse module is required to run this script"
print "argparse is standard in python >= 2.7,"
print " or can be installed with 'pip install argparse'"
sys.exit(-1)
parser = argparse.ArgumentParser(
description="Produce python/pydicom code from a DICOM file",
epilog="Binary data (e.g. pixels) larger than --exclude-size "
"(default %d bytes) is not included. A dummy line with a syntax "
"error is produced. "
"Private data elements are not included "
"by default." % default_exclude_size)
parser.add_argument('filename',
help="DICOM file from which to produce code lines")
parser.add_argument('outfile', nargs='?', type=argparse.FileType('wb'),
help="Filename to write python code to. "
"If not specified, code is written to stdout",
default=sys.stdout)
parser.add_argument('-e', '--exclude-size', type=long,
default=default_exclude_size,
help='Exclude binary data larger than specified (bytes)'
'. Default is %d bytes' % default_exclude_size)
parser.add_argument('-p', '--include-private', action="store_true",
help='Include private data elements '
'(default is to exclude them)')
parser.add_argument('-s', '--save-as',
help="End the code with a ds.save_as(save_filename)")
args = parser.parse_args()
# Read the requested file and convert to python/pydicom code lines
filename = args.filename
code_lines = code_file(filename, args.exclude_size, args.include_private)
# If requested, write a code line to save the dataset
if args.save_as:
save_line = "\nds.save_as('{filename}')".format(filename=args.save_as)
code_lines += save_line
# Write the code lines to specified file or to standard output
args.outfile.write(code_lines)
|