# HG changeset patch # User Sebastien Jodogne # Date 1431696872 -7200 # Node ID 0649c5aef34a4a47cca5ae74d83ad508aa141300 # Parent 4378a66361873d0fda2ad4fbfdc24e0c99943277 DicomFindQuery diff -r 4378a6636187 -r 0649c5aef34a CMakeLists.txt --- a/CMakeLists.txt Fri May 15 13:17:37 2015 +0200 +++ b/CMakeLists.txt Fri May 15 15:34:32 2015 +0200 @@ -171,6 +171,7 @@ OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/ExportedResource.cpp OrthancServer/ResourceFinder.cpp + OrthancServer/DicomFindQuery.cpp # From "lua-scripting" branch OrthancServer/DicomInstanceToStore.cpp diff -r 4378a6636187 -r 0649c5aef34a Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Fri May 15 13:17:37 2015 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Fri May 15 15:34:32 2015 +0200 @@ -406,4 +406,16 @@ DicomArray a(*this); a.Print(fp); } + + + void DicomMap::GetTags(std::set& tags) const + { + tags.clear(); + + for (Map::const_iterator it = map_.begin(); + it != map_.end(); ++it) + { + tags.insert(it->first); + } + } } diff -r 4378a6636187 -r 0649c5aef34a Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Fri May 15 13:17:37 2015 +0200 +++ b/Core/DicomFormat/DicomMap.h Fri May 15 15:34:32 2015 +0200 @@ -171,5 +171,7 @@ static void GetMainDicomTags(std::set& result); void Print(FILE* fp) const; + + void GetTags(std::set& tags) const; }; } diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/DicomFindQuery.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomFindQuery.cpp Fri May 15 15:34:32 2015 +0200 @@ -0,0 +1,358 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + + +#include "PrecompiledHeadersServer.h" +#include "DicomFindQuery.h" + +#include "FromDcmtkBridge.h" + +#include + + +namespace Orthanc +{ + class DicomFindQuery::ValueConstraint : public DicomFindQuery::IConstraint + { + private: + bool isCaseSensitive_; + std::string expected_; + + public: + ValueConstraint(const std::string& value, + bool caseSensitive) : + isCaseSensitive_(caseSensitive), + expected_(value) + { + } + + const std::string& GetValue() const + { + return expected_; + } + + virtual bool IsExactConstraint() const + { + return isCaseSensitive_; + } + + virtual bool Apply(const std::string& value) const + { + if (isCaseSensitive_) + { + return expected_ == value; + } + else + { + std::string v, c; + Toolbox::ToLowerCase(v, value); + Toolbox::ToLowerCase(c, expected_); + return v == c; + } + } + }; + + + class DicomFindQuery::ListConstraint : public DicomFindQuery::IConstraint + { + private: + std::set values_; + + public: + ListConstraint(const std::string& values) + { + std::vector items; + Toolbox::TokenizeString(items, values, '\\'); + + for (size_t i = 0; i < items.size(); i++) + { + std::string lower; + Toolbox::ToLowerCase(lower, items[i]); + values_.insert(lower); + } + } + + virtual bool Apply(const std::string& value) const + { + std::string tmp; + Toolbox::ToLowerCase(tmp, value); + return values_.find(tmp) != values_.end(); + } + }; + + + class DicomFindQuery::RangeConstraint : public DicomFindQuery::IConstraint + { + private: + std::string lower_; + std::string upper_; + + public: + RangeConstraint(const std::string& range) + { + size_t separator = range.find('-'); + Toolbox::ToLowerCase(lower_, range.substr(0, separator)); + Toolbox::ToLowerCase(upper_, range.substr(separator + 1)); + } + + virtual bool Apply(const std::string& value) const + { + std::string v; + Toolbox::ToLowerCase(v, value); + + if (lower_.size() == 0 && + upper_.size() == 0) + { + return false; + } + + if (lower_.size() == 0) + { + return v <= upper_; + } + + if (upper_.size() == 0) + { + return v >= lower_; + } + + return (v >= lower_ && v <= upper_); + } + }; + + + class DicomFindQuery::WildcardConstraint : public DicomFindQuery::IConstraint + { + private: + boost::regex pattern_; + + public: + WildcardConstraint(const std::string& wildcard) + { + pattern_ = boost::regex(Toolbox::WildcardToRegularExpression(wildcard), + boost::regex::icase /* case insensitive search */); + } + + virtual bool Apply(const std::string& value) const + { + return boost::regex_match(value, pattern_); + } + }; + + + void DicomFindQuery::PrepareMainDicomTags(ResourceType level) + { + std::set tags; + DicomMap::GetMainDicomTags(tags, level); + + for (std::set::const_iterator + it = tags.begin(); it != tags.end(); ++it) + { + mainDicomTags_[*it] = level; + } + } + + + DicomFindQuery::DicomFindQuery() : + level_(ResourceType_Patient), + filterJson_(false) + { + PrepareMainDicomTags(ResourceType_Patient); + PrepareMainDicomTags(ResourceType_Study); + PrepareMainDicomTags(ResourceType_Series); + PrepareMainDicomTags(ResourceType_Instance); + } + + + DicomFindQuery::~DicomFindQuery() + { + for (Constraints::iterator it = constraints_.begin(); + it != constraints_.end(); it++) + { + delete it->second; + } + } + + + + + void DicomFindQuery::AssignConstraint(const DicomTag& tag, + IConstraint* constraint) + { + Constraints::iterator it = constraints_.find(tag); + + if (it != constraints_.end()) + { + constraints_.erase(it); + } + + constraints_[tag] = constraint; + + MainDicomTags::const_iterator tmp = mainDicomTags_.find(tag); + if (tmp == mainDicomTags_.end()) + { + // The query depends upon a DICOM tag that is not a main tag + // from the point of view of Orthanc, we need to decode the + // JSON file on the disk. + filterJson_ = true; + } + else + { + filteredLevels_.insert(tmp->second); + } + } + + + void DicomFindQuery::SetConstraint(const DicomTag& tag, + 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) + { + AssignConstraint(tag, new RangeConstraint(constraint)); + } + else if (constraint.find('\\') != std::string::npos) + { + AssignConstraint(tag, new ListConstraint(constraint)); + } + else if (constraint.find('*') != std::string::npos || + constraint.find('?') != std::string::npos) + { + AssignConstraint(tag, new WildcardConstraint(constraint)); + } + 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 + **/ + + AssignConstraint(tag, new ValueConstraint(constraint, FromDcmtkBridge::IsPNValueRepresentation(tag))); + } + } + + + bool DicomFindQuery::RestrictIdentifier(std::string& value, + DicomTag identifier) const + { + Constraints::const_iterator it = constraints_.find(identifier); + if (it == constraints_.end() || + !it->second->IsExactConstraint()) + { + return false; + } + else + { + value = dynamic_cast(it->second)->GetValue(); + return true; + } + } + + bool DicomFindQuery::HasMainDicomTagsFilter(ResourceType level) const + { + return filteredLevels_.find(level) != filteredLevels_.end(); + } + + bool DicomFindQuery::FilterMainDicomTags(const DicomMap& mainTags, + ResourceType level) const + { + std::set tags; + mainTags.GetTags(tags); + + for (std::set::const_iterator + it = tags.begin(); it != tags.end(); ++it) + { + Constraints::const_iterator constraint = constraints_.find(*it); + if (!constraint->second->Apply(mainTags.GetValue(*it).AsString())) + { + return false; + } + } + + return true; + } + + bool DicomFindQuery::HasInstanceFilter() const + { + return filterJson_; + } + + bool DicomFindQuery::FilterInstance(const std::string& instanceId, + const Json::Value& content) const + { + for (Constraints::const_iterator it = constraints_.begin(); + it != constraints_.end(); ++it) + { + std::string tag = it->first.Format(); + std::string value; + if (content.isMember(tag)) + { + value = content.get(tag, Json::arrayValue).get("Value", "").asString(); + } + + if (!it->second->Apply(value)) + { + return false; + } + } + + return true; + } +} diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/DicomFindQuery.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/DicomFindQuery.h Fri May 15 15:34:32 2015 +0200 @@ -0,0 +1,109 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "ResourceFinder.h" + +namespace Orthanc +{ + class DicomFindQuery : public ResourceFinder::IQuery + { + private: + class IConstraint : public boost::noncopyable + { + public: + virtual ~IConstraint() + { + } + + virtual bool IsExactConstraint() const + { + return false; + } + + virtual bool Apply(const std::string& value) const = 0; + }; + + + class ValueConstraint; + class RangeConstraint; + class ListConstraint; + class WildcardConstraint; + + typedef std::map Constraints; + typedef std::map MainDicomTags; + + MainDicomTags mainDicomTags_; + ResourceType level_; + bool filterJson_; + Constraints constraints_; + std::set filteredLevels_; + + void AssignConstraint(const DicomTag& tag, + IConstraint* constraint); + + void PrepareMainDicomTags(ResourceType level); + + + public: + DicomFindQuery(); + + virtual ~DicomFindQuery(); + + void SetLevel(ResourceType level) + { + level_ = level; + } + + virtual ResourceType GetLevel() const + { + return level_; + } + + void SetConstraint(const DicomTag& tag, + const std::string& constraint); + + virtual bool RestrictIdentifier(std::string& value, + DicomTag identifier) const; + + virtual bool HasMainDicomTagsFilter(ResourceType level) const; + + virtual bool FilterMainDicomTags(const DicomMap& mainTags, + ResourceType level) const; + + virtual bool HasInstanceFilter() const; + + virtual bool FilterInstance(const std::string& instanceId, + const Json::Value& content) const; + }; +} diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Fri May 15 13:17:37 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Fri May 15 15:34:32 2015 +0200 @@ -721,4 +721,12 @@ return false; } } + + + bool FromDcmtkBridge::IsPNValueRepresentation(const DicomTag& tag) + { + DcmTag t(tag.GetGroup(), tag.GetElement()); + return t.getEVR() == EVR_PN; + } + } diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Fri May 15 13:17:37 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Fri May 15 15:34:32 2015 +0200 @@ -105,5 +105,7 @@ static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet); + + static bool IsPNValueRepresentation(const DicomTag& tag); }; } diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Fri May 15 13:17:37 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Fri May 15 15:34:32 2015 +0200 @@ -860,18 +860,18 @@ request["Level"].type() == Json::stringValue && request["Query"].type() == Json::objectValue) { - std::string level = request["Level"].asString(); - - ResourceFinder finder(context); - finder.SetLevel(StringToResourceType(level.c_str())); - bool expand = false; if (request.isMember("Expand")) { expand = request["Expand"].asBool(); } - /*if (request.isMember("CaseSensitive")) + std::string level = request["Level"].asString(); + + /*ResourceFinder finder(context); + finder.SetLevel(StringToResourceType(level.c_str())); + + if (request.isMember("CaseSensitive")) { finder.SetCaseSensitive(request["CaseSensitive"].asBool()); } @@ -885,11 +885,11 @@ } finder.AddTag(members[i], request["Query"][members[i]].asString()); - }*/ + } std::list resources; finder.Apply(resources); - AnswerListOfResources(call.GetOutput(), context.GetIndex(), resources, finder.GetLevel(), expand); + AnswerListOfResources(call.GetOutput(), context.GetIndex(), resources, finder.GetLevel(), expand);*/ } else { diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/ResourceFinder.cpp --- a/OrthancServer/ResourceFinder.cpp Fri May 15 13:17:37 2015 +0200 +++ b/OrthancServer/ResourceFinder.cpp Fri May 15 15:34:32 2015 +0200 @@ -31,7 +31,7 @@ #include "PrecompiledHeadersServer.h" -#include "BaseResourceFinder.h" +#include "ResourceFinder.h" #include "FromDcmtkBridge.h" #include "ServerContext.h" @@ -41,12 +41,12 @@ namespace Orthanc { - class BaseResourceFinder::CandidateResources + class ResourceFinder::CandidateResources { private: typedef std::map Query; - BaseResourceFinder& finder_; + ResourceFinder& finder_; ServerIndex& index_; ResourceType level_; bool isFilterApplied_; @@ -64,49 +64,8 @@ } - void RestrictIdentifier(const DicomTag& tag, - const std::string& value) - { - assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || - (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || - (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || - (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || - (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); - - LOG(INFO) << "Lookup for identifier tag " - << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; - - std::list resources; - index_.LookupIdentifier(resources, tag, value, level_); - - if (isFilterApplied_) - { - std::set s; - ListToSet(s, resources); - - std::set tmp = filtered_; - filtered_.clear(); - - for (std::set::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) - { - if (s.find(*it) != s.end()) - { - filtered_.insert(*it); - } - } - } - else - { - assert(filtered_.empty()); - isFilterApplied_ = true; - ListToSet(filtered_, resources); - } - } - - public: - CandidateResources(BaseResourceFinder& finder) : + CandidateResources(ResourceFinder& finder) : finder_(finder), index_(finder.context_.GetIndex()), level_(ResourceType_Patient), @@ -184,19 +143,56 @@ } - void RestrictIdentifier(const DicomTag& tag) + void RestrictIdentifier(const IQuery& query, + const DicomTag& tag) { - Identifiers::const_iterator it = finder_.identifiers_.find(tag); - if (it != finder_.identifiers_.end()) + assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || + (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || + (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || + (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || + (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); + + std::string value; + if (!query.RestrictIdentifier(value, tag)) + { + return; + } + + LOG(INFO) << "Lookup for identifier tag " + << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")"; + + std::list resources; + index_.LookupIdentifier(resources, tag, value, level_); + + if (isFilterApplied_) { - RestrictIdentifier(it->first, it->second); + std::set s; + ListToSet(s, resources); + + std::set tmp = filtered_; + filtered_.clear(); + + for (std::set::const_iterator + it = tmp.begin(); it != tmp.end(); ++it) + { + if (s.find(*it) != s.end()) + { + filtered_.insert(*it); + } + } + } + else + { + assert(filtered_.empty()); + isFilterApplied_ = true; + ListToSet(filtered_, resources); } } - void RestrictMainDicomTags() + void RestrictMainDicomTags(const IQuery& query) { - if (finder_.mainTagsFilter_ == NULL) + if (!query.HasMainDicomTagsFilter(level_)) { return; } @@ -213,7 +209,7 @@ DicomMap mainTags; if (index_.GetMainDicomTags(mainTags, *it, level_)) { - if (finder_.mainTagsFilter_->Apply(mainTags, level_)) + if (query.FilterMainDicomTags(mainTags, level_)) { filtered_.insert(*it); } @@ -223,46 +219,46 @@ }; - BaseResourceFinder::BaseResourceFinder(ServerContext& context) : + ResourceFinder::ResourceFinder(ServerContext& context) : context_(context), - level_(ResourceType_Patient), maxResults_(0) { } - void BaseResourceFinder::ApplyAtLevel(CandidateResources& candidates, - ResourceType level) + void ResourceFinder::ApplyAtLevel(CandidateResources& candidates, + const IQuery& query, + ResourceType level) { if (level != ResourceType_Patient) { candidates.GoDown(); } - switch (level_) + switch (level) { case ResourceType_Patient: { - candidates.RestrictIdentifier(DICOM_TAG_PATIENT_ID); + candidates.RestrictIdentifier(query, DICOM_TAG_PATIENT_ID); break; } case ResourceType_Study: { - candidates.RestrictIdentifier(DICOM_TAG_STUDY_INSTANCE_UID); - candidates.RestrictIdentifier(DICOM_TAG_ACCESSION_NUMBER); + candidates.RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID); + candidates.RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER); break; } case ResourceType_Series: { - candidates.RestrictIdentifier(DICOM_TAG_SERIES_INSTANCE_UID); + candidates.RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID); break; } case ResourceType_Instance: { - candidates.RestrictIdentifier(DICOM_TAG_SOP_INSTANCE_UID); + candidates.RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID); break; } @@ -270,24 +266,11 @@ throw OrthancException(ErrorCode_InternalError); } - candidates.RestrictMainDicomTags(); + candidates.RestrictMainDicomTags(query); } - void BaseResourceFinder::SetIdentifier(const DicomTag& tag, - const std::string& value) - { - assert((level_ >= ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || - (level_ >= ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || - (level_ >= ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || - (level_ >= ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || - (level_ >= ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); - - identifiers_[tag] = value; - } - - static bool LookupOneInstance(std::string& result, ServerIndex& index, const std::string& id, @@ -317,31 +300,34 @@ } - bool BaseResourceFinder::Apply(std::list& result) + bool ResourceFinder::Apply(std::list& result, + const IQuery& query) { CandidateResources candidates(*this); - ApplyAtLevel(candidates, ResourceType_Patient); + ApplyAtLevel(candidates, query, ResourceType_Patient); + + const ResourceType level = query.GetLevel(); - if (level_ == ResourceType_Study || - level_ == ResourceType_Series || - level_ == ResourceType_Instance) + if (level == ResourceType_Study || + level == ResourceType_Series || + level == ResourceType_Instance) { - ApplyAtLevel(candidates, ResourceType_Study); + ApplyAtLevel(candidates, query, ResourceType_Study); } - if (level_ == ResourceType_Series || - level_ == ResourceType_Instance) + if (level == ResourceType_Series || + level == ResourceType_Instance) { - ApplyAtLevel(candidates, ResourceType_Series); + ApplyAtLevel(candidates, query, ResourceType_Series); } - if (level_ == ResourceType_Instance) + if (level == ResourceType_Instance) { - ApplyAtLevel(candidates, ResourceType_Instance); + ApplyAtLevel(candidates, query, ResourceType_Instance); } - if (instanceFilter_ == NULL) + if (!query.HasInstanceFilter()) { candidates.Flatten(result); @@ -368,11 +354,11 @@ try { std::string instance; - if (LookupOneInstance(instance, context_.GetIndex(), *resource, level_)) + if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) { Json::Value content; context_.ReadJson(content, instance); - if (instanceFilter_->Apply(*resource, content)) + if (query.FilterInstance(*resource, content)) { result.push_back(*resource); diff -r 4378a6636187 -r 0649c5aef34a OrthancServer/ResourceFinder.h --- a/OrthancServer/ResourceFinder.h Fri May 15 13:17:37 2015 +0200 +++ b/OrthancServer/ResourceFinder.h Fri May 15 15:34:32 2015 +0200 @@ -38,30 +38,30 @@ namespace Orthanc { - class BaseResourceFinder : public boost::noncopyable + class ResourceFinder : public boost::noncopyable { public: - class IMainTagsFilter : public boost::noncopyable + class IQuery : public boost::noncopyable { public: - virtual ~IMainTagsFilter() + virtual ~IQuery() { } - virtual bool Apply(const DicomMap& mainTags, - ResourceType level) = 0; - }; + virtual ResourceType GetLevel() const = 0; + virtual bool RestrictIdentifier(std::string& value, + DicomTag identifier) const = 0; + + virtual bool HasMainDicomTagsFilter(ResourceType level) const = 0; - class IInstanceFilter : public boost::noncopyable - { - public: - virtual ~IInstanceFilter() - { - } + virtual bool FilterMainDicomTags(const DicomMap& mainTags, + ResourceType level) const = 0; - virtual bool Apply(const std::string& instanceId, - const Json::Value& content) = 0; + virtual bool HasInstanceFilter() const = 0; + + virtual bool FilterInstance(const std::string& instanceId, + const Json::Value& content) const = 0; }; @@ -71,40 +71,14 @@ class CandidateResources; ServerContext& context_; - ResourceType level_; size_t maxResults_; - Identifiers identifiers_; - IMainTagsFilter *mainTagsFilter_; - IInstanceFilter *instanceFilter_; void ApplyAtLevel(CandidateResources& candidates, + const IQuery& query, ResourceType level); public: - BaseResourceFinder(ServerContext& context); - - ResourceType GetLevel() const - { - return level_; - } - - void SetLevel(ResourceType level) - { - level_ = level; - } - - void SetIdentifier(const DicomTag& tag, - const std::string& value); - - void SetMainTagsFilter(IMainTagsFilter& filter) - { - mainTagsFilter_ = &filter; - } - - void SetInstanceFilter(IInstanceFilter& filter) - { - instanceFilter_ = &filter; - } + ResourceFinder(ServerContext& context); void SetMaxResults(size_t value) { @@ -119,7 +93,8 @@ // Returns "true" iff. all the matching resources have been // returned. Will be "false" if the results were truncated by // "SetMaxResults()". - bool Apply(std::list& result); + bool Apply(std::list& result, + const IQuery& query); }; } diff -r 4378a6636187 -r 0649c5aef34a UnitTestsSources/DicomMapTests.cpp --- a/UnitTestsSources/DicomMapTests.cpp Fri May 15 13:17:37 2015 +0200 +++ b/UnitTestsSources/DicomMapTests.cpp Fri May 15 15:34:32 2015 +0200 @@ -83,22 +83,38 @@ TEST(DicomMap, Tags) { + std::set s; + DicomMap m; + m.GetTags(s); + ASSERT_EQ(0, s.size()); + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME)); ASSERT_FALSE(m.HasTag(0x0010, 0x0010)); m.SetValue(0x0010, 0x0010, "PatientName"); ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME)); ASSERT_TRUE(m.HasTag(0x0010, 0x0010)); + m.GetTags(s); + ASSERT_EQ(1, s.size()); + ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); + ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID)); m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); + m.GetTags(s); + ASSERT_EQ(2, s.size()); + m.Remove(DICOM_TAG_PATIENT_ID); ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException); + m.GetTags(s); + ASSERT_EQ(1, s.size()); + ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); + std::auto_ptr mm(m.Clone()); ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); diff -r 4378a6636187 -r 0649c5aef34a UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Fri May 15 13:17:37 2015 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Fri May 15 15:34:32 2015 +0200 @@ -287,3 +287,10 @@ } } } + + +TEST(FromDcmtkBridge, VR) +{ + ASSERT_TRUE(FromDcmtkBridge::IsPNValueRepresentation(DICOM_TAG_PATIENT_NAME)); + ASSERT_FALSE(FromDcmtkBridge::IsPNValueRepresentation(DICOM_TAG_PATIENT_ID)); +}