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