Mercurial > hg > orthanc-dicomweb
changeset 112:528d18573c09
Fix issue #14 (Aggregate fields empty for QIDO-RS study/series-level queries)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 28 Apr 2016 17:20:36 +0200 |
parents | 2be81c3f1b25 |
children | 04fbfd59a60e 481a88323f8d |
files | NEWS Plugin/Dicom.cpp Plugin/QidoRs.cpp |
diffstat | 3 files changed, 148 insertions(+), 29 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Thu Apr 28 15:13:07 2016 +0200 +++ b/NEWS Thu Apr 28 17:20:36 2016 +0200 @@ -3,6 +3,7 @@ * Fix issue #13 (QIDO-RS study-level query is slow) +* Fix issue #14 (Aggregate fields empty for QIDO-RS study/series-level queries) Version 0.2 (2015/12/10)
--- a/Plugin/Dicom.cpp Thu Apr 28 15:13:07 2016 +0200 +++ b/Plugin/Dicom.cpp Thu Apr 28 17:20:36 2016 +0200 @@ -477,6 +477,10 @@ { value.append_child(pugi::node_pcdata).set_value(tmp.c_str()); } + else + { + value.append_child(pugi::node_pcdata).set_value(""); + } } } } @@ -578,8 +582,13 @@ if (ConvertDicomStringToUtf8(value, dictionary, file, *it, sourceEncoding)) { node["Value"].append(value.c_str()); - ok = true; } + else + { + node["Value"].append(""); + } + + ok = true; } if (ok)
--- a/Plugin/QidoRs.cpp Thu Apr 28 15:13:07 2016 +0200 +++ b/Plugin/QidoRs.cpp Thu Apr 28 17:20:36 2016 +0200 @@ -42,7 +42,7 @@ namespace { - static std::string Format(const gdcm::Tag& tag) + static std::string FormatOrthancTag(const gdcm::Tag& tag) { char b[16]; sprintf(b, "%04x,%04x", tag.GetGroup(), tag.GetElement()); @@ -54,7 +54,7 @@ const gdcm::Tag& tag, const std::string& defaultValue) { - std::string s = Format(tag); + std::string s = FormatOrthancTag(tag); if (source.isMember(s) && source[s].type() == Json::objectValue && @@ -82,10 +82,10 @@ class ModuleMatcher { - private: + public: typedef std::map<gdcm::Tag, std::string> Filters; - const gdcm::Dict& dictionary_; + private: bool fuzzy_; unsigned int offset_; unsigned int limit_; @@ -94,7 +94,6 @@ Filters filters_; - static void AddResultAttributesForLevel(std::list<gdcm::Tag>& result, QueryLevel level) { @@ -107,7 +106,7 @@ result.push_back(gdcm::Tag(0x0008, 0x0030)); // Study Time result.push_back(gdcm::Tag(0x0008, 0x0050)); // Accession Number result.push_back(gdcm::Tag(0x0008, 0x0056)); // Instance Availability - result.push_back(gdcm::Tag(0x0008, 0x0061)); // Modalities in Study + //result.push_back(gdcm::Tag(0x0008, 0x0061)); // Modalities in Study => SPECIAL CASE result.push_back(gdcm::Tag(0x0008, 0x0090)); // Referring Physician's Name result.push_back(gdcm::Tag(0x0008, 0x0201)); // Timezone Offset From UTC //result.push_back(gdcm::Tag(0x0008, 0x1190)); // Retrieve URL => SPECIAL CASE @@ -117,8 +116,8 @@ result.push_back(gdcm::Tag(0x0010, 0x0040)); // Patient's Sex result.push_back(gdcm::Tag(0x0020, 0x000D)); // Study Instance UID result.push_back(gdcm::Tag(0x0020, 0x0010)); // Study ID - result.push_back(gdcm::Tag(0x0020, 0x1206)); // Number of Study Related Series - result.push_back(gdcm::Tag(0x0020, 0x1208)); // Number of Study Related Instances + //result.push_back(gdcm::Tag(0x0020, 0x1206)); // Number of Study Related Series => SPECIAL CASE + //result.push_back(gdcm::Tag(0x0020, 0x1208)); // Number of Study Related Instances => SPECIAL CASE break; case QueryLevel_Series: @@ -130,7 +129,7 @@ //result.push_back(gdcm::Tag(0x0008, 0x1190)); // Retrieve URL => SPECIAL CASE result.push_back(gdcm::Tag(0x0020, 0x000E)); // Series Instance UID result.push_back(gdcm::Tag(0x0020, 0x0011)); // Series Number - result.push_back(gdcm::Tag(0x0020, 0x1209)); // Number of Series Related Instances + //result.push_back(gdcm::Tag(0x0020, 0x1209)); // Number of Series Related Instances => SPECIAL CASE result.push_back(gdcm::Tag(0x0040, 0x0244)); // Performed Procedure Step Start Date result.push_back(gdcm::Tag(0x0040, 0x0245)); // Performed Procedure Step Start Time result.push_back(gdcm::Tag(0x0040, 0x0275)); // Request Attribute Sequence @@ -157,10 +156,8 @@ } - public: ModuleMatcher(const OrthancPluginHttpRequest* request) : - dictionary_(gdcm::Global::GetInstance().GetDicts().GetPublicDict()), fuzzy_(false), offset_(0), limit_(0), @@ -209,13 +206,13 @@ Orthanc::Toolbox::TokenizeString(tags, value, ','); for (size_t i = 0; i < tags.size(); i++) { - includeFields_.push_back(OrthancPlugins::ParseTag(dictionary_, tags[i])); + includeFields_.push_back(OrthancPlugins::ParseTag(*dictionary_, tags[i])); } } } else { - filters_[OrthancPlugins::ParseTag(dictionary_, key)] = value; + filters_[OrthancPlugins::ParseTag(*dictionary_, key)] = value; } } } @@ -275,11 +272,88 @@ for (Filters::const_iterator it = filters_.begin(); it != filters_.end(); ++it) { - result["Query"][Format(it->first)] = it->second; + result["Query"][FormatOrthancTag(it->first)] = it->second; } } + void ComputeDerivedTags(Filters& target, + QueryLevel level, + const std::string& resource) const + { + target.clear(); + + switch (level) + { + case QueryLevel_Study: + { + Json::Value series, instances; + if (OrthancPlugins::RestApiGetJson(series, context_, "/studies/" + resource + "/series?expand") && + OrthancPlugins::RestApiGetJson(instances, context_, "/studies/" + resource + "/instances")) + { + // Number of Study Related Series + target[gdcm::Tag(0x0020, 0x1206)] = boost::lexical_cast<std::string>(series.size()); + + // Number of Study Related Instances + target[gdcm::Tag(0x0020, 0x1208)] = boost::lexical_cast<std::string>(instances.size()); + + // Collect the Modality of all the child series + std::set<std::string> modalities; + for (Json::Value::ArrayIndex i = 0; i < series.size(); i++) + { + if (series[i].isMember("MainDicomTags") && + series[i]["MainDicomTags"].isMember("Modality")) + { + modalities.insert(series[i]["MainDicomTags"]["Modality"].asString()); + } + } + + std::string s; + for (std::set<std::string>::const_iterator + it = modalities.begin(); it != modalities.end(); ++it) + { + if (!s.empty()) + { + s += "\\"; + } + + s += *it; + } + + target[gdcm::Tag(0x0008, 0x0061)] = s; // Modalities in Study + } + else + { + target[gdcm::Tag(0x0008, 0x0061)] = ""; // Modalities in Study + target[gdcm::Tag(0x0020, 0x1206)] = "0"; // Number of Study Related Series + target[gdcm::Tag(0x0020, 0x1208)] = "0"; // Number of Study Related Instances + } + + break; + } + + case QueryLevel_Series: + { + Json::Value instances; + if (OrthancPlugins::RestApiGetJson(instances, context_, "/series/" + resource + "/instances")) + { + // Number of Series Related Instances + target[gdcm::Tag(0x0020, 0x1209)] = boost::lexical_cast<std::string>(instances.size()); + } + else + { + target[gdcm::Tag(0x0020, 0x1209)] = "0"; // Number of Series Related Instances + } + + break; + } + + default: + break; + } + } + + void ExtractFields(gdcm::DataSet& result, const OrthancPlugins::ParsedDicomFile& dicom, const std::string& wadoBase, @@ -317,7 +391,7 @@ // Copy all the required fields to the target for (std::list<gdcm::Tag>::const_iterator - it = fields.begin(); it != fields.end(); it++) + it = fields.begin(); it != fields.end(); ++it) { if (dicom.GetDataSet().FindDataElement(*it)) { @@ -384,9 +458,9 @@ // Copy all the required fields to the target for (std::list<gdcm::Tag>::const_iterator - it = fields.begin(); it != fields.end(); it++) + it = fields.begin(); it != fields.end(); ++it) { - std::string tag = Format(*it); + std::string tag = FormatOrthancTag(*it); if (source.isMember(tag)) { result[tag] = source[tag]; @@ -412,7 +486,7 @@ tmp["Type"] = "String"; tmp["Value"] = url; - result[Format(OrthancPlugins::DICOM_TAG_RETRIEVE_URL)] = tmp; + result[FormatOrthancTag(OrthancPlugins::DICOM_TAG_RETRIEVE_URL)] = tmp; } }; } @@ -437,26 +511,30 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } - std::list<std::string> instances; + typedef std::list< std::pair<std::string, std::string> > ResourcesAndInstances; + + ResourcesAndInstances resourcesAndInstances; std::string root = (level == QueryLevel_Study ? "/studies/" : "/series/"); for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) { + const std::string resource = resources[i].asString(); + if (level == QueryLevel_Study || level == QueryLevel_Series) { // Find one child instance of this resource Json::Value tmp; - if (OrthancPlugins::RestApiGetJson(tmp, context_, root + resources[i].asString() + "/instances") && + if (OrthancPlugins::RestApiGetJson(tmp, context_, root + resource + "/instances") && tmp.type() == Json::arrayValue && tmp.size() > 0) { - instances.push_back(tmp[0]["ID"].asString()); + resourcesAndInstances.push_back(std::make_pair(resource, tmp[0]["ID"].asString())); } } else { - instances.push_back(resources[i].asString()); + resourcesAndInstances.push_back(std::make_pair(resource, resource)); } } @@ -469,27 +547,43 @@ // downloaded and decoded using GDCM, which slows down things // wrt. the new implementation below that directly uses the Orthanc // pre-computed JSON summary. - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); it++) + for (ResourcesAndInstances::const_iterator + it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it) { + ModuleMatcher::Filters derivedTags; + matcher.ComputeDerivedTags(derivedTags, level, it->first); + std::string file; - if (OrthancPlugins::RestApiGetString(file, context_, "/instances/" + *it + "/file")) + if (OrthancPlugins::RestApiGetString(file, context_, "/instances/" + it->second + "/file")) { OrthancPlugins::ParsedDicomFile dicom(file); std::auto_ptr<gdcm::DataSet> result(new gdcm::DataSet); matcher.ExtractFields(*result, dicom, wadoBase, level); + + // Inject the derived tags + ModuleMatcher::Filters derivedTags; + matcher.ComputeDerivedTags(derivedTags, level, it->first); + + for (ModuleMatcher::Filters::const_iterator + tag = derivedTags.begin(); tag != derivedTags.end(); ++tag) + { + gdcm::DataElement element(tag->first); + element.SetByteValue(tag->second.c_str(), tag->second.size()); + result->Replace(element); + } + results.Add(dicom.GetFile(), *result); } } #else // Fix of issue #13 - for (std::list<std::string>::const_iterator - it = instances.begin(); it != instances.end(); it++) + for (ResourcesAndInstances::const_iterator + it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it) { Json::Value tags; - if (OrthancPlugins::RestApiGetJson(tags, context_, "/instances/" + *it + "/tags")) + if (OrthancPlugins::RestApiGetJson(tags, context_, "/instances/" + it->second + "/tags")) { std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl( wadoBase, @@ -499,6 +593,21 @@ Json::Value result; matcher.ExtractFields(result, tags, wadoBase, level); + + // Inject the derived tags + ModuleMatcher::Filters derivedTags; + matcher.ComputeDerivedTags(derivedTags, level, it->first); + + for (ModuleMatcher::Filters::const_iterator + tag = derivedTags.begin(); tag != derivedTags.end(); ++tag) + { + Json::Value tmp = Json::objectValue; + tmp["Name"] = OrthancPlugins::GetKeyword(*dictionary_, tag->first); + tmp["Type"] = "String"; + tmp["Value"] = tag->second; + result[FormatOrthancTag(tag->first)] = tmp; + } + results.AddFromOrthanc(result, wadoUrl); } }