comparison OrthancServer/Sources/OrthancFindRequestHandler.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents OrthancServer/OrthancFindRequestHandler.cpp@c6e82885f570
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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 "../Core/DicomFormat/DicomArray.h"
38 #include "../Core/DicomParsing/FromDcmtkBridge.h"
39 #include "../Core/Logging.h"
40 #include "../Core/Lua/LuaFunctionCall.h"
41 #include "../Core/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 ServerToolbox::SimplifyTags(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 }