Mercurial > hg > orthanc
diff OrthancServer/OrthancGetRequestHandler.cpp @ 3818:4f78da5613a1 c-get
Add C-GET SCP support
author | Stacy Loesch <stacy.loesch@varian.com> |
---|---|
date | Fri, 27 Mar 2020 10:06:58 -0400 |
parents | |
children | d30bce4bdae9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancGetRequestHandler.cpp Fri Mar 27 10:06:58 2020 -0400 @@ -0,0 +1,368 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + +#include "PrecompiledHeadersServer.h" +#include "OrthancGetRequestHandler.h" + +#include <dcmtk/dcmnet/assoc.h> +#include <dcmtk/dcmnet/dimse.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmnet/diutil.h> + +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Core/DicomFormat/DicomArray.h" +#include "../Core/Logging.h" +#include "../Core/MetricsRegistry.h" +#include "OrthancConfiguration.h" +#include "ServerContext.h" +#include "ServerJobs/DicomModalityStoreJob.h" + + + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + + static void getSubOpProgressCallback(void * /* callbackData */, + T_DIMSE_StoreProgress *progress, + T_DIMSE_C_StoreRQ * /*req*/) + { + // SBL - no logging to be done here. + } + } + + OrthancGetRequestHandler::Status OrthancGetRequestHandler::DoNext(T_ASC_Association* assoc) + { + if (position_ >= instances_.size()) + { + return Status_Failure; + } + + const std::string& id = instances_[position_++]; + + std::string dicom; + context_.ReadDicom(dicom, id); + + if(dicom.size() <= 0) + { + return Status_Failure; + } + + DcmInputBufferStream is; + is.setBuffer(&dicom[0], dicom.size()); + is.setEos(); + + DcmFileFormat dcmff; + OFCondition cond = dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength); + if(cond.bad()) + { + return Status_Failure; + } + + // Determine the storage SOP class UID for this instance + DIC_UI sopClass; + DIC_UI sopInstance; + +#if DCMTK_VERSION_NUMBER >= 364 + if (!DU_findSOPClassAndInstanceInDataSet(static_cast<DcmItem *> (dcmff.getDataset()), + sopClass, sizeof(sopClass), + sopInstance, sizeof(sopInstance))) +#else + if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClassUid, sopInstance)) +#endif + { + throw OrthancException(ErrorCode_NoSopClassOrInstance, + "Unable to determine the SOP class/instance for C-STORE with AET " + + originatorAet_); + } + + + + + cond = performGetSubOp(assoc, sopClass, sopInstance, dcmff.getDataset()); + if (getCancelled_) { + LOG (INFO) << "Get SCP: Received C-Cancel RQ"; + } + + if(cond.bad() || getCancelled_) + { + return Status_Failure; + } + + return Status_Success; + } + + void OrthancGetRequestHandler::addFailedUIDInstance(const char *sopInstance) + { + size_t len; + + if (failedUIDs_ == NULL) { + if ((failedUIDs_ = (char*)malloc(DIC_UI_LEN+1)) == NULL) { + LOG (ERROR) << "malloc failure: addFailedUIDInstance"; + return; + } + strcpy(failedUIDs_, sopInstance); + } else { + len = strlen(failedUIDs_); + if ((failedUIDs_ = (char*)realloc(failedUIDs_, + (len+strlen(sopInstance)+2))) == NULL) { + LOG (ERROR) << "realloc failure: addFailedUIDInstance"; + return; + } + // tag sopInstance onto end of old with '\' between + strcat(failedUIDs_, "\\"); + strcat(failedUIDs_, sopInstance); + } + } + + + OFCondition OrthancGetRequestHandler::performGetSubOp(T_ASC_Association* assoc, + DIC_UI sopClass, + DIC_UI sopInstance, + DcmDataset *dataset) + { + OFCondition cond = EC_Normal; + T_DIMSE_C_StoreRQ req; + T_DIMSE_C_StoreRSP rsp; + DIC_US msgId; + T_ASC_PresentationContextID presId; + DcmDataset *stDetail = NULL; + + + msgId = assoc->nextMsgID++; + + // which presentation context should be used + presId = ASC_findAcceptedPresentationContextID(assoc, + sopClass); + if (presId == 0) { + nFailed_++; + addFailedUIDInstance(sopInstance); + LOG (ERROR) << "Get SCP: storeSCU: No presentation context for: (" + << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass; + return DIMSE_NOVALIDPRESENTATIONCONTEXTID; + } else { + // make sure that we can send images in this presentation context + T_ASC_PresentationContext pc; + ASC_findAcceptedPresentationContext(assoc->params, presId, &pc); + // the acceptedRole is the association requestor role + if ((pc.acceptedRole != ASC_SC_ROLE_SCP) && (pc.acceptedRole != ASC_SC_ROLE_SCUSCP)) + { + // the role is not appropriate + nFailed_++; + addFailedUIDInstance(sopInstance); + LOG (ERROR) <<"Get SCP: storeSCU: [No presentation context with requestor SCP role for: (" + << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass; + return DIMSE_NOVALIDPRESENTATIONCONTEXTID; + } + } + + req.MessageID = msgId; + strcpy(req.AffectedSOPClassUID, sopClass); + strcpy(req.AffectedSOPInstanceUID, sopInstance); + req.DataSetType = DIMSE_DATASET_PRESENT; + req.Priority = priority_; + req.opts = 0; + + LOG (INFO) << "Store SCU RQ: MsgID " << msgId << ", (" + << dcmSOPClassUIDToModality(sopClass, "OT") << ")"; + + T_DIMSE_DetectedCancelParameters cancelParameters; + + cond = DIMSE_storeUser(assoc, presId, &req, + NULL, dataset, getSubOpProgressCallback, this, DIMSE_BLOCKING, 0, + &rsp, &stDetail, &cancelParameters); + + if (cond.good()) { + if (cancelParameters.cancelEncountered) { + if (origPresId == cancelParameters.presId && + origMsgId == cancelParameters.req.MessageIDBeingRespondedTo) { + getCancelled_ = OFTrue; + } else { + LOG (ERROR) << "Get SCP: Unexpected C-Cancel-RQ encountered: pid=" << (int)cancelParameters.presId + << ", mid=" << (int)cancelParameters.req.MessageIDBeingRespondedTo; + } + } + + if (rsp.DimseStatus == STATUS_Success) { + // everything ok + nCompleted_++; + } else if ((rsp.DimseStatus & 0xf000) == 0xb000) { + // a warning status message + warningCount_++; + LOG (ERROR) << "Get SCP: Store Warning: Response Status: " << DU_cstoreStatusString(rsp.DimseStatus); + } else { + nFailed_++; + addFailedUIDInstance(sopInstance); + // print a status message + LOG (ERROR) << "Get SCP: Store Failed: Response Status: " + << DU_cstoreStatusString(rsp.DimseStatus); + } + } else { + nFailed_++; + addFailedUIDInstance(sopInstance); + OFString temp_str; + LOG (ERROR) << "Get SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond); + } + if (stDetail) { + LOG (INFO) << " Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail); + delete stDetail; + } + return cond; + } + + bool OrthancGetRequestHandler::LookupIdentifiers(std::vector<std::string>& publicIds, + ResourceType level, + const DicomMap& input) + { + DicomTag tag(0, 0); // Dummy initialization + + switch (level) + { + case ResourceType_Patient: + tag = DICOM_TAG_PATIENT_ID; + break; + + case ResourceType_Study: + tag = (input.HasTag(DICOM_TAG_ACCESSION_NUMBER) ? + DICOM_TAG_ACCESSION_NUMBER : DICOM_TAG_STUDY_INSTANCE_UID); + break; + + case ResourceType_Series: + tag = DICOM_TAG_SERIES_INSTANCE_UID; + break; + + case ResourceType_Instance: + tag = DICOM_TAG_SOP_INSTANCE_UID; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (!input.HasTag(tag)) + { + return false; + } + + const DicomValue& value = input.GetValue(tag); + if (value.IsNull() || + value.IsBinary()) + { + return false; + } + else + { + const std::string& content = value.GetContent(); + context_.GetIndex().LookupIdentifierExact(publicIds, level, tag, content); + return true; + } + } + + + bool OrthancGetRequestHandler::Handle(const DicomMap& input, + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet) + { + MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms"); + + LOG(WARNING) << "Get-SCU request received from AET \"" << originatorAet << "\""; + + { + DicomArray query(input); + for (size_t i = 0; i < query.GetSize(); i++) + { + if (!query.GetElement(i).GetValue().IsNull()) + { + LOG(INFO) << " " << query.GetElement(i).GetTag() + << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) + << " = " << query.GetElement(i).GetValue().GetContent(); + } + } + } + + /** + * Retrieve the query level. + **/ + + const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + + assert(levelTmp != NULL); + ResourceType level = StringToResourceType(levelTmp->GetContent().c_str()); + + + /** + * Lookup for the resource to be sent. + **/ + + std::vector<std::string> publicIds; + + bool retVal = LookupIdentifiers(publicIds, level, input); + localAet_ = context_.GetDefaultLocalApplicationEntityTitle(); + position_ = 0; + originatorAet_ = originatorAet; + + { + OrthancConfiguration::ReaderLock lock; + remote_ = lock.GetConfiguration().GetModalityUsingAet(originatorAet); + } + + for (size_t i = 0; i < publicIds.size(); i++) + { + LOG(INFO) << "Sending resource " << publicIds[i] << " to modality \"" + << originatorAet << "\" in synchronous mode"; + + std::list<std::string> tmp; + context_.GetIndex().GetChildInstances(tmp, publicIds[i]); + + instances_.reserve(tmp.size()); + for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) + { + instances_.push_back(*it); + } + } + failedUIDs_ = NULL; + getCancelled_ = OFFalse; + + nRemaining_ = GetSubOperationCount(); + nCompleted_ = 0; + nFailed_ = 0; + warningCount_ = 0; + + return retVal; + + + } +};