view Core/Dicom.cpp @ 4:a373f73e30b8

stow sample
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 14 Mar 2015 16:39:00 +0100
parents 8e7bc8bf874d
children 1b383403c080
line wrap: on
line source

/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2015 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 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/>.
 **/


#include "Dicom.h"

#include "ChunkedBuffer.h"

#include <gdcmDictEntry.h>
#include <boost/lexical_cast.hpp>
#include <json/writer.h>

namespace OrthancPlugins
{
  namespace
  {
    class ChunkedBufferWriter : public pugi::xml_writer
    {
    private:
      ChunkedBuffer buffer_;

    public:
      virtual void write(const void *data, size_t size)
      {
        if (size > 0)
        {
          buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
        }
      }

      void Flatten(std::string& s)
      {
        buffer_.Flatten(s);
      }
    };
  }



  void ParsedDicomFile::Setup(const std::string& dicom)
  {
    // Prepare a memory stream over the DICOM instance
    std::stringstream stream(dicom);

    // Parse the DICOM instance using GDCM
    reader_.SetStream(stream);
    if (!reader_.Read())
    {
      throw std::runtime_error("GDCM cannot read this DICOM instance of length " + 
                               boost::lexical_cast<std::string>(dicom.size()));
    }
  }


  ParsedDicomFile::ParsedDicomFile(const OrthancPlugins::MultipartItem& item)
  {
    std::string dicom(item.data_, item.data_ + item.size_);
    Setup(dicom);
  }


  bool ParsedDicomFile::GetTag(std::string& result,
                               const gdcm::Tag& tag,
                               bool stripSpaces) const
  {
    const gdcm::DataSet& dataset = GetDataSet();

    if (dataset.FindDataElement(tag))
    {
      const gdcm::ByteValue* value = dataset.GetDataElement(tag).GetByteValue();
      if (value)
      {
        result = std::string(value->GetPointer(), value->GetLength());

        if (stripSpaces)
        {
          result = OrthancPlugins::StripSpaces(result);
        }

        return true;
      }
    }

    return false;
  }


  std::string ParsedDicomFile::GetTagWithDefault(const gdcm::Tag& tag,
                                                 const std::string& defaultValue,
                                                 bool stripSpaces) const
  {
    std::string result;
    if (!GetTag(result, tag, false))
    {
      result = defaultValue;
    }

    if (stripSpaces)
    {
      result = OrthancPlugins::StripSpaces(result);
    }

    return result;
  }


  static std::string FormatTag(const gdcm::Tag& tag)
  {
    char tmp[16];
    sprintf(tmp, "%04X%04X", tag.GetGroup(), tag.GetElement());
    return std::string(tmp);
  }


  static const char* GetKeyword(const gdcm::Dict& dictionary,
                                const gdcm::Tag& tag)
  {
    const gdcm::DictEntry &entry = dictionary.GetDictEntry(tag);
    const char* keyword = entry.GetKeyword();

    if (strlen(keyword) != 0)
    {
      return keyword;
    }

    if (tag == DICOM_TAG_RETRIEVE_URL)
    {
      return "RetrieveURL";
    }

    throw std::runtime_error("Unknown keyword for tag: " + FormatTag(tag));
  }



  static const char* GetVRName(bool& isSequence,
                               const gdcm::Dict& dictionary,
                               const gdcm::DataElement& element)
  {
    gdcm::VR vr = element.GetVR();
    if (vr == gdcm::VR::INVALID)
    {
      const gdcm::DictEntry &entry = dictionary.GetDictEntry(element.GetTag());
      vr = entry.GetVR();
    }

    isSequence = (vr == gdcm::VR::SQ);

    return gdcm::VR::GetVRString(vr);
  }


  static Encoding DetectEncoding(const gdcm::DataSet& dicom)
  {
    if (!dicom.FindDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET))
    {
      return Encoding_Ascii;
    }

