changeset 244:4273518c2009

OrthancWSIDicomizer: Support importing of images from Cytomine
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 06 Dec 2021 21:18:18 +0100
parents 7d189530d648
children 7ae09ea0cccd
files Applications/Dicomizer.cpp Framework/DicomizerParameters.cpp Framework/DicomizerParameters.h Framework/Inputs/CytomineImage.cpp Framework/Inputs/CytomineImage.h NEWS
diffstat 6 files changed, 282 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Dicomizer.cpp	Mon Dec 06 16:15:10 2021 +0100
+++ b/Applications/Dicomizer.cpp	Mon Dec 06 21:18:18 2021 +0100
@@ -26,6 +26,7 @@
 #include "../Framework/DicomizerParameters.h"
 #include "../Framework/ImageToolbox.h"
 #include "../Framework/ImagedVolumeParameters.h"
+#include "../Framework/Inputs/CytomineImage.h"
 #include "../Framework/Inputs/HierarchicalTiff.h"
 #include "../Framework/Inputs/OpenSlidePyramid.h"
 #include "../Framework/Inputs/TiledJpegImage.h"
@@ -83,6 +84,13 @@
 static const char* OPTION_VERBOSE = "verbose";
 static const char* OPTION_VERSION = "version";
 
+// New in release 1.1
+static const char* OPTION_CYTOMINE_URL = "cytomine-url";
+static const char* OPTION_CYTOMINE_IMAGE_INSTANCE_ID = "cytomine-image";
+static const char* OPTION_CYTOMINE_PUBLIC_KEY = "cytomine-public-key";
+static const char* OPTION_CYTOMINE_PRIVATE_KEY = "cytomine-private-key";
+static const char* OPTION_CYTOMINE_COMPRESSION = "cytomine-compression";
+
 
 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 0)
 
@@ -586,6 +594,21 @@
      "Color of the background (e.g. \"255,0,0\")")
     ;
 
+  boost::program_options::options_description cytomine("Options if importing from Cytomine");
+  cytomine.add_options()
+    (OPTION_CYTOMINE_URL, boost::program_options::value<std::string>(),
+     "URL of the source Cytomine server, for instance: https://demo.cytomine.be/")
+    (OPTION_CYTOMINE_PUBLIC_KEY, boost::program_options::value<std::string>(),
+     "Your personal public key in Cytomine (to be kept secret)")
+    (OPTION_CYTOMINE_PRIVATE_KEY, boost::program_options::value<std::string>(),
+     "Your personal private key in Cytomine (to be kept secret)")
+    (OPTION_CYTOMINE_IMAGE_INSTANCE_ID, boost::program_options::value<int>(),
+     "ID of the Image Instance of interest in Cytomine (must be an integer)")
+    (OPTION_CYTOMINE_COMPRESSION, boost::program_options::value<std::string>()->default_value("jpeg"),
+     "Compression to be used for downloading the tiles from Cytomine, "
+     "can be \"jpeg\" (faster) or \"png\" (better quality)")
+    ;
+
   boost::program_options::options_description pyramid("Options to construct the pyramid");
   pyramid.add_options()
     (OPTION_PYRAMID, boost::program_options::value<bool>()->default_value(false), 
@@ -659,6 +682,7 @@
   allWithoutHidden
     .add(generic)
     .add(source)
+    .add(cytomine)
     .add(pyramid)
     .add(target)
     .add(volumeOptions)
@@ -697,10 +721,70 @@
     return false;
   }
 
+  // New in release 1.1
+  if (options.count(OPTION_CYTOMINE_URL) ||
+      options.count(OPTION_CYTOMINE_PUBLIC_KEY) ||
+      options.count(OPTION_CYTOMINE_PRIVATE_KEY) ||
+      options.count(OPTION_CYTOMINE_IMAGE_INSTANCE_ID))
+  {
+    if (!options.count(OPTION_CYTOMINE_URL))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "URL to the Cytomine server is missing");
+    }
+
+    if (!options.count(OPTION_CYTOMINE_PUBLIC_KEY))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Public key for the Cytomine server is missing");
+    }
+
+    if (!options.count(OPTION_CYTOMINE_PRIVATE_KEY))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "Private key for the Cytomine server is missing");
+    }
+
+    if (!options.count(OPTION_CYTOMINE_IMAGE_INSTANCE_ID))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "The Image Instance ID from the Cytomine server is missing");
+    }
+
+    if (!options.count(OPTION_CYTOMINE_COMPRESSION))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "The tile compression scheme for Cytomine is missing");
+    }
+
+    const std::string s = options[OPTION_CYTOMINE_COMPRESSION].as<std::string>();
+
+    OrthancWSI::ImageCompression compression;
+    if (s == "jpeg")
+    {
+      compression = OrthancWSI::ImageCompression_Jpeg;
+    }
+    else if (s == "png")
+    {
+      compression = OrthancWSI::ImageCompression_Png;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "The tile compression scheme must be \"jpeg\" or \"png\", found: " + s);
+    }    
+    
+    parameters.SetCytomineSource(options[OPTION_CYTOMINE_URL].as<std::string>(),
+                                 options[OPTION_CYTOMINE_PUBLIC_KEY].as<std::string>(),
+                                 options[OPTION_CYTOMINE_PRIVATE_KEY].as<std::string>(),
+                                 options[OPTION_CYTOMINE_IMAGE_INSTANCE_ID].as<int>(), compression);
+  }
+
   if (!error &&
       options.count(OPTION_HELP) == 0 &&
       options.count(OPTION_VERSION) == 0 &&
-      options.count(OPTION_INPUT) != 1)
+      options.count(OPTION_INPUT) != 1 &&
+      !parameters.IsCytomineSource())
   {
     LOG(ERROR) << "No input file was specified";
     error = true;
@@ -796,7 +880,10 @@
     parameters.SetTargetTileSize(w, h);
   }
 
