File: services-scp.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 (206 lines) | stat: -rw-r--r-- 8,061 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
201
202
203
204
205
206
DICOM services -- server side
=============================

.. highlight:: c++

The design of the :abbr:`SCPs (Service Class Provider)`: an abstract base class, `odil::SCP`_, implementing common features and concrete classes for each service (see table below). Every service requires an association which is correctly associated and has negotiated the corresponding abstract syntax.

.. table:: DICOM services and Odil classes
  
  +-------------+-------------------+
  | Service     | Odil class        |
  +=============+===================+
  | `C-ECHO`_   | `odil::EchoSCP`_  |
  +-------------+-------------------+
  | `C-FIND`_   | `odil::FindSCP`_  |
  +-------------+-------------------+
  | `C-GET`_    | `odil::GetSCP`_   |
  +-------------+-------------------+
  | `C-MOVE`_   | `odil::MoveSCP`_  |
  +-------------+-------------------+
  | `C-STORE`_  | `odil::StoreSCP`_ |
  +-------------+-------------------+

The SCP classes handle the low-level message exchange, and the action (e.g. querying a data base or storing a file) is left to the user. If the SCP's responses contain no data set (C-ECHO, C-STORE), the action is a callback with a single parameter (the request message) which returns the status of the operation; if the SCP returns data sets (C-FIND, C-GET, C-MOVE), the action is specified as a class which inherits from `odil::SCP::DataSetGenerator`_. A data set generator must specify the following member functions:

- ``void initialize(message::Request const &request)``: initialize the generator given an incoming request
- ``bool done()``: test whether all elements have been generated
- ``void next()``: prepare the next element
- ``DataSet get()``: return the current element

The SCP may process an already-existing message using the ``operator()`` or may receive a message on the underlying association and process it through the ``receive_and_process`` member function. In the former case, the function will return once the response has been sent; in the latter case, the function will block until a message has been received *and* the response has been sent.

SCPs returning no data set
--------------------------

The following example show the use of a C-ECHO SCP. The message handler prints a message on the server console and returns successfully.

::

  #include <iostream>
  
  #include <odil/Association.h>
  #include <odil/EchoSCP.h>
  #include <odil/message/CEchoRequest.h>
  
  odil::Value::Integer echo(odil::message::CEchoRequest const & request)
  {
      std::cout << "Received echo\n";
      std::cout << "  ID: " << request.get_message_id() << "\n";
      std::cout << "  Affected SOP Class UID: " << request.get_affected_sop_class_uid() << "\n";
      return odil::message::Response::Success;
  }
    
  int main()
  {
      odil::Association association;
      association.receive_association(boost::asio::ip::tcp::v4(), 11112);
      odil::EchoSCP scp(association, echo);
      scp.receive_and_process();
  }

SCPs returning data sets
------------------------

The following example shows an implementation of a ``DataSetGenerator`` which simulates the query of a database returning two matching data sets. The generator is the used in a C-FIND SCP.

::
  
  #include <vector>
  
  #include <odil/Association.h>
  #include <odil/DataSet.h>
  #include <odil/FindSCP.h>
  #include <odil/message/CFindRequest.h>
  
  class FindGenerator: public odil::SCP::DataSetGenerator
  {
  public:
      FindGenerator()
      {
          // Nothing to do
      }

      virtual ~FindGenerator()
      {
          // Nothing to do.
      }

      virtual void initialize(odil::message::CFindRequest const & )
      {
          odil::DataSet data_set_1;
          data_set_1.add(odil::registry::PatientName, {"Hello^World"});
          data_set_1.add(odil::registry::PatientID, {"1234"});
          this->_responses.push_back(data_set_1);

          odil::DataSet data_set_2;
          data_set_2.add(odil::registry::PatientName, {"Doe^John"});
          data_set_2.add(odil::registry::PatientID, {"5678"});
          this->_responses.push_back(data_set_2);

          this->_response_iterator = this->_responses.begin();
      }

      virtual bool done() const
      {
          return (this->_response_iterator == this->_responses.end());
      }

      virtual odil::DataSet get() const
      {
          return *this->_response_iterator;
      }

      virtual void next()
      {
          ++this->_response_iterator;
      }


  private:
      std::vector<odil::DataSet> _responses;
      std::vector<odil::DataSet>::const_iterator _response_iterator;
  };
  
  int main()
  {
      odil::Association association;
      association.receive_association(boost::asio::ip::tcp::v4(), 11112);
      
      FindGenerator generator;
      odil::FindSCP scp(association, generator);
      scp.receive_and_process();
  }

