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
|