changeset 608:0bedf8ff9288 find-move-scp

basic find scp
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 17 Oct 2013 18:07:55 +0200
parents ce5d2040c47b
children 5ba825b87b21
files Core/Toolbox.cpp Core/Toolbox.h OrthancServer/OrthancFindRequestHandler.cpp Resources/CMake/BoostConfiguration.cmake UnitTests/main.cpp
diffstat 5 files changed, 293 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Toolbox.cpp	Thu Oct 17 14:20:13 2013 +0200
+++ b/Core/Toolbox.cpp	Thu Oct 17 18:07:55 2013 +0200
@@ -42,6 +42,7 @@
 #include <boost/uuid/sha1.hpp>
 #include <algorithm>
 #include <ctype.h>
+#include <boost/regex.hpp> 
 
 #if defined(_WIN32)
 #include <windows.h>
@@ -726,4 +727,60 @@
         throw OrthancException(ErrorCode_NotImplemented);
     }
   }
+
+
+  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
+  {
+    // TODO - Speed up this with a regular expression
+
+    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;
+  }
+
+
+
+  void Toolbox::TokenizeString(std::vector<std::string>& result,
+                               const std::string& value,
+                               char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
 }
+
--- a/Core/Toolbox.h	Thu Oct 17 14:20:13 2013 +0200
+++ b/Core/Toolbox.h	Thu Oct 17 18:07:55 2013 +0200
@@ -108,5 +108,11 @@
     void UrlDecode(std::string& s);
 
     Endianness DetectEndianness();
+
+    std::string WildcardToRegularExpression(const std::string& s);
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
   }
 }
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Thu Oct 17 14:20:13 2013 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Thu Oct 17 18:07:55 2013 +0200
@@ -32,11 +32,167 @@
 #include "OrthancFindRequestHandler.h"
 
 #include <glog/logging.h>
+#include <boost/regex.hpp> 
 
 #include "../Core/DicomFormat/DicomArray.h"
