# HG changeset patch # User Sebastien Jodogne # Date 1686923500 -7200 # Node ID 8c1fe0ca24f57f36d476e713dc5f682f27b9abf8 # Parent c34ecc350b32039bc6336f17cb6d0c27d930cffd added the possibility to replace DICOMweb by DICOM JSON diff -r c34ecc350b32 -r 8c1fe0ca24f5 CMakeLists.txt --- a/CMakeLists.txt Thu Jun 15 22:21:46 2023 +0200 +++ b/CMakeLists.txt Fri Jun 16 15:51:40 2023 +0200 @@ -75,7 +75,7 @@ set(ENABLE_LOCALE OFF) # Disable support for locales (notably in Boost) set(ENABLE_ZLIB ON) - set(ENABLE_MODULE_DICOM OFF CACHE INTERNAL "") + set(ENABLE_MODULE_DICOM ON CACHE INTERNAL "") set(ENABLE_MODULE_IMAGES OFF CACHE INTERNAL "") set(ENABLE_MODULE_JOBS OFF CACHE INTERNAL "") diff -r c34ecc350b32 -r 8c1fe0ca24f5 Sources/OrthancExplorer.js --- a/Sources/OrthancExplorer.js Thu Jun 15 22:21:46 2023 +0200 +++ b/Sources/OrthancExplorer.js Fri Jun 16 15:51:40 2023 +0200 @@ -44,7 +44,11 @@ b.insertAfter($('#study-info')); b.click(function() { - window.open('../ohif/viewer?StudyInstanceUIDs=' + studyInstanceUid); + if (${USE_DICOM_WEB}) { + window.open('../ohif/viewer?StudyInstanceUIDs=' + studyInstanceUid); + } else { + window.open('../ohif/viewer?url=../ohif-source/' + studyId); + } }); } }); diff -r c34ecc350b32 -r 8c1fe0ca24f5 Sources/Plugin.cpp --- a/Sources/Plugin.cpp Thu Jun 15 22:21:46 2023 +0200 +++ b/Sources/Plugin.cpp Fri Jun 16 15:51:40 2023 +0200 @@ -24,12 +24,13 @@ #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" +#include +#include #include #include #include #include #include -#include #include @@ -93,6 +94,7 @@ ohifStudyTags_[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID] = TagInformation(DataType_String, "StudyInstanceUID"); ohifStudyTags_[Orthanc::DICOM_TAG_STUDY_DATE] = TagInformation(DataType_String, "StudyDate"); ohifStudyTags_[Orthanc::DICOM_TAG_STUDY_TIME] = TagInformation(DataType_String, "StudyTime"); + ohifStudyTags_[Orthanc::DICOM_TAG_STUDY_DESCRIPTION] = TagInformation(DataType_String, "StudyDescription"); ohifStudyTags_[Orthanc::DICOM_TAG_PATIENT_NAME] = TagInformation(DataType_String, "PatientName"); ohifStudyTags_[Orthanc::DICOM_TAG_PATIENT_ID] = TagInformation(DataType_String, "PatientID"); ohifStudyTags_[Orthanc::DICOM_TAG_ACCESSION_NUMBER] = TagInformation(DataType_String, "AccessionNumber"); @@ -101,6 +103,7 @@ ohifSeriesTags_[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID] = TagInformation(DataType_String, "SeriesInstanceUID"); ohifSeriesTags_[Orthanc::DICOM_TAG_SERIES_NUMBER] = TagInformation(DataType_Integer, "SeriesNumber"); + ohifSeriesTags_[Orthanc::DICOM_TAG_SERIES_DESCRIPTION] = TagInformation(DataType_String, "SeriesDescription"); ohifSeriesTags_[Orthanc::DICOM_TAG_MODALITY] = TagInformation(DataType_String, "Modality"); ohifSeriesTags_[Orthanc::DICOM_TAG_SLICE_THICKNESS] = TagInformation(DataType_Float, "SliceThickness"); @@ -217,13 +220,13 @@ }; -static void GetOhifDicomTags(Json::Value& target, +static bool GetOhifDicomTags(Json::Value& target, const std::string& instanceId) { Json::Value source; if (!OrthancPlugins::RestApiGet(source, "/instances/" + instanceId + "/tags?short", false)) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + return false; } if (source.type() != Json::objectValue) @@ -321,11 +324,14 @@ } } } + + return true; } static ResourcesCache cache_; static std::string routerBasename_; +static bool useDicomWeb_; void ServeFile(OrthancPluginRestOutput* output, const char* url, @@ -339,7 +345,7 @@ OrthancPluginSetHttpHeader(context, output, "Cross-Origin-Opener-Policy", "same-origin"); OrthancPluginSetHttpHeader(context, output, "Cross-Origin-Resource-Policy", "same-origin"); - std::string uri = request->groups[0]; + const std::string uri = request->groups[0]; if (uri == "app-config.js") { @@ -349,6 +355,7 @@ std::map dictionary; dictionary["ROUTER_BASENAME"] = routerBasename_; + dictionary["USE_DICOM_WEB"] = (useDicomWeb_ ? "true" : "false"); system = Orthanc::Toolbox::SubstituteVariables(system, dictionary); @@ -366,6 +373,199 @@ } +static void GenerateOhifStudy(Json::Value& target, + const std::string& studyId) +{ + // https://v3-docs.ohif.org/configuration/dataSources/dicom-json + static const char* const KEY_ID = "ID"; + const std::string KEY_PATIENT_ID = Orthanc::DICOM_TAG_PATIENT_ID.Format(); + const std::string KEY_STUDY_INSTANCE_UID = Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format(); + const std::string KEY_SERIES_INSTANCE_UID = Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format(); + const std::string KEY_SOP_INSTANCE_UID = Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format(); + + Json::Value instancesIds; + if (!OrthancPlugins::RestApiGet(instancesIds, "/studies/" + studyId + "/instances", false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + if (instancesIds.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::vector instancesTags; + instancesTags.reserve(instancesIds.size()); + + for (Json::ArrayIndex i = 0; i < instancesIds.size(); i++) + { + if (instancesIds[i].type() != Json::objectValue || + !instancesIds[i].isMember(KEY_ID) || + instancesIds[i][KEY_ID].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + Json::Value t; + if (GetOhifDicomTags(t, instancesIds[i][KEY_ID].asString())) + { + instancesTags.push_back(t); + } + } + + typedef std::list ListOfResources; + typedef std::map MapOfResources; + + MapOfResources studies; + for (Json::ArrayIndex i = 0; i < instancesTags.size(); i++) + { + if (instancesTags[i].isMember(KEY_STUDY_INSTANCE_UID)) + { + if (instancesTags[i][KEY_STUDY_INSTANCE_UID].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + const std::string& studyInstanceUid = instancesTags[i][KEY_STUDY_INSTANCE_UID].asString(); + studies[studyInstanceUid].push_back(&instancesTags[i]); + } + } + } + + target["studies"] = Json::arrayValue; + + for (MapOfResources::const_iterator it = studies.begin(); it != studies.end(); ++it) + { + if (!it->second.empty()) + { + assert(it->second.front() != NULL); + const Json::Value& firstInstanceInStudy = *it->second.front(); + + Json::Value study = Json::objectValue; + for (TagsDictionary::const_iterator tag = ohifStudyTags_.begin(); tag != ohifStudyTags_.end(); ++tag) + { + if (firstInstanceInStudy.isMember(tag->first.Format())) + { + study[tag->second.GetName()] = firstInstanceInStudy[tag->first.Format()]; + } + } + + MapOfResources seriesInStudy; + for (ListOfResources::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + assert(*it2 != NULL); + const Json::Value& instanceInStudy = **it2; + + if (instanceInStudy.isMember(KEY_SERIES_INSTANCE_UID)) + { + if (instanceInStudy[KEY_SERIES_INSTANCE_UID].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + const std::string& seriesInstanceUid = instanceInStudy[KEY_SERIES_INSTANCE_UID].asString(); + seriesInStudy[seriesInstanceUid].push_back(&instanceInStudy); + } + } + } + + study["series"] = Json::arrayValue; + + for (MapOfResources::const_iterator it3 = seriesInStudy.begin(); it3 != seriesInStudy.end(); ++it3) + { + if (!it3->second.empty()) + { + assert(it3->second.front() != NULL); + const Json::Value& firstInstanceInSeries = *it3->second.front(); + + Json::Value series = Json::objectValue; + for (TagsDictionary::const_iterator tag = ohifSeriesTags_.begin(); tag != ohifSeriesTags_.end(); ++tag) + { + if (firstInstanceInSeries.isMember(tag->first.Format())) + { + series[tag->second.GetName()] = firstInstanceInSeries[tag->first.Format()]; + } + } + + series["instances"] = Json::arrayValue; + + for (ListOfResources::const_iterator it4 = it3->second.begin(); it4 != it3->second.end(); ++it4) + { + assert(*it4 != NULL); + const Json::Value& instanceInSeries = **it4; + + Json::Value metadata; + for (TagsDictionary::const_iterator tag = ohifInstanceTags_.begin(); tag != ohifInstanceTags_.end(); ++tag) + { + if (instanceInSeries.isMember(tag->first.Format())) + { + metadata[tag->second.GetName()] = instanceInSeries[tag->first.Format()]; + } + } + + Orthanc::DicomInstanceHasher hasher(instanceInSeries[KEY_PATIENT_ID].asString(), + instanceInSeries[KEY_STUDY_INSTANCE_UID].asString(), + instanceInSeries[KEY_SERIES_INSTANCE_UID].asString(), + instanceInSeries[KEY_SOP_INSTANCE_UID].asString()); + + Json::Value instance = Json::objectValue; + instance["metadata"] = metadata; + instance["url"] = "dicomweb:../instances/" + hasher.HashInstance() + "/file"; + + series["instances"].append(instance); + } + + study["series"].append(series); + } + } + + target["studies"].append(study); + } + } +} + + +void GetOhifStudy(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + const std::string studyId = request->groups[0]; + + Json::Value v; + GenerateOhifStudy(v, studyId); + + std::string s; + Orthanc::Toolbox::WriteFastJson(s, v); + + OrthancPluginAnswerBuffer(context, output, s.c_str(), s.size(), "application/json"); +} + + +static OrthancPluginErrorCode RedirectRoot(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + } + else + { + // TODO - Is there a better way to go to the list of studies in DICOMweb? + // TODO - What if not using DICOMweb? + OrthancPluginRedirect(context, output, "ohif/viewer?StudyInstanceUIDs="); + } + + return OrthancPluginErrorCode_Success; +} + + OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char* resourceId) @@ -374,41 +574,26 @@ { if (changeType == OrthancPluginChangeType_OrthancStarted) { - /*{ - Json::Value v; - GetOhifDicomTags(v, "54bfd747-407e46b1-ef106fdd-dc19e482-ff8dbe02"); - std::cout << v.toStyledString(); - std::string s; - Orthanc::Toolbox::WriteFastJson(s, v); - std::cout << s.size() << std::endl; - - Orthanc::GzipCompressor c; - std::string ss; - Orthanc::IBufferCompressor::Compress(ss, c, s); - std::cout << ss.size() << std::endl; + if (useDicomWeb_) + { + Json::Value info; + if (!OrthancPlugins::RestApiGet(info, "/plugins/dicom-web", false)) + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_InternalError, + "The OHIF plugin requires the DICOMweb plugin to be installed"); + } - std::string sss; - Orthanc::Toolbox::EncodeBase64(sss, ss); - std::cout << sss.size() << std::endl; - }*/ - - Json::Value info; - if (!OrthancPlugins::RestApiGet(info, "/plugins/dicom-web", false)) - { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_InternalError, - "The OHIF plugin requires the DICOMweb plugin to be installed"); - } - - if (info.type() != Json::objectValue || - !info.isMember("ID") || - !info.isMember("Version") || - info["ID"].type() != Json::stringValue || - info["Version"].type() != Json::stringValue || - info["ID"].asString() != "dicom-web") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "The DICOMweb plugin is not properly installed"); + if (info.type() != Json::objectValue || + !info.isMember("ID") || + !info.isMember("Version") || + info["ID"].type() != Json::stringValue || + info["Version"].type() != Json::stringValue || + info["ID"].asString() != "dicom-web") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "The DICOMweb plugin is required by OHIF, but is not properly installed"); + } } } } @@ -457,17 +642,27 @@ } routerBasename_ = configuration.GetStringValue("RouterBasename", "/ohif"); + useDicomWeb_ = configuration.GetBooleanValue("DicomWeb", false); OrthancPluginSetDescription(context, "OHIF plugin for Orthanc."); + OrthancPluginRegisterRestCallback(context, "/ohif", RedirectRoot); OrthancPlugins::RegisterRestCallback("/ohif/(.*)", true); + OrthancPlugins::RegisterRestCallback("/ohif-source/(.*)", true); OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); - // Extend the default Orthanc Explorer with custom JavaScript for OHIF - std::string explorer; - Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); - OrthancPluginExtendOrthancExplorer(context, explorer.c_str()); + { + // Extend the default Orthanc Explorer with custom JavaScript for OHIF + std::string explorer; + Orthanc::EmbeddedResources::GetFileResource(explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); + + std::map dictionary; + dictionary["USE_DICOM_WEB"] = (useDicomWeb_ ? "true" : "false"); + explorer = Orthanc::Toolbox::SubstituteVariables(explorer, dictionary); + + OrthancPluginExtendOrthancExplorer(context, explorer.c_str()); + } return 0; } diff -r c34ecc350b32 -r 8c1fe0ca24f5 Sources/app-config-system.js --- a/Sources/app-config-system.js Thu Jun 15 22:21:46 2023 +0200 +++ b/Sources/app-config-system.js Fri Jun 16 15:51:40 2023 +0200 @@ -6,29 +6,46 @@ window.config.routerBasename = '${ROUTER_BASENAME}'; -window.config.dataSources = [ - { - friendlyName: 'Orthanc', - namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', - sourceName: 'dicomweb', - configuration: { - name: 'orthanc', +if (${USE_DICOM_WEB}) { + window.config.dataSources = [ + { + friendlyName: 'Orthanc DICOMweb', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'orthanc', - wadoUriRoot: '../dicom-web', - qidoRoot: '../dicom-web', - wadoRoot: '../dicom-web', - - qidoSupportsIncludeField: false, - supportsReject: false, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, - supportsFuzzyMatching: false, - supportsWildcard: true, - staticWado: true, - singlepart: 'bulkdata' + wadoUriRoot: '../dicom-web', + qidoRoot: '../dicom-web', + wadoRoot: '../dicom-web', + + qidoSupportsIncludeField: false, + supportsReject: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata' + } } - } -]; + ]; + + window.config.defaultDataSourceName = 'dicomweb'; -window.config.defaultDataSourceName = 'dicomweb'; +} else { + window.config.showStudyList = false; + window.config.dataSources = [ + { + friendlyName: 'Orthanc JSON', + namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', + sourceName: 'dicomjson', + configuration: { + name: 'json', + }, + } + ]; + + window.config.defaultDataSourceName = 'dicomjson'; +}