-  parameters.SetInputFile(options[OPTION_INPUT].as<std::string>());
+  if (!parameters.IsCytomineSource())
+  {
+    parameters.SetInputFile(options[OPTION_INPUT].as<std::string>());
+  }
 
   if (options.count(OPTION_COLOR))
   {
@@ -926,6 +1013,20 @@
                                             const std::string& path,
                                             const OrthancWSI::DicomizerParameters& parameters)
 {
+  if (parameters.IsCytomineSource())
+  {
+    // New in release 1.1
+    LOG(WARNING) << "Importing Image Instance " << parameters.GetCytomineImageInstanceId()
+                 << " from Cytomine server: " << parameters.GetCytomineServer().GetUrl();
+    sourceCompression = OrthancWSI::ImageCompression_Unknown;
+    return new OrthancWSI::CytomineImage(parameters.GetCytomineServer(),
+                                         parameters.GetCytominePublicKey(),
+                                         parameters.GetCytominePrivateKey(),
+                                         parameters.GetCytomineImageInstanceId(),
+                                         parameters.GetTargetTileWidth(512),
+                                         parameters.GetTargetTileHeight(512));
+  }
+  
   LOG(WARNING) << "The input image is: " << path;
 
   OrthancWSI::ImageCompression format = OrthancWSI::DetectFormatFromFile(path);
--- a/Framework/DicomizerParameters.cpp	Mon Dec 06 16:15:10 2021 +0100
+++ b/Framework/DicomizerParameters.cpp	Mon Dec 06 21:18:18 2021 +0100
@@ -70,7 +70,8 @@
     smooth_(false),
     jpegQuality_(90),
     forceReencode_(false),
-    opticalPath_(OpticalPath_Brightfield)
+    opticalPath_(OpticalPath_Brightfield),
+    isCytomineSource_(false)
   {
     backgroundColor_[0] = 255;
     backgroundColor_[1] = 255;
@@ -276,4 +277,84 @@
       return new FolderTarget(folder_ + "/" + folderPattern_);
     }
   }
