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();