changeset 1354:3dd494f201a1

ResourceFinder
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 13 May 2015 12:17:35 +0200
parents d7da97e21161
children 28563d910039
files OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ResourceFinder.cpp OrthancServer/ResourceFinder.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 6 files changed, 330 insertions(+), 227 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Tue May 12 18:27:14 2015 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Wed May 13 12:17:35 2015 +0200
@@ -432,12 +432,7 @@
         }
         else
         {
-          Json::Value tmp;
-          index_.GetAllUuids(tmp, level_);
-          for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++)
-          {
-            resources.push_back(tmp[i].asString());
-          }
+          index_.GetAllUuids(resources, level_);
         }
       }
 
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Tue May 12 18:27:14 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed May 13 12:17:35 2015 +0200
@@ -35,6 +35,7 @@
 
 #include "../ServerToolbox.h"
 #include "../FromDcmtkBridge.h"
+#include "../ResourceFinder.h"
 
 #include <glog/logging.h>
 
@@ -42,32 +43,44 @@
 {
   // List all the patients, studies, series or instances ----------------------
  
+  static void AnswerListOfResources(RestApiOutput& output,
+                                    ServerIndex& index,
+                                    const std::list<std::string>& resources,
+                                    ResourceType level,
+                                    bool expand)
+  {
+    Json::Value answer = Json::arrayValue;
+
+    for (std::list<std::string>::const_iterator
+           resource = resources.begin(); resource != resources.end(); resource++)
+    {
+      if (expand)
+      {
+        Json::Value item;
+        if (index.LookupResource(item, *resource, level))
+        {
+          answer.append(item);
+        }
+      }
+      else
+      {
+        answer.append(*resource);
+      }
+    }
+
+    output.AnswerJson(answer);
+  }
+
+
   template <enum ResourceType resourceType>
   static void ListResources(RestApiGetCall& call)
   {
     ServerIndex& index = OrthancRestApi::GetIndex(call);
 
-    Json::Value result;
+    std::list<std::string> result;
     index.GetAllUuids(result, resourceType);
 
-    if (call.HasArgument("expand"))
-    {
-      Json::Value expanded = Json::arrayValue;
-      for (Json::Value::ArrayIndex i = 0; i < result.size(); i++)
-      {
-        Json::Value item;
-        if (index.LookupResource(item, result[i].asString(), resourceType))
-        {
-          expanded.append(item);
-        }
-      }
-
-      call.GetOutput().AnswerJson(expanded);
-    }
-    else
-    {
-      call.GetOutput().AnswerJson(result);
-    }
+    AnswerListOfResources(call.GetOutput(), index, result, resourceType, call.HasArgument("expand"));
   }
 
   template <enum ResourceType resourceType>
@@ -835,6 +848,56 @@
   }
 
 
+  static void Find(RestApiPostCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+
+    Json::Value request;
+    if (call.ParseJsonRequest(request) &&
+        request.type() == Json::objectValue &&
+        request.isMember("Level") &&
+        request.isMember("Query") &&
+        request["Level"].type() == Json::stringValue &&
+        request["Query"].type() == Json::objectValue)
+    {
+      std::string level = request["Level"].asString();
+
+      ResourceFinder finder(index);
+      finder.SetLevel(StringToResourceType(level.c_str()));
+
+      if (request.isMember("CaseSensitive"))
+      {
+        finder.SetCaseSensitive(request["CaseSensitive"].asBool());
+      }
+
+      bool expand = false;
+      if (request.isMember("Expand"))
+      {
+        expand = request["Expand"].asBool();
+      }
+
+      Json::Value::Members members = request["Query"].getMemberNames();
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        if (request["Query"][members[i]].type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+
+        finder.AddTag(members[i], request["Query"][members[i]].asString());
+      }
+
+      std::list<std::string> resources;
+      finder.Apply(resources);
+      AnswerListOfResources(call.GetOutput(), index, resources, finder.GetLevel(), expand);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
   template <enum ResourceType start, 
             enum ResourceType end>
   static void GetChildResources(RestApiGetCall& call)
@@ -1042,6 +1105,7 @@
     Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment);
 
     Register("/tools/lookup", Lookup);
+    Register("/tools/find", Find);
 
     Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>);
     Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>);
