changeset 1924:6c73df12ca51

New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 07 Mar 2016 17:43:20 +0100
parents 6ac7f31fc543
children 56276bad7e42
files CMakeLists.txt Core/DicomFormat/DicomImageInformation.cpp Core/DicomFormat/DicomImageInformation.h Core/DicomFormat/DicomIntegerPixelAccessor.cpp NEWS OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/Internals/DicomFrameIndex.cpp OrthancServer/Internals/DicomFrameIndex.h OrthancServer/Internals/DicomImageDecoder.cpp OrthancServer/Internals/DicomImageDecoder.h OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h
diffstat 14 files changed, 629 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Mar 07 08:29:22 2016 +0100
+++ b/CMakeLists.txt	Mon Mar 07 17:43:20 2016 +0100
@@ -163,6 +163,7 @@
   OrthancServer/ExportedResource.cpp
   OrthancServer/FromDcmtkBridge.cpp
   OrthancServer/Internals/CommandDispatcher.cpp
+  OrthancServer/Internals/DicomFrameIndex.cpp
   OrthancServer/Internals/DicomImageDecoder.cpp
   OrthancServer/Internals/FindScp.cpp
   OrthancServer/Internals/MoveScp.cpp
--- a/Core/DicomFormat/DicomImageInformation.cpp	Mon Mar 07 08:29:22 2016 +0100
+++ b/Core/DicomFormat/DicomImageInformation.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -260,4 +260,13 @@
 
     return false;
   }
+
+
+  size_t DicomImageInformation::GetFrameSize() const
+  {
+    return (GetHeight() * 
+            GetWidth() * 
+            GetBytesPerValue() * 
+            GetChannelCount());
+  }
 }
--- a/Core/DicomFormat/DicomImageInformation.h	Mon Mar 07 08:29:22 2016 +0100
+++ b/Core/DicomFormat/DicomImageInformation.h	Mon Mar 07 17:43:20 2016 +0100
@@ -121,5 +121,7 @@
 
     bool ExtractPixelFormat(PixelFormat& format,
                             bool ignorePhotometricInterpretation) const;
+
+    size_t GetFrameSize() const;
   };
 }
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Mon Mar 07 08:29:22 2016 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -54,8 +54,7 @@
     size_(size)
   {
     frame_ = 0;
-    frameOffset_ = (information_.GetHeight() * information_.GetWidth() * 
-                    information_.GetBytesPerValue() * information_.GetChannelCount());
+    frameOffset_ = information_.GetFrameSize();
 
     if (information_.GetNumberOfFrames() * frameOffset_ > size)
     {
--- a/NEWS	Mon Mar 07 08:29:22 2016 +0100
+++ b/NEWS	Mon Mar 07 17:43:20 2016 +0100
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+
+* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
 * Support of optional tags for counting resources in C-Find:
   0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209
 * Support of Move Originator Message ID (0000,1031) in C-Store responses driven by C-Move
--- a/OrthancServer/FromDcmtkBridge.cpp	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -1566,4 +1566,26 @@
 
     throw OrthancException(ErrorCode_ParameterOutOfRange);
   }
+
+
+  DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset)
+  {
+    DcmElement *element = NULL;
+    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), element).good())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
+    DcmPixelSequence* pixelSequence = NULL;
+    if (!pixelData.getEncapsulatedRepresentation
+        (dataset.getOriginalXfer(), NULL, pixelSequence).good())
+    {
+      return NULL;
+    }
+    else
+    {
+      return pixelSequence;
+    }
+  }
 }
--- a/OrthancServer/FromDcmtkBridge.h	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Mon Mar 07 17:43:20 2016 +0100
@@ -38,6 +38,7 @@
 
 #include <dcmtk/dcmdata/dcdatset.h>
 #include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
 #include <json/json.h>
 
 namespace Orthanc
@@ -139,5 +140,7 @@
                                 Encoding dicomEncoding);
 
     static DcmEVR ParseValueRepresentation(const std::string& s);
