diff Core/DicomParsing/Internals/DicomFrameIndex.cpp @ 2382:7284093111b0

big reorganization to cleanly separate framework vs. server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Aug 2017 21:17:35 +0200
parents OrthancServer/Internals/DicomFrameIndex.cpp@b8969010b534
children 878b59270859
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/Internals/DicomFrameIndex.cpp	Tue Aug 29 21:17:35 2017 +0200
@@ -0,0 +1,439 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 "../../PrecompiledHeaders.h"
+#include "DicomFrameIndex.h"
+
+#include "../../OrthancException.h"
+#include "../../DicomFormat/DicomImageInformation.h"
+#include "../FromDcmtkBridge.h"
+#include "../../Endianness.h"
+#include "DicomImageDecoder.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+#include <dcmtk/dcmdata/dcpixseq.h>
+
+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_);
+      }
+    }
+  };
+
+
+
+  bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom)
+  {
+    // Retrieve the transfer syntax from the DICOM header
+    const char* value = NULL;
+    if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() ||
+        value == NULL)
+    {
+      return false;
+    }
+
+    const std::string transferSyntax(value);
+
+    // Video standards supported in DICOM 2016a
+    // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html
+    if (transferSyntax == "1.2.840.10008.1.2.4.100" ||  // MPEG2 MP@ML option of ISO/IEC MPEG2
+        transferSyntax == "1.2.840.10008.1.2.4.101" ||  // MPEG2 MP@HL option of ISO/IEC MPEG2
+        transferSyntax == "1.2.840.10008.1.2.4.102" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.103" ||  // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.104" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.105" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
+        transferSyntax == "1.2.840.10008.1.2.4.106")    // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264
+    {
+      return true;
+    }
+
+    return false;
+  }
+
+
+  unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom)
+  {
+    // Assume 1 frame for video transfer syntaxes
+    if (IsVideo(dicom))
+    {
+      return 1;
+    }        
+
+    const char* tmp = NULL;
+    if (!dicom.getDataset()->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(DcmFileFormat& dicom)
+  {
+    countFrames_ = GetFramesCount(dicom);
+    if (countFrames_ == 0)
+    {
+      // The image has no frame. No index is to be built.
+      return;
+    }
+
+    DcmDataset& dataset = *dicom.getDataset();
+
+    // 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::ExtractDicomSummary(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();
+    }
+  }
+}