File: index.rst

package info (click to toggle)
odil 0.12.2-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,320 kB
  • sloc: cpp: 55,035; python: 3,971; javascript: 460; xml: 182; makefile: 98
file content (200 lines) | stat: -rw-r--r-- 6,856 bytes parent folder | download | duplicates (2)
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
Python API
==========

.. highlight:: python

The Python API tries to mimic the :doc:`C++ API <../cpp/index>` 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")