/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, 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.
*
* 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 "DatabaseLookup.h"
#include "../../../../OrthancFramework/Sources/OrthancException.h"
#include "../../Search/DicomTagConstraint.h"
#include "../../ServerToolbox.h"
#include "SetOfResources.h"
namespace Orthanc
{
namespace Compatibility
{
namespace
{
// Anonymous namespace to avoid clashes between compiler modules
class MainTagsConstraints : boost::noncopyable
{
private:
std::vector constraints_;
public:
~MainTagsConstraints()
{
for (size_t i = 0; i < constraints_.size(); i++)
{
assert(constraints_[i] != NULL);
delete constraints_[i];
}
}
void Reserve(size_t n)
{
constraints_.reserve(n);
}
size_t GetSize() const
{
return constraints_.size();
}
DicomTagConstraint& GetConstraint(size_t i) const
{
if (i >= constraints_.size())
{
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
else
{
assert(constraints_[i] != NULL);
return *constraints_[i];
}
}
void Add(const DatabaseDicomTagConstraint& constraint)
{
constraints_.push_back(new DicomTagConstraint(constraint));
}
};
}
static void ApplyIdentifierConstraint(SetOfResources& candidates,
ILookupResources& compatibility,
const DatabaseDicomTagConstraint& constraint,
ResourceType level)
{
std::list matches;
switch (constraint.GetConstraintType())
{
case ConstraintType_Equal:
compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
IdentifierConstraintType_Equal, constraint.GetSingleValue());
break;
case ConstraintType_SmallerOrEqual:
compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
IdentifierConstraintType_SmallerOrEqual, constraint.GetSingleValue());
break;
case ConstraintType_GreaterOrEqual:
compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
IdentifierConstraintType_GreaterOrEqual, constraint.GetSingleValue());
break;
case ConstraintType_Wildcard:
compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
IdentifierConstraintType_Wildcard, constraint.GetSingleValue());
break;
case ConstraintType_List:
for (size_t i = 0; i < constraint.GetValuesCount(); i++)
{
std::list tmp;
compatibility.LookupIdentifier(tmp, level, constraint.GetTag(),
IdentifierConstraintType_Wildcard, constraint.GetValue(i));
matches.splice(matches.end(), tmp);
}
break;
default:
throw OrthancException(ErrorCode_InternalError);
}
candidates.Intersect(matches);
}
static void ApplyIdentifierRange(SetOfResources& candidates,
ILookupResources& compatibility,
const DatabaseDicomTagConstraint& smaller,
const DatabaseDicomTagConstraint& greater,
ResourceType level)
{
assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual &&
greater.GetConstraintType() == ConstraintType_GreaterOrEqual &&
smaller.GetTag() == greater.GetTag() &&
ServerToolbox::IsIdentifier(smaller.GetTag(), level));
std::list matches;
compatibility.LookupIdentifierRange(matches, level, smaller.GetTag(),
greater.GetSingleValue(), smaller.GetSingleValue());
candidates.Intersect(matches);
}
static void ApplyLevel(SetOfResources& candidates,
IDatabaseWrapper::ITransaction& transaction,
ILookupResources& compatibility,
const DatabaseDicomTagConstraints& lookup,
ResourceType level)
{
typedef std::set SetOfConstraints;
typedef std::map Identifiers;
// (1) Select which constraints apply to this level, and split
// them between "identifier tags" constraints and "main DICOM
// tags" constraints
Identifiers identifiers;
SetOfConstraints mainTags;
for (size_t i = 0; i < lookup.GetSize(); i++)
{
const DatabaseDicomTagConstraint& constraint = lookup.GetConstraint(i);
if (constraint.GetLevel() == level)
{
if (constraint.IsIdentifier())
{
identifiers[constraint.GetTag()].insert(&constraint);
}
else
{
mainTags.insert(&constraint);
}
}
}
// (2) Apply the constraints over the identifiers
for (Identifiers::const_iterator it = identifiers.begin();
it != identifiers.end(); ++it)
{
// Check whether some range constraint over identifiers is
// present at this level
const DatabaseDicomTagConstraint* smaller = NULL;
const DatabaseDicomTagConstraint* greater = NULL;
for (SetOfConstraints::const_iterator it2 = it->second.begin();
it2 != it->second.end(); ++it2)
{
assert(*it2 != NULL);
if ((*it2)->GetConstraintType() == ConstraintType_SmallerOrEqual)
{
smaller = *it2;
}
if ((*it2)->GetConstraintType() == ConstraintType_GreaterOrEqual)
{
greater = *it2;
}
}
if (smaller != NULL &&
greater != NULL)
{
// There is a range constraint: Apply it, as it is more efficient
ApplyIdentifierRange(candidates, compatibility, *smaller, *greater, level);
}
else
{
smaller = NULL;
greater = NULL;
}
for (SetOfConstraints::const_iterator it2 = it->second.begin();
it2 != it->second.end(); ++it2)
{
// Check to avoid applying twice the range constraint
if (*it2 != smaller &&
*it2 != greater)
{
ApplyIdentifierConstraint(candidates, compatibility, **it2, level);
}
}
}
// (3) Apply the constraints over the main DICOM tags (no index
// here, so this is less efficient than filtering over the
// identifiers)
if (!mainTags.empty())
{
MainTagsConstraints c;
c.Reserve(mainTags.size());
for (SetOfConstraints::const_iterator it = mainTags.begin();
it != mainTags.end(); ++it)
{
assert(*it != NULL);
c.Add(**it);
}
std::list source;
candidates.Flatten(compatibility, source);
candidates.Clear();
std::list filtered;
for (std::list::const_iterator candidate = source.begin();
candidate != source.end(); ++candidate)
{
DicomMap tags;
transaction.GetMainDicomTags(tags, *candidate);
bool match = true;
for (size_t i = 0; i < c.GetSize(); i++)
{
if (!c.GetConstraint(i).IsMatch(tags))
{
match = false;
break;
}
}
if (match)
{
filtered.push_back(*candidate);
}
}
candidates.Intersect(filtered);
}
}
static std::string GetOneInstance(IDatabaseWrapper::ITransaction& compatibility,
int64_t resource,
ResourceType level)
{
for (int i = level; i < ResourceType_Instance; i++)
{
assert(compatibility.GetResourceType(resource) == static_cast(i));
std::list children;
compatibility.GetChildrenInternalId(children, resource);
if (children.empty())
{
throw OrthancException(ErrorCode_Database);
}
resource = children.front();
}
return compatibility.GetPublicId(resource);
}
void DatabaseLookup::ApplyLookupResources(std::list& resourcesId,
std::list* instancesId,
const DatabaseDicomTagConstraints& lookup,
ResourceType queryLevel,
size_t limit)
{
// This is a re-implementation of
// "../../../Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp"
assert(ResourceType_Patient < ResourceType_Study &&
ResourceType_Study < ResourceType_Series &&
ResourceType_Series < ResourceType_Instance);
ResourceType upperLevel = queryLevel;
ResourceType lowerLevel = queryLevel;
for (size_t i = 0; i < lookup.GetSize(); i++)
{
ResourceType level = lookup.GetConstraint(i).GetLevel();
if (level < upperLevel)
{
upperLevel = level;
}
if (level > lowerLevel)
{
lowerLevel = level;
}
}
assert(upperLevel <= queryLevel &&
queryLevel <= lowerLevel);
SetOfResources candidates(transaction_, upperLevel);
for (int level = upperLevel; level <= lowerLevel; level++)
{
ApplyLevel(candidates, transaction_, compatibility_, lookup, static_cast(level));
if (level != lowerLevel)
{
candidates.GoDown();
}
}
std::list resources;
candidates.Flatten(compatibility_, resources);
// Climb up, up to queryLevel
for (int level = lowerLevel; level > queryLevel; level--)
{
std::list parents;
for (std::list::const_iterator
it = resources.begin(); it != resources.end(); ++it)
{
int64_t parent;
if (transaction_.LookupParent(parent, *it))
{
parents.push_back(parent);
}
}
resources.swap(parents);
}
// Apply the limit, if given
if (limit != 0 &&
resources.size() > limit)
{
resources.resize(limit);
}
// Get the public ID of all the selected resources
size_t pos = 0;
for (std::list::const_iterator
it = resources.begin(); it != resources.end(); ++it, pos++)
{
assert(transaction_.GetResourceType(*it) == queryLevel);
const std::string resource = transaction_.GetPublicId(*it);
resourcesId.push_back(resource);
if (instancesId != NULL)
{
if (queryLevel == ResourceType_Instance)
{
// The resource is itself the instance
instancesId->push_back(resource);
}
else
{
// Collect one child instance for each of the selected resources
instancesId->push_back(GetOneInstance(transaction_, *it, queryLevel));
}
}
}
}
}
}