changeset 243:7d189530d648

added class CytomineImage
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 06 Dec 2021 16:15:10 +0100
parents 49f647ed1b4c
children 4273518c2009
files .hgignore Applications/CMakeLists.txt Framework/Inputs/CytomineImage.cpp Framework/Inputs/CytomineImage.h
diffstat 4 files changed, 366 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Mon Dec 06 16:15:10 2021 +0100
@@ -0,0 +1,4 @@
+syntax: glob
+ThirdPartyDownloads/
+*.orig
+*~
--- a/Applications/CMakeLists.txt	Mon Dec 06 16:05:42 2021 +0100
+++ b/Applications/CMakeLists.txt	Mon Dec 06 16:15:10 2021 +0100
@@ -111,6 +111,7 @@
   ${ORTHANC_WSI_DIR}/Framework/Enumerations.cpp
   ${ORTHANC_WSI_DIR}/Framework/ImageToolbox.cpp
   ${ORTHANC_WSI_DIR}/Framework/ImagedVolumeParameters.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/CytomineImage.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/DecodedTiledPyramid.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramid.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidInstance.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Inputs/CytomineImage.cpp	Mon Dec 06 16:15:10 2021 +0100
@@ -0,0 +1,271 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ * Copyright (C) 2021-2021 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 "CytomineImage.h"
+
+#include <Images/ImageProcessing.h>
+#include <Images/JpegWriter.h>
+#include <Images/PngReader.h>
+#include <Logging.h>
+#include <OrthancException.h>
+#include <Toolbox.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
+#include <openssl/hmac.h>
+
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+// OpenSSL < 1.1.0
+
+namespace
+{
+  class HmacContext : public boost::noncopyable
+  {
+  private:
+    HMAC_CTX hmac_;
+
+  public:
+    HmacContext()
+    {
+      HMAC_CTX_init(&hmac_);
+    }
+
+    ~HmacContext()
+    {
+      HMAC_CTX_cleanup(&hmac_);
+    }
+
+    HMAC_CTX* GetObject()
+    {
+      return &hmac_;
+    }
+  };
+}
+
+#else
+// OpenSSL >= 1.1.0
+
+namespace
+{
+  class HmacContext : public boost::noncopyable
+  {
+  private:
+    HMAC_CTX* hmac_;
+
+  public:
+    HmacContext()
+    {
+      hmac_ = HMAC_CTX_new();
+      if (hmac_ == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    ~HmacContext()
+    {
+      HMAC_CTX_free(hmac_);
+    }
+
+    HMAC_CTX* GetObject()
+    {
+      return hmac_;
+    }
+  };
+}
+
+#endif
+
+
+namespace OrthancWSI
+{
+  bool CytomineImage::GetCytomine(std::string& target,
+                                  const std::string& uri,
+                                  Orthanc::MimeType contentType) const
+  {
+    if (uri.empty() ||
+        uri[0] == '/')
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string t = Orthanc::EnumerationToString(contentType);
+    
+    std::string date;
+    
+    {
+      const boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time();
+
+      std::stringstream stream;
+      stream.imbue(std::locale(std::locale::classic(),
+                               new boost::posix_time::time_facet("%a, %d %b %Y %H:%M:%S +0000")));
+      stream << now;
+      date = stream.str();
+    }
+
+    std::string auth;
+
+    {
+      const std::string token = "GET\n\n" + t + "\n" + date + "\n/" + uri;
+  
+      HmacContext hmac;
+
+      unsigned char md[64];
+      unsigned int length = 0;
+      
+      if (HMAC_Init_ex(hmac.GetObject(), privateKey_.c_str(), privateKey_.length(), EVP_sha1(), NULL) != 1 ||
+          HMAC_Update(hmac.GetObject(), reinterpret_cast<const unsigned char*>(token.c_str()), token.size()) != 1 ||
+          HMAC_Final(hmac.GetObject(), md, &length) != 1)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      Orthanc::Toolbox::EncodeBase64(auth, std::string(reinterpret_cast<const char*>(md), length));
+    }
+
+    Orthanc::HttpClient c(parameters_, uri);
+    c.AddHeader("content-type", t);
+    c.AddHeader("authorization", "CYTOMINE " + publicKey_ + ":" + auth);
+    c.AddHeader("date", date);
+
+    return c.Apply(target);
+  }
+
+
+  void CytomineImage::ReadRegion(Orthanc::ImageAccessor& target,
+                                 unsigned int level,
+                                 unsigned int x,
+                                 unsigned int y)
+  {
+    if (level != 0 ||
+        x >= fullWidth_ ||
+        y >= fullHeight_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    unsigned int w = std::min(tileWidth_, fullWidth_ - x);
+    unsigned int h = std::min(tileHeight_, fullHeight_ - y);
+    
+    const std::string uri = ("api/imageinstance/" + boost::lexical_cast<std::string>(imageId_) + "/window-" +
+                             boost::lexical_cast<std::string>(x) + "-" +
+                             boost::lexical_cast<std::string>(y) + "-" +
+                             boost::lexical_cast<std::string>(w) + "-" +
+                             boost::lexical_cast<std::string>(h) + ".png");
+
+    std::string png;
+    if (!GetCytomine(png, uri, Orthanc::MimeType_Png))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Cannot read a tile from Cytomine");
+    }
+
+    Orthanc::PngReader reader;
+    reader.ReadFromMemory(png);
+
+    if (reader.GetWidth() != w ||
+        reader.GetHeight() != h)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Cytomine returned a tile of bad size");
+    }
+
+    Orthanc::ImageProcessing::Set(target, 255, 255, 255, 255);
+    
+    Orthanc::ImageAccessor region;
+    target.GetRegion(region, 0, 0, w, h);
+
+    Orthanc::ImageProcessing::Copy(target, reader);
+  }
+  
+
+  CytomineImage::CytomineImage(const Orthanc::WebServiceParameters& parameters,
+                               const std::string& publicKey,
+                               const std::string& privateKey,
+                               int imageId,
+                               unsigned int tileWidth,
+                               unsigned int tileHeight) :
+    parameters_(parameters),
+    publicKey_(publicKey),
+    privateKey_(privateKey),
+    imageId_(imageId),
+    tileWidth_(tileWidth),
+    tileHeight_(tileHeight)
+  {
+    if (tileWidth_ < 16 ||
+        tileHeight_ < 16)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string info;
+    if (!GetCytomine(info, "api/imageinstance/" + boost::lexical_cast<std::string>(imageId_) + ".json", Orthanc::MimeType_Json))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "Inexistent image in Cytomine: " +
+                                      boost::lexical_cast<std::string>(imageId));
+    }
+
+    const char* const WIDTH = "width";
+    const char* const HEIGHT = "height";
+
+    Json::Value json;
+    if (!Orthanc::Toolbox::ReadJson(json, info) ||
+        !json.isMember(WIDTH) ||
+        !json.isMember(HEIGHT) ||
+        json[WIDTH].type() != Json::intValue ||
+        json[HEIGHT].type() != Json::intValue ||
+        json[WIDTH].asInt() < 0 ||
+        json[HEIGHT].asInt() < 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Unsupported version of the Cytomine REST API");
+    }
+
+    fullWidth_ = json[WIDTH].asUInt();
+    fullHeight_ = json[HEIGHT].asUInt();
+    LOG(INFO) << "Reading an image of size " << fullWidth_ << "x" << fullHeight_ << " from Cytomine";
+  }
+
+  
+  unsigned int CytomineImage::GetLevelWidth(unsigned int level) const
+  {
+    if (level == 0)
+    {
+      return fullWidth_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  unsigned int CytomineImage::GetLevelHeight(unsigned int level) const
+  {
+    if (level == 0)
+    {
+      return fullHeight_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Inputs/CytomineImage.h	Mon Dec 06 16:15:10 2021 +0100
@@ -0,0 +1,90 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ * Copyright (C) 2021-2021 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 "DecodedTiledPyramid.h"
+
+#include <HttpClient.h>
+
+namespace OrthancWSI
+{
+  class CytomineImage : public DecodedTiledPyramid
+  {
+  private:
+    Orthanc::WebServiceParameters  parameters_;
+    std::string   publicKey_;
+    std::string   privateKey_;
+    int           imageId_;
+    unsigned int  fullWidth_;
+    unsigned int  fullHeight_;
+    unsigned int  tileWidth_;
+    unsigned int  tileHeight_;
+
+    bool GetCytomine(std::string& target,
+                     const std::string& uri,
+                     Orthanc::MimeType contentType) const;
+
+  protected:
+    virtual void ReadRegion(Orthanc::ImageAccessor& target,
+                            unsigned int level,
+                            unsigned int x,
+                            unsigned int y) ORTHANC_OVERRIDE;
+
+  public:
+    CytomineImage(const Orthanc::WebServiceParameters& parameters,
+                  const std::string& publicKey,
+                  const std::string& privateKey,
+                  int imageId,
+                  unsigned int tileWidth,
+                  unsigned int tileHeight);
+
+    virtual unsigned int GetTileWidth(unsigned int level) const ORTHANC_OVERRIDE
+    {
+      return tileWidth_;
+    }
+
+    virtual unsigned int GetTileHeight(unsigned int level) const ORTHANC_OVERRIDE
+    {
+      return tileHeight_;
+    }
+
+    virtual unsigned int GetLevelCount() const ORTHANC_OVERRIDE
+    {
+      return 1;
+    }
+
+    virtual unsigned int GetLevelWidth(unsigned int level) const ORTHANC_OVERRIDE;
+
+    virtual unsigned int GetLevelHeight(unsigned int level) const ORTHANC_OVERRIDE;
+
+    virtual Orthanc::PixelFormat GetPixelFormat() const ORTHANC_OVERRIDE
+    {
+      return Orthanc::PixelFormat_RGB24;
+    }
+
+    virtual Orthanc::PhotometricInterpretation GetPhotometricInterpretation() const ORTHANC_OVERRIDE
+    {
+      return Orthanc::PhotometricInterpretation_RGB;
+    }
+  };
+}