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 {