Mercurial > hg > orthanc-wsi
changeset 167:605247fc8758
Fix issue #144 (OrthancWSIDicomizer PhotometricInterpretation)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 12 Jul 2019 12:00:31 +0200 |
parents | f0dac1e8f736 |
children | d3aea0af03e1 |
files | Applications/DicomToTiff.cpp Applications/Dicomizer.cpp Framework/DicomizerParameters.cpp Framework/ImageToolbox.cpp Framework/ImageToolbox.h Framework/Inputs/PyramidWithRawTiles.cpp Framework/Outputs/DicomPyramidWriter.cpp Framework/Outputs/DicomPyramidWriter.h Framework/Outputs/HierarchicalTiffWriter.cpp Framework/Outputs/HierarchicalTiffWriter.h Framework/Outputs/MultiframeDicomWriter.cpp Framework/Outputs/MultiframeDicomWriter.h NEWS |
diffstat | 13 files changed, 171 insertions(+), 111 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/DicomToTiff.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Applications/DicomToTiff.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -183,20 +183,36 @@ static void Run(OrthancWSI::ITiledPyramid& source, const boost::program_options::variables_map& options) { + bool reencode = (options.count(OPTION_REENCODE) && + options[OPTION_REENCODE].as<bool>()); + + Orthanc::PhotometricInterpretation targetPhotometric; + + if (reencode) + { + // The DicomToTiff tool only creates TIFF images with JPEG tiles (*) + targetPhotometric = Orthanc::PhotometricInterpretation_YBRFull422; + } + else + { + targetPhotometric = source.GetPhotometricInterpretation(); + } + OrthancWSI::HierarchicalTiffWriter target(options[OPTION_OUTPUT].as<std::string>(), source.GetPixelFormat(), - OrthancWSI::ImageCompression_Jpeg, + OrthancWSI::ImageCompression_Jpeg, // (*) source.GetTileWidth(), - source.GetTileHeight()); - - bool reencode = (options.count(OPTION_REENCODE) && - options[OPTION_REENCODE].as<bool>()); + source.GetTileHeight(), + targetPhotometric); if (options.count(OPTION_JPEG_QUALITY)) { target.SetJpegQuality(options[OPTION_JPEG_QUALITY].as<int>()); } + LOG(WARNING) << "Source photometric interpretation: " << EnumerationToString(source.GetPhotometricInterpretation()); + LOG(WARNING) << "Target photometric interpretation: " << EnumerationToString(targetPhotometric); + std::auto_ptr<Orthanc::ImageAccessor> empty(CreateEmptyTile(target, options)); for (unsigned int level = 0; level < source.GetLevelCount(); level++) @@ -226,15 +242,31 @@ bool missing = false; bool success = true; - // Give a first try to get the raw tile std::string tile; OrthancWSI::ImageCompression compression; + if (source.ReadRawTile(tile, compression, level, tileX, tileY)) { - if (reencode || - compression == OrthancWSI::ImageCompression_Jpeg) + if (compression == OrthancWSI::ImageCompression_Jpeg) + { + // Transcoding of JPEG tiles + target.WriteRawTile(tile, compression, level, tileX, tileY); + } + else if (reencode) { - target.WriteRawTile(tile, compression, level, tileX, tileY); + std::auto_ptr<Orthanc::ImageAccessor> decoded; + + if (compression == OrthancWSI::ImageCompression_None) + { + decoded.reset(OrthancWSI::ImageToolbox::DecodeRawTile(tile, source.GetPixelFormat(), + source.GetTileWidth(), source.GetTileHeight())); + } + else + { + decoded.reset(OrthancWSI::ImageToolbox::DecodeTile(tile, compression)); + } + + target.EncodeTile(*decoded, level, tileX, tileY); } else { @@ -243,23 +275,8 @@ } else { - // Give a second try to get the decoded tile - compression = OrthancWSI::ImageCompression_Unknown; - - std::auto_ptr<Orthanc::ImageAccessor> tile(source.DecodeTile(level, tileX, tileY)); - if (tile.get() == NULL) - { - // Unable to read the raw tile or to decode it: The tile is missing (sparse tiling) - missing = true; - } - else if (reencode) - { - target.EncodeTile(*empty, level, tileX, tileY); - } - else - { - success = false; // Re-encoding is mandatory - } + // Unable to read the raw tile: The tile is missing (sparse tiling) + missing = true; } if (!success)
--- a/Applications/Dicomizer.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Applications/Dicomizer.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -84,6 +84,8 @@ OrthancWSI::ITiledPyramid& source, const OrthancWSI::DicomizerParameters& parameters) { + LOG(WARNING) << "Transcoding the source pyramid (not re-encoding)"; + Orthanc::BagOfTasks tasks; for (unsigned int i = 0; i < source.GetLevelCount(); i++) @@ -93,8 +95,6 @@ target.AddLevel(source.GetLevelWidth(i), source.GetLevelHeight(i)); } - LOG(WARNING) << "Transcoding the source pyramid"; - OrthancWSI::TranscodeTileCommand::PrepareBagOfTasks(tasks, target, source, parameters); OrthancWSI::ApplicationToolbox::Execute(tasks, parameters.GetThreadsCount()); } @@ -104,6 +104,8 @@ OrthancWSI::ITiledPyramid& source, const OrthancWSI::DicomizerParameters& parameters) { + LOG(WARNING) << "Re-encoding the source pyramid (not transcoding, slower process)"; + Orthanc::BagOfTasks tasks; unsigned int levelsCount = parameters.GetPyramidLevelsCount(target, source); @@ -160,12 +162,15 @@ OrthancWSI::ITiledPyramid& source, const DcmDataset& dataset, const OrthancWSI::DicomizerParameters& parameters, - const OrthancWSI::ImagedVolumeParameters& volume) + const OrthancWSI::ImagedVolumeParameters& volume, + OrthancWSI::ImageCompression sourceCompression) { OrthancWSI::TiledPyramidStatistics stats(source); LOG(WARNING) << "Size of source tiles: " << stats.GetTileWidth() << "x" << stats.GetTileHeight(); - LOG(WARNING) << "Pixel format: " << Orthanc::EnumerationToString(stats.GetPixelFormat()); + LOG(WARNING) << "Pixel format: " << Orthanc::EnumerationToString(source.GetPixelFormat()); + LOG(WARNING) << "Source photometric interpretation: " << Orthanc::EnumerationToString(source.GetPhotometricInterpretation()); + LOG(WARNING) << "Source compression: " << EnumerationToString(sourceCompression); LOG(WARNING) << "Smoothing is " << (parameters.IsSmoothEnabled() ? "enabled" : "disabled"); if (parameters.IsRepaintBackground()) @@ -180,26 +185,60 @@ LOG(WARNING) << "No repainting of the background"; } + Orthanc::PhotometricInterpretation targetPhotometric; + bool transcoding; + + if (parameters.IsForceReencode() || + parameters.IsReconstructPyramid() || + sourceCompression != parameters.GetTargetCompression()) + { + // The tiles of the source image will be re-encoded + transcoding = false; + + switch (parameters.GetTargetCompression()) + { + case OrthancWSI::ImageCompression_Jpeg: + case OrthancWSI::ImageCompression_Jpeg2000: + targetPhotometric = Orthanc::PhotometricInterpretation_YBRFull422; + break; + + case OrthancWSI::ImageCompression_None: + targetPhotometric = Orthanc::PhotometricInterpretation_RGB; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + else + { + // Transcoding: The tiles are copied (no re-encoding) + transcoding = true; + targetPhotometric = source.GetPhotometricInterpretation(); + } + OrthancWSI::DicomPyramidWriter target(output, dataset, source.GetPixelFormat(), parameters.GetTargetCompression(), parameters.GetTargetTileWidth(source), parameters.GetTargetTileHeight(source), parameters.GetDicomMaxFileSize(), - volume); + volume, targetPhotometric); target.SetJpegQuality(parameters.GetJpegQuality()); LOG(WARNING) << "Size of target tiles: " << target.GetTileWidth() << "x" << target.GetTileHeight(); + LOG(WARNING) << "Target photometric interpretation: " << Orthanc::EnumerationToString(targetPhotometric); - if (target.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg) + if (!transcoding && + target.GetImageCompression() == OrthancWSI::ImageCompression_Jpeg) { - LOG(WARNING) << "Target image compression: Jpeg with quality " + LOG(WARNING) << "Target compression: Jpeg with quality " << static_cast<int>(target.GetJpegQuality()); target.SetJpegQuality(target.GetJpegQuality()); } else { - LOG(WARNING) << "Target image compression: " + LOG(WARNING) << "Target compression: " << OrthancWSI::EnumerationToString(target.GetImageCompression()); } @@ -219,13 +258,13 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); } - if (parameters.IsReconstructPyramid()) + if (transcoding) { - ReconstructPyramid(target, stats, parameters); + TranscodePyramid(target, stats, parameters); } else { - TranscodePyramid(target, stats, parameters); + ReconstructPyramid(target, stats, parameters); } target.Flush(); @@ -863,6 +902,7 @@ } + OrthancWSI::ITiledPyramid* OpenInputPyramid(OrthancWSI::ImageCompression& sourceCompression, const std::string& path, const OrthancWSI::DicomizerParameters& parameters) @@ -954,7 +994,7 @@ EnrichDataset(*dataset, *source, sourceCompression, parameters, volume); std::auto_ptr<OrthancWSI::IFileTarget> output(parameters.CreateTarget()); - Recompress(*output, *source, *dataset, parameters, volume); + Recompress(*output, *source, *dataset, parameters, volume, sourceCompression); } } catch (Orthanc::OrthancException& e)
--- a/Framework/DicomizerParameters.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/DicomizerParameters.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -156,12 +156,6 @@ unsigned int DicomizerParameters::GetPyramidLevelsCount(const IPyramidWriter& target, const ITiledPyramid& source) const { - if (!reconstructPyramid_) - { - // Only makes sense if reconstructing the pyramid - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - if (pyramidLevelsCount_ != 0) { return pyramidLevelsCount_; @@ -205,12 +199,6 @@ unsigned int DicomizerParameters::GetPyramidLowerLevelsCount(const IPyramidWriter& target, const ITiledPyramid& source) const { - if (!reconstructPyramid_) - { - // Only makes sense if reconstructing the pyramid - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - if (pyramidLowerLevelsCount_ != 0) { return pyramidLowerLevelsCount_;
--- a/Framework/ImageToolbox.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/ImageToolbox.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -158,6 +158,25 @@ } + Orthanc::ImageAccessor* DecodeRawTile(const std::string& source, + Orthanc::PixelFormat format, + unsigned int width, + unsigned int height) + { + unsigned int bpp = GetBytesPerPixel(format); + + if (bpp * width * height != source.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + Orthanc::ImageAccessor accessor; + accessor.AssignReadOnly(format, width, height, bpp * width, source.empty() ? NULL : source.c_str()); + + return Orthanc::Image::Clone(accessor); + } + + void EncodeTile(std::string& target, const Orthanc::ImageAccessor& source, ImageCompression compression,
--- a/Framework/ImageToolbox.h Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/ImageToolbox.h Fri Jul 12 12:00:31 2019 +0200 @@ -54,6 +54,11 @@ Orthanc::ImageAccessor* DecodeTile(const std::string& source, ImageCompression compression); + Orthanc::ImageAccessor* DecodeRawTile(const std::string& source, + Orthanc::PixelFormat format, + unsigned int width, + unsigned int height); + void EncodeTile(std::string& target, const Orthanc::ImageAccessor& source, ImageCompression compression,
--- a/Framework/Inputs/PyramidWithRawTiles.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Inputs/PyramidWithRawTiles.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -22,10 +22,7 @@ #include "../PrecompiledHeadersWSI.h" #include "PyramidWithRawTiles.h" -#include <Core/Images/PngReader.h> -#include <Core/Images/JpegReader.h> -#include <Core/OrthancException.h> -#include "../Jpeg2000Reader.h" +#include "../ImageToolbox.h" namespace OrthancWSI { @@ -40,39 +37,13 @@ { return NULL; } - - std::auto_ptr<Orthanc::ImageAccessor> result; - - switch (compression) + else if (compression == ImageCompression_None) { - case ImageCompression_None: - result.reset(new Orthanc::ImageAccessor); - result->AssignReadOnly(GetPixelFormat(), - GetTileWidth(), - GetTileHeight(), - GetBytesPerPixel(GetPixelFormat()) * GetTileWidth(), - tile.c_str()); - break; - - case ImageCompression_Jpeg: - result.reset(new Orthanc::JpegReader); - dynamic_cast<Orthanc::JpegReader&>(*result).ReadFromMemory(tile); - break; - - case ImageCompression_Png: - result.reset(new Orthanc::PngReader); - dynamic_cast<Orthanc::PngReader&>(*result).ReadFromMemory(tile); - break; - - case ImageCompression_Jpeg2000: - result.reset(new Jpeg2000Reader); - dynamic_cast<Jpeg2000Reader&>(*result).ReadFromMemory(tile); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + return ImageToolbox::DecodeRawTile(tile, GetPixelFormat(), GetTileWidth(), GetTileHeight()); } - - return result.release(); + else + { + return ImageToolbox::DecodeTile(tile, compression); + } } }
--- a/Framework/Outputs/DicomPyramidWriter.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Outputs/DicomPyramidWriter.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -140,7 +140,7 @@ { writer = new MultiframeDicomWriter (dataset_, GetImageCompression(), GetPixelFormat(), level.width_, level.height_, - GetTileWidth(), GetTileHeight()); + GetTileWidth(), GetTileHeight(), photometric_); writers_[z] = writer; } @@ -166,14 +166,16 @@ unsigned int tileWidth, unsigned int tileHeight, size_t maxSize, // If "0", no automatic flushing - const ImagedVolumeParameters& volume) : + const ImagedVolumeParameters& volume, + Orthanc::PhotometricInterpretation photometric) : PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight), target_(target), dataset_(dataset), maxSize_(maxSize), countTiles_(0), countInstances_(0), - volume_(volume) + volume_(volume), + photometric_(photometric) { }
--- a/Framework/Outputs/DicomPyramidWriter.h Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Outputs/DicomPyramidWriter.h Fri Jul 12 12:00:31 2019 +0200 @@ -39,8 +39,9 @@ size_t maxSize_; size_t countTiles_; unsigned int countInstances_; - - const ImagedVolumeParameters& volume_; + + const ImagedVolumeParameters& volume_; + Orthanc::PhotometricInterpretation photometric_; void FlushInternal(MultiframeDicomWriter& writer, bool force); @@ -70,7 +71,8 @@ unsigned int tileWidth, unsigned int tileHeight, size_t maxSize, // If "0", no automatic flushing - const ImagedVolumeParameters& volume); + const ImagedVolumeParameters& volume, + Orthanc::PhotometricInterpretation photometric); virtual ~DicomPyramidWriter();
--- a/Framework/Outputs/HierarchicalTiffWriter.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Outputs/HierarchicalTiffWriter.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -266,12 +266,29 @@ case Orthanc::PixelFormat_RGB24: { uint16_t samplesPerPixel = 3; - uint16_t photometric = PHOTOMETRIC_YCBCR; + uint16_t photometric; uint16_t planar = PLANARCONFIG_CONTIG; // Interleaved RGB uint16_t bpp = 8; uint16_t subsampleHorizontal = 2; uint16_t subsampleVertical = 2; + switch (photometric_) + { + case Orthanc::PhotometricInterpretation_YBRFull422: + photometric = PHOTOMETRIC_YCBCR; + break; + + case Orthanc::PhotometricInterpretation_RGB: + photometric = PHOTOMETRIC_RGB; + break; + + default: + throw Orthanc::OrthancException( + Orthanc::ErrorCode_ParameterOutOfRange, + "Unsupported photometric interpreation: " + + std::string(Orthanc::EnumerationToString(photometric_))); + } + if (TIFFSetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel) != 1 || TIFFSetField(tiff_, TIFFTAG_PHOTOMETRIC, photometric) != 1 || TIFFSetField(tiff_, TIFFTAG_BITSPERSAMPLE, bpp) != 1 || @@ -383,12 +400,14 @@ Orthanc::PixelFormat pixelFormat, ImageCompression compression, unsigned int tileWidth, - unsigned int tileHeight) : + unsigned int tileHeight, + Orthanc::PhotometricInterpretation photometric) : PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight), currentLevel_(0), nextX_(0), nextY_(0), - isFirst_(true) + isFirst_(true), + photometric_(photometric) { tiff_ = TIFFOpen(path.c_str(), "w"); if (tiff_ == NULL)
--- a/Framework/Outputs/HierarchicalTiffWriter.h Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Outputs/HierarchicalTiffWriter.h Fri Jul 12 12:00:31 2019 +0200 @@ -46,7 +46,8 @@ unsigned int nextX_; unsigned int nextY_; bool isFirst_; - + Orthanc::PhotometricInterpretation photometric_; + void Close() { TIFFClose(tiff_); @@ -78,7 +79,8 @@ Orthanc::PixelFormat pixelFormat, ImageCompression compression, unsigned int tileWidth, - unsigned int tileHeight); + unsigned int tileHeight, + Orthanc::PhotometricInterpretation photometric); virtual ~HierarchicalTiffWriter();
--- a/Framework/Outputs/MultiframeDicomWriter.cpp Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Outputs/MultiframeDicomWriter.cpp Fri Jul 12 12:00:31 2019 +0200 @@ -147,7 +147,8 @@ unsigned int width, unsigned int height, unsigned int tileWidth, - unsigned int tileHeight) : + unsigned int tileHeight, + Orthanc::PhotometricInterpretation photometric) : compression_(compression), width_(width), height_(height) @@ -187,28 +188,18 @@ DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsStored, 8); DicomToolbox::SetUint16Tag(sharedTags_, DCM_HighBit, 7); DicomToolbox::SetUint16Tag(sharedTags_, DCM_PixelRepresentation, 0); // Unsigned values + DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, Orthanc::EnumerationToString(photometric)); switch (pixelFormat) { case Orthanc::PixelFormat_RGB24: uncompressedFrameSize_ = 3 * tileWidth * tileHeight; DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 3); - - if (compression_ == ImageCompression_Jpeg) - { - DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "YBR_FULL_422"); - } - else - { - DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "RGB"); - } - break; case Orthanc::PixelFormat_Grayscale8: uncompressedFrameSize_ = tileWidth * tileHeight; DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 1); - DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "MONOCHROME2"); break; default:
--- a/Framework/Outputs/MultiframeDicomWriter.h Fri Jul 12 09:06:54 2019 +0200 +++ b/Framework/Outputs/MultiframeDicomWriter.h Fri Jul 12 12:00:31 2019 +0200 @@ -63,7 +63,8 @@ unsigned int width, unsigned int height, unsigned int tileWidth, - unsigned int tileHeight); + unsigned int tileHeight, + Orthanc::PhotometricInterpretation photometric); void AddFrame(const std::string& frame, DcmItem* functionalGroup); // This takes the ownership
--- a/NEWS Fri Jul 12 09:06:54 2019 +0200 +++ b/NEWS Fri Jul 12 12:00:31 2019 +0200 @@ -1,6 +1,9 @@ Pending changes in the mainline =============================== +* Improved consistency when transcoding/re-encoding is applied +* Fix issue #144 (OrthancWSIDicomizer PhotometricInterpretation) + Version 0.6 (2019-01-26) ======================== @@ -23,7 +26,7 @@ Version 0.4 (2017-03-01) ======================== -* Fix issue #30: Bad colorspace if using OpenSlide +* Fix issue #30 (Bad colorspace if using OpenSlide) Version 0.3 (2016-12-23)