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,