+
+    static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Internals/DicomFrameIndex.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -0,0 +1,411 @@
+/**
+ * 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 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 "../PrecompiledHeadersServer.h"
+#include "DicomFrameIndex.h"
+
+#include "../../Core/OrthancException.h"
+#include "../../Core/DicomFormat/DicomImageInformation.h"
+#include "../FromDcmtkBridge.h"
+#include "DicomImageDecoder.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+
+#if defined(_WIN32)   // Windows machines are always little-endian
+#  define le32toh(x) x
+#elif defined(__APPLE__)
+#  include <libkern/OSByteOrder.h>
+#  define le32toh(x) OSSwapLittleToHostInt32(x)
+#else
+#  include <endian.h>
+#endif
+
+
+namespace Orthanc
+{
+  class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex
+  {
+  private:
+    DcmPixelSequence*           pixelSequence_;
+    std::vector<DcmPixelItem*>  startFragment_;
+    std::vector<unsigned int>   countFragments_;
+    std::vector<unsigned int>   frameSize_;
+
+    void GetOffsetTable(std::vector<uint32_t>& table)
+    {
+      DcmPixelItem* item = NULL;
+      if (!pixelSequence_->getItem(item, 0).good() ||
+          item == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      uint32_t length = item->getLength();
+      if (length == 0)
+      {
+        table.clear();
+        return;
+      }
+
+      if (length % 4 != 0)
+      {
+        // Error: Each fragment is index with 4 bytes (uint32_t)
+        throw OrthancException(ErrorCode_BadFileFormat);        
+      }
+
+      uint8_t* content = NULL;
+      if (!item->getUint8Array(content).good() ||
+          content == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      table.resize(length / 4);
+
+      // The offset table is always in little endian in the DICOM
+      // file. Swap it to host endianness if needed.
+      const uint32_t* offset = reinterpret_cast<const uint32_t*>(content);
+      for (size_t i = 0; i < table.size(); i++, offset++)
+      {
+        table[i] = le32toh(*offset);
+      }
+    }
+
+
+  public:
+    FragmentIndex(DcmPixelSequence* pixelSequence,
+                  unsigned int countFrames) :
+      pixelSequence_(pixelSequence)
+    {
+      assert(pixelSequence != NULL);
+
+      startFragment_.resize(countFrames);
+      countFragments_.resize(countFrames);
+      frameSize_.resize(countFrames);
+
+      // The first fragment corresponds to the offset table
+      unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card());
+      if (countFragments < countFrames + 1)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      if (countFragments == countFrames + 1)
+      {
+        // Simple case: There is one fragment per frame.
+
+        DcmObject* fragment = pixelSequence_->nextInContainer(NULL);  // Skip the offset table
+        if (fragment == NULL)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        for (unsigned int i = 0; i < countFrames; i++)
+        {
+          fragment = pixelSequence_->nextInContainer(fragment);
+          startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment);
+          frameSize_[i] = fragment->getLength();
+          countFragments_[i] = 1;
+        }
+
+        return;
+      }
+
+      // Parse the offset table
+      std::vector<uint32_t> offsetOfFrame;
+      GetOffsetTable(offsetOfFrame);
+      
+      if (offsetOfFrame.size() != countFrames ||
+          offsetOfFrame[0] != 0)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+
+      // Loop over the fragments (ignoring the offset table). This is
+      // an alternative, faster implementation to DCMTK's
+      // "DcmCodec::determineStartFragment()".
+      DcmObject* fragment = pixelSequence_->nextInContainer(NULL);
+      if (fragment == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table
+      if (fragment == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      uint32_t offset = 0;
+      unsigned int currentFrame = 0;
+      startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment);
+
+      unsigned int currentFragment = 1;
+      while (fragment != NULL)
+      {
+        if (currentFrame + 1 < countFrames &&
+            offset == offsetOfFrame[currentFrame + 1])
+        {
+          currentFrame += 1;
+          startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment);
+        }
+
+        frameSize_[currentFrame] += fragment->getLength();
+        countFragments_[currentFrame]++;
+
+        // 8 bytes = overhead for the item tag and length field
+        offset += fragment->getLength() + 8;
+
+        currentFragment++;
+        fragment = pixelSequence_->nextInContainer(fragment);
+      }
+
+      if (currentFragment != countFragments ||
+          currentFrame + 1 != countFrames ||
+          fragment != NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      assert(startFragment_.size() == countFragments_.size() &&
+             startFragment_.size() == frameSize_.size());
+    }
+
+
+    virtual void GetRawFrame(std::string& frame,
+                             unsigned int index) const
+    {
+      if (index >= startFragment_.size())
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      frame.resize(frameSize_[index]);
+      if (frame.size() == 0)
+      {
+        return;
+      }
+
+      uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]);
+
+      size_t offset = 0;
+      DcmPixelItem* fragment = startFragment_[index];
+      for (unsigned int i = 0; i < countFragments_[index]; i++)
+      {
+        uint8_t* content = NULL;
+        if (!fragment->getUint8Array(content).good() ||
+            content == NULL)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        assert(offset + fragment->getLength() <= frame.size());
+
+        memcpy(target + offset, content, fragment->getLength());
+        offset += fragment->getLength();
+
+        fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment));
+      }
+    }
+  };
+
+
+
+  class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex
+  {
+  private:
+    uint8_t*  pixelData_;
+    size_t    frameSize_;
+
+  public: 
+    UncompressedIndex(DcmDataset& dataset,
+                      unsigned int countFrames,
+                      size_t frameSize) :
+      pixelData_(NULL),
+      frameSize_(frameSize)
+    {
+      size_t size = 0;
+
+      DcmElement* e;
+      if (dataset.findAndGetElement(DCM_PixelData, e).good() &&
+          e != NULL)
+      {
+        size = e->getLength();
+
+        if (size > 0)
+        {
+          pixelData_ = NULL;
+          if (!e->getUint8Array(pixelData_).good() ||
+              pixelData_ == NULL)
+          {
+            throw OrthancException(ErrorCode_BadFileFormat);
+          }
+        }
+      }
+
+      if (size < frameSize_ * countFrames)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    virtual void GetRawFrame(std::string& frame,
+                             unsigned int index) const
+    {
+      frame.resize(frameSize_);
+      if (frameSize_ > 0)
+      {
+        memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_);
+      }
+    }
+  };
+
+
+  class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex
+  {
+  private:
+    std::string  pixelData_;
+    size_t       frameSize_;
+
+  public: 
+    PsmctRle1Index(DcmDataset& dataset,
+                   unsigned int countFrames,
+                   size_t frameSize) :
+      frameSize_(frameSize)
+    {
+      if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) ||
+          pixelData_.size() < frameSize * countFrames)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    virtual void GetRawFrame(std::string& frame,
+                             unsigned int index) const
+    {
+      frame.resize(frameSize_);
+      if (frameSize_ > 0)
+      {
+        memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_);
+      }
+    }
+  };
+
+
+
+
+  unsigned int DicomFrameIndex::GetFramesCount(DcmDataset& dataset)
+  {
+    const char* tmp = NULL;
+    if (!dataset.findAndGetString(DCM_NumberOfFrames, tmp).good() ||
+        tmp == NULL)
+    {
+      return 1;
+    }
+
+    int count = -1;
+    try
+    {
+      count = boost::lexical_cast<int>(tmp);
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+    }
+
+    if (count < 0)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);        
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  DicomFrameIndex::DicomFrameIndex(DcmDataset& dataset)
+  {
+    countFrames_ = GetFramesCount(dataset);
+    if (countFrames_ == 0)
+    {
+      // The image has no frame. No index is to be built.
+      return;
+    }
+
+    // Test whether this image is composed of a sequence of fragments
+    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
+    if (pixelSequence != NULL)
+    {
+      index_.reset(new FragmentIndex(pixelSequence, countFrames_));
+      return;
+    }
+
+    // Extract information about the image structure
+    DicomMap tags;
+    FromDcmtkBridge::Convert(tags, dataset);
+
+    DicomImageInformation information(tags);
+
+    // Access to the raw pixel data
+    if (DicomImageDecoder::IsPsmctRle1(dataset))
+    {
+      index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize()));
+    }
+    else
+    {
+      index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize()));
+    }
+  }
+
+
+  void DicomFrameIndex::GetRawFrame(std::string& frame,
+                                    unsigned int index) const
+  {
+    if (index >= countFrames_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else if (index_.get() != NULL)
+    {
+      return index_->GetRawFrame(frame, index);
+    }
+    else
+    {
+      frame.clear();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Internals/DicomFrameIndex.h	Mon Mar 07 17:43:20 2016 +0100
@@ -0,0 +1,77 @@
+/**
+ * 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 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/>.
+ **/
+
+
+#pragma once
+
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <vector>
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+#include <memory>
+
+namespace Orthanc
+{
+  class DicomFrameIndex
+  {
+  private:
+    class IIndex : public boost::noncopyable
+    {
+    public:
+      virtual ~IIndex()
+      {
+      }
+
+      virtual void GetRawFrame(std::string& frame,
+                               unsigned int index) const = 0;
+    };
+
+    class FragmentIndex;
+    class UncompressedIndex;
+    class PsmctRle1Index;
+
+    std::auto_ptr<IIndex>  index_;
+    unsigned int           countFrames_;
+
+  public:
+    DicomFrameIndex(DcmDataset& dataset);
+
+    unsigned int GetFramesCount() const
+    {
+      return countFrames_;
+    }
+
+    void GetRawFrame(std::string& frame,
+                     unsigned int index) const;
+
+    static unsigned int GetFramesCount(DcmDataset& dataset);
+  };
+}
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -121,7 +121,7 @@
   static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011);
 
 
