changeset 1751:fb569ee09a69 db-changes

LookupResource complete
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 27 Oct 2015 16:05:42 +0100
parents 55d52567bebb
children c3d8ec63a179
files OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/Search/ListConstraint.h OrthancServer/Search/LookupIdentifierQuery.cpp OrthancServer/Search/LookupResource.cpp OrthancServer/Search/LookupResource.h OrthancServer/Search/RangeConstraint.h OrthancServer/Search/SetOfResources.cpp OrthancServer/Search/SetOfResources.h OrthancServer/Search/ValueConstraint.h OrthancServer/Search/WildcardConstraint.cpp OrthancServer/Search/WildcardConstraint.h OrthancServer/ServerContext.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 14 files changed, 303 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Tue Oct 27 16:05:42 2015 +0100
@@ -30,6 +30,10 @@
  **/
 
 
+#define USE_LOOKUP_RESOURCE 0
+
+
+
 #include "PrecompiledHeadersServer.h"
 #include "OrthancFindRequestHandler.h"
 
@@ -41,6 +45,7 @@
 
 #include "ResourceFinder.h"
 #include "DicomFindQuery.h"
+#include "Search/LookupResource.h"
 
 #include <boost/regex.hpp> 
 
@@ -276,9 +281,13 @@
      * Build up the query object.
      **/
 
+#if USE_LOOKUP_RESOURCE == 1
+    LookupResource finder(level);
+#else
     CFindQuery findQuery(answers, context_.GetIndex(), query);
     findQuery.SetLevel(level);
-        
+#endif     
+   
     for (size_t i = 0; i < query.GetSize(); i++)
     {
       const DicomTag tag = query.GetElement(i).GetTag();
@@ -297,6 +306,13 @@
         continue;
       }
 
+#if USE_LOOKUP_RESOURCE == 1
+      // TODO SetModalitiesInStudy(value);
+
+      finder.Add(tag, value, caseSensitivePN);
+
+#else
+
       if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
       {
         findQuery.SetModalitiesInStudy(value);
@@ -305,6 +321,7 @@
       {
         findQuery.SetConstraint(tag, value, caseSensitivePN);
       }
+#endif
     }
 
 
@@ -312,7 +329,9 @@
      * Run the query.
      **/
 
+#if USE_LOOKUP_RESOURCE != 1
     ResourceFinder finder(context_);
+#endif
 
     switch (level)
     {
@@ -331,7 +350,12 @@
     }
 
     std::list<std::string> tmp;
+
+#if USE_LOOKUP_RESOURCE == 1
+    bool finished = context_.Apply(tmp, finder);
+#else
     bool finished = finder.Apply(tmp, findQuery);
+#endif
 
     LOG(INFO) << "Number of matching resources: " << tmp.size();
 
--- a/OrthancServer/Search/ListConstraint.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/ListConstraint.h	Tue Oct 27 16:05:42 2015 +0100
@@ -44,6 +44,13 @@
     std::set<std::string>  allowedValues_;
     bool                   isCaseSensitive_;
 
+    ListConstraint(const ListConstraint& other) : 
+      IFindConstraint(other.GetTag()),
+      allowedValues_(other.allowedValues_),
+      isCaseSensitive_(other.isCaseSensitive_)
+    {
+    }
+
   public:
     ListConstraint(const DicomTag& tag, 
                    bool isCaseSensitive) : 
@@ -54,6 +61,11 @@
 
     void AddAllowedValue(const std::string& value);
 
+    virtual IFindConstraint* Clone() const
+    {
+      return new ListConstraint(*this);
+    }
+
     virtual void Setup(LookupIdentifierQuery& lookup) const;
 
     virtual bool Match(const std::string& value) const;
--- a/OrthancServer/Search/LookupIdentifierQuery.cpp	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/LookupIdentifierQuery.cpp	Tue Oct 27 16:05:42 2015 +0100
@@ -156,6 +156,7 @@
                                             const std::string& value)
   {
     assert(IsIdentifier(tag));
+    constraints_.push_back(new Disjunction);
     constraints_.back()->Add(tag, type, value);
   }
 
