Mercurial > hg > orthanc
comparison OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.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 | Core/DicomNetworking/DicomControlUserConnection.cpp@cc6ed76bba27 |
children | bf7b9edf6b81 |
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 "../PrecompiledHeaders.h" | |
35 #include "DicomControlUserConnection.h" | |
36 | |
37 #include "../Compatibility.h" | |
38 #include "../DicomFormat/DicomArray.h" | |
39 #include "../DicomParsing/FromDcmtkBridge.h" | |
40 #include "../Logging.h" | |
41 #include "../OrthancException.h" | |
42 #include "DicomAssociation.h" | |
43 | |
44 #include <dcmtk/dcmdata/dcdeftag.h> | |
45 #include <dcmtk/dcmnet/diutil.h> | |
46 | |
47 namespace Orthanc | |
48 { | |
49 static void TestAndCopyTag(DicomMap& result, | |
50 const DicomMap& source, | |
51 const DicomTag& tag) | |
52 { | |
53 if (!source.HasTag(tag)) | |
54 { | |
55 throw OrthancException(ErrorCode_BadRequest); | |
56 } | |
57 else | |
58 { | |
59 result.SetValue(tag, source.GetValue(tag)); | |
60 } | |
61 } | |
62 | |
63 | |
64 namespace | |
65 { | |
66 struct FindPayload | |
67 { | |
68 DicomFindAnswers* answers; | |
69 const char* level; | |
70 bool isWorklist; | |
71 }; | |
72 } | |
73 | |
74 | |
75 static void FindCallback( | |
76 /* in */ | |
77 void *callbackData, | |
78 T_DIMSE_C_FindRQ *request, /* original find request */ | |
79 int responseCount, | |
80 T_DIMSE_C_FindRSP *response, /* pending response received */ | |
81 DcmDataset *responseIdentifiers /* pending response identifiers */ | |
82 ) | |
83 { | |
84 FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData); | |
85 | |
86 if (responseIdentifiers != NULL) | |
87 { | |
88 if (payload.isWorklist) | |
89 { | |
90 ParsedDicomFile answer(*responseIdentifiers); | |
91 payload.answers->Add(answer); | |
92 } | |
93 else | |
94 { | |
95 DicomMap m; | |
96 FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); | |
97 | |
98 if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) | |
99 { | |
100 m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); | |
101 } | |
102 | |
103 payload.answers->Add(m); | |
104 } | |
105 } | |
106 } | |
107 | |
108 | |
109 static void NormalizeFindQuery(DicomMap& fixedQuery, | |
110 ResourceType level, | |
111 const DicomMap& fields) | |
112 { | |
113 std::set<DicomTag> allowedTags; | |
114 | |
115 // WARNING: Do not add "break" or reorder items in this switch-case! | |
116 switch (level) | |
117 { | |
118 case ResourceType_Instance: | |
119 DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); | |
120 | |
121 case ResourceType_Series: | |
122 DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); | |
123 | |
124 case ResourceType_Study: | |
125 DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); | |
126 | |
127 case ResourceType_Patient: | |
128 DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); | |
129 break; | |
130 | |
131 default: | |
132 throw OrthancException(ErrorCode_InternalError); | |
133 } | |
134 | |
135 switch (level) | |
136 { | |
137 case ResourceType_Patient: | |
138 allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); | |
139 allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); | |
140 allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); | |
141 break; | |
142 | |
143 case ResourceType_Study: | |
144 allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); | |
145 allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); | |
146 allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); | |
147 allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); | |
148 break; | |
149 | |
150 case ResourceType_Series: | |
151 allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); | |
152 break; | |
153 | |
154 default: | |
155 break; | |
156 } | |
157 | |
158 allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); | |
159 | |
160 DicomArray query(fields); | |
161 for (size_t i = 0; i < query.GetSize(); i++) | |
162 { | |
163 const DicomTag& tag = query.GetElement(i).GetTag(); | |
164 if (allowedTags.find(tag) == allowedTags.end()) | |
165 { | |
166 LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; | |
167 } | |
168 else | |
169 { | |
170 fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); | |
171 } | |
172 } | |
173 } | |
174 | |
175 | |
176 | |
177 static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, | |
178 ModalityManufacturer manufacturer) | |
179 { | |
180 // Fix outgoing C-Find requests issue for Syngo.Via and its | |
181 // solution was reported by Emsy Chan by private mail on | |
182 // 2015-06-17. According to Robert van Ommen (2015-11-30), the | |
183 // same fix is required for Agfa Impax. This was generalized for | |
184 // generic manufacturer since it seems to affect PhilipsADW, | |
185 // GEWAServer as well: | |
186 // https://bitbucket.org/sjodogne/orthanc/issues/31/ | |
187 | |
188 switch (manufacturer) | |
189 { | |
190 case ModalityManufacturer_GenericNoWildcardInDates: | |
191 case ModalityManufacturer_GenericNoUniversalWildcard: | |
192 { | |
193 std::unique_ptr<DicomMap> fix(fields.Clone()); | |
194 | |
195 std::set<DicomTag> tags; | |
196 fix->GetTags(tags); | |
197 | |
198 for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) | |
199 { | |
200 // Replace a "*" wildcard query by an empty query ("") for | |
201 // "date" or "all" value representations depending on the | |
202 // type of manufacturer. | |
203 if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || | |
204 (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && | |
205 FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) | |
206 { | |
207 const DicomValue* value = fix->TestAndGetValue(*it); | |
208 | |
209 if (value != NULL && | |
210 !value->IsNull() && | |
211 value->GetContent() == "*") | |
212 { | |
213 fix->SetValue(*it, "", false); | |
214 } | |
215 } | |
216 } | |
217 | |
218 return new ParsedDicomFile(*fix, GetDefaultDicomEncoding(), | |
219 false /* be strict */); | |
220 } | |
221 | |
222 default: | |
223 return new ParsedDicomFile(fields, GetDefaultDicomEncoding(), | |
224 false /* be strict */); | |
225 } | |
226 } | |
227 | |
228 | |
229 | |
230 void DicomControlUserConnection::SetupPresentationContexts() | |
231 { | |
232 assert(association_.get() != NULL); | |
233 association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); | |
234 association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); | |
235 association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); | |
236 association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); | |
237 association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); | |
238 } | |
239 | |
240 | |
241 void DicomControlUserConnection::FindInternal(DicomFindAnswers& answers, | |
242 DcmDataset* dataset, | |
243 const char* sopClass, | |
244 bool isWorklist, | |
245 const char* level) | |
246 { | |
247 assert(isWorklist ^ (level != NULL)); | |
248 assert(association_.get() != NULL); | |
249 | |
250 association_->Open(parameters_); | |
251 | |
252 FindPayload payload; | |
253 payload.answers = &answers; | |
254 payload.level = level; | |
255 payload.isWorklist = isWorklist; | |
256 | |
257 // Figure out which of the accepted presentation contexts should be used | |
258 int presID = ASC_findAcceptedPresentationContextID( | |
259 &association_->GetDcmtkAssociation(), sopClass); | |
260 if (presID == 0) | |
261 { | |
262 throw OrthancException(ErrorCode_DicomFindUnavailable, | |
263 "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); | |
264 } | |
265 | |
266 T_DIMSE_C_FindRQ request; | |
267 memset(&request, 0, sizeof(request)); | |
268 request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; | |
269 strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); | |
270 request.Priority = DIMSE_PRIORITY_MEDIUM; | |
271 request.DataSetType = DIMSE_DATASET_PRESENT; | |
272 | |
273 T_DIMSE_C_FindRSP response; | |
274 DcmDataset* statusDetail = NULL; | |
275 | |
276 #if DCMTK_VERSION_NUMBER >= 364 | |
277 int responseCount; | |
278 #endif | |
279 | |
280 OFCondition cond = DIMSE_findUser( | |
281 &association_->GetDcmtkAssociation(), presID, &request, dataset, | |
282 #if DCMTK_VERSION_NUMBER >= 364 | |
283 responseCount, | |
284 #endif | |
285 FindCallback, &payload, | |
286 /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
287 /*opt_dimse_timeout*/ parameters_.GetTimeout(), | |
288 &response, &statusDetail); | |
289 | |
290 if (statusDetail) | |
291 { | |
292 delete statusDetail; | |
293 } | |
294 | |
295 DicomAssociation::CheckCondition(cond, parameters_, "C-FIND"); | |
296 | |
297 | |
298 /** | |
299 * New in Orthanc 1.6.0: Deal with failures during C-FIND. | |
300 * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1 | |
301 **/ | |
302 | |
303 if (response.DimseStatus != 0x0000 && // Success | |
304 response.DimseStatus != 0xFF00 && // Pending - Matches are continuing | |
305 response.DimseStatus != 0xFF01) // Pending - Matches are continuing | |
306 { | |
307 char buf[16]; | |
308 sprintf(buf, "%04X", response.DimseStatus); | |
309 | |
310 if (response.DimseStatus == STATUS_FIND_Failed_UnableToProcess) | |
311 { | |
312 throw OrthancException(ErrorCode_NetworkProtocol, | |
313 HttpStatus_422_UnprocessableEntity, | |
314 "C-FIND SCU to AET \"" + | |
315 parameters_.GetRemoteModality().GetApplicationEntityTitle() + | |
316 "\" has failed with DIMSE status 0x" + buf + | |
317 " (unable to process - invalid query ?)"); | |
318 } | |
319 else | |
320 { | |
321 throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" + | |
322 parameters_.GetRemoteModality().GetApplicationEntityTitle() + | |
323 "\" has failed with DIMSE status 0x" + buf); | |
324 } | |
325 } | |
326 } | |
327 | |
328 | |
329 void DicomControlUserConnection::MoveInternal(const std::string& targetAet, | |
330 ResourceType level, | |
331 const DicomMap& fields) | |
332 { | |
333 assert(association_.get() != NULL); | |
334 association_->Open(parameters_); | |
335 | |
336 std::unique_ptr<ParsedDicomFile> query( | |
337 ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer())); | |
338 DcmDataset* dataset = query->GetDcmtkObject().getDataset(); | |
339 | |
340 const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; | |
341 switch (level) | |
342 { | |
343 case ResourceType_Patient: | |
344 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); | |
345 break; | |
346 | |
347 case ResourceType_Study: | |
348 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); | |
349 break; | |
350 | |
351 case ResourceType_Series: | |
352 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); | |
353 break; | |
354 | |
355 case ResourceType_Instance: | |
356 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); | |
357 break; | |
358 | |
359 default: | |
360 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
361 } | |
362 | |
363 // Figure out which of the accepted presentation contexts should be used | |
364 int presID = ASC_findAcceptedPresentationContextID(&association_->GetDcmtkAssociation(), sopClass); | |
365 if (presID == 0) | |
366 { | |
367 throw OrthancException(ErrorCode_DicomMoveUnavailable, | |
368 "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); | |
369 } | |
370 | |
371 T_DIMSE_C_MoveRQ request; | |
372 memset(&request, 0, sizeof(request)); | |
373 request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; | |
374 strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); | |
375 request.Priority = DIMSE_PRIORITY_MEDIUM; | |
376 request.DataSetType = DIMSE_DATASET_PRESENT; | |
377 strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); | |
378 | |
379 T_DIMSE_C_MoveRSP response; | |
380 DcmDataset* statusDetail = NULL; | |
381 DcmDataset* responseIdentifiers = NULL; | |
382 OFCondition cond = DIMSE_moveUser( | |
383 &association_->GetDcmtkAssociation(), presID, &request, dataset, NULL, NULL, | |
384 /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
385 /*opt_dimse_timeout*/ parameters_.GetTimeout(), | |
386 &association_->GetDcmtkNetwork(), NULL, NULL, | |
387 &response, &statusDetail, &responseIdentifiers); | |
388 | |
389 if (statusDetail) | |
390 { | |
391 delete statusDetail; | |
392 } | |
393 | |
394 if (responseIdentifiers) | |
395 { | |
396 delete responseIdentifiers; | |
397 } | |
398 | |
399 DicomAssociation::CheckCondition(cond, parameters_, "C-MOVE"); | |
400 | |
401 | |
402 /** | |
403 * New in Orthanc 1.6.0: Deal with failures during C-MOVE. | |
404 * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2 | |
405 **/ | |
406 | |
407 if (response.DimseStatus != 0x0000 && // Success | |
408 response.DimseStatus != 0xFF00) // Pending - Sub-operations are continuing | |
409 { | |
410 char buf[16]; | |
411 sprintf(buf, "%04X", response.DimseStatus); | |
412 | |
413 if (response.DimseStatus == STATUS_MOVE_Failed_UnableToProcess) | |
414 { | |
415 throw OrthancException(ErrorCode_NetworkProtocol, | |
416 HttpStatus_422_UnprocessableEntity, | |
417 "C-MOVE SCU to AET \"" + | |
418 parameters_.GetRemoteModality().GetApplicationEntityTitle() + | |
419 "\" has failed with DIMSE status 0x" + buf + | |
420 " (unable to process - resource not found ?)"); | |
421 } | |
422 else | |
423 { | |
424 throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" + | |
425 parameters_.GetRemoteModality().GetApplicationEntityTitle() + | |
426 "\" has failed with DIMSE status 0x" + buf); | |
427 } | |
428 } | |
429 } | |
430 | |
431 | |
432 DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) : | |
433 parameters_(params), | |
434 association_(new DicomAssociation) | |
435 { | |
436 SetupPresentationContexts(); | |
437 } | |
438 | |
439 | |
440 void DicomControlUserConnection::Close() | |
441 { | |
442 assert(association_.get() != NULL); | |
443 association_->Close(); | |
444 } | |
445 | |
446 | |
447 bool DicomControlUserConnection::Echo() | |
448 { | |
449 assert(association_.get() != NULL); | |
450 association_->Open(parameters_); | |
451 | |
452 DIC_US status; | |
453 DicomAssociation::CheckCondition( | |
454 DIMSE_echoUser(&association_->GetDcmtkAssociation(), | |
455 association_->GetDcmtkAssociation().nextMsgID++, | |
456 /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
457 /*opt_dimse_timeout*/ parameters_.GetTimeout(), | |
458 &status, NULL), | |
459 parameters_, "C-ECHO"); | |
460 | |
461 return status == STATUS_Success; | |
462 } | |
463 | |
464 | |
465 void DicomControlUserConnection::Find(DicomFindAnswers& result, | |
466 ResourceType level, | |
467 const DicomMap& originalFields, | |
468 bool normalize) | |
469 { | |
470 std::unique_ptr<ParsedDicomFile> query; | |
471 | |
472 if (normalize) | |
473 { | |
474 DicomMap fields; | |
475 NormalizeFindQuery(fields, level, originalFields); | |
476 query.reset(ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer())); | |
477 } | |
478 else | |
479 { | |
480 query.reset(new ParsedDicomFile(originalFields, GetDefaultDicomEncoding(), | |
481 false /* be strict */)); | |
482 } | |
483 | |
484 DcmDataset* dataset = query->GetDcmtkObject().getDataset(); | |
485 | |
486 const char* clevel = NULL; | |
487 const char* sopClass = NULL; | |
488 | |
489 switch (level) | |
490 { | |
491 case ResourceType_Patient: | |
492 clevel = "PATIENT"; | |
493 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); | |
494 sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; | |
495 break; | |
496 | |
497 case ResourceType_Study: | |
498 clevel = "STUDY"; | |
499 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); | |
500 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | |
501 break; | |
502 | |
503 case ResourceType_Series: | |
504 clevel = "SERIES"; | |
505 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); | |
506 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | |
507 break; | |
508 | |
509 case ResourceType_Instance: | |
510 clevel = "IMAGE"; | |
511 DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); | |
512 sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; | |
513 break; | |
514 | |
515 default: | |
516 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
517 } | |
518 | |
519 | |
520 const char* universal; | |
521 if (parameters_.GetRemoteModality().GetManufacturer() == ModalityManufacturer_GE) | |
522 { | |
523 universal = "*"; | |
524 } | |
525 else | |
526 { | |
527 universal = ""; | |
528 } | |
529 | |
530 | |
531 // Add the expected tags for this query level. | |
532 // WARNING: Do not reorder or add "break" in this switch-case! | |
533 switch (level) | |
534 { | |
535 case ResourceType_Instance: | |
536 if (!dataset->tagExists(DCM_SOPInstanceUID)) | |
537 { | |
538 DU_putStringDOElement(dataset, DCM_SOPInstanceUID, universal); | |
539 } | |
540 | |
541 case ResourceType_Series: | |
542 if (!dataset->tagExists(DCM_SeriesInstanceUID)) | |
543 { | |
544 DU_putStringDOElement(dataset, DCM_SeriesInstanceUID, universal); | |
545 } | |
546 | |
547 case ResourceType_Study: | |
548 if (!dataset->tagExists(DCM_AccessionNumber)) | |
549 { | |
550 DU_putStringDOElement(dataset, DCM_AccessionNumber, universal); | |
551 } | |
552 | |
553 if (!dataset->tagExists(DCM_StudyInstanceUID)) | |
554 { | |
555 DU_putStringDOElement(dataset, DCM_StudyInstanceUID, universal); | |
556 } | |
557 | |
558 case ResourceType_Patient: | |
559 if (!dataset->tagExists(DCM_PatientID)) | |
560 { | |
561 DU_putStringDOElement(dataset, DCM_PatientID, universal); | |
562 } | |
563 | |
564 break; | |
565 | |
566 default: | |
567 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
568 } | |
569 | |
570 assert(clevel != NULL && sopClass != NULL); | |
571 FindInternal(result, dataset, sopClass, false, clevel); | |
572 } | |
573 | |
574 | |
575 void DicomControlUserConnection::Move(const std::string& targetAet, | |
576 ResourceType level, | |
577 const DicomMap& findResult) | |
578 { | |
579 DicomMap move; | |
580 switch (level) | |
581 { | |
582 case ResourceType_Patient: | |
583 TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); | |
584 break; | |
585 | |
586 case ResourceType_Study: | |
587 TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); | |
588 break; | |
589 | |
590 case ResourceType_Series: | |
591 TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); | |
592 TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); | |
593 break; | |
594 | |
595 case ResourceType_Instance: | |
596 TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); | |
597 TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); | |
598 TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); | |
599 break; | |
600 | |
601 default: | |
602 throw OrthancException(ErrorCode_InternalError); | |
603 } | |
604 | |
605 MoveInternal(targetAet, level, move); | |
606 } | |
607 | |
608 | |
609 void DicomControlUserConnection::Move(const std::string& targetAet, | |
610 const DicomMap& findResult) | |
611 { | |
612 if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) | |
613 { | |
614 throw OrthancException(ErrorCode_InternalError); | |
615 } | |
616 | |
617 const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); | |
618 ResourceType level = StringToResourceType(tmp.c_str()); | |
619 | |
620 Move(targetAet, level, findResult); | |
621 } | |
622 | |
623 | |
624 void DicomControlUserConnection::MovePatient(const std::string& targetAet, | |
625 const std::string& patientId) | |
626 { | |
627 DicomMap query; | |
628 query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); | |
629 MoveInternal(targetAet, ResourceType_Patient, query); | |
630 } | |
631 | |
632 | |
633 void DicomControlUserConnection::MoveStudy(const std::string& targetAet, | |
634 const std::string& studyUid) | |
635 { | |
636 DicomMap query; | |
637 query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); | |
638 MoveInternal(targetAet, ResourceType_Study, query); | |
639 } | |
640 | |
641 | |
642 void DicomControlUserConnection::MoveSeries(const std::string& targetAet, | |
643 const std::string& studyUid, | |
644 const std::string& seriesUid) | |
645 { | |
646 DicomMap query; | |
647 query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); | |
648 query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); | |
649 MoveInternal(targetAet, ResourceType_Series, query); | |
650 } | |
651 | |
652 | |
653 void DicomControlUserConnection::MoveInstance(const std::string& targetAet, | |
654 const std::string& studyUid, | |
655 const std::string& seriesUid, | |
656 const std::string& instanceUid) | |
657 { | |
658 DicomMap query; | |
659 query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); | |
660 query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); | |
661 query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); | |
662 MoveInternal(targetAet, ResourceType_Instance, query); | |
663 } | |
664 | |
665 | |
666 void DicomControlUserConnection::FindWorklist(DicomFindAnswers& result, | |
667 ParsedDicomFile& query) | |
668 { | |
669 DcmDataset* dataset = query.GetDcmtkObject().getDataset(); | |
670 const char* sopClass = UID_FINDModalityWorklistInformationModel; | |
671 | |
672 FindInternal(result, dataset, sopClass, true, NULL); | |
673 } | |
674 } |