-  static bool IsPsmctRle1(DcmDataset& dataset)
+  bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset)
   {
     DcmElement* e;
     char* c;
@@ -144,8 +144,8 @@
   }
 
 
-  static bool DecodePsmctRle1(std::string& output,
-                              DcmDataset& dataset)
+  bool DicomImageDecoder::DecodePsmctRle1(std::string& output,
+                                          DcmDataset& dataset)
   {
     // Check whether the DICOM instance contains an image encoded with
     // the PMSCT_RLE1 scheme.
@@ -459,38 +459,24 @@
   }
 
 
-  static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset)
-  {
-    DcmElement *element = NULL;
-    if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), element).good())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
-    DcmPixelSequence* pixelSequence = NULL;
-    if (!pixelData.getEncapsulatedRepresentation
-        (dataset.getOriginalXfer(), NULL, pixelSequence).good() ||
-        pixelSequence == NULL)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-
-    return pixelSequence;
-  }
-
-
   ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec,
                                                const DcmCodecParameter& parameters,
                                                DcmDataset& dataset,
                                                unsigned int frame)
   {
+    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
+    if (pixelSequence == NULL)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
     std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true));
 
     Uint32 startFragment = 0;  // Default 
     OFString decompressedColorModel;  // Out
     DJ_RPLossless representationParameter;
