comparison OrthancServer/OrthancGetRequestHandler.cpp @ 3959:76a24be12912 c-get

c-get: support of transcoding
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 May 2020 18:29:04 +0200
parents 66879215cbf3
children d30eb4ae5bb6
comparison
equal deleted inserted replaced
3958:596912ebab5f 3959:76a24be12912
31 **/ 31 **/
32 32
33 #include "PrecompiledHeadersServer.h" 33 #include "PrecompiledHeadersServer.h"
34 #include "OrthancGetRequestHandler.h" 34 #include "OrthancGetRequestHandler.h"
35 35
36 #include <dcmtk/dcmdata/dcdeftag.h>
37 #include <dcmtk/dcmdata/dcfilefo.h>
38 #include <dcmtk/dcmdata/dcistrmb.h>
36 #include <dcmtk/dcmnet/assoc.h> 39 #include <dcmtk/dcmnet/assoc.h>
37 #include <dcmtk/dcmnet/dimse.h> 40 #include <dcmtk/dcmnet/dimse.h>
38 #include <dcmtk/dcmdata/dcdeftag.h>
39 #include <dcmtk/dcmdata/dcistrmb.h>
40 #include <dcmtk/dcmnet/diutil.h> 41 #include <dcmtk/dcmnet/diutil.h>
41 42
42 #include "../../Core/DicomParsing/FromDcmtkBridge.h" 43 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
43 #include "../Core/DicomFormat/DicomArray.h" 44 #include "../Core/DicomFormat/DicomArray.h"
44 #include "../Core/Logging.h" 45 #include "../Core/Logging.h"
46 #include "OrthancConfiguration.h" 47 #include "OrthancConfiguration.h"
47 #include "ServerContext.h" 48 #include "ServerContext.h"
48 #include "ServerJobs/DicomModalityStoreJob.h" 49 #include "ServerJobs/DicomModalityStoreJob.h"
49 50
50 51
51
52 namespace Orthanc 52 namespace Orthanc
53 { 53 {
54 namespace 54 namespace
55 { 55 {
56 // Anonymous namespace to avoid clashes between compilation modules 56 // Anonymous namespace to avoid clashes between compilation modules
62 { 62 {
63 // SBL - no logging to be done here. 63 // SBL - no logging to be done here.
64 } 64 }
65 } 65 }
66 66
67 OrthancGetRequestHandler::Status OrthancGetRequestHandler::DoNext(T_ASC_Association* assoc) 67 OrthancGetRequestHandler::Status
68 OrthancGetRequestHandler::DoNext(T_ASC_Association* assoc)
68 { 69 {
69 if (position_ >= instances_.size()) 70 if (position_ >= instances_.size())
70 { 71 {
71 return Status_Failure; 72 return Status_Failure;
72 } 73 }
79 if (dicom.size() <= 0) 80 if (dicom.size() <= 0)
80 { 81 {
81 return Status_Failure; 82 return Status_Failure;
82 } 83 }
83 84
84 ParsedDicomFile parsed(dicom); 85 std::unique_ptr<DcmFileFormat> parsed(
85 86 FromDcmtkBridge::LoadFromMemoryBuffer(dicom.c_str(), dicom.size()));
86 if (parsed.GetDcmtkObject().getDataset() == NULL) 87
88 if (parsed.get() == NULL ||
89 parsed->getDataset() == NULL)
87 { 90 {
88 throw OrthancException(ErrorCode_InternalError); 91 throw OrthancException(ErrorCode_InternalError);
89 } 92 }
90 93
91 DcmDataset& dataset = *parsed.GetDcmtkObject().getDataset(); 94 DcmDataset& dataset = *parsed->getDataset();
92 95
93 OFString a, b; 96 OFString a, b;
94 if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() || 97 if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() ||
95 !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good()) 98 !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good())
96 { 99 {
100 } 103 }
101 104
102 std::string sopClassUid(a.c_str()); 105 std::string sopClassUid(a.c_str());
103 std::string sopInstanceUid(b.c_str()); 106 std::string sopInstanceUid(b.c_str());
104 107
105 OFCondition cond = PerformGetSubOp(assoc, sopClassUid, sopInstanceUid, dataset); 108 OFCondition cond = PerformGetSubOp(assoc, sopClassUid, sopInstanceUid, parsed.release());
106 109
107 if (getCancelled_) 110 if (getCancelled_)
108 { 111 {
109 LOG(INFO) << "Get SCP: Received C-Cancel RQ"; 112 LOG(INFO) << "C-GET SCP: Received C-Cancel RQ";
110 } 113 }
111 114
112 if (cond.bad() || getCancelled_) 115 if (cond.bad() || getCancelled_)
113 { 116 {
114 return Status_Failure; 117 return Status_Failure;
127 else 130 else
128 { 131 {
129 failedUIDs_ += "\\" + sopInstance; 132 failedUIDs_ += "\\" + sopInstance;
130 } 133 }
131 } 134 }
135
136
137 static bool SelectPresentationContext(T_ASC_PresentationContextID& selectedPresentationId,
138 DicomTransferSyntax& selectedSyntax,
139 T_ASC_Association* assoc,
140 const std::string& sopClassUid,
141 DicomTransferSyntax sourceSyntax,
142 bool allowTranscoding)
143 {
144 typedef std::map<DicomTransferSyntax, T_ASC_PresentationContextID> Accepted;
145
146 Accepted accepted;
147
148 /**
149 * 1. Inspect and index all the accepted transfer syntaxes. This
150 * is similar to the code from "DicomAssociation::Open()".
151 **/
152
153 LST_HEAD **l = &assoc->params->DULparams.acceptedPresentationContext;
154 if (*l != NULL)
155 {
156 DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
157 LST_Position(l, (LST_NODE*)pc);
158 while (pc)
159 {
160 if (pc->result == ASC_P_ACCEPTANCE &&
161 std::string(pc->abstractSyntax) == sopClassUid)
162 {
163 DicomTransferSyntax transferSyntax;
164 if (LookupTransferSyntax(transferSyntax, pc->acceptedTransferSyntax))
165 {
166 accepted[transferSyntax] = pc->presentationContextID;
167 }
168 else
169 {
170 LOG(WARNING) << "C-GET: Unknown transfer syntax received: "
171 << pc->acceptedTransferSyntax;
172 }
173 }
174
175 pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
176 }
177 }
178
179
180 /**
181 * 2. Select the preferred transfer syntaxes, which corresponds to
182 * the source transfer syntax, plus all the uncompressed transfer
183 * syntaxes if transcoding is enabled.
184 **/
185
186 std::list<DicomTransferSyntax> preferred;
187 preferred.push_back(sourceSyntax);
188
189 if (allowTranscoding)
190 {
191 if (sourceSyntax != DicomTransferSyntax_LittleEndianImplicit)
192 {
193 // Default Transfer Syntax for DICOM
194 preferred.push_back(DicomTransferSyntax_LittleEndianImplicit);
195 }
196
197 if (sourceSyntax != DicomTransferSyntax_LittleEndianExplicit)
198 {
199 preferred.push_back(DicomTransferSyntax_LittleEndianExplicit);
200 }
201
202 if (sourceSyntax != DicomTransferSyntax_BigEndianExplicit)
203 {
204 // Retired
205 preferred.push_back(DicomTransferSyntax_BigEndianExplicit);
206 }
207 }
208
209
210 /**
211 * 3. Lookup whether one of the preferred transfer syntaxes was
212 * accepted.
213 **/
214
215 for (std::list<DicomTransferSyntax>::const_iterator
216 it = preferred.begin(); it != preferred.end(); ++it)
217 {
218 Accepted::const_iterator found = accepted.find(*it);
219 if (found != accepted.end())
220 {
221 selectedPresentationId = found->second;
222 selectedSyntax = *it;
223 return true;
224 }
225 }
226
227 // No preferred syntax was accepted
228 return false;
229 }
132 230
133 231
134 OFCondition OrthancGetRequestHandler::PerformGetSubOp(T_ASC_Association* assoc, 232 OFCondition OrthancGetRequestHandler::PerformGetSubOp(T_ASC_Association* assoc,
135 const std::string& sopClassUid, 233 const std::string& sopClassUid,
136 const std::string& sopInstanceUid, 234 const std::string& sopInstanceUid,
137 DcmDataset& dataset) 235 DcmFileFormat* dicomRaw)
138 { 236 {
139 T_ASC_PresentationContextID presId; 237 assert(dicomRaw != NULL);
140 238 std::unique_ptr<DcmFileFormat> dicom(dicomRaw);
141 // which presentation context should be used 239
142 presId = ASC_findAcceptedPresentationContextID(assoc, sopClassUid.c_str()); 240 DicomTransferSyntax sourceSyntax;
143 241 if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *dicom))
144 if (presId == 0)
145 { 242 {
146 nFailed_++; 243 nFailed_++;
147 AddFailedUIDInstance(sopInstanceUid); 244 AddFailedUIDInstance(sopInstanceUid);
148 LOG(ERROR) << "Get SCP: storeSCU: No presentation context for: (" 245 LOG(ERROR) << "C-GET SCP: Unknown transfer syntax: ("
246 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
247 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
248 }
249
250 bool allowTranscoding = (context_.IsTranscodeDicomProtocol() &&
251 remote_.IsTranscodingAllowed());
252
253 T_ASC_PresentationContextID presId;
254 DicomTransferSyntax selectedSyntax;
255 if (!SelectPresentationContext(presId, selectedSyntax, assoc, sopClassUid,
256 sourceSyntax, allowTranscoding) ||
257 presId == 0)
258 {
259 nFailed_++;
260 AddFailedUIDInstance(sopInstanceUid);
261 LOG(ERROR) << "C-GET SCP: storeSCU: No presentation context for: ("
149 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid; 262 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
150 return DIMSE_NOVALIDPRESENTATIONCONTEXTID; 263 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
151 } 264 }
152 else 265 else
153 { 266 {
159 (pc.acceptedRole != ASC_SC_ROLE_SCUSCP)) 272 (pc.acceptedRole != ASC_SC_ROLE_SCUSCP))
160 { 273 {
161 // the role is not appropriate 274 // the role is not appropriate
162 nFailed_++; 275 nFailed_++;
163 AddFailedUIDInstance(sopInstanceUid); 276 AddFailedUIDInstance(sopInstanceUid);
164 LOG(ERROR) <<"Get SCP: storeSCU: [No presentation context with requestor SCP role for: (" 277 LOG(ERROR) << "C-GET SCP: storeSCU: [No presentation context with requestor SCP role for: ("
165 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid; 278 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
166 return DIMSE_NOVALIDPRESENTATIONCONTEXTID; 279 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
167 } 280 }
168 } 281 }
169 282
189 302
190 std::unique_ptr<DcmDataset> stDetail; 303 std::unique_ptr<DcmDataset> stDetail;
191 304
192 OFCondition cond; 305 OFCondition cond;
193 306
194 { 307 if (sourceSyntax == selectedSyntax)
308 {
309 // No transcoding is required
195 DcmDataset *stDetailTmp = NULL; 310 DcmDataset *stDetailTmp = NULL;
196 cond = DIMSE_storeUser(assoc, presId, &req, NULL /* imageFileName */, &dataset, 311 cond = DIMSE_storeUser(
197 GetSubOpProgressCallback, this /* callbackData */, 312 assoc, presId, &req, NULL /* imageFileName */, dicom->getDataset(),
198 (timeout_ > 0 ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout_, 313 GetSubOpProgressCallback, this /* callbackData */,
199 &rsp, &stDetailTmp, &cancelParameters); 314 (timeout_ > 0 ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout_,
315 &rsp, &stDetailTmp, &cancelParameters);
200 stDetail.reset(stDetailTmp); 316 stDetail.reset(stDetailTmp);
317 }
318 else
319 {
320 // Transcoding to the selected uncompressed transfer syntax
321 IDicomTranscoder::DicomImage source, transcoded;
322 source.AcquireParsed(dicom.release());
323
324 std::set<DicomTransferSyntax> ts;
325 ts.insert(selectedSyntax);
326
327 if (context_.Transcode(transcoded, source, ts, true))
328 {
329 // Transcoding has succeeded
330 DcmDataset *stDetailTmp = NULL;
331 cond = DIMSE_storeUser(
332 assoc, presId, &req, NULL /* imageFileName */,
333 transcoded.GetParsed().getDataset(),
334 GetSubOpProgressCallback, this /* callbackData */,
335 (timeout_ > 0 ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout_,
336 &rsp, &stDetailTmp, &cancelParameters);
337 stDetail.reset(stDetailTmp);
338 }
339 else
340 {
341 // Cannot transcode
342 nFailed_++;
343 AddFailedUIDInstance(sopInstanceUid);
344 LOG(ERROR) << "C-GET SCP: Cannot transcode " << sopClassUid
345 << " from transfer syntax " << GetTransferSyntaxUid(sourceSyntax)
346 << " to " << GetTransferSyntaxUid(selectedSyntax);
347 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
348 }
201 } 349 }
202 350
203 if (cond.good()) 351 if (cond.good())
204 { 352 {
205 if (cancelParameters.cancelEncountered) 353 if (cancelParameters.cancelEncountered)
209 { 357 {
210 getCancelled_ = OFTrue; 358 getCancelled_ = OFTrue;
211 } 359 }
212 else 360 else
213 { 361 {
214 LOG(ERROR) << "Get SCP: Unexpected C-Cancel-RQ encountered: pid=" << (int)cancelParameters.presId 362 LOG(ERROR) << "C-GET SCP: Unexpected C-Cancel-RQ encountered: pid=" << (int)cancelParameters.presId
215 << ", mid=" << (int)cancelParameters.req.MessageIDBeingRespondedTo; 363 << ", mid=" << (int)cancelParameters.req.MessageIDBeingRespondedTo;
216 } 364 }
217 } 365 }
218 366
219 if (rsp.DimseStatus == STATUS_Success) 367 if (rsp.DimseStatus == STATUS_Success)
223 } 371 }
224 else if ((rsp.DimseStatus & 0xf000) == 0xb000) 372 else if ((rsp.DimseStatus & 0xf000) == 0xb000)
225 { 373 {
226 // a warning status message 374 // a warning status message
227 warningCount_++; 375 warningCount_++;
228 LOG(ERROR) << "Get SCP: Store Warning: Response Status: " 376 LOG(ERROR) << "C-GET SCP: Store Warning: Response Status: "
229 << DU_cstoreStatusString(rsp.DimseStatus); 377 << DU_cstoreStatusString(rsp.DimseStatus);
230 } 378 }
231 else 379 else
232 { 380 {
233 nFailed_++; 381 nFailed_++;
234 AddFailedUIDInstance(sopInstanceUid); 382 AddFailedUIDInstance(sopInstanceUid);
235 // print a status message 383 // print a status message
236 LOG(ERROR) << "Get SCP: Store Failed: Response Status: " 384 LOG(ERROR) << "C-GET SCP: Store Failed: Response Status: "
237 << DU_cstoreStatusString(rsp.DimseStatus); 385 << DU_cstoreStatusString(rsp.DimseStatus);
238 } 386 }
239 } 387 }
240 else 388 else
241 { 389 {
242 nFailed_++; 390 nFailed_++;
243 AddFailedUIDInstance(sopInstanceUid); 391 AddFailedUIDInstance(sopInstanceUid);
244 OFString temp_str; 392 OFString temp_str;
245 LOG(ERROR) << "Get SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond); 393 LOG(ERROR) << "C-GET SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond);
246 } 394 }
247 395
248 if (stDetail.get() != NULL) 396 if (stDetail.get() != NULL)
249 { 397 {
250 LOG(INFO) << " Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail); 398 LOG(INFO) << " Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail);
341 const std::string& calledAet, 489 const std::string& calledAet,
342 uint32_t timeout) 490 uint32_t timeout)
343 { 491 {
344 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms"); 492 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms");
345 493
346 LOG(WARNING) << "Get-SCU request received from AET \"" << originatorAet << "\""; 494 LOG(WARNING) << "C-GET-SCU request received from AET \"" << originatorAet << "\"";
347 495
348 { 496 {
349 DicomArray query(input); 497 DicomArray query(input);
350 for (size_t i = 0; i < query.GetSize(); i++) 498 for (size_t i = 0; i < query.GetSize(); i++)
351 { 499 {