Mercurial > hg > orthanc-wsi
diff Framework/Inputs/HierarchicalTiff.cpp @ 0:4a7a53257c7d
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 22 Oct 2016 21:48:33 +0200 |
parents | |
children | 7a88c614be04 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/HierarchicalTiff.cpp Sat Oct 22 21:48:33 2016 +0200 @@ -0,0 +1,307 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, 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 "HierarchicalTiff.h" + +#include "../Orthanc/Core/Logging.h" +#include "../Orthanc/Core/OrthancException.h" + +#include <iostream> +#include <algorithm> +#include <cassert> +#include <string.h> + +namespace OrthancWSI +{ + HierarchicalTiff::Level::Level(TIFF* tiff, + tdir_t directory, + unsigned int width, + unsigned int height) : + directory_(directory), + width_(width), + height_(height) + { + // Read the JPEG headers shared at that level, if any + uint8_t *tables = NULL; + uint32_t size; + if (TIFFGetField(tiff, TIFFTAG_JPEGTABLES, &size, &tables) && + size > 0 && + tables != NULL) + { + // Look for the EOI (end-of-image) tag == FF D9 + // https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format + + bool found = false; + + for (size_t i = 0; i + 1 < size; i++) + { + if (tables[i] == 0xff && + tables[i + 1] == 0xd9) + { + headers_.assign(reinterpret_cast<const char*>(tables), i); + found = true; + } + } + + if (!found) + { + headers_.assign(reinterpret_cast<const char*>(tables), size); + } + } + } + + struct HierarchicalTiff::Comparator + { + bool operator() (const HierarchicalTiff::Level& a, + const HierarchicalTiff::Level& b) const + { + return a.width_ > b.width_; + } + }; + + + void HierarchicalTiff::Finalize() + { + if (tiff_) + { + TIFFClose(tiff_); + tiff_ = NULL; + } + } + + + void HierarchicalTiff::CheckLevel(unsigned int level) const + { + if (level >= levels_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + bool HierarchicalTiff::GetCurrentCompression(ImageCompression& compression) + { + uint16_t c; + if (!TIFFGetField(tiff_, TIFFTAG_COMPRESSION, &c)) + { + return false; + } + + switch (c) + { + case COMPRESSION_NONE: + compression = ImageCompression_None; + return true; + + case COMPRESSION_JPEG: + compression = ImageCompression_Jpeg; + return true; + + default: + return false; + } + } + + + bool HierarchicalTiff::GetCurrentPixelFormat(Orthanc::PixelFormat& pixelFormat, + ImageCompression compression) + { + // http://www.awaresystems.be/imaging/tiff/tifftags/baseline.html + + uint16_t channels, photometric, bpp, planar; + if (!TIFFGetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, &channels) || + channels == 0 || + !TIFFGetField(tiff_, TIFFTAG_PHOTOMETRIC, &photometric) || + !TIFFGetField(tiff_, TIFFTAG_BITSPERSAMPLE, &bpp) || + !TIFFGetField(tiff_, TIFFTAG_PLANARCONFIG, &planar)) + { + return false; + } + + if (compression == ImageCompression_Jpeg && + channels == 3 && // This is a color image + bpp == 8 && + photometric == PHOTOMETRIC_YCBCR && + planar == PLANARCONFIG_CONTIG) // This is interleaved RGB + { + pixelFormat = Orthanc::PixelFormat_RGB24; + } + else + { + return false; + } + + return true; + } + + + bool HierarchicalTiff::Initialize() + { + bool first = true; + tdir_t pos = 0; + + do + { + uint32_t w, h, tw, th; + ImageCompression compression; + Orthanc::PixelFormat pixelFormat; + + if (TIFFSetDirectory(tiff_, pos) && + TIFFGetField(tiff_, TIFFTAG_IMAGEWIDTH, &w) && + TIFFGetField(tiff_, TIFFTAG_IMAGELENGTH, &h) && + TIFFGetField(tiff_, TIFFTAG_TILEWIDTH, &tw) && + TIFFGetField(tiff_, TIFFTAG_TILELENGTH, &th) && + w > 0 && + h > 0 && + tw > 0 && + th > 0 && + GetCurrentCompression(compression) && + GetCurrentPixelFormat(pixelFormat, compression)) + { + if (first) + { + tileWidth_ = tw; + tileHeight_ = th; + compression_ = compression; + pixelFormat_ = pixelFormat; + first = false; + } + else if (tw != tileWidth_ || + th != tileHeight_ || + compression_ != compression || + pixelFormat_ != pixelFormat) + { + LOG(ERROR) << "The tile size or compression of the TIFF file varies along levels, this is not supported"; + return false; + } + + levels_.push_back(Level(tiff_, pos, w, h)); + } + + pos++; + } + while (TIFFReadDirectory(tiff_)); + + if (levels_.size() == 0) + { + LOG(ERROR) << "This is not a tiled TIFF image"; + return false; + } + + std::sort(levels_.begin(), levels_.end(), Comparator()); + return true; + } + + + HierarchicalTiff::HierarchicalTiff(const std::string& path) : + tileWidth_(0), + tileHeight_(0) + { + tiff_ = TIFFOpen(path.c_str(), "r"); + if (tiff_ == NULL) + { + LOG(ERROR) << "libtiff cannot open: " << path; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); + } + + if (!Initialize()) + { + Finalize(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + unsigned int HierarchicalTiff::GetLevelWidth(unsigned int level) const + { + CheckLevel(level); + return levels_[level].width_; + } + + + unsigned int HierarchicalTiff::GetLevelHeight(unsigned int level) const + { + CheckLevel(level); + return levels_[level].height_; + } + + + bool HierarchicalTiff::ReadRawTile(std::string& tile, + unsigned int level, + unsigned int tileX, + unsigned int tileY) + { + boost::mutex::scoped_lock lock(mutex_); + + CheckLevel(level); + + // Make the TIFF context point to the level of interest + if (!TIFFSetDirectory(tiff_, levels_[level].directory_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + // Get the index of the tile + ttile_t index = TIFFComputeTile(tiff_, tileX * tileWidth_, tileY * tileHeight_, 0 /*z*/, 0 /*sample*/); + + // Read the raw tile + toff_t *sizes; + if (!TIFFGetField(tiff_, TIFFTAG_TILEBYTECOUNTS, &sizes)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + std::string raw; + raw.resize(sizes[index]); + + tsize_t read = TIFFReadRawTile(tiff_, index, &raw[0], raw.size()); + if (read != static_cast<tsize_t>(sizes[index])) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + const std::string& headers = levels_[level].headers_; + + // Possibly prepend the raw tile with the shared JPEG headers + if (headers.empty() || + compression_ != ImageCompression_Jpeg) + { + tile.swap(raw); // Same as "tile.assign(raw)", but optimizes memory + } + else + { + assert(compression_ == ImageCompression_Jpeg); + + // Check that the raw JPEG tile starts with the SOI (start-of-image) tag == FF D8 + if (raw.size() < 2 || + static_cast<uint8_t>(raw[0]) != 0xff || + static_cast<uint8_t>(raw[1]) != 0xd8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile); + } + + tile.resize(headers.size() + raw.size() - 2); + memcpy(&tile[0], &headers[0], headers.size()); + memcpy(&tile[0] + headers.size(), &raw[2], raw.size() - 2); + } + + return true; + } +}