-    OFCondition c = codec.decodeFrame(&representationParameter, GetPixelSequence(dataset), &parameters, 
+    OFCondition c = codec.decodeFrame(&representationParameter, 
+                                      pixelSequence, &parameters, 
                                       &dataset, frame, startFragment, target->GetBuffer(), 
                                       target->GetSize(), decompressedColorModel);
 
--- a/OrthancServer/Internals/DicomImageDecoder.h	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Mon Mar 07 17:43:20 2016 +0100
@@ -57,8 +57,6 @@
     static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset,
                                                   unsigned int frame);
 
-    static bool IsPsmctRle1(DcmDataset& dataset);
-
     static ImageAccessor* ApplyCodec(const DcmCodec& codec,
                                      const DcmCodecParameter& parameters,
                                      DcmDataset& dataset,
@@ -74,6 +72,11 @@
                                     ImageExtractionMode mode);
 
   public:
+    static bool IsPsmctRle1(DcmDataset& dataset);
+
+    static bool DecodePsmctRle1(std::string& output,
+                                DcmDataset& dataset);
+
     static ImageAccessor *Decode(ParsedDicomFile& dicom,
                                  unsigned int frame);
 
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -466,6 +466,35 @@
 
 
 
+  static void GetRawFrame(RestApiGetCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    std::string frameId = call.GetUriComponent("frame", "0");
+
+    unsigned int frame;
+    try
+    {
+      frame = boost::lexical_cast<unsigned int>(frameId);
+    }
+    catch (boost::bad_lexical_cast)
+    {
+      return;
+    }
+
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string raw, mime;
+
+    {
+      ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId);
+      locker.GetDicom().GetRawFrame(raw, mime, frame);
+    }
+
+    call.GetOutput().AnswerBuffer(raw, mime);
+  }
+
+
+
   static void GetResourceStatistics(RestApiGetCall& call)
   {
     std::string publicId = call.GetUriComponent("id", "");
@@ -1328,6 +1357,7 @@
     Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
     Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>);
     Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage);