--- a/OrthancServer/Search/LookupResource.cpp	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/LookupResource.cpp	Tue Oct 27 16:05:42 2015 +0100
@@ -33,9 +33,15 @@
 #include "../PrecompiledHeadersServer.h"
 #include "LookupResource.h"
 
+#include "ListConstraint.h"
+#include "RangeConstraint.h"
+#include "ValueConstraint.h"
+#include "WildcardConstraint.h"
+
 #include "../../Core/OrthancException.h"
 #include "../../Core/FileStorage/StorageAccessor.h"
 #include "../ServerToolbox.h"
+#include "../FromDcmtkBridge.h"
 
 
 namespace Orthanc
@@ -269,39 +275,37 @@
 
 
 
-  void LookupResource::ApplyUnoptimizedConstraints(SetOfResources& candidates,
+  bool LookupResource::ApplyUnoptimizedConstraints(std::list<int64_t>& result,
+                                                   const std::list<int64_t>& candidates,
+                                                   boost::mutex& databaseMutex,
                                                    IDatabaseWrapper& database,
                                                    IStorageArea& storageArea) const
   {
-    if (unoptimizedConstraints_.empty())
-    {
-      // Nothing to do
-      return;
-    }
-
-    std::list<int64_t>  source;
-    candidates.Flatten(source);
-    candidates.Clear();
+    assert(!unoptimizedConstraints_.empty());
 
     StorageAccessor accessor(storageArea);
 
-    std::list<int64_t>  filtered;
-    for (std::list<int64_t>::const_iterator candidate = source.begin(); 
-         candidate != source.end(); ++candidate)
+    for (std::list<int64_t>::const_iterator candidate = candidates.begin(); 
+         candidate != candidates.end(); ++candidate)
     {
       if (maxResults_ != 0 &&
-          filtered.size() >= maxResults_)
+          result.size() >= maxResults_)
       {
-        // We have enough results
-        break;
+        // We have enough results, not finished
+        return false;
       }
 
       int64_t instance;
       FileInfo attachment;
-      if (!Toolbox::FindOneChildInstance(instance, database, *candidate, level_) ||
-          !database.LookupAttachment(attachment, instance, FileContentType_DicomAsJson))
+
       {
-        continue;
+        boost::mutex::scoped_lock lock(databaseMutex);
+
+        if (!Toolbox::FindOneChildInstance(instance, database, *candidate, level_) ||
+            !database.LookupAttachment(attachment, instance, FileContentType_DicomAsJson))
+        {
+          continue;
+        }
       }
 
       Json::Value content;
@@ -330,11 +334,11 @@
 
       if (match)
       {
-        filtered.push_back(*candidate);
+        result.push_back(*candidate);
       }
     }
 
-    candidates.Intersect(filtered);
+    return true;  // Finished
   }
 
 
@@ -350,58 +354,170 @@
   }
 
 
