DICOM web services#

Note

Even though Odil implements the DICOM web services, it does not contain an HTTP server; the library however contains everything required to create and parse the content of HTTP requests and responses.

The web services implementation in Odil revolves around two classes: odil::webservices::HTTPRequest and odil::webservices::HTTPResponse which create and parse HTTP requests and responses. Each web service is implemented by a pair of classes which generate and parse the specific service requests and responses, as show in the table below.

Common data structures#

URLs are represented by odil::webservices::URL and contain fields as defined in RFC 3986 (scheme, authority, path, query and fragment). The URL class can parse a string to the URL components and be converted to a string:

#include <iostream>
#include <odil/webservices/URL.h>

int main()
{
    auto url = odil::webservices::URL::parse("foo://info.example.com?fred");
    url.path = "/people";
    std::cout << url << "\n";
}

HTTP messages, i.e. requests and responses, are handled by odil::webservices::HTTPRequest and odil::webservices::HTTPResponse, both having serialization (i.e. operator<<) and de-serialization (i.e. operator>>). Both contains headers and a body; the HTTP request contains the method (e.g. GET or POST), the target URL, the HTTP version, the headers and the body; the HTTP response contains the HTTP version, the status and its text representation (i.e. the reason), the headers and the body.

#include <iostream>
#include <sstream>
#include <odil/webservices/HTTPRequest.h>
#include <odil/webservices/HTTPResponse.h>

int main()
{
    odil::webservices::HTTPRequest request{
        "POST", odil::webservices::URL::parse("https://www.example.com:80/")};
    request.set_header("Content-Type", "application/json");
    request.set_body("{\"foo\": \"bar\" }");
    std::cout << request << "\n";

    std::istringstream stream(
        "HTTP/1.0 200 OK\n"
        "Content-Type: application/json\n");

    odil::webservices::HTTPResponse response;
    stream >> response;
    std::cout << response.has_header("Content-Type") << "\n";
}

Querying a database#

The following example show how to use Boost.Asio in order to create a very simple HTTP client. The http_client function is assume to be defined in subsequent examples.

#include <boost/asio.hpp>
#include <odil/webservices/HTTPRequest.h>
#include <odil/webservices/HTTPResponse.h>
#include <odil/webservices/URL.h>

odil::webservices::HTTPResponse
http_client(
    std::string const & host, std::string const & port,
    odil::webservices::HTTPRequest const & request)
{
    boost::asio::ip::tcp::iostream stream;
    stream.connect(host, port);

    stream << request;

    odil::webservices::HTTPResponse response;
    stream >> response;

    return response;
}

QIDO-RS queries are similar to C-FIND queries: both have a query root and a data set. The query root is specified by set_affected_sop_class in C-services, while in web services it is specified by odil::webservices::Selector. The data set is specified the same way in C-services and web services: non-empty elements are used as filters while empty terms are used to specified extra fields to be returned. The following example shows how to retrieve all the studies of a given patient through QIDO:

#include <iostream>
#include <odil/DataSet.h>
#include <odil/webservices/QIDORSRequest.h>
#include <odil/webservices/QIDORSResponse.h>
#include <odil/webservices/Selector.h>
#include <odil/webservices/URL.h>

int main()
{
    auto const root = odil::webservices::URL::parse(
        "http://dicomserver.co.uk:81/qido");

    odil::webservices::QIDORSRequest qido_request(root);

    odil::webservices::Selector selector;
    selector.set_study("");

    odil::DataSet query;
    query.add("PatientName", {"Doe"});
    query.add("StudyDescription");

    qido_request.request_datasets(
        odil::webservices::Representation::DICOM_JSON, selector, query,
        false, -1, 0, true);

    auto http_request = qido_request.get_http_request();
    auto const http_response = http_client(
        "dicomserver.co.uk", "81", http_request);

    odil::webservices::QIDORSResponse qido_response(http_response);

    std::cout
        << "Got " << qido_response.get_data_sets().size() << " data sets"
        << "\n";
}

Retrieving data sets#

The data sets to be received through WADO-RS works are specified by the selector. Using WADO-RS is otherwise very similar to using QIDO-RS:

#include <iostream>
#include <odil/DataSet.h>
#include <odil/webservices/QIDORSRequest.h>
#include <odil/webservices/QIDORSResponse.h>
#include <odil/webservices/WADORSRequest.h>
#include <odil/webservices/WADORSResponse.h>
#include <odil/webservices/Selector.h>
#include <odil/webservices/URL.h>

int main()
{
    odil::webservices::QIDORSRequest qido_request(
        odil::webservices::URL::parse("http://dicomserver.co.uk:81/qido"));

    odil::DataSet query;
    query.add("PatientName", {"Doe"});

    qido_request.request_datasets(
        odil::webservices::Representation::DICOM_JSON,
        std::map<std::string, std::string>({{"studies", ""}}),
        query, false, 1, 0, true);

    auto http_request = qido_request.get_http_request();
    auto http_response = http_client("dicomserver.co.uk", "81", http_request);

    odil::webservices::QIDORSResponse const qido_response(http_response);
    auto const study = qido_response.get_data_sets()[0];

    odil::webservices::WADORSRequest wado_request(
        odil::webservices::URL::parse("http://dicomserver.co.uk:81/wado"));
    wado_request.request_dicom(
        odil::webservices::Representation::DICOM_JSON,
        std::map<std::string, std::string>({
            {"studies", study.as_string("StudyInstanceUID")[0]}})
    );

    http_request = wado_request.get_http_request();
    http_response = http_client("dicomserver.co.uk", "81", http_request);

    odil::webservices::WADORSResponse const wado_response(http_response);
    auto const data_sets = wado_response.get_data_sets();
    std::cout
        << "Got " << wado_response.get_data_sets().size() << " data sets"
        << "\n";
}

Storing data sets#

STOW-RS requires both a selector and a vector of data sets. The selector must contain the studies term, and may specify the Study Instance UID:

#include <iostream>
#include <odil/DataSet.h>
#include <odil/uid.h>
#include <odil/webservices/STOWRSRequest.h>
#include <odil/webservices/STOWRSResponse.h>
#include <odil/webservices/Selector.h>
#include <odil/webservices/URL.h>

int main()
{
    odil::webservices::STOWRSRequest stow_request(
        odil::webservices::URL::parse("http://dicomserver.co.uk:81/stow"));

    odil::DataSet data_set;
    data_set.add("PatientName", {"Doe^John"});
    data_set.add("PatientID", {odil::generate_uid()});

    data_set.add("StudyInstanceUID", {odil::generate_uid()});

    data_set.add("Modality", {"OT"});
    data_set.add("SeriesInstanceUID", {odil::generate_uid()});

    data_set.add("SOPClassUID", {odil::registry::RawDataStorage});
    data_set.add("SOPInstanceUID", {odil::generate_uid()});

    stow_request.request_dicom(
        {data_set}, std::map<std::string,std::string>({{"studies", ""}}),
        odil::webservices::Representation::DICOM_XML);

    auto http_request = stow_request.get_http_request();
    http_request.set_http_version("HTTP/1.1");
    http_request.set_header("Host", stow_request.get_base_url().authority);
    http_request.set_header("Content-Length", std::to_string(http_request.get_body().size()));
    http_request.set_header("Accept", "application/dicom+xml");
    http_request.set_header("Connection", "close");

    auto const http_response = http_client(
      "dicomserver.co.uk", "81", http_request);

    odil::webservices::STOWRSResponse const stow_response(http_response);
    auto const responses = stow_response.get_store_instance_responses();
}