diff --git a/Modules/DICOMweb/CMakeLists.txt b/Modules/DICOMweb/CMakeLists.txt new file mode 100644 index 0000000000..d503d99cfe --- /dev/null +++ b/Modules/DICOMweb/CMakeLists.txt @@ -0,0 +1,3 @@ +mitk_create_module(DEPENDS MitkCore + MitkREST MitkRESTService) + diff --git a/Modules/DICOMweb/files.cmake b/Modules/DICOMweb/files.cmake new file mode 100644 index 0000000000..adba99da21 --- /dev/null +++ b/Modules/DICOMweb/files.cmake @@ -0,0 +1,3 @@ +set(CPP_FILES + mitkDICOMweb.cpp +) diff --git a/Modules/DICOMweb/include/mitkDICOMweb.h b/Modules/DICOMweb/include/mitkDICOMweb.h new file mode 100644 index 0000000000..8fdc321f4e --- /dev/null +++ b/Modules/DICOMweb/include/mitkDICOMweb.h @@ -0,0 +1,141 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#ifndef mitkDICOMweb_h +#define mitkDICOMweb_h + +#include + +#include "cpprest/asyncrt_utils.h" +#include "cpprest/http_client.h" +#include +#include +#include +#include + +#include +#include + +/** + * @brief This class represents the implementation of the RESTful DICOMweb standard + * (http://dicom.nema.org/medical/dicom/current/output/html/part18.html). It is used to communicate DICOM data over HTTP + * to a DICOMweb enabled PACS server. + * + * @author Tobias Stein + */ + +namespace mitk +{ + + class MITKDICOMWEB_EXPORT DICOMweb + { + public: + typedef web::http::uri_builder MitkUriBuilder; + typedef web::http::http_request MitkRequest; + typedef web::http::http_response MitkResponse; + typedef web::http::methods MitkRESTMethods; + + DICOMweb(); + + /** + * @brief Creates a DICOMweb service instance which allow basic DICOM operations on the given base URI. + * + * @param baseURI the uri for the PACS server: for example a dcm4chee + * http:///dcm4chee-arc/aets/DCM4CHEE/ + */ + DICOMweb(utility::string_t baseURI); + + /** + * @brief Sends a STOW request with the file in the given path to the study given bei its UID. + * + * @param filePath the path to a valid DICOM file which should be send + * @param studyUID the DICOM study uid + * @param the task to wait for + */ + pplx::task SendSTOW(utility::string_t filePath, utility::string_t studyUID); + + /** + * @brief Sends a WADO request for an DICOM object instance matching the given uid parameters and stores it at the + * given file path. + * + * @param filePath the path at which the retrieved DICOM object instance will be stored + * @param studyUID the DICOM study uid + * @param seriesUID the DICOM series uid + * @param instanceUID the DICOM instance uid + * @return the task to wait for, which unfolds no value when finished + */ + pplx::task SendWADO(utility::string_t filePath, + utility::string_t studyUID, + utility::string_t seriesUID, + utility::string_t instanceUID); + + /** + * @brief Sends a WADO request for an DICOM object series matching the given uid parameters and stores all the + * containing instances at the given folder path. + * + * @param folderPath the path at which the retrieved DICOM object instances of the retrieved series will be stored + * @param studyUID the DICOM study uid + * @param seriesUID the DICOM series uid + * @return the task to wait for, which unfolds the name of the first DICOM object file within the folder path + */ + pplx::task SendWADO(utility::string_t folderPath, + utility::string_t studyUID, + utility::string_t seriesUID); + + /** + * @brief Sends a QIDO request containing the given parameters to filter the query. + * + * Example Map: + * + * mitk::RESTUtil::ParamMap seriesInstancesParams; + * seriesInstancesParams.insert(mitk::RESTUtil::ParamMap::value_type(U("limit"), U("1"))); + * + * + * @param map the map of parameters to filter the query + * @return the task to wait for, which unfolds the result JSON response for the request when finished + */ + pplx::task SendQIDO(mitk::RESTUtil::ParamMap map); + + private: + /** + * @brief Creates a QIDO request URI with the given parameter map + */ + utility::string_t CreateQIDOUri(mitk::RESTUtil::ParamMap map); + + /** + * @brief Creates a WADO request URI with the given parameter + */ + utility::string_t CreateWADOUri(utility::string_t studyUID, + utility::string_t seriesUID, + utility::string_t instanceUID); + + /** + * @brief Creates a STOW request URI with the study uid + */ + utility::string_t CreateSTOWUri(utility::string_t studyUID); + + /** + * @brief Initializes the rest manager for this service instance. Should be called in constructor to make sure the + * public API can work properly. + */ + void InitializeRESTManager(); + + utility::string_t m_BaseURI; + mitk::IRESTManager *m_RESTManager; + }; +} + +#endif // DICOMweb_h diff --git a/Modules/DICOMweb/src/mitkDICOMweb.cpp b/Modules/DICOMweb/src/mitkDICOMweb.cpp new file mode 100644 index 0000000000..4ec930c449 --- /dev/null +++ b/Modules/DICOMweb/src/mitkDICOMweb.cpp @@ -0,0 +1,221 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkDICOMweb.h" + +mitk::DICOMweb::DICOMweb() {} + +mitk::DICOMweb::DICOMweb(utility::string_t baseURI) : m_BaseURI(baseURI) +{ + MITK_INFO << "base uri: " << mitk::RESTUtil::convertToUtf8(m_BaseURI); + InitializeRESTManager(); +} + +utility::string_t mitk::DICOMweb::CreateQIDOUri(mitk::RESTUtil::ParamMap map) +{ + MitkUriBuilder queryBuilder(m_BaseURI + U("rs/instances")); + + for (auto const &element : map) + { + queryBuilder.append_query(element.first, element.second); + } + + return queryBuilder.to_string(); +} + +utility::string_t mitk::DICOMweb::CreateWADOUri(utility::string_t studyUID, + utility::string_t seriesUID, + utility::string_t instanceUID) +{ + MitkUriBuilder builder(m_BaseURI + U("wado")); + builder.append_query(U("requestType"), U("WADO")); + builder.append_query(U("studyUID"), studyUID); + builder.append_query(U("seriesUID"), seriesUID); + builder.append_query(U("objectUID"), instanceUID); + builder.append_query(U("contentType"), U("application/dicom")); + + return builder.to_string(); +} + +utility::string_t mitk::DICOMweb::CreateSTOWUri(utility::string_t studyUID) +{ + MitkUriBuilder builder(m_BaseURI + U("rs/studies")); + builder.append_path(studyUID); + return builder.to_string(); +} + +pplx::task mitk::DICOMweb::SendSTOW(utility::string_t filePath, utility::string_t studyUID) +{ + auto uri = CreateSTOWUri(studyUID); + + // this is the working stow-rs request which supports just one dicom file packed into a multipart message + std::ifstream input(filePath, std::ios::binary); + if (!input) + { + MITK_WARN << "could not read file to POST"; + return pplx::task(); + } + + std::vector result; + std::vector buffer; + + // Stop eating new lines in binary mode!!! + input.unsetf(std::ios::skipws); + + input.seekg(0, std::ios::end); + const std::streampos fileSize = input.tellg(); + input.seekg(0, std::ios::beg); + + MITK_INFO << fileSize << " bytes will be sent."; + buffer.reserve(fileSize); // file size + std::copy( + std::istream_iterator(input), std::istream_iterator(), std::back_inserter(buffer)); + + // in future more than one file should also be supported.. + std::string head = ""; + head += "\r\n--boundary"; + head += "\r\nContent-Type: " + mitk::RESTUtil::convertToUtf8(U("application/dicom")) + "\r\n\r\n"; + + std::vector bodyVector(head.begin(), head.end()); + + std::string tail = ""; + tail += "\r\n--boundary--"; + + result.insert(result.end(), bodyVector.begin(), bodyVector.end()); + result.insert(result.end(), buffer.begin(), buffer.end()); + result.insert(result.end(), tail.begin(), tail.end()); + + mitk::RESTUtil::ParamMap headers; + headers.insert(mitk::RESTUtil::ParamMap::value_type( + U("Content-Type"), U("multipart/related; type=\"application/dicom\"; boundary=boundary"))); + + try + { + return m_RESTManager->SendBinaryRequest(uri, mitk::IRESTManager::RequestType::Post, &result, headers) + .then([=](web::json::value result) { + MITK_INFO << "after send"; + MITK_INFO << mitk::RESTUtil::convertToUtf8(result.serialize()); + result.is_null(); + }); + } + catch (std::exception &e) + { + MITK_WARN << e.what(); + } + + return pplx::task(); +} + +pplx::task mitk::DICOMweb::SendWADO(utility::string_t filePath, + utility::string_t studyUID, + utility::string_t seriesUID, + utility::string_t instanceUID) +{ + auto uri = CreateWADOUri(studyUID, seriesUID, instanceUID); + + // don't want return something + try + { + return m_RESTManager->SendJSONRequest(uri, mitk::IRESTManager::RequestType::Get, nullptr, {}, filePath) + .then([=](web::json::value result) { result.is_null(); }); + } + catch (const mitk::Exception &e) + { + mitkThrow() << e.what(); + } +} + +pplx::task mitk::DICOMweb::SendWADO(utility::string_t folderPath, + utility::string_t studyUID, + utility::string_t seriesUID) +{ + mitk::RESTUtil::ParamMap seriesInstances; + seriesInstances.insert(mitk::RESTUtil::ParamMap::value_type(U("StudyInstanceUID"), studyUID)); + seriesInstances.insert(mitk::RESTUtil::ParamMap::value_type(U("SeriesInstanceUID"), seriesUID)); + + return SendQIDO(seriesInstances).then([=](web::json::value jsonResult) -> pplx::task { + auto jsonListResult = jsonResult; + auto resultArray = jsonListResult.as_array(); + + auto firstFileName = std::string(); + + std::vector> tasks; + + for (unsigned short i = 0; i < resultArray.size(); i++) + { + try + { + auto firstResult = resultArray[i]; + auto sopInstanceUIDKey = firstResult.at(U("00080018")); + auto sopInstanceObject = sopInstanceUIDKey.as_object(); + auto valueKey = sopInstanceObject.at(U("Value")); + auto valueArray = valueKey.as_array(); + auto sopInstanceUID = valueArray[0].as_string(); + + auto fileName = utility::string_t(sopInstanceUID).append(U(".dcm")); + + // save first file name as result to load series + if (i == 0) + { + firstFileName = utility::conversions::to_utf8string(fileName); + } + + auto filePath = utility::string_t(folderPath).append(fileName); + auto task = SendWADO(filePath, studyUID, seriesUID, sopInstanceUID); + tasks.push_back(task); + } + catch (const web::json::json_exception &e) + { + MITK_ERROR << e.what(); + mitkThrow() << e.what(); + } + } + + auto joinTask = pplx::when_all(begin(tasks), end(tasks)); + + auto returnTask = joinTask.then([=](void) -> std::string { + auto folderPathUtf8 = utility::conversions::to_utf8string(folderPath); + auto result = folderPathUtf8 + firstFileName; + + return result; + }); + + return returnTask; + }); +} + +pplx::task mitk::DICOMweb::SendQIDO(mitk::RESTUtil::ParamMap map) +{ + auto uri = CreateQIDOUri(map); + + mitk::RESTUtil::ParamMap headers; + headers.insert(mitk::RESTUtil::ParamMap::value_type(U("Accept"), U("application/json"))); + return m_RESTManager->SendJSONRequest(uri, mitk::IRESTManager::RequestType::Get, nullptr, headers); +} + +void mitk::DICOMweb::InitializeRESTManager() +{ + auto *context = us::GetModuleContext(); + auto managerRef = context->GetServiceReference(); + if (managerRef) + { + auto managerService = context->GetService(managerRef); + if (managerService) + { + m_RESTManager = managerService; + } + } +} diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index 6c9775ec78..d153e96dad 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,85 +1,86 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(MITK_MODULES Core CommandLine AppUtil LegacyIO DataTypesExt Annotation LegacyGL AlgorithmsExt MapperExt DICOMReader DICOMReaderServices DICOMQI DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction SceneSerialization Gizmo GraphAlgorithms Multilabel Chart ImageStatistics ContourModel SurfaceInterpolation Segmentation QtWidgets QtWidgetsExt ImageStatisticsUI SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification OpenIGTLink IGTBase IGT CameraCalibration OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI PhotoacousticsHardware PhotoacousticsAlgorithms PhotoacousticsLib US USUI DicomUI Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI DicomRT RTUI IOExt XNAT TubeGraph BiophotonicsHardware # TumorInvasionAnalysis BoundingShape RenderWindowManager RenderWindowManagerUI SemanticRelations SemanticRelationsUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI DICOMPM REST RESTService + DICOMweb ) if(MITK_ENABLE_PIC_READER) list(APPEND MITK_MODULES IpPicSupportIO) endif()