diff OrthancStone/Sources/Toolbox/DicomStructureSet2.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Toolbox/DicomStructureSet2.cpp@30deba7bc8e2
children 4fb8fdf03314
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Toolbox/DicomStructureSet2.cpp	Tue Jul 07 16:21:02 2020 +0200
@@ -0,0 +1,311 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., 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 <http://www.gnu.org/licenses/>.
+ **/
+
+#ifdef BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+
+#include "DicomStructureSet2.h"
+
+#include "../Toolbox/LinearAlgebra.h"
+#include "../StoneException.h"
+
+#include <Logging.h>
+#include <OrthancException.h>
+#include <Toolbox.h>
+#include <DicomFormat/DicomTag.h>
+
+#include <FullOrthancDataset.h>
+#include <DicomDatasetReader.h>
+
+namespace OrthancStone
+{
+  static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
+  static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016);
+  static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040);
+  static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
+  static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046);
+  static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
+  static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039);
+  static const OrthancPlugins::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a);
+  static const OrthancPlugins::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026);
+  static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4);
+  static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080);
+  static const OrthancPlugins::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020);
+
+  static inline uint8_t ConvertAndClipToByte(double v)
+  {
+    if (v < 0)
+    {
+      return 0;
+    }
+    else if (v >= 255)
+    {
+      return 255;
+    }
+    else
+    {
+      return static_cast<uint8_t>(v);
+    }
+  }
+
+  static bool ReadDicomToVector(Vector& target,
+    const OrthancPlugins::IDicomDataset& dataset,
+    const OrthancPlugins::DicomPath& tag)
+  {
+    std::string value;
+    return (dataset.GetStringValue(value, tag) &&
+      LinearAlgebra::ParseVector(target, value));
+  }
+
+
+  void DicomPathToString(std::string& s, const OrthancPlugins::DicomPath& dicomPath)
+  {
+    std::stringstream tmp;
+    for (size_t i = 0; i < dicomPath.GetPrefixLength(); ++i)
+    {
+      OrthancPlugins::DicomTag tag = dicomPath.GetPrefixTag(i);
+
+      // We use this other object to be able to use GetMainTagsName
+      // and Format
+      Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
+      size_t index = dicomPath.GetPrefixIndex(i);
+      tmp << tag2.GetMainTagsName() << " (" << tag2.Format() << ") [" << index << "] / ";
+    }
+    const OrthancPlugins::DicomTag& tag = dicomPath.GetFinalTag();
+    Orthanc::DicomTag tag2(tag.GetGroup(), tag.GetElement());
+    tmp << tag2.GetMainTagsName() << " (" << tag2.Format() << ")";
+    s = tmp.str();
+  }
+
+  std::ostream& operator<<(std::ostream& s, const OrthancPlugins::DicomPath& dicomPath)
+  {
+    std::string tmp;
+    DicomPathToString(tmp, dicomPath);
+    s << tmp;
+    return s;
+  }
+
+
+  DicomStructureSet2::DicomStructureSet2()
+  {
+
+  }
+
+
+  DicomStructureSet2::~DicomStructureSet2()
+  {
+
+  }
+
+  void DicomStructureSet2::SetContents(const OrthancPlugins::FullOrthancDataset& tags)
+  {
+    FillStructuresFromDataset(tags);
+    ComputeDependentProperties();
+  }
+
+  void DicomStructureSet2::ComputeDependentProperties()
+  {
+    for (size_t i = 0; i < structures_.size(); ++i)
+    {
+      structures_[i].ComputeDependentProperties();
+    }
+  }
+
+  void DicomStructureSet2::FillStructuresFromDataset(const OrthancPlugins::FullOrthancDataset& tags)
+  {
+    OrthancPlugins::DicomDatasetReader reader(tags);
+
+    // a few sanity checks
+    size_t count = 0, tmp = 0;
+
+    //  DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE (0x3006, 0x0080);
+    //  DICOM_TAG_ROI_CONTOUR_SEQUENCE         (0x3006, 0x0039);
+    //  DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE   (0x3006, 0x0020);
+    if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) ||
+      !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) ||
+      tmp != count ||
+      !tags.GetSequenceSize(tmp, DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE) ||
+      tmp != count)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    // let's now parse the structures stored in the dicom file
+    //  DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE  (0x3006, 0x0080)
+    //  DICOM_TAG_RT_ROI_INTERPRETED_TYPE       (0x3006, 0x00a4)
+    //  DICOM_TAG_ROI_DISPLAY_COLOR             (0x3006, 0x002a)
+    //  DICOM_TAG_ROI_NAME                      (0x3006, 0x0026)
+    structures_.resize(count);
+    for (size_t i = 0; i < count; i++)
+    {
+      // (0x3006, 0x0080)[i]/(0x3006, 0x00a4)
+      structures_[i].interpretation_ = reader.GetStringValue
+      (OrthancPlugins::DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
+        DICOM_TAG_RT_ROI_INTERPRETED_TYPE),
+        "No interpretation");
+
+      // (0x3006, 0x0020)[i]/(0x3006, 0x0026)
+      structures_[i].name_ = reader.GetStringValue
+      (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
+        DICOM_TAG_ROI_NAME),
+        "No name");
+
+      Vector color;
+      // (0x3006, 0x0039)[i]/(0x3006, 0x002a)
+      if (ReadDicomToVector(color, tags, OrthancPlugins::DicomPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_ROI_DISPLAY_COLOR)) 
+        && color.size() == 3)
+      {
+        structures_[i].red_   = ConvertAndClipToByte(color[0]);
+        structures_[i].green_ = ConvertAndClipToByte(color[1]);
+        structures_[i].blue_  = ConvertAndClipToByte(color[2]);
+      }
+      else
+      {
+        structures_[i].red_ = 255;
+        structures_[i].green_ = 0;
+        structures_[i].blue_ = 0;
+      }
+
+      size_t countSlices;
+      //  DICOM_TAG_ROI_CONTOUR_SEQUENCE          (0x3006, 0x0039);
+      //  DICOM_TAG_CONTOUR_SEQUENCE              (0x3006, 0x0040);
+      if (!tags.GetSequenceSize(countSlices, OrthancPlugins::DicomPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE)))
+      {
+        LOG(WARNING) << "DicomStructureSet2::SetContents | structure \"" << structures_[i].name_ << "\" has no slices!";
+        countSlices = 0;
+      }
+
+      LOG(INFO) << "New RT structure: \"" << structures_[i].name_
+        << "\" with interpretation \"" << structures_[i].interpretation_
+        << "\" containing " << countSlices << " slices (color: "
+        << static_cast<int>(structures_[i].red_) << ","
+        << static_cast<int>(structures_[i].green_) << ","
+        << static_cast<int>(structures_[i].blue_) << ")";
+
+      // These temporary variables avoid allocating many vectors in the loop below
+      
+      // (0x3006, 0x0039)[i]/(0x3006, 0x0040)[0]/(0x3006, 0x0046)
+      OrthancPlugins::DicomPath countPointsPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_NUMBER_OF_CONTOUR_POINTS);
+
+      OrthancPlugins::DicomPath geometricTypePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_GEOMETRIC_TYPE);
+
+      OrthancPlugins::DicomPath imageSequencePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_IMAGE_SEQUENCE);
+
+      // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)
+      OrthancPlugins::DicomPath referencedInstancePath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
+        DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
+
+      OrthancPlugins::DicomPath contourDataPath(
+        DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
+        DICOM_TAG_CONTOUR_SEQUENCE, 0,
+        DICOM_TAG_CONTOUR_DATA);
+
+      for (size_t j = 0; j < countSlices; j++)
+      {
+        unsigned int countPoints = 0;
+
+        countPointsPath.SetPrefixIndex(1, j);
+        if (!reader.GetUnsignedIntegerValue(countPoints, countPointsPath))
+        {
+          std::string s;
+          DicomPathToString(s, countPointsPath);
+          LOG(ERROR) << "Dicom path " << s << " is not valid (should contain an unsigned integer)";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices";
+
+        geometricTypePath.SetPrefixIndex(1, j);
+        std::string type = reader.GetMandatoryStringValue(geometricTypePath);
+        if (type != "CLOSED_PLANAR")
+        {
+          // TODO: support points!!
+          LOG(WARNING) << "Ignoring contour with geometry type: " << type;
+          continue;
+        }
+
+        size_t size = 0;
+
+        imageSequencePath.SetPrefixIndex(1, j);
+        if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1)
+        {
+          LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry.";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+
+        referencedInstancePath.SetPrefixIndex(1, j);
+        std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
+
+        contourDataPath.SetPrefixIndex(1, j);
+        std::string slicesData = reader.GetMandatoryStringValue(contourDataPath);
+
+        Vector points;
+        if (!LinearAlgebra::ParseVector(points, slicesData) ||
+          points.size() != 3 * countPoints)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+
+        // seen in real world
+        if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
+        {
+          LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)";
+        }
+
+        DicomStructurePolygon2 polygon(sopInstanceUid,type);
+        polygon.Reserve(countPoints);
+
+        for (size_t k = 0; k < countPoints; k++)
+        {
+          Vector v(3);
+          v[0] = points[3 * k];
+          v[1] = points[3 * k + 1];
+          v[2] = points[3 * k + 2];
+          polygon.AddPoint(v);
+        }
+        structures_[i].AddPolygon(polygon);
+      }
+    }
+  }
+
+
+  void DicomStructureSet2::Clear()
+  {
+    structures_.clear();
+  }
+
+}
+
+#endif 
+// BGO_ENABLE_DICOMSTRUCTURESETLOADER2
+