# HG changeset patch # User Sebastien Jodogne # Date 1382026075 -7200 # Node ID 0bedf8ff9288a5156b5bae8bf8301fa1441c6f80 # Parent ce5d2040c47bba7a2f1828f7eae24df27f358f73 basic find scp diff -r ce5d2040c47b -r 0bedf8ff9288 Core/Toolbox.cpp --- 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 #include #include +#include #if defined(_WIN32) #include @@ -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& 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); + } } + diff -r ce5d2040c47b -r 0bedf8ff9288 Core/Toolbox.h --- 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& result, + const std::string& source, + char separator); } } diff -r ce5d2040c47b -r 0bedf8ff9288 OrthancServer/OrthancFindRequestHandler.cpp --- 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 +#include #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 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 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); } } diff -r ce5d2040c47b -r 0bedf8ff9288 Resources/CMake/BoostConfiguration.cmake --- 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 ) diff -r ce5d2040c47b -r 0bedf8ff9288 UnitTests/main.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 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.