The generators used by the C-GET and C-MOVE SCPs have an extra member function, ``count``, which must return the number of data sets that the SCP will send. Moreover, since C-MOVE needs to establish a new association to send its responses, generators for the C-MOVE SCPs must implement the ``get_association`` member function which returns a non-associated association.

SCP dispatcher
--------------

In order to facilitate the development of a DICOM server handling multiple services, the `odil::SCPDispatcher`_ class maps the type of a message to an instance of a SCP and dispatches an incoming message to the correct SCP.

::
  
  #include <iostream>
  #include <memory>
  
  #include <odil/EchoSCP.h>
  #include <odil/FindSCP.h>
  #include <odil/message/CEchoRequest.h>
  #include <odil/message/CFindRequest.h>
  #include <odil/SCPDispatcher.h>
  
  // See above for the definitions
  odil::Value::Integer echo(odil::message::CEchoRequest const & request);
  class FindGenerator;
    
  int main()
  {
      odil::Association association;
      association.receive_association(boost::asio::ip::tcp::v4(), 11112);
      
      auto echo_scp = std::make_shared<odil::EchoSCP>(association, echo);
      auto find_scp = std::make_shared<odil::FindSCP>(
        association, std::make_shared<FindGenerator>());
      
      odil::SCPDispatcher dispatcher(association);
      dispatcher.set_scp(odil::message::Message::Command::C_ECHO_RQ, echo_scp);
      dispatcher.set_scp(odil::message::Message::Command::C_FIND_RQ, find_scp);
      
      bool done = false;
      while(!done)
      {
          try
          {
              dispatcher.dispatch();
          }
          catch(odil::AssociationReleased const &)
          {
              std::cout << "Peer released association" << std::endl;
              done = true;
          }
          catch(odil::AssociationAborted const & e)
          {
              std::cout
                  << "Peer aborted association, "
                  << "source: " << int(e.source) << ", "
                  << "reason: " << int(e.reason)
                  << std::endl;
              done = true;
          }
      }
  }

.. _C-ECHO: http://dicom.nema.org/medical/dicom/current/output/chtml/part04/chapter_A.html
.. _C-FIND: http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#sect_C.4.1
.. _C-GET: http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.3.html
.. _C-MOVE: http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html
.. _C-STORE: http://dicom.nema.org/medical/dicom/current/output/chtml/part04/chapter_B.html
.. _odil::EchoSCP: ../../_static/doxygen/classodil_1_1EchoSCP.html
.. _odil::FindSCP: ../../_static/doxygen/classodil_1_1FindSCP.html
.. _odil::GetSCP: ../../_static/doxygen/classodil_1_1GetSCP.html
.. _odil::MoveSCP: ../../_static/doxygen/classodil_1_1MoveSCP.html
.. _odil::SCP: ../../_static/doxygen/classodil_1_1SCP.html
.. _odil::SCP::DataSetGenerator: ../../_static/doxygen/classodil_1_1SCP_1_1DataSetGenerator.html
.. _odil::SCPDispatcher: ../../_static/doxygen/classodil_1_1SCPDispatcher.html
.. _odil::StoreSCP: ../../_static/doxygen/classodil_1_1StoreSCP.html