diff OrthancCppClient/Series.cpp @ 479:0cd977e94479

initial commit of the c++ client
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 16 Jul 2013 09:08:09 +0200
parents
children f3d4193c571a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancCppClient/Series.cpp	Tue Jul 16 09:08:09 2013 +0200
@@ -0,0 +1,431 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Series.h"
+
+#include "OrthancConnection.h"
+#include "../Core/OrthancException.h"
+
+#include <set>
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancClient
+{
+  namespace
+  {
+    class SliceLocator
+    {
+    private:
+      float normal_[3];
+
+    public:
+      SliceLocator(Instance& someSlice)
+      {
+        /**
+         * Compute the slice normal from Image Orientation Patient.
+         * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice
+         * http://www.itk.org/pipermail/insight-users/2003-September/004762.html
+         **/
+
+        std::vector<float> cosines;
+        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");
+
+        if (cosines.size() != 6)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
+        normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
+        normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
+      }
+
+
+      /**
+       * Compute the distance of some slice along the slice normal.
+       **/
+      float ComputeSliceLocation(Instance& instance) const
+      {
+        std::vector<float> ipp;
+        instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");
+        if (ipp.size() != 3)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        float dist = 0;
+
+        for (int i = 0; i < 3; i++)
+        {
+          dist += normal_[i] * ipp[i];
+        }
+
+        return dist;
+      }
+    };
+
+    class ImageDownloadCommand : public Orthanc::ICommand
+    {
+    private:
+      Orthanc::PixelFormat format_;
+      Orthanc::ImageExtractionMode mode_;
+      Instance& instance_;
+      void* target_;
+      size_t lineStride_;
+
+    public:
+      ImageDownloadCommand(Instance& instance, 
+                           Orthanc::PixelFormat format,
+                           Orthanc::ImageExtractionMode mode,
+                           void* target,
+                           size_t lineStride) :
+        format_(format),
+        mode_(mode),
+        instance_(instance),
+        target_(target),
+        lineStride_(lineStride)
+      {
+        instance_.SetImageExtractionMode(mode);
+      }
+
+      virtual bool Execute()
+      {
+        using namespace Orthanc;
+
+        unsigned int width = instance_.GetHeight();
+
+        for (unsigned int y = 0; y < instance_.GetHeight(); y++)
+        {
+          uint8_t* p = reinterpret_cast<uint8_t*>(target_) + y * lineStride_;
+
+          if (instance_.GetPixelFormat() == format_)
+          {
+            memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth());
+          }
+          else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
+                   format_ == PixelFormat_RGB24)
+          {
+            const uint8_t* s = reinterpret_cast<const uint8_t*>(instance_.GetBuffer(y));
+            for (unsigned int x = 0; x < width; x++, s++, p += 3)
+            {
+              p[0] = *s;
+              p[1] = *s;
+              p[2] = *s;
+            }
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_NotImplemented);
+          }
+        }
+
+        // Do not keep the image in memory, as we are loading 3D images
+        instance_.DiscardImage();
+
+        return true;
+      }
+    };
+  }
+
+
+  void Series::Check3DImage()
+  {
+    if (!Is3DImage())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  bool Series::Is3DImageInternal()
+  {
+    try
+    {
+      if (GetInstanceCount() == 0)
+      {
+        return true;
+      }
+
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        if (GetInstance(0).GetTagAsString("Columns") != GetInstance(i).GetTagAsString("Columns") ||
+            GetInstance(0).GetTagAsString("Rows") != GetInstance(i).GetTagAsString("Rows") ||
+            GetInstance(0).GetTagAsString("ImageOrientationPatient") != GetInstance(i).GetTagAsString("ImageOrientationPatient") ||
+            GetInstance(0).GetTagAsString("SliceThickness") != GetInstance(i).GetTagAsString("SliceThickness") ||
+            GetInstance(0).GetTagAsString("PixelSpacing") != GetInstance(i).GetTagAsString("PixelSpacing"))
+        {
+          return false;
+        }              
+      }
+
+      SliceLocator locator(GetInstance(0));
+      std::set<float> l;
+      for (unsigned int i = 0; i < GetInstanceCount(); i++)
+      {
+        l.insert(locator.ComputeSliceLocation(GetInstance(i)));
+      }
+
+      return l.size() == GetInstanceCount();
+    }
+    catch (Orthanc::OrthancException)
+    {
+      return false;
+    }
+  }
+
+  void Series::ReadSeries()
+  {
+    Orthanc::HttpClient client(connection_.GetHttpClient());
+
+    client.SetUrl(connection_.GetOrthancUrl() + "/series/" + id_);
+    Json::Value v;
+    if (!client.Apply(series_))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+  }
+
+  Orthanc::IDynamicObject* Series::GetFillerItem(size_t index)
+  {
+    return new Instance(connection_, series_["Instances"][index].asString());
+  }
+
+  Series::Series(const OrthancConnection& connection,
+                 const std::string& id) :
+    connection_(connection),
+    id_(id),
+    instances_(*this)
+  {
+    ReadSeries();
+    status_ = Status3DImage_NotTested;
+
+    instances_.SetThreadCount(connection.GetThreadCount());
+  }
+
+
+  bool Series::Is3DImage()
+  {
+    if (status_ == Status3DImage_NotTested)
+    {
+      status_ = Is3DImageInternal() ? Status3DImage_True : Status3DImage_False;
+    }
+
+    return status_ == Status3DImage_True;
+  }
+
+  unsigned int Series::GetInstanceCount()
+  {
+    return instances_.GetSize();
+  }
+
+  Instance& Series::GetInstance(unsigned int index)
+  {
+    return dynamic_cast<Instance&>(instances_.GetItem(index));
+  }
+
+  std::string Series::GetUrl() const
+  {
+    return connection_.GetOrthancUrl() + "/series/" + id_;
+  }
+
+  unsigned int Series::GetWidth()
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+      return 0;
+    else
+      return GetInstance(0).GetTagAsInt("Columns");
+  }
+
+  unsigned int Series::GetHeight()
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+      return 0;
+    else
+      return GetInstance(0).GetTagAsInt("Rows");
+  }
+
+  void Series::GetVoxelSize(float& sizeX, float& sizeY, float& sizeZ)
+  {
+    Check3DImage();
+
+    if (GetInstanceCount() == 0)
+    {
+      sizeX = 0;
+      sizeY = 0;
+      sizeZ = 0;
+    }
+    else
+    {
+      try
+      {
+        std::string s = GetInstance(0).GetTagAsString("PixelSpacing");
+        size_t pos = s.find('\\');
+        assert(pos != std::string::npos);
+        std::string sy = s.substr(0, pos);
+        std::string sx = s.substr(pos + 1);
+
+        sizeX = boost::lexical_cast<float>(sx);
+        sizeY = boost::lexical_cast<float>(sy);
+        sizeZ = GetInstance(0).GetTagAsFloat("SliceThickness");
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  std::string Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
+  {
+    if (series_["MainDicomTags"].isMember(tag))
+    {
+      return series_["MainDicomTags"][tag].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  
+  void Series::Load3DImage(void* target,
+                           Orthanc::PixelFormat format,
+                           size_t lineStride,
+                           size_t stackStride,
+                           Orthanc::ThreadedCommandProcessor::IListener* listener)
+  {
+    using namespace Orthanc;
+
+    // Choose the extraction mode, depending on the format of the
+    // target image.
+
+    uint8_t bytesPerPixel;
+    ImageExtractionMode mode;
+
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        bytesPerPixel = 3;
+        mode = ImageExtractionMode_Preview;
+        break;
+
+      case PixelFormat_Grayscale8:
+        bytesPerPixel = 1;
+        mode = ImageExtractionMode_UInt8;  // Preview ???
+        break; 
+
+      case PixelFormat_Grayscale16:
+        bytesPerPixel = 2;
+        mode = ImageExtractionMode_UInt16;
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        bytesPerPixel = 2;
+        mode = ImageExtractionMode_UInt16;
+        format = PixelFormat_Grayscale16;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+
+    // Check that the target image is properly sized
+    unsigned int sx = GetWidth();
+    unsigned int sy = GetHeight();
+
+    if (lineStride < sx * bytesPerPixel ||
+        stackStride < sx * sy * bytesPerPixel)
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    if (sx == 0 || sy == 0 || GetInstanceCount() == 0)
+    {
+      // Empty image, nothing to do
+      if (listener)
+        listener->SignalSuccess(0);
+      return;
+    }
+
+
+    /**
+     * Order the stacks according to their distance along the slice
+     * normal (using the "Image Position Patient" tag). This works
+     * even if the "SliceLocation" tag is absent.
+     **/
+    SliceLocator locator(GetInstance(0));
+
+    typedef std::map<float, Instance*> Instances;
+    Instances instances;
+    for (unsigned int i = 0; i < GetInstanceCount(); i++)
+    {
+      float dist = locator.ComputeSliceLocation(GetInstance(i));
+      instances[dist] = &GetInstance(i);
+    }
+
+    if (instances.size() != GetInstanceCount())
+    {
+      // Several instances have the same Z coordinate
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+
+    // Submit the download of each stack as a set of commands
+    ThreadedCommandProcessor processor(connection_.GetThreadCount());
+
+    if (listener != NULL)
+    {
+      processor.SetListener(*listener);
+    }
+
+    uint8_t* stackTarget = reinterpret_cast<uint8_t*>(target);
+    for (Instances::iterator it = instances.begin(); it != instances.end(); it++)
+    {
+      processor.Post(new ImageDownloadCommand(*it->second, format, mode, stackTarget, lineStride));
+      stackTarget += stackStride;
+    }
+
+
+    // Wait for all the stacks to be downloaded
+    if (!processor.Join())
+    {
+      throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+}