--- a/OrthancServer/ResourceFinder.cpp	Tue May 12 18:27:14 2015 +0200
+++ b/OrthancServer/ResourceFinder.cpp	Wed May 13 12:17:35 2015 +0200
@@ -55,243 +55,236 @@
   }
 
 
-  namespace
+  class ResourceFinder::CandidateResources
   {
-    class CandidateResources
-    {
-    private:
-      typedef std::map<DicomTag, std::string>  Query;
+  private:
+    typedef std::map<DicomTag, std::string>  Query;
 
-      ServerIndex&  index_;
-      ResourceType  level_;
-      bool  isFilterApplied_;
-      std::set<std::string>  filtered_;
+    ServerIndex&  index_;
+    ResourceType  level_;
+    bool  isFilterApplied_;
+    std::set<std::string>  filtered_;
 
      
-      static void ListToSet(std::set<std::string>& target,
-                            const std::list<std::string>& source)
+    static void ListToSet(std::set<std::string>& target,
+                          const std::list<std::string>& source)
+    {
+      for (std::list<std::string>::const_iterator
+             it = source.begin(); it != source.end(); ++it)
       {
-        for (std::list<std::string>::const_iterator
-               it = source.begin(); it != source.end(); ++it)
-        {
-          target.insert(*it);
-        }
+        target.insert(*it);
       }
+    }
 
 
-      void RestrictIdentifier(const DicomTag& tag, 
-                              const std::string& value)
-      {
-        assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) ||
-               (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) ||
-               (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) ||
-               (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
-               (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
+    void RestrictIdentifier(const DicomTag& tag, 
+                            const std::string& value)
+    {
+      assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) ||
+             (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) ||
+             (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) ||
+             (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
+             (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
 
-        LOG(INFO) << "Lookup for identifier tag "
-                  << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")";
+      LOG(INFO) << "Lookup for identifier tag "
+                << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")";
 
-        std::list<std::string> resources;
-        index_.LookupIdentifier(resources, tag, value, level_);
+      std::list<std::string> resources;
+      index_.LookupIdentifier(resources, tag, value, level_);
 
-        if (isFilterApplied_)
-        {
-          std::set<std::string>  s;
-          ListToSet(s, resources);
-
-          std::set<std::string> tmp = filtered_;
-          filtered_.clear();
+      if (isFilterApplied_)
+      {
+        std::set<std::string>  s;
+        ListToSet(s, resources);
 
-          for (std::set<std::string>::const_iterator 
-                 it = tmp.begin(); it != tmp.end(); ++it)
+        std::set<std::string> tmp = filtered_;
+        filtered_.clear();
+
+        for (std::set<std::string>::const_iterator 
+               it = tmp.begin(); it != tmp.end(); ++it)
+        {
+          if (s.find(*it) != s.end())
           {
-            if (s.find(*it) != s.end())
-            {
-              filtered_.insert(*it);
-            }
+            filtered_.insert(*it);
           }
         }
-        else
-        {
-          assert(filtered_.empty());
-          isFilterApplied_ = true;
-          ListToSet(filtered_, resources);
-        }
+      }
+      else
+      {
+        assert(filtered_.empty());
+        isFilterApplied_ = true;
+        ListToSet(filtered_, resources);
       }
+    }
+
+
+    void RestrictIdentifier(Query& query,
+                            const DicomTag& tag)
+    {
+      Query::iterator it = query.find(tag);
+      if (it != query.end())
+      {
+        RestrictIdentifier(it->first, it->second);
+        query.erase(it);
+      }
+    }
 
 
-      void RestrictIdentifier(const Query& query,
-                              const DicomTag& tag)
+  public:
+    CandidateResources(ServerIndex& index) : 
+      index_(index), 
+      level_(ResourceType_Patient), 
+      isFilterApplied_(false)
+    {
+    }
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    void GoDown()
+    {
+      assert(level_ != ResourceType_Instance);
+
+      if (isFilterApplied_)
       {
-        Query::const_iterator it = query.find(tag);
-        if (it != query.end())
+        std::set<std::string> tmp = filtered_;
+
+        filtered_.clear();
+
+        for (std::set<std::string>::const_iterator 
+               it = tmp.begin(); it != tmp.end(); ++it)
         {
-          RestrictIdentifier(it->first, it->second);
+          std::list<std::string> children;
+          index_.GetChildren(children, *it);
+          ListToSet(filtered_, children);
         }
       }
 
-
-    public:
-      CandidateResources(ServerIndex& index) : 
-        index_(index), 
-        level_(ResourceType_Patient), 
-        isFilterApplied_(false)
-      {
-      }
-
-      ResourceType GetLevel() const
+      switch (level_)
       {
-        return level_;
-      }
+        case ResourceType_Patient:
+          level_ = ResourceType_Study;
+          break;
 
-      void GoDown()
-      {
-        assert(level_ != ResourceType_Instance);
+        case ResourceType_Study:
+          level_ = ResourceType_Series;
+          break;
 
-        if (isFilterApplied_)
-        {
-          std::set<std::string> tmp = filtered_;
-
-          filtered_.clear();
+        case ResourceType_Series:
+          level_ = ResourceType_Instance;
+          break;
 
-          for (std::set<std::string>::const_iterator 
-                 it = tmp.begin(); it != tmp.end(); ++it)
-          {
-            std::list<std::string> children;
-            index_.GetChildren(children, *it);
-            ListToSet(filtered_, children);
-          }
-        }
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
 
-        switch (level_)
+    void Flatten(std::list<std::string>& resources) const
+    {
+      resources.clear();
+
+      if (isFilterApplied_)
+      {
+        for (std::set<std::string>::const_iterator 
+               it = filtered_.begin(); it != filtered_.end(); ++it)
         {
-          case ResourceType_Patient:
-            level_ = ResourceType_Study;
-            break;
-
-          case ResourceType_Study:
-            level_ = ResourceType_Series;
-            break;
-
-          case ResourceType_Series:
-            level_ = ResourceType_Instance;
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_InternalError);
+          resources.push_back(*it);
         }
       }
-
-
-      void Flatten(std::list<std::string>& resources) const
+      else
       {
-        resources.clear();
-
-        if (isFilterApplied_)
-        {
-          for (std::set<std::string>::const_iterator 
-                 it = filtered_.begin(); it != filtered_.end(); ++it)
-          {
-            resources.push_back(*it);
-          }
-        }
-        else
-        {
-          Json::Value tmp;
-          index_.GetAllUuids(tmp, level_);
-          for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++)
-          {
-            resources.push_back(tmp[i].asString());
-          }
-        }
+        index_.GetAllUuids(resources, level_);
       }
+    }
 
 
-      void RestrictIdentifier(const Query& query)
+    void RestrictIdentifier(Query& query)
+    {
+      switch (level_)
       {
-        switch (level_)
+        case ResourceType_Patient:
         {
-          case ResourceType_Patient:
-          {
-            RestrictIdentifier(query, DICOM_TAG_PATIENT_ID);
-            break;
-          }
-
-          case ResourceType_Study:
-          {
-            RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID);
-            RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER);
-            break;
-          }
+          RestrictIdentifier(query, DICOM_TAG_PATIENT_ID);
+          break;
+        }
 
-          case ResourceType_Series:
-          {
-            RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID);
-            break;
-          }
-
-          case ResourceType_Instance:
-          {
-            RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID);
-            break;
-          }
+        case ResourceType_Study:
+        {
+          RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID);
+          RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER);
+          break;
+        }
 
-          default:
-            throw OrthancException(ErrorCode_InternalError);
+        case ResourceType_Series:
+        {
+          RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID);
+          break;
         }
-      }
-
 
