Mercurial > hg > orthanc
changeset 4827:4cfd96732076
Support decoding of black-and-white images (with 1 bit per pixel), notably DICOM SEG
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 25 Nov 2021 15:52:54 +0100 |
parents | 381c2ca04860 |
children | 2425fa7bd50d |
files | NEWS OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp |
diffstat | 4 files changed, 145 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Thu Nov 25 13:58:10 2021 +0100 +++ b/NEWS Thu Nov 25 15:52:54 2021 +0100 @@ -11,7 +11,7 @@ the new storage cache. * New configuration option "ZipLoaderThreads" to configure the number of threads used to read instances from storage when createing a Zip archive/media. - +* Support decoding of black-and-white images (with 1 bit per pixel), notably DICOM SEG Maintenance -----------
--- a/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp Thu Nov 25 13:58:10 2021 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp Thu Nov 25 15:52:54 2021 +0100 @@ -124,6 +124,11 @@ bitsStored_ = bitsAllocated_; } + if (bitsStored_ > bitsAllocated_) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + if (!values.ParseUnsignedInteger32(highBit_, DICOM_TAG_HIGH_BIT)) { highBit_ = bitsStored_ - 1; @@ -168,7 +173,8 @@ } if (bitsAllocated_ != 8 && bitsAllocated_ != 16 && - bitsAllocated_ != 24 && bitsAllocated_ != 32) + bitsAllocated_ != 24 && bitsAllocated_ != 32 && + bitsAllocated_ != 1 /* new in Orthanc 1.9.8 */) { throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: " + boost::lexical_cast<std::string>(bitsAllocated_) + " bits allocated"); } @@ -186,7 +192,26 @@ throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: samples per pixel is 0"); } - bytesPerValue_ = bitsAllocated_ / 8; + if (bitsStored_ == 1) + { + // This is the case of DICOM SEG, new in Orthanc 1.9.8 + if (bitsAllocated_ != 1) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else if (width_ % 8 != 0) + { + throw OrthancException(ErrorCode_BadFileFormat, "Bad number of columns for a black-and-white image"); + } + else + { + bytesPerValue_ = 0; // Arbitrary initialization + } + } + else + { + bytesPerValue_ = bitsAllocated_ / 8; + } isPlanar_ = (planarConfiguration != 0 ? true : false); isSigned_ = (pixelRepresentation != 0 ? true : false); @@ -237,7 +262,16 @@ size_t DicomImageInformation::GetBytesPerValue() const { - return bytesPerValue_; + if (bitsStored_ == 1) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls, + "This call is incompatible with black-and-white images"); + } + else + { + assert(bitsAllocated_ >= 8); + return bytesPerValue_; + } } bool DicomImageInformation::IsSigned() const @@ -315,6 +349,13 @@ format = PixelFormat_Grayscale32; return true; } + + if (GetBitsStored() == 1 && GetChannelCount() == 1 && !IsSigned()) + { + // This is the case of DICOM SEG, new in Orthanc 1.9.8 + format = PixelFormat_Grayscale8; + return true; + } } if (GetBitsStored() == 8 && @@ -332,10 +373,27 @@ size_t DicomImageInformation::GetFrameSize() const { - return (GetHeight() * - GetWidth() * - GetBytesPerValue() * - GetChannelCount()); + if (bitsStored_ == 1) + { + assert(GetWidth() % 8 == 0); + + if (GetChannelCount() == 1) + { + return GetHeight() * GetWidth() / 8; + } + else + { + throw OrthancException(ErrorCode_IncompatibleImageFormat, + "Image not supported (multi-channel black-and-image image)"); + } + } + else + { + return (GetHeight() * + GetWidth() * + GetBytesPerValue() * + GetChannelCount()); + } }
--- a/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp Thu Nov 25 13:58:10 2021 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp Thu Nov 25 15:52:54 2021 +0100 @@ -88,7 +88,24 @@ * means the order of the pixel values sent shall be R1, G1, B1, * R2, G2, B2, ..., etc. **/ - rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue() * information_.GetChannelCount(); + if (information_.GetBitsStored() == 1) + { + if (information_.GetChannelCount() == 1 && + information_.GetBitsAllocated() == 1) + { + assert(information_.GetWidth() % 8 == 0); // Tested by DicomImageInformation + rowOffset_ = information_.GetWidth() / 8; + } + else + { + throw OrthancException(ErrorCode_IncompatibleImageFormat, + "Image not supported (multi-channel black-and-image image)"); + } + } + else + { + rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue() * information_.GetChannelCount(); + } } } @@ -133,53 +150,74 @@ assert(x < information_.GetWidth() && y < information_.GetHeight() && channel < information_.GetChannelCount()); + + const uint8_t* pixel = (reinterpret_cast<const uint8_t*>(pixelData_) + + y * rowOffset_ + frame_ * frameOffset_); - const uint8_t* pixel = reinterpret_cast<const uint8_t*>(pixelData_) + - y * rowOffset_ + frame_ * frameOffset_; - - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3 - if (information_.IsPlanar()) + if (information_.GetBitsStored() == 1) { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - assert(frameOffset_ % information_.GetChannelCount() == 0); - pixel += channel * frameOffset_ / information_.GetChannelCount() + x * information_.GetBytesPerValue(); + // New in Orthanc 1.9.8, notably for DICOM SEG + assert(information_.GetBitsAllocated() == 1 && + information_.GetChannelCount() == 1 && + !information_.IsPlanar()); + + uint8_t b = pixel[x / 8]; + + if (b & (1 << (x % 8))) + { + return 255; + } + else + { + return 0; + } } else { - /** - * The sample values for the first pixel are followed by the - * sample values for the second pixel, etc. For RGB images, this - * means the order of the pixel values sent shall be R1, G1, B1, - * R2, G2, B2, ..., etc. - **/ - pixel += channel * information_.GetBytesPerValue() + x * information_.GetChannelCount() * information_.GetBytesPerValue(); - } + // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3 + if (information_.IsPlanar()) + { + /** + * Each color plane shall be sent contiguously. For RGB images, + * this means the order of the pixel values sent is R1, R2, R3, + * ..., G1, G2, G3, ..., B1, B2, B3, etc. + **/ + assert(frameOffset_ % information_.GetChannelCount() == 0); + pixel += channel * frameOffset_ / information_.GetChannelCount() + x * information_.GetBytesPerValue(); + } + else + { + /** + * The sample values for the first pixel are followed by the + * sample values for the second pixel, etc. For RGB images, this + * means the order of the pixel values sent shall be R1, G1, B1, + * R2, G2, B2, ..., etc. + **/ + pixel += channel * information_.GetBytesPerValue() + x * information_.GetChannelCount() * information_.GetBytesPerValue(); + } - uint32_t v; - v = pixel[0]; - if (information_.GetBytesPerValue() >= 2) - v = v + (static_cast<uint32_t>(pixel[1]) << 8); - if (information_.GetBytesPerValue() >= 3) - v = v + (static_cast<uint32_t>(pixel[2]) << 16); - if (information_.GetBytesPerValue() >= 4) - v = v + (static_cast<uint32_t>(pixel[3]) << 24); + uint32_t v; + v = pixel[0]; + if (information_.GetBytesPerValue() >= 2) + v = v + (static_cast<uint32_t>(pixel[1]) << 8); + if (information_.GetBytesPerValue() >= 3) + v = v + (static_cast<uint32_t>(pixel[2]) << 16); + if (information_.GetBytesPerValue() >= 4) + v = v + (static_cast<uint32_t>(pixel[3]) << 24); - v = v >> information_.GetShift(); + v = v >> information_.GetShift(); - if (v & signMask_) - { - // Signed value - // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N - return -static_cast<int32_t>(mask_) + static_cast<int32_t>(v & mask_) - 1; - } - else - { - // Unsigned value - return static_cast<int32_t>(v & mask_); + if (v & signMask_) + { + // Signed value + // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N + return -static_cast<int32_t>(mask_) + static_cast<int32_t>(v & mask_) - 1; + } + else + { + // Unsigned value + return static_cast<int32_t>(v & mask_); + } } }
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Thu Nov 25 13:58:10 2021 +0100 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Thu Nov 25 15:52:54 2021 +0100 @@ -542,6 +542,7 @@ bool fastVersionSuccess = false; PixelFormat sourceFormat; if (!info.IsPlanar() && + info.GetBitsStored() != 1 && // Black-and-white image, notably DICOM SEG (new in Orthanc 1.9.8) info.ExtractPixelFormat(sourceFormat, false)) { try