+
+
+  void DicomizerParameters::SetCytomineSource(const std::string& url,
+                                              const std::string& publicKey,
+                                              const std::string& privateKey,
+                                              int imageInstanceId,
+                                              ImageCompression cytomineCompression)
+  {
+    isCytomineSource_ = true;
+    cytomineServer_.SetUrl(url);
+    cytominePublicKey_ = publicKey;
+    cytominePrivateKey_ = privateKey;
+    cytomineImageInstanceId_ = imageInstanceId;
+    cytomineCompression_ = cytomineCompression;
+  }
+
+
+  const Orthanc::WebServiceParameters& DicomizerParameters::GetCytomineServer() const
+  {
+    if (isCytomineSource_)
+    {
+      return cytomineServer_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  const std::string& DicomizerParameters::GetCytominePublicKey() const
+  {
+    if (isCytomineSource_)
+    {
+      return cytominePublicKey_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  const std::string& DicomizerParameters::GetCytominePrivateKey() const
+  {
+    if (isCytomineSource_)
+    {
+      return cytominePrivateKey_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  int DicomizerParameters::GetCytomineImageInstanceId() const
+  {
+    if (isCytomineSource_)
+    {
+      return cytomineImageInstanceId_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  ImageCompression DicomizerParameters::GetCytomineCompression() const
+  {
+    if (isCytomineSource_)
+    {
+      return cytomineCompression_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
 }
--- a/Framework/DicomizerParameters.h	Mon Dec 06 16:15:10 2021 +0100
+++ b/Framework/DicomizerParameters.h	Mon Dec 06 21:18:18 2021 +0100
@@ -59,6 +59,14 @@
 
     Orthanc::WebServiceParameters  orthanc_;
 
+    // New in release 1.1
+    bool                           isCytomineSource_;
+    Orthanc::WebServiceParameters  cytomineServer_;
+    int                            cytomineImageInstanceId_;
+    std::string                    cytominePublicKey_;
+    std::string                    cytominePrivateKey_;
+    ImageCompression               cytomineCompression_;
+
   public:
     DicomizerParameters();
 
@@ -249,5 +257,26 @@
     {
       return iccProfile_;
     }
+
+    void SetCytomineSource(const std::string& url,
+                           const std::string& publicKey,
+                           const std::string& privateKey,
+                           int imageInstanceId,
+                           ImageCompression compression);
+
+    bool IsCytomineSource() const
+    {
+      return isCytomineSource_;
+    }
+
+    const Orthanc::WebServiceParameters& GetCytomineServer() const;
+
+    const std::string& GetCytominePublicKey() const;
+
+    const std::string& GetCytominePrivateKey() const;
+
+    int GetCytomineImageInstanceId() const;
+
+    ImageCompression GetCytomineCompression() const;
   };
 }
--- a/Framework/Inputs/CytomineImage.cpp	Mon Dec 06 16:15:10 2021 +0100
+++ b/Framework/Inputs/CytomineImage.cpp	Mon Dec 06 21:18:18 2021 +0100
@@ -22,7 +22,9 @@
 
 #include "CytomineImage.h"
 
+#include <Compatibility.h>
 #include <Images/ImageProcessing.h>
+#include <Images/JpegReader.h>
 #include <Images/JpegWriter.h>
 #include <Images/PngReader.h>
 #include <Logging.h>
@@ -166,24 +168,61 @@
 
     unsigned int w = std::min(tileWidth_, fullWidth_ - x);
     unsigned int h = std::min(tileHeight_, fullHeight_ - y);
+
+    std::string extension;
+    Orthanc::MimeType mime;
+
+    switch (compression_)
+    {
+      case ImageCompression_Png:
+        extension = ".png";
+        mime = Orthanc::MimeType_Png;
+        break;
+        
+      case ImageCompression_Jpeg:
+        // 20.03user 0.58system 0:32.58elapsed 63%CPU (0avgtext+0avgdata 397808maxresident)k
+        extension = ".jpg";
+        mime = Orthanc::MimeType_Jpeg;
+        break;
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
     
     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");
+                             boost::lexical_cast<std::string>(h) + extension);
 
-    std::string png;
-    if (!GetCytomine(png, uri, Orthanc::MimeType_Png))
+    std::string compressedImage;
+    if (!GetCytomine(compressedImage, uri, mime))
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Cannot read a tile from Cytomine");
     }
 
-    Orthanc::PngReader reader;
-    reader.ReadFromMemory(png);
+    std::unique_ptr<Orthanc::ImageAccessor> reader;
 
-    if (reader.GetWidth() != w ||
-        reader.GetHeight() != h)
+    switch (compression_)
+    {
+      case ImageCompression_Png:
+        reader.reset(new Orthanc::PngReader);
+        dynamic_cast<Orthanc::PngReader&>(*reader).ReadFromMemory(compressedImage);
+        break;
+        
+      case ImageCompression_Jpeg:
+        reader.reset(new Orthanc::JpegReader);
+        dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(compressedImage);
+        break;
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    assert(reader.get() != NULL);
+
+    if (reader->GetWidth() != w ||
+        reader->GetHeight() != h)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Cytomine returned a tile of bad size");
     }
@@ -193,7 +232,7 @@
     Orthanc::ImageAccessor region;
     target.GetRegion(region, 0, 0, w, h);
 
-    Orthanc::ImageProcessing::Copy(target, reader);
+    Orthanc::ImageProcessing::Copy(target, *reader);
   }
   
 
@@ -208,7 +247,8 @@
     privateKey_(privateKey),
     imageId_(imageId),
     tileWidth_(tileWidth),
-    tileHeight_(tileHeight)
+    tileHeight_(tileHeight),
+    compression_(ImageCompression_Jpeg)
   {
     if (tileWidth_ < 16 ||
         tileHeight_ < 16)
@@ -268,4 +308,18 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
   }
+
+  
+  void CytomineImage::SetImageCompression(ImageCompression compression)
+  {
+    if (compression == ImageCompression_Jpeg ||
+        compression == ImageCompression_Png)
+    {
+      compression_ = compression;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/Framework/Inputs/CytomineImage.h	Mon Dec 06 16:15:10 2021 +0100
+++ b/Framework/Inputs/CytomineImage.h	Mon Dec 06 21:18:18 2021 +0100
@@ -39,6 +39,7 @@
     unsigned int  fullHeight_;
     unsigned int  tileWidth_;
     unsigned int  tileHeight_;
+    ImageCompression  compression_;
 
     bool GetCytomine(std::string& target,
                      const std::string& uri,
@@ -86,5 +87,7 @@
     {
       return Orthanc::PhotometricInterpretation_RGB;
     }
+
+    void SetImageCompression(ImageCompression compression);
   };
 }
--- a/NEWS	Mon Dec 06 16:15:10 2021 +0100
+++ b/NEWS	Mon Dec 06 21:18:18 2021 +0100
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+* OrthancWSIDicomizer: Support importing of images from Cytomine
+
 
 Version 1.0 (2021-01-14)
 ========================