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