-      void RestrictMainDicomTags(const Query& query,
-                                 bool caseSensitive)
-      {
-        if (query.size() == 0)
+        case ResourceType_Instance:
         {
-          return;
+          RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID);
+          break;
         }
 
-        std::list<std::string> resources;
-        Flatten(resources);
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
 
-        isFilterApplied_ = true;
-        filtered_.clear();
+    void RestrictMainDicomTags(const Query& query,
+                               bool caseSensitive)
+    {
+      if (query.size() == 0)
+      {
+        return;
+      }
+
+      std::list<std::string> resources;
+      Flatten(resources);
 
-        for (std::list<std::string>::const_iterator
-               it = resources.begin(); it != resources.end(); it++)
+      isFilterApplied_ = true;
+      filtered_.clear();
+
+      for (std::list<std::string>::const_iterator
+             it = resources.begin(); it != resources.end(); it++)
+      {
+        DicomMap mainTags;
+        if (index_.GetMainDicomTags(mainTags, *it, level_))
         {
-          DicomMap mainTags;
-          if (index_.GetMainDicomTags(mainTags, *it, level_))
+          for (Query::const_iterator tag = query.begin(); 
+               tag != query.end(); ++tag)
           {
-            for (Query::const_iterator tag = query.begin(); 
-                 tag != query.end(); ++tag)
+            assert(DicomMap::IsMainDicomTag(tag->first, level_));
+            if (tag->first != DICOM_TAG_PATIENT_ID &&
+                tag->first != DICOM_TAG_STUDY_INSTANCE_UID &&
+                tag->first != DICOM_TAG_ACCESSION_NUMBER &&
+                tag->first != DICOM_TAG_SERIES_INSTANCE_UID &&
+                tag->first != DICOM_TAG_SOP_INSTANCE_UID)
             {
-              assert(DicomMap::IsMainDicomTag(tag->first, level_));
-              if (tag->first != DICOM_TAG_PATIENT_ID &&
-                  tag->first != DICOM_TAG_STUDY_INSTANCE_UID &&
-                  tag->first != DICOM_TAG_ACCESSION_NUMBER &&
-                  tag->first != DICOM_TAG_SERIES_INSTANCE_UID &&
-                  tag->first != DICOM_TAG_SOP_INSTANCE_UID)
-              {
-                LOG(INFO) << "Lookup for main DICOM tag "
-                          << FromDcmtkBridge::GetName(tag->first) << " (value: " << tag->second << ")";
+              LOG(INFO) << "Lookup for main DICOM tag "
+                        << FromDcmtkBridge::GetName(tag->first) << " (value: " << tag->second << ")";
                 
-                const DicomValue* value = mainTags.TestAndGetValue(tag->first);
-                if (value != NULL &&
-                    Compare(value->AsString(), tag->second, caseSensitive))
-                {
-                  filtered_.insert(*it);
-                }
+              const DicomValue* value = mainTags.TestAndGetValue(tag->first);
+              if (value != NULL &&
+                  Compare(value->AsString(), tag->second, caseSensitive))
+              {
+                filtered_.insert(*it);
               }
-            }            
-          }
+            }
+          }            
         }
       }
