Mercurial > hg > orthanc
comparison Core/DicomParsing/Internals/DicomImageDecoder.cpp @ 2382:7284093111b0
big reorganization to cleanly separate framework vs. server
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 29 Aug 2017 21:17:35 +0200 |
parents | OrthancServer/Internals/DicomImageDecoder.cpp@b8969010b534 |
children | 7e217a1cc63f |
comparison
equal
deleted
inserted
replaced
2381:b8969010b534 | 2382:7284093111b0 |
---|---|
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 Osimis, 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 | |
34 #include "../../PrecompiledHeaders.h" | |
35 #include "DicomImageDecoder.h" | |
36 | |
37 | |
38 /*========================================================================= | |
39 | |
40 This file is based on portions of the following project | |
41 (cf. function "DecodePsmctRle1()"): | |
42 | |
43 Program: GDCM (Grassroots DICOM). A DICOM library | |
44 Module: http://gdcm.sourceforge.net/Copyright.html | |
45 | |
46 Copyright (c) 2006-2011 Mathieu Malaterre | |
47 Copyright (c) 1993-2005 CREATIS | |
48 (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) | |
49 All rights reserved. | |
50 | |
51 Redistribution and use in source and binary forms, with or without | |
52 modification, are permitted provided that the following conditions are met: | |
53 | |
54 * Redistributions of source code must retain the above copyright notice, | |
55 this list of conditions and the following disclaimer. | |
56 | |
57 * Redistributions in binary form must reproduce the above copyright notice, | |
58 this list of conditions and the following disclaimer in the documentation | |
59 and/or other materials provided with the distribution. | |
60 | |
61 * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any | |
62 contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to | |
63 endorse or promote products derived from this software without specific | |
64 prior written permission. | |
65 | |
66 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' | |
67 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
68 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
69 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR | |
70 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
71 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
72 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
73 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
74 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
75 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
76 | |
77 =========================================================================*/ | |
78 | |
79 | |
80 #include "../../Logging.h" | |
81 #include "../../OrthancException.h" | |
82 #include "../../Images/Image.h" | |
83 #include "../../Images/ImageProcessing.h" | |
84 #include "../../DicomFormat/DicomIntegerPixelAccessor.h" | |
85 #include "../ToDcmtkBridge.h" | |
86 #include "../FromDcmtkBridge.h" | |
87 #include "../ParsedDicomFile.h" | |
88 | |
89 #if ORTHANC_ENABLE_PNG == 1 | |
90 # include "../../Images/PngWriter.h" | |
91 #endif | |
92 | |
93 #if ORTHANC_ENABLE_JPEG == 1 | |
94 # include "../../Images/JpegWriter.h" | |
95 #endif | |
96 | |
97 #include <boost/lexical_cast.hpp> | |
98 | |
99 #include <dcmtk/dcmdata/dcfilefo.h> | |
100 #include <dcmtk/dcmdata/dcrleccd.h> | |
101 #include <dcmtk/dcmdata/dcrlecp.h> | |
102 | |
103 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 | |
104 # include <dcmtk/dcmjpls/djcodecd.h> | |
105 # include <dcmtk/dcmjpls/djcparam.h> | |
106 # include <dcmtk/dcmjpeg/djrplol.h> | |
107 #endif | |
108 | |
109 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 | |
110 # include <dcmtk/dcmjpeg/djcodecd.h> | |
111 # include <dcmtk/dcmjpeg/djcparam.h> | |
112 # include <dcmtk/dcmjpeg/djdecbas.h> | |
113 # include <dcmtk/dcmjpeg/djdecext.h> | |
114 # include <dcmtk/dcmjpeg/djdeclol.h> | |
115 # include <dcmtk/dcmjpeg/djdecpro.h> | |
116 # include <dcmtk/dcmjpeg/djdecsps.h> | |
117 # include <dcmtk/dcmjpeg/djdecsv1.h> | |
118 #endif | |
119 | |
120 #if DCMTK_VERSION_NUMBER <= 360 | |
121 # define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax | |
122 # define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax | |
123 # define EXS_JPEGProcess6_8 EXS_JPEGProcess6_8TransferSyntax | |
124 # define EXS_JPEGProcess10_12 EXS_JPEGProcess10_12TransferSyntax | |
125 # define EXS_JPEGProcess14 EXS_JPEGProcess14TransferSyntax | |
126 # define EXS_JPEGProcess14SV1 EXS_JPEGProcess14SV1TransferSyntax | |
127 #endif | |
128 | |
129 namespace Orthanc | |
130 { | |
131 static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); | |
132 static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); | |
133 | |
134 | |
135 bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset) | |
136 { | |
137 DcmElement* e; | |
138 char* c; | |
139 | |
140 // Check whether the DICOM instance contains an image encoded with | |
141 // the PMSCT_RLE1 scheme. | |
142 if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || | |
143 e == NULL || | |
144 !e->isaString() || | |
145 !e->getString(c).good() || | |
146 c == NULL || | |
147 strcmp("PMSCT_RLE1", c)) | |
148 { | |
149 return false; | |
150 } | |
151 else | |
152 { | |
153 return true; | |
154 } | |
155 } | |
156 | |
157 | |
158 bool DicomImageDecoder::DecodePsmctRle1(std::string& output, | |
159 DcmDataset& dataset) | |
160 { | |
161 // Check whether the DICOM instance contains an image encoded with | |
162 // the PMSCT_RLE1 scheme. | |
163 if (!IsPsmctRle1(dataset)) | |
164 { | |
165 return false; | |
166 } | |
167 | |
168 // OK, this is a custom RLE encoding from Philips. Get the pixel | |
169 // data from the appropriate private DICOM tag. | |
170 Uint8* pixData = NULL; | |
171 DcmElement* e; | |
172 if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || | |
173 e == NULL || | |
174 e->getUint8Array(pixData) != EC_Normal) | |
175 { | |
176 return false; | |
177 } | |
178 | |
179 // The "unsigned" below IS VERY IMPORTANT | |
180 const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData); | |
181 const size_t length = e->getLength(); | |
182 | |
183 /** | |
184 * The code below is an adaptation of a sample code for GDCM by | |
185 * Mathieu Malaterre (under a BSD license). | |
186 * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html | |
187 **/ | |
188 | |
189 // RLE pass | |
190 std::vector<uint8_t> temp; | |
191 temp.reserve(length); | |
192 for (size_t i = 0; i < length; i++) | |
193 { | |
194 if (inbuffer[i] == 0xa5) | |
195 { | |
196 temp.push_back(inbuffer[i+2]); | |
197 for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) | |
198 { | |
199 temp.push_back(inbuffer[i+2]); | |
200 } | |
201 i += 2; | |
202 } | |
203 else | |
204 { | |
205 temp.push_back(inbuffer[i]); | |
206 } | |
207 } | |
208 | |
209 // Delta encoding pass | |
210 uint16_t delta = 0; | |
211 output.clear(); | |
212 output.reserve(temp.size()); | |
213 for (size_t i = 0; i < temp.size(); i++) | |
214 { | |
215 uint16_t value; | |
216 | |
217 if (temp[i] == 0x5a) | |
218 { | |
219 uint16_t v1 = temp[i + 1]; | |
220 uint16_t v2 = temp[i + 2]; | |
221 value = (v2 << 8) + v1; | |
222 i += 2; | |
223 } | |
224 else | |
225 { | |
226 value = delta + (int8_t) temp[i]; | |
227 } | |
228 | |
229 output.push_back(value & 0xff); | |
230 output.push_back(value >> 8); | |
231 delta = value; | |
232 } | |
233 | |
234 if (output.size() % 2) | |
235 { | |
236 output.resize(output.size() - 1); | |
237 } | |
238 | |
239 return true; | |
240 } | |
241 | |
242 | |
243 class DicomImageDecoder::ImageSource | |
244 { | |
245 private: | |
246 std::string psmct_; | |
247 std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_; | |
248 | |
249 public: | |
250 void Setup(DcmDataset& dataset, | |
251 unsigned int frame) | |
252 { | |
253 psmct_.clear(); | |
254 slowAccessor_.reset(NULL); | |
255 | |
256 // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data | |
257 | |
258 DicomMap m; | |
259 FromDcmtkBridge::ExtractDicomSummary(m, dataset); | |
260 | |
261 /** | |
262 * Create an accessor to the raw values of the DICOM image. | |
263 **/ | |
264 | |
265 DcmElement* e; | |
266 if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && | |
267 e != NULL) | |
268 { | |
269 Uint8* pixData = NULL; | |
270 if (e->getUint8Array(pixData) == EC_Normal) | |
271 { | |
272 slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); | |
273 } | |
274 } | |
275 else if (DecodePsmctRle1(psmct_, dataset)) | |
276 { | |
277 LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; | |
278 Uint8* pixData = NULL; | |
279 if (psmct_.size() > 0) | |
280 { | |
281 pixData = reinterpret_cast<Uint8*>(&psmct_[0]); | |
282 } | |
283 | |
284 slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); | |
285 } | |
286 | |
287 if (slowAccessor_.get() == NULL) | |
288 { | |
289 throw OrthancException(ErrorCode_BadFileFormat); | |
290 } | |
291 | |
292 slowAccessor_->SetCurrentFrame(frame); | |
293 } | |
294 | |
295 unsigned int GetWidth() const | |
296 { | |
297 assert(slowAccessor_.get() != NULL); | |
298 return slowAccessor_->GetInformation().GetWidth(); | |
299 } | |
300 | |
301 unsigned int GetHeight() const | |
302 { | |
303 assert(slowAccessor_.get() != NULL); | |
304 return slowAccessor_->GetInformation().GetHeight(); | |
305 } | |
306 | |
307 unsigned int GetChannelCount() const | |
308 { | |
309 assert(slowAccessor_.get() != NULL); | |
310 return slowAccessor_->GetInformation().GetChannelCount(); | |
311 } | |
312 | |
313 const DicomIntegerPixelAccessor& GetAccessor() const | |
314 { | |
315 assert(slowAccessor_.get() != NULL); | |
316 return *slowAccessor_; | |
317 } | |
318 | |
319 unsigned int GetSize() const | |
320 { | |
321 assert(slowAccessor_.get() != NULL); | |
322 return slowAccessor_->GetSize(); | |
323 } | |
324 }; | |
325 | |
326 | |
327 ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset, | |
328 bool ignorePhotometricInterpretation) | |
329 { | |
330 DicomMap m; | |
331 FromDcmtkBridge::ExtractDicomSummary(m, dataset); | |
332 | |
333 DicomImageInformation info(m); | |
334 PixelFormat format; | |
335 | |
336 if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation)) | |
337 { | |
338 LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() | |
339 << "bpp, " << info.GetChannelCount() << " channels, " | |
340 << (info.IsSigned() ? "signed" : "unsigned") | |
341 << (info.IsPlanar() ? ", planar, " : ", non-planar, ") | |
342 << EnumerationToString(info.GetPhotometricInterpretation()) | |
343 << " photometric interpretation"; | |
344 throw OrthancException(ErrorCode_NotImplemented); | |
345 } | |
346 | |
347 return new Image(format, info.GetWidth(), info.GetHeight(), false); | |
348 } | |
349 | |
350 | |
351 template <typename PixelType> | |
352 static void CopyPixels(ImageAccessor& target, | |
353 const DicomIntegerPixelAccessor& source) | |
354 { | |
355 const PixelType minValue = std::numeric_limits<PixelType>::min(); | |
356 const PixelType maxValue = std::numeric_limits<PixelType>::max(); | |
357 | |
358 for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) | |
359 { | |
360 PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y)); | |
361 for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) | |
362 { | |
363 for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) | |
364 { | |
365 int32_t v = source.GetValue(x, y, c); | |
366 if (v < static_cast<int32_t>(minValue)) | |
367 { | |
368 *pixel = minValue; | |
369 } | |
370 else if (v > static_cast<int32_t>(maxValue)) | |
371 { | |
372 *pixel = maxValue; | |
373 } | |
374 else | |
375 { | |
376 *pixel = static_cast<PixelType>(v); | |
377 } | |
378 } | |
379 } | |
380 } | |
381 } | |
382 | |
383 | |
384 ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, | |
385 unsigned int frame) | |
386 { | |
387 ImageSource source; | |
388 source.Setup(dataset, frame); | |
389 | |
390 | |
391 /** | |
392 * Resize the target image. | |
393 **/ | |
394 | |
395 std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); | |
396 | |
397 if (source.GetWidth() != target->GetWidth() || | |
398 source.GetHeight() != target->GetHeight()) | |
399 { | |
400 throw OrthancException(ErrorCode_InternalError); | |
401 } | |
402 | |
403 | |
404 /** | |
405 * If the format of the DICOM buffer is natively supported, use a | |
406 * direct access to copy its values. | |
407 **/ | |
408 | |
409 const DicomImageInformation& info = source.GetAccessor().GetInformation(); | |
410 | |
411 bool fastVersionSuccess = false; | |
412 PixelFormat sourceFormat; | |
413 if (!info.IsPlanar() && | |
414 info.ExtractPixelFormat(sourceFormat, false)) | |
415 { | |
416 try | |
417 { | |
418 size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat); | |
419 if ((frame + 1) * frameSize <= source.GetSize()) | |
420 { | |
421 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData()); | |
422 | |
423 ImageAccessor sourceImage; | |
424 sourceImage.AssignReadOnly(sourceFormat, | |
425 info.GetWidth(), | |
426 info.GetHeight(), | |
427 info.GetWidth() * GetBytesPerPixel(sourceFormat), | |
428 buffer + frame * frameSize); | |
429 | |
430 ImageProcessing::Convert(*target, sourceImage); | |
431 ImageProcessing::ShiftRight(*target, info.GetShift()); | |
432 fastVersionSuccess = true; | |
433 } | |
434 } | |
435 catch (OrthancException&) | |
436 { | |
437 // Unsupported conversion, use the slow version | |
438 } | |
439 } | |
440 | |
441 /** | |
442 * Slow version : loop over the DICOM buffer, storing its value | |
443 * into the target image. | |
444 **/ | |
445 | |
446 if (!fastVersionSuccess) | |
447 { | |
448 switch (target->GetFormat()) | |
449 { | |
450 case PixelFormat_RGB24: | |
451 case PixelFormat_RGBA32: | |
452 case PixelFormat_Grayscale8: | |
453 CopyPixels<uint8_t>(*target, source.GetAccessor()); | |
454 break; | |
455 | |
456 case PixelFormat_Grayscale16: | |
457 CopyPixels<uint16_t>(*target, source.GetAccessor()); | |
458 break; | |
459 | |
460 case PixelFormat_SignedGrayscale16: | |
461 CopyPixels<int16_t>(*target, source.GetAccessor()); | |
462 break; | |
463 | |
464 default: | |
465 throw OrthancException(ErrorCode_InternalError); | |
466 } | |
467 } | |
468 | |
469 return target.release(); | |
470 } | |
471 | |
472 | |
473 ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec, | |
474 const DcmCodecParameter& parameters, | |
475 DcmDataset& dataset, | |
476 unsigned int frame) | |
477 { | |
478 DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); | |
479 if (pixelSequence == NULL) | |
480 { | |
481 throw OrthancException(ErrorCode_BadFileFormat); | |
482 } | |
483 | |
484 std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); | |
485 | |
486 Uint32 startFragment = 0; // Default | |
487 OFString decompressedColorModel; // Out | |
488 DJ_RPLossless representationParameter; | |
489 OFCondition c = codec.decodeFrame(&representationParameter, | |
490 pixelSequence, ¶meters, | |
491 &dataset, frame, startFragment, target->GetBuffer(), | |
492 target->GetSize(), decompressedColorModel); | |
493 | |
494 if (c.good()) | |
495 { | |
496 return target.release(); | |
497 } | |
498 else | |
499 { | |
500 LOG(ERROR) << "Cannot decode an image"; | |
501 throw OrthancException(ErrorCode_BadFileFormat); | |
502 } | |
503 } | |
504 | |
505 | |
506 ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, | |
507 unsigned int frame) | |
508 { | |
509 DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); | |
510 E_TransferSyntax syntax = dataset.getOriginalXfer(); | |
511 | |
512 /** | |
513 * Deal with uncompressed, raw images. | |
514 * http://support.dcmtk.org/docs/dcxfer_8h-source.html | |
515 **/ | |
516 if (syntax == EXS_Unknown || | |
517 syntax == EXS_LittleEndianImplicit || | |
518 syntax == EXS_BigEndianImplicit || | |
519 syntax == EXS_LittleEndianExplicit || | |
520 syntax == EXS_BigEndianExplicit) | |
521 { | |
522 return DecodeUncompressedImage(dataset, frame); | |
523 } | |
524 | |
525 | |
526 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 | |
527 /** | |
528 * Deal with JPEG-LS images. | |
529 **/ | |
530 | |
531 if (syntax == EXS_JPEGLSLossless || | |
532 syntax == EXS_JPEGLSLossy) | |
533 { | |
534 DJLSCodecParameter parameters; | |
535 std::auto_ptr<DJLSDecoderBase> decoder; | |
536 | |
537 switch (syntax) | |
538 { | |
539 case EXS_JPEGLSLossless: | |
540 LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image"; | |
541 decoder.reset(new DJLSLosslessDecoder); | |
542 break; | |
543 | |
544 case EXS_JPEGLSLossy: | |
545 LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image"; | |
546 decoder.reset(new DJLSNearLosslessDecoder); | |
547 break; | |
548 | |
549 default: | |
550 throw OrthancException(ErrorCode_InternalError); | |
551 } | |
552 | |
553 return ApplyCodec(*decoder, parameters, dataset, frame); | |
554 } | |
555 #endif | |
556 | |
557 | |
558 #if ORTHANC_ENABLE_DCMTK_JPEG == 1 | |
559 /** | |
560 * Deal with JPEG images. | |
561 **/ | |
562 | |
563 if (syntax == EXS_JPEGProcess1 || // DJDecoderBaseline | |
564 syntax == EXS_JPEGProcess2_4 || // DJDecoderExtended | |
565 syntax == EXS_JPEGProcess6_8 || // DJDecoderSpectralSelection (retired) | |
566 syntax == EXS_JPEGProcess10_12 || // DJDecoderProgressive (retired) | |
567 syntax == EXS_JPEGProcess14 || // DJDecoderLossless | |
568 syntax == EXS_JPEGProcess14SV1) // DJDecoderP14SV1 | |
569 { | |
570 // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d | |
571 DJCodecParameter parameters( | |
572 ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression | |
573 EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr | |
574 EUC_default, // Mode for UID creation, unused for decompression | |
575 EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation | |
576 std::auto_ptr<DJCodecDecoder> decoder; | |
577 | |
578 switch (syntax) | |
579 { | |
580 case EXS_JPEGProcess1: | |
581 LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image"; | |
582 decoder.reset(new DJDecoderBaseline); | |
583 break; | |
584 | |
585 case EXS_JPEGProcess2_4 : | |
586 LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image"; | |
587 decoder.reset(new DJDecoderExtended); | |
588 break; | |
589 | |
590 case EXS_JPEGProcess6_8: // Retired | |
591 LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image"; | |
592 decoder.reset(new DJDecoderSpectralSelection); | |
593 break; | |
594 | |
595 case EXS_JPEGProcess10_12: // Retired | |
596 LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image"; | |
597 decoder.reset(new DJDecoderProgressive); | |
598 break; | |
599 | |
600 case EXS_JPEGProcess14: | |
601 LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image"; | |
602 decoder.reset(new DJDecoderLossless); | |
603 break; | |
604 | |
605 case EXS_JPEGProcess14SV1: | |
606 LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image"; | |
607 decoder.reset(new DJDecoderP14SV1); | |
608 break; | |
609 | |
610 default: | |
611 throw OrthancException(ErrorCode_InternalError); | |
612 } | |
613 | |
614 return ApplyCodec(*decoder, parameters, dataset, frame); | |
615 } | |
616 #endif | |
617 | |
618 | |
619 if (syntax == EXS_RLELossless) | |
620 { | |
621 LOG(INFO) << "Decoding a RLE lossless DICOM image"; | |
622 DcmRLECodecParameter parameters; | |
623 DcmRLECodecDecoder decoder; | |
624 return ApplyCodec(decoder, parameters, dataset, frame); | |
625 } | |
626 | |
627 | |
628 /** | |
629 * This DICOM image format is not natively supported by | |
630 * Orthanc. As a last resort, try and decode it through DCMTK by | |
631 * converting its transfer syntax to Little Endian. This will | |
632 * result in higher memory consumption. This is actually the | |
633 * second example of the following page: | |
634 * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples | |
635 **/ | |
636 | |
637 { | |
638 LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian"; | |
639 | |
640 std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); | |
641 converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); | |
642 | |
643 if (converted->canWriteXfer(EXS_LittleEndianExplicit)) | |
644 { | |
645 return DecodeUncompressedImage(*converted, frame); | |
646 } | |
647 } | |
648 | |
649 LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder"; | |
650 throw OrthancException(ErrorCode_BadFileFormat); | |
651 } | |
652 | |
653 | |
654 static bool IsColorImage(PixelFormat format) | |
655 { | |
656 return (format == PixelFormat_RGB24 || | |
657 format == PixelFormat_RGBA32); | |
658 } | |
659 | |
660 | |
661 bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, | |
662 PixelFormat format, | |
663 bool allowColorConversion) | |
664 { | |
665 // If specified, prevent the conversion between color and | |
666 // grayscale images | |
667 bool isSourceColor = IsColorImage(image->GetFormat()); | |
668 bool isTargetColor = IsColorImage(format); | |
669 | |
670 if (!allowColorConversion) | |
671 { | |
672 if (isSourceColor ^ isTargetColor) | |
673 { | |
674 return false; | |
675 } | |
676 } | |
677 | |
678 if (image->GetFormat() != format) | |
679 { | |
680 // A conversion is required | |
681 std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false)); | |
682 ImageProcessing::Convert(*target, *image); | |
683 image = target; | |
684 } | |
685 | |
686 return true; | |
687 } | |
688 | |
689 | |
690 bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image) | |
691 { | |
692 switch (image->GetFormat()) | |
693 { | |
694 case PixelFormat_RGB24: | |
695 { | |
696 // Directly return color images without modification (RGB) | |
697 return true; | |
698 } | |
699 | |
700 case PixelFormat_Grayscale8: | |
701 case PixelFormat_Grayscale16: | |
702 case PixelFormat_SignedGrayscale16: | |
703 { | |
704 // Grayscale image: Stretch its dynamics to the [0,255] range | |
705 int64_t a, b; | |
706 ImageProcessing::GetMinMaxValue(a, b, *image); | |
707 | |
708 if (a == b) | |
709 { | |
710 ImageProcessing::Set(*image, 0); | |
711 } | |
712 else | |
713 { | |
714 ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); | |
715 } | |
716 | |
717 // If the source image is not grayscale 8bpp, convert it | |
718 if (image->GetFormat() != PixelFormat_Grayscale8) | |
719 { | |
720 std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); | |
721 ImageProcessing::Convert(*target, *image); | |
722 image = target; | |
723 } | |
724 | |
725 return true; | |
726 } | |
727 | |
728 default: | |
729 throw OrthancException(ErrorCode_NotImplemented); | |
730 } | |
731 } | |
732 | |
733 | |
734 void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, | |
735 ImageExtractionMode mode, | |
736 bool invert) | |
737 { | |
738 if (image.get() == NULL) | |
739 { | |
740 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
741 } | |
742 | |
743 bool ok = false; | |
744 | |
745 switch (mode) | |
746 { | |
747 case ImageExtractionMode_UInt8: | |
748 ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false); | |
749 break; | |
750 | |
751 case ImageExtractionMode_UInt16: | |
752 ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false); | |
753 break; | |
754 | |
755 case ImageExtractionMode_Int16: | |
756 ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false); | |
757 break; | |
758 | |
759 case ImageExtractionMode_Preview: | |
760 ok = PreviewDecodedImage(image); | |
761 break; | |
762 | |
763 default: | |
764 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
765 } | |
766 | |
767 if (ok) | |
768 { | |
769 assert(image.get() != NULL); | |
770 | |
771 if (invert) | |
772 { | |
773 Orthanc::ImageProcessing::Invert(*image); | |
774 } | |
775 } | |
776 else | |
777 { | |
778 throw OrthancException(ErrorCode_NotImplemented); | |
779 } | |
780 } | |
781 | |
782 | |
783 #if ORTHANC_ENABLE_PNG == 1 | |
784 void DicomImageDecoder::ExtractPngImage(std::string& result, | |
785 std::auto_ptr<ImageAccessor>& image, | |
786 ImageExtractionMode mode, | |
787 bool invert) | |
788 { | |
789 ApplyExtractionMode(image, mode, invert); | |
790 | |
791 PngWriter writer; | |
792 writer.WriteToMemory(result, *image); | |
793 } | |
794 #endif | |
795 | |
796 | |
797 #if ORTHANC_ENABLE_JPEG == 1 | |
798 void DicomImageDecoder::ExtractJpegImage(std::string& result, | |
799 std::auto_ptr<ImageAccessor>& image, | |
800 ImageExtractionMode mode, | |
801 bool invert, | |
802 uint8_t quality) | |
803 { | |
804 if (mode != ImageExtractionMode_UInt8 && | |
805 mode != ImageExtractionMode_Preview) | |
806 { | |
807 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
808 } | |
809 | |
810 ApplyExtractionMode(image, mode, invert); | |
811 | |
812 JpegWriter writer; | |
813 writer.SetQuality(quality); | |
814 writer.WriteToMemory(result, *image); | |
815 } | |
816 #endif | |
817 } |