+    Register("/instances/{id}/frames/{frame}/raw", GetRawFrame);
     Register("/instances/{id}/pdf", ExtractPdf);
     Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
     Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
--- a/OrthancServer/ParsedDicomFile.cpp	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Mon Mar 07 17:43:20 2016 +0100
@@ -84,6 +84,7 @@
 #include "ServerToolbox.h"
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
+#include "Internals/DicomFrameIndex.h"
 #include "../Core/Images/JpegReader.h"
 #include "../Core/Images/PngReader.h"
 #include "../Core/Logging.h"
@@ -145,6 +146,7 @@
   struct ParsedDicomFile::PImpl
   {
     std::auto_ptr<DcmFileFormat> file_;
+    std::auto_ptr<DicomFrameIndex>  frameIndex_;
   };
 
 
@@ -516,6 +518,8 @@
 
   void ParsedDicomFile::Remove(const DicomTag& tag)
   {
+    InvalidateCache();
+
     DcmTagKey key(tag.GetGroup(), tag.GetElement());
     DcmElement* element = pimpl_->file_->getDataset()->remove(key);
     if (element != NULL)
@@ -528,6 +532,8 @@
 
   void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep)
   {
+    InvalidateCache();
+
     DcmDataset& dataset = *pimpl_->file_->getDataset();
 
     // Loop over the dataset to detect its private tags
@@ -591,6 +597,8 @@
                                const Json::Value& value,
                                bool decodeDataUriScheme)
   {
+    InvalidateCache();
+
     std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
     InsertInternal(*pimpl_->file_->getDataset(), element.release());
   }
@@ -679,6 +687,8 @@
                                 const std::string& utf8Value,
                                 DicomReplaceMode mode)
   {
+    InvalidateCache();
+
     std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
     FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, false, GetEncoding());
     ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
@@ -691,6 +701,8 @@
                                 bool decodeDataUriScheme,
                                 DicomReplaceMode mode)
   {
+    InvalidateCache();
+
     std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
     ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
 
@@ -921,6 +933,8 @@
   void ParsedDicomFile::EmbedImage(const std::string& mime,
                                    const std::string& content)
   {
+    InvalidateCache();
+
     if (mime == "image/png")
     {
       PngReader reader;
@@ -1228,4 +1242,39 @@
 
     return result.release();
   }
+
+
+  void ParsedDicomFile::GetRawFrame(std::string& target,
+                                    std::string& mime,
+                                    unsigned int frameId)
+  {
+    if (pimpl_->frameIndex_.get() == NULL)
+    {
+      pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_->getDataset()));
+    }
+
+    pimpl_->frameIndex_->GetRawFrame(target, frameId);
+
+    E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
+    switch (transferSyntax)
+    {
+      case EXS_JPEGProcess1TransferSyntax:
+        mime = "image/jpeg";
+        break;
+       
+      case EXS_JPEG2000LosslessOnly:
+      case EXS_JPEG2000:
+        mime = "image/jp2";
+
+      default:
+        mime = "application/octet-stream";
+        break;
+    }
+  }
+
+
+  void ParsedDicomFile::InvalidateCache()
+  {
+    pimpl_->frameIndex_.reset(NULL);
+  }
 }
--- a/OrthancServer/ParsedDicomFile.h	Mon Mar 07 08:29:22 2016 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Mon Mar 07 17:43:20 2016 +0100
@@ -60,6 +60,8 @@
                           const std::string& value,
                           bool decodeDataUriScheme);
 
+    void InvalidateCache();
+
   public:
     ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
 
@@ -146,6 +148,10 @@
 
     void Convert(DicomMap& tags);
 
+    void GetRawFrame(std::string& target, // OUT
+                     std::string& mime,   // OUT
+                     unsigned int frameId);  // IN
+
     static ParsedDicomFile* CreateFromJson(const Json::Value& value,
                                            DicomFromJsonFlags flags);
   };