-    };
-  }
+    }
+  };
 
 
-  ResourceFinder::ResourceFinder(ServerContext& context) : 
-    context_(context),
+  ResourceFinder::ResourceFinder(ServerIndex& index) : 
+    index_(index),
     level_(ResourceType_Patient),
     caseSensitive_(true)
   {
@@ -305,29 +298,75 @@
   }
 
 
-  void ResourceFinder::GetTagsForLevel(Query& result,
-                                       const Query& source,
-                                       ResourceType level)
+  void ResourceFinder::ExtractTagsForLevel(Query& target,
+                                           Query& source,
+                                           ResourceType level)
   {
     typedef std::set<DicomTag>  Tags;
 
     Tags  tags;
     DicomMap::GetMainDicomTags(tags, level);
 
+    target.clear();
+
     for (Tags::const_iterator tag = tags.begin(); tag != tags.end(); tag++)
     {
-      Query::const_iterator value = source.find(*tag);
+      Query::iterator value = source.find(*tag);
       if (value != source.end())
       {
-        result.insert(*value);
+        target.insert(*value);
+        source.erase(value);
       }
     }
   }
 
 
+  void ResourceFinder::ApplyAtLevel(CandidateResources& candidates,
+                                    ResourceType level)
+  {
+    if (level != ResourceType_Patient)
+    {
+      candidates.GoDown();
+    }
+
+    candidates.RestrictIdentifier(query_);
+
+    Query tmp;
+    ExtractTagsForLevel(tmp, query_, level);
+    candidates.RestrictMainDicomTags(tmp, caseSensitive_);
+  }
+
+
   void ResourceFinder::Apply(std::list<std::string>& result)
   {
-    result.clear();
+    CandidateResources candidates(index_);
+
+    ApplyAtLevel(candidates, ResourceType_Patient);
 
+    if (level_ == ResourceType_Study ||
+        level_ == ResourceType_Series ||
+        level_ == ResourceType_Instance)
+    {
+      ApplyAtLevel(candidates, ResourceType_Study);
+    }
+        
+    if (level_ == ResourceType_Series ||
+        level_ == ResourceType_Instance)
+    {
+      ApplyAtLevel(candidates, ResourceType_Series);
+    }
+        
+    if (level_ == ResourceType_Instance)
+    {
+      ApplyAtLevel(candidates, ResourceType_Instance);
+    }
+        
+    if (!query_.empty())
+    {
+      LOG(ERROR) << "Invalid query: Searching against a tag that is not valid for the requested level";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    candidates.Flatten(result);
   }
 }
