Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancFindRequestHandler.cpp @ 4092:fb64d481940a
making the "framework" branch the new "default"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 30 Jun 2020 15:53:17 +0200 |
parents | 9214e3a7b0a2 |
children | c02a2d9efbc2 |
comparison
equal
deleted
inserted
replaced
4089:a2060a76ed6a | 4092:fb64d481940a |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "PrecompiledHeadersServer.h" | |
35 #include "OrthancFindRequestHandler.h" | |
36 | |
37 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" | |
38 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" | |
39 #include "../../OrthancFramework/Sources/Logging.h" | |
40 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h" | |
41 #include "../../OrthancFramework/Sources/MetricsRegistry.h" | |
42 #include "OrthancConfiguration.h" | |
43 #include "Search/DatabaseLookup.h" | |
44 #include "ServerContext.h" | |
45 #include "ServerToolbox.h" | |
46 | |
47 #include <boost/regex.hpp> | |
48 | |
49 | |
50 namespace Orthanc | |
51 { | |
52 static void GetChildren(std::list<std::string>& target, | |
53 ServerIndex& index, | |
54 const std::list<std::string>& source) | |
55 { | |
56 target.clear(); | |
57 | |
58 for (std::list<std::string>::const_iterator | |
59 it = source.begin(); it != source.end(); ++it) | |
60 { | |
61 std::list<std::string> tmp; | |
62 index.GetChildren(tmp, *it); | |
63 target.splice(target.end(), tmp); | |
64 } | |
65 } | |
66 | |
67 | |
68 static void StoreSetOfStrings(DicomMap& result, | |
69 const DicomTag& tag, | |
70 const std::set<std::string>& values) | |
71 { | |
72 bool isFirst = true; | |
73 | |
74 std::string s; | |
75 for (std::set<std::string>::const_iterator | |
76 it = values.begin(); it != values.end(); ++it) | |
77 { | |
78 if (isFirst) | |
79 { | |
80 isFirst = false; | |
81 } | |
82 else | |
83 { | |
84 s += "\\"; | |
85 } | |
86 | |
87 s += *it; | |
88 } | |
89 | |
90 result.SetValue(tag, s, false); | |
91 } | |
92 | |
93 | |
94 static void ComputePatientCounters(DicomMap& result, | |
95 ServerIndex& index, | |
96 const std::string& patient, | |
97 const DicomMap& query) | |
98 { | |
99 std::list<std::string> studies; | |
100 index.GetChildren(studies, patient); | |
101 | |
102 if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES)) | |
103 { | |
104 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, | |
105 boost::lexical_cast<std::string>(studies.size()), false); | |
106 } | |
107 | |
108 if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && | |
109 !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
110 { | |
111 return; | |
112 } | |
113 | |
114 std::list<std::string> series; | |
115 GetChildren(series, index, studies); | |
116 studies.clear(); // This information is useless below | |
117 | |
118 if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES)) | |
119 { | |
120 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, | |
121 boost::lexical_cast<std::string>(series.size()), false); | |
122 } | |
123 | |
124 if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
125 { | |
126 return; | |
127 } | |
128 | |
129 std::list<std::string> instances; | |
130 GetChildren(instances, index, series); | |
131 | |
132 if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
133 { | |
134 result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, | |
135 boost::lexical_cast<std::string>(instances.size()), false); | |
136 } | |
137 } | |
138 | |
139 | |
140 static void ComputeStudyCounters(DicomMap& result, | |
141 ServerContext& context, | |
142 const std::string& study, | |
143 const DicomMap& query) | |
144 { | |
145 ServerIndex& index = context.GetIndex(); | |
146 | |
147 std::list<std::string> series; | |
148 index.GetChildren(series, study); | |
149 | |
150 if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES)) | |
151 { | |
152 result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, | |
153 boost::lexical_cast<std::string>(series.size()), false); | |
154 } | |
155 | |
156 if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | |
157 { | |
158 std::set<std::string> values; | |
159 | |
160 for (std::list<std::string>::const_iterator | |
161 it = series.begin(); it != series.end(); ++it) | |
162 { | |
163 DicomMap tags; | |
164 if (index.GetMainDicomTags(tags, *it, ResourceType_Series, ResourceType_Series)) | |
165 { | |
166 const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY); | |
167 | |
168 if (value != NULL && | |
169 !value->IsNull() && | |
170 !value->IsBinary()) | |
171 { | |
172 values.insert(value->GetContent()); | |
173 } | |
174 } | |
175 } | |
176 | |
177 StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values); | |
178 } | |
179 | |
180 if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && | |
181 !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) | |
182 { | |
183 return; | |
184 } | |
185 | |
186 std::list<std::string> instances; | |
187 GetChildren(instances, index, series); | |
188 | |
189 if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES)) | |
190 { | |
191 result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, | |
192 boost::lexical_cast<std::string>(instances.size()), false); | |
193 } | |
194 | |
195 if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) | |
196 { | |
197 std::set<std::string> values; | |
198 | |
199 for (std::list<std::string>::const_iterator | |
200 it = instances.begin(); it != instances.end(); ++it) | |
201 { | |
202 std::string value; | |
203 if (context.LookupOrReconstructMetadata(value, *it, MetadataType_Instance_SopClassUid)) | |
204 { | |
205 values.insert(value); | |
206 } | |
207 } | |
208 | |
209 StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); | |
210 } | |
211 } | |
212 | |
213 | |
214 static void ComputeSeriesCounters(DicomMap& result, | |
215 ServerIndex& index, | |
216 const std::string& series, | |
217 const DicomMap& query) | |
218 { | |
219 std::list<std::string> instances; | |
220 index.GetChildren(instances, series); | |
221 | |
222 if (query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) | |
223 { | |
224 result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, | |
225 boost::lexical_cast<std::string>(instances.size()), false); | |
226 } | |
227 } | |
228 | |
229 | |
230 static DicomMap* ComputeCounters(ServerContext& context, | |
231 const std::string& instanceId, | |
232 ResourceType level, | |
233 const DicomMap& query) | |
234 { | |
235 switch (level) | |
236 { | |
237 case ResourceType_Patient: | |
238 if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) && | |
239 !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && | |
240 !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) | |
241 { | |
242 return NULL; | |
243 } | |
244 | |
245 break; | |
246 | |
247 case ResourceType_Study: | |
248 if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) && | |
249 !query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && | |
250 !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) && | |
251 !query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) | |
252 { | |
253 return NULL; | |
254 } | |
255 | |
256 break; | |
257 | |
258 case ResourceType_Series: | |
259 if (!query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) | |
260 { | |
261 return NULL; | |
262 } | |
263 | |
264 break; | |
265 | |
266 default: | |
267 return NULL; | |
268 } | |
269 | |
270 std::string parent; | |
271 if (!context.GetIndex().LookupParent(parent, instanceId, level)) | |
272 { | |
273 throw OrthancException(ErrorCode_UnknownResource); // The resource was deleted in between | |
274 } | |
275 | |
276 std::unique_ptr<DicomMap> result(new DicomMap); | |
277 | |
278 switch (level) | |
279 { | |
280 case ResourceType_Patient: | |
281 ComputePatientCounters(*result, context.GetIndex(), parent, query); | |
282 break; | |
283 | |
284 case ResourceType_Study: | |
285 ComputeStudyCounters(*result, context, parent, query); | |
286 break; | |
287 | |
288 case ResourceType_Series: | |
289 ComputeSeriesCounters(*result, context.GetIndex(), parent, query); | |
290 break; | |
291 | |
292 default: | |
293 throw OrthancException(ErrorCode_InternalError); | |
294 } | |
295 | |
296 return result.release(); | |
297 } | |
298 | |
299 | |
300 static void AddAnswer(DicomFindAnswers& answers, | |
301 const DicomMap& mainDicomTags, | |
302 const Json::Value* dicomAsJson, | |
303 const DicomArray& query, | |
304 const std::list<DicomTag>& sequencesToReturn, | |
305 const DicomMap* counters, | |
306 const std::string& defaultPrivateCreator, | |
307 const std::map<uint16_t, std::string>& privateCreators) | |
308 { | |
309 DicomMap match; | |
310 | |
311 if (dicomAsJson != NULL) | |
312 { | |
313 match.FromDicomAsJson(*dicomAsJson); | |
314 } | |
315 else | |
316 { | |
317 match.Assign(mainDicomTags); | |
318 } | |
319 | |
320 DicomMap result; | |
321 | |
322 for (size_t i = 0; i < query.GetSize(); i++) | |
323 { | |
324 if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) | |
325 { | |
326 // Fix issue 30 on Google Code (QR response missing "Query/Retrieve Level" (008,0052)) | |
327 result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue()); | |
328 } | |
329 else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) | |
330 { | |
331 // Do not include the encoding, this is handled by class ParsedDicomFile | |
332 } | |
333 else | |
334 { | |
335 const DicomTag& tag = query.GetElement(i).GetTag(); | |
336 const DicomValue* value = match.TestAndGetValue(tag); | |
337 | |
338 if (value != NULL && | |
339 !value->IsNull() && | |
340 !value->IsBinary()) | |
341 { | |
342 result.SetValue(tag, value->GetContent(), false); | |
343 } | |
344 else | |
345 { | |
346 result.SetValue(tag, "", false); | |
347 } | |
348 } | |
349 } | |
350 | |
351 if (counters != NULL) | |
352 { | |
353 DicomArray tmp(*counters); | |
354 for (size_t i = 0; i < tmp.GetSize(); i++) | |
355 { | |
356 result.SetValue(tmp.GetElement(i).GetTag(), tmp.GetElement(i).GetValue().GetContent(), false); | |
357 } | |
358 } | |
359 | |
360 if (result.GetSize() == 0 && | |
361 sequencesToReturn.empty()) | |
362 { | |
363 LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; | |
364 } | |
365 else if (sequencesToReturn.empty()) | |
366 { | |
367 answers.Add(result); | |
368 } | |
369 else if (dicomAsJson == NULL) | |
370 { | |
371 LOG(WARNING) << "C-FIND query requesting a sequence, but reading JSON from disk is disabled"; | |
372 answers.Add(result); | |
373 } | |
374 else | |
375 { | |
376 ParsedDicomFile dicom(result, GetDefaultDicomEncoding(), | |
377 true /* be permissive, cf. issue #136 */, defaultPrivateCreator, privateCreators); | |
378 | |
379 for (std::list<DicomTag>::const_iterator tag = sequencesToReturn.begin(); | |
380 tag != sequencesToReturn.end(); ++tag) | |
381 { | |
382 assert(dicomAsJson != NULL); | |
383 const Json::Value& source = (*dicomAsJson) [tag->Format()]; | |
384 | |
385 if (source.type() == Json::objectValue && | |
386 source.isMember("Type") && | |
387 source.isMember("Value") && | |
388 source["Type"].asString() == "Sequence" && | |
389 source["Value"].type() == Json::arrayValue) | |
390 { | |
391 Json::Value content = Json::arrayValue; | |
392 | |
393 for (Json::Value::ArrayIndex i = 0; i < source["Value"].size(); i++) | |
394 { | |
395 Json::Value item; | |
396 Toolbox::SimplifyDicomAsJson(item, source["Value"][i], DicomToJsonFormat_Short); | |
397 content.append(item); | |
398 } | |
399 | |
400 if (tag->IsPrivate()) | |
401 { | |
402 std::map<uint16_t, std::string>::const_iterator found = privateCreators.find(tag->GetGroup()); | |
403 | |
404 if (found != privateCreators.end()) | |
405 { | |
406 dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, found->second.c_str()); | |
407 } | |
408 else | |
409 { | |
410 dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, defaultPrivateCreator); | |
411 } | |
412 } | |
413 else | |
414 { | |
415 dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, "" /* no private creator */); | |
416 } | |
417 } | |
418 } | |
419 | |
420 answers.Add(dicom); | |
421 } | |
422 } | |
423 | |
424 | |
425 | |
426 bool OrthancFindRequestHandler::FilterQueryTag(std::string& value /* can be modified */, | |
427 ResourceType level, | |
428 const DicomTag& tag, | |
429 ModalityManufacturer manufacturer) | |
430 { | |
431 // Whatever the manufacturer, remove the GenericGroupLength tags | |
432 // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.2.html | |
433 // https://bitbucket.org/sjodogne/orthanc/issues/31/ | |
434 if (tag.GetElement() == 0x0000) | |
435 { | |
436 return false; | |
437 } | |
438 | |
439 switch (manufacturer) | |
440 { | |
441 case ModalityManufacturer_Vitrea: | |
442 // Following Denis Nesterov's mail on 2015-11-30 | |
443 if (tag == DicomTag(0x5653, 0x0010)) // "PrivateCreator = Vital Images SW 3.4" | |
444 { | |
445 return false; | |
446 } | |
447 | |
448 break; | |
449 | |
450 default: | |
451 break; | |
452 } | |
453 | |
454 return true; | |
455 } | |
456 | |
457 | |
458 bool OrthancFindRequestHandler::ApplyLuaFilter(DicomMap& target, | |
459 const DicomMap& source, | |
460 const std::string& remoteIp, | |
461 const std::string& remoteAet, | |
462 const std::string& calledAet, | |
463 ModalityManufacturer manufacturer) | |
464 { | |
465 static const char* LUA_CALLBACK = "IncomingFindRequestFilter"; | |
466 | |
467 LuaScripting::Lock lock(context_.GetLuaScripting()); | |
468 | |
469 if (!lock.GetLua().IsExistingFunction(LUA_CALLBACK)) | |
470 { | |
471 return false; | |
472 } | |
473 else | |
474 { | |
475 Json::Value origin; | |
476 FormatOrigin(origin, remoteIp, remoteAet, calledAet, manufacturer); | |
477 | |
478 LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK); | |
479 call.PushDicom(source); | |
480 call.PushJson(origin); | |
481 FromDcmtkBridge::ExecuteToDicom(target, call); | |
482 | |
483 return true; | |
484 } | |
485 } | |
486 | |
487 | |
488 OrthancFindRequestHandler::OrthancFindRequestHandler(ServerContext& context) : | |
489 context_(context), | |
490 maxResults_(0), | |
491 maxInstances_(0) | |
492 { | |
493 } | |
494 | |
495 | |
496 class OrthancFindRequestHandler::LookupVisitor : public ServerContext::ILookupVisitor | |
497 { | |
498 private: | |
499 DicomFindAnswers& answers_; | |
500 ServerContext& context_; | |
501 ResourceType level_; | |
502 const DicomMap& query_; | |
503 DicomArray queryAsArray_; | |
504 const std::list<DicomTag>& sequencesToReturn_; | |
505 std::string defaultPrivateCreator_; // the private creator to use if the group is not defined in the query itself | |
506 const std::map<uint16_t, std::string>& privateCreators_; // the private creators defined in the query itself | |
507 | |
508 public: | |
509 LookupVisitor(DicomFindAnswers& answers, | |
510 ServerContext& context, | |
511 ResourceType level, | |
512 const DicomMap& query, | |
513 const std::list<DicomTag>& sequencesToReturn, | |
514 const std::map<uint16_t, std::string>& privateCreators) : | |
515 answers_(answers), | |
516 context_(context), | |
517 level_(level), | |
518 query_(query), | |
519 queryAsArray_(query), | |
520 sequencesToReturn_(sequencesToReturn), | |
521 privateCreators_(privateCreators) | |
522 { | |
523 answers_.SetComplete(false); | |
524 | |
525 { | |
526 OrthancConfiguration::ReaderLock lock; | |
527 defaultPrivateCreator_ = lock.GetConfiguration().GetDefaultPrivateCreator(); | |
528 } | |
529 } | |
530 | |
531 virtual bool IsDicomAsJsonNeeded() const | |
532 { | |
533 // Ask the "DICOM-as-JSON" attachment only if sequences are to | |
534 // be returned OR if "query_" contains non-main DICOM tags! | |
535 | |
536 DicomMap withoutSpecialTags; | |
537 withoutSpecialTags.Assign(query_); | |
538 | |
539 // Check out "ComputeCounters()" | |
540 withoutSpecialTags.Remove(DICOM_TAG_MODALITIES_IN_STUDY); | |
541 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); | |
542 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); | |
543 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); | |
544 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); | |
545 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); | |
546 withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); | |
547 withoutSpecialTags.Remove(DICOM_TAG_SOP_CLASSES_IN_STUDY); | |
548 | |
549 // Check out "AddAnswer()" | |
550 withoutSpecialTags.Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET); | |
551 withoutSpecialTags.Remove(DICOM_TAG_QUERY_RETRIEVE_LEVEL); | |
552 | |
553 return (!sequencesToReturn_.empty() || | |
554 !withoutSpecialTags.HasOnlyMainDicomTags()); | |
555 } | |
556 | |
557 virtual void MarkAsComplete() | |
558 { | |
559 answers_.SetComplete(true); | |
560 } | |
561 | |
562 virtual void Visit(const std::string& publicId, | |
563 const std::string& instanceId, | |
564 const DicomMap& mainDicomTags, | |
565 const Json::Value* dicomAsJson) | |
566 { | |
567 std::unique_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_)); | |
568 | |
569 AddAnswer(answers_, mainDicomTags, dicomAsJson, | |
570 queryAsArray_, sequencesToReturn_, counters.get(), defaultPrivateCreator_, privateCreators_); | |
571 } | |
572 }; | |
573 | |
574 | |
575 void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, | |
576 const DicomMap& input, | |
577 const std::list<DicomTag>& sequencesToReturn, | |
578 const std::string& remoteIp, | |
579 const std::string& remoteAet, | |
580 const std::string& calledAet, | |
581 ModalityManufacturer manufacturer) | |
582 { | |
583 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_find_scp_duration_ms"); | |
584 | |
585 /** | |
586 * Possibly apply the user-supplied Lua filter. | |
587 **/ | |
588 | |
589 DicomMap lua; | |
590 const DicomMap* filteredInput = &input; | |
591 | |
592 if (ApplyLuaFilter(lua, input, remoteIp, remoteAet, calledAet, manufacturer)) | |
593 { | |
594 filteredInput = &lua; | |
595 } | |
596 | |
597 | |
598 /** | |
599 * Retrieve the query level. | |
600 **/ | |
601 | |
602 assert(filteredInput != NULL); | |
603 const DicomValue* levelTmp = filteredInput->TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); | |
604 if (levelTmp == NULL || | |
605 levelTmp->IsNull() || | |
606 levelTmp->IsBinary()) | |
607 { | |
608 throw OrthancException(ErrorCode_BadRequest, | |
609 "C-FIND request without the tag 0008,0052 (QueryRetrieveLevel)"); | |
610 } | |
611 | |
612 ResourceType level = StringToResourceType(levelTmp->GetContent().c_str()); | |
613 | |
614 if (level != ResourceType_Patient && | |
615 level != ResourceType_Study && | |
616 level != ResourceType_Series && | |
617 level != ResourceType_Instance) | |
618 { | |
619 throw OrthancException(ErrorCode_NotImplemented); | |
620 } | |
621 | |
622 | |
623 DicomArray query(*filteredInput); | |
624 LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level); | |
625 | |
626 for (size_t i = 0; i < query.GetSize(); i++) | |
627 { | |
628 if (!query.GetElement(i).GetValue().IsNull()) | |
629 { | |
630 LOG(INFO) << " " << query.GetElement(i).GetTag() | |
631 << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) | |
632 << " = " << query.GetElement(i).GetValue().GetContent(); | |
633 } | |
634 } | |
635 | |
636 for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin(); | |
637 it != sequencesToReturn.end(); ++it) | |
638 { | |
639 LOG(INFO) << " (" << it->Format() | |
640 << ") " << FromDcmtkBridge::GetTagName(*it, "") | |
641 << " : sequence tag whose content will be copied"; | |
642 } | |
643 | |
644 // collect the private creators from the query itself | |
645 std::map<uint16_t, std::string> privateCreators; | |
646 for (size_t i = 0; i < query.GetSize(); i++) | |
647 { | |
648 const DicomElement& element = query.GetElement(i); | |
649 if (element.GetTag().IsPrivate() && element.GetTag().GetElement() == 0x10) | |
650 { | |
651 privateCreators[element.GetTag().GetGroup()] = element.GetValue().GetContent(); | |
652 } | |
653 } | |
654 | |
655 /** | |
656 * Build up the query object. | |
657 **/ | |
658 | |
659 DatabaseLookup lookup; | |
660 | |
661 bool caseSensitivePN; | |
662 | |
663 { | |
664 OrthancConfiguration::ReaderLock lock; | |
665 caseSensitivePN = lock.GetConfiguration().GetBooleanParameter("CaseSensitivePN", false); | |
666 } | |
667 | |
668 for (size_t i = 0; i < query.GetSize(); i++) | |
669 { | |
670 const DicomElement& element = query.GetElement(i); | |
671 const DicomTag tag = element.GetTag(); | |
672 | |
673 if (element.GetValue().IsNull() || | |
674 tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || | |
675 tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) | |
676 { | |
677 continue; | |
678 } | |
679 | |
680 std::string value = element.GetValue().GetContent(); | |
681 if (value.size() == 0) | |
682 { | |
683 // An empty string corresponds to an universal constraint, so we ignore it | |
684 continue; | |
685 } | |
686 | |
687 if (FilterQueryTag(value, level, tag, manufacturer)) | |
688 { | |
689 ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); | |
690 | |
691 // DICOM specifies that searches must be case sensitive, except | |
692 // for tags with a PN value representation | |
693 bool sensitive = true; | |
694 if (vr == ValueRepresentation_PersonName) | |
695 { | |
696 sensitive = caseSensitivePN; | |
697 } | |
698 | |
699 lookup.AddDicomConstraint(tag, value, sensitive, true /* mandatory */); | |
700 } | |
701 else | |
702 { | |
703 LOG(INFO) << "Because of a patch for the manufacturer of the remote modality, " | |
704 << "ignoring constraint on tag (" << tag.Format() << ") " << FromDcmtkBridge::GetTagName(element); | |
705 } | |
706 } | |
707 | |
708 | |
709 /** | |
710 * Run the query. | |
711 **/ | |
712 | |
713 size_t limit = (level == ResourceType_Instance) ? maxInstances_ : maxResults_; | |
714 | |
715 | |
716 LookupVisitor visitor(answers, context_, level, *filteredInput, sequencesToReturn, privateCreators); | |
717 context_.Apply(visitor, lookup, level, 0 /* "since" is not relevant to C-FIND */, limit); | |
718 } | |
719 | |
720 | |
721 void OrthancFindRequestHandler::FormatOrigin(Json::Value& origin, | |
722 const std::string& remoteIp, | |
723 const std::string& remoteAet, | |
724 const std::string& calledAet, | |
725 ModalityManufacturer manufacturer) | |
726 { | |
727 origin = Json::objectValue; | |
728 origin["RemoteIp"] = remoteIp; | |
729 origin["RemoteAet"] = remoteAet; | |
730 origin["CalledAet"] = calledAet; | |
731 origin["Manufacturer"] = EnumerationToString(manufacturer); | |
732 } | |
733 } |