# HG changeset patch # User Sebastien Jodogne # Date 1545225611 -3600 # Node ID 54e422fe31ceac9818f710f7fca941f2a951faaa # Parent 5da6d1063d8f7a59867455f10d02ec9e0c6362ce moving LookupResource to graveyard diff -r 5da6d1063d8f -r 54e422fe31ce CMakeLists.txt --- a/CMakeLists.txt Wed Dec 19 13:58:28 2018 +0100 +++ b/CMakeLists.txt Wed Dec 19 14:20:11 2018 +0100 @@ -78,7 +78,6 @@ OrthancServer/Search/IFindConstraint.cpp OrthancServer/Search/ListConstraint.cpp OrthancServer/Search/LookupIdentifierQuery.cpp - OrthancServer/Search/LookupResource.cpp OrthancServer/Search/RangeConstraint.cpp OrthancServer/Search/SetOfResources.cpp OrthancServer/Search/ValueConstraint.cpp diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -39,7 +39,6 @@ #include "../Core/Logging.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "OrthancConfiguration.h" -#include "Search/LookupResource.h" #include "ServerToolbox.h" #include diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/OrthancMoveRequestHandler.cpp --- a/OrthancServer/OrthancMoveRequestHandler.cpp Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -226,7 +226,7 @@ const std::string& content = value.GetContent(); - std::list ids; + std::vector ids; context_.GetIndex().LookupIdentifierExact(ids, level, tag, content); if (ids.size() != 1) @@ -235,7 +235,7 @@ } else { - publicId = ids.front(); + publicId = ids[0]; return true; } } diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -40,8 +40,6 @@ #include "../../Core/HttpServer/HttpContentNegociation.h" #include "../../Core/Logging.h" #include "../OrthancConfiguration.h" -#include "../Search/DatabaseLookup.h" -#include "../Search/LookupResource.h" #include "../ServerContext.h" #include "../ServerToolbox.h" #include "../SliceOrdering.h" @@ -1228,13 +1226,12 @@ const std::string& value, ResourceType level) { - std::list tmp; + std::vector tmp; index.LookupIdentifierExact(tmp, level, tag, value); - for (std::list::const_iterator - it = tmp.begin(); it != tmp.end(); ++it) + for (size_t i = 0; i < tmp.size(); i++) { - result.push_back(std::make_pair(level, *it)); + result.push_back(std::make_pair(level, tmp[i])); } } diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/Search/LookupResource.cpp --- a/OrthancServer/Search/LookupResource.cpp Wed Dec 19 13:58:28 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,479 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., 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 "LookupResource.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/FileStorage/StorageAccessor.h" -#include "../ServerToolbox.h" -#include "../../Core/DicomParsing/FromDcmtkBridge.h" - - -namespace Orthanc -{ - static bool DoesDicomMapMatch(const DicomMap& dicom, - const DicomTag& tag, - const IFindConstraint& constraint) - { - const DicomValue* value = dicom.TestAndGetValue(tag); - - return (value != NULL && - !value->IsNull() && - !value->IsBinary() && - constraint.Match(value->GetContent())); - } - - - LookupResource::Level::Level(ResourceType level) : level_(level) - { - const DicomTag* tags = NULL; - size_t size; - - ServerToolbox::LoadIdentifiers(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - identifiers_.insert(tags[i]); - } - - DicomMap::LoadMainDicomTags(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - if (identifiers_.find(tags[i]) == identifiers_.end()) - { - mainTags_.insert(tags[i]); - } - } - } - - LookupResource::Level::~Level() - { - for (Constraints::iterator it = mainTagsConstraints_.begin(); - it != mainTagsConstraints_.end(); ++it) - { - delete it->second; - } - - for (Constraints::iterator it = identifiersConstraints_.begin(); - it != identifiersConstraints_.end(); ++it) - { - delete it->second; - } - } - - bool LookupResource::Level::Add(const DicomTag& tag, - std::auto_ptr& constraint) - { - if (identifiers_.find(tag) != identifiers_.end()) - { - if (level_ == ResourceType_Patient) - { - // The filters on the patient level must be cloned to the study level - identifiersConstraints_[tag] = constraint->Clone(); - } - else - { - identifiersConstraints_[tag] = constraint.release(); - } - - return true; - } - else if (mainTags_.find(tag) != mainTags_.end()) - { - if (level_ == ResourceType_Patient) - { - // The filters on the patient level must be cloned to the study level - mainTagsConstraints_[tag] = constraint->Clone(); - } - else - { - mainTagsConstraints_[tag] = constraint.release(); - } - - return true; - } - else - { - // This is not a main DICOM tag - return false; - } - } - - - bool LookupResource::Level::IsMatch(const DicomMap& dicom) const - { - for (Constraints::const_iterator it = identifiersConstraints_.begin(); - it != identifiersConstraints_.end(); ++it) - { - assert(it->second != NULL); - - if (!DoesDicomMapMatch(dicom, it->first, *it->second)) - { - return false; - } - } - - for (Constraints::const_iterator it = mainTagsConstraints_.begin(); - it != mainTagsConstraints_.end(); ++it) - { - assert(it->second != NULL); - - if (!DoesDicomMapMatch(dicom, it->first, *it->second)) - { - return false; - } - } - - return true; - } - - - LookupResource::LookupResource(ResourceType level) : level_(level) - { - switch (level) - { - case ResourceType_Patient: - levels_[ResourceType_Patient] = new Level(ResourceType_Patient); - break; - - case ResourceType_Instance: - levels_[ResourceType_Instance] = new Level(ResourceType_Instance); - // Do not add "break" here - - case ResourceType_Series: - levels_[ResourceType_Series] = new Level(ResourceType_Series); - // Do not add "break" here - - case ResourceType_Study: - levels_[ResourceType_Study] = new Level(ResourceType_Study); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - LookupResource::~LookupResource() - { - for (Levels::iterator it = levels_.begin(); - it != levels_.end(); ++it) - { - delete it->second; - } - - for (Constraints::iterator it = unoptimizedConstraints_.begin(); - it != unoptimizedConstraints_.end(); ++it) - { - delete it->second; - } - } - - - - bool LookupResource::AddInternal(ResourceType level, - const DicomTag& tag, - std::auto_ptr& constraint) - { - Levels::iterator it = levels_.find(level); - if (it != levels_.end()) - { - if (it->second->Add(tag, constraint)) - { - return true; - } - } - - return false; - } - - - void LookupResource::Add(const DicomTag& tag, - IFindConstraint* constraint) - { - std::auto_ptr c(constraint); - - if (!AddInternal(ResourceType_Patient, tag, c) && - !AddInternal(ResourceType_Study, tag, c) && - !AddInternal(ResourceType_Series, tag, c) && - !AddInternal(ResourceType_Instance, tag, c)) - { - unoptimizedConstraints_[tag] = c.release(); - } - } - - - static bool Match(const DicomMap& tags, - const DicomTag& tag, - const IFindConstraint& constraint) - { - const DicomValue* value = tags.TestAndGetValue(tag); - - if (value == NULL || - value->IsNull() || - value->IsBinary()) - { - return false; - } - else - { - return constraint.Match(value->GetContent()); - } - } - - - void LookupResource::Level::Apply(SetOfResources& candidates, - IDatabaseWrapper& database) const - { - // First, use the indexed identifiers - LookupIdentifierQuery query(level_); - - for (Constraints::const_iterator it = identifiersConstraints_.begin(); - it != identifiersConstraints_.end(); ++it) - { - it->second->Setup(query, it->first); - } - - query.Apply(candidates, database); - - /*{ - query.Print(std::cout); - std::list source; - candidates.Flatten(source); - printf("=> %d\n", source.size()); - }*/ - - // Secondly, filter using the main DICOM tags - if (!identifiersConstraints_.empty() || - !mainTagsConstraints_.empty()) - { - std::list source; - candidates.Flatten(source); - candidates.Clear(); - - std::list filtered; - for (std::list::const_iterator candidate = source.begin(); - candidate != source.end(); ++candidate) - { - DicomMap tags; - database.GetMainDicomTags(tags, *candidate); - - bool match = true; - - // Re-apply the identifier constraints, as their "Setup" - // method is less restrictive than their "Match" method - for (Constraints::const_iterator it = identifiersConstraints_.begin(); - match && it != identifiersConstraints_.end(); ++it) - { - if (!Match(tags, it->first, *it->second)) - { - match = false; - } - } - - for (Constraints::const_iterator it = mainTagsConstraints_.begin(); - match && it != mainTagsConstraints_.end(); ++it) - { - if (!Match(tags, it->first, *it->second)) - { - match = false; - } - } - - if (match) - { - filtered.push_back(*candidate); - } - } - - candidates.Intersect(filtered); - } - } - - - - bool LookupResource::IsMatch(const DicomMap& dicom) const - { - for (Levels::const_iterator it = levels_.begin(); it != levels_.end(); ++it) - { - if (!it->second->IsMatch(dicom)) - { - return false; - } - } - - for (Constraints::const_iterator it = unoptimizedConstraints_.begin(); - it != unoptimizedConstraints_.end(); ++it) - { - assert(it->second != NULL); - - if (!DoesDicomMapMatch(dicom, it->first, *it->second)) - { - return false; - } - } - - return true; - } - - - void LookupResource::ApplyLevel(SetOfResources& candidates, - ResourceType level, - IDatabaseWrapper& database) const - { - Levels::const_iterator it = levels_.find(level); - if (it != levels_.end()) - { - it->second->Apply(candidates, database); - } - - if (level == ResourceType_Study && - modalitiesInStudy_.get() != NULL) - { - // There is a constraint on the "ModalitiesInStudy" DICOM - // extension. Check out whether one child series has one of the - // allowed modalities - std::list allStudies, matchingStudies; - candidates.Flatten(allStudies); - - for (std::list::const_iterator - study = allStudies.begin(); study != allStudies.end(); ++study) - { - std::list childrenSeries; - database.GetChildrenInternalId(childrenSeries, *study); - - for (std::list::const_iterator - series = childrenSeries.begin(); series != childrenSeries.end(); ++series) - { - DicomMap tags; - database.GetMainDicomTags(tags, *series); - - const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY); - if (value != NULL && - !value->IsNull() && - !value->IsBinary()) - { - if (modalitiesInStudy_->Match(value->GetContent())) - { - matchingStudies.push_back(*study); - break; - } - } - } - } - - candidates.Intersect(matchingStudies); - } - } - - - void LookupResource::FindCandidates(std::list& result, - IDatabaseWrapper& database) const - { - ResourceType startingLevel; - if (level_ == ResourceType_Patient) - { - startingLevel = ResourceType_Patient; - } - else - { - startingLevel = ResourceType_Study; - } - - SetOfResources candidates(database, startingLevel); - - 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); - } - - candidates.Flatten(result); - } - - - void LookupResource::SetModalitiesInStudy(const std::string& modalities) - { - modalitiesInStudy_.reset(new ListConstraint(true /* case sensitive */)); - - std::vector items; - Toolbox::TokenizeString(items, modalities, '\\'); - - for (size_t i = 0; i < items.size(); i++) - { - modalitiesInStudy_->AddAllowedValue(items[i]); - } - } - - - void LookupResource::AddDicomConstraint(const DicomTag& tag, - const std::string& dicomQuery, - bool caseSensitive) - { - // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained - // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html - if (tag == DICOM_TAG_MODALITIES_IN_STUDY) - { - SetModalitiesInStudy(dicomQuery); - } - else - { - Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive)); - } - } - -} diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/Search/LookupResource.h --- a/OrthancServer/Search/LookupResource.h Wed Dec 19 13:58:28 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2018 Osimis S.A., 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 "ListConstraint.h" -#include "SetOfResources.h" - -#include - -namespace Orthanc -{ - class LookupResource : public boost::noncopyable - { - private: - typedef std::map Constraints; - - class Level - { - private: - ResourceType level_; - std::set identifiers_; - std::set mainTags_; - Constraints identifiersConstraints_; - Constraints mainTagsConstraints_; - - public: - Level(ResourceType level); - - ~Level(); - - bool Add(const DicomTag& tag, - std::auto_ptr& constraint); - - void Apply(SetOfResources& candidates, - IDatabaseWrapper& database) const; - - bool IsMatch(const DicomMap& dicom) const; - }; - - typedef std::map Levels; - - ResourceType level_; - Levels levels_; - Constraints unoptimizedConstraints_; // Constraints on non-main DICOM tags - std::auto_ptr modalitiesInStudy_; - - bool AddInternal(ResourceType level, - const DicomTag& tag, - std::auto_ptr& constraint); - - void ApplyLevel(SetOfResources& candidates, - ResourceType level, - IDatabaseWrapper& database) const; - - public: - LookupResource(ResourceType level); - - ~LookupResource(); - - ResourceType GetLevel() const - { - return level_; - } - - void SetModalitiesInStudy(const std::string& modalities); - - void Add(const DicomTag& tag, - IFindConstraint* constraint); // Takes ownership - - void AddDicomConstraint(const DicomTag& tag, - const std::string& dicomQuery, - bool caseSensitive); - - void FindCandidates(std::list& result, - IDatabaseWrapper& database) const; - - bool HasOnlyMainDicomTags() const - { - return unoptimizedConstraints_.empty(); - } - - bool IsMatch(const DicomMap& dicom) const; - }; -} diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/ServerContext.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -42,7 +42,6 @@ #include "../Plugins/Engine/OrthancPlugins.h" #include "OrthancConfiguration.h" #include "OrthancRestApi/OrthancRestApi.h" -#include "Search/LookupResource.h" #include "ServerJobs/OrthancJobUnserializer.h" #include "ServerToolbox.h" diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/ServerContext.h Wed Dec 19 14:20:11 2018 +0100 @@ -38,7 +38,6 @@ #include "LuaScripting.h" #include "OrthancHttpHandler.h" #include "ServerIndex.h" -#include "Search/LookupResource.h" #include "../Core/Cache/MemoryCache.h" #include "../Core/Cache/SharedArchive.h" diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/ServerIndex.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -50,7 +50,6 @@ #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "ServerContext.h" #include "DicomInstanceToStore.h" -#include "Search/LookupResource.h" #include #include @@ -2124,7 +2123,7 @@ - void ServerIndex::LookupIdentifierExact(std::list& result, + void ServerIndex::LookupIdentifierExact(std::vector& result, ResourceType level, const DicomTag& tag, const std::string& value) @@ -2137,11 +2136,17 @@ result.clear(); - boost::mutex::scoped_lock lock(mutex_); - - LookupIdentifierQuery query(level); - query.AddConstraint(tag, IdentifierConstraintType_Equal, value); - query.Apply(result, db_); + DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true); + + std::vector query; + query.push_back(DatabaseConstraint(c, level, DicomTagType_Identifier)); + + std::vector instancesId; + + { + boost::mutex::scoped_lock lock(mutex_); + db_.ApplyLookupResources(result, instancesId, query, level, 0); + } } @@ -2431,36 +2436,6 @@ } - void ServerIndex::FindCandidates(std::vector& resources, - std::vector& instances, - const ::Orthanc::LookupResource& lookup) - { - boost::mutex::scoped_lock lock(mutex_); - - std::list tmp; - lookup.FindCandidates(tmp, db_); - - resources.resize(tmp.size()); - instances.resize(tmp.size()); - - size_t pos = 0; - for (std::list::const_iterator - it = tmp.begin(); it != tmp.end(); ++it, pos++) - { - assert(db_.GetResourceType(*it) == lookup.GetLevel()); - - int64_t instance; - if (!ServerToolbox::FindOneChildInstance(instance, db_, *it, lookup.GetLevel())) - { - throw OrthancException(ErrorCode_InternalError); - } - - resources[pos] = db_.GetPublicId(*it); - instances[pos] = db_.GetPublicId(instance); - } - } - - bool ServerIndex::LookupParent(std::string& target, const std::string& publicId, ResourceType parentType) diff -r 5da6d1063d8f -r 54e422fe31ce OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Wed Dec 19 13:58:28 2018 +0100 +++ b/OrthancServer/ServerIndex.h Wed Dec 19 14:20:11 2018 +0100 @@ -255,7 +255,7 @@ /* out */ uint64_t& dicomUncompressedSize, const std::string& publicId); - void LookupIdentifierExact(std::list& result, + void LookupIdentifierExact(std::vector& result, ResourceType level, const DicomTag& tag, const std::string& value); @@ -289,10 +289,6 @@ unsigned int GetDatabaseVersion(); - void FindCandidates(std::vector& resources, - std::vector& instances, - const ::Orthanc::LookupResource& lookup); - bool LookupParent(std::string& target, const std::string& publicId, ResourceType parentType); diff -r 5da6d1063d8f -r 54e422fe31ce Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Wed Dec 19 13:58:28 2018 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -1644,7 +1644,7 @@ throw OrthancException(ErrorCode_InternalError); } - std::list result; + std::vector result; { PImpl::ServerContextLock lock(*pimpl_); @@ -1653,7 +1653,7 @@ if (result.size() == 1) { - *p.result = CopyString(result.front()); + *p.result = CopyString(result[0]); } else { diff -r 5da6d1063d8f -r 54e422fe31ce Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp Wed Dec 19 14:20:11 2018 +0100 @@ -0,0 +1,479 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "LookupResource.h" + +#include "../../Core/OrthancException.h" +#include "../../Core/FileStorage/StorageAccessor.h" +#include "../ServerToolbox.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" + + +namespace Orthanc +{ + static bool DoesDicomMapMatch(const DicomMap& dicom, + const DicomTag& tag, + const IFindConstraint& constraint) + { + const DicomValue* value = dicom.TestAndGetValue(tag); + + return (value != NULL && + !value->IsNull() && + !value->IsBinary() && + constraint.Match(value->GetContent())); + } + + + LookupResource::Level::Level(ResourceType level) : level_(level) + { + const DicomTag* tags = NULL; + size_t size; + + ServerToolbox::LoadIdentifiers(tags, size, level); + + for (size_t i = 0; i < size; i++) + { + identifiers_.insert(tags[i]); + } + + DicomMap::LoadMainDicomTags(tags, size, level); + + for (size_t i = 0; i < size; i++) + { + if (identifiers_.find(tags[i]) == identifiers_.end()) + { + mainTags_.insert(tags[i]); + } + } + } + + LookupResource::Level::~Level() + { + for (Constraints::iterator it = mainTagsConstraints_.begin(); + it != mainTagsConstraints_.end(); ++it) + { + delete it->second; + } + + for (Constraints::iterator it = identifiersConstraints_.begin(); + it != identifiersConstraints_.end(); ++it) + { + delete it->second; + } + } + + bool LookupResource::Level::Add(const DicomTag& tag, + std::auto_ptr& constraint) + { + if (identifiers_.find(tag) != identifiers_.end()) + { + if (level_ == ResourceType_Patient) + { + // The filters on the patient level must be cloned to the study level + identifiersConstraints_[tag] = constraint->Clone(); + } + else + { + identifiersConstraints_[tag] = constraint.release(); + } + + return true; + } + else if (mainTags_.find(tag) != mainTags_.end()) + { + if (level_ == ResourceType_Patient) + { + // The filters on the patient level must be cloned to the study level + mainTagsConstraints_[tag] = constraint->Clone(); + } + else + { + mainTagsConstraints_[tag] = constraint.release(); + } + + return true; + } + else + { + // This is not a main DICOM tag + return false; + } + } + + + bool LookupResource::Level::IsMatch(const DicomMap& dicom) const + { + for (Constraints::const_iterator it = identifiersConstraints_.begin(); + it != identifiersConstraints_.end(); ++it) + { + assert(it->second != NULL); + + if (!DoesDicomMapMatch(dicom, it->first, *it->second)) + { + return false; + } + } + + for (Constraints::const_iterator it = mainTagsConstraints_.begin(); + it != mainTagsConstraints_.end(); ++it) + { + assert(it->second != NULL); + + if (!DoesDicomMapMatch(dicom, it->first, *it->second)) + { + return false; + } + } + + return true; + } + + + LookupResource::LookupResource(ResourceType level) : level_(level) + { + switch (level) + { + case ResourceType_Patient: + levels_[ResourceType_Patient] = new Level(ResourceType_Patient); + break; + + case ResourceType_Instance: + levels_[ResourceType_Instance] = new Level(ResourceType_Instance); + // Do not add "break" here + + case ResourceType_Series: + levels_[ResourceType_Series] = new Level(ResourceType_Series); + // Do not add "break" here + + case ResourceType_Study: + levels_[ResourceType_Study] = new Level(ResourceType_Study); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + + LookupResource::~LookupResource() + { + for (Levels::iterator it = levels_.begin(); + it != levels_.end(); ++it) + { + delete it->second; + } + + for (Constraints::iterator it = unoptimizedConstraints_.begin(); + it != unoptimizedConstraints_.end(); ++it) + { + delete it->second; + } + } + + + + bool LookupResource::AddInternal(ResourceType level, + const DicomTag& tag, + std::auto_ptr& constraint) + { + Levels::iterator it = levels_.find(level); + if (it != levels_.end()) + { + if (it->second->Add(tag, constraint)) + { + return true; + } + } + + return false; + } + + + void LookupResource::Add(const DicomTag& tag, + IFindConstraint* constraint) + { + std::auto_ptr c(constraint); + + if (!AddInternal(ResourceType_Patient, tag, c) && + !AddInternal(ResourceType_Study, tag, c) && + !AddInternal(ResourceType_Series, tag, c) && + !AddInternal(ResourceType_Instance, tag, c)) + { + unoptimizedConstraints_[tag] = c.release(); + } + } + + + static bool Match(const DicomMap& tags, + const DicomTag& tag, + const IFindConstraint& constraint) + { + const DicomValue* value = tags.TestAndGetValue(tag); + + if (value == NULL || + value->IsNull() || + value->IsBinary()) + { + return false; + } + else + { + return constraint.Match(value->GetContent()); + } + } + + + void LookupResource::Level::Apply(SetOfResources& candidates, + IDatabaseWrapper& database) const + { + // First, use the indexed identifiers + LookupIdentifierQuery query(level_); + + for (Constraints::const_iterator it = identifiersConstraints_.begin(); + it != identifiersConstraints_.end(); ++it) + { + it->second->Setup(query, it->first); + } + + query.Apply(candidates, database); + + /*{ + query.Print(std::cout); + std::list source; + candidates.Flatten(source); + printf("=> %d\n", source.size()); + }*/ + + // Secondly, filter using the main DICOM tags + if (!identifiersConstraints_.empty() || + !mainTagsConstraints_.empty()) + { + std::list source; + candidates.Flatten(source); + candidates.Clear(); + + std::list filtered; + for (std::list::const_iterator candidate = source.begin(); + candidate != source.end(); ++candidate) + { + DicomMap tags; + database.GetMainDicomTags(tags, *candidate); + + bool match = true; + + // Re-apply the identifier constraints, as their "Setup" + // method is less restrictive than their "Match" method + for (Constraints::const_iterator it = identifiersConstraints_.begin(); + match && it != identifiersConstraints_.end(); ++it) + { + if (!Match(tags, it->first, *it->second)) + { + match = false; + } + } + + for (Constraints::const_iterator it = mainTagsConstraints_.begin(); + match && it != mainTagsConstraints_.end(); ++it) + { + if (!Match(tags, it->first, *it->second)) + { + match = false; + } + } + + if (match) + { + filtered.push_back(*candidate); + } + } + + candidates.Intersect(filtered); + } + } + + + + bool LookupResource::IsMatch(const DicomMap& dicom) const + { + for (Levels::const_iterator it = levels_.begin(); it != levels_.end(); ++it) + { + if (!it->second->IsMatch(dicom)) + { + return false; + } + } + + for (Constraints::const_iterator it = unoptimizedConstraints_.begin(); + it != unoptimizedConstraints_.end(); ++it) + { + assert(it->second != NULL); + + if (!DoesDicomMapMatch(dicom, it->first, *it->second)) + { + return false; + } + } + + return true; + } + + + void LookupResource::ApplyLevel(SetOfResources& candidates, + ResourceType level, + IDatabaseWrapper& database) const + { + Levels::const_iterator it = levels_.find(level); + if (it != levels_.end()) + { + it->second->Apply(candidates, database); + } + + if (level == ResourceType_Study && + modalitiesInStudy_.get() != NULL) + { + // There is a constraint on the "ModalitiesInStudy" DICOM + // extension. Check out whether one child series has one of the + // allowed modalities + std::list allStudies, matchingStudies; + candidates.Flatten(allStudies); + + for (std::list::const_iterator + study = allStudies.begin(); study != allStudies.end(); ++study) + { + std::list childrenSeries; + database.GetChildrenInternalId(childrenSeries, *study); + + for (std::list::const_iterator + series = childrenSeries.begin(); series != childrenSeries.end(); ++series) + { + DicomMap tags; + database.GetMainDicomTags(tags, *series); + + const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY); + if (value != NULL && + !value->IsNull() && + !value->IsBinary()) + { + if (modalitiesInStudy_->Match(value->GetContent())) + { + matchingStudies.push_back(*study); + break; + } + } + } + } + + candidates.Intersect(matchingStudies); + } + } + + + void LookupResource::FindCandidates(std::list& result, + IDatabaseWrapper& database) const + { + ResourceType startingLevel; + if (level_ == ResourceType_Patient) + { + startingLevel = ResourceType_Patient; + } + else + { + startingLevel = ResourceType_Study; + } + + SetOfResources candidates(database, startingLevel); + + 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); + } + + candidates.Flatten(result); + } + + + void LookupResource::SetModalitiesInStudy(const std::string& modalities) + { + modalitiesInStudy_.reset(new ListConstraint(true /* case sensitive */)); + + std::vector items; + Toolbox::TokenizeString(items, modalities, '\\'); + + for (size_t i = 0; i < items.size(); i++) + { + modalitiesInStudy_->AddAllowedValue(items[i]); + } + } + + + void LookupResource::AddDicomConstraint(const DicomTag& tag, + const std::string& dicomQuery, + bool caseSensitive) + { + // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained + // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html + if (tag == DICOM_TAG_MODALITIES_IN_STUDY) + { + SetModalitiesInStudy(dicomQuery); + } + else + { + Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive)); + } + } + +} diff -r 5da6d1063d8f -r 54e422fe31ce Resources/Graveyard/DatabaseOptimizations/LookupResource.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Graveyard/DatabaseOptimizations/LookupResource.h Wed Dec 19 14:20:11 2018 +0100 @@ -0,0 +1,115 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "ListConstraint.h" +#include "SetOfResources.h" + +#include + +namespace Orthanc +{ + class LookupResource : public boost::noncopyable + { + private: + typedef std::map Constraints; + + class Level + { + private: + ResourceType level_; + std::set identifiers_; + std::set mainTags_; + Constraints identifiersConstraints_; + Constraints mainTagsConstraints_; + + public: + Level(ResourceType level); + + ~Level(); + + bool Add(const DicomTag& tag, + std::auto_ptr& constraint); + + void Apply(SetOfResources& candidates, + IDatabaseWrapper& database) const; + + bool IsMatch(const DicomMap& dicom) const; + }; + + typedef std::map Levels; + + ResourceType level_; + Levels levels_; + Constraints unoptimizedConstraints_; // Constraints on non-main DICOM tags + std::auto_ptr modalitiesInStudy_; + + bool AddInternal(ResourceType level, + const DicomTag& tag, + std::auto_ptr& constraint); + + void ApplyLevel(SetOfResources& candidates, + ResourceType level, + IDatabaseWrapper& database) const; + + public: + LookupResource(ResourceType level); + + ~LookupResource(); + + ResourceType GetLevel() const + { + return level_; + } + + void SetModalitiesInStudy(const std::string& modalities); + + void Add(const DicomTag& tag, + IFindConstraint* constraint); // Takes ownership + + void AddDicomConstraint(const DicomTag& tag, + const std::string& dicomQuery, + bool caseSensitive); + + void FindCandidates(std::list& result, + IDatabaseWrapper& database) const; + + bool HasOnlyMainDicomTags() const + { + return unoptimizedConstraints_.empty(); + } + + bool IsMatch(const DicomMap& dicom) const; + }; +}