Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancFindRequestHandler.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 | 6eff25f70121 |
children | 2cfa50d8eb60 |
comparison
equal
deleted
inserted
replaced
4939:e8a2e145c80e | 4940:304514ce84ee |
---|---|
36 #include <boost/regex.hpp> | 36 #include <boost/regex.hpp> |
37 | 37 |
38 | 38 |
39 namespace Orthanc | 39 namespace Orthanc |
40 { | 40 { |
41 static void GetChildren(std::list<std::string>& target, | |
42 ServerIndex& index, | |
43 const std::list<std::string>& source) | |
44 { | |
45 target.clear(); | |
46 | |
47 for (std::list<std::string>::const_iterator | |
48 it = source.begin(); it != source.end(); ++it) | |
49 { | |
50 std::list<std::string> tmp; | |
51 index.GetChildren(tmp, *it); | |
52 target.splice(target.end(), tmp); | |
53 } | |
54 } | |
55 | |
56 | |
57 static void StoreSetOfStrings(DicomMap& result, | |
58 const DicomTag& tag, | |
59 const std::set<std::string>& values) | |
60 { | |
61 bool isFirst = true; | |
62 | |
63 std::string s; | |
64 for (std::set<std::string>::const_iterator | |
65 it = values.begin(); it != values.end(); ++it) | |
66 { | |
67 if (isFirst) | |
68 { | |
69 isFirst = false; | |
70 } | |
71 else | |
72 { | |
73 s += "\\"; | |
74 } | |
75 | |
76 s += *it; | |
77 } | |
78 | |
79 result.SetValue(tag, s, false); | |
80 } | |
81 | |
82 | |
83 static void ComputePatientCounters(DicomMap& result, | |
84 ServerIndex& index, | |
85 const std::string& patient, | |
86 const DicomMap& query) | |
87 { | |
88 std::list<std::string> studies; | |
89 index.GetChildren(studies, patient); | |
90 | |
91 if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES)) | |
92 { | |
93 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, | |
94 boost::lexical_cast<std::string>(studies.size()), false); | |
95 } | |
96 | |
97 if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && | |
98 !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
99 { | |
100 return; | |
101 } | |
102 | |
103 std::list<std::string> series; | |
104 GetChildren(series, index, studies); | |
105 studies.clear(); // This information is useless below | |
106 | |
107 if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES)) | |
108 { | |
109 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, | |
110 boost::lexical_cast<std::string>(series.size()), false); | |
111 } | |
112 | |
113 if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
114 { | |
115 return; | |
116 } | |
117 | |
118 std::list<std::string> instances; | |
119 GetChildren(instances, index, series); | |
120 | |
121 if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
122 { | |
123 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, | |
124 boost::lexical_cast<std::string>(instances.size()), false); | |
125 } | |
126 } | |
127 | |
128 | |
129 static void ComputeStudyCounters(DicomMap& result, | |
130 ServerContext& context, | |
131 const std::string& study, | |
132 const DicomMap& query) | |
133 { | |
134 ServerIndex& index = context.GetIndex(); | |
135 | |
136 std::list<std::string> series; | |
137 index.GetChildren(series, study); | |
138 | |
139 if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES)) | |
140 { | |
141 result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, | |
142 boost::lexical_cast<std::string>(series.size()), false); | |
143 } | |
144 | |
145 if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | |
146 { | |
147 std::set<std::string> values; | |
148 | |
149 for (std::list<std::string>::const_iterator | |
150 it = series.begin(); it != series.end(); ++it) | |
151 { | |
152 DicomMap tags; | |
153 if (index.GetMainDicomTags(tags, *it, ResourceType_Series, ResourceType_Series)) | |
154 { | |
155 const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY); | |
156 | |
157 if (value != NULL && | |
158 !value->IsNull() && | |
159 !value->IsBinary()) | |
160 { | |
161 values.insert(value->GetContent()); | |
162 } | |
163 } | |
164 } | |
165 | |
166 StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values); | |
167 } | |
168 | |
169 if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && | |
170 !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) | |
171 { | |
172 return; | |
173 } | |
174 | |
175 std::list<std::string> instances; | |
176 GetChildren(instances, index, series); | |
177 | |
178 if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES)) | |
179 { | |
180 result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, | |
181 boost::lexical_cast<std::string>(instances.size()), false); | |
182 } | |
183 | |
184 if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) | |
185 { | |
186 std::set<std::string> values; | |
187 | |
188 for (std::list<std::string>::const_iterator | |
189 it = instances.begin(); it != instances.end(); ++it) | |
190 { | |
191 std::string value; | |
192 if (context.LookupOrReconstructMetadata(value, *it, ResourceType_Instance, MetadataType_Instance_SopClassUid)) | |
193 { | |
194 values.insert(value); | |
195 } | |
196 } | |
197 | |
198 StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); | |
199 } | |
200 } | |
201 | |
202 | |
203 static void ComputeSeriesCounters(DicomMap& result, | |
204 ServerIndex& index, | |
205 const std::string& series, | |
206 const DicomMap& query) | |
207 { | |
208 std::list<std::string> instances; | |
209 index.GetChildren(instances, series); | |
210 | |
211 if (query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) | |
212 { | |
213 result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, | |
214 boost::lexical_cast<std::string>(instances.size()), false); | |
215 } | |
216 } | |
217 | |
218 | |
219 static DicomMap* ComputeCounters(ServerContext& context, | |
220 const std::string& instanceId, | |
221 ResourceType level, | |
222 const DicomMap& query) | |
223 { | |
224 switch (level) | |
225 { | |
226 case ResourceType_Patient: | |
227 if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) && | |
228 !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && | |
229 !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
230 { | |
231 return NULL; | |
232 } | |
233 | |
234 break; | |
235 | |
236 case ResourceType_Study: | |
237 if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) && | |
238 !query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && | |
239 !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) && | |
240 !query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | |
241 { | |
242 return NULL; | |
243 } | |
244 | |
245 break; | |
246 | |
247 case ResourceType_Series: | |
248 if (!query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) | |
249 { | |
250 return NULL; | |
251 } | |
252 | |
253 break; | |
254 | |
255 default: | |
256 return NULL; | |
257 } | |
258 | |
259 std::string parent; | |
260 if (!context.GetIndex().LookupParent(parent, instanceId, level)) | |
261 { | |
262 throw OrthancException(ErrorCode_UnknownResource); // The resource was deleted in between | |
263 } | |
264 | |
265 std::unique_ptr<DicomMap> result(new DicomMap); | |
266 | |
267 switch (level) | |
268 { | |
269 case ResourceType_Patient: | |
270 ComputePatientCounters(*result, context.GetIndex(), parent, query); | |
271 break; | |
272 | |
273 case ResourceType_Study: | |
274 ComputeStudyCounters(*result, context, parent, query); | |
275 break; | |
276 | |
277 case ResourceType_Series: | |
278 ComputeSeriesCounters(*result, context.GetIndex(), parent, query); | |
279 break; | |
280 | |
281 default: | |
282 throw OrthancException(ErrorCode_InternalError); | |
283 } | |
284 | |
285 return result.release(); | |
286 } | |
287 | |
288 | |
289 static void AddAnswer(DicomFindAnswers& answers, | 41 static void AddAnswer(DicomFindAnswers& answers, |
42 ServerContext& context, | |
43 const std::string& publicId, | |
44 const std::string& instanceId, | |
290 const DicomMap& mainDicomTags, | 45 const DicomMap& mainDicomTags, |
291 const Json::Value* dicomAsJson, | 46 const Json::Value* dicomAsJson, |
47 ResourceType level, | |
292 const DicomArray& query, | 48 const DicomArray& query, |
293 const std::list<DicomTag>& sequencesToReturn, | 49 const std::list<DicomTag>& sequencesToReturn, |
294 const DicomMap* counters, | |
295 const std::string& defaultPrivateCreator, | 50 const std::string& defaultPrivateCreator, |
296 const std::map<uint16_t, std::string>& privateCreators, | 51 const std::map<uint16_t, std::string>& privateCreators, |
297 const std::string& retrieveAet) | 52 const std::string& retrieveAet) |
298 { | 53 { |
299 DicomMap match; | 54 ExpandedResource resource; |
300 | 55 std::set<DicomTag> requestedTags; |
301 if (dicomAsJson != NULL) | |
302 { | |
303 match.FromDicomAsJson(*dicomAsJson); | |
304 } | |
305 else | |
306 { | |
307 match.Assign(mainDicomTags); | |
308 } | |
309 | 56 |
57 query.GetTags(requestedTags); | |
58 | |
59 // reuse ExpandResource to get missing tags and computed tags (ModalitiesInStudy ...). This code is therefore shared between C-Find, tools/find, list-resources and QIDO-RS | |
60 context.ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceDbFlags_IncludeMainDicomTags); | |
61 | |
310 DicomMap result; | 62 DicomMap result; |
311 | 63 |
312 /** | 64 /** |
313 * Add the mandatory "Retrieve AE Title (0008,0054)" tag, which was missing in Orthanc <= 1.7.2. | 65 * Add the mandatory "Retrieve AE Title (0008,0054)" tag, which was missing in Orthanc <= 1.7.2. |
314 * http://dicom.nema.org/medical/dicom/current/output/html/part04.html#sect_C.4.1.1.3.2 | 66 * http://dicom.nema.org/medical/dicom/current/output/html/part04.html#sect_C.4.1.1.3.2 |
328 // Do not include the encoding, this is handled by class ParsedDicomFile | 80 // Do not include the encoding, this is handled by class ParsedDicomFile |
329 } | 81 } |
330 else | 82 else |
331 { | 83 { |
332 const DicomTag& tag = query.GetElement(i).GetTag(); | 84 const DicomTag& tag = query.GetElement(i).GetTag(); |
333 const DicomValue* value = match.TestAndGetValue(tag); | 85 const DicomValue* value = resource.tags_.TestAndGetValue(tag); |
334 | 86 |
335 if (value != NULL && | 87 if (value != NULL && |
336 !value->IsNull() && | 88 !value->IsNull() && |
337 !value->IsBinary()) | 89 !value->IsBinary()) |
338 { | 90 { |
340 } | 92 } |
341 else | 93 else |
342 { | 94 { |
343 result.SetValue(tag, "", false); | 95 result.SetValue(tag, "", false); |
344 } | 96 } |
345 } | |
346 } | |
347 | |
348 if (counters != NULL) | |
349 { | |
350 DicomArray tmp(*counters); | |
351 for (size_t i = 0; i < tmp.GetSize(); i++) | |
352 { | |
353 result.SetValue(tmp.GetElement(i).GetTag(), tmp.GetElement(i).GetValue().GetContent(), false); | |
354 } | 97 } |
355 } | 98 } |
356 | 99 |
357 if (result.GetSize() == 0 && | 100 if (result.GetSize() == 0 && |
358 sequencesToReturn.empty()) | 101 sequencesToReturn.empty()) |
561 virtual void Visit(const std::string& publicId, | 304 virtual void Visit(const std::string& publicId, |
562 const std::string& instanceId, | 305 const std::string& instanceId, |
563 const DicomMap& mainDicomTags, | 306 const DicomMap& mainDicomTags, |
564 const Json::Value* dicomAsJson) ORTHANC_OVERRIDE | 307 const Json::Value* dicomAsJson) ORTHANC_OVERRIDE |
565 { | 308 { |
566 std::unique_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_)); | 309 AddAnswer(answers_, context_, publicId, instanceId, mainDicomTags, dicomAsJson, level_, queryAsArray_, sequencesToReturn_, |
567 | 310 defaultPrivateCreator_, privateCreators_, retrieveAet_); |
568 AddAnswer(answers_, mainDicomTags, dicomAsJson, queryAsArray_, sequencesToReturn_, | |
569 counters.get(), defaultPrivateCreator_, privateCreators_, retrieveAet_); | |
570 } | 311 } |
571 }; | 312 }; |
572 | 313 |
573 | 314 |
574 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, | 315 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, |