-  void LookupResource::Apply(std::list<int64_t>& result,
-                             IDatabaseWrapper& database,
-                             IStorageArea& storageArea) const
-  {
-    SetOfResources candidates(database, level_);
-
-    switch (level_)
-    {
-      case ResourceType_Patient:
-        ApplyLevel(candidates, ResourceType_Patient, database);
-        break;
-
-      case ResourceType_Study:
-        ApplyLevel(candidates, ResourceType_Study, database);
-        break;
-
-      case ResourceType_Series:
-        ApplyLevel(candidates, ResourceType_Study, database);
-        candidates.GoDown();
-        ApplyLevel(candidates, ResourceType_Series, database);
-        break;
-
-      case ResourceType_Instance:
-        ApplyLevel(candidates, ResourceType_Study, database);
-        candidates.GoDown();
-        ApplyLevel(candidates, ResourceType_Series, database);
-        candidates.GoDown();
-        ApplyLevel(candidates, ResourceType_Instance, database);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    ApplyUnoptimizedConstraints(candidates, database, storageArea);
-    candidates.Flatten(result);
-  }
-
-
-  void LookupResource::Apply(std::list<std::string>& result,
+  bool LookupResource::Apply(std::list<int64_t>& result,
+                             boost::mutex& databaseMutex,
                              IDatabaseWrapper& database,
                              IStorageArea& storageArea) const
   {
     std::list<int64_t> tmp;
-    Apply(tmp, database, storageArea);
+
+    {
+      boost::mutex::scoped_lock lock(databaseMutex);
+      SetOfResources candidates(database, level_);
+
+      switch (level_)
+      {
+        case ResourceType_Patient:
+          ApplyLevel(candidates, ResourceType_Patient, database);
+          break;
+
+        case ResourceType_Study:
+          ApplyLevel(candidates, ResourceType_Study, database);
+          break;
+
+        case ResourceType_Series:
+          ApplyLevel(candidates, ResourceType_Study, database);
+          candidates.GoDown();
+          ApplyLevel(candidates, ResourceType_Series, database);
+          break;
+
+        case ResourceType_Instance:
+          ApplyLevel(candidates, ResourceType_Study, database);
+          candidates.GoDown();
+          ApplyLevel(candidates, ResourceType_Series, database);
+          candidates.GoDown();
+          ApplyLevel(candidates, ResourceType_Instance, database);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (unoptimizedConstraints_.empty())
+      {
+        return candidates.Flatten(result, maxResults_);
+      }
+      else
+      {
+        candidates.Flatten(tmp);
+      }
+    }
+
+    return ApplyUnoptimizedConstraints(result, tmp, databaseMutex, database, storageArea);
+  }
+
+
+  bool LookupResource::Apply(std::list<std::string>& result,
+                             boost::mutex& databaseMutex,
+                             IDatabaseWrapper& database,
+                             IStorageArea& storageArea) const
+  {
+
+    std::list<int64_t> tmp;
+    bool finished = Apply(tmp, databaseMutex, database, storageArea);
 
     result.clear();
 
-    for (std::list<int64_t>::const_iterator
-           it = tmp.begin(); it != tmp.end(); ++it)
+    {
+      boost::mutex::scoped_lock lock(databaseMutex);
+
+      for (std::list<int64_t>::const_iterator
+             it = tmp.begin(); it != tmp.end(); ++it)
+      {
+        result.push_back(database.GetPublicId(*it));
+      }
+    }
+
+    return finished;
+  }
+
+
+  void LookupResource::Add(const DicomTag& tag,
+                           const std::string& dicomQuery,
+                           bool caseSensitivePN)
+  {
+    ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
+
+    bool sensitive = true;
+    if (vr == ValueRepresentation_PatientName)
+    {
+      sensitive = caseSensitivePN;
+    }
+
+    // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
+    // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html  
+
+    if ((vr == ValueRepresentation_Date ||
+         vr == ValueRepresentation_DateTime ||
+         vr == ValueRepresentation_Time) &&
+        dicomQuery.find('-') != std::string::npos)
+    {
+      /**
+       * Range matching is only defined for TM, DA and DT value
+       * representations. This code fixes issues 35 and 37.
+       *
+       * Reference: "Range matching is not defined for types of
+       * Attributes other than dates and times", DICOM PS 3.4,
+       * C.2.2.2.5 ("Range Matching").
+       **/
+      size_t separator = dicomQuery.find('-');
+      std::string lower = dicomQuery.substr(0, separator);
+      std::string upper = dicomQuery.substr(separator + 1);
+      Add(new RangeConstraint(tag, lower, upper, sensitive));
+    }
+    else if (dicomQuery.find('\\') != std::string::npos)
     {
-      result.push_back(database.GetPublicId(*it));
+      std::auto_ptr<ListConstraint> constraint(new ListConstraint(tag, sensitive));
+
+      std::vector<std::string> items;
+      Toolbox::TokenizeString(items, dicomQuery, '\\');
+
+      for (size_t i = 0; i < items.size(); i++)
+      {
+        constraint->AddAllowedValue(items[i]);
+      }
+
+      Add(constraint.release());
+    }
+    else if (dicomQuery.find('*') != std::string::npos ||
+             dicomQuery.find('?') != std::string::npos)
+    {
+      Add(new WildcardConstraint(tag, dicomQuery, sensitive));
+    }
+    else
+    {
+      /**
+       * Case-insensitive match for PN value representation (Patient
+       * Name). Case-senstive match for all the other value
+       * representations.
+       *
+       * Reference: DICOM PS 3.4
+       *   - C.2.2.2.1 ("Single Value Matching") 
+       *   - C.2.2.2.4 ("Wild Card Matching")
+       * http://medical.nema.org/Dicom/2011/11_04pu.pdf
+       *
+       * "Except for Attributes with a PN Value Representation, only
+       * entities with values which match exactly the value specified in the
+       * request shall match. This matching is case-sensitive, i.e.,
+       * sensitive to the exact encoding of the key attribute value in
+       * character sets where a letter may have multiple encodings (e.g.,
+       * based on its case, its position in a word, or whether it is
+       * accented)
+       * 
+       * For Attributes with a PN Value Representation (e.g., Patient Name
+       * (0010,0010)), an application may perform literal matching that is
+       * either case-sensitive, or that is insensitive to some or all
+       * aspects of case, position, accent, or other character encoding
+       * variants."
+       *
+       * (0008,0018) UI SOPInstanceUID     => Case-sensitive
+       * (0008,0050) SH AccessionNumber    => Case-sensitive
+       * (0010,0020) LO PatientID          => Case-sensitive
+       * (0020,000D) UI StudyInstanceUID   => Case-sensitive
+       * (0020,000E) UI SeriesInstanceUID  => Case-sensitive
+      **/
+
+      Add(new ValueConstraint(tag, dicomQuery, sensitive));
     }
   }
 }
--- a/OrthancServer/Search/LookupResource.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/LookupResource.h	Tue Oct 27 16:05:42 2015 +0100
@@ -36,6 +36,7 @@
 #include "SetOfResources.h"
 
 #include <memory>
+#include <boost/thread/mutex.hpp>
 
 namespace Orthanc
 {
@@ -78,7 +79,9 @@
                     ResourceType level,
                     IDatabaseWrapper& database) const;
 
-    void ApplyUnoptimizedConstraints(SetOfResources& candidates,
+    bool ApplyUnoptimizedConstraints(std::list<int64_t>& result,
+                                     const std::list<int64_t>& candidates,
+                                     boost::mutex& databaseMutex,
                                      IDatabaseWrapper& database,
                                      IStorageArea& storageArea) const;
 
@@ -89,6 +92,10 @@
 
     void Add(IFindConstraint* constraint);   // Takes ownership
 
+    void Add(const DicomTag& tag,
+             const std::string& dicomQuery,
+             bool caseSensitivePN);
+
     void SetMaxResults(size_t maxResults)
     {
       maxResults_ = maxResults;
@@ -99,11 +106,13 @@
       return maxResults_;
     }
 
-    void Apply(std::list<int64_t>& result,
+    bool Apply(std::list<int64_t>& result,
+               boost::mutex& databaseMutex,
                IDatabaseWrapper& database,
                IStorageArea& storageArea) const;
 
-    void Apply(std::list<std::string>& result,
+    bool Apply(std::list<std::string>& result,
+               boost::mutex& databaseMutex,
                IDatabaseWrapper& database,
                IStorageArea& storageArea) const;
   };
--- a/OrthancServer/Search/RangeConstraint.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/RangeConstraint.h	Tue Oct 27 16:05:42 2015 +0100
@@ -43,12 +43,25 @@
     std::string  upper_;
     bool         isCaseSensitive_;
 
+    RangeConstraint(const RangeConstraint& other) : 
+      IFindConstraint(other.GetTag()),
+      lower_(other.lower_),
+      upper_(other.upper_),
+      isCaseSensitive_(other.isCaseSensitive_)
+    {
+    }
+
   public:
     RangeConstraint(const DicomTag& tag, 
                     const std::string& lower,
                     const std::string& upper,
                     bool isCaseSensitive);
 
+    virtual IFindConstraint* Clone() const
+    {
+      return new RangeConstraint(*this);
+    }
+
     virtual void Setup(LookupIdentifierQuery& lookup) const;
 
     virtual bool Match(const std::string& value) const;
--- a/OrthancServer/Search/SetOfResources.cpp	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/SetOfResources.cpp	Tue Oct 27 16:05:42 2015 +0100
@@ -150,4 +150,24 @@
       }
     }
   }
+
+
+  bool SetOfResources::Flatten(std::list<int64_t>& result,
+                               size_t maxResults)
+  {
+    Flatten(result);
+
+    if (maxResults != 0 &&
+        result.size() > maxResults)
+    {
+      std::list<int64_t>::iterator cut = result.begin();
+      std::advance(cut, maxResults);
+      result.erase(cut, result.end());
+      return false;
+    }
+    else
+    {
+      return true;
+    }
+  }
 }
--- a/OrthancServer/Search/SetOfResources.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/SetOfResources.h	Tue Oct 27 16:05:42 2015 +0100
@@ -70,6 +70,9 @@
 
     void Flatten(std::list<std::string>& result);
 
+    bool Flatten(std::list<int64_t>& result,
+                 size_t maxResults);
+
     void Clear()
     {
       resources_.reset(NULL);
--- a/OrthancServer/Search/ValueConstraint.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/ValueConstraint.h	Tue Oct 27 16:05:42 2015 +0100
@@ -42,11 +42,23 @@
     std::string  value_;
     bool         isCaseSensitive_;
 
+    ValueConstraint(const ValueConstraint& other) : 
+      IFindConstraint(other.GetTag()),
+      value_(other.value_),
+      isCaseSensitive_(other.isCaseSensitive_)
+    {
+    }
+
   public:
     ValueConstraint(const DicomTag& tag, 
                     const std::string& value,
                     bool isCaseSensitive);
 
+    virtual IFindConstraint* Clone() const
+    {
+      return new ValueConstraint(*this);
+    }
+
     virtual void Setup(LookupIdentifierQuery& lookup) const;
 
     virtual bool Match(const std::string& value) const;
--- a/OrthancServer/Search/WildcardConstraint.cpp	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/WildcardConstraint.cpp	Tue Oct 27 16:05:42 2015 +0100
@@ -43,6 +43,14 @@
     std::string   wildcard_;
   };
 
+
+  WildcardConstraint::WildcardConstraint(const WildcardConstraint& other) :
+    IFindConstraint(other.GetTag()),
+    pimpl_(new PImpl(*other.pimpl_))
+  {
+  }
+
+
   WildcardConstraint::WildcardConstraint(const DicomTag& tag, 
                                          const std::string& wildcard,
                                          bool isCaseSensitive) :
--- a/OrthancServer/Search/WildcardConstraint.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/Search/WildcardConstraint.h	Tue Oct 27 16:05:42 2015 +0100
@@ -44,11 +44,18 @@
     struct PImpl;
     boost::shared_ptr<PImpl>  pimpl_;
 
+    WildcardConstraint(const WildcardConstraint& other);
+
   public:
     WildcardConstraint(const DicomTag& tag, 
                        const std::string& wildcard,
                        bool isCaseSensitive);
 
+    virtual IFindConstraint* Clone() const
+    {
+      return new WildcardConstraint(*this);
+    }
+
     virtual void Setup(LookupIdentifierQuery& lookup) const;
 
     virtual bool Match(const std::string& value) const;
--- a/OrthancServer/ServerContext.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/ServerContext.h	Tue Oct 27 16:05:42 2015 +0100
@@ -262,5 +262,11 @@
 
     bool HasPlugins() const;
 
+    
+    bool Apply(std::list<std::string>& result,
+               ::Orthanc::LookupResource& lookup)
+    {
+      return index_.Apply(result, lookup, area_);
+    }
   };
 }
--- a/OrthancServer/ServerIndex.cpp	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/ServerIndex.cpp	Tue Oct 27 16:05:42 2015 +0100
@@ -2115,11 +2115,10 @@
   }
 
 
-  void ServerIndex::Apply(std::list<std::string>& result,
+  bool ServerIndex::Apply(std::list<std::string>& result,
                           ::Orthanc::LookupResource& lookup,
                           IStorageArea& area)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-    lookup.Apply(result, db_, area);
+    return lookup.Apply(result, mutex_, db_, area);
   }
 }
--- a/OrthancServer/ServerIndex.h	Tue Oct 27 12:45:50 2015 +0100
+++ b/OrthancServer/ServerIndex.h	Tue Oct 27 16:05:42 2015 +0100
@@ -263,7 +263,7 @@
 
     unsigned int GetDatabaseVersion();
 
-    void Apply(std::list<std::string>& result,
+    bool Apply(std::list<std::string>& result,
                ::Orthanc::LookupResource& lookup,
                IStorageArea& area);
   };