changeset 77:3c71e14e3f10 refactoring

start refactoring of QIDO-RS
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 01 Dec 2015 17:53:19 +0100
parents 73b9a82670e3
children e8e07471104c
files Plugin/Configuration.cpp Plugin/Configuration.h Plugin/QidoRs.cpp
diffstat 3 files changed, 142 insertions(+), 415 deletions(-) [+]
line wrap: on
line diff
--- a/Plugin/Configuration.cpp	Thu Nov 12 10:16:19 2015 +0100
+++ b/Plugin/Configuration.cpp	Tue Dec 01 17:53:19 2015 +0100
@@ -133,6 +133,7 @@
   {
     OrthancPluginMemoryBuffer buffer;
     int code;
+
     if (applyPlugins)
     {
       code = OrthancPluginRestApiGetAfterPlugins(context, &buffer, uri.c_str());
@@ -174,10 +175,62 @@
 
   bool RestApiGetJson(Json::Value& result,
                       OrthancPluginContext* context,
-                      const std::string& uri)
+                      const std::string& uri,
+                      bool applyPlugins)
   {
     std::string content;
-    RestApiGetString(content, context, uri);
+    RestApiGetString(content, context, uri, applyPlugins);
+    
+    Json::Reader reader;
+    return reader.parse(content, result);
+  }
+
+
+  bool RestApiPostString(std::string& result,
+                         OrthancPluginContext* context,
+                         const std::string& uri,
+                         const std::string& body)
+  {
+    OrthancPluginMemoryBuffer buffer;
+    int code = OrthancPluginRestApiPost(context, &buffer, uri.c_str(), body.c_str(), body.size());
+
+    if (code)
+    {
+      // Error
+      return false;
+    }
+
+    bool ok = true;
+
+    try
+    {
+      if (buffer.size)
+      {
+        result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size);
+      }
+      else
+      {
+        result.clear();
+      }
+    }
+    catch (std::bad_alloc&)
+    {
+      ok = false;
+    }
+
+    OrthancPluginFreeMemoryBuffer(context, &buffer);
+
+    return ok;
+  }
+
+
+  bool RestApiPostJson(Json::Value& result,
+                      OrthancPluginContext* context,
+                      const std::string& uri,
+                      const std::string& body)
+  {
+    std::string content;
+    RestApiPostString(content, context, uri, body);
     
     Json::Reader reader;
     return reader.parse(content, result);
--- a/Plugin/Configuration.h	Thu Nov 12 10:16:19 2015 +0100
+++ b/Plugin/Configuration.h	Tue Dec 01 17:53:19 2015 +0100
@@ -65,7 +65,18 @@
 
   bool RestApiGetJson(Json::Value& result,
                       OrthancPluginContext* context,
-                      const std::string& uri);
+                      const std::string& uri,
+                      bool applyPlugins = false);
+
+  bool RestApiPostString(std::string& result,
+                         OrthancPluginContext* context,
+                         const std::string& uri,
+                         const std::string& body);
+
+  bool RestApiPostJson(Json::Value& result,
+                       OrthancPluginContext* context,
+                       const std::string& uri,
+                       const std::string& body);
 
   namespace Configuration
   {
--- a/Plugin/QidoRs.cpp	Thu Nov 12 10:16:19 2015 +0100
+++ b/Plugin/QidoRs.cpp	Tue Dec 01 17:53:19 2015 +0100
@@ -87,9 +87,24 @@
     }
 
 
