Mercurial > hg > orthanc
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 { |