    const gdcm::DataElement& element = 
      dicom.GetDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET);

    const gdcm::ByteValue* data = element.GetByteValue();
    if (!data)
    {
      return Encoding_Unknown;
    }

    std::string tmp(data->GetPointer(), data->GetLength());
    StripSpaces(tmp);

    return GetDicomEncoding(tmp.c_str());
  }


  static bool ConvertDicomStringToUf8(std::string& result,
                                      const gdcm::DataElement& element,
                                      const Encoding sourceEncoding)
  {
    const gdcm::ByteValue* data = element.GetByteValue();
    if (!data)
    {
      return false;
    }

    if (sourceEncoding == Encoding_Utf8)
    {
      result.assign(data->GetPointer(), data->GetLength());
    }
    else
    {
      std::string tmp(data->GetPointer(), data->GetLength());
      result = ConvertToUtf8(tmp, sourceEncoding);
    }

    StripSpaces(result);
    return true;
  }


  static void DicomToXmlInternal(pugi::xml_node& target,
                                 const gdcm::Dict& dictionary,
                                 const gdcm::DataSet& dicom,
                                 const Encoding sourceEncoding)
  {
    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
    {
      pugi::xml_node node = target.append_child("DicomAttribute");
      node.append_attribute("tag").set_value(FormatTag(it->GetTag()).c_str());
      node.append_attribute("keyword").set_value(GetKeyword(dictionary, it->GetTag()));

      bool isSequence = false;
      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
      {
        // The VR of this attribute has changed from UT to UR.
        node.append_attribute("vr").set_value("UR");
      }
      else
      {
        node.append_attribute("vr").set_value(GetVRName(isSequence, dictionary, *it));
      }

      if (isSequence)
      {
        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();

        for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
        {
          pugi::xml_node item = node.append_child("Item");
          std::string number = boost::lexical_cast<std::string>(i);
          item.append_attribute("number").set_value(number.c_str());
          DicomToXmlInternal(item, dictionary, seq->GetItem(i).GetNestedDataSet(), sourceEncoding);
        }
      }
      else
      {
        // Deal with other value representations
        pugi::xml_node value = node.append_child("Value");
        value.append_attribute("number").set_value("1");

        std::string tmp;
        if (ConvertDicomStringToUf8(tmp, *it, sourceEncoding)) 
        {
          value.append_child(pugi::node_pcdata).set_value(tmp.c_str());
        }
      }
    }
  }


  void DicomToXml(pugi::xml_document& target,
                  const gdcm::Dict& dictionary,
                  const gdcm::DataSet& dicom)
  {
    pugi::xml_node root = target.append_child("NativeDicomModel");
    root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
    root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
    root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance");

    Encoding encoding = DetectEncoding(dicom);
    DicomToXmlInternal(root, dictionary, dicom, encoding);

    pugi::xml_node decl = target.prepend_child(pugi::node_declaration);
    decl.append_attribute("version").set_value("1.0");
    decl.append_attribute("encoding").set_value("utf-8");
  }


  static void DicomToJsonInternal(Json::Value& target,
                                  const gdcm::Dict& dictionary,
                                  const gdcm::DataSet& dicom,
                                  Encoding sourceEncoding)
  {
    target = Json::objectValue;

    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
    {
      Json::Value node = Json::objectValue;

      bool isSequence = false;
      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
      {
        // The VR of this attribute has changed from UT to UR.
        node["vr"] = "UR";
      }
      else
      {
        node["vr"] = GetVRName(isSequence, dictionary, *it);
      }

      if (isSequence)
      {
        // Deal with sequences
        node["Value"] = Json::arrayValue;

        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();

        for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
        {
          Json::Value child;
          DicomToJsonInternal(child, dictionary, seq->GetItem(i).GetNestedDataSet(), sourceEncoding);
          node["Value"].append(child);
        }
      }
      else
      {
        // Deal with other value representations
        node["Value"] = Json::arrayValue;

        std::string value;
        if (ConvertDicomStringToUf8(value, *it, sourceEncoding)) 
        {
          node["Value"].append(value.c_str());
        }
      }

      target[FormatTag(it->GetTag())] = node;
    }
  }


  void DicomToJson(Json::Value& target,
                   const gdcm::Dict& dictionary,
                   const gdcm::DataSet& dicom)
  {
    Encoding encoding = DetectEncoding(dicom);
    DicomToJsonInternal(target, dictionary, dicom, encoding);
  }


  void GenerateSingleDicomAnswer(std::string& result,
                                 const gdcm::Dict& dictionary,
                                 const gdcm::DataSet& dicom,
                                 bool isXml)
  {
    if (isXml)
    {
      pugi::xml_document doc;
      DicomToXml(doc, dictionary, dicom);
    
      ChunkedBufferWriter writer;
      doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);

      writer.Flatten(result);
    }
    else
    {
      Json::Value v;
      DicomToJson(v, dictionary, dicom);

      Json::FastWriter writer;
      result = writer.write(v); 
    }
  }


  void AnswerDicom(OrthancPluginContext* context,
                   OrthancPluginRestOutput* output,
                   const gdcm::Dict& dictionary,
                   const gdcm::DataSet& dicom,
                   bool isXml)
  {
    std::string answer;
    GenerateSingleDicomAnswer(answer, dictionary, dicom, isXml);
    OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), 
                              isXml ? "application/dicom+xml" : "application/json");
  }
}