Mercurial > hg > orthanc
comparison OrthancServer/Sources/ServerContext.cpp @ 4196:37310bb1cd30
Fix handling of "ModalitiesInStudy" (0008,0061) in C-FIND and "/tools/find"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Sep 2020 13:22:30 +0200 |
parents | 1ec3e1e18f50 |
children | 4d42408da117 |
comparison
equal
deleted
inserted
replaced
4194:2bc49197f806 | 4196:37310bb1cd30 |
---|---|
32 | 32 |
33 | 33 |
34 #include "PrecompiledHeadersServer.h" | 34 #include "PrecompiledHeadersServer.h" |
35 #include "ServerContext.h" | 35 #include "ServerContext.h" |
36 | 36 |
37 #include "../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" | |
38 #include "../../OrthancFramework/Sources/Cache/SharedArchive.h" | 37 #include "../../OrthancFramework/Sources/Cache/SharedArchive.h" |
39 #include "../../OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h" | 38 #include "../../OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h" |
40 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" | 39 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" |
40 #include "../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" | |
41 #include "../../OrthancFramework/Sources/FileStorage/StorageAccessor.h" | 41 #include "../../OrthancFramework/Sources/FileStorage/StorageAccessor.h" |
42 #include "../../OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h" | 42 #include "../../OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h" |
43 #include "../../OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.h" | 43 #include "../../OrthancFramework/Sources/HttpServer/HttpStreamTranscoder.h" |
44 #include "../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h" | 44 #include "../../OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h" |
45 #include "../../OrthancFramework/Sources/Logging.h" | 45 #include "../../OrthancFramework/Sources/Logging.h" |
934 return false; | 934 return false; |
935 #endif | 935 #endif |
936 } | 936 } |
937 | 937 |
938 | 938 |
939 void ServerContext::Apply(ILookupVisitor& visitor, | 939 void ServerContext::ApplyInternal(ILookupVisitor& visitor, |
940 const DatabaseLookup& lookup, | 940 const DatabaseLookup& lookup, |
941 ResourceType queryLevel, | 941 ResourceType queryLevel, |
942 size_t since, | 942 size_t since, |
943 size_t limit) | 943 size_t limit) |
944 { | 944 { |
945 unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? | 945 unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? |
946 limitFindInstances_ : limitFindResults_); | 946 limitFindInstances_ : limitFindResults_); |
947 | 947 |
948 std::vector<std::string> resources, instances; | 948 std::vector<std::string> resources, instances; |
949 | 949 |
992 // The instance has been removed during the execution of the | 992 // The instance has been removed during the execution of the |
993 // lookup, ignore it | 993 // lookup, ignore it |
994 continue; | 994 continue; |
995 } | 995 } |
996 | 996 |
997 #if 1 | |
998 // New in Orthanc 1.6.0: Only keep the main DICOM tags at the | 997 // New in Orthanc 1.6.0: Only keep the main DICOM tags at the |
999 // level of interest for the query | 998 // level of interest for the query |
1000 switch (queryLevel) | 999 switch (queryLevel) |
1001 { | 1000 { |
1002 // WARNING: Don't reorder cases below, and don't add "break" | 1001 // WARNING: Don't reorder cases below, and don't add "break" |
1014 break; | 1013 break; |
1015 | 1014 |
1016 default: | 1015 default: |
1017 throw OrthancException(ErrorCode_InternalError); | 1016 throw OrthancException(ErrorCode_InternalError); |
1018 } | 1017 } |
1019 | |
1020 // Special case of the "Modality" at the study level, in order | |
1021 // to deal with C-FIND on "ModalitiesInStudy" (0008,0061). | |
1022 // Check out integration test "test_rest_modalities_in_study". | |
1023 if (queryLevel == ResourceType_Study) | |
1024 { | |
1025 dicom.CopyTagIfExists(tmp, DICOM_TAG_MODALITY); | |
1026 } | |
1027 #else | |
1028 dicom.Assign(tmp); // This emulates Orthanc <= 1.5.8 | |
1029 #endif | |
1030 | 1018 |
1031 hasOnlyMainDicomTags = true; | 1019 hasOnlyMainDicomTags = true; |
1032 } | 1020 } |
1033 else | 1021 else |
1034 { | 1022 { |
1093 } | 1081 } |
1094 | 1082 |
1095 LOG(INFO) << "Number of matching resources: " << countResults; | 1083 LOG(INFO) << "Number of matching resources: " << countResults; |
1096 } | 1084 } |
1097 | 1085 |
1086 | |
1087 | |
1088 namespace | |
1089 { | |
1090 class ModalitiesInStudyVisitor : public ServerContext::ILookupVisitor | |
1091 { | |
1092 private: | |
1093 class Study : public boost::noncopyable | |
1094 { | |
1095 private: | |
1096 std::string orthancId_; | |
1097 std::string instanceId_; | |
1098 DicomMap mainDicomTags_; | |
1099 Json::Value dicomAsJson_; | |
1100 std::set<std::string> modalitiesInStudy_; | |
1101 | |
1102 public: | |
1103 Study(const std::string& instanceId, | |
1104 const DicomMap& seriesTags) : | |
1105 instanceId_(instanceId), | |
1106 dicomAsJson_(Json::nullValue) | |
1107 { | |
1108 { | |
1109 DicomMap tmp; | |
1110 tmp.Assign(seriesTags); | |
1111 tmp.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "dummy", false); | |
1112 DicomInstanceHasher hasher(tmp); | |
1113 orthancId_ = hasher.HashStudy(); | |
1114 } | |
1115 | |
1116 mainDicomTags_.MergeMainDicomTags(seriesTags, ResourceType_Study); | |
1117 mainDicomTags_.MergeMainDicomTags(seriesTags, ResourceType_Patient); | |
1118 AddModality(seriesTags); | |
1119 } | |
1120 | |
1121 void AddModality(const DicomMap& seriesTags) | |
1122 { | |
1123 std::string modality; | |
1124 if (seriesTags.LookupStringValue(modality, DICOM_TAG_MODALITY, false) && | |
1125 !modality.empty()) | |
1126 { | |
1127 modalitiesInStudy_.insert(modality); | |
1128 } | |
1129 } | |
1130 | |
1131 void SetDicomAsJson(const Json::Value& dicomAsJson) | |
1132 { | |
1133 dicomAsJson_ = dicomAsJson; | |
1134 } | |
1135 | |
1136 const std::string& GetOrthancId() const | |
1137 { | |
1138 return orthancId_; | |
1139 } | |
1140 | |
1141 const std::string& GetInstanceId() const | |
1142 { | |
1143 return instanceId_; | |
1144 } | |
1145 | |
1146 const DicomMap& GetMainDicomTags() const | |
1147 { | |
1148 return mainDicomTags_; | |
1149 } | |
1150 | |
1151 const Json::Value* GetDicomAsJson() const | |
1152 { | |
1153 if (dicomAsJson_.type() == Json::nullValue) | |
1154 { | |
1155 return NULL; | |
1156 } | |
1157 else | |
1158 { | |
1159 return &dicomAsJson_; | |
1160 } | |
1161 } | |
1162 }; | |
1163 | |
1164 typedef std::map<std::string, Study*> Studies; | |
1165 | |
1166 bool isDicomAsJsonNeeded_; | |
1167 bool complete_; | |
1168 Studies studies_; | |
1169 | |
1170 public: | |
1171 ModalitiesInStudyVisitor(bool isDicomAsJsonNeeded) : | |
1172 isDicomAsJsonNeeded_(isDicomAsJsonNeeded) | |
1173 { | |
1174 } | |
1175 | |
1176 ~ModalitiesInStudyVisitor() | |
1177 { | |
1178 for (Studies::const_iterator it = studies_.begin(); it != studies_.end(); ++it) | |
1179 { | |
1180 assert(it->second != NULL); | |
1181 delete it->second; | |
1182 } | |
1183 | |
1184 studies_.clear(); | |
1185 } | |
1186 | |
1187 virtual bool IsDicomAsJsonNeeded() const | |
1188 { | |
1189 return isDicomAsJsonNeeded_; | |
1190 } | |
1191 | |
1192 virtual void MarkAsComplete() | |
1193 { | |
1194 complete_ = true; | |
1195 } | |
1196 | |
1197 virtual void Visit(const std::string& publicId, | |
1198 const std::string& instanceId, | |
1199 const DicomMap& seriesTags, | |
1200 const Json::Value* dicomAsJson) | |
1201 { | |
1202 std::string studyInstanceUid; | |
1203 if (seriesTags.LookupStringValue(studyInstanceUid, DICOM_TAG_STUDY_INSTANCE_UID, false)) | |
1204 { | |
1205 Studies::iterator found = studies_.find(studyInstanceUid); | |
1206 if (found == studies_.end()) | |
1207 { | |
1208 // New study | |
1209 std::unique_ptr<Study> study(new Study(instanceId, seriesTags)); | |
1210 | |
1211 if (dicomAsJson != NULL) | |
1212 { | |
1213 study->SetDicomAsJson(*dicomAsJson); | |
1214 } | |
1215 | |
1216 studies_[studyInstanceUid] = study.release(); | |
1217 } | |
1218 else | |
1219 { | |
1220 // Already existing study | |
1221 found->second->AddModality(seriesTags); | |
1222 } | |
1223 } | |
1224 } | |
1225 | |
1226 void Forward(ILookupVisitor& callerVisitor, | |
1227 size_t since, | |
1228 size_t limit) const | |
1229 { | |
1230 size_t index = 0; | |
1231 size_t countForwarded = 0; | |
1232 | |
1233 for (Studies::const_iterator it = studies_.begin(); it != studies_.end(); ++it, index++) | |
1234 { | |
1235 if (limit == 0 || | |
1236 (index >= since && | |
1237 index < limit)) | |
1238 { | |
1239 assert(it->second != NULL); | |
1240 const Study& study = *it->second; | |
1241 | |
1242 countForwarded++; | |
1243 callerVisitor.Visit(study.GetOrthancId(), study.GetInstanceId(), | |
1244 study.GetMainDicomTags(), study.GetDicomAsJson()); | |
1245 } | |
1246 } | |
1247 | |
1248 if (countForwarded == studies_.size()) | |
1249 { | |
1250 callerVisitor.MarkAsComplete(); | |
1251 } | |
1252 } | |
1253 }; | |
1254 } | |
1255 | |
1256 | |
1257 void ServerContext::Apply(ILookupVisitor& visitor, | |
1258 const DatabaseLookup& lookup, | |
1259 ResourceType queryLevel, | |
1260 size_t since, | |
1261 size_t limit) | |
1262 { | |
1263 if (queryLevel == ResourceType_Study && | |
1264 lookup.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | |
1265 { | |
1266 // Convert the study-level query, into a series-level query, | |
1267 // where "ModalitiesInStudy" is replaced by "Modality" | |
1268 DatabaseLookup seriesLookup; | |
1269 | |
1270 for (size_t i = 0; i < lookup.GetConstraintsCount(); i++) | |
1271 { | |
1272 const DicomTagConstraint& constraint = lookup.GetConstraint(i); | |
1273 if (constraint.GetTag() == DICOM_TAG_MODALITIES_IN_STUDY) | |
1274 { | |
1275 if ((constraint.GetConstraintType() == ConstraintType_Equal && constraint.GetValue().empty()) || | |
1276 (constraint.GetConstraintType() == ConstraintType_List && constraint.GetValues().empty())) | |
1277 { | |
1278 // Ignore universal lookup on "ModalitiesInStudy" (0008,0061), | |
1279 // this should have been handled by the caller | |
1280 return ApplyInternal(visitor, lookup, queryLevel, since, limit); | |
1281 } | |
1282 else | |
1283 { | |
1284 DicomTagConstraint modality(constraint); | |
1285 modality.SetTag(DICOM_TAG_MODALITY); | |
1286 seriesLookup.AddConstraint(modality); | |
1287 } | |
1288 } | |
1289 else | |
1290 { | |
1291 seriesLookup.AddConstraint(constraint); | |
1292 } | |
1293 } | |
1294 | |
1295 ModalitiesInStudyVisitor seriesVisitor(visitor.IsDicomAsJsonNeeded()); | |
1296 ApplyInternal(seriesVisitor, seriesLookup, ResourceType_Series, 0, 0); | |
1297 seriesVisitor.Forward(visitor, since, limit); | |
1298 } | |
1299 else | |
1300 { | |
1301 ApplyInternal(visitor, lookup, queryLevel, since, limit); | |
1302 } | |
1303 } | |
1304 | |
1098 | 1305 |
1099 bool ServerContext::LookupOrReconstructMetadata(std::string& target, | 1306 bool ServerContext::LookupOrReconstructMetadata(std::string& target, |
1100 const std::string& publicId, | 1307 const std::string& publicId, |
1101 MetadataType metadata) | 1308 MetadataType metadata) |
1102 { | 1309 { |