+    static std::string Format(const gdcm::Tag& tag)
+    {
+      char b[16];
+      sprintf(b, "%04x,%04x", tag.GetGroup(), tag.GetElement());
+      return std::string(b);
+    }
+
+
     gdcm::Tag  ParseTag(const std::string& key) const
     {
-      if (key.size() == 8 &&
+      if (key.find('.') != std::string::npos)
+      {
+        std::string s = "This DICOMweb plugin does not support hierarchical queries: " + key;
+        OrthancPluginLogError(context_, s.c_str());
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+
+     if (key.size() == 8 &&
           isxdigit(key[0]) &&
           isxdigit(key[1]) &&
           isxdigit(key[2]) &&
@@ -128,129 +143,6 @@
     }
 
 
-    static bool IsWildcard(const std::string& constraint)
-    {
-      return (constraint.find('-') != std::string::npos ||
-              constraint.find('*') != std::string::npos ||
-              constraint.find('\\') != std::string::npos ||
-              constraint.find('?') != std::string::npos);
-    }
-
-    static bool ApplyRangeConstraint(const std::string& value,
-                                     const std::string& constraint)
-    {
-      size_t separator = constraint.find('-');
-      std::string lower(constraint.substr(0, separator));
-      std::string upper(constraint.substr(separator + 1));
-      std::string v(value);
-
-      Orthanc::Toolbox::ToLowerCase(lower);
-      Orthanc::Toolbox::ToLowerCase(upper);
-      Orthanc::Toolbox::ToLowerCase(v);
-
-      if (lower.size() == 0 && upper.size() == 0)
-      {
-        return false;
-      }
-
-      if (lower.size() == 0)
-      {
-        return v <= upper;
-      }
-
-      if (upper.size() == 0)
-      {
-        return v >= lower;
-      }
-    
-      return (v >= lower && v <= upper);
-    }
-
-
-    static bool ApplyListConstraint(const std::string& value,
-                                    const std::string& constraint)
-    {
-      std::string v1(value);
-      Orthanc::Toolbox::ToLowerCase(v1);
-
-      std::vector<std::string> items;
-      Orthanc::Toolbox::TokenizeString(items, constraint, '\\');
-
-      for (size_t i = 0; i < items.size(); i++)
-      {
-        std::string lower(items[i]);
-        Orthanc::Toolbox::ToLowerCase(lower);
-        if (lower == v1)
-        {
-          return true;
-        }
-      }
-
-      return false;
-    }
-
-
-    static std::string WildcardToRegularExpression(const std::string& source)
-    {
-      std::string result = source;
-
-      // Escape all special characters
-      boost::replace_all(result, "\\", "\\\\");
-      boost::replace_all(result, "^", "\\^");
-      boost::replace_all(result, ".", "\\.");
-      boost::replace_all(result, "$", "\\$");
-      boost::replace_all(result, "|", "\\|");
-      boost::replace_all(result, "(", "\\(");
-      boost::replace_all(result, ")", "\\)");
-      boost::replace_all(result, "[", "\\[");
-      boost::replace_all(result, "]", "\\]");
-      boost::replace_all(result, "+", "\\+");
-      boost::replace_all(result, "/", "\\/");
-      boost::replace_all(result, "{", "\\{");
-      boost::replace_all(result, "}", "\\}");
-
-      // Convert wildcards '*' and '?' to their regex equivalents
-      boost::replace_all(result, "?", ".");
-      boost::replace_all(result, "*", ".*");
-
-      return result;
-    }
-
-
-    static bool Matches(const std::string& value,
-                        const std::string& constraint)
-    {
-      // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
-      // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html  
-
-      if (constraint.find('-') != std::string::npos)
-      {
-        return ApplyRangeConstraint(value, constraint);
-      }
-    
-      if (constraint.find('\\') != std::string::npos)
-      {
-        return ApplyListConstraint(value, constraint);
-      }
-
-      if (constraint.find('*') != std::string::npos ||
-          constraint.find('?') != std::string::npos)
-      {
-        boost::regex pattern(WildcardToRegularExpression(constraint),
-                             boost::regex::icase /* case insensitive search */);
-        return boost::regex_match(value, pattern);
-      }
-      else
-      {
-        std::string v(value), c(constraint);
-        Orthanc::Toolbox::ToLowerCase(v);
-        Orthanc::Toolbox::ToLowerCase(c);
-        return v == c;
-      }
-    }
-
-
-
     static void AddResultAttributesForLevel(std::list<gdcm::Tag>& result,
                                             QueryLevel level)
     {
@@ -392,43 +284,50 @@
       filters_[tag] = constraint;
     }
 
-    bool LookupExactFilter(std::string& constraint,
-                           const gdcm::Tag& tag) const
+    void Print(std::ostream& out) const 
     {
-      Filters::const_iterator it = filters_.find(tag);
-      if (it != filters_.end() &&
-          !IsWildcard(it->second))
+      for (Filters::const_iterator it = filters_.begin(); 
+           it != filters_.end(); ++it)
       {
-        constraint = it->second;
-        return true;
-      }
-      else
-      {
-        return false;
+        printf("Filter [%04x,%04x] = [%s]\n", it->first.GetGroup(), it->first.GetElement(), it->second.c_str());
       }
     }
 
-    bool Matches(const OrthancPlugins::ParsedDicomFile& dicom) const
+    void ConvertToOrthanc(Json::Value& result,
+                          QueryLevel level) const
     {
-      for (Filters::const_iterator it = filters_.begin();
+      result = Json::objectValue;
+
+      switch (level)
+      {
+        case QueryLevel_Study:
+          result["Level"] = "Study";
+          break;
+
+        case QueryLevel_Series:
+          result["Level"] = "Series";
+          break;
+
+        case QueryLevel_Instance:
+          result["Level"] = "Instance";
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      result["Expand"] = false;
+      result["CaseSensitive"] = true;
+      result["Query"] = Json::objectValue;
+
+      for (Filters::const_iterator it = filters_.begin(); 
            it != filters_.end(); ++it)
       {
-        std::string value;
-        if (!dicom.GetTag(value, it->first, true))
-        {
-          return false;
-        }
-
-        if (!Matches(value, it->second))
-        {
-          return false;
-        }
+        result["Query"][Format(it->first)] = it->second;
       }
-
-      return true;
     }
 
-
+#if 0
     void ExtractFields(gdcm::DataSet& result,
                        const OrthancPlugins::ParsedDicomFile& dicom,
                        const std::string& wadoBase,
@@ -493,248 +392,7 @@
       element.SetByteValue(url.c_str(), url.size());
       result.Replace(element);
     }
-  };
-
-
-
-
-  class CandidateResources
-  {
-  private:
-    typedef std::set<std::string>  Resources;
-
-    bool        all_;
-    QueryLevel  level_;
-    Resources   resources_;
-
-    static bool CallLookup(std::string& orthancId,
-                           const std::string& dicomId,
-                           char* (lookup) (OrthancPluginContext*, const char*))
-    {
-      bool result = false;
-
-      char* tmp = lookup(context_, dicomId.c_str());
-      if (tmp != NULL)
-      {
-        orthancId = tmp;
-        result = true;
-      }
-
-      OrthancPluginFreeString(context_, tmp);
-
-      return result;
-    }
-
-
-    void FilterByIdentifierInternal(const ModuleMatcher& matcher,
-                                    const gdcm::Tag& tag,
-                                    char* (lookup) (OrthancPluginContext*, const char*))
-    {
-      std::string orthancId, dicomId;
-
-      if (!matcher.LookupExactFilter(dicomId, tag))
-      {
-        // There is no restriction at this level
-        return;
-      }
-
-      if (CallLookup(orthancId, dicomId, lookup) &&
-          (all_ || resources_.find(orthancId) != resources_.end()))
-      {
-        // There remains a single candidate resource
-        resources_.clear();
-        resources_.insert(orthancId);
-      }
-      else
-      {
-        // No matching resource remains
-        resources_.clear();            
-      }
-
-      all_ = false;
-    }
-
-
-    bool PickOneInstance(std::string& instance,
-                         const std::string& resource) const
-    {
-      if (level_ == QueryLevel_Instance)
-      {
-        instance = resource;
-        return true;
-      }
-
-      std::string uri;
-      if (level_ == QueryLevel_Study)
-      {
-        uri = "/studies/" + resource + "/instances";
-      }
-      else
-      {
-        assert(level_ == QueryLevel_Series);
-        uri = "/series/" + resource + "/instances";
-      }
-
-      Json::Value instances;
-      if (!OrthancPlugins::RestApiGetJson(instances, context_, uri) ||
-          instances.type() != Json::arrayValue ||
-          instances.size() == 0)
-      {
-        return false;
-      }
-      else
-      {
-        instance = instances[0]["ID"].asString();
-        return true;
-      }
-    }
-
-
-  public:
-    CandidateResources() : all_(true), level_(QueryLevel_Study)
-    {
-    }
-
-    void GoDown()
-    {
-      std::string baseUri;
-      std::string nextLevel;
-      switch (level_)
-      {
-        case QueryLevel_Study:
-          baseUri = "/studies/";
-          nextLevel = "Series";
-          break;
-
-        case QueryLevel_Series:
-          baseUri = "/series/";
-          nextLevel = "Instances";
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-
-
-      if (!all_)
-      {
-        Resources  children;
-      
-        for (Resources::const_iterator it = resources_.begin();
-             it != resources_.end(); it++)
-        {
-          Json::Value tmp;
-          if (OrthancPlugins::RestApiGetJson(tmp, context_, baseUri + *it) &&
-              tmp.type() == Json::objectValue &&
-              tmp.isMember(nextLevel) &&
-              tmp[nextLevel].type() == Json::arrayValue)
-          {
-            for (Json::Value::ArrayIndex i = 0; i < tmp[nextLevel].size(); i++)
-            {
-              children.insert(tmp[nextLevel][i].asString());
-            }
-          }
-        }
-
-        resources_ = children;
-      }
-
-
-      switch (level_)
-      {
-        case QueryLevel_Study:
-          level_ = QueryLevel_Series;
-          break;
-
-        case QueryLevel_Series:
-          level_ = QueryLevel_Instance;
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-
-    void FilterByIdentifier(const ModuleMatcher& matcher)
-    {
-      switch (level_)
-      {
-        case QueryLevel_Study:
-          FilterByIdentifierInternal(matcher, OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID,
-                                     OrthancPluginLookupStudy);
-          FilterByIdentifierInternal(matcher, OrthancPlugins::DICOM_TAG_ACCESSION_NUMBER,
-                                     OrthancPluginLookupStudyWithAccessionNumber);
-          break;
-
-        case QueryLevel_Series:
-          FilterByIdentifierInternal(matcher, OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID,
-                                     OrthancPluginLookupSeries);
-          break;
-
-        case QueryLevel_Instance:
-          FilterByIdentifierInternal(matcher, OrthancPlugins::DICOM_TAG_SOP_INSTANCE_UID,
-                                     OrthancPluginLookupInstance);
-          break;
-
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-
-
-    void Flatten(std::list<std::string>& result) const
-    {
-      std::string instance;
-
-      result.clear();
-
-      if (all_)
-      {
-        std::string uri;
-        switch (level_)
-        {
-          case QueryLevel_Study:
-            uri = "/studies/";
-            break;
-
-          case QueryLevel_Series:
-            uri = "/series/";
-            break;
-
-          case QueryLevel_Instance:
-            uri = "/instances/";
-            break;
-
-          default:
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        Json::Value tmp;
-        if (OrthancPlugins::RestApiGetJson(tmp, context_, uri) &&
-            tmp.type() == Json::arrayValue)
-        {
-          for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++)
-          {
-            if (PickOneInstance(instance, tmp[i].asString()))
-            {
-              result.push_back(instance);
-            }
-          }
-        }
-      }
-      else
-      {
-        for (Resources::const_iterator 
-               it = resources_.begin(); it != resources_.end(); it++)
-        {
-          if (PickOneInstance(instance, *it))
-          {
-            result.push_back(instance);
-          }
-        }
-      }
-    }
+#endif
   };
 }
 
@@ -744,9 +402,29 @@
 static void ApplyMatcher(OrthancPluginRestOutput* output,
                          const OrthancPluginHttpRequest* request,
                          const ModuleMatcher& matcher,
-                         const CandidateResources& candidates,
                          QueryLevel level)
 {
+  matcher.Print(std::cout);
+
+  Json::Value find;
+  matcher.ConvertToOrthanc(find, level);
+  std::cout << find.toStyledString();
+
+  Json::FastWriter writer;
+  std::string body = writer.write(find);
+  
+  Json::Value tmp;
+  if (OrthancPlugins::RestApiPostJson(tmp, context_, "/tools/find", body))
+  {
+    std::cout << tmp.toStyledString();
+  }
+  else
+  {
+    printf("Nope\n");
+  }
+
+
+#if 0
   std::list<std::string> resources;
   candidates.Flatten(resources);
 
@@ -771,6 +449,7 @@
   }
 
   results.Answer();
+#endif
 }
 
 
@@ -788,11 +467,7 @@
     }
 
     ModuleMatcher matcher(request);
-
-    CandidateResources candidates;
-    candidates.FilterByIdentifier(matcher);
-
-    ApplyMatcher(output, request, matcher, candidates, QueryLevel_Study);
+    ApplyMatcher(output, request, matcher, QueryLevel_Study);
 
     return REST_RETURN_SUCCESS;
   }
@@ -834,12 +509,7 @@
       matcher.AddFilter(OrthancPlugins::DICOM_TAG_STUDY_INSTANCE_UID, request->groups[0]);
     }
 
-    CandidateResources candidates;
-    candidates.FilterByIdentifier(matcher);
-    candidates.GoDown();
-    candidates.FilterByIdentifier(matcher);
-
-    ApplyMatcher(output, request, matcher, candidates, QueryLevel_Series);
+    ApplyMatcher(output, request, matcher, QueryLevel_Series);
 
     return REST_RETURN_SUCCESS;
   }
@@ -887,14 +557,7 @@
       matcher.AddFilter(OrthancPlugins::DICOM_TAG_SERIES_INSTANCE_UID, request->groups[1]);
     }
 
-    CandidateResources candidates;
-    candidates.FilterByIdentifier(matcher);
-    candidates.GoDown();
-    candidates.FilterByIdentifier(matcher);
-    candidates.GoDown();
-    candidates.FilterByIdentifier(matcher);
-
-    ApplyMatcher(output, request, matcher, candidates, QueryLevel_Instance);
+    ApplyMatcher(output, request, matcher, QueryLevel_Instance);
 
     return REST_RETURN_SUCCESS;
   }