Mercurial > hg > orthanc
diff Core/DicomParsing/DicomWebJsonVisitor.cpp @ 3202:ef4d86d05503
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 06 Feb 2019 15:21:32 +0100 |
parents | |
children | 810772486249 |
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; + } +}