Mercurial > hg > orthanc
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, ¶meters, | |
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, ¶meters, | |
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 } |