# HG changeset patch # User Sebastien Jodogne # Date 1445958342 -3600 # Node ID fb569ee09a69dc65ce4046a33a5c4bc1b2532ab6 # Parent 55d52567bebbe63e6faf7a9d4ca35eb58ba6360f LookupResource complete diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/OrthancFindRequestHandler.cpp --- 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 @@ -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 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(); diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/ListConstraint.h --- 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 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; diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/LookupIdentifierQuery.cpp --- 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); } diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/LookupResource.cpp --- 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& result, + const std::list& candidates, + boost::mutex& databaseMutex, IDatabaseWrapper& database, IStorageArea& storageArea) const { - if (unoptimizedConstraints_.empty()) - { - // Nothing to do - return; - } - - std::list source; - candidates.Flatten(source); - candidates.Clear(); + assert(!unoptimizedConstraints_.empty()); StorageAccessor accessor(storageArea); - std::list filtered; - for (std::list::const_iterator candidate = source.begin(); - candidate != source.end(); ++candidate) + for (std::list::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& 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& result, + bool LookupResource::Apply(std::list& result, + boost::mutex& databaseMutex, IDatabaseWrapper& database, IStorageArea& storageArea) const { std::list 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& result, + boost::mutex& databaseMutex, + IDatabaseWrapper& database, + IStorageArea& storageArea) const + { + + std::list tmp; + bool finished = Apply(tmp, databaseMutex, database, storageArea); result.clear(); - for (std::list::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) + { + boost::mutex::scoped_lock lock(databaseMutex); + + for (std::list::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 constraint(new ListConstraint(tag, sensitive)); + + std::vector 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)); } } } diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/LookupResource.h --- 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 +#include namespace Orthanc { @@ -78,7 +79,9 @@ ResourceType level, IDatabaseWrapper& database) const; - void ApplyUnoptimizedConstraints(SetOfResources& candidates, + bool ApplyUnoptimizedConstraints(std::list& result, + const std::list& 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& result, + bool Apply(std::list& result, + boost::mutex& databaseMutex, IDatabaseWrapper& database, IStorageArea& storageArea) const; - void Apply(std::list& result, + bool Apply(std::list& result, + boost::mutex& databaseMutex, IDatabaseWrapper& database, IStorageArea& storageArea) const; }; diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/RangeConstraint.h --- 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; diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/SetOfResources.cpp --- 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& result, + size_t maxResults) + { + Flatten(result); + + if (maxResults != 0 && + result.size() > maxResults) + { + std::list::iterator cut = result.begin(); + std::advance(cut, maxResults); + result.erase(cut, result.end()); + return false; + } + else + { + return true; + } + } } diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/SetOfResources.h --- 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& result); + bool Flatten(std::list& result, + size_t maxResults); + void Clear() { resources_.reset(NULL); diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/ValueConstraint.h --- 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; diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/WildcardConstraint.cpp --- 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) : diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/Search/WildcardConstraint.h --- 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_; + 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; diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/ServerContext.h --- 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& result, + ::Orthanc::LookupResource& lookup) + { + return index_.Apply(result, lookup, area_); + } }; } diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/ServerIndex.cpp --- 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& result, + bool ServerIndex::Apply(std::list& result, ::Orthanc::LookupResource& lookup, IStorageArea& area) { - boost::mutex::scoped_lock lock(mutex_); - lookup.Apply(result, db_, area); + return lookup.Apply(result, mutex_, db_, area); } } diff -r 55d52567bebb -r fb569ee09a69 OrthancServer/ServerIndex.h --- 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& result, + bool Apply(std::list& result, ::Orthanc::LookupResource& lookup, IStorageArea& area); };