+#include "ServerToolbox.h"
 
 namespace Orthanc
 {
+  static bool ApplyRangeConstraint(const std::string& value,
+                                   const std::string& constraint)
+  {
+    // TODO
+    return false;
+  }
+
+
+  static bool ApplyListConstraint(const std::string& value,
+                                  const std::string& constraint)
+  {
+    std::cout << value << std::endl;
+
+    std::string v1 = value;
+    Toolbox::ToLowerCase(v1);
+
+    std::vector<std::string> items;
+    Toolbox::TokenizeString(items, constraint, '\\');
+
+    for (size_t i = 0; i < items.size(); i++)
+    {
+      Toolbox::ToLowerCase(items[i]);
+      if (items[i] == v1)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  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)
+    {
+      // TODO - Cache the constructed regular expression
+      boost::regex pattern(Toolbox::WildcardToRegularExpression(constraint),
+                           boost::regex::icase /* case insensitive search */);
+      return boost::regex_match(value, pattern);
+    }
+    else
+    {
+      std::string v1 = value;
+      std::string v2 = constraint;
+
+      Toolbox::ToLowerCase(v1);
+      Toolbox::ToLowerCase(v2);
+
+      return v1 == v2;
+    }
+  }
+
+
+  static bool LookupOneInstance(std::string& result,
+                                ServerIndex& index,
+                                const std::string& id,
+                                ResourceType type)
+  {
+    if (type == ResourceType_Instance)
+    {
+      result = id;
+      return true;
+    }
+
+    std::string childId;
+    
+    {
+      std::list<std::string> children;
+      index.GetChildInstances(children, id);
+
+      if (children.size() == 0)
+      {
+        return false;
+      }
+
+      childId = children.front();
+    }
+
+    return LookupOneInstance(result, index, childId, GetChildResourceType(type));
+  }
+
+
+  static bool Matches(const Json::Value& resource,
+                      const DicomArray& query)
+  {
+    for (size_t i = 0; i < query.GetSize(); i++)
+    {
+      if (query.GetElement(i).GetValue().IsNull() ||
+          query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL ||
+          query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        continue;
+      }
+
+      std::string tag = query.GetElement(i).GetTag().Format();
+      std::cout << tag << std::endl;
+
+      std::string value;
+      if (resource.isMember(tag))
+      {
+        value = resource.get(tag, Json::arrayValue).get("Value", "").asString();
+      }
+
+      if (!Matches(value, query.GetElement(i).GetValue().AsString()))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  static void AddAnswer(DicomFindAnswers& answers,
+                        const Json::Value& resource,
+                        const DicomArray& query)
+  {
+    DicomMap result;
+
+    for (size_t i = 0; i < query.GetSize(); i++)
+    {
+      if (query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL &&
+          query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        std::string tag = query.GetElement(i).GetTag().Format();
+        std::string value;
+        if (resource.isMember(tag))
+        {
+          value = resource.get(tag, Json::arrayValue).get("Value", "").asString();
+          result.SetValue(query.GetElement(i).GetTag(), value);
+        }
+      }
+    }
+
+    answers.Add(result);
+  }
+
+
   void OrthancFindRequestHandler::Handle(const DicomMap& input,
                                          DicomFindAnswers& answers)
   {
@@ -63,46 +219,43 @@
 
 
     /**
-     * Retrieve the constraints of the query.
+     * Retrieve all the resources for this query level.
+     **/
+
+    Json::Value resources;
+    context_.GetIndex().GetAllUuids(resources, level);
+    assert(resources.type() == Json::arrayValue);
+
+
+    // TODO : Speed up using MainDicomTags (to avoid looping over ALL
+    // the resources and reading the JSON file for each of them)
+
+
+    /**
+     * Loop over all the resources for this query level.
      **/
 
     DicomArray query(input);
-
-    DicomMap constraintsTmp;
-    DicomMap wildcardConstraintsTmp;
-
-    for (size_t i = 0; i < query.GetSize(); i++)
+    for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
     {
-      if (!query.GetElement(i).GetValue().IsNull() &&
-          query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL &&
-          query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      try
       {
-        DicomTag tag = query.GetElement(i).GetTag();
-        std::string value = query.GetElement(i).GetValue().AsString();
-
-        if (value.find('*') != std::string::npos ||
-            value.find('?') != std::string::npos ||
-            value.find('\\') != std::string::npos ||
-            value.find('-') != std::string::npos)
+        std::string instance;
+        if (LookupOneInstance(instance, context_.GetIndex(), resources[i].asString(), level))
         {
-          wildcardConstraintsTmp.SetValue(tag, value);
-        }
-        else
-        {
-          constraintsTmp.SetValue(tag, value);
+          Json::Value resource;
+          context_.ReadJson(resource, instance);
+        
+          if (Matches(resource, query))
+          {
+            AddAnswer(answers, resource, query);
+          }
         }
       }
+      catch (OrthancException&)
+      {
+        // This resource has been deleted during the find request
+      }
     }
-
-    DicomArray constraints(constraintsTmp);
-    DicomArray wildcardConstraints(wildcardConstraintsTmp);
-
-    // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
-    // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html
-
-    constraints.Print(stdout);
-    printf("\n"); fflush(stdout);
-    wildcardConstraints.Print(stdout);
-    printf("\n"); fflush(stdout);
   }
 }
--- a/Resources/CMake/BoostConfiguration.cmake	Thu Oct 17 14:20:13 2013 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Thu Oct 17 18:07:55 2013 +0200
@@ -1,14 +1,14 @@
 if (${STATIC_BUILD})
-  SET(BOOST_STATIC 1)
+  set(BOOST_STATIC 1)
 else()
   include(FindBoost)
 
-  SET(BOOST_STATIC 0)
+  set(BOOST_STATIC 0)
   #set(Boost_DEBUG 1)
   #set(Boost_USE_STATIC_LIBS ON)
 
   find_package(Boost
-    COMPONENTS filesystem thread system date_time)
+    COMPONENTS filesystem thread system date_time regex)
 
   if (NOT Boost_FOUND)
     message(FATAL_ERROR "Unable to locate Boost on this system")
@@ -30,7 +30,7 @@
   #if (${Boost_VERSION} LESS 104800)
   # boost::locale is only available from 1.48.00
   #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version")
-  #  SET(BOOST_STATIC 1)
+  #  set(BOOST_STATIC 1)
   #endif()
 
   include_directories(${Boost_INCLUDE_DIRS})
@@ -40,12 +40,12 @@
 
 if (BOOST_STATIC)
   # Parameters for Boost 1.54.0
-  SET(BOOST_NAME boost_1_54_0)
-  SET(BOOST_BCP_SUFFIX bcpdigest-0.6.2)
-  SET(BOOST_MD5 "a464288a976ba133f9b325f454cb503d")
-  SET(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src")
+  set(BOOST_NAME boost_1_54_0)
+  set(BOOST_BCP_SUFFIX bcpdigest-0.6.2)
+  set(BOOST_MD5 "a464288a976ba133f9b325f454cb503d")
+  set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src")
   
-  SET(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
+  set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
   DownloadPackage(
     "${BOOST_MD5}"
     "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz"
@@ -87,6 +87,11 @@
     ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
     ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
     ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
+    ${BOOST_SOURCES_DIR}/libs/regex/src/cpp_regex_traits.cpp
+    ${BOOST_SOURCES_DIR}/libs/regex/src/regex.cpp
+    ${BOOST_SOURCES_DIR}/libs/regex/src/regex_raw_buffer.cpp
+    ${BOOST_SOURCES_DIR}/libs/regex/src/regex_traits_defaults.cpp
+    ${BOOST_SOURCES_DIR}/libs/regex/src/static_mutex.cpp
     ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
     )
 
--- a/UnitTests/main.cpp	Thu Oct 17 14:20:13 2013 +0200
+++ b/UnitTests/main.cpp	Thu Oct 17 18:07:55 2013 +0200
@@ -491,6 +491,37 @@
 }
 
 
+TEST(Toolbox, Wildcard)
+{
+  ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd"));
+  ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd"));
+  ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd"));
+  ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d"));
+  ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]"));
+}
+
+
+TEST(Toolbox, Tokenize)
+{
+  std::vector<std::string> t;
+  
+  Toolbox::TokenizeString(t, "", ','); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("", t[0]);
+  
+  Toolbox::TokenizeString(t, "abc", ','); 
+  ASSERT_EQ(1, t.size());
+  ASSERT_EQ("abc", t[0]);
+  
+  Toolbox::TokenizeString(t, "ab,cd,ef,", ','); 
+  ASSERT_EQ(4, t.size());
+  ASSERT_EQ("ab", t[0]);
+  ASSERT_EQ("cd", t[1]);
+  ASSERT_EQ("ef", t[2]);
+  ASSERT_EQ("", t[3]);
+}
+
+
 int main(int argc, char **argv)
 {
   // Initialize Google's logging library.