Mercurial > hg > orthanc
changeset 4938:f630796a59b1 more-tags
ExpandResource now able to return computed tags (like ModalitiesInStudies)
author | Alain Mazy <am@osimis.io> |
---|---|
date | Mon, 14 Mar 2022 16:44:00 +0100 |
parents | 3f9b9865c8cc |
children | e8a2e145c80e |
files | NEWS OrthancFramework/Sources/Toolbox.cpp OrthancFramework/Sources/Toolbox.h OrthancFramework/UnitTestsSources/ToolboxTests.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp |
diffstat | 5 files changed, 258 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Mar 14 13:13:29 2022 +0100 +++ b/NEWS Mon Mar 14 16:44:00 2022 +0100 @@ -1,6 +1,12 @@ Pending changes in the mainline =============================== +* BREAKING_CHANGE: (TODO: when CFind uses ExpandResource) + If trying to get SOPClassesInStudy in a study that was saved in an Orthanc version + prior to 1.2.0, you will now get an error. Orthanc logs will show an error message + asking you to POST to /studies/.../reconstruct to rebuild the missing study data. + + Documentation -------------
--- a/OrthancFramework/Sources/Toolbox.cpp Mon Mar 14 13:13:29 2022 +0100 +++ b/OrthancFramework/Sources/Toolbox.cpp Mon Mar 14 16:44:00 2022 +0100 @@ -56,6 +56,7 @@ #include <boost/algorithm/string/case_conv.hpp> #include <boost/algorithm/string/replace.hpp> +#include <boost/algorithm/string/join.hpp> #include <boost/lexical_cast.hpp> #include <boost/regex.hpp> @@ -1032,6 +1033,21 @@ } + void Toolbox::JoinStrings(std::string& result, + std::set<std::string>& source, + const char* separator) + { + result = boost::algorithm::join(source, separator); + } + + void JoinStrings(std::string& result, + std::vector<std::string>& source, + const char* separator) + { + result = boost::algorithm::join(source, separator); + } + + #if ORTHANC_ENABLE_PUGIXML == 1 class ChunkedBufferWriter : public pugi::xml_writer {
--- a/OrthancFramework/Sources/Toolbox.h Mon Mar 14 13:13:29 2022 +0100 +++ b/OrthancFramework/Sources/Toolbox.h Mon Mar 14 16:44:00 2022 +0100 @@ -185,6 +185,14 @@ const std::string& source, char separator); + static void JoinStrings(std::string& result, + std::set<std::string>& source, + const char* separator); + + static void JoinStrings(std::string& result, + std::vector<std::string>& source, + const char* separator); + // returns true if all element of 'needles' are found in 'haystack' template <typename T> static bool IsSetInSet(const std::set<T>& needles, const std::set<T>& haystack) {
--- a/OrthancFramework/UnitTestsSources/ToolboxTests.cpp Mon Mar 14 13:13:29 2022 +0100 +++ b/OrthancFramework/UnitTestsSources/ToolboxTests.cpp Mon Mar 14 16:44:00 2022 +0100 @@ -279,3 +279,46 @@ ASSERT_EQ(0, Toolbox::GetMissingsFromSet<int>(missings, needles, haystack)); } } + +TEST(Toolbox, JoinStrings) +{ + { + std::set<std::string> source; + std::string result; + + Toolbox::JoinStrings(result, source, ";"); + ASSERT_EQ("", result); + } + + { + std::set<std::string> source; + source.insert("1"); + + std::string result; + + Toolbox::JoinStrings(result, source, ";"); + ASSERT_EQ("1", result); + } + + { + std::set<std::string> source; + source.insert("2"); + source.insert("1"); + + std::string result; + + Toolbox::JoinStrings(result, source, ";"); + ASSERT_EQ("1;2", result); + } + + { + std::set<std::string> source; + source.insert("2"); + source.insert("1"); + + std::string result; + + Toolbox::JoinStrings(result, source, "\\"); + ASSERT_EQ("1\\2", result); + } +} \ No newline at end of file
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Mar 14 13:13:29 2022 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Mar 14 16:44:00 2022 +0100 @@ -760,6 +760,163 @@ } } + static void ComputeSeriesTags(DicomMap& result, + const std::list<std::string>& children, + const std::set<DicomTag>& requestedTags) + { + if (requestedTags.count(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES) > 0) + { + result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, + boost::lexical_cast<std::string>(children.size()), false); + } + } + + static void ComputeStudyTags(DicomMap& result, + ReadOnlyTransaction& transaction, + const std::string& studyPublicId, + int64_t studyInternalId, + const std::set<DicomTag>& requestedTags) + { + std::list<int64_t> seriesInternalIds; + std::list<int64_t> instancesInternalIds; + + transaction.GetChildrenInternalId(seriesInternalIds, studyInternalId); + + if (requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) > 0) + { + result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, + boost::lexical_cast<std::string>(seriesInternalIds.size()), false); + } + + if (requestedTags.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0) + { + std::set<std::string> values; + + for (std::list<int64_t>::const_iterator + it = seriesInternalIds.begin(); it != seriesInternalIds.end(); ++it) + { + if (requestedTags.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0) + { + DicomMap tags; + transaction.GetMainDicomTags(tags, *it); + + const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY); + + if (value != NULL && + !value->IsNull() && + !value->IsBinary()) + { + values.insert(value->GetContent()); + } + } + } + + std::string modalities; + Toolbox::JoinStrings(modalities, values, "\\"); + result.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, modalities, false); + } + + if (requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0 + || requestedTags.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0) + { + for (std::list<int64_t>::const_iterator + it = seriesInternalIds.begin(); it != seriesInternalIds.end(); ++it) + { + std::list<int64_t> seriesInstancesIds; + transaction.GetChildrenInternalId(seriesInstancesIds, *it); + + instancesInternalIds.splice(instancesInternalIds.end(), seriesInstancesIds); + } + + if (requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0) + { + result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, + boost::lexical_cast<std::string>(instancesInternalIds.size()), false); + } + + if (requestedTags.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0) + { + std::set<std::string> values; + + for (std::list<int64_t>::const_iterator + it = instancesInternalIds.begin(); it != instancesInternalIds.end(); ++it) + { + std::map<MetadataType, std::string> instanceMetadata; + // Extract the metadata + transaction.GetAllMetadata(instanceMetadata, *it); + + std::string value; + if (!LookupStringMetadata(value, instanceMetadata, MetadataType_Instance_SopClassUid)) + { + throw OrthancException(ErrorCode_InternalError, "Unable to get the SOP Class Uid from an instance of the study " + studyPublicId + " because the instance has been saved with an old version of Orthanc (< 1.2.0). You should POST to /studies/" + studyPublicId + "/reconstruct to avoid this error"); + } + + values.insert(value); + } + + std::string sopClassUids; + Toolbox::JoinStrings(sopClassUids, values, "\\"); + result.SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, sopClassUids, false); + } + } + } + + static void ComputePatientTags(DicomMap& result, + ReadOnlyTransaction& transaction, + const std::string& patientPublicId, + int64_t patientInternalId, + const std::set<DicomTag>& requestedTags) + { + std::list<int64_t> studiesInternalIds; + std::list<int64_t> seriesInternalIds; + std::list<int64_t> instancesInternalIds; + + bool hasNbRelatedStudies = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) > 0; + bool hasNbRelatedSeries = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) > 0; + bool hasNbRelatedInstances = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES) > 0; + + transaction.GetChildrenInternalId(studiesInternalIds, patientInternalId); + + if (hasNbRelatedStudies) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, + boost::lexical_cast<std::string>(studiesInternalIds.size()), false); + } + + if (hasNbRelatedSeries || hasNbRelatedInstances) + { + for (std::list<int64_t>::const_iterator + it = studiesInternalIds.begin(); it != studiesInternalIds.end(); ++it) + { + std::list<int64_t> thisSeriesIds; + transaction.GetChildrenInternalId(thisSeriesIds, *it); + seriesInternalIds.splice(seriesInternalIds.end(), thisSeriesIds); + + if (hasNbRelatedInstances) + { + for (std::list<int64_t>::const_iterator + it2 = seriesInternalIds.begin(); it2 != seriesInternalIds.end(); ++it2) + { + std::list<int64_t> thisInstancesIds; + transaction.GetChildrenInternalId(thisInstancesIds, *it2); + instancesInternalIds.splice(instancesInternalIds.end(), thisInstancesIds); + } + } + } + + if (hasNbRelatedSeries) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, + boost::lexical_cast<std::string>(seriesInternalIds.size()), false); + } + + if (hasNbRelatedInstances) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, + boost::lexical_cast<std::string>(instancesInternalIds.size()), false); + } + } + } public: virtual void ApplyTuple(ReadOnlyTransaction& transaction, @@ -918,6 +1075,34 @@ currentInternalId = currentParentId; } + + { // handle the tags that must be rebuilt because they are not saved in DB + if (target.type_ == ResourceType_Study && ( + target.missingRequestedTags_.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0 + || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0 + || target.missingRequestedTags_.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0 + || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) > 0 + )) + { + ComputeStudyTags(target.tags_, transaction, target.id_, internalId, requestedTags); + } + + if (target.type_ == ResourceType_Series + && target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES) > 0) + { + ComputeSeriesTags(target.tags_, target.childrenIds_, requestedTags); + } + + if (target.type_ == ResourceType_Patient && ( + target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) > 0 + || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) > 0 + || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES) > 0 + )) + { + ComputePatientTags(target.tags_, transaction, target.id_, internalId, requestedTags); + } + } + } std::string tmp;