diff Framework/Outputs/HierarchicalTiffWriter.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/Outputs/HierarchicalTiffWriter.cpp	Sat Oct 22 21:48:33 2016 +0200
@@ -0,0 +1,421 @@
+/**
+ * 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 "HierarchicalTiffWriter.h"
+
+#include "../Orthanc/Core/Logging.h"
+#include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Uuid.h"
+
+namespace OrthancWSI
+{
+  class HierarchicalTiffWriter::PendingTile
+  {
+  private:
+    HierarchicalTiffWriter&          that_;
+    unsigned int                     level_;
+    unsigned int                     tileX_;
+    unsigned int                     tileY_;
+    Orthanc::Toolbox::TemporaryFile  file_;
+      
+  public:
+    PendingTile(HierarchicalTiffWriter& that,
+                unsigned int level,
+                unsigned int tileX,
+                unsigned int tileY,
+                const std::string& tile) :
+      that_(that),
+      level_(level),
+      tileX_(tileX),
+      tileY_(tileY)
+    {
+      file_.Write(tile);
+    }
+
+    unsigned int GetLevel() const
+    {
+      return level_;
+    }
+
+    unsigned int GetTileX() const
+    {
+      return tileX_;
+    }
+
+    unsigned int GetTileY() const
+    {
+      return tileY_;
+    }
+
+    void Store(TIFF* tiff)
+    {
+      std::string tile;
+      file_.Read(tile);
+      that_.StoreTile(tile, tileX_, tileY_);
+    }
+  };
+
+
+  struct HierarchicalTiffWriter::Comparator
+  {
+    inline bool operator() (PendingTile* const& a,
+                            PendingTile* const& b)
+    {
+      if (a->GetLevel() < b->GetLevel())
+      {
+        return true;
+      }
+
+      if (a->GetLevel() > b->GetLevel())
+      {
+        return false;
+      }
+
+      if (a->GetTileY() < b->GetTileY())
+      {
+        return true;
+      }
+
+      if (a->GetTileY() > b->GetTileY())
+      {
+        return false;
+      }
+
+      return a->GetTileX() < b->GetTileX();
+    }
+  };
+
+
+  static uint8_t GetUint8(const std::string& tile,
+                          size_t index)
+  {
+    if (index >= tile.size())
+    {
+      return 0;
+    }
+    else
+    {
+      return static_cast<uint8_t>(tile[index]);
+    }
+  }
+
+ 
+#if 0
+  static uint16_t GetUint16(const std::string& tile,
+                            size_t index)
+  {
+    if (index + 1 >= tile.size())
+    {
+      return 0;
+    }
+    else
+    {
+      return static_cast<uint16_t>(tile[index]) * 256 + static_cast<uint16_t>(tile[index + 1]);
+    }
+  }
+#endif
+
+
+  static void CheckJpegTile(const std::string& tile,
+                            Orthanc::PixelFormat pixelFormat)
+  {
+    // Check the sampling by accessing the "Start of Frame" header of JPEG
+
+    if (tile.size() < 3 ||
+        static_cast<uint8_t>(tile[0]) != 0xff ||
+        static_cast<uint8_t>(tile[1]) != 0xd8 ||
+        static_cast<uint8_t>(tile[2]) != 0xff)
+    {
+      LOG(WARNING) << "The source image does not contain JPEG tiles";
+      return;
+    }
+
+    // Look for the "Start of Frame" header (FF C0)
+    for (size_t i = 2; i + 1 < tile.size(); i++)
+    {
+      if (static_cast<uint8_t>(tile[i]) == 0xff &&
+          static_cast<uint8_t>(tile[i + 1]) == 0xc0)
+      {
+        uint8_t numberOfComponents = GetUint8(tile, i + 9);
+          
+        switch (pixelFormat)
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            if (numberOfComponents != 3)
+            {
+              LOG(WARNING) << "The source image does not contain a grayscale image as expected";
+            }
+            break;
+
+          case Orthanc::PixelFormat_RGB24:
+          {
+            if (numberOfComponents != 3)
+            {
+              LOG(WARNING) << "The source image does not contain a RGB24 color image as expected";
+            }
+
+            // Read the header corresponding to the first component
+            const size_t component = 0;
+            const size_t offset = i + 10 + component * 3; 
+            const uint8_t sampling = GetUint8(tile, offset + 1);
+            const int samplingH = sampling / 16;
+            const int samplingV = sampling % 16;
+
+            LOG(WARNING) << "The source image uses chroma sampling " << samplingH << ":" << samplingV;
+
+            if (samplingH != 2 ||
+                samplingV != 2)
+            {
+              LOG(WARNING) << "The source image has not a chroma sampling of 2:2, "
+                           << "you should consider using option \"--reencode\"";
+            }
+
+            break;
+          }
+
+          default:
+            break;
+        }
+      }
+    }
+  }
+
+
+ 
+  void HierarchicalTiffWriter::StoreTile(const std::string& tile,
+                                         unsigned int tileX,
+                                         unsigned int tileY)
+  {
+    // Get the index of the tile
+    ttile_t index = TIFFComputeTile(tiff_, tileX * GetTileWidth(), tileY * GetTileHeight(), 0 /*z*/, 0 /*sample*/);
+
+    if (TIFFWriteRawTile(tiff_, index, tile.size() ? const_cast<char*>(&tile[0]) : NULL, tile.size()) != 
+        static_cast<tsize_t>(tile.size()))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+
+    if (isFirst_ &&
+        GetImageCompression() == ImageCompression_Jpeg)
+    {
+      CheckJpegTile(tile, GetPixelFormat());
+    }
+
+    isFirst_ = false;
+  }
+
+
+  void HierarchicalTiffWriter::ConfigureLevel(const Level& level,
+                                              bool createLevel)
+  {
+    if (createLevel && TIFFWriteDirectory(tiff_) != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+
+    if (TIFFFlush(tiff_) != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+
+    currentLevel_ = level.z_;
+    nextX_ = 0;
+    nextY_ = 0;
+
+    switch (GetImageCompression())
+    {
+      case ImageCompression_Jpeg:
+      {
+        uint16_t c = COMPRESSION_JPEG;
+
+        if (TIFFSetField(tiff_, TIFFTAG_COMPRESSION, c) != 1)
+        {
+          Close();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+
+    switch (GetPixelFormat())
+    {
+      case Orthanc::PixelFormat_RGB24:
+      {
+        uint16_t samplesPerPixel = 3;
+        uint16_t photometric = PHOTOMETRIC_YCBCR;
+        uint16_t planar = PLANARCONFIG_CONTIG;   // Interleaved RGB
+        uint16_t bpp = 8;
+        uint16_t subsampleHorizontal = 2;
+        uint16_t subsampleVertical = 2;
+
+        if (TIFFSetField(tiff_, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_PHOTOMETRIC, photometric) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_BITSPERSAMPLE, bpp) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_PLANARCONFIG, planar) != 1 ||
+            TIFFSetField(tiff_, TIFFTAG_YCBCRSUBSAMPLING, subsampleHorizontal, subsampleVertical) != 1)
+        {
+          Close();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+        }
+
+        break;
+      }
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    uint32_t w = level.width_;
+    uint32_t h = level.height_;
+    uint32_t tw = GetTileWidth();
+    uint32_t th = GetTileHeight();
+
+    if (TIFFSetField(tiff_, TIFFTAG_IMAGEWIDTH, w) != 1 ||
+        TIFFSetField(tiff_, TIFFTAG_IMAGELENGTH, h) != 1 ||
+        TIFFSetField(tiff_, TIFFTAG_TILEWIDTH, tw) != 1 ||
+        TIFFSetField(tiff_, TIFFTAG_TILELENGTH, th) != 1)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }          
+  }
+
+
+  void HierarchicalTiffWriter::AdvanceToNextTile()
+  {
+    assert(currentLevel_ < levels_.size());
+
+    nextX_ += 1;
+    if (nextX_ >= levels_[currentLevel_].countTilesX_)
+    {
+      nextX_ = 0;
+      nextY_ += 1;
+
+      if (nextY_ >= levels_[currentLevel_].countTilesY_)
+      {
+        currentLevel_ += 1;
+
+        if (currentLevel_ < levels_.size())
+        {
+          ConfigureLevel(levels_[currentLevel_], true);
+        }
+      }
+    }
+  }
+
+
+  void HierarchicalTiffWriter::ScanPending()
+  {
+    std::sort(pending_.begin(), pending_.end(), Comparator());
+
+    while (currentLevel_ < levels_.size() &&
+           !pending_.empty() &&
+           pending_.front()->GetLevel() == currentLevel_ &&
+           pending_.front()->GetTileX() == nextX_ &&
+           pending_.front()->GetTileY() == nextY_)
+    {
+      pending_.front()->Store(tiff_);
+      delete pending_.front();
+      pending_.pop_front();
+      AdvanceToNextTile();
+    }
+  }
+          
+
+  void HierarchicalTiffWriter::WriteRawTileInternal(const std::string& tile,
+                                                    const Level& level,
+                                                    unsigned int tileX,
+                                                    unsigned int tileY)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (level.z_ == currentLevel_ &&
+        tileX == nextX_ &&
+        tileY == nextY_)
+    {
+      StoreTile(tile, tileX, tileY);
+      AdvanceToNextTile();
+      ScanPending();
+    }
+    else
+    {
+      pending_.push_back(new PendingTile(*this, level.z_, tileX, tileY, tile));
+    }
+  }
+
+        
+  void HierarchicalTiffWriter::AddLevelInternal(const Level& level)
+  {
+    if (level.z_ == 0)
+    {
+      // Configure the finest level on initialization
+      ConfigureLevel(level, false);
+    }
+
+    levels_.push_back(level);
+  }
+
+
+  HierarchicalTiffWriter::HierarchicalTiffWriter(const std::string& path,
+                                                 Orthanc::PixelFormat pixelFormat, 
+                                                 ImageCompression compression,
+                                                 unsigned int tileWidth,
+                                                 unsigned int tileHeight) :
+    PyramidWriterBase(pixelFormat, compression, tileWidth, tileHeight),
+    currentLevel_(0),
+    isFirst_(true)
+  {
+    tiff_ = TIFFOpen(path.c_str(), "w");
+    if (tiff_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CannotWriteFile);
+    }
+  }
+
+
+  HierarchicalTiffWriter::~HierarchicalTiffWriter()
+  {
+    if (pending_.size())
+    {
+      LOG(ERROR) << "Some tiles (" << pending_.size() << ") were not written to the TIFF file";
+    }
+
+    for (size_t i = 0; i < pending_.size(); i++)
+    {
+      if (pending_[i])
+      {
+        delete pending_[i];
+      }
+    }
+
+    Close();
+  }
+
+
+  void HierarchicalTiffWriter::Flush()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    ScanPending();
+  }
+}