# HG changeset patch # User Sebastien Jodogne # Date 1464265496 -7200 # Node ID 364cc624eb652b8563a0317731e92c1d45844178 # Parent 9b61701c35f20ae96497535c2f34dc1e47aca7c8 New URI "/modalities/.../move" to issue C-Move SCU requests diff -r 9b61701c35f2 -r 364cc624eb65 NEWS --- a/NEWS Wed May 25 15:16:17 2016 +0200 +++ b/NEWS Thu May 26 14:24:56 2016 +0200 @@ -2,6 +2,13 @@ =============================== +REST API +-------- + +* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding) +* New URI "/modalities/.../move" to issue C-Move SCU requests +* "MoveOriginatorID" can be specified for "/modalities/.../store" + Dicom protocol -------------- @@ -9,20 +16,12 @@ 0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209 * Support of Move Originator Message ID (0000,1031) in C-Store responses driven by C-Move -Image decoding --------------- - -* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding) -* Huge speedup if decoding the family of JPEG transfer syntaxes -* Refactoring leading to speedups with custom image decoders (including Web viewer plugin) -* Support decoding of RLE Lossless transfer syntax -* Support of signed 16bpp images in ParsedDicomFile - Plugins ------- * New callback to filter incoming HTTP requests: OrthancPluginRegisterIncomingHttpRequestFilter() -* New callback to handle non-worklists C-Find requests: OrthancPluginRegisterCFindCallback() +* New callback to handle non-worklists C-Find requests: OrthancPluginRegisterFindCallback() +* New callback to handle C-Move requests: OrthancPluginRegisterMoveCallback() * New function: "OrthancPluginHttpClient()" to do HTTP requests with full control * New function: "OrthancPluginGenerateUuid()" to generate a UUID @@ -31,11 +30,13 @@ * Access to the HTTP headers in the "IncomingHttpRequestFilter()" callback -REST API --------- +Image decoding +-------------- -* New URI "/modalities/.../move" to issue C-Move SCU requests -* "MoveOriginatorID" can be specified for "/modalities/.../store" +* Huge speedup if decoding the family of JPEG transfer syntaxes +* Refactoring leading to speedups with custom image decoders (including Web viewer plugin) +* Support decoding of RLE Lossless transfer syntax +* Support of signed 16bpp images in ParsedDicomFile Maintenance ----------- diff -r 9b61701c35f2 -r 364cc624eb65 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Wed May 25 15:16:17 2016 +0200 +++ b/OrthancServer/main.cpp Thu May 26 14:24:56 2016 +0200 @@ -767,6 +767,11 @@ { dicomServer.SetFindRequestHandlerFactory(*plugins); } + + if (plugins->HasMoveHandler()) + { + dicomServer.SetMoveRequestHandlerFactory(*plugins); + } } #endif diff -r 9b61701c35f2 -r 364cc624eb65 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Wed May 25 15:16:17 2016 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu May 26 14:24:56 2016 +0200 @@ -316,12 +316,14 @@ OrthancPluginFindCallback findCallback_; OrthancPluginWorklistCallback worklistCallback_; OrthancPluginDecodeImageCallback decodeImageCallback_; + _OrthancPluginMoveCallback moveCallbacks_; IncomingHttpRequestFilters incomingHttpRequestFilters_; std::auto_ptr storageArea_; boost::recursive_mutex restCallbackMutex_; boost::recursive_mutex storedCallbackMutex_; boost::recursive_mutex changeCallbackMutex_; boost::mutex findCallbackMutex_; + boost::mutex moveCallbackMutex_; boost::mutex worklistCallbackMutex_; boost::mutex decodeImageCallbackMutex_; boost::recursive_mutex invokeServiceMutex_; @@ -339,6 +341,7 @@ argc_(1), argv_(NULL) { + memset(&moveCallbacks_, 0, sizeof(moveCallbacks_)); } }; @@ -540,6 +543,155 @@ + class OrthancPlugins::MoveHandler : public IMoveRequestHandler + { + private: + class Driver : public IMoveRequestIterator + { + private: + void* driver_; + unsigned int count_; + unsigned int pos_; + OrthancPluginApplyMove apply_; + OrthancPluginFreeMove free_; + + public: + Driver(void* driver, + unsigned int count, + OrthancPluginApplyMove apply, + OrthancPluginFreeMove free) : + driver_(driver), + count_(count), + pos_(0), + apply_(apply), + free_(free) + { + if (driver_ == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + } + + virtual ~Driver() + { + if (driver_ != NULL) + { + free_(driver_); + driver_ = NULL; + } + } + + virtual unsigned int GetSubOperationCount() const + { + return count_; + } + + virtual Status DoNext() + { + if (pos_ >= count_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + OrthancPluginErrorCode error = apply_(driver_); + if (error != OrthancPluginErrorCode_Success) + { + LOG(ERROR) << "Error while doing C-Move from plugin: " << EnumerationToString(static_cast(error)); + return Status_Failure; + } + else + { + pos_++; + return Status_Success; + } + } + } + }; + + + _OrthancPluginMoveCallback params_; + + + static std::string ReadTag(const DicomMap& input, + const DicomTag& tag) + { + const DicomValue* value = input.TestAndGetValue(tag); + if (value != NULL && + !value->IsBinary() && + !value->IsNull()) + { + return value->GetContent(); + } + else + { + return std::string(); + } + } + + + + public: + MoveHandler(OrthancPlugins& that) + { + boost::mutex::scoped_lock lock(that.pimpl_->moveCallbackMutex_); + params_ = that.pimpl_->moveCallbacks_; + + if (params_.callback == NULL || + params_.getMoveSize == NULL || + params_.applyMove == NULL || + params_.freeMove == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + } + + virtual IMoveRequestIterator* Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + uint16_t messageId) + { + std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL); + std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID); + std::string accessionNumber = ReadTag(input, DICOM_TAG_ACCESSION_NUMBER); + std::string studyInstanceUid = ReadTag(input, DICOM_TAG_STUDY_INSTANCE_UID); + std::string seriesInstanceUid = ReadTag(input, DICOM_TAG_SERIES_INSTANCE_UID); + std::string sopInstanceUid = ReadTag(input, DICOM_TAG_SOP_INSTANCE_UID); + + OrthancPluginResourceType level = OrthancPluginResourceType_None; + + if (!levelString.empty()) + { + level = Plugins::Convert(StringToResourceType(levelString.c_str())); + } + + void* driver = params_.callback(level, + patientId.empty() ? NULL : patientId.c_str(), + accessionNumber.empty() ? NULL : accessionNumber.c_str(), + studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(), + seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(), + sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(), + remoteAet.c_str(), + calledAet.c_str(), + targetAet.c_str(), + messageId); + + if (driver == NULL) + { + LOG(ERROR) << "Plugin cannot create a driver for an incoming C-MOVE request"; + throw OrthancException(ErrorCode_Plugin); + } + + unsigned int size = params_.getMoveSize(driver); + + return new Driver(driver, size, params_.applyMove, params_.freeMove); + } + }; + + + OrthancPlugins::OrthancPlugins() { /* Sanity check of the compiler */ @@ -881,6 +1033,26 @@ } + void OrthancPlugins::RegisterMoveCallback(const void* parameters) + { + const _OrthancPluginMoveCallback& p = + *reinterpret_cast(parameters); + + boost::mutex::scoped_lock lock(pimpl_->moveCallbackMutex_); + + if (pimpl_->moveCallbacks_.callback != NULL) + { + LOG(ERROR) << "Can only register one plugin to handle C-MOVE requests"; + throw OrthancException(ErrorCode_Plugin); + } + else + { + LOG(INFO) << "Plugin has registered a callback to handle C-MOVE requests"; + pimpl_->moveCallbacks_ = p; + } + } + + void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters) { const _OrthancPluginDecodeImageCallback& p = @@ -1982,6 +2154,10 @@ RegisterFindCallback(parameters); return true; + case _OrthancPluginService_RegisterMoveCallback: + RegisterMoveCallback(parameters); + return true; + case _OrthancPluginService_RegisterDecodeImageCallback: RegisterDecodeImageCallback(parameters); return true; @@ -2666,6 +2842,26 @@ } + IMoveRequestHandler* OrthancPlugins::ConstructMoveRequestHandler() + { + if (HasMoveHandler()) + { + return new MoveHandler(*this); + } + else + { + return NULL; + } + } + + + bool OrthancPlugins::HasMoveHandler() + { + boost::mutex::scoped_lock lock(pimpl_->moveCallbackMutex_); + return pimpl_->moveCallbacks_.callback != NULL; + } + + bool OrthancPlugins::HasCustomImageDecoder() { boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_); diff -r 9b61701c35f2 -r 364cc624eb65 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Wed May 25 15:16:17 2016 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu May 26 14:24:56 2016 +0200 @@ -54,6 +54,7 @@ #include "../../OrthancServer/IDicomImageDecoder.h" #include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h" #include "../../OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h" +#include "../../OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h" #include "OrthancPluginDatabase.h" #include "PluginsManager.h" @@ -71,7 +72,8 @@ public IWorklistRequestHandlerFactory, public IDicomImageDecoder, public IIncomingHttpRequestFilter, - public IFindRequestHandlerFactory + public IFindRequestHandlerFactory, + public IMoveRequestHandlerFactory { private: struct PImpl; @@ -79,6 +81,7 @@ class WorklistHandler; class FindHandler; + class MoveHandler; void CheckContextAvailable(); @@ -93,6 +96,8 @@ void RegisterFindCallback(const void* parameters); + void RegisterMoveCallback(const void* parameters); + void RegisterDecodeImageCallback(const void* parameters); void RegisterIncomingHttpRequestFilter(const void* parameters); @@ -260,6 +265,10 @@ bool HasFindHandler(); virtual IFindRequestHandler* ConstructFindRequestHandler(); + + bool HasMoveHandler(); + + virtual IMoveRequestHandler* ConstructMoveRequestHandler(); }; } diff -r 9b61701c35f2 -r 364cc624eb65 Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Wed May 25 15:16:17 2016 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Thu May 26 14:24:56 2016 +0200 @@ -20,6 +20,7 @@ * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter(). * -# void OrthancPluginFinalize(): @@ -417,6 +418,7 @@ _OrthancPluginService_RegisterDecodeImageCallback = 1006, _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007, _OrthancPluginService_RegisterFindCallback = 1008, + _OrthancPluginService_RegisterMoveCallback = 1009, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -966,7 +968,7 @@ * * @param answers The target structure where answers must be stored. * @param query The worklist query. - * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. * @return 0 if success, other value if error. * @ingroup DicomCallbacks @@ -974,33 +976,12 @@ typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( OrthancPluginWorklistAnswers* answers, const OrthancPluginWorklistQuery* query, - const char* remoteAet, + const char* issuerAet, const char* calledAet); /** - * @brief Callback to handle the C-Find SCP requests. - * - * Signature of a callback function that is triggered when Orthanc - * receives a C-Find SCP request not concerning modality worklists. - * - * @param answers The target structure where answers must be stored. - * @param query The worklist query. - * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. - * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. - * @return 0 if success, other value if error. - * @ingroup DicomCallbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) ( - OrthancPluginFindAnswers* answers, - const OrthancPluginFindQuery* query, - const char* remoteAet, - const char* calledAet); - - - - /** * @brief Callback to filter incoming HTTP requests received by Orthanc. * * Signature of a callback function that is triggered whenever @@ -1029,6 +1010,116 @@ /** + * @brief Callback to handle incoming C-Find SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const char* issuerAet, + const char* calledAet); + + + + /** + * @brief Callback to handle incoming C-Move SCP requests. + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer. + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param issuerAet The Application Entity Title (AET) of the + * modality from which the request originates. + * @param sourceAet The Application Entity Title (AET) of the + * modality that should send its DICOM files to another modality. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + typedef void* (*OrthancPluginMoveCallback) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const char* issuerAet, + const char* sourceAet, + const char* targetAet, + uint16_t moveOriginatorId); + + + /** + * @brief Callback to read the size of a C-Move driver. + * + * Signature of a callback function that returns the number of + * C-Move suboperations that are to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return The number of suboperations. + **/ + typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver); + + + /** + * @brief Callback to apply one C-Move suboperation. + * + * Signature of a callback function that applies the next C-Move + * suboperation that os to be achieved by the given C-Move + * driver. This driver is the return value of a previous call to the + * OrthancPluginMoveCallback() callback. + * + * @param moveDriver The C-Move driver of interest. + * @return 0 if success, or the error code if failure. + **/ + typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver); + + + /** + * @brief Callback to free one C-Move driver. + * + * Signature of a callback function that releases the resources + * allocated by the given C-Move driver. This driver is the return + * value of a previous call to the OrthancPluginMoveCallback() + * callback. + * + * @param moveDriver The C-Move driver of interest. + **/ + typedef void (*OrthancPluginFreeMove) (void* moveDriver); + + + + /** * @brief Data structure that contains information about the Orthanc core. **/ typedef struct _OrthancPluginContext_t @@ -5183,6 +5274,49 @@ } } + + + + typedef struct + { + OrthancPluginMoveCallback callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback; + + /** + * @brief Register a callback to handle C-Move requests. + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperations. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback( + OrthancPluginContext* context, + OrthancPluginMoveCallback callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, ¶ms); + } + + + + #ifdef __cplusplus } #endif