diff Framework/Jpeg2000Reader.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/Jpeg2000Reader.cpp	Sat Oct 22 21:48:33 2016 +0200
@@ -0,0 +1,490 @@
+/**
+ * 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 "Jpeg2000Reader.h"
+
+#include "Orthanc/Core/OrthancException.h"
+#include "Orthanc/Core/Toolbox.h"
+#include "ImageToolbox.h"
+
+#include <cassert>
+#include <string.h>
+#include <openjpeg.h>
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+#  define OPJ_CLRSPC_GRAY CLRSPC_GRAY
+#  define OPJ_CLRSPC_SRGB CLRSPC_SRGB
+#  define OPJ_CODEC_J2K   CODEC_J2K
+#  define OPJ_CODEC_JP2   CODEC_JP2
+typedef opj_dinfo_t opj_codec_t;
+typedef opj_cio_t opj_stream_t;
+#elif ORTHANC_OPENJPEG_MAJOR_VERSION == 2
+#else
+#error Unsupported version of OpenJpeg
+#endif
+
+
+namespace OrthancWSI
+{
+  namespace
+  {
+    // Check out opj_dparameters_t::decod_format
+    enum InputFormat
+    {
+      InputFormat_J2K = 0,
+      InputFormat_JP2 = 1,
+      InputFormat_JPT = 2
+    };
+
+    enum OutputFormat
+    {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+      OutputFormat_PGX = 1
+#else
+      OutputFormat_PGX = 11
+#endif
+    };
+
+    class OpenJpegDecoder : public boost::noncopyable
+    {
+    private:
+      opj_dparameters_t  parameters_;
+      opj_codec_t* dinfo_;
+
+      void SetupParameters(InputFormat format)
+      {
+        opj_set_default_decoder_parameters(&parameters_);
+
+        parameters_.decod_format = format;
+        parameters_.cod_format = OutputFormat_PGX;
+
+#if OPENJPEG_MAJOR_VERSION == 1
+        parameters_.cp_layer = 0;
+        parameters_.cp_reduce = 0;
+#endif
+      }
+
+      void Finalize()
+      {
+        if (dinfo_ != NULL)
+        {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          opj_destroy_decompress(dinfo_);
+#else
+          opj_destroy_codec(dinfo_);
+#endif
+          dinfo_ = NULL;
+        }
+      }
+
+    public:
+      OpenJpegDecoder(Jpeg2000Format format) : dinfo_(NULL)
+      {
+        switch (format)
+        {
+          case Jpeg2000Format_J2K:
+            SetupParameters(InputFormat_J2K);
+            dinfo_ = opj_create_decompress(OPJ_CODEC_J2K);
+            break;
+
+          case Jpeg2000Format_JP2:
+            SetupParameters(InputFormat_JP2);
+            dinfo_ = opj_create_decompress(OPJ_CODEC_JP2);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        if (!dinfo_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        opj_setup_decoder(dinfo_, &parameters_);
+#else
+        if (!opj_setup_decoder(dinfo_, &parameters_))
+        {
+          Finalize();
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+#endif
+      }
+
+      ~OpenJpegDecoder()
+      {
+        Finalize();
+      } 
+
+      opj_codec_t* GetObject()
+      {
+        return dinfo_;
+      }
+
+      const opj_dparameters_t& GetParameters() const
+      {
+        return parameters_;
+      }
+    };
+
+
+    class OpenJpegInput : public boost::noncopyable
+    {
+    private:
+      opj_stream_t* cio_;
+
+      const uint8_t* buffer_;
+      size_t         size_;
+      size_t         position_;
+      
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 2
+      static void Free(void *userData)
+      {
+      }
+
+      static OPJ_SIZE_T Read(void *target, 
+                             OPJ_SIZE_T size, 
+                             void *userData)
+      {
+        OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData);
+        assert(that.position_ >= 0 && that.position_ <= that.size_);
+        assert(size >= 0);
+
+        if (that.position_ == that.size_)
+        {
+          // End of file
+          return -1;
+        }
+        else
+        {
+          if (that.position_ + size > that.size_)
+          {
+            size = that.size_ - that.position_;
+          }
+
+          if (size > 0)
+          {
+            memcpy(target, that.buffer_ + that.position_, size);
+          }
+
+          that.position_ += size;
+          return size;
+        }
+      }
+ 
+      static OPJ_OFF_T Skip(OPJ_OFF_T skip, 
+                            void *userData)
+      {
+        assert(skip >= 0);
+        OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData);
+
+        if (that.position_ == that.size_)
+        {
+          // End of file
+          return -1;
+        }
+        else if (that.position_ + skip > that.size_)
+        {
+          size_t offset = that.size_ - that.position_;
+          that.position_ = that.size_;
+          return offset;
+        }
+        else
+        {
+          that.position_ += skip;
+          return skip;
+        }
+      }
+ 
+      static OPJ_BOOL Seek(OPJ_OFF_T position, 
+                           void *userData)
+      {
+        assert(position >= 0);
+        OpenJpegInput& that = *reinterpret_cast<OpenJpegInput*>(userData);
+        
+        if (static_cast<size_t>(position) > that.size_)
+        {
+          that.position_ = that.size_;
+          return false;
+        }
+        else
+        {
+          that.position_ = position;
+          return true;
+        }
+      }
+#endif
+
+    public:
+      OpenJpegInput(OpenJpegDecoder& decoder,
+                    const void* buffer,
+                    size_t size) :
+        buffer_(reinterpret_cast<const uint8_t*>(buffer)),
+        size_(size),
+        position_(0)
+      {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        cio_ = opj_cio_open(reinterpret_cast<opj_common_ptr>(decoder.GetObject()), 
+                            reinterpret_cast<unsigned char*>(const_cast<void*>(buffer)),
+                            size);
+#else
+        cio_ = opj_stream_create(size_, 1 /* input stream */);
+        if (!cio_)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        // http://openjpeg.narkive.com/zHqG2fMe/opj-stream-set-user-data-length
+        // "I'd suggest to precise in the documentation that the skip
+        // and read callback functions should return -1 on end of
+        // stream, and the seek callback function should return false
+        // on end of stream."
+
+        opj_stream_set_user_data(cio_, this, Free);
+        opj_stream_set_user_data_length(cio_, size);
+        opj_stream_set_read_function(cio_, Read);
+        opj_stream_set_skip_function(cio_, Skip);
+        opj_stream_set_seek_function(cio_, Seek);
+#endif
+    }
+
+      ~OpenJpegInput()
+      {
+        if (cio_)
+        {
+ #if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+          opj_cio_close(cio_);
+#else
+          opj_stream_destroy(cio_);
+#endif
+          cio_ = NULL;
+        }
+      }
+
+      opj_stream_t* GetObject()
+      {
+        return cio_;
+      }
+    };
+
+
+    class OpenJpegImage
+    {
+    private:
+      opj_image_t* image_;
+
+      void CopyChannel(Orthanc::ImageAccessor& target,
+                       unsigned int channel,
+                       unsigned int targetIncrement)
+      {
+        int32_t* q = image_->comps[channel].data;
+        assert(q != NULL);
+
+        for (unsigned int y = 0; y < target.GetHeight(); y++)
+        {
+          uint8_t *p = reinterpret_cast<uint8_t*>(target.GetRow(y)) + channel;
+
+          for (unsigned int x = 0; x < target.GetWidth(); x++, p += targetIncrement)
+          {
+            *p = *q;
+            q++;
+          }
+        }        
+      }
+
+    public:
+      OpenJpegImage(OpenJpegDecoder& decoder,
+                    OpenJpegInput& input) :
+        image_(NULL)
+      {
+#if ORTHANC_OPENJPEG_MAJOR_VERSION == 1
+        image_ = opj_decode(decoder.GetObject(), input.GetObject());
+        if (image_ == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+#else
+        if (!opj_read_header(input.GetObject(), decoder.GetObject(), &image_) ||
+            image_ == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        if (!opj_set_decode_area(decoder.GetObject(), image_, 
+                                 static_cast<int32_t>(decoder.GetParameters().DA_x0),
+                                 static_cast<int32_t>(decoder.GetParameters().DA_y0),
+                                 static_cast<int32_t>(decoder.GetParameters().DA_x1),
+                                 static_cast<int32_t>(decoder.GetParameters().DA_y1)) ||  // Decode the whole image
+            !opj_decode(decoder.GetObject(), input.GetObject(), image_) ||
+            !opj_end_decompress(decoder.GetObject(), input.GetObject()))
+        {
+          opj_image_destroy(image_);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+#endif
+      }
+
+      ~OpenJpegImage()
+      {
+        if (image_ != NULL)
+        {
+          opj_image_destroy(image_);
+          image_ = NULL;
+        }
+      }
+
+      Orthanc::ImageAccessor* ProvideImage()
+      {
+        if (image_->x1 < 0 ||
+            image_->y1 < 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        if (image_->x0 != 0 ||
+            image_->y0 != 0)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+
+        for (unsigned int c = 0; c < static_cast<unsigned int>(image_->numcomps); c++)
+        {
+          if (image_->comps[c].dx != 1 ||
+              image_->comps[c].dy != 1 ||
+              image_->comps[c].x0 != 0 ||
+              image_->comps[c].y0 != 0 ||
+              image_->comps[c].w != image_->x1 ||
+              image_->comps[c].h != image_->y1 ||
+              image_->comps[c].prec != 8 ||
+              image_->comps[c].sgnd != 0)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+          }
+        }
+
+        unsigned int width = static_cast<unsigned int>(image_->x1);
+        unsigned int height = static_cast<unsigned int>(image_->y1);
+
+        Orthanc::PixelFormat format;
+        if (image_->numcomps == 1 && image_->color_space != OPJ_CLRSPC_GRAY)
+        {
+          format = Orthanc::PixelFormat_Grayscale8;
+        }
+        else if (image_->numcomps == 3 && image_->color_space != OPJ_CLRSPC_SRGB)
+        {
+          format = Orthanc::PixelFormat_RGB24;
+        }
+        else
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+
+        std::auto_ptr<Orthanc::ImageAccessor> image(ImageToolbox::Allocate(format, width, height));
+        
+        switch (format)
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+          {
+            CopyChannel(*image, 0, 1);
+            break;
+          }
+
+          case Orthanc::PixelFormat_RGB24:
+          {
+            CopyChannel(*image, 0, 3);
+            CopyChannel(*image, 1, 3);
+            CopyChannel(*image, 2, 3);
+            break;
+          }
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        return image.release();
+      }
+
+    };
+  }
+
+
+  void Jpeg2000Reader::ReadFromMemory(const void* buffer,
+                                      size_t size)
+  {
+    OpenJpegDecoder decoder(DetectFormatFromMemory(buffer, size));
+    OpenJpegInput input(decoder, buffer, size);
+    OpenJpegImage image(decoder, input);
+    
+    image_.reset(image.ProvideImage());
+    AssignReadOnly(image_->GetFormat(), 
+                   image_->GetWidth(),
+                   image_->GetHeight(), 
+                   image_->GetPitch(), 
+                   image_->GetConstBuffer());
+  }
+
+
+  void Jpeg2000Reader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.empty())
+    {
+      ReadFromMemory(NULL, 0);
+    }
+    else
+    {
+      ReadFromMemory(buffer.c_str(), buffer.size());
+    }
+  }
+
+  void Jpeg2000Reader::ReadFromFile(const std::string& filename)
+  {
+    // TODO Use opj_stream_create_file_stream() ?
+
+    std::string content;
+    Orthanc::Toolbox::ReadFile(content, filename);
+  }
+
+
+  Jpeg2000Format Jpeg2000Reader::DetectFormatFromMemory(const void* buffer,
+                                                        size_t size)
+  {
+    static const char JP2_RFC3745_HEADER[] = "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a";
+    static const char JP2_HEADER[] = "\x0d\x0a\x87\x0a";
+    static const char J2K_HEADER[] = "\xff\x4f\xff\x51";
+
+    if (size < sizeof(JP2_RFC3745_HEADER) - 1)
+    {
+      return Jpeg2000Format_Unknown;
+    }
+     
+    if (memcmp(buffer, JP2_RFC3745_HEADER, sizeof(JP2_RFC3745_HEADER) - 1) == 0 || 
+        memcmp(buffer, JP2_HEADER, sizeof(JP2_HEADER) - 1) == 0)
+    {
+      return Jpeg2000Format_JP2;
+    }
+    else if (memcmp(buffer, J2K_HEADER, sizeof(J2K_HEADER) - 1) == 0)
+    {
+      return Jpeg2000Format_J2K;
+    }
+    
+    return Jpeg2000Format_Unknown;
+  }
+}