DICOM services – client side#
SCUs (i.e. clients) are available for C-ECHO, C-FIND, C-GET, C-MOVE, and C-STORE, with an abstract base class, odil::SCU, 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 |
---|---|
C-ECHO |
|
C-FIND |
|
C-GET |
|
C-MOVE |
|
C-STORE |
Verifying DICOM communication#
The simplest service is C-ECHO, used to verify that the two peers can exchange information using DICOM.
#include <odil/Association.h>
#include <odil/EchoSCU.h>
#include <odil/registry.h>
int main()
{
odil::Association 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::registry::Verification,
{ odil::registry::ExplicitVRLittleEndian },
odil::AssociationParameters::PresentationContext::Role::SCU
}
});
association.associate();
odil::EchoSCU echo_scu(association);
echo_scu.echo();
association.release();
}
Querying a database#
The query service, C-FIND, is parameterized by a query and either returns all the matching data sets or calls a function for each match.
#include <iostream>
#include <odil/Association.h>
#include <odil/FindSCU.h>
#include <odil/registry.h>
void print_informations(odil::DataSet const & response)
{
auto const name =
(response.has("PatientName") && !response.empty("PatientName"))
?response.as_string("PatientName", 0):"(no name)";
auto const study =
(response.has("StudyDescription") && !response.empty("StudyDescription"))
?response.as_string("StudyDescription", 0):"(no description)";
std::cout << name << ": " << study << "\n";
}
int main()
{
auto const model = odil::registry::StudyRootQueryRetrieveInformationModelFind;
odil::Association 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({
{
model, { odil::registry::ExplicitVRLittleEndian },
odil::AssociationParameters::PresentationContext::Role::SCU }
});
association.associate();
odil::DataSet query;
query.add("PatientName", { "*" });
query.add("QueryRetrieveLevel", { "STUDY" });
query.add("StudyDescription");
query.add("StudyDate");
odil::FindSCU find_scu(association);
find_scu.set_affected_sop_class(model);
auto const result = find_scu.find(query);
for(auto const & dataset: result)
{
print_informations(dataset);
}
find_scu.find(query, print_informations);
association.release();
}
Retrieving data sets#
The retrieval of data sets, using either C-GET or C-MOVE, is very similar to querying a database. The main difference is the additional presentation contexts required to transfer the data sets.
#include <iostream>
#include <string>
#include <vector>
#include <odil/Association.h>
#include <odil/FindSCU.h>
#include <odil/GetSCU.h>
#include <odil/registry.h>
odil::DataSet find(
std::string const & host, unsigned int port,
std::string const & calling_aet, std::string const & called_aet)
{
auto const model = odil::registry::StudyRootQueryRetrieveInformationModelFind;
odil::Association association;
association.set_peer_host(host);
association.set_peer_port(port);
association.update_parameters()
.set_calling_ae_title(calling_aet).set_called_ae_title(called_aet)
.set_presentation_contexts({
{
model, { odil::registry::ExplicitVRLittleEndian },
odil::AssociationParameters::PresentationContext::Role::SCU }
});
association.associate();
odil::DataSet query;
query.add("QueryRetrieveLevel", { "STUDY" });
query.add("StudyInstanceUID");
query.add("SOPClassesInStudy");
query.add("StudyDate");
odil::FindSCU scu(association);
scu.set_affected_sop_class(model);
auto const studies = scu.find(query);
association.release();
if(studies.empty())
{
throw std::runtime_error("No matching studies");
}
return studies[0];
}
std::vector<odil::DataSet>
get_study(
std::string const & host, unsigned int port,
std::string const & calling_aet, std::string const & called_aet,
odil::DataSet const & study)
{
auto const model = odil::registry::StudyRootQueryRetrieveInformationModelGet;
odil::Association association;
association.set_peer_host(host);
association.set_peer_port(port);
association.update_parameters()
.set_calling_ae_title(calling_aet).set_called_ae_title(called_aet);
std::vector<odil::AssociationParameters::PresentationContext> contexts{
{
model, { odil::registry::ExplicitVRLittleEndian },
odil::AssociationParameters::PresentationContext::Role::SCU }
};
for(auto const & abstract_syntax: study.as_string("SOPClassesInStudy"))
{
contexts.push_back({
abstract_syntax, { odil::registry::ExplicitVRLittleEndian },
odil::AssociationParameters::PresentationContext::Role::SCP
});
}
association.update_parameters().set_presentation_contexts(contexts);
association.associate();
odil::DataSet query;
query.add("QueryRetrieveLevel", { "STUDY" });
query.add("StudyInstanceUID", study.as_string("StudyInstanceUID"));
odil::GetSCU scu(association);
scu.set_affected_sop_class(model);
auto const data_sets = scu.get(query);
association.release();
return data_sets;
}
int main()
{
std::string const host = "www.dicomserver.co.uk";
unsigned int port = 11112;
std::string const calling_aet = "WORKSTATION";
std::string const called_aet = "SERVER";
auto const study = find(host, port, calling_aet, called_aet);
auto const data_sets = get_study(
host, port, calling_aet, called_aet, study);
std::cout
<< data_sets.size() << " data set" << (data_sets.size()>0?"s":"") << " "
<< "received\n";
}
Note that several presentation contexts must be specified: the C-GET context and one for each type of object returned by C-FIND in the SOP Classes In Study element.
The C-GET SCU can also be called using one or two callbacks: one which will be called for each C-STORE operation initiated by the server and an optional one which will be called for each C-GET response. The latter one may for example be used for progress information.
The C-MOVE SCU is similar to the C-GET SCU in terms of required presentation contexts (the XXX Root Query Retrieve Information Model GET being replaced by XXX Root Query Retrieve Information Model MOVE) and callbacks. The C-MOVE SCU has additional member functions used to specify where the remote peer will send the data sets: get_move_destination
and set_move_destination
. If the move destination is the local peer and not a third party, the port of a temporary C-STORE SCP may be specified through set_incoming_port
. The default value of 0
means that no C-STORE SCP should be started.
Storing data sets#
The C-STORE SCU differs from the query/retrieve SCUs since it has no callback: each data set is either stored successfully or an exception is raised. An optional move origin (if the C-STORE SCU is created by a C-MOVE SCP) may be specified.