--- a/OrthancServer/ResourceFinder.h	Tue May 12 18:27:14 2015 +0200
+++ b/OrthancServer/ResourceFinder.h	Wed May 13 12:17:35 2015 +0200
@@ -32,7 +32,7 @@
 
 #pragma once
 
-#include "ServerContext.h"
+#include "ServerIndex.h"
 
 #include <boost/noncopyable.hpp>
 
@@ -43,17 +43,22 @@
   private:
     typedef std::map<DicomTag, std::string>  Query;
 
-    ServerContext&  context_;
-    ResourceType    level_;
-    bool            caseSensitive_;
-    Query           query_;
+    class CandidateResources;
+
+    ServerIndex&  index_;
+    ResourceType  level_;
+    bool          caseSensitive_;
+    Query         query_;
 
-    static void GetTagsForLevel(Query& result,
-                                const Query& source,
-                                ResourceType level);
+    static void ExtractTagsForLevel(Query& result,
+                                    Query& source,
+                                    ResourceType level);
+
+    void ApplyAtLevel(CandidateResources& candidates,
+                      ResourceType level);
 
   public:
-    ResourceFinder(ServerContext& context);
+    ResourceFinder(ServerIndex& index);
 
     bool IsCaseSensitive() const
     {
@@ -62,7 +67,7 @@
 
     void SetCaseSensitive(bool sensitive)
     {
-      caseSensitive_ = true;
+      caseSensitive_ = sensitive;
     }
 
     ResourceType GetLevel() const
--- a/OrthancServer/ServerIndex.cpp	Tue May 12 18:27:14 2015 +0200
+++ b/OrthancServer/ServerIndex.cpp	Wed May 13 12:17:35 2015 +0200
@@ -1075,7 +1075,7 @@
 
 
 
-  void ServerIndex::GetAllUuids(Json::Value& target,
+  void ServerIndex::GetAllUuids(std::list<std::string>& target,
                                 ResourceType resourceType)
   {
     std::list<std::string> lst;
@@ -1085,11 +1085,11 @@
       db_.GetAllPublicIds(lst, resourceType);
     }
 
-    target = Json::arrayValue;
+    target.clear();
     for (std::list<std::string>::const_iterator
            it = lst.begin(); it != lst.end(); ++it)
     {
-      target.append(*it);
+      target.push_back(*it);
     }
   }
 
--- a/OrthancServer/ServerIndex.h	Tue May 12 18:27:14 2015 +0200
+++ b/OrthancServer/ServerIndex.h	Wed May 13 12:17:35 2015 +0200
@@ -158,7 +158,7 @@
                           const std::string& instanceUuid,
                           FileContentType contentType);
 
-    void GetAllUuids(Json::Value& target,
+    void GetAllUuids(std::list<std::string>& target,
                      ResourceType resourceType);
 
     bool DeleteResource(Json::Value& target /* out */,