Mercurial > hg > orthanc
diff 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 |
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Wed Oct 16 19:34:42 2024 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Tue Oct 29 17:25:49 2024 +0100 @@ -234,7 +234,7 @@ - void DicomControlUserConnection::SetupPresentationContexts() + void DicomControlUserConnection::SetupPresentationContexts() // TODO-GET, setup only the presentation contexts that are enabled for that modality { assert(association_.get() != NULL); association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); @@ -243,6 +243,12 @@ association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); + association_->ProposeGenericPresentationContext(UID_GETStudyRootQueryRetrieveInformationModel); + association_->ProposeGenericPresentationContext(UID_GETPatientRootQueryRetrieveInformationModel); + + // 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 + association_->ProposeGenericPresentationContext(UID_ComputedRadiographyImageStorage); + association_->ProposeGenericPresentationContext(UID_MRImageStorage); } @@ -445,6 +451,220 @@ } + void DicomControlUserConnection::Get(const DicomMap& findResult, + CGetInstanceReceivedCallback instanceReceivedCallback, + void* callbackContext) + { + assert(association_.get() != NULL); + association_->Open(parameters_); + + // TODO-GET: if findResults is the result of a C-Find, we can use the SopClassUIDs for the negotiation + + std::unique_ptr<ParsedDicomFile> query( + ConvertQueryFields(findResult, parameters_.GetRemoteModality().GetManufacturer())); + DcmDataset* queryDataset = query->GetDcmtkObject().getDataset(); + + std::string remoteAet; + std::string remoteIp; + std::string calledAet; + + association_->GetAssociationParameters(remoteAet, remoteIp, calledAet); + + const char* sopClass = NULL; + const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); + ResourceType level = StringToResourceType(tmp.c_str()); + switch (level) + { + case ResourceType_Patient: + sopClass = UID_GETPatientRootQueryRetrieveInformationModel; + // DU_putStringDOElement(queryDataset, DCM_QueryRetrieveLevel, ResourceTypeToDicomQueryRetrieveLevel(ResourceType_Patient)); // TODO-GET + break; + case ResourceType_Study: + sopClass = UID_GETStudyRootQueryRetrieveInformationModel; + // DU_putStringDOElement(queryDataset, DCM_QueryRetrieveLevel, ResourceTypeToDicomQueryRetrieveLevel(ResourceType_Study)); // TODO-GET + break; + default: + throw OrthancException(ErrorCode_InternalError); // TODO-GET: implement series + instances + } + + // Figure out which of the accepted presentation contexts should be used + int cgetPresID = ASC_findAcceptedPresentationContextID(&association_->GetDcmtkAssociation(), sopClass); + if (cgetPresID == 0) + { + throw OrthancException(ErrorCode_DicomGetUnavailable, + "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); + } + + T_DIMSE_Message msgGetRequest; + memset((char*)&msgGetRequest, 0, sizeof(msgGetRequest)); + msgGetRequest.CommandField = DIMSE_C_GET_RQ; + + T_DIMSE_C_GetRQ* request = &(msgGetRequest.msg.CGetRQ); + request->MessageID = association_->GetDcmtkAssociation().nextMsgID++; + strncpy(request->AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request->Priority = DIMSE_PRIORITY_MEDIUM; + request->DataSetType = DIMSE_DATASET_PRESENT; + + { + OFString str; + CLOG(TRACE, DICOM) << "Sending Get Request:" << std::endl + << DIMSE_dumpMessage(str, *request, DIMSE_OUTGOING, NULL, cgetPresID); + } + + OFCondition cond = DIMSE_sendMessageUsingMemoryData( + &(association_->GetDcmtkAssociation()), cgetPresID, &msgGetRequest, NULL /* statusDetail */, queryDataset, + NULL /* progress callback TODO-GET */, NULL /* callback context */, NULL /* commandSet */); + + if (cond.bad()) + { + OFString tempStr; + CLOG(TRACE, DICOM) << "Failed sending C-GET request: " << DimseCondition::dump(tempStr, cond); + // return cond; + } + + // equivalent to handleCGETSession in DCMTK + bool continueSession = true; + + // As long we want to continue (usually, as long as we receive more objects, + // i.e. the final C-GET response has not arrived yet) + while (continueSession) + { + T_DIMSE_Message rsp; + // Make sure everything is zeroed (especially options) + memset((char*)&rsp, 0, sizeof(rsp)); + + // DcmDataset* statusDetail = NULL; + T_ASC_PresentationContextID cmdPresId = 0; + + OFCondition result = DIMSE_receiveCommand(&(association_->GetDcmtkAssociation()), + (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + parameters_.GetTimeout(), + &cmdPresId, + &rsp, + NULL /* statusDetail */, + NULL /* not interested in the command set */); + + if (result.bad()) + { + OFString tempStr; + CLOG(TRACE, DICOM) << "Failed receiving DIMSE command: " << DimseCondition::dump(tempStr, result); + // delete statusDetail; + break; // TODO: return value + } + // Handle C-GET Response + if (rsp.CommandField == DIMSE_C_GET_RSP) + { + { + OFString tempStr; + CLOG(TRACE, DICOM) << "Received C-GET Response: " << std::endl + << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId); + } + + // TODO-GET: for progress handler + // OFunique_ptr<RetrieveResponse> getRSP(new RetrieveResponse()); + // getRSP->m_affectedSOPClassUID = rsp.msg.CGetRSP.AffectedSOPClassUID; + // getRSP->m_messageIDRespondedTo = rsp.msg.CGetRSP.MessageIDBeingRespondedTo; + // getRSP->m_status = rsp.msg.CGetRSP.DimseStatus; + // getRSP->m_numberOfRemainingSubops = rsp.msg.CGetRSP.NumberOfRemainingSubOperations; + // getRSP->m_numberOfCompletedSubops = rsp.msg.CGetRSP.NumberOfCompletedSubOperations; + // getRSP->m_numberOfFailedSubops = rsp.msg.CGetRSP.NumberOfFailedSubOperations; + // getRSP->m_numberOfWarningSubops = rsp.msg.CGetRSP.NumberOfWarningSubOperations; + // getRSP->m_statusDetail = statusDetail; + + } + // Handle C-STORE Request + else if (rsp.CommandField == DIMSE_C_STORE_RQ) + { + { + OFString tempStr; + CLOG(TRACE, DICOM) << "Received C-STORE Request: " << std::endl + << DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, cmdPresId); + } + + T_DIMSE_C_StoreRQ* storeRequest = &(rsp.msg.CStoreRQ); + + // Check if dataset is announced correctly + if (rsp.msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL) + { + CLOG(WARNING, DICOM) << "C-GET SCU handler: Incoming C-STORE with no dataset"; + } + + Uint16 desiredCStoreReturnStatus = 0; + DcmDataset* dataObject = NULL; + + // Receive dataset + result = DIMSE_receiveDataSetInMemory(&(association_->GetDcmtkAssociation()), + (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), + parameters_.GetTimeout(), + &cmdPresId, + &dataObject, + NULL /*callback*/, NULL /*callbackData*/); // TODO-GET + + if (result.bad()) + { + desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand; + // TODO-GET: return ? + } + else + { + // callback the OrthancServer with the received data + if (instanceReceivedCallback != NULL) + { + desiredCStoreReturnStatus = instanceReceivedCallback(callbackContext, *dataObject, remoteAet, remoteIp, calledAet); + } + + // send the Store response + T_DIMSE_Message storeResponse; + memset((char*)&storeResponse, 0, sizeof(storeResponse)); + storeResponse.CommandField = DIMSE_C_STORE_RSP; + + T_DIMSE_C_StoreRSP& storeRsp = storeResponse.msg.CStoreRSP; + storeRsp.MessageIDBeingRespondedTo = storeRequest->MessageID; + storeRsp.DimseStatus = desiredCStoreReturnStatus; + storeRsp.DataSetType = DIMSE_DATASET_NULL; + + OFStandard::strlcpy( + storeRsp.AffectedSOPClassUID, storeRequest->AffectedSOPClassUID, sizeof(storeRsp.AffectedSOPClassUID)); + OFStandard::strlcpy( + storeRsp.AffectedSOPInstanceUID, storeRequest->AffectedSOPInstanceUID, sizeof(storeRsp.AffectedSOPInstanceUID)); + storeRsp.opts = O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID; + + result = DIMSE_sendMessageUsingMemoryData(&(association_->GetDcmtkAssociation()), + cmdPresId, + &storeResponse, NULL /* statusDetail */, NULL /* dataObject */, + NULL /* progress callback TODO-GET */, NULL /* callback context */, NULL /* commandSet */); + if (result.bad()) + { + continueSession = false; + } + else + { + OFString tempStr; + CLOG(TRACE, DICOM) << "Sent C-STORE Response: " << std::endl + << DIMSE_dumpMessage(tempStr, storeResponse, DIMSE_OUTGOING, NULL, cmdPresId); + } + } + } + // Handle other DIMSE command (error since other command than GET/STORE not expected) + else + { + CLOG(WARNING, DICOM) << "Expected C-GET response or C-STORE request but received DIMSE command 0x" + << std::hex << std::setfill('0') << std::setw(4) + << static_cast<unsigned int>(rsp.CommandField); + + result = DIMSE_BADCOMMANDTYPE; + continueSession = false; + } + + // delete statusDetail; // should be NULL if not existing or added to response list + // statusDetail = NULL; + } + /* All responses received or break signal occurred */ + + // return result; +} + + DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) : parameters_(params), association_(new DicomAssociation)