# HG changeset patch # User Sebastien Jodogne # Date 1448295011 -3600 # Node ID 67466dcd23b1c92cd020e7f73936fd78b62f5c23 # Parent cd213ebcaefd180a4d3b2c7d6abe732b77944e36# Parent 796d0b087fb812ddd329837ce4e762e4da76138b integration worklists->mainline diff -r cd213ebcaefd -r 67466dcd23b1 CMakeLists.txt --- a/CMakeLists.txt Mon Nov 23 15:24:39 2015 +0100 +++ b/CMakeLists.txt Mon Nov 23 17:10:11 2015 +0100 @@ -31,6 +31,7 @@ SET(ENABLE_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression") SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Build the ServeFolders plugin") +SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Build the sample plugin to serve modality worklists") # Advanced parameters to fine-tune linking against system libraries SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") @@ -511,6 +512,49 @@ ##################################################################### +## Build the "ModalityWorklists" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_MODALITY_WORKLISTS) + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py + ${ORTHANC_VERSION} ModalityWorklists ModalityWorklists.dll "Sample Orthanc plugin to serve modality worklists" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/ModalityWorklists.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + add_definitions(-DMODALITY_WORKLISTS_VERSION="${ORTHANC_VERSION}") + + include_directories(${CMAKE_SOURCE_DIR}/Plugins/Include) + + add_library(ModalityWorklists SHARED + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + Plugins/Samples/ModalityWorklists/Plugin.cpp + ${AUTOGENERATED_DIR}/ModalityWorklists.rc + ) + + set_target_properties( + ModalityWorklists PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} + ) + + install( + TARGETS ModalityWorklists + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) +endif() + + + +##################################################################### ## Generate the documentation if Doxygen is present ##################################################################### diff -r cd213ebcaefd -r 67466dcd23b1 OrthancServer/DicomProtocol/DicomUserConnection.cpp --- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -353,7 +353,8 @@ struct FindPayload { DicomFindAnswers* answers; - std::string level; + const char* level; + bool isWorklist; }; } @@ -371,15 +372,23 @@ if (responseIdentifiers != NULL) { - DicomMap m; - FromDcmtkBridge::Convert(m, *responseIdentifiers); - - if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + if (payload.isWorklist) + { + ParsedDicomFile answer(*responseIdentifiers); + payload.answers->Add(answer); + } + else { - m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level); + DicomMap m; + FromDcmtkBridge::Convert(m, *responseIdentifiers); + + if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level); + } + + payload.answers->Add(m); } - - payload.answers->Add(m); } } @@ -474,6 +483,52 @@ } + static void ExecuteFind(DicomFindAnswers& answers, + T_ASC_Association* association, + DcmDataset* dataset, + const char* sopClass, + bool isWorklist, + const char* level, + uint32_t dimseTimeout) + { + assert(isWorklist ^ (level != NULL)); + + FindPayload payload; + payload.answers = &answers; + payload.level = level; + payload.isWorklist = isWorklist; + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(association, sopClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_DicomFindUnavailable); + } + + T_DIMSE_C_FindRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = association->nextMsgID++; + strcpy(request.AffectedSOPClassUID, sopClass); + request.DataSetType = DIMSE_DATASET_PRESENT; + request.Priority = DIMSE_PRIORITY_MEDIUM; + + T_DIMSE_C_FindRSP response; + DcmDataset* statusDetail = NULL; + OFCondition cond = DIMSE_findUser(association, presID, &request, dataset, + FindCallback, &payload, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ dimseTimeout, + &response, &statusDetail); + + if (statusDetail) + { + delete statusDetail; + } + + Check(cond); + } + + void DicomUserConnection::Find(DicomFindAnswers& result, ResourceType level, const DicomMap& originalFields) @@ -483,34 +538,32 @@ CheckIsOpen(); - FindPayload payload; - payload.answers = &result; + std::auto_ptr dataset(ConvertQueryFields(fields, manufacturer_)); + const char* clevel = NULL; + const char* sopClass = NULL; - std::auto_ptr dataset(ConvertQueryFields(fields, manufacturer_)); - - const char* sopClass; switch (level) { case ResourceType_Patient: - payload.level = "PATIENT"; + clevel = "PATIENT"; DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT"); sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; break; case ResourceType_Study: - payload.level = "STUDY"; + clevel = "STUDY"; DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY"); sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; break; case ResourceType_Series: - payload.level = "SERIES"; + clevel = "SERIES"; DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES"); sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; break; case ResourceType_Instance: - payload.level = "INSTANCE"; + clevel = "INSTANCE"; if (manufacturer_ == ModalityManufacturer_ClearCanvas || manufacturer_ == ModalityManufacturer_Dcm4Chee) { @@ -565,34 +618,8 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomFindUnavailable); - } - - T_DIMSE_C_FindRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = pimpl_->assoc_->nextMsgID++; - strcpy(request.AffectedSOPClassUID, sopClass); - request.DataSetType = DIMSE_DATASET_PRESENT; - request.Priority = DIMSE_PRIORITY_MEDIUM; - - T_DIMSE_C_FindRSP response; - DcmDataset* statusDetail = NULL; - OFCondition cond = DIMSE_findUser(pimpl_->assoc_, presID, &request, dataset.get(), - FindCallback, &payload, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - &response, &statusDetail); - - if (statusDetail) - { - delete statusDetail; - } - - Check(cond); + assert(clevel != NULL && sopClass != NULL); + ExecuteFind(result, pimpl_->assoc_, dataset.get(), sopClass, false, clevel, pimpl_->dimseTimeout_); } @@ -685,13 +712,14 @@ defaultStorageSOPClasses_.clear(); // Copy the short list of storage SOP classes from DCMTK, making - // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE. + // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**). std::set uncommon; uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); + uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage); // Add the storage syntaxes for C-STORE for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) @@ -721,11 +749,12 @@ pimpl_->params_ = NULL; pimpl_->assoc_ = NULL; - // SOP classes for C-ECHO, C-FIND and C-MOVE + // SOP classes for C-ECHO, C-FIND and C-MOVE (**) reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel); ResetStorageSOPClasses(); } @@ -1101,4 +1130,15 @@ CheckStorageSOPClassesInvariant(); } + + void DicomUserConnection::FindWorklist(DicomFindAnswers& result, + ParsedDicomFile& query) + { + CheckIsOpen(); + + DcmDataset* dataset = query.GetDcmtkObject().getDataset(); + const char* sopClass = UID_FINDModalityWorklistInformationModel; + + ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_); + } } diff -r cd213ebcaefd -r 67466dcd23b1 OrthancServer/DicomProtocol/DicomUserConnection.h --- a/OrthancServer/DicomProtocol/DicomUserConnection.h Mon Nov 23 15:24:39 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.h Mon Nov 23 17:10:11 2015 +0100 @@ -164,5 +164,8 @@ void SetTimeout(uint32_t seconds); void DisableTimeout(); + + void FindWorklist(DicomFindAnswers& result, + ParsedDicomFile& query); }; } diff -r cd213ebcaefd -r 67466dcd23b1 OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -620,7 +620,7 @@ throw OrthancException(ErrorCode_BadRequest); } - ParsedDicomFile dicom; + ParsedDicomFile dicom(true); { Encoding encoding; @@ -834,7 +834,7 @@ else { // Compatibility with Orthanc <= 0.9.3 - ParsedDicomFile dicom; + ParsedDicomFile dicom(true); CreateDicomV1(dicom, call, request); std::string id; diff -r cd213ebcaefd -r 67466dcd23b1 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/OrthancServer/ParsedDicomFile.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -814,13 +814,17 @@ } - ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl) + ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl) { pimpl_->file_.reset(new DcmFileFormat); - Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); - Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); - Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); - Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + + if (createIdentifiers) + { + Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); + Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); + Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); + Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } } diff -r cd213ebcaefd -r 67466dcd23b1 OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Mon Nov 23 15:24:39 2015 +0100 +++ b/OrthancServer/ParsedDicomFile.h Mon Nov 23 17:10:11 2015 +0100 @@ -62,7 +62,7 @@ bool decodeBinaryTags); public: - ParsedDicomFile(); // Create a minimal DICOM instance + ParsedDicomFile(bool createIdentifiers); // Create a minimal DICOM instance ParsedDicomFile(const DicomMap& map); diff -r cd213ebcaefd -r 67466dcd23b1 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/OrthancServer/main.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -736,7 +736,8 @@ dicomServer.SetFindRequestHandlerFactory(serverFactory); #if ORTHANC_PLUGINS_ENABLED == 1 - if (plugins) + if (plugins && + plugins->HasWorklistHandler()) { dicomServer.SetWorklistRequestHandlerFactory(*plugins); } @@ -1106,6 +1107,23 @@ { OrthancInitialize(configurationFile); + if (0) + { + // TODO REMOVE THIS TEST + DicomUserConnection c; + c.SetRemoteHost("localhost"); + c.SetRemotePort(4243); + c.SetRemoteApplicationEntityTitle("ORTHANCTEST"); + c.Open(); + ParsedDicomFile f(false); + f.Replace(DICOM_TAG_PATIENT_NAME, "M*"); + DicomFindAnswers a; + c.FindWorklist(a, f); + Json::Value j; + a.ToJson(j, true); + std::cout << j; + } + bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade); if (restart) { diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -1932,7 +1932,7 @@ ApplyDicomToJson(service, parameters); return true; - case _OrthancPluginService_AddWorklistAnswer: + case _OrthancPluginService_WorklistAddAnswer: { const _OrthancPluginWorklistAnswersOperation& p = *reinterpret_cast(parameters); @@ -1940,7 +1940,7 @@ return true; } - case _OrthancPluginService_MarkWorklistAnswersIncomplete: + case _OrthancPluginService_WorklistMarkIncomplete: { const _OrthancPluginWorklistAnswersOperation& p = *reinterpret_cast(parameters); @@ -1948,7 +1948,7 @@ return true; } - case _OrthancPluginService_IsWorklistMatch: + case _OrthancPluginService_WorklistIsMatch: { const _OrthancPluginWorklistQueryOperation& p = *reinterpret_cast(parameters); @@ -1956,7 +1956,7 @@ return true; } - case _OrthancPluginService_GetWorklistQueryDicom: + case _OrthancPluginService_WorklistGetDicomQuery: { const _OrthancPluginWorklistQueryOperation& p = *reinterpret_cast(parameters); @@ -2085,14 +2085,7 @@ IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler() { - bool hasHandler; - - { - boost::recursive_mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); - hasHandler = !pimpl_->worklistCallbacks_.empty(); - } - - if (hasHandler) + if (HasWorklistHandler()) { return new WorklistHandler(*this); } @@ -2101,4 +2094,12 @@ return NULL; } } + + + bool OrthancPlugins::HasWorklistHandler() + { + boost::recursive_mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); + return !pimpl_->worklistCallbacks_.empty(); + } + } diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Mon Nov 23 15:24:39 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.h Mon Nov 23 17:10:11 2015 +0100 @@ -211,6 +211,7 @@ SignalChangeInternal(OrthancPluginChangeType_OrthancStopped, OrthancPluginResourceType_None, NULL); } + bool HasWorklistHandler(); virtual IWorklistRequestHandler* ConstructWorklistRequestHandler(); }; diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Mon Nov 23 15:24:39 2015 +0100 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Mon Nov 23 17:10:11 2015 +0100 @@ -469,10 +469,10 @@ _OrthancPluginService_DrawText = 6011, /* Primitives for handling worklists */ - _OrthancPluginService_AddWorklistAnswer = 7000, - _OrthancPluginService_MarkWorklistAnswersIncomplete = 7001, - _OrthancPluginService_IsWorklistMatch = 7002, - _OrthancPluginService_GetWorklistQueryDicom = 7003, + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, _OrthancPluginService_INTERNAL = 0x7fffffff } _OrthancPluginService; @@ -4126,7 +4126,7 @@ * @return 0 if success, other value if error. * @ingroup Worklists **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddWorklistAnswer( + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( OrthancPluginContext* context, OrthancPluginWorklistAnswers* answers, const OrthancPluginWorklistQuery* query, @@ -4139,7 +4139,7 @@ params.dicom = dicom; params.size = size; - return context->InvokeService(context, _OrthancPluginService_AddWorklistAnswer, ¶ms); + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); } @@ -4156,7 +4156,7 @@ * @return 0 if success, other value if error. * @ingroup Worklists **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginMarkWorklistAnswersIncomplete( + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( OrthancPluginContext* context, OrthancPluginWorklistAnswers* answers) { @@ -4166,7 +4166,7 @@ params.dicom = NULL; params.size = 0; - return context->InvokeService(context, _OrthancPluginService_MarkWorklistAnswersIncomplete, ¶ms); + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); } @@ -4185,8 +4185,7 @@ * This function checks whether one worklist (encoded as a DICOM * file) matches the C-Find SCP query against modality * worklists. This function must be called before adding the - * worklist as an answer through - * OrthancPluginWorklistAddWorklistAnswer(). + * worklist as an answer through OrthancPluginWorklistAddAnswer(). * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param query The worklist query, as received by the callback. @@ -4195,7 +4194,7 @@ * @return 1 if the worklist matches the query, 0 otherwise. * @ingroup Worklists **/ - ORTHANC_PLUGIN_INLINE int32_t OrthancPluginIsWorklistMatch( + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( OrthancPluginContext* context, const OrthancPluginWorklistQuery* query, const void* dicom, @@ -4210,7 +4209,7 @@ params.isMatch = &isMatch; params.target = NULL; - if (context->InvokeService(context, _OrthancPluginService_IsWorklistMatch, ¶ms) == OrthancPluginErrorCode_Success) + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) { return isMatch; } @@ -4234,7 +4233,7 @@ * @return 0 if success, other value if error. * @ingroup Worklists **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetWorklistQueryDicom( + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( OrthancPluginContext* context, OrthancPluginMemoryBuffer* target, const OrthancPluginWorklistQuery* query) @@ -4246,7 +4245,7 @@ params.isMatch = NULL; params.target = target; - return context->InvokeService(context, _OrthancPluginService_GetWorklistQueryDicom, ¶ms); + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); } diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Samples/ModalityWorklists/CMakeLists.txt --- a/Plugins/Samples/ModalityWorklists/CMakeLists.txt Mon Nov 23 15:24:39 2015 +0100 +++ b/Plugins/Samples/ModalityWorklists/CMakeLists.txt Mon Nov 23 17:10:11 2015 +0100 @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 2.8) -project(SampleModalityWorklists) +project(ModalityWorklists) -SET(SAMPLE_MODALITY_WORKLISTS_VERSION "0.0" CACHE STRING "Version of the plugin") +SET(MODALITY_WORKLISTS_VERSION "0.0" CACHE STRING "Version of the plugin") SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") @@ -10,28 +10,27 @@ SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost") set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..) -include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) +include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake) include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake) include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake) -add_library(SampleModalityWorklists SHARED +add_library(ModalityWorklists SHARED Plugin.cpp ${JSONCPP_SOURCES} ${BOOST_SOURCES} ) -message("Setting the version of the plugin to ${SAMPLE_MODALITY_WORKLISTS_VERSION}") +message("Setting the version of the plugin to ${MODALITY_WORKLISTS_VERSION}") add_definitions( - -DSAMPLE_MODALITY_WORKLISTS_VERSION="${SAMPLE_MODALITY_WORKLISTS_VERSION}" - -DDEFAULT_WORKLISTS_FOLDER="${CMAKE_SOURCE_DIR}/WorklistsDatabase" + -DMODALITY_WORKLISTS_VERSION="${MODALITY_WORKLISTS_VERSION}" ) -set_target_properties(SampleModalityWorklists PROPERTIES - VERSION ${SAMPLE_MODALITY_WORKLISTS_VERSION} - SOVERSION ${SAMPLE_MODALITY_WORKLISTS_VERSION}) +set_target_properties(ModalityWorklists PROPERTIES + VERSION ${MODALITY_WORKLISTS_VERSION} + SOVERSION ${MODALITY_WORKLISTS_VERSION}) install( - TARGETS SampleModalityWorklists + TARGETS ModalityWorklists RUNTIME DESTINATION lib # Destination for Windows LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux ) diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Samples/ModalityWorklists/Plugin.cpp --- a/Plugins/Samples/ModalityWorklists/Plugin.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/Plugins/Samples/ModalityWorklists/Plugin.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -61,10 +61,10 @@ return OrthancPluginErrorCode_Success; } - if (OrthancPluginIsWorklistMatch(context_, query, dicom.c_str(), dicom.size())) + if (OrthancPluginWorklistIsMatch(context_, query, dicom.c_str(), dicom.size())) { // This DICOM file matches the worklist query, add it to the answers - return OrthancPluginWorklistAddWorklistAnswer + return OrthancPluginWorklistAddAnswer (context_, answers, query, dicom.c_str(), dicom.size()); } else @@ -98,7 +98,7 @@ const OrthancPluginWorklistQuery* query) { OrthancPluginMemoryBuffer dicom; - if (OrthancPluginGetWorklistQueryDicom(context_, &dicom, query)) + if (OrthancPluginWorklistGetDicomQuery(context_, &dicom, query)) { return false; } @@ -169,7 +169,8 @@ ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c) { context_ = c; - OrthancPluginLogWarning(context_, "Storage plugin is initializing"); + OrthancPluginLogWarning(context_, "Sample worklist plugin is initializing"); + OrthancPluginSetDescription(context_, "Serve DICOM modality worklists from a folder with Orthanc."); /* Check the version of the Orthanc core */ if (OrthancPluginCheckVersion(c) == 0) @@ -187,29 +188,53 @@ Json::Value configuration; if (!ConvertToJson(configuration, OrthancPluginGetConfiguration(context_))) { - OrthancPluginLogError(context_, "Cannot access the configuration"); + OrthancPluginLogError(context_, "Cannot access the configuration of the worklist server"); return -1; } - if (configuration.isMember("WorklistsFolder")) + bool enabled = false; + + if (configuration.isMember("Worklists")) { - if (configuration["WorklistsFolder"].type() != Json::stringValue) + const Json::Value& config = configuration["Worklists"]; + if (!config.isMember("Enable") || + config["Enable"].type() != Json::booleanValue) { - OrthancPluginLogError(context_, "The configuration option \"WorklistsFolder\" must be a string"); + OrthancPluginLogError(context_, "The configuration option \"Worklists.Enable\" must contain a Boolean"); return -1; } + else + { + enabled = config["Enable"].asBool(); + if (enabled) + { + if (!config.isMember("Database") || + config["Database"].type() != Json::stringValue) + { + OrthancPluginLogError(context_, "The configuration option \"Worklists.Database\" must contain a path"); + return -1; + } - folder_ = configuration["WorklistsFolder"].asString(); + folder_ = config["Database"].asString(); + } + else + { + OrthancPluginLogWarning(context_, "Worklists server is disabled by the configuration file"); + } + } } else { - folder_ = DEFAULT_WORKLISTS_FOLDER; + OrthancPluginLogWarning(context_, "Worklists server is disabled, no suitable configuration section was provided"); } - std::string message = "The database of worklists will be read from folder: " + folder_; - OrthancPluginLogWarning(context_, message.c_str()); + if (enabled) + { + std::string message = "The database of worklists will be read from folder: " + folder_; + OrthancPluginLogWarning(context_, message.c_str()); - OrthancPluginRegisterWorklistCallback(context_, Callback); + OrthancPluginRegisterWorklistCallback(context_, Callback); + } return 0; } @@ -223,12 +248,12 @@ ORTHANC_PLUGINS_API const char* OrthancPluginGetName() { - return "sample-worklists"; + return "worklists"; } ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() { - return SAMPLE_MODALITY_WORKLISTS_VERSION; + return MODALITY_WORKLISTS_VERSION; } } diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Samples/ModalityWorklists/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Samples/ModalityWorklists/README Mon Nov 23 17:10:11 2015 +0100 @@ -0,0 +1,60 @@ +Introduction +============ + +This sample plugin enables Orthanc to serve DICOM modality worklists +that are read from some folder. + +Whenever a C-Find SCP request is issued to Orthanc, it will read the +content of this folder to locate the worklists that match the request. +An external application can dynamically modify the content of this +folder while Orthanc is running to add/remove worklists. + +This sample mimics the behavior of the "wlmscpfs" tool from the +DCMTK package: +http://support.dcmtk.org/docs/wlmscpfs.html + + +Compilation for Linux +===================== + +# mkdir Build +# cd Build +# cmake .. +# make + + +Cross-compilation for Windows using MinGW +========================================= + +# mkdir Build +# cd Build +# cmake .. -DCMAKE_TOOLCHAIN_FILE=../../../Resources/MinGWToolchain.cmake +# make + + +Configuration +============= + +First, generate the default configuration of Orthanc: +https://orthanc.chu.ulg.ac.be/book/users/configuration.html + +Then, modify the "Plugins" option to point to the folder containing +the built plugins. Finally, create a section "ModalityWorklists" in +the configuration file to configure the worklist server. If using the +build commands above, a sample configuration would read as follows: + +{ + [...] + "Plugins" : [ + "." + ], + "Worklists" : { + "Enable": true, + "Database": "../WorklistsDatabase" + } +} + +The folder "WorklistsDatabase" contains a database of sample +worklists, that come from the DCMTK source distribution, as described +in the FAQ entry #37 of the DCMTK project: +http://forum.dcmtk.org/viewtopic.php?t=84 diff -r cd213ebcaefd -r 67466dcd23b1 Plugins/Samples/ServeFolders/README --- a/Plugins/Samples/ServeFolders/README Mon Nov 23 15:24:39 2015 +0100 +++ b/Plugins/Samples/ServeFolders/README Mon Nov 23 17:10:11 2015 +0100 @@ -27,7 +27,7 @@ ============= First, generate the default configuration of Orthanc: -https://code.google.com/p/orthanc/wiki/OrthancConfiguration +https://orthanc.chu.ulg.ac.be/book/users/configuration.html Then, modify the "Plugins" option to point to the folder containing the built plugins. diff -r cd213ebcaefd -r 67466dcd23b1 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Mon Nov 23 15:24:39 2015 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Nov 23 17:10:11 2015 +0100 @@ -75,7 +75,7 @@ //m.Replace(DICOM_TAG_PATIENT_ID, "coucou"); //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); - ParsedDicomFile o; + ParsedDicomFile o(true); o.SaveToFile("UnitTestsResults/anon.dcm"); for (int i = 0; i < 10; i++) @@ -103,7 +103,7 @@ ASSERT_EQ(0x1020, privateTag2.GetElement()); std::string s; - ParsedDicomFile o; + ParsedDicomFile o(true); o.Replace(DICOM_TAG_PATIENT_NAME, "coucou"); ASSERT_FALSE(o.GetTagValue(s, privateTag)); o.Insert(privateTag, "private tag", false); @@ -161,7 +161,7 @@ ASSERT_EQ(5u, reader.GetWidth()); ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat()); - ParsedDicomFile o; + ParsedDicomFile o(true); o.EmbedContent(s); o.SaveToFile("UnitTestsResults/png1.dcm"); @@ -267,7 +267,7 @@ std::string dicom; { - ParsedDicomFile f; + ParsedDicomFile f(true); f.SetEncoding(testEncodings[i]); std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); @@ -407,7 +407,7 @@ TEST(ParsedDicomFile, InsertReplaceStrings) { - ParsedDicomFile f; + ParsedDicomFile f(true); f.Insert(DICOM_TAG_PATIENT_NAME, "World", false); ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException); // Already existing tag @@ -446,7 +446,7 @@ TEST(ParsedDicomFile, InsertReplaceJson) { - ParsedDicomFile f; + ParsedDicomFile f(true); Json::Value a; CreateSampleJson(a); @@ -498,7 +498,7 @@ TEST(ParsedDicomFile, JsonEncoding) { - ParsedDicomFile f; + ParsedDicomFile f(true); for (unsigned int i = 0; i < testEncodingsCount; i++) { @@ -528,7 +528,7 @@ FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), EVR_PN, "MyPrivateTag", 1, 1); FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1); - ParsedDicomFile f; + ParsedDicomFile f(true); f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false); // Even group => public tag f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false); // Even group => public, unknown tag f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false); // Odd group => private tag @@ -575,7 +575,7 @@ TEST(ParsedDicomFile, ToJsonFlags2) { - ParsedDicomFile f; + ParsedDicomFile f(true); f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false); Json::Value v; @@ -620,7 +620,7 @@ } { - ParsedDicomFile d; + ParsedDicomFile d(true); d.Replace(DICOM_TAG_PATIENT_ID, "my"); a.Add(d); }