Mercurial > hg > orthanc
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, |