Mercurial > hg > orthanc-dicomweb
changeset 361:1dc8e5753d3f
Optimization of QIDO-RS for large studies/series
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 11 Sep 2019 17:35:20 +0200 |
parents | c9cc22e56952 |
children | 04a1e07a3085 |
files | NEWS Plugin/DicomWebFormatter.cpp Plugin/DicomWebFormatter.h Plugin/QidoRs.cpp |
diffstat | 4 files changed, 111 insertions(+), 96 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Wed Sep 11 15:08:13 2019 +0200 +++ b/NEWS Wed Sep 11 17:35:20 2019 +0200 @@ -6,6 +6,7 @@ * Accept multiple MIME types in Accept header for WADO-RS "Retrieve Metadata" * Added explicit "Accept" header to avoid uncompressing DICOM files by Google cloud https://groups.google.com/d/msg/orthanc-users/w1Ekrsc6-U8/T2a_DoQ5CwAJ +* Optimization of QIDO-RS for large studies/series Version 1.0 (2019-06-26)
--- a/Plugin/DicomWebFormatter.cpp Wed Sep 11 15:08:13 2019 +0200 +++ b/Plugin/DicomWebFormatter.cpp Wed Sep 11 17:35:20 2019 +0200 @@ -183,6 +183,27 @@ } + void DicomWebFormatter::HttpWriter::AddOrthancMap(const Orthanc::DicomMap& value) + { + Json::Value json = Json::objectValue; + + std::set<Orthanc::DicomTag> tags; + value.GetTags(tags); + + for (std::set<Orthanc::DicomTag>::const_iterator + it = tags.begin(); it != tags.end(); ++it) + { + std::string s; + if (value.LookupStringValue(s, *it, false)) + { + json[it->Format()] = s; + } + } + + AddOrthancJson(json); + } + + void DicomWebFormatter::HttpWriter::AddOrthancJson(const Json::Value& value) { MemoryBuffer dicom;
--- a/Plugin/DicomWebFormatter.h Wed Sep 11 15:08:13 2019 +0200 +++ b/Plugin/DicomWebFormatter.h Wed Sep 11 17:35:20 2019 +0200 @@ -22,6 +22,7 @@ #pragma once #include <Core/ChunkedBuffer.h> +#include <Core/DicomFormat/DicomMap.h> #include <orthanc/OrthancCPlugin.h> @@ -109,6 +110,8 @@ AddInternal(dicom, size, OrthancPluginDicomWebBinaryMode_BulkDataUri, bulkRoot); } + void AddOrthancMap(const Orthanc::DicomMap& value); + void AddOrthancJson(const Json::Value& value); void AddDicomWebSerializedJson(const void* data,
--- a/Plugin/QidoRs.cpp Wed Sep 11 15:08:13 2019 +0200 +++ b/Plugin/QidoRs.cpp Wed Sep 11 17:35:20 2019 +0200 @@ -24,6 +24,7 @@ #include "Configuration.h" #include "DicomWebFormatter.h" +#include <Core/DicomFormat/DicomMap.h> #include <Core/DicomFormat/DicomTag.h> #include <Core/Toolbox.h> #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h> @@ -36,41 +37,6 @@ namespace { - static std::string GetOrthancTag(const Json::Value& source, - const Orthanc::DicomTag& tag, - const std::string& defaultValue) - { - const std::string s = tag.Format(); - - if (source.isMember(s)) - { - switch (source[s].type()) - { - case Json::stringValue: - return source[s].asString(); - - // The conversions below should *not* be necessary - - case Json::intValue: - return boost::lexical_cast<std::string>(source[s].asInt()); - - case Json::uintValue: - return boost::lexical_cast<std::string>(source[s].asUInt()); - - case Json::realValue: - return boost::lexical_cast<std::string>(source[s].asFloat()); - - default: - return defaultValue; - } - } - else - { - return defaultValue; - } - } - - class ModuleMatcher { public: @@ -292,36 +258,52 @@ } - void ComputeDerivedTags(Filters& target, - Orthanc::ResourceType level, - const std::string& resource) const + void InjectDerivedTags(Orthanc::DicomMap& target, + Orthanc::ResourceType level, + const std::string& resource) const { - target.clear(); - + static const char* const INSTANCES = "Instances"; + static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; + static const char* const MODALITY = "Modality"; + switch (level) { case Orthanc::ResourceType_Study: { - Json::Value series, instances; + Json::Value series; if (OrthancPlugins::RestApiGet(series, "/studies/" + resource + "/series?expand", false) && - OrthancPlugins::RestApiGet(instances, "/studies/" + resource + "/instances", false)) + series.type() == Json::arrayValue) { - target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES] = - boost::lexical_cast<std::string>(series.size()); - target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES] = - boost::lexical_cast<std::string>(instances.size()); - - // Collect the Modality of all the child series + // Collect the Modality of all the child series, and std::set<std::string> modalities; + unsigned int countInstances = 0; + for (Json::Value::ArrayIndex i = 0; i < series.size(); i++) { - if (series[i].isMember("MainDicomTags") && - series[i]["MainDicomTags"].isMember("Modality")) + if (series[i].type() == Json::objectValue) { - modalities.insert(series[i]["MainDicomTags"]["Modality"].asString()); + if (series[i].isMember(MAIN_DICOM_TAGS) && + series[i][MAIN_DICOM_TAGS].type() == Json::objectValue && + series[i][MAIN_DICOM_TAGS].isMember(MODALITY) && + series[i][MAIN_DICOM_TAGS][MODALITY].type() == Json::stringValue) + { + modalities.insert(series[i][MAIN_DICOM_TAGS][MODALITY].asString()); + } + + if (series[i].isMember(INSTANCES) && + series[i][INSTANCES].type() == Json::arrayValue) + { + countInstances += series[i][INSTANCES].size(); + } } } + target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, + boost::lexical_cast<std::string>(series.size()), false); + + target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, + boost::lexical_cast<std::string>(countInstances), false); + std::string s; for (std::set<std::string>::const_iterator it = modalities.begin(); it != modalities.end(); ++it) @@ -334,13 +316,13 @@ s += *it; } - target[Orthanc::DICOM_TAG_MODALITIES_IN_STUDY] = s; + target.SetValue(Orthanc::DICOM_TAG_MODALITIES_IN_STUDY, s, false); } else { - target[Orthanc::DICOM_TAG_MODALITIES_IN_STUDY] = ""; - target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES] = "0"; - target[Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES] = "0"; + target.SetValue(Orthanc::DICOM_TAG_MODALITIES_IN_STUDY, "", false); + target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, "0", false); + target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, "0", false); } break; @@ -348,16 +330,19 @@ case Orthanc::ResourceType_Series: { - Json::Value instances; - if (OrthancPlugins::RestApiGet(instances, "/series/" + resource + "/instances", false)) + Json::Value series; + if (OrthancPlugins::RestApiGet(series, "/series/" + resource, false) && + series.type() == Json::objectValue && + series.isMember(INSTANCES) && + series[INSTANCES].type() == Json::arrayValue) { // Number of Series Related Instances - target[Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES] = - boost::lexical_cast<std::string>(instances.size()); + target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, + boost::lexical_cast<std::string>(series[INSTANCES].size()), false); } else { - target[Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES] = "0"; + target.SetValue(Orthanc::DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, "0", false); } break; @@ -369,12 +354,11 @@ } - void ExtractFields(Json::Value& result, - const Json::Value& source, + void ExtractFields(Orthanc::DicomMap& result, + const Orthanc::DicomMap& source, const std::string& wadoBase, Orthanc::ResourceType level) const { - result = Json::objectValue; std::list<Orthanc::DicomTag> fields = includeFields_; // The list of attributes for this query level @@ -409,33 +393,30 @@ for (std::list<Orthanc::DicomTag>::const_iterator it = fields.begin(); it != fields.end(); ++it) { - // Complies to the JSON produced internally by Orthanc - char tag[16]; - sprintf(tag, "%04x,%04x", it->GetGroup(), it->GetElement()); - - if (source.isMember(tag)) + std::string value; + if (source.LookupStringValue(value, *it, false /* no binary */)) { - result[tag] = source[tag]; + result.SetValue(*it, value, false); } } // Set the retrieve URL for WADO-RS - std::string url = (wadoBase + "studies/" + - GetOrthancTag(source, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, "")); + std::string url = (wadoBase + "studies/" + + source.GetStringValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, "", false)); if (level == Orthanc::ResourceType_Series || level == Orthanc::ResourceType_Instance) { - url += "/series/" + GetOrthancTag(source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, ""); + url += "/series/" + source.GetStringValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, "", false); } if (level == Orthanc::ResourceType_Instance) { - url += "/instances/" + GetOrthancTag(source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, ""); + url += "/instances/" + source.GetStringValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "", false); } static const Orthanc::DicomTag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190); - result[DICOM_TAG_RETRIEVE_URL.Format()] = url; + result.SetValue(DICOM_TAG_RETRIEVE_URL, url, false); } }; } @@ -447,6 +428,8 @@ const ModuleMatcher& matcher, Orthanc::ResourceType level) { + static const char* const INSTANCES = "Instances"; + Json::Value find; matcher.ConvertToOrthanc(find, level); @@ -468,15 +451,14 @@ ResourcesAndInstances resourcesAndInstances; std::string root = (level == Orthanc::ResourceType_Study ? "/studies/" : "/series/"); - + for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) { const std::string resource = resources[i].asString(); - if (level == Orthanc::ResourceType_Study || - level == Orthanc::ResourceType_Series) + if (level == Orthanc::ResourceType_Study) { - // Find one child instance of this resource + // Find one child instance of this study Json::Value tmp; if (OrthancPlugins::RestApiGet(tmp, root + resource + "/instances", false) && tmp.type() == Json::arrayValue && @@ -485,6 +467,20 @@ resourcesAndInstances.push_back(std::make_pair(resource, tmp[0]["ID"].asString())); } } + else if (level == Orthanc::ResourceType_Series) + { + // Find one child instance of this series + Json::Value tmp; + if (OrthancPlugins::RestApiGet(tmp, root + resource, false) && + tmp.type() == Json::objectValue && + tmp.isMember(INSTANCES) && + tmp[INSTANCES].type() == Json::arrayValue && + tmp[INSTANCES].size() > 0 && + tmp[INSTANCES][0].type() == Json::stringValue) + { + resourcesAndInstances.push_back(std::make_pair(resource, tmp[INSTANCES][0].asString())); + } + } else { resourcesAndInstances.push_back(std::make_pair(resource, resource)); @@ -501,28 +497,22 @@ it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it) { Json::Value tags; - if (OrthancPlugins::RestApiGet(tags, "/instances/" + it->second + "/tags?short", false)) + if (OrthancPlugins::RestApiGet(tags, "/instances/" + it->second + "/tags", false)) { - std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl( - wadoBase, - GetOrthancTag(tags, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, ""), - GetOrthancTag(tags, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, ""), - GetOrthancTag(tags, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "")); - - Json::Value result; - matcher.ExtractFields(result, tags, wadoBase, level); + Orthanc::DicomMap source; + source.FromDicomAsJson(tags); - // Inject the derived tags - ModuleMatcher::Filters derivedTags; - matcher.ComputeDerivedTags(derivedTags, level, it->first); + std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl( + wadoBase, + source.GetStringValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, "", false), + source.GetStringValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, "", false), + source.GetStringValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "", false)); - for (ModuleMatcher::Filters::const_iterator - tag = derivedTags.begin(); tag != derivedTags.end(); ++tag) - { - result[tag->first.Format()] = tag->second; - } + Orthanc::DicomMap target; + matcher.ExtractFields(target, source, wadoBase, level); + matcher.InjectDerivedTags(target, level, it->first); - writer.AddOrthancJson(result); + writer.AddOrthancMap(target); } }