diff OrthancServer/Search/LookupResource.cpp @ 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 faf2ecab3472
line wrap: on
line diff
--- 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));
     }
   }
 }