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