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, &parameters,
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 }