# HG changeset patch # User Sebastien Jodogne # Date 1715168243 -7200 # Node ID 3d0aa94b44b30e44dd0e36f90c1a935d112c3ff9 # Parent a3732285f8b636e2329a7dfaf1213b83f3fe1f30 reorganization diff -r a3732285f8b6 -r 3d0aa94b44b3 OrthancServer/CMakeLists.txt --- a/OrthancServer/CMakeLists.txt Wed May 08 12:58:16 2024 +0200 +++ b/OrthancServer/CMakeLists.txt Wed May 08 13:37:23 2024 +0200 @@ -122,6 +122,7 @@ ${CMAKE_SOURCE_DIR}/Sources/OrthancRestApi/OrthancRestSystem.cpp ${CMAKE_SOURCE_DIR}/Sources/OrthancWebDav.cpp ${CMAKE_SOURCE_DIR}/Sources/QueryRetrieveHandler.cpp + ${CMAKE_SOURCE_DIR}/Sources/ResourceFinder.cpp ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseConstraint.cpp ${CMAKE_SOURCE_DIR}/Sources/Search/DatabaseLookup.cpp ${CMAKE_SOURCE_DIR}/Sources/Search/DicomTagConstraint.cpp diff -r a3732285f8b6 -r 3d0aa94b44b3 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed May 08 12:58:16 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed May 08 13:37:23 2024 +0200 @@ -21,6 +21,8 @@ #include "../PrecompiledHeadersServer.h" +#include "../ResourceFinder.h" + #include "OrthancRestApi.h" #include "../../../OrthancFramework/Sources/Compression/GzipCompressor.h" @@ -198,385 +200,6 @@ } - class ResourceFinder : public boost::noncopyable - { - private: - FindRequest request_; - bool expand_; - std::set requestedTags_; - DicomToJsonFormat format_; - bool includeAllMetadata_; // Same as: ExpandResourceFlags_IncludeAllMetadata - - SeriesStatus GetSeriesStatus(uint32_t& expectedNumberOfInstances, - const FindResponse::Resource& resource) const - { - if (request_.GetLevel() != ResourceType_Series) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - std::string s; - if (!resource.LookupMetadata(s, ResourceType_Series, MetadataType_Series_ExpectedNumberOfInstances) || - !SerializationToolbox::ParseUnsignedInteger32(expectedNumberOfInstances, s)) - { - return SeriesStatus_Unknown; - } - - std::list values; - if (!resource.LookupChildrenMetadata(values, MetadataType_Instance_IndexInSeries)) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - std::set instances; - - for (std::list::const_iterator - it = values.begin(); it != values.end(); ++it) - { - int64_t index; - - if (!SerializationToolbox::ParseInteger64(index, *it)) - { - return SeriesStatus_Unknown; - } - - if (index <= 0 || - index > static_cast(expectedNumberOfInstances)) - { - // Out-of-range instance index - return SeriesStatus_Inconsistent; - } - - if (instances.find(index) != instances.end()) - { - // Twice the same instance index - return SeriesStatus_Inconsistent; - } - - instances.insert(index); - } - - if (instances.size() == static_cast(expectedNumberOfInstances)) - { - return SeriesStatus_Complete; - } - else - { - return SeriesStatus_Missing; - } - } - - - void Expand(Json::Value& target, - const FindResponse::Resource& resource, - ServerIndex& index) const - { - /** - * This method closely follows "SerializeExpandedResource()" in - * "ServerContext.cpp" from Orthanc 1.12.3. - **/ - - if (resource.GetLevel() != request_.GetLevel()) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (!requestedTags_.empty()) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - target = Json::objectValue; - - target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true); - target["ID"] = resource.GetIdentifier(); - - switch (resource.GetLevel()) - { - case ResourceType_Patient: - break; - - case ResourceType_Study: - target["ParentPatient"] = resource.GetParentIdentifier(); - break; - - case ResourceType_Series: - target["ParentStudy"] = resource.GetParentIdentifier(); - break; - - case ResourceType_Instance: - target["ParentSeries"] = resource.GetParentIdentifier(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (resource.GetLevel() != ResourceType_Instance) - { - const std::set& children = resource.GetChildrenIdentifiers(); - - Json::Value c = Json::arrayValue; - for (std::set::const_iterator - it = children.begin(); it != children.end(); ++it) - { - c.append(*it); - } - - switch (resource.GetLevel()) - { - case ResourceType_Patient: - target["Studies"] = c; - break; - - case ResourceType_Study: - target["Series"] = c; - break; - - case ResourceType_Series: - target["Instances"] = c; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - switch (resource.GetLevel()) - { - case ResourceType_Patient: - case ResourceType_Study: - break; - - case ResourceType_Series: - { - uint32_t expectedNumberOfInstances; - SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource); - - target["Status"] = EnumerationToString(status); - - static const char* EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances"; - - if (status == SeriesStatus_Unknown) - { - target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue; - } - else - { - target[EXPECTED_NUMBER_OF_INSTANCES] = expectedNumberOfInstances; - } - - break; - } - - case ResourceType_Instance: - { - FileInfo info; - if (resource.LookupAttachment(info, FileContentType_Dicom)) - { - target["FileSize"] = static_cast(info.GetUncompressedSize()); - target["FileUuid"] = info.GetUuid(); - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - - static const char* INDEX_IN_SERIES = "IndexInSeries"; - - std::string s; - uint32_t index; - if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) && - SerializationToolbox::ParseUnsignedInteger32(index, s)) - { - target[INDEX_IN_SERIES] = index; - } - else - { - target[INDEX_IN_SERIES] = Json::nullValue; - } - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string s; - if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_AnonymizedFrom)) - { - target["AnonymizedFrom"] = s; - } - - if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_ModifiedFrom)) - { - target["ModifiedFrom"] = s; - } - - if (resource.GetLevel() == ResourceType_Patient || - resource.GetLevel() == ResourceType_Study || - resource.GetLevel() == ResourceType_Series) - { - target["IsStable"] = !index.IsUnstableResource(resource.GetLevel(), resource.GetInternalId()); - - if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_LastUpdate)) - { - target["LastUpdate"] = s; - } - } - - { - static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; - static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; - - // TODO-FIND : (expandFlags & ExpandResourceFlags_IncludeMainDicomTags) - DicomMap allMainDicomTags; - resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel()); - - DicomMap levelMainDicomTags; - allMainDicomTags.ExtractResourceInformation(levelMainDicomTags, resource.GetLevel()); - - target[MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], levelMainDicomTags, format_); - - if (resource.GetLevel() == ResourceType_Study) - { - DicomMap patientMainDicomTags; - allMainDicomTags.ExtractPatientInformation(patientMainDicomTags); - - target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format_); - } - - /* - TODO-FIND - - if (!requestedTags_.empty()) - { - static const char* const REQUESTED_TAGS = "RequestedTags"; - - DicomMap tags; - resource.GetMainDicomTags().ExtractTags(tags, requestedTags); - - target[REQUESTED_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format); - } - */ - } - - { - Json::Value labels = Json::arrayValue; - - for (std::set::const_iterator - it = resource.GetLabels().begin(); it != resource.GetLabels().end(); ++it) - { - labels.append(*it); - } - - target["Labels"] = labels; - } - - if (includeAllMetadata_) // new in Orthanc 1.12.4 - { - const std::map& m = resource.GetMetadata(resource.GetLevel()); - - Json::Value metadata = Json::objectValue; - - for (std::map::const_iterator it = m.begin(); it != m.end(); ++it) - { - metadata[EnumerationToString(it->first)] = it->second; - } - - target["Metadata"] = metadata; - } - } - - - public: - ResourceFinder(ResourceType level, - bool expand) : - request_(level), - expand_(expand), - format_(DicomToJsonFormat_Human), - includeAllMetadata_(false) - { - if (expand) - { - request_.SetRetrieveMainDicomTags(level, true); - request_.SetRetrieveMetadata(level, true); - request_.SetRetrieveLabels(true); - - if (level == ResourceType_Series) - { - request_.AddRetrieveChildrenMetadata(MetadataType_Instance_IndexInSeries); // required for the SeriesStatus - } - - if (level == ResourceType_Instance) - { - request_.SetRetrieveAttachments(true); // for FileSize & FileUuid - } - else - { - request_.SetRetrieveChildrenIdentifiers(true); - } - - if (level != ResourceType_Patient) - { - request_.SetRetrieveParentIdentifier(true); - } - } - } - - void SetRequestedTags(const std::set& tags) - { - requestedTags_ = tags; - } - - void SetFormat(DicomToJsonFormat format) - { - format_ = format; - } - - void SetLimits(uint64_t since, - uint64_t count) - { - request_.SetLimits(since, count); - } - - void SetIncludeAllMetadata(bool include) - { - includeAllMetadata_ = include; - } - - void Execute(Json::Value& target, - ServerContext& context) - { - FindResponse response; - context.GetIndex().ExecuteFind(response, request_); - - target = Json::arrayValue; - - if (expand_) - { - for (size_t i = 0; i < response.GetSize(); i++) - { - Json::Value item; - Expand(item, response.GetResource(i), context.GetIndex()); - target.append(item); - } - } - else - { - for (size_t i = 0; i < response.GetSize(); i++) - { - target.append(response.GetResource(i).GetIdentifier()); - } - } - } - }; - - template static void ListResources(RestApiGetCall& call) { diff -r a3732285f8b6 -r 3d0aa94b44b3 OrthancServer/Sources/ResourceFinder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/ResourceFinder.cpp Wed May 08 13:37:23 2024 +0200 @@ -0,0 +1,381 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 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 "ResourceFinder.h" + +#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" +#include "../../OrthancFramework/Sources/OrthancException.h" +#include "../../OrthancFramework/Sources/SerializationToolbox.h" +#include "ServerContext.h" +#include "ServerIndex.h" + + +namespace Orthanc +{ + SeriesStatus ResourceFinder::GetSeriesStatus(uint32_t& expectedNumberOfInstances, + const FindResponse::Resource& resource) const + { + if (request_.GetLevel() != ResourceType_Series) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + std::string s; + if (!resource.LookupMetadata(s, ResourceType_Series, MetadataType_Series_ExpectedNumberOfInstances) || + !SerializationToolbox::ParseUnsignedInteger32(expectedNumberOfInstances, s)) + { + return SeriesStatus_Unknown; + } + + std::list values; + if (!resource.LookupChildrenMetadata(values, MetadataType_Instance_IndexInSeries)) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + std::set instances; + + for (std::list::const_iterator + it = values.begin(); it != values.end(); ++it) + { + int64_t index; + + if (!SerializationToolbox::ParseInteger64(index, *it)) + { + return SeriesStatus_Unknown; + } + + if (index <= 0 || + index > static_cast(expectedNumberOfInstances)) + { + // Out-of-range instance index + return SeriesStatus_Inconsistent; + } + + if (instances.find(index) != instances.end()) + { + // Twice the same instance index + return SeriesStatus_Inconsistent; + } + + instances.insert(index); + } + + if (instances.size() == static_cast(expectedNumberOfInstances)) + { + return SeriesStatus_Complete; + } + else + { + return SeriesStatus_Missing; + } + } + + + void ResourceFinder::Expand(Json::Value& target, + const FindResponse::Resource& resource, + ServerIndex& index) const + { + /** + * This method closely follows "SerializeExpandedResource()" in + * "ServerContext.cpp" from Orthanc 1.12.3. + **/ + + if (resource.GetLevel() != request_.GetLevel()) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (!requestedTags_.empty()) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + target = Json::objectValue; + + target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true); + target["ID"] = resource.GetIdentifier(); + + switch (resource.GetLevel()) + { + case ResourceType_Patient: + break; + + case ResourceType_Study: + target["ParentPatient"] = resource.GetParentIdentifier(); + break; + + case ResourceType_Series: + target["ParentStudy"] = resource.GetParentIdentifier(); + break; + + case ResourceType_Instance: + target["ParentSeries"] = resource.GetParentIdentifier(); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (resource.GetLevel() != ResourceType_Instance) + { + const std::set& children = resource.GetChildrenIdentifiers(); + + Json::Value c = Json::arrayValue; + for (std::set::const_iterator + it = children.begin(); it != children.end(); ++it) + { + c.append(*it); + } + + switch (resource.GetLevel()) + { + case ResourceType_Patient: + target["Studies"] = c; + break; + + case ResourceType_Study: + target["Series"] = c; + break; + + case ResourceType_Series: + target["Instances"] = c; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + switch (resource.GetLevel()) + { + case ResourceType_Patient: + case ResourceType_Study: + break; + + case ResourceType_Series: + { + uint32_t expectedNumberOfInstances; + SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource); + + target["Status"] = EnumerationToString(status); + + static const char* EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances"; + + if (status == SeriesStatus_Unknown) + { + target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue; + } + else + { + target[EXPECTED_NUMBER_OF_INSTANCES] = expectedNumberOfInstances; + } + + break; + } + + case ResourceType_Instance: + { + FileInfo info; + if (resource.LookupAttachment(info, FileContentType_Dicom)) + { + target["FileSize"] = static_cast(info.GetUncompressedSize()); + target["FileUuid"] = info.GetUuid(); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + + static const char* INDEX_IN_SERIES = "IndexInSeries"; + + std::string s; + uint32_t index; + if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) && + SerializationToolbox::ParseUnsignedInteger32(index, s)) + { + target[INDEX_IN_SERIES] = index; + } + else + { + target[INDEX_IN_SERIES] = Json::nullValue; + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + std::string s; + if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_AnonymizedFrom)) + { + target["AnonymizedFrom"] = s; + } + + if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_ModifiedFrom)) + { + target["ModifiedFrom"] = s; + } + + if (resource.GetLevel() == ResourceType_Patient || + resource.GetLevel() == ResourceType_Study || + resource.GetLevel() == ResourceType_Series) + { + target["IsStable"] = !index.IsUnstableResource(resource.GetLevel(), resource.GetInternalId()); + + if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_LastUpdate)) + { + target["LastUpdate"] = s; + } + } + + { + static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; + static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; + + // TODO-FIND : (expandFlags & ExpandResourceFlags_IncludeMainDicomTags) + DicomMap allMainDicomTags; + resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel()); + + DicomMap levelMainDicomTags; + allMainDicomTags.ExtractResourceInformation(levelMainDicomTags, resource.GetLevel()); + + target[MAIN_DICOM_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], levelMainDicomTags, format_); + + if (resource.GetLevel() == ResourceType_Study) + { + DicomMap patientMainDicomTags; + allMainDicomTags.ExtractPatientInformation(patientMainDicomTags); + + target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format_); + } + + /* + TODO-FIND + + if (!requestedTags_.empty()) + { + static const char* const REQUESTED_TAGS = "RequestedTags"; + + DicomMap tags; + resource.GetMainDicomTags().ExtractTags(tags, requestedTags); + + target[REQUESTED_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format); + } + */ + } + + { + Json::Value labels = Json::arrayValue; + + for (std::set::const_iterator + it = resource.GetLabels().begin(); it != resource.GetLabels().end(); ++it) + { + labels.append(*it); + } + + target["Labels"] = labels; + } + + if (includeAllMetadata_) // new in Orthanc 1.12.4 + { + const std::map& m = resource.GetMetadata(resource.GetLevel()); + + Json::Value metadata = Json::objectValue; + + for (std::map::const_iterator it = m.begin(); it != m.end(); ++it) + { + metadata[EnumerationToString(it->first)] = it->second; + } + + target["Metadata"] = metadata; + } + } + + + ResourceFinder::ResourceFinder(ResourceType level, + bool expand) : + request_(level), + expand_(expand), + format_(DicomToJsonFormat_Human), + includeAllMetadata_(false) + { + if (expand) + { + request_.SetRetrieveMainDicomTags(level, true); + request_.SetRetrieveMetadata(level, true); + request_.SetRetrieveLabels(true); + + if (level == ResourceType_Series) + { + request_.AddRetrieveChildrenMetadata(MetadataType_Instance_IndexInSeries); // required for the SeriesStatus + } + + if (level == ResourceType_Instance) + { + request_.SetRetrieveAttachments(true); // for FileSize & FileUuid + } + else + { + request_.SetRetrieveChildrenIdentifiers(true); + } + + if (level != ResourceType_Patient) + { + request_.SetRetrieveParentIdentifier(true); + } + } + } + + + void ResourceFinder::Execute(Json::Value& target, + ServerContext& context) + { + FindResponse response; + context.GetIndex().ExecuteFind(response, request_); + + target = Json::arrayValue; + + if (expand_) + { + for (size_t i = 0; i < response.GetSize(); i++) + { + Json::Value item; + Expand(item, response.GetResource(i), context.GetIndex()); + target.append(item); + } + } + else + { + for (size_t i = 0; i < response.GetSize(); i++) + { + target.append(response.GetResource(i).GetIdentifier()); + } + } + } +} diff -r a3732285f8b6 -r 3d0aa94b44b3 OrthancServer/Sources/ResourceFinder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/ResourceFinder.h Wed May 08 13:37:23 2024 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2024 Osimis S.A., Belgium + * Copyright (C) 2021-2024 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 . + **/ + + +#pragma once + +#include "Database/FindRequest.h" +#include "Database/FindResponse.h" + +namespace Orthanc +{ + class ServerContext; + class ServerIndex; + + class ResourceFinder : public boost::noncopyable + { + private: + FindRequest request_; + bool expand_; + std::set requestedTags_; + DicomToJsonFormat format_; + bool includeAllMetadata_; // Same as: ExpandResourceFlags_IncludeAllMetadata + + SeriesStatus GetSeriesStatus(uint32_t& expectedNumberOfInstances, + const FindResponse::Resource& resource) const; + + void Expand(Json::Value& target, + const FindResponse::Resource& resource, + ServerIndex& index) const; + + public: + ResourceFinder(ResourceType level, + bool expand); + + void SetRequestedTags(const std::set& tags) + { + requestedTags_ = tags; + } + + void SetFormat(DicomToJsonFormat format) + { + format_ = format; + } + + void SetLimits(uint64_t since, + uint64_t count) + { + request_.SetLimits(since, count); + } + + void SetIncludeAllMetadata(bool include) + { + includeAllMetadata_ = include; + } + + void Execute(Json::Value& target, + ServerContext& context); + }; +}