Mercurial > hg > orthanc
comparison OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp @ 5853:4d932683049d get-scu tip
very first implementation of C-Get SCU
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 29 Oct 2024 17:25:49 +0100 |
parents | f7adfb22e20e |
children |
comparison
equal
deleted
inserted
replaced
5839:7aef730c0859 | 5853:4d932683049d |
---|---|
232 } | 232 } |
233 } | 233 } |
234 | 234 |
235 | 235 |
236 | 236 |
237 void DicomControlUserConnection::SetupPresentationContexts() | 237 void DicomControlUserConnection::SetupPresentationContexts() // TODO-GET, setup only the presentation contexts that are enabled for that modality |
238 { | 238 { |
239 assert(association_.get() != NULL); | 239 assert(association_.get() != NULL); |
240 association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); | 240 association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); |
241 association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); | 241 association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); |
242 association_->ProposeGenericPresentationContext(UID_MOVEPatientRootQueryRetrieveInformationModel); | 242 association_->ProposeGenericPresentationContext(UID_MOVEPatientRootQueryRetrieveInformationModel); |
243 association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); | 243 association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); |
244 association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); | 244 association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); |
245 association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); | 245 association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); |
246 association_->ProposeGenericPresentationContext(UID_GETStudyRootQueryRetrieveInformationModel); | |
247 association_->ProposeGenericPresentationContext(UID_GETPatientRootQueryRetrieveInformationModel); | |
248 | |
249 // for C-GET SCU, in order to receive the C-Store message TODO-GET: we need to refine this list based on what we know we are going to retrieve | |
250 association_->ProposeGenericPresentationContext(UID_ComputedRadiographyImageStorage); | |
251 association_->ProposeGenericPresentationContext(UID_MRImageStorage); | |
246 } | 252 } |
247 | 253 |
248 | 254 |
249 void DicomControlUserConnection::FindInternal(DicomFindAnswers& answers, | 255 void DicomControlUserConnection::FindInternal(DicomFindAnswers& answers, |
250 DcmDataset* dataset, | 256 DcmDataset* dataset, |
443 } | 449 } |
444 } | 450 } |
445 } | 451 } |
446 | 452 |
447 | 453 |
454 void DicomControlUserConnection::Get(const DicomMap& findResult, | |
455 CGetInstanceReceivedCallback instanceReceivedCallback, | |
456 void* callbackContext) | |
457 { | |
458 assert(association_.get() != NULL); | |
459 association_->Open(parameters_); | |
460 | |
461 // TODO-GET: if findResults is the result of a C-Find, we can use the SopClassUIDs for the negotiation | |
462 | |
463 std::unique_ptr<ParsedDicomFile> query( | |
464 ConvertQueryFields(findResult, parameters_.GetRemoteModality().GetManufacturer())); | |
465 DcmDataset* queryDataset = query->GetDcmtkObject().getDataset(); | |
466 | |
467 std::string remoteAet; | |
468 std::string remoteIp; | |
469 std::string calledAet; | |
470 | |
471 association_->GetAssociationParameters(remoteAet, remoteIp, calledAet); | |
472 | |
473 const char* sopClass = NULL; | |
474 const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); | |
475 ResourceType level = StringToResourceType(tmp.c_str()); | |
476 switch (level) | |
477 { | |
478 case ResourceType_Patient: | |
479 sopClass = UID_GETPatientRootQueryRetrieveInformationModel; | |
480 // DU_putStringDOElement(queryDataset, DCM_QueryRetrieveLevel, ResourceTypeToDicomQueryRetrieveLevel(ResourceType_Patient)); // TODO-GET | |
481 break; | |
482 case ResourceType_Study: | |
483 sopClass = UID_GETStudyRootQueryRetrieveInformationModel; | |
484 // DU_putStringDOElement(queryDataset, DCM_QueryRetrieveLevel, ResourceTypeToDicomQueryRetrieveLevel(ResourceType_Study)); // TODO-GET | |
485 break; | |
486 default: | |
487 throw OrthancException(ErrorCode_InternalError); // TODO-GET: implement series + instances | |
488 } | |
489 | |
490 // Figure out which of the accepted presentation contexts should be used | |
491 int cgetPresID = ASC_findAcceptedPresentationContextID(&association_->GetDcmtkAssociation(), sopClass); | |
492 if (cgetPresID == 0) | |
493 { | |
494 throw OrthancException(ErrorCode_DicomGetUnavailable, | |
495 "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); | |
496 } | |
497 | |
498 T_DIMSE_Message msgGetRequest; | |
499 memset((char*)&msgGetRequest, 0, sizeof(msgGetRequest)); | |
500 msgGetRequest.CommandField = DIMSE_C_GET_RQ; | |
501 | |
502 T_DIMSE_C_GetRQ* request = &(msgGetRequest.msg.CGetRQ); | |
503 request->MessageID = association_->GetDcmtkAssociation().nextMsgID++; | |
504 strncpy(request->AffectedSOPClassUID, sopClass, DIC_UI_LEN); | |
505 request->Priority = DIMSE_PRIORITY_MEDIUM; | |
506 request->DataSetType = DIMSE_DATASET_PRESENT; | |
507 | |
508 { | |
509 OFString str; | |
510 CLOG(TRACE, DICOM) << "Sending Get Request:" << std::endl | |
511 << DIMSE_dumpMessage(str, *request, DIMSE_OUTGOING, NULL, cgetPresID); | |
512 } | |
513 | |
514 OFCondition cond = DIMSE_sendMessageUsingMemoryData( | |
515 &(association_->GetDcmtkAssociation()), cgetPresID, &msgGetRequest, NULL /* statusDetail */, queryDataset, | |
516 NULL /* progress callback TODO-GET */, NULL /* callback context */, NULL /* commandSet */); | |
517 | |
518 if (cond.bad()) | |
519 { | |
520 OFString tempStr; | |
521 CLOG(TRACE, DICOM) << "Failed sending C-GET request: " << DimseCondition::dump(tempStr, cond); | |
522 // return cond; | |
523 } | |
524 | |
525 // equivalent to handleCGETSession in DCMTK | |
526 bool continueSession = true; | |
527 | |
528 // As long we want to continue (usually, as long as we receive more objects, | |
529 // i.e. the final C-GET response has not arrived yet) | |
530 while (continueSession) | |
531 { | |
532 T_DIMSE_Message rsp; | |
533 // Make sure everything is zeroed (especially options) | |
534 memset((char*)&rsp, 0, sizeof(rsp)); | |
535 | |
536 // DcmDataset* statusDetail = NULL; | |
537 T_ASC_PresentationContextID cmdPresId = 0; | |
538 | |
539 OFCondition result = DIMSE_receiveCommand(&(association_->GetDcmtkAssociation()), | |
540 (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
541 parameters_.GetTimeout(), | |
542 &cmdPresId, | |
543 &rsp, | |
544 NULL /* statusDetail */, | |
545 NULL /* not interested in the command set */); | |
546 | |
547 if (result.bad()) | |
548 { | |
549 OFString tempStr; | |
550 CLOG(TRACE, DICOM) << "Failed receiving DIMSE command: " << DimseCondition::dump(tempStr, result); | |
551 // delete statusDetail; | |
552 break; // TODO: return value | |
553 } | |
554 // Handle C-GET Response | |
555 if (rsp.CommandField == DIMSE_C_GET_RSP) | |
556 { | |
557 { | |
558 OFString tempStr; | |
559 CLOG(TRACE, DICOM) << "Received C-GET Response: " << std::endl | |
560 << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId); | |
561 } | |
562 | |
563 // TODO-GET: for progress handler | |
564 // OFunique_ptr<RetrieveResponse> getRSP(new RetrieveResponse()); | |
565 // getRSP->m_affectedSOPClassUID = rsp.msg.CGetRSP.AffectedSOPClassUID; | |
566 // getRSP->m_messageIDRespondedTo = rsp.msg.CGetRSP.MessageIDBeingRespondedTo; | |
567 // getRSP->m_status = rsp.msg.CGetRSP.DimseStatus; | |
568 // getRSP->m_numberOfRemainingSubops = rsp.msg.CGetRSP.NumberOfRemainingSubOperations; | |
569 // getRSP->m_numberOfCompletedSubops = rsp.msg.CGetRSP.NumberOfCompletedSubOperations; | |
570 // getRSP->m_numberOfFailedSubops = rsp.msg.CGetRSP.NumberOfFailedSubOperations; | |
571 // getRSP->m_numberOfWarningSubops = rsp.msg.CGetRSP.NumberOfWarningSubOperations; | |
572 // getRSP->m_statusDetail = statusDetail; | |
573 | |
574 } | |
575 // Handle C-STORE Request | |
576 else if (rsp.CommandField == DIMSE_C_STORE_RQ) | |
577 { | |
578 { | |
579 OFString tempStr; | |
580 CLOG(TRACE, DICOM) << "Received C-STORE Request: " << std::endl | |
581 << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId); | |
582 } | |
583 | |
584 T_DIMSE_C_StoreRQ* storeRequest = &(rsp.msg.CStoreRQ); | |
585 | |
586 // Check if dataset is announced correctly | |
587 if (rsp.msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL) | |
588 { | |
589 CLOG(WARNING, DICOM) << "C-GET SCU handler: Incoming C-STORE with no dataset"; | |
590 } | |
591 | |
592 Uint16 desiredCStoreReturnStatus = 0; | |
593 DcmDataset* dataObject = NULL; | |
594 | |
595 // Receive dataset | |
596 result = DIMSE_receiveDataSetInMemory(&(association_->GetDcmtkAssociation()), | |
597 (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
598 parameters_.GetTimeout(), | |
599 &cmdPresId, | |
600 &dataObject, | |
601 NULL /*callback*/, NULL /*callbackData*/); // TODO-GET | |
602 | |
603 if (result.bad()) | |
604 { | |
605 desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand; | |
606 // TODO-GET: return ? | |
607 } | |
608 else | |
609 { | |
610 // callback the OrthancServer with the received data | |
611 if (instanceReceivedCallback != NULL) | |
612 { | |
613 desiredCStoreReturnStatus = instanceReceivedCallback(callbackContext, *dataObject, remoteAet, remoteIp, calledAet); | |
614 } | |
615 | |
616 // send the Store response | |
617 T_DIMSE_Message storeResponse; | |
618 memset((char*)&storeResponse, 0, sizeof(storeResponse)); | |
619 storeResponse.CommandField = DIMSE_C_STORE_RSP; | |
620 | |
621 T_DIMSE_C_StoreRSP& storeRsp = storeResponse.msg.CStoreRSP; | |
622 storeRsp.MessageIDBeingRespondedTo = storeRequest->MessageID; | |
623 storeRsp.DimseStatus = desiredCStoreReturnStatus; | |
624 storeRsp.DataSetType = DIMSE_DATASET_NULL; | |
625 | |
626 OFStandard::strlcpy( | |
627 storeRsp.AffectedSOPClassUID, storeRequest->AffectedSOPClassUID, sizeof(storeRsp.AffectedSOPClassUID)); | |
628 OFStandard::strlcpy( | |
629 storeRsp.AffectedSOPInstanceUID, storeRequest->AffectedSOPInstanceUID, sizeof(storeRsp.AffectedSOPInstanceUID)); | |
630 storeRsp.opts = O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID; | |
631 | |
632 result = DIMSE_sendMessageUsingMemoryData(&(association_->GetDcmtkAssociation()), | |
633 cmdPresId, | |
634 &storeResponse, NULL /* statusDetail */, NULL /* dataObject */, | |
635 NULL /* progress callback TODO-GET */, NULL /* callback context */, NULL /* commandSet */); | |
636 if (result.bad()) | |
637 { | |
638 continueSession = false; | |
639 } | |
640 else | |
641 { | |
642 OFString tempStr; | |
643 CLOG(TRACE, DICOM) << "Sent C-STORE Response: " << std::endl | |
644 << DIMSE_dumpMessage(tempStr, storeResponse, DIMSE_OUTGOING, NULL, cmdPresId); | |
645 } | |
646 } | |
647 } | |
648 // Handle other DIMSE command (error since other command than GET/STORE not expected) | |
649 else | |
650 { | |
651 CLOG(WARNING, DICOM) << "Expected C-GET response or C-STORE request but received DIMSE command 0x" | |
652 << std::hex << std::setfill('0') << std::setw(4) | |
653 << static_cast<unsigned int>(rsp.CommandField); | |
654 | |
655 result = DIMSE_BADCOMMANDTYPE; | |
656 continueSession = false; | |
657 } | |
658 | |
659 // delete statusDetail; // should be NULL if not existing or added to response list | |
660 // statusDetail = NULL; | |
661 } | |
662 /* All responses received or break signal occurred */ | |
663 | |
664 // return result; | |
665 } | |
666 | |
667 | |
448 DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) : | 668 DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) : |
449 parameters_(params), | 669 parameters_(params), |
450 association_(new DicomAssociation) | 670 association_(new DicomAssociation) |
451 { | 671 { |
452 SetupPresentationContexts(); | 672 SetupPresentationContexts(); |