Mercurial > hg > orthanc-wsi
changeset 298:fa734a851551
New option: "tiff-alignment" to control deep zoom of plain TIFF over IIIF
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 18 Jul 2023 08:19:16 +0200 |
parents | c1687b8fc800 |
children | 9cd5690e7684 |
files | Applications/CMakeLists.txt Applications/Dicomizer.cpp Framework/DicomizerParameters.cpp Framework/DicomizerParameters.h Framework/Inputs/PlainTiff.cpp Framework/Inputs/PlainTiff.h NEWS |
diffstat | 7 files changed, 268 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/CMakeLists.txt Tue Jul 18 07:13:36 2023 +0200 +++ b/Applications/CMakeLists.txt Tue Jul 18 08:19:16 2023 +0200 @@ -119,6 +119,7 @@ ${ORTHANC_WSI_DIR}/Framework/Inputs/HierarchicalTiff.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlideLibrary.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlidePyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/PlainTiff.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/SingleLevelDecodedPyramid.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp
--- a/Applications/Dicomizer.cpp Tue Jul 18 07:13:36 2023 +0200 +++ b/Applications/Dicomizer.cpp Tue Jul 18 08:19:16 2023 +0200 @@ -29,6 +29,7 @@ #include "../Framework/Inputs/CytomineImage.h" #include "../Framework/Inputs/HierarchicalTiff.h" #include "../Framework/Inputs/OpenSlidePyramid.h" +#include "../Framework/Inputs/PlainTiff.h" #include "../Framework/Inputs/TiledJpegImage.h" #include "../Framework/Inputs/TiledPngImage.h" #include "../Framework/Inputs/TiledPyramidStatistics.h" @@ -91,6 +92,9 @@ static const char* OPTION_CYTOMINE_PRIVATE_KEY = "cytomine-private-key"; static const char* OPTION_CYTOMINE_COMPRESSION = "cytomine-compression"; +// New in release 2.1 +static const char* OPTION_TIFF_ALIGNMENT = "tiff-alignment"; + #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 0) @@ -592,6 +596,9 @@ "Whether to repaint the background of the image (Boolean)") (OPTION_COLOR, boost::program_options::value<std::string>(), "Color of the background (e.g. \"255,0,0\")") + (OPTION_TIFF_ALIGNMENT, boost::program_options::value<int>()->default_value(64), + "Add padding to plain TIFF images to align the width/height to multiples " + "of this value, very useful to enable deep zoom with IIIF (1 means no padding)") ; boost::program_options::options_description cytomine("Options if importing from Cytomine"); @@ -1004,6 +1011,20 @@ parameters.SetIccProfilePath(options[OPTION_ICC_PROFILE].as<std::string>()); } + if (options.count(OPTION_TIFF_ALIGNMENT)) + { + int value = options[OPTION_TIFF_ALIGNMENT].as<int>(); + if (value <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "TIFF alignment must be >= 1"); + } + else + { + parameters.SetTiffAlignment(static_cast<unsigned int>(value)); + } + } + return true; } @@ -1061,8 +1082,17 @@ } catch (Orthanc::OrthancException&) { - LOG(WARNING) << "This is not a standard hierarchical TIFF file"; + LOG(WARNING) << "This is not a standard hierarchical TIFF file, fallback to plain TIFF"; } + + sourceCompression = OrthancWSI::ImageCompression_Unknown; + return new OrthancWSI::PlainTiff(path, + parameters.GetTargetTileWidth(512), + parameters.GetTargetTileHeight(512), + parameters.GetTiffAlignment(), + parameters.GetBackgroundColorRed(), + parameters.GetBackgroundColorGreen(), + parameters.GetBackgroundColorBlue()); } default:
--- a/Framework/DicomizerParameters.cpp Tue Jul 18 07:13:36 2023 +0200 +++ b/Framework/DicomizerParameters.cpp Tue Jul 18 08:19:16 2023 +0200 @@ -73,7 +73,8 @@ opticalPath_(OpticalPath_Brightfield), isCytomineSource_(false), cytomineImageInstanceId_(-1), - cytomineCompression_(ImageCompression_Png) + cytomineCompression_(ImageCompression_Png), + tiffAlignment_(1) { backgroundColor_[0] = 255; backgroundColor_[1] = 255; @@ -359,4 +360,17 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } } + + + void DicomizerParameters::SetTiffAlignment(unsigned int alignment) + { + if (alignment <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + tiffAlignment_ = alignment; + } + } }
--- a/Framework/DicomizerParameters.h Tue Jul 18 07:13:36 2023 +0200 +++ b/Framework/DicomizerParameters.h Tue Jul 18 08:19:16 2023 +0200 @@ -67,6 +67,9 @@ std::string cytominePrivateKey_; ImageCompression cytomineCompression_; + // New in release 2.1 + unsigned int tiffAlignment_; + public: DicomizerParameters(); @@ -278,5 +281,12 @@ int GetCytomineImageInstanceId() const; ImageCompression GetCytomineCompression() const; + + void SetTiffAlignment(unsigned int alignment); + + unsigned int GetTiffAlignment() const + { + return tiffAlignment_; + } }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/PlainTiff.cpp Tue Jul 18 08:19:16 2023 +0200 @@ -0,0 +1,166 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "PlainTiff.h" + +#include "../TiffReader.h" + +#include <Images/Image.h> +#include <Images/ImageProcessing.h> +#include <Logging.h> +#include <OrthancException.h> + +#include <cassert> +#include <string.h> + + +namespace OrthancWSI +{ + PlainTiff::PlainTiff(const std::string& path, + unsigned int tileWidth, + unsigned int tileHeight, + unsigned int paddingAlignement, + uint8_t paddingRed, + uint8_t paddingGreen, + uint8_t paddingBlue) : + SingleLevelDecodedPyramid(tileWidth, tileHeight) + { + TiffReader reader(path); + + // Look for the largest sub-image + bool first = true; + unsigned int width = 0; + unsigned int height = 0; + tdir_t largest = 0; + + tdir_t pos = 0; + + do + { + uint32_t w, h, tw, th; + + if (TIFFSetDirectory(reader.GetTiff(), pos) && + !TIFFGetField(reader.GetTiff(), TIFFTAG_TILEWIDTH, &tw) && // Must not be a tiled image + !TIFFGetField(reader.GetTiff(), TIFFTAG_TILELENGTH, &th) && // Must not be a tiled image + TIFFGetField(reader.GetTiff(), TIFFTAG_IMAGEWIDTH, &w) && + TIFFGetField(reader.GetTiff(), TIFFTAG_IMAGELENGTH, &h) && + w > 0 && + h > 0) + { + if (first) + { + first = false; + width = w; + height = h; + largest = pos; + } + else if (w > width && + h > height) + { + width = w; + height = h; + largest = pos; + } + } + + pos++; + } + while (TIFFReadDirectory(reader.GetTiff())); + + if (first) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "This is an empty TIFF image"); + } + + // Back to the largest directory + if (!TIFFSetDirectory(reader.GetTiff(), largest)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + ImageCompression compression; + Orthanc::PixelFormat pixelFormat; + Orthanc::PhotometricInterpretation photometric; + + if (!reader.GetCurrentDirectoryInformation(compression, pixelFormat, photometric)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (pixelFormat != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + LOG(WARNING) << "Size of the source TIFF image: " << width << "x" << height; + + const unsigned int paddedWidth = paddingAlignement * CeilingDivision(width, paddingAlignement); + const unsigned int paddedHeight = paddingAlignement * CeilingDivision(height, paddingAlignement); + assert(paddedWidth >= width && + paddedHeight >= height); + + LOG(WARNING) << "Size of the padded TIFF image: " << paddedWidth << "x" << paddedHeight; + + decoded_.reset(new Orthanc::Image(pixelFormat, paddedWidth, paddedHeight, false)); + Orthanc::ImageProcessing::Set(*decoded_, paddingRed, paddingGreen, paddingBlue, 255); + + std::string strip; + strip.resize(TIFFStripSize(reader.GetTiff())); + + const size_t stripPitch = width * Orthanc::GetBytesPerPixel(pixelFormat); + + if (strip.empty() || + stripPitch == 0 || + strip.size() < stripPitch || + strip.size() % stripPitch != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + const size_t stripHeight = (strip.size() / stripPitch); + const size_t stripCount = CeilingDivision(height, stripHeight); + + if (TIFFNumberOfStrips(reader.GetTiff()) != stripCount) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + for (unsigned int i = 0; i < stripCount; i++) + { + TIFFReadEncodedStrip(reader.GetTiff(), i, &strip[0], static_cast<tsize_t>(-1)); + + const unsigned int y = i * stripHeight; + + const uint8_t* p = reinterpret_cast<const uint8_t*>(&strip[0]); + uint8_t* q = reinterpret_cast<uint8_t*>(decoded_->GetRow(y)); + + for (unsigned j = 0; j < stripHeight && y + j < height; j++) + { + memcpy(q, p, stripPitch); + p += stripPitch; + q += decoded_->GetPitch(); + } + } + + SetImage(*decoded_); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/PlainTiff.h Tue Jul 18 08:19:16 2023 +0200 @@ -0,0 +1,44 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "SingleLevelDecodedPyramid.h" + + +namespace OrthancWSI +{ + class PlainTiff : public SingleLevelDecodedPyramid + { + private: + std::unique_ptr<Orthanc::ImageAccessor> decoded_; + + public: + PlainTiff(const std::string& path, + unsigned int tileWidth, + unsigned int tileHeight, + unsigned int paddingAlignement, + uint8_t paddingRed, + uint8_t paddingGreen, + uint8_t paddingBlue); + }; +}
--- a/NEWS Tue Jul 18 07:13:36 2023 +0200 +++ b/NEWS Tue Jul 18 08:19:16 2023 +0200 @@ -2,6 +2,7 @@ =============================== * OrthancWSIDicomizer supports plain TIFF, besides hierarchical TIFF +* New option: "tiff-alignment" to control deep zoom of plain TIFF over IIIF Version 2.0 (2023-10-07)