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);
     }
   }