Python API#
The Python API tries to mimic the C++ API as much as possible: most classes and other constructs keep the same name and semantics. However, when C++ and Python differ too much, new Python functions or classes are defined.
Basic operations#
The following example show basic manipulations of a data set. This code snippet, and all the others in this section assume that for Python 2, from __future__ import print_function
was used. Note that the Python version of odil.DataSet
adds a few member functions to the C++ version (e.g. items
, __len__
, __iter__
) to make it more similar to Python containers.
import sys
import odil
# Reading a data set
with odil.open(sys.argv[1]) as stream:
header, data_set = odil.Reader.read_file(stream)
# Data set size, using C++ API
print(
"The header {} empty and has {} elements".format(
"is" if header.empty() else "is not", header.size()))
# Data set size, using Python API
print(
"The data set {} empty and has {} elements".format(
"is" if not data_set else "is not", len(data_set)))
# Element presence, C++ and Python API
print("Patient's Name {} in header".format(
"is" if header.has(odil.registry.PatientName) else "is not"))
print("Patient's Name {} in data set".format(
"is" if "PatientName" in data_set else "is not"))
# Element access, assuming PatientName is in data set
data_set.as_string("PatientName")[0] = "Somebody^Else"
print("Patient's Name: {}".format(list(data_set.as_string("PatientName"))))
# Iteration, sequence-like
for tag in header:
element = header[tag]
print(tag.get_name(), element.vr)
# Iteration, dict-like
for tag, element in data_set.items():
try:
name = tag.get_name()
except odil.Exception as e:
name = str(tag)
print(name, element.vr)
# Writing a data set
with odil.open(sys.argv[2], "w") as stream:
odil.Writer.write_file(data_set, stream)
DICOM services – client side#
The behavior of C++ SCUs w is kept as is in Python: services which return data sets (C-FIND, C-GET, C-MOVE) either return all data sets to the caller or call a function for each of them. The following example adapts the C++ examples for C-ECHO, C-FIND and C-GET in Python.
import sys
import odil
transfer_syntaxes = [
getattr(odil.registry, "{}VRLittleEndian".format(x))
for x in ["Implicit", "Explicit"]]
# Create the association
association = odil.Association()
association.set_peer_host("www.dicomserver.co.uk")
association.set_peer_port(11112)
association.update_parameters()\
.set_calling_ae_title("WORKSTATION")\
.set_called_ae_title("SERVER")\
.set_presentation_contexts([
odil.AssociationParameters.PresentationContext(
odil.registry.Verification,
transfer_syntaxes,
odil.AssociationParameters.PresentationContext.Role.SCU
),
odil.AssociationParameters.PresentationContext(
odil.registry.StudyRootQueryRetrieveInformationModelFind,
transfer_syntaxes,
odil.AssociationParameters.PresentationContext.Role.SCU
)
])
association.associate()
# Check DICOM connectivity
echo_scu = odil.EchoSCU(association)
try:
echo_scu.echo()
except odil.Exception as e:
print("DICOM connection to remote server failed: {}".format(e))
sys.exit(1)
# Find the matching studies
query = odil.DataSet()
query.add("QueryRetrieveLevel", [ "STUDY" ])
query.add("PatientName", ["Doe"])
query.add("StudyInstanceUID")
query.add("SOPClassesInStudy")
find_scu = odil.FindSCU(association)
find_scu.set_affected_sop_class(odil.registry.StudyRootQueryRetrieveInformationModelFind)
study = find_scu.find(query)[0]
# Fetch the first study
association.release()
association = odil.Association()
association.set_peer_host("www.dicomserver.co.uk")
association.set_peer_port(11112)
association.update_parameters()\
.set_calling_ae_title("WORKSTATION")\
.set_called_ae_title("SERVER")\
.set_presentation_contexts([
odil.AssociationParameters.PresentationContext(
odil.registry.StudyRootQueryRetrieveInformationModelGet,
transfer_syntaxes,
odil.AssociationParameters.PresentationContext.Role.SCU
)
]
+[
odil.AssociationParameters.PresentationContext(
x, [odil.registry.ExplicitVRLittleEndian],
odil.AssociationParameters.PresentationContext.Role.SCU)
for x in study.as_string("SOPClassesInStudy")
])
association.associate()
query = odil.DataSet()
query.add("QueryRetrieveLevel", [ "STUDY" ])
query.add("StudyInstanceUID", study.as_string("StudyInstanceUID"))
query.add("SOPClassesInStudy")
get_scu = odil.GetSCU(association)
get_scu.set_affected_sop_class(odil.registry.StudyRootQueryRetrieveInformationModelGet)
def data_set_received(data_set):
print("Got data set {}".format(data_set.as_string("SOPInstanceUID")[0]))
get_scu.get(query, data_set_received)
DICOM services – server side#
Similar to C++ SCPs, the Python SCPs work with generators, inherited from their base classes (e.g. odil.FindSCP.DataSetGenerator
). The following example shows the implementation of a dummy C-FIND SCP.
import odil
# Create the association
association = odil.Association()
association.receive_association("v4", 11112)
# Create the generator
class Generator(odil.FindSCP.DataSetGenerator):
def __init__(self):
odil.FindSCP.DataSetGenerator.__init__(self)
self._responses = []
self._response_index = None
def initialize(self, message):
data_set = odil.DataSet()
data_set.add("PatientName", ["Hello^World"])
data_set.add("PatientID", ["1234"])
self._responses.append(data_set)
self._response_index = 0
def done(self):
return (self._response_index == len(self._responses))
def next(self):
self._response_index += 1
def get(self):
return self._responses[self._response_index]
find_scp = odil.FindSCP(association)
generator = Generator()
find_scp.set_generator(generator)
# Receive and handle a message
message = association.receive_message()
find_scp(message)
# Check if we have more
termination_ok = False
try:
association.receive_message()
except odil.AssociationReleased:
print("Association released")
except odil.AssociationAborted:
print("Association aborted")