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-2021 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 Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #include "../PrecompiledHeaders.h"
24 
25 #ifndef NOMINMAX
26 #define NOMINMAX
27 #endif
28 
29 #include "DicomImageInformation.h"
30 
31 #include "../Compatibility.h"
32 #include "../OrthancException.h"
33 #include "../Toolbox.h"
34 #include <boost/lexical_cast.hpp>
35 #include <limits>
36 #include <cassert>
37 #include <stdio.h>
38 #include <memory>
39 
40 namespace Orthanc
41 {
DicomImageInformation(const DicomMap & values)42   DicomImageInformation::DicomImageInformation(const DicomMap& values)
43   {
44     uint32_t pixelRepresentation = 0;
45     uint32_t planarConfiguration = 0;
46 
47     try
48     {
49       std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).GetContent();
50       Toolbox::ToUpperCase(p);
51 
52       if (p == "RGB")
53       {
54         photometric_ = PhotometricInterpretation_RGB;
55       }
56       else if (p == "MONOCHROME1")
57       {
58         photometric_ = PhotometricInterpretation_Monochrome1;
59       }
60       else if (p == "MONOCHROME2")
61       {
62         photometric_ = PhotometricInterpretation_Monochrome2;
63       }
64       else if (p == "PALETTE COLOR")
65       {
66         photometric_ = PhotometricInterpretation_Palette;
67       }
68       else if (p == "HSV")
69       {
70         photometric_ = PhotometricInterpretation_HSV;
71       }
72       else if (p == "ARGB")
73       {
74         photometric_ = PhotometricInterpretation_ARGB;
75       }
76       else if (p == "CMYK")
77       {
78         photometric_ = PhotometricInterpretation_CMYK;
79       }
80       else if (p == "YBR_FULL")
81       {
82         photometric_ = PhotometricInterpretation_YBRFull;
83       }
84       else if (p == "YBR_FULL_422")
85       {
86         photometric_ = PhotometricInterpretation_YBRFull422;
87       }
88       else if (p == "YBR_PARTIAL_420")
89       {
90         photometric_ = PhotometricInterpretation_YBRPartial420;
91       }
92       else if (p == "YBR_PARTIAL_422")
93       {
94         photometric_ = PhotometricInterpretation_YBRPartial422;
95       }
96       else if (p == "YBR_ICT")
97       {
98         photometric_ = PhotometricInterpretation_YBR_ICT;
99       }
100       else if (p == "YBR_RCT")
101       {
102         photometric_ = PhotometricInterpretation_YBR_RCT;
103       }
104       else
105       {
106         photometric_ = PhotometricInterpretation_Unknown;
107       }
108 
109       values.GetValue(DICOM_TAG_COLUMNS).ParseFirstUnsignedInteger(width_); // in some US images, we've seen tag values of "800\0"; that's why we parse the 'first' value
110       values.GetValue(DICOM_TAG_ROWS).ParseFirstUnsignedInteger(height_);
111 
112       if (!values.ParseUnsignedInteger32(bitsAllocated_, DICOM_TAG_BITS_ALLOCATED))
113       {
114         throw OrthancException(ErrorCode_BadFileFormat);
115       }
116 
117       if (!values.ParseUnsignedInteger32(samplesPerPixel_, DICOM_TAG_SAMPLES_PER_PIXEL))
118       {
119         samplesPerPixel_ = 1;  // Assume 1 color channel
120       }
121 
122       if (!values.ParseUnsignedInteger32(bitsStored_, DICOM_TAG_BITS_STORED))
123       {
124         bitsStored_ = bitsAllocated_;
125       }
126 
127       if (!values.ParseUnsignedInteger32(highBit_, DICOM_TAG_HIGH_BIT))
128       {
129         highBit_ = bitsStored_ - 1;
130       }
131 
132       if (!values.ParseUnsignedInteger32(pixelRepresentation, DICOM_TAG_PIXEL_REPRESENTATION))
133       {
134         pixelRepresentation = 0;  // Assume unsigned pixels
135       }
136 
137       if (samplesPerPixel_ > 1)
138       {
139         // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1
140         // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3
141 
142         if (!values.ParseUnsignedInteger32(planarConfiguration, DICOM_TAG_PLANAR_CONFIGURATION))
143         {
144           planarConfiguration = 0;  // Assume interleaved color channels
145         }
146       }
147     }
148     catch (boost::bad_lexical_cast&)
149     {
150       throw OrthancException(ErrorCode_NotImplemented);
151     }
152     catch (OrthancException&)
153     {
154       throw OrthancException(ErrorCode_NotImplemented);
155     }
156 
157 
158     if (values.HasTag(DICOM_TAG_NUMBER_OF_FRAMES))
159     {
160       if (!values.ParseUnsignedInteger32(numberOfFrames_, DICOM_TAG_NUMBER_OF_FRAMES))
161       {
162         throw OrthancException(ErrorCode_NotImplemented);
163       }
164     }
165     else
166     {
167       numberOfFrames_ = 1;
168     }
169 
170     if (bitsAllocated_ != 8 && bitsAllocated_ != 16 &&
171         bitsAllocated_ != 24 && bitsAllocated_ != 32)
172     {
173       throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: " + boost::lexical_cast<std::string>(bitsAllocated_) + " bits allocated");
174     }
175     else if (numberOfFrames_ == 0)
176     {
177       throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported (no frames)");
178     }
179     else if (planarConfiguration != 0 && planarConfiguration != 1)
180     {
181       throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: planar configuration is " + boost::lexical_cast<std::string>(planarConfiguration));
182     }
183 
184     if (samplesPerPixel_ == 0)
185     {
186       throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: samples per pixel is 0");
187     }
188 
189     bytesPerValue_ = bitsAllocated_ / 8;
190 
191     isPlanar_ = (planarConfiguration != 0 ? true : false);
192     isSigned_ = (pixelRepresentation != 0 ? true : false);
193   }
194 
Clone() const195   DicomImageInformation* DicomImageInformation::Clone() const
196   {
197     std::unique_ptr<DicomImageInformation> target(new DicomImageInformation);
198     target->width_ = width_;
199     target->height_ = height_;
200     target->samplesPerPixel_ = samplesPerPixel_;
201     target->numberOfFrames_ = numberOfFrames_;
202     target->isPlanar_ = isPlanar_;
203     target->isSigned_ = isSigned_;
204     target->bytesPerValue_ = bytesPerValue_;
205     target->bitsAllocated_ = bitsAllocated_;
206     target->bitsStored_ = bitsStored_;
207     target->highBit_ = highBit_;
208     target->photometric_ = photometric_;
209 
210     return target.release();
211   }
212 
GetWidth() const213   unsigned int DicomImageInformation::GetWidth() const
214   {
215     return width_;
216   }
217 
GetHeight() const218   unsigned int DicomImageInformation::GetHeight() const
219   {
220     return height_;
221   }
222 
GetNumberOfFrames() const223   unsigned int DicomImageInformation::GetNumberOfFrames() const
224   {
225     return numberOfFrames_;
226   }
227 
GetChannelCount() const228   unsigned int DicomImageInformation::GetChannelCount() const
229   {
230     return samplesPerPixel_;
231   }
232 
GetBitsStored() const233   unsigned int DicomImageInformation::GetBitsStored() const
234   {
235     return bitsStored_;
236   }
237 
GetBytesPerValue() const238   size_t DicomImageInformation::GetBytesPerValue() const
239   {
240     return bytesPerValue_;
241   }
242 
IsSigned() const243   bool DicomImageInformation::IsSigned() const
244   {
245     return isSigned_;
246   }
247 
GetBitsAllocated() const248   unsigned int DicomImageInformation::GetBitsAllocated() const
249   {
250     return bitsAllocated_;
251   }
252 
GetHighBit() const253   unsigned int DicomImageInformation::GetHighBit() const
254   {
255     return highBit_;
256   }
257 
IsPlanar() const258   bool DicomImageInformation::IsPlanar() const
259   {
260     return isPlanar_;
261   }
262 
GetShift() const263   unsigned int DicomImageInformation::GetShift() const
264   {
265     return highBit_ + 1 - bitsStored_;
266   }
267 
GetPhotometricInterpretation() const268   PhotometricInterpretation DicomImageInformation::GetPhotometricInterpretation() const
269   {
270     return photometric_;
271   }
272 
ExtractPixelFormat(PixelFormat & format,bool ignorePhotometricInterpretation) const273   bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format,
274                                                  bool ignorePhotometricInterpretation) const
275   {
276     if (photometric_ == PhotometricInterpretation_Palette)
277     {
278       if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned())
279       {
280         format = PixelFormat_RGB24;
281         return true;
282       }
283 
284       if (GetBitsStored() == 16 && GetChannelCount() == 1 && !IsSigned())
285       {
286         format = PixelFormat_RGB48;
287         return true;
288       }
289     }
290 
291     if (ignorePhotometricInterpretation ||
292         photometric_ == PhotometricInterpretation_Monochrome1 ||
293         photometric_ == PhotometricInterpretation_Monochrome2)
294     {
295       if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned())
296       {
297         format = PixelFormat_Grayscale8;
298         return true;
299       }
300 
301       if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned())
302       {
303         format = PixelFormat_Grayscale16;
304         return true;
305       }
306 
307       if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned())
308       {
309         format = PixelFormat_SignedGrayscale16;
310         return true;
311       }
312 
313       if (GetBitsAllocated() == 32 && GetChannelCount() == 1 && !IsSigned())
314       {
315         format = PixelFormat_Grayscale32;
316         return true;
317       }
318     }
319 
320     if (GetBitsStored() == 8 &&
321         GetChannelCount() == 3 &&
322         !IsSigned() &&
323         (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_RGB))
324     {
325       format = PixelFormat_RGB24;
326       return true;
327     }
328 
329     return false;
330   }
331 
332 
GetFrameSize() const333   size_t DicomImageInformation::GetFrameSize() const
334   {
335     return (GetHeight() *
336             GetWidth() *
337             GetBytesPerValue() *
338             GetChannelCount());
339   }
340 
341 
GetUsefulTagLength()342   unsigned int DicomImageInformation::GetUsefulTagLength()
343   {
344     return 256;
345   }
346 }
347