# HG changeset patch # User Sebastien Jodogne # Date 1638803710 -3600 # Node ID 7d189530d6488d4a77911c602aa91812705821ad # Parent 49f647ed1b4c115c8dd8bd2568ce9a178979a4d5 added class CytomineImage diff -r 49f647ed1b4c -r 7d189530d648 .hgignore --- /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 +*~ diff -r 49f647ed1b4c -r 7d189530d648 Applications/CMakeLists.txt --- 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 diff -r 49f647ed1b4c -r 7d189530d648 Framework/Inputs/CytomineImage.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 . + **/ + + +#include "CytomineImage.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#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(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(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(imageId_) + "/window-" + + boost::lexical_cast(x) + "-" + + boost::lexical_cast(y) + "-" + + boost::lexical_cast(w) + "-" + + boost::lexical_cast(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(imageId_) + ".json", Orthanc::MimeType_Json)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource, "Inexistent image in Cytomine: " + + boost::lexical_cast(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); + } + } +} diff -r 49f647ed1b4c -r 7d189530d648 Framework/Inputs/CytomineImage.h --- /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 . + **/ + + +#pragma once + +#include "DecodedTiledPyramid.h" + +#include + +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; + } + }; +}