comparison OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp @ 4940:304514ce84ee more-tags

tools/find + C-Find + list-resources now all using the same code (ExpandResource) to build 'computed tags'
author Alain Mazy <am@osimis.io>
date Tue, 15 Mar 2022 15:57:21 +0100
parents e8a2e145c80e
children 8fba26292a9f
comparison
equal deleted inserted replaced
4939:e8a2e145c80e 4940:304514ce84ee
711 711
712 712
713 bool StatelessDatabaseOperations::ExpandResource(ExpandedResource& target, 713 bool StatelessDatabaseOperations::ExpandResource(ExpandedResource& target,
714 const std::string& publicId, 714 const std::string& publicId,
715 ResourceType level, 715 ResourceType level,
716 const std::set<DicomTag>& requestedTags) 716 const std::set<DicomTag>& requestedTags,
717 ExpandResourceDbFlags expandFlags)
717 { 718 {
718 class Operations : public ReadOnlyOperationsT5< 719 class Operations : public ReadOnlyOperationsT6<
719 bool&, ExpandedResource&, const std::string&, ResourceType, const std::set<DicomTag>&> 720 bool&, ExpandedResource&, const std::string&, ResourceType, const std::set<DicomTag>&, ExpandResourceDbFlags>
720 { 721 {
721 private: 722 private:
722 723
723 static bool LookupStringMetadata(std::string& result, 724 static bool LookupStringMetadata(std::string& result,
724 const std::map<MetadataType, std::string>& metadata, 725 const std::map<MetadataType, std::string>& metadata,
757 { 758 {
758 return false; 759 return false;
759 } 760 }
760 } 761 }
761 762
762 static void ComputeSeriesTags(DicomMap& result,
763 const std::list<std::string>& children,
764 const std::set<DicomTag>& requestedTags)
765 {
766 if (requestedTags.count(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES) > 0)
767 {
768 result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES,
769 boost::lexical_cast<std::string>(children.size()), false);
770 }
771 }
772
773 static void ComputeStudyTags(DicomMap& result,
774 ReadOnlyTransaction& transaction,
775 const std::string& studyPublicId,
776 int64_t studyInternalId,
777 const std::set<DicomTag>& requestedTags)
778 {
779 std::list<int64_t> seriesInternalIds;
780 std::list<int64_t> instancesInternalIds;
781
782 transaction.GetChildrenInternalId(seriesInternalIds, studyInternalId);
783
784 if (requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) > 0)
785 {
786 result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES,
787 boost::lexical_cast<std::string>(seriesInternalIds.size()), false);
788 }
789
790 if (requestedTags.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0)
791 {
792 std::set<std::string> values;
793
794 for (std::list<int64_t>::const_iterator
795 it = seriesInternalIds.begin(); it != seriesInternalIds.end(); ++it)
796 {
797 if (requestedTags.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0)
798 {
799 DicomMap tags;
800 transaction.GetMainDicomTags(tags, *it);
801
802 const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
803
804 if (value != NULL &&
805 !value->IsNull() &&
806 !value->IsBinary())
807 {
808 values.insert(value->GetContent());
809 }
810 }
811 }
812
813 std::string modalities;
814 Toolbox::JoinStrings(modalities, values, "\\");
815 result.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, modalities, false);
816 }
817
818 if (requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0
819 || requestedTags.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0)
820 {
821 for (std::list<int64_t>::const_iterator
822 it = seriesInternalIds.begin(); it != seriesInternalIds.end(); ++it)
823 {
824 std::list<int64_t> seriesInstancesIds;
825 transaction.GetChildrenInternalId(seriesInstancesIds, *it);
826
827 instancesInternalIds.splice(instancesInternalIds.end(), seriesInstancesIds);
828 }
829
830 if (requestedTags.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0)
831 {
832 result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES,
833 boost::lexical_cast<std::string>(instancesInternalIds.size()), false);
834 }
835
836 if (requestedTags.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0)
837 {
838 std::set<std::string> values;
839
840 for (std::list<int64_t>::const_iterator
841 it = instancesInternalIds.begin(); it != instancesInternalIds.end(); ++it)
842 {
843 std::map<MetadataType, std::string> instanceMetadata;
844 // Extract the metadata
845 transaction.GetAllMetadata(instanceMetadata, *it);
846
847 std::string value;
848 if (!LookupStringMetadata(value, instanceMetadata, MetadataType_Instance_SopClassUid))
849 {
850 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");
851 }
852
853 values.insert(value);
854 }
855
856 std::string sopClassUids;
857 Toolbox::JoinStrings(sopClassUids, values, "\\");
858 result.SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, sopClassUids, false);
859 }
860 }
861 }
862
863 static void ComputePatientTags(DicomMap& result,
864 ReadOnlyTransaction& transaction,
865 const std::string& patientPublicId,
866 int64_t patientInternalId,
867 const std::set<DicomTag>& requestedTags)
868 {
869 std::list<int64_t> studiesInternalIds;
870 std::list<int64_t> seriesInternalIds;
871 std::list<int64_t> instancesInternalIds;
872
873 bool hasNbRelatedStudies = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) > 0;
874 bool hasNbRelatedSeries = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) > 0;
875 bool hasNbRelatedInstances = requestedTags.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES) > 0;
876
877 transaction.GetChildrenInternalId(studiesInternalIds, patientInternalId);
878
879 if (hasNbRelatedStudies)
880 {
881 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES,
882 boost::lexical_cast<std::string>(studiesInternalIds.size()), false);
883 }
884
885 if (hasNbRelatedSeries || hasNbRelatedInstances)
886 {
887 for (std::list<int64_t>::const_iterator
888 it = studiesInternalIds.begin(); it != studiesInternalIds.end(); ++it)
889 {
890 std::list<int64_t> thisSeriesIds;
891 transaction.GetChildrenInternalId(thisSeriesIds, *it);
892 seriesInternalIds.splice(seriesInternalIds.end(), thisSeriesIds);
893
894 if (hasNbRelatedInstances)
895 {
896 for (std::list<int64_t>::const_iterator
897 it2 = seriesInternalIds.begin(); it2 != seriesInternalIds.end(); ++it2)
898 {
899 std::list<int64_t> thisInstancesIds;
900 transaction.GetChildrenInternalId(thisInstancesIds, *it2);
901 instancesInternalIds.splice(instancesInternalIds.end(), thisInstancesIds);
902 }
903 }
904 }
905
906 if (hasNbRelatedSeries)
907 {
908 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES,
909 boost::lexical_cast<std::string>(seriesInternalIds.size()), false);
910 }
911
912 if (hasNbRelatedInstances)
913 {
914 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES,
915 boost::lexical_cast<std::string>(instancesInternalIds.size()), false);
916 }
917 }
918 }
919 763
920 public: 764 public:
921 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 765 virtual void ApplyTuple(ReadOnlyTransaction& transaction,
922 const Tuple& tuple) ORTHANC_OVERRIDE 766 const Tuple& tuple) ORTHANC_OVERRIDE
923 { 767 {
931 tuple.get<0>() = false; 775 tuple.get<0>() = false;
932 } 776 }
933 else 777 else
934 { 778 {
935 ExpandedResource& target = tuple.get<1>(); 779 ExpandedResource& target = tuple.get<1>();
780 ExpandResourceDbFlags expandFlags = tuple.get<5>();
936 781
937 // Set information about the parent resource (if it exists) 782 // Set information about the parent resource (if it exists)
938 if (type == ResourceType_Patient) 783 if (type == ResourceType_Patient)
939 { 784 {
940 if (!parent.empty()) 785 if (!parent.empty())
950 } 795 }
951 796
952 target.parentId_ = parent; 797 target.parentId_ = parent;
953 } 798 }
954 799
955 // List the children resources
956 transaction.GetChildrenPublicId(target.childrenIds_, internalId);
957
958 // Extract the metadata
959 transaction.GetAllMetadata(target.metadata_, internalId);
960
961 // Set the resource type
962 target.type_ = type; 800 target.type_ = type;
963 801 target.id_ = tuple.get<2>();
964 switch (type) 802
965 { 803 if (expandFlags & ExpandResourceDbFlags_IncludeChildren)
966 case ResourceType_Patient: 804 {
967 case ResourceType_Study: 805 // List the children resources
968 break; 806 transaction.GetChildrenPublicId(target.childrenIds_, internalId);
969 807 }
970 case ResourceType_Series: 808
809 if (expandFlags & ExpandResourceDbFlags_IncludeMetadata)
810 {
811 // Extract the metadata
812 transaction.GetAllMetadata(target.metadata_, internalId);
813
814 switch (type)
971 { 815 {
972 int64_t i; 816 case ResourceType_Patient:
973 if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Series_ExpectedNumberOfInstances)) 817 case ResourceType_Study:
818 break;
819
820 case ResourceType_Series:
974 { 821 {
975 target.expectedNumberOfInstances_ = static_cast<int>(i); 822 int64_t i;
976 target.status_ = EnumerationToString(transaction.GetSeriesStatus(internalId, i)); 823 if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Series_ExpectedNumberOfInstances))
977 } 824 {
978 else 825 target.expectedNumberOfInstances_ = static_cast<int>(i);
979 { 826 target.status_ = EnumerationToString(transaction.GetSeriesStatus(internalId, i));
980 target.expectedNumberOfInstances_ = -1; 827 }
981 target.status_ = EnumerationToString(SeriesStatus_Unknown); 828 else
982 } 829 {
983 830 target.expectedNumberOfInstances_ = -1;
984 break; 831 target.status_ = EnumerationToString(SeriesStatus_Unknown);
985 } 832 }
986 833
987 case ResourceType_Instance:
988 {
989 FileInfo attachment;
990 int64_t revision; // ignored
991 if (!transaction.LookupAttachment(attachment, revision, internalId, FileContentType_Dicom))
992 {
993 throw OrthancException(ErrorCode_InternalError);
994 }
995
996 target.fileSize_ = static_cast<unsigned int>(attachment.GetUncompressedSize());
997 target.fileUuid_ = attachment.GetUuid();
998
999 int64_t i;
1000 if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Instance_IndexInSeries))
1001 {
1002 target.indexInSeries_ = static_cast<int>(i);
1003 }
1004 else
1005 {
1006 target.indexInSeries_ = -1;
1007 }
1008
1009 break;
1010 }
1011
1012 default:
1013 throw OrthancException(ErrorCode_InternalError);
1014 }
1015
1016 // check the main dicom tags list has not changed since the resource was stored
1017 target.mainDicomTagsSignature_ = DicomMap::GetDefaultMainDicomTagsSignature(type);
1018 LookupStringMetadata(target.mainDicomTagsSignature_, target.metadata_, MetadataType_MainDicomTagsSignature);
1019
1020 // Record the remaining information
1021 target.id_ = tuple.get<2>();
1022
1023 // read all tags from DB
1024 transaction.GetMainDicomTags(target.tags_, internalId);
1025
1026 // check if we have access to all requestedTags or if we must get tags from parents
1027 const std::set<DicomTag>& requestedTags = tuple.get<4>();
1028
1029 if (requestedTags.size() > 0)
1030 {
1031 std::set<DicomTag> savedMainDicomTags;
1032
1033 FromDcmtkBridge::ParseListOfTags(savedMainDicomTags, target.mainDicomTagsSignature_);
1034
1035 // read parent main dicom tags as long as we don't have gathered all requested tags
1036 ResourceType currentLevel = target.type_;
1037 int64_t currentInternalId = internalId;
1038 Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags);
1039
1040 while ((target.missingRequestedTags_.size() > 0)
1041 && currentLevel != ResourceType_Patient)
1042 {
1043 currentLevel = GetParentResourceType(currentLevel);
1044
1045 int64_t currentParentId;
1046 if (!transaction.LookupParent(currentParentId, currentInternalId))
1047 {
1048 break; 834 break;
1049 } 835 }
1050 836
1051 std::map<MetadataType, std::string> parentMetadata; 837 case ResourceType_Instance:
1052 transaction.GetAllMetadata(parentMetadata, currentParentId); 838 {
1053 839 FileInfo attachment;
1054 std::string parentMainDicomTagsSignature = DicomMap::GetDefaultMainDicomTagsSignature(currentLevel); 840 int64_t revision; // ignored
1055 LookupStringMetadata(parentMainDicomTagsSignature, parentMetadata, MetadataType_MainDicomTagsSignature); 841 if (!transaction.LookupAttachment(attachment, revision, internalId, FileContentType_Dicom))
1056 842 {
1057 std::set<DicomTag> parentSavedMainDicomTags; 843 throw OrthancException(ErrorCode_InternalError);
1058 FromDcmtkBridge::ParseListOfTags(parentSavedMainDicomTags, parentMainDicomTagsSignature); 844 }
845
846 target.fileSize_ = static_cast<unsigned int>(attachment.GetUncompressedSize());
847 target.fileUuid_ = attachment.GetUuid();
848
849 int64_t i;
850 if (LookupIntegerMetadata(i, target.metadata_, MetadataType_Instance_IndexInSeries))
851 {
852 target.indexInSeries_ = static_cast<int>(i);
853 }
854 else
855 {
856 target.indexInSeries_ = -1;
857 }
858
859 break;
860 }
861
862 default:
863 throw OrthancException(ErrorCode_InternalError);
864 }
865
866 // check the main dicom tags list has not changed since the resource was stored
867 target.mainDicomTagsSignature_ = DicomMap::GetDefaultMainDicomTagsSignature(type);
868 LookupStringMetadata(target.mainDicomTagsSignature_, target.metadata_, MetadataType_MainDicomTagsSignature);
869 }
870
871 if (expandFlags & ExpandResourceDbFlags_IncludeMainDicomTags)
872 {
873 // read all tags from DB
874 transaction.GetMainDicomTags(target.tags_, internalId);
875
876 // check if we have access to all requestedTags or if we must get tags from parents
877 const std::set<DicomTag>& requestedTags = tuple.get<4>();
878
879 if (requestedTags.size() > 0)
880 {
881 std::set<DicomTag> savedMainDicomTags;
1059 882
1060 size_t previousMissingCount = target.missingRequestedTags_.size(); 883 FromDcmtkBridge::ParseListOfTags(savedMainDicomTags, target.mainDicomTagsSignature_);
1061 Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags); 884
885 // read parent main dicom tags as long as we don't have gathered all requested tags
886 ResourceType currentLevel = target.type_;
887 int64_t currentInternalId = internalId;
1062 Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags); 888 Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags);
1063 889
1064 // read the parent tags from DB only if it reduces the number of missing tags 890 while ((target.missingRequestedTags_.size() > 0)
1065 if (target.missingRequestedTags_.size() < previousMissingCount) 891 && currentLevel != ResourceType_Patient)
1066 { 892 {
893 currentLevel = GetParentResourceType(currentLevel);
894
895 int64_t currentParentId;
896 if (!transaction.LookupParent(currentParentId, currentInternalId))
897 {
898 break;
899 }
900
901 std::map<MetadataType, std::string> parentMetadata;
902 transaction.GetAllMetadata(parentMetadata, currentParentId);
903
904 std::string parentMainDicomTagsSignature = DicomMap::GetDefaultMainDicomTagsSignature(currentLevel);
905 LookupStringMetadata(parentMainDicomTagsSignature, parentMetadata, MetadataType_MainDicomTagsSignature);
906
907 std::set<DicomTag> parentSavedMainDicomTags;
908 FromDcmtkBridge::ParseListOfTags(parentSavedMainDicomTags, parentMainDicomTagsSignature);
909
910 size_t previousMissingCount = target.missingRequestedTags_.size();
1067 Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags); 911 Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags);
1068 912 Toolbox::GetMissingsFromSet(target.missingRequestedTags_, requestedTags, savedMainDicomTags);
1069 DicomMap parentTags; 913
1070 transaction.GetMainDicomTags(parentTags, currentParentId); 914 // read the parent tags from DB only if it reduces the number of missing tags
1071 915 if (target.missingRequestedTags_.size() < previousMissingCount)
1072 target.tags_.Merge(parentTags); 916 {
917 Toolbox::AppendSets(savedMainDicomTags, parentSavedMainDicomTags);
918
919 DicomMap parentTags;
920 transaction.GetMainDicomTags(parentTags, currentParentId);
921
922 target.tags_.Merge(parentTags);
923 }
924
925 currentInternalId = currentParentId;
1073 } 926 }
1074
1075 currentInternalId = currentParentId;
1076 } 927 }
1077
1078 { // handle the tags that must be rebuilt because they are not saved in DB
1079 if (target.type_ == ResourceType_Study && (
1080 target.missingRequestedTags_.count(DICOM_TAG_MODALITIES_IN_STUDY) > 0
1081 || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) > 0
1082 || target.missingRequestedTags_.count(DICOM_TAG_SOP_CLASSES_IN_STUDY) > 0
1083 || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) > 0
1084 ))
1085 {
1086 ComputeStudyTags(target.tags_, transaction, target.id_, internalId, requestedTags);
1087 }
1088
1089 if (target.type_ == ResourceType_Series
1090 && target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES) > 0)
1091 {
1092 ComputeSeriesTags(target.tags_, target.childrenIds_, requestedTags);
1093 }
1094
1095 if (target.type_ == ResourceType_Patient && (
1096 target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) > 0
1097 || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) > 0
1098 || target.missingRequestedTags_.count(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES) > 0
1099 ))
1100 {
1101 ComputePatientTags(target.tags_, transaction, target.id_, internalId, requestedTags);
1102 }
1103 }
1104
1105 } 928 }
1106 929
1107 std::string tmp; 930 std::string tmp;
1108 931
1109 if (LookupStringMetadata(tmp, target.metadata_, MetadataType_AnonymizedFrom)) 932 if (LookupStringMetadata(tmp, target.metadata_, MetadataType_AnonymizedFrom))
1137 } 960 }
1138 }; 961 };
1139 962
1140 bool found; 963 bool found;
1141 Operations operations; 964 Operations operations;
1142 operations.Apply(*this, found, target, publicId, level, requestedTags); 965 operations.Apply(*this, found, target, publicId, level, requestedTags, expandFlags);
1143 return found; 966 return found;
1144 } 967 }
1145 968
1146 969
1147 void StatelessDatabaseOperations::GetAllMetadata(std::map<MetadataType, std::string>& target, 970 void StatelessDatabaseOperations::GetAllMetadata(std::map<MetadataType, std::string>& target,