Mercurial > hg > orthanc
changeset 3202:ef4d86d05503
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 06 Feb 2019 15:21:32 +0100 |
parents | b69fe409cb4d |
children | 810772486249 |
files | Core/DicomParsing/DicomWebJsonVisitor.cpp Core/DicomParsing/DicomWebJsonVisitor.h Resources/CMake/OrthancFrameworkConfiguration.cmake UnitTestsSources/DicomMapTests.cpp |
diffstat | 4 files changed, 739 insertions(+), 631 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomWebJsonVisitor.cpp Wed Feb 06 15:21:32 2019 +0100 @@ -0,0 +1,573 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., 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 "DicomWebJsonVisitor.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "FromDcmtkBridge.h" + +#include <boost/math/special_functions/round.hpp> +#include <boost/lexical_cast.hpp> + + +static const char* const KEY_ALPHABETIC = "Alphabetic"; +static const char* const KEY_BULK_DATA_URI = "BulkDataURI"; +static const char* const KEY_INLINE_BINARY = "InlineBinary"; +static const char* const KEY_SQ = "SQ"; +static const char* const KEY_VALUE = "Value"; +static const char* const KEY_VR = "vr"; + + +namespace Orthanc +{ +#if ORTHANC_ENABLE_PUGIXML == 1 + static void ExploreXmlDataset(pugi::xml_node& target, + const Json::Value& source) + { + assert(source.type() == Json::objectValue); + + Json::Value::Members members = source.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]); + const Json::Value& content = source[members[i]]; + + assert(content.type() == Json::objectValue && + content.isMember("vr") && + content["vr"].type() == Json::stringValue); + const std::string vr = content["vr"].asString(); + + const std::string keyword = FromDcmtkBridge::GetTagName(tag, ""); + + pugi::xml_node node = target.append_child("DicomAttribute"); + node.append_attribute("tag").set_value(members[i].c_str()); + node.append_attribute("vr").set_value(vr.c_str()); + + if (keyword != std::string(DcmTag_ERROR_TagName)) + { + node.append_attribute("keyword").set_value(keyword.c_str()); + } + + if (content.isMember(KEY_VALUE)) + { + assert(content[KEY_VALUE].type() == Json::arrayValue); + + for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++) + { + std::string number = boost::lexical_cast<std::string>(j + 1); + + if (vr == "SQ") + { + if (content[KEY_VALUE][j].type() == Json::objectValue) + { + pugi::xml_node child = node.append_child("Item"); + child.append_attribute("number").set_value(number.c_str()); + ExploreXmlDataset(child, content[KEY_VALUE][j]); + } + } + if (vr == "PN") + { + if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) && + content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue) + { + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^'); + + pugi::xml_node child = node.append_child("PersonName"); + child.append_attribute("number").set_value(number.c_str()); + + pugi::xml_node name = child.append_child(KEY_ALPHABETIC); + + if (tokens.size() >= 1) + { + name.append_child("FamilyName").text() = tokens[0].c_str(); + } + + if (tokens.size() >= 2) + { + name.append_child("GivenName").text() = tokens[1].c_str(); + } + + if (tokens.size() >= 3) + { + name.append_child("MiddleName").text() = tokens[2].c_str(); + } + + if (tokens.size() >= 4) + { + name.append_child("NamePrefix").text() = tokens[3].c_str(); + } + + if (tokens.size() >= 5) + { + name.append_child("NameSuffix").text() = tokens[4].c_str(); + } + } + } + else + { + pugi::xml_node child = node.append_child("Value"); + child.append_attribute("number").set_value(number.c_str()); + + switch (content[KEY_VALUE][j].type()) + { + case Json::stringValue: + child.text() = content[KEY_VALUE][j].asCString(); + break; + + case Json::realValue: + child.text() = content[KEY_VALUE][j].asFloat(); + break; + + case Json::intValue: + child.text() = content[KEY_VALUE][j].asInt(); + break; + + case Json::uintValue: + child.text() = content[KEY_VALUE][j].asUInt(); + break; + + default: + break; + } + } + } + } + else if (content.isMember(KEY_BULK_DATA_URI) && + content[KEY_BULK_DATA_URI].type() == Json::stringValue) + { + pugi::xml_node child = node.append_child("BulkData"); + child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString()); + } + else if (content.isMember(KEY_INLINE_BINARY) && + content[KEY_INLINE_BINARY].type() == Json::stringValue) + { + pugi::xml_node child = node.append_child("InlineBinary"); + child.text() = content[KEY_INLINE_BINARY].asCString(); + } + } + } +#endif + + +#if ORTHANC_ENABLE_PUGIXML == 1 + static void DicomWebJsonToXml(pugi::xml_document& target, + const Json::Value& source) + { + 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"); + + ExploreXmlDataset(root, source); + + 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"); + } +#endif + + + std::string DicomWebJsonVisitor::FormatTag(const DicomTag& tag) + { + char buf[16]; + sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement()); + return std::string(buf); + } + + + Json::Value& DicomWebJsonVisitor::CreateNode(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag) + { + assert(parentTags.size() == parentIndexes.size()); + + Json::Value* node = &result_; + + for (size_t i = 0; i < parentTags.size(); i++) + { + std::string t = FormatTag(parentTags[i]); + + if (!node->isMember(t)) + { + Json::Value item = Json::objectValue; + item[KEY_VR] = KEY_SQ; + item[KEY_VALUE] = Json::arrayValue; + item[KEY_VALUE].append(Json::objectValue); + (*node) [t] = item; + + node = &(*node)[t][KEY_VALUE][0]; + } + else if ((*node) [t].type() != Json::objectValue || + !(*node) [t].isMember(KEY_VR) || + (*node) [t][KEY_VR].type() != Json::stringValue || + (*node) [t][KEY_VR].asString() != KEY_SQ || + !(*node) [t].isMember(KEY_VALUE) || + (*node) [t][KEY_VALUE].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + size_t currentSize = (*node) [t][KEY_VALUE].size(); + + if (parentIndexes[i] < currentSize) + { + // The node already exists + } + else if (parentIndexes[i] == currentSize) + { + (*node) [t][KEY_VALUE].append(Json::objectValue); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + + node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])]; + } + } + + assert(node->type() == Json::objectValue); + + std::string t = FormatTag(tag); + if (node->isMember(t)) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + (*node) [t] = Json::objectValue; + return (*node) [t]; + } + } + + + Json::Value DicomWebJsonVisitor::FormatInteger(int64_t value) + { + if (value < 0) + { + return Json::Value(static_cast<int32_t>(value)); + } + else + { + return Json::Value(static_cast<uint32_t>(value)); + } + } + + + Json::Value DicomWebJsonVisitor::FormatDouble(double value) + { + long long a = boost::math::llround<double>(value); + + double d = fabs(value - static_cast<double>(a)); + + if (d <= std::numeric_limits<double>::epsilon() * 100.0) + { + return FormatInteger(a); + } + else + { + return Json::Value(value); + } + } + + +#if ORTHANC_ENABLE_PUGIXML == 1 + void DicomWebJsonVisitor::FormatXml(pugi::xml_document& target) const + { + DicomWebJsonToXml(target, result_); + } +#endif + + + void DicomWebJsonVisitor::VisitEmptySequence(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag) + { + if (tag.GetElement() != 0x0000) + { + Json::Value& node = CreateNode(parentTags, parentIndexes, tag); + node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence); + } + } + + + void DicomWebJsonVisitor::VisitBinary(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const void* data, + size_t size) + { + assert(vr == ValueRepresentation_OtherByte || + vr == ValueRepresentation_OtherDouble || + vr == ValueRepresentation_OtherFloat || + vr == ValueRepresentation_OtherLong || + vr == ValueRepresentation_OtherWord || + vr == ValueRepresentation_Unknown); + + if (tag.GetElement() != 0x0000) + { + BinaryMode mode; + std::string bulkDataUri; + + if (formatter_ == NULL) + { + mode = BinaryMode_InlineBinary; + } + else + { + mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr); + } + + if (mode != BinaryMode_Ignore) + { + Json::Value& node = CreateNode(parentTags, parentIndexes, tag); + node[KEY_VR] = EnumerationToString(vr); + + switch (mode) + { + case BinaryMode_BulkDataUri: + node[KEY_BULK_DATA_URI] = bulkDataUri; + break; + + case BinaryMode_InlineBinary: + { + std::string tmp(static_cast<const char*>(data), size); + + std::string base64; + Toolbox::EncodeBase64(base64, tmp); + + node[KEY_INLINE_BINARY] = base64; + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } + } + + + void DicomWebJsonVisitor::VisitIntegers(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::vector<int64_t>& values) + { + if (tag.GetElement() != 0x0000 && + vr != ValueRepresentation_NotSupported) + { + Json::Value& node = CreateNode(parentTags, parentIndexes, tag); + node[KEY_VR] = EnumerationToString(vr); + + if (!values.empty()) + { + Json::Value content = Json::arrayValue; + for (size_t i = 0; i < values.size(); i++) + { + content.append(FormatInteger(values[i])); + } + + node[KEY_VALUE] = content; + } + } + } + + void DicomWebJsonVisitor::VisitDoubles(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::vector<double>& values) + { + if (tag.GetElement() != 0x0000 && + vr != ValueRepresentation_NotSupported) + { + Json::Value& node = CreateNode(parentTags, parentIndexes, tag); + node[KEY_VR] = EnumerationToString(vr); + + if (!values.empty()) + { + Json::Value content = Json::arrayValue; + for (size_t i = 0; i < values.size(); i++) + { + content.append(FormatDouble(values[i])); + } + + node[KEY_VALUE] = content; + } + } + } + + + void DicomWebJsonVisitor::VisitAttributes(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + const std::vector<DicomTag>& values) + { + if (tag.GetElement() != 0x0000) + { + Json::Value& node = CreateNode(parentTags, parentIndexes, tag); + node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag); + + if (!values.empty()) + { + Json::Value content = Json::arrayValue; + for (size_t i = 0; i < values.size(); i++) + { + content.append(FormatTag(values[i])); + } + + node[KEY_VALUE] = content; + } + } + } + + + ITagVisitor::Action + DicomWebJsonVisitor::VisitString(std::string& newValue, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::string& value) + { + if (tag.GetElement() == 0x0000 || + vr == ValueRepresentation_NotSupported) + { + return Action_None; + } + else + { + Json::Value& node = CreateNode(parentTags, parentIndexes, tag); + node[KEY_VR] = EnumerationToString(vr); + + if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + // TODO - The JSON file has an UTF-8 encoding, thus DCMTK + // replaces the specific character set with "ISO_IR 192" + // (UNICODE UTF-8). It is unclear whether the source + // character set should be kept: We thus mimic DCMTK. + node[KEY_VALUE].append("ISO_IR 192"); + } + else + { + std::string truncated; + + if (!value.empty() && + value[value.size() - 1] == '\0') + { + truncated = value.substr(0, value.size() - 1); + } + else + { + truncated = value; + } + + if (!truncated.empty()) + { + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, truncated, '\\'); + + node[KEY_VALUE] = Json::arrayValue; + for (size_t i = 0; i < tokens.size(); i++) + { + try + { + switch (vr) + { + case ValueRepresentation_PersonName: + { + Json::Value value = Json::objectValue; + if (!tokens[i].empty()) + { + value[KEY_ALPHABETIC] = tokens[i]; + } + node[KEY_VALUE].append(value); + break; + } + + case ValueRepresentation_IntegerString: + if (tokens[i].empty()) + { + node[KEY_VALUE].append(Json::nullValue); + } + else + { + int64_t value = boost::lexical_cast<int64_t>(tokens[i]); + node[KEY_VALUE].append(FormatInteger(value)); + } + + break; + + case ValueRepresentation_DecimalString: + if (tokens[i].empty()) + { + node[KEY_VALUE].append(Json::nullValue); + } + else + { + double value = boost::lexical_cast<double>(tokens[i]); + node[KEY_VALUE].append(FormatDouble(value)); + } + break; + + default: + if (tokens[i].empty()) + { + node[KEY_VALUE].append(Json::nullValue); + } + else + { + node[KEY_VALUE].append(tokens[i]); + } + + break; + } + } + catch (boost::bad_lexical_cast&) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + } + } + } + + return Action_None; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomWebJsonVisitor.h Wed Feb 06 15:21:32 2019 +0100 @@ -0,0 +1,164 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., 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 + +#if !defined(ORTHANC_ENABLE_PUGIXML) +# error Macro ORTHANC_ENABLE_PUGIXML must be defined to use this file +#endif + +#if ORTHANC_ENABLE_PUGIXML == 1 +# include <pugixml.hpp> +#endif + +#include "ITagVisitor.h" + +#include <json/value.h> + + +namespace Orthanc +{ + class DicomWebJsonVisitor : public ITagVisitor + { + public: + enum BinaryMode + { + BinaryMode_Ignore, + BinaryMode_BulkDataUri, + BinaryMode_InlineBinary + }; + + class IBinaryFormatter : public boost::noncopyable + { + public: + virtual ~IBinaryFormatter() + { + } + + virtual BinaryMode Format(std::string& bulkDataUri, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr) = 0; + }; + + private: + Json::Value result_; + IBinaryFormatter *formatter_; + + static std::string FormatTag(const DicomTag& tag); + + Json::Value& CreateNode(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag); + + static Json::Value FormatInteger(int64_t value); + + static Json::Value FormatDouble(double value); + + public: + DicomWebJsonVisitor() : + formatter_(NULL) + { + Clear(); + } + + void SetFormatter(IBinaryFormatter& formatter) + { + formatter_ = &formatter; + } + + void Clear() + { + result_ = Json::objectValue; + } + + const Json::Value& GetResult() const + { + return result_; + } + +#if ORTHANC_ENABLE_PUGIXML == 1 + void FormatXml(pugi::xml_document& target) const; +#endif + + virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr) + ORTHANC_OVERRIDE + { + } + + virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag) + ORTHANC_OVERRIDE; + + virtual void VisitBinary(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const void* data, + size_t size) + ORTHANC_OVERRIDE; + + virtual void VisitIntegers(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::vector<int64_t>& values) + ORTHANC_OVERRIDE; + + virtual void VisitDoubles(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::vector<double>& values) + ORTHANC_OVERRIDE; + + virtual void VisitAttributes(const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + const std::vector<DicomTag>& values) + ORTHANC_OVERRIDE; + + virtual Action VisitString(std::string& newValue, + const std::vector<DicomTag>& parentTags, + const std::vector<size_t>& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::string& value) + ORTHANC_OVERRIDE; + }; +}
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Feb 06 15:01:37 2019 +0100 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Feb 06 15:21:32 2019 +0100 @@ -448,6 +448,7 @@ set(ORTHANC_DICOM_SOURCES_INTERNAL ${ORTHANC_ROOT}/Core/DicomParsing/DicomModification.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/DicomWebJsonVisitor.cpp ${ORTHANC_ROOT}/Core/DicomParsing/FromDcmtkBridge.cpp ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomFile.cpp ${ORTHANC_ROOT}/Core/DicomParsing/ToDcmtkBridge.cpp
--- a/UnitTestsSources/DicomMapTests.cpp Wed Feb 06 15:01:37 2019 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Wed Feb 06 15:21:32 2019 +0100 @@ -38,6 +38,7 @@ #include "../Core/DicomFormat/DicomMap.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/DicomParsing/ParsedDicomFile.h" +#include "../Core/DicomParsing/DicomWebJsonVisitor.h" #include "../OrthancServer/DicomInstanceToStore.h" @@ -554,633 +555,6 @@ - - -#include <boost/math/special_functions/round.hpp> -#include <pugixml.hpp> - - -static const char* const KEY_ALPHABETIC = "Alphabetic"; -static const char* const KEY_BULK_DATA_URI = "BulkDataURI"; -static const char* const KEY_INLINE_BINARY = "InlineBinary"; -static const char* const KEY_SQ = "SQ"; -static const char* const KEY_VALUE = "Value"; -static const char* const KEY_VR = "vr"; - -namespace Orthanc -{ - static void ExploreDataset(pugi::xml_node& target, - const Json::Value& source) - { - assert(source.type() == Json::objectValue); - - Json::Value::Members members = source.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]); - const Json::Value& content = source[members[i]]; - - assert(content.type() == Json::objectValue && - content.isMember("vr") && - content["vr"].type() == Json::stringValue); - const std::string vr = content["vr"].asString(); - - const std::string keyword = FromDcmtkBridge::GetTagName(tag, ""); - - pugi::xml_node node = target.append_child("DicomAttribute"); - node.append_attribute("tag").set_value(members[i].c_str()); - node.append_attribute("vr").set_value(vr.c_str()); - - if (keyword != std::string(DcmTag_ERROR_TagName)) - { - node.append_attribute("keyword").set_value(keyword.c_str()); - } - - if (content.isMember(KEY_VALUE)) - { - assert(content[KEY_VALUE].type() == Json::arrayValue); - - for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++) - { - std::string number = boost::lexical_cast<std::string>(j + 1); - - if (vr == "SQ") - { - if (content[KEY_VALUE][j].type() == Json::objectValue) - { - pugi::xml_node child = node.append_child("Item"); - child.append_attribute("number").set_value(number.c_str()); - ExploreDataset(child, content[KEY_VALUE][j]); - } - } - if (vr == "PN") - { - if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) && - content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue) - { - std::vector<std::string> tokens; - Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^'); - - pugi::xml_node child = node.append_child("PersonName"); - child.append_attribute("number").set_value(number.c_str()); - - pugi::xml_node name = child.append_child(KEY_ALPHABETIC); - - if (tokens.size() >= 1) - { - name.append_child("FamilyName").text() = tokens[0].c_str(); - } - - if (tokens.size() >= 2) - { - name.append_child("GivenName").text() = tokens[1].c_str(); - } - - if (tokens.size() >= 3) - { - name.append_child("MiddleName").text() = tokens[2].c_str(); - } - - if (tokens.size() >= 4) - { - name.append_child("NamePrefix").text() = tokens[3].c_str(); - } - - if (tokens.size() >= 5) - { - name.append_child("NameSuffix").text() = tokens[4].c_str(); - } - } - } - else - { - pugi::xml_node child = node.append_child("Value"); - child.append_attribute("number").set_value(number.c_str()); - - switch (content[KEY_VALUE][j].type()) - { - case Json::stringValue: - child.text() = content[KEY_VALUE][j].asCString(); - break; - - case Json::realValue: - child.text() = content[KEY_VALUE][j].asFloat(); - break; - - case Json::intValue: - child.text() = content[KEY_VALUE][j].asInt(); - break; - - case Json::uintValue: - child.text() = content[KEY_VALUE][j].asUInt(); - break; - - default: - break; - } - } - } - } - else if (content.isMember(KEY_BULK_DATA_URI) && - content[KEY_BULK_DATA_URI].type() == Json::stringValue) - { - pugi::xml_node child = node.append_child("BulkData"); - child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString()); - } - else if (content.isMember(KEY_INLINE_BINARY) && - content[KEY_INLINE_BINARY].type() == Json::stringValue) - { - pugi::xml_node child = node.append_child("InlineBinary"); - child.text() = content[KEY_INLINE_BINARY].asCString(); - } - } - } - - - static void DicomWebJsonToXml(pugi::xml_document& target, - const Json::Value& source) - { - 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"); - - ExploreDataset(root, source); - - 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"); - } - - - enum DicomWebBinaryMode - { - DicomWebBinaryMode_Ignore, - DicomWebBinaryMode_BulkDataUri, - DicomWebBinaryMode_InlineBinary - }; - - class IDicomWebBinaryFormatter : public boost::noncopyable - { - public: - virtual ~IDicomWebBinaryFormatter() - { - } - - virtual DicomWebBinaryMode Format(std::string& bulkDataUri, - const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr) = 0; - }; - - class DicomWebJsonVisitor : public ITagVisitor - { - private: - Json::Value result_; - IDicomWebBinaryFormatter *formatter_; - - static std::string FormatTag(const DicomTag& tag) - { - char buf[16]; - sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement()); - return std::string(buf); - } - - Json::Value& CreateNode(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag) - { - assert(parentTags.size() == parentIndexes.size()); - - Json::Value* node = &result_; - - for (size_t i = 0; i < parentTags.size(); i++) - { - std::string t = FormatTag(parentTags[i]); - - if (!node->isMember(t)) - { - Json::Value item = Json::objectValue; - item[KEY_VR] = KEY_SQ; - item[KEY_VALUE] = Json::arrayValue; - item[KEY_VALUE].append(Json::objectValue); - (*node) [t] = item; - - node = &(*node)[t][KEY_VALUE][0]; - } - else if ((*node) [t].type() != Json::objectValue || - !(*node) [t].isMember(KEY_VR) || - (*node) [t][KEY_VR].type() != Json::stringValue || - (*node) [t][KEY_VR].asString() != KEY_SQ || - !(*node) [t].isMember(KEY_VALUE) || - (*node) [t][KEY_VALUE].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - size_t currentSize = (*node) [t][KEY_VALUE].size(); - - if (parentIndexes[i] < currentSize) - { - // The node already exists - } - else if (parentIndexes[i] == currentSize) - { - (*node) [t][KEY_VALUE].append(Json::objectValue); - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - - node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])]; - } - } - - assert(node->type() == Json::objectValue); - - std::string t = FormatTag(tag); - if (node->isMember(t)) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - (*node) [t] = Json::objectValue; - return (*node) [t]; - } - } - - static Json::Value FormatInteger(int64_t value) - { - if (value < 0) - { - return Json::Value(static_cast<int32_t>(value)); - } - else - { - return Json::Value(static_cast<uint32_t>(value)); - } - } - - static Json::Value FormatDouble(double value) - { - long long a = boost::math::llround<double>(value); - - double d = fabs(value - static_cast<double>(a)); - - if (d <= std::numeric_limits<double>::epsilon() * 100.0) - { - return FormatInteger(a); - } - else - { - return Json::Value(value); - } - } - - public: - DicomWebJsonVisitor() : - formatter_(NULL) - { - Clear(); - } - - void SetFormatter(IDicomWebBinaryFormatter& formatter) - { - formatter_ = &formatter; - } - - void Clear() - { - result_ = Json::objectValue; - } - - const Json::Value& GetResult() const - { - return result_; - } - - void FormatXml(pugi::xml_document& target) const - { - DicomWebJsonToXml(target, result_); - } - - virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr) ORTHANC_OVERRIDE - { - } - - virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag) ORTHANC_OVERRIDE - { - if (tag.GetElement() != 0x0000) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence); - } - } - - virtual void VisitBinary(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const void* data, - size_t size) ORTHANC_OVERRIDE - { - assert(vr == ValueRepresentation_OtherByte || - vr == ValueRepresentation_OtherDouble || - vr == ValueRepresentation_OtherFloat || - vr == ValueRepresentation_OtherLong || - vr == ValueRepresentation_OtherWord || - vr == ValueRepresentation_Unknown); - - if (tag.GetElement() != 0x0000) - { - DicomWebBinaryMode mode; - std::string bulkDataUri; - - if (formatter_ == NULL) - { - mode = DicomWebBinaryMode_InlineBinary; - } - else - { - mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr); - } - - /*mode = DicomWebBinaryMode_BulkDataUri; - bulkDataUri = "http://localhost/" + tag.Format();*/ - - if (mode != DicomWebBinaryMode_Ignore) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - switch (mode) - { - case DicomWebBinaryMode_BulkDataUri: - node[KEY_BULK_DATA_URI] = bulkDataUri; - break; - - case DicomWebBinaryMode_InlineBinary: - { - std::string tmp(static_cast<const char*>(data), size); - - std::string base64; - Toolbox::EncodeBase64(base64, tmp); - - node[KEY_INLINE_BINARY] = base64; - break; - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - } - } - - virtual void VisitIntegers(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector<int64_t>& values) ORTHANC_OVERRIDE - { - if (tag.GetElement() != 0x0000 && - vr != ValueRepresentation_NotSupported) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - if (!values.empty()) - { - Json::Value content = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - content.append(FormatInteger(values[i])); - } - - node[KEY_VALUE] = content; - } - } - } - - virtual void VisitDoubles(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector<double>& values) ORTHANC_OVERRIDE - { - if (tag.GetElement() != 0x0000 && - vr != ValueRepresentation_NotSupported) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - if (!values.empty()) - { - Json::Value content = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - content.append(FormatDouble(values[i])); - } - - node[KEY_VALUE] = content; - } - } - } - - virtual void VisitAttributes(const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - const std::vector<DicomTag>& values) ORTHANC_OVERRIDE - { - if (tag.GetElement() != 0x0000) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag); - - if (!values.empty()) - { - Json::Value content = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - content.append(FormatTag(values[i])); - } - - node[KEY_VALUE] = content; - } - } - } - - virtual Action VisitString(std::string& newValue, - const std::vector<DicomTag>& parentTags, - const std::vector<size_t>& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::string& value) ORTHANC_OVERRIDE - { - if (tag.GetElement() == 0x0000 || - vr == ValueRepresentation_NotSupported) - { - return Action_None; - } - else - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - // TODO - The JSON file has an UTF-8 encoding, thus DCMTK - // replaces the specific character set with "ISO_IR 192" - // (UNICODE UTF-8). It is unclear whether the source - // character set should be kept: We thus mimic DCMTK. - node[KEY_VALUE].append("ISO_IR 192"); - } - else - { - std::string truncated; - - if (!value.empty() && - value[value.size() - 1] == '\0') - { - truncated = value.substr(0, value.size() - 1); - } - else - { - truncated = value; - } - - if (!truncated.empty()) - { - std::vector<std::string> tokens; - Toolbox::TokenizeString(tokens, truncated, '\\'); - - node[KEY_VALUE] = Json::arrayValue; - for (size_t i = 0; i < tokens.size(); i++) - { - try - { - switch (vr) - { - case ValueRepresentation_PersonName: - { - Json::Value value = Json::objectValue; - if (!tokens[i].empty()) - { - value[KEY_ALPHABETIC] = tokens[i]; - } - node[KEY_VALUE].append(value); - break; - } - - case ValueRepresentation_IntegerString: - if (tokens[i].empty()) - { - node[KEY_VALUE].append(Json::nullValue); - } - else - { - int64_t value = boost::lexical_cast<int64_t>(tokens[i]); - node[KEY_VALUE].append(FormatInteger(value)); - } - - break; - - case ValueRepresentation_DecimalString: - if (tokens[i].empty()) - { - node[KEY_VALUE].append(Json::nullValue); - } - else - { - double value = boost::lexical_cast<double>(tokens[i]); - node[KEY_VALUE].append(FormatDouble(value)); - } - break; - - default: - if (tokens[i].empty()) - { - node[KEY_VALUE].append(Json::nullValue); - } - else - { - node[KEY_VALUE].append(tokens[i]); - } - - break; - } - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - } - } - } - - return Action_None; - } - }; -} - - - - - - -#include "../Core/SystemToolbox.h" - - -/* - -MarekLatin2.dcm -HierarchicalAnonymization/StructuredReports/IM0 -DummyCT.dcm -Brainix/Epi/IM-0001-0018.dcm -Issue22.dcm - - -cat << EOF > /tmp/tutu.py -import json -import sys -j = json.loads(sys.stdin.read().decode("utf-8-sig")) -print(json.dumps(j, indent=4, sort_keys=True, ensure_ascii=False).encode('utf-8')) -EOF - -DCMDICTPATH=/home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/data/dicom.dic /home/jodogne/Downloads/dcmtk-3.6.4/i/bin/dcm2json ~/Subversion/orthanc-tests/Database/DummyCT.dcm | tr -d '\0' | sed 's/\\u0000//g' | sed 's/\.0$//' | python /tmp/tutu.py > /tmp/a.json - -make -j4 && ./UnitTests --gtest_filter=DicomWeb* && python /tmp/tutu.py < tutu.json > /tmp/b.json && diff -i /tmp/a.json /tmp/b.json - -*/ - -TEST(DicomWebJson, Basic) -{ - std::string content; - Orthanc::SystemToolbox::ReadFile(content, "/home/jodogne/Subversion/orthanc-tests/Database/DummyCT.dcm"); - - Orthanc::ParsedDicomFile dicom(content); - - Orthanc::DicomWebJsonVisitor visitor; - dicom.Apply(visitor); - - Orthanc::SystemToolbox::WriteFile(visitor.GetResult().toStyledString(), "tutu.json"); - - pugi::xml_document xml; - visitor.FormatXml(xml); - xml.print(std::cout); -} - - TEST(DicomWebJson, Multiplicity) { // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.4.html @@ -1214,7 +588,6 @@ pugi::xml_document xml; visitor.FormatXml(xml); - xml.print(std::cout); } @@ -1245,7 +618,6 @@ pugi::xml_document xml; visitor.FormatXml(xml); - xml.print(std::cout); } @@ -1388,7 +760,6 @@ pugi::xml_document xml; visitor.FormatXml(xml); - xml.print(std::cout); } @@ -1433,5 +804,4 @@ pugi::xml_document xml; visitor.FormatXml(xml); - xml.print(std::cout); }