comparison 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
comparison
equal deleted inserted replaced
3815:c81ac6ff232b 3818:4f78da5613a1
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-2019 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 #include "PrecompiledHeadersServer.h"
34 #include "OrthancGetRequestHandler.h"
35
36 #include <dcmtk/dcmnet/assoc.h>
37 #include <dcmtk/dcmnet/dimse.h>
38 #include <dcmtk/dcmdata/dcdeftag.h>
39 #include <dcmtk/dcmdata/dcistrmb.h>
40 #include <dcmtk/dcmnet/diutil.h>
41
42 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
43 #include "../Core/DicomFormat/DicomArray.h"
44 #include "../Core/Logging.h"
45 #include "../Core/MetricsRegistry.h"
46 #include "OrthancConfiguration.h"
47 #include "ServerContext.h"
48 #include "ServerJobs/DicomModalityStoreJob.h"
49
50
51
52 namespace Orthanc
53 {
54 namespace
55 {
56 // Anonymous namespace to avoid clashes between compilation modules
57
58 static void getSubOpProgressCallback(void * /* callbackData */,
59 T_DIMSE_StoreProgress *progress,
60 T_DIMSE_C_StoreRQ * /*req*/)
61 {
62 // SBL - no logging to be done here.
63 }
64 }
65
66 OrthancGetRequestHandler::Status OrthancGetRequestHandler::DoNext(T_ASC_Association* assoc)
67 {
68 if (position_ >= instances_.size())
69 {
70 return Status_Failure;
71 }
72
73 const std::string& id = instances_[position_++];
74
75 std::string dicom;
76 context_.ReadDicom(dicom, id);
77
78 if(dicom.size() <= 0)
79 {
80 return Status_Failure;
81 }
82
83 DcmInputBufferStream is;
84 is.setBuffer(&dicom[0], dicom.size());
85 is.setEos();
86
87 DcmFileFormat dcmff;
88 OFCondition cond = dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength);
89 if(cond.bad())
90 {
91 return Status_Failure;
92 }
93
94 // Determine the storage SOP class UID for this instance
95 DIC_UI sopClass;
96 DIC_UI sopInstance;
97
98 #if DCMTK_VERSION_NUMBER >= 364
99 if (!DU_findSOPClassAndInstanceInDataSet(static_cast<DcmItem *> (dcmff.getDataset()),
100 sopClass, sizeof(sopClass),
101 sopInstance, sizeof(sopInstance)))
102 #else
103 if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClassUid, sopInstance))
104 #endif
105 {
106 throw OrthancException(ErrorCode_NoSopClassOrInstance,
107 "Unable to determine the SOP class/instance for C-STORE with AET " +
108 originatorAet_);
109 }
110
111
112
113
114 cond = performGetSubOp(assoc, sopClass, sopInstance, dcmff.getDataset());
115 if (getCancelled_) {
116 LOG (INFO) << "Get SCP: Received C-Cancel RQ";
117 }
118
119 if(cond.bad() || getCancelled_)
120 {
121 return Status_Failure;
122 }
123
124 return Status_Success;
125 }
126
127 void OrthancGetRequestHandler::addFailedUIDInstance(const char *sopInstance)
128 {
129 size_t len;
130
131 if (failedUIDs_ == NULL) {
132 if ((failedUIDs_ = (char*)malloc(DIC_UI_LEN+1)) == NULL) {
133 LOG (ERROR) << "malloc failure: addFailedUIDInstance";
134 return;
135 }
136 strcpy(failedUIDs_, sopInstance);
137 } else {
138 len = strlen(failedUIDs_);
139 if ((failedUIDs_ = (char*)realloc(failedUIDs_,
140 (len+strlen(sopInstance)+2))) == NULL) {
141 LOG (ERROR) << "realloc failure: addFailedUIDInstance";
142 return;
143 }
144 // tag sopInstance onto end of old with '\' between
145 strcat(failedUIDs_, "\\");
146 strcat(failedUIDs_, sopInstance);
147 }
148 }
149
150
151 OFCondition OrthancGetRequestHandler::performGetSubOp(T_ASC_Association* assoc,
152 DIC_UI sopClass,
153 DIC_UI sopInstance,
154 DcmDataset *dataset)
155 {
156 OFCondition cond = EC_Normal;
157 T_DIMSE_C_StoreRQ req;
158 T_DIMSE_C_StoreRSP rsp;
159 DIC_US msgId;
160 T_ASC_PresentationContextID presId;
161 DcmDataset *stDetail = NULL;
162
163
164 msgId = assoc->nextMsgID++;
165
166 // which presentation context should be used
167 presId = ASC_findAcceptedPresentationContextID(assoc,
168 sopClass);
169 if (presId == 0) {
170 nFailed_++;
171 addFailedUIDInstance(sopInstance);
172 LOG (ERROR) << "Get SCP: storeSCU: No presentation context for: ("
173 << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass;
174 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
175 } else {
176 // make sure that we can send images in this presentation context
177 T_ASC_PresentationContext pc;
178 ASC_findAcceptedPresentationContext(assoc->params, presId, &pc);
179 // the acceptedRole is the association requestor role
180 if ((pc.acceptedRole != ASC_SC_ROLE_SCP) && (pc.acceptedRole != ASC_SC_ROLE_SCUSCP))
181 {
182 // the role is not appropriate
183 nFailed_++;
184 addFailedUIDInstance(sopInstance);
185 LOG (ERROR) <<"Get SCP: storeSCU: [No presentation context with requestor SCP role for: ("
186 << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass;
187 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
188 }
189 }
190
191 req.MessageID = msgId;
192 strcpy(req.AffectedSOPClassUID, sopClass);
193 strcpy(req.AffectedSOPInstanceUID, sopInstance);
194 req.DataSetType = DIMSE_DATASET_PRESENT;
195 req.Priority = priority_;
196 req.opts = 0;
197
198 LOG (INFO) << "Store SCU RQ: MsgID " << msgId << ", ("
199 << dcmSOPClassUIDToModality(sopClass, "OT") << ")";
200
201 T_DIMSE_DetectedCancelParameters cancelParameters;
202
203 cond = DIMSE_storeUser(assoc, presId, &req,
204 NULL, dataset, getSubOpProgressCallback, this, DIMSE_BLOCKING, 0,
205 &rsp, &stDetail, &cancelParameters);
206
207 if (cond.good()) {
208 if (cancelParameters.cancelEncountered) {
209 if (origPresId == cancelParameters.presId &&
210 origMsgId == cancelParameters.req.MessageIDBeingRespondedTo) {
211 getCancelled_ = OFTrue;
212 } else {
213 LOG (ERROR) << "Get SCP: Unexpected C-Cancel-RQ encountered: pid=" << (int)cancelParameters.presId
214 << ", mid=" << (int)cancelParameters.req.MessageIDBeingRespondedTo;
215 }
216 }
217
218 if (rsp.DimseStatus == STATUS_Success) {
219 // everything ok
220 nCompleted_++;
221 } else if ((rsp.DimseStatus & 0xf000) == 0xb000) {
222 // a warning status message
223 warningCount_++;
224 LOG (ERROR) << "Get SCP: Store Warning: Response Status: " << DU_cstoreStatusString(rsp.DimseStatus);
225 } else {
226 nFailed_++;
227 addFailedUIDInstance(sopInstance);
228 // print a status message
229 LOG (ERROR) << "Get SCP: Store Failed: Response Status: "
230 << DU_cstoreStatusString(rsp.DimseStatus);
231 }
232 } else {
233 nFailed_++;
234 addFailedUIDInstance(sopInstance);
235 OFString temp_str;
236 LOG (ERROR) << "Get SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond);
237 }
238 if (stDetail) {
239 LOG (INFO) << " Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail);
240 delete stDetail;
241 }
242 return cond;
243 }
244
245 bool OrthancGetRequestHandler::LookupIdentifiers(std::vector<std::string>& publicIds,
246 ResourceType level,
247 const DicomMap& input)
248 {
249 DicomTag tag(0, 0); // Dummy initialization
250
251 switch (level)
252 {
253 case ResourceType_Patient:
254 tag = DICOM_TAG_PATIENT_ID;
255 break;
256
257 case ResourceType_Study:
258 tag = (input.HasTag(DICOM_TAG_ACCESSION_NUMBER) ?
259 DICOM_TAG_ACCESSION_NUMBER : DICOM_TAG_STUDY_INSTANCE_UID);
260 break;
261
262 case ResourceType_Series:
263 tag = DICOM_TAG_SERIES_INSTANCE_UID;
264 break;
265
266 case ResourceType_Instance:
267 tag = DICOM_TAG_SOP_INSTANCE_UID;
268 break;
269
270 default:
271 throw OrthancException(ErrorCode_ParameterOutOfRange);
272 }
273
274 if (!input.HasTag(tag))
275 {
276 return false;
277 }
278
279 const DicomValue& value = input.GetValue(tag);
280 if (value.IsNull() ||
281 value.IsBinary())
282 {
283 return false;
284 }
285 else
286 {
287 const std::string& content = value.GetContent();
288 context_.GetIndex().LookupIdentifierExact(publicIds, level, tag, content);
289 return true;
290 }
291 }
292
293
294 bool OrthancGetRequestHandler::Handle(const DicomMap& input,
295 const std::string& originatorIp,
296 const std::string& originatorAet,
297 const std::string& calledAet)
298 {
299 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms");
300
301 LOG(WARNING) << "Get-SCU request received from AET \"" << originatorAet << "\"";
302
303 {
304 DicomArray query(input);
305 for (size_t i = 0; i < query.GetSize(); i++)
306 {
307 if (!query.GetElement(i).GetValue().IsNull())
308 {
309 LOG(INFO) << " " << query.GetElement(i).GetTag()
310 << " " << FromDcmtkBridge::GetTagName(query.GetElement(i))
311 << " = " << query.GetElement(i).GetValue().GetContent();
312 }
313 }
314 }
315
316 /**
317 * Retrieve the query level.
318 **/
319
320 const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
321
322 assert(levelTmp != NULL);
323 ResourceType level = StringToResourceType(levelTmp->GetContent().c_str());
324
325
326 /**
327 * Lookup for the resource to be sent.
328 **/
329
330 std::vector<std::string> publicIds;
331
332 bool retVal = LookupIdentifiers(publicIds, level, input);
333 localAet_ = context_.GetDefaultLocalApplicationEntityTitle();
334 position_ = 0;
335 originatorAet_ = originatorAet;
336
337 {
338 OrthancConfiguration::ReaderLock lock;
339 remote_ = lock.GetConfiguration().GetModalityUsingAet(originatorAet);
340 }
341
342 for (size_t i = 0; i < publicIds.size(); i++)
343 {
344 LOG(INFO) << "Sending resource " << publicIds[i] << " to modality \""
345 << originatorAet << "\" in synchronous mode";
346
347 std::list<std::string> tmp;
348 context_.GetIndex().GetChildInstances(tmp, publicIds[i]);
349
350 instances_.reserve(tmp.size());
351 for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it)
352 {
353 instances_.push_back(*it);
354 }
355 }
356 failedUIDs_ = NULL;
357 getCancelled_ = OFFalse;
358
359 nRemaining_ = GetSubOperationCount();
360 nCompleted_ = 0;
361 nFailed_ = 0;
362 warningCount_ = 0;
363
364 return retVal;
365
366
367 }
368 };