DICOM services – server side#
The design of the SCPs: 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.
Service |
Odil class |
---|---|
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 requestbool done()
: test whether all elements have been generatedvoid next()
: prepare the next elementDataSet 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;
}
}
}