comparison OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp @ 4044:d25f4c0fa160 framework

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