Mercurial > hg > orthanc-dicomweb
changeset 41:9e7f9134f237
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 01 Aug 2015 17:18:21 +0200 |
parents | 43085eac99ff |
children | 3a2040a79007 |
files | CMakeLists.txt Core/ChunkedBuffer.cpp Core/ChunkedBuffer.h Core/Configuration.cpp Core/Configuration.h Core/Dicom.cpp Core/Dicom.h Core/DicomResults.cpp Core/DicomResults.h Core/Toolbox.cpp Core/Toolbox.h Plugin/ChunkedBuffer.cpp Plugin/ChunkedBuffer.h Plugin/Configuration.cpp Plugin/Configuration.h Plugin/Dicom.cpp Plugin/Dicom.h Plugin/DicomResults.cpp Plugin/DicomResults.h Plugin/Plugin.cpp Plugin/QidoRs.cpp Plugin/StowRs.cpp Plugin/Toolbox.cpp Plugin/Toolbox.h Plugin/WadoRs.cpp UnitTestsSources/UnitTestsMain.cpp |
diffstat | 26 files changed, 1709 insertions(+), 1709 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Jul 31 13:36:02 2015 +0200 +++ b/CMakeLists.txt Sat Aug 01 17:18:21 2015 +0200 @@ -98,11 +98,11 @@ ${BOOST_SOURCES} ${JSONCPP_SOURCES} ${PUGIXML_SOURCES} - Core/ChunkedBuffer.cpp - Core/Configuration.cpp - Core/Dicom.cpp - Core/DicomResults.cpp - Core/Toolbox.cpp + Plugin/ChunkedBuffer.cpp + Plugin/Configuration.cpp + Plugin/Dicom.cpp + Plugin/DicomResults.cpp + Plugin/Toolbox.cpp ) add_library(OrthancDicomWeb SHARED ${CORE_SOURCES}
--- a/Core/ChunkedBuffer.cpp Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/** - * 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 "ChunkedBuffer.h" - -#include <cassert> -#include <string.h> - - -namespace OrthancPlugins -{ - void ChunkedBuffer::Clear() - { - numBytes_ = 0; - - for (Chunks::iterator it = chunks_.begin(); - it != chunks_.end(); ++it) - { - delete *it; - } - } - - - void ChunkedBuffer::AddChunk(const char* chunkData, - size_t chunkSize) - { - if (chunkSize == 0) - { - return; - } - - assert(chunkData != NULL); - chunks_.push_back(new std::string(chunkData, chunkSize)); - numBytes_ += chunkSize; - } - - - void ChunkedBuffer::AddChunk(const std::string& chunk) - { - if (chunk.size() > 0) - { - AddChunk(&chunk[0], chunk.size()); - } - } - - - void ChunkedBuffer::Flatten(std::string& result) - { - result.resize(numBytes_); - - size_t pos = 0; - for (Chunks::iterator it = chunks_.begin(); - it != chunks_.end(); ++it) - { - assert(*it != NULL); - - size_t s = (*it)->size(); - if (s != 0) - { - memcpy(&result[pos], (*it)->c_str(), s); - pos += s; - } - - delete *it; - } - - chunks_.clear(); - } -}
--- a/Core/ChunkedBuffer.h Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include <list> -#include <string> - -namespace OrthancPlugins -{ - class ChunkedBuffer - { - private: - typedef std::list<std::string*> Chunks; - size_t numBytes_; - Chunks chunks_; - - void Clear(); - - public: - ChunkedBuffer() : numBytes_(0) - { - } - - ~ChunkedBuffer() - { - Clear(); - } - - size_t GetNumBytes() const - { - return numBytes_; - } - - void AddChunk(const char* chunkData, - size_t chunkSize); - - void AddChunk(const std::string& chunk); - - void Flatten(std::string& result); - }; -}
--- a/Core/Configuration.cpp Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/** - * 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 "Configuration.h" - -#include "Toolbox.h" - -#include <fstream> -#include <json/reader.h> - -namespace OrthancPlugins -{ - namespace Configuration - { - bool Read(Json::Value& configuration, - OrthancPluginContext* context) - { - std::string s; - - { - char* tmp = OrthancPluginGetConfiguration(context); - if (tmp == NULL) - { - OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc"); - return false; - } - - s.assign(tmp); - OrthancPluginFreeString(context, tmp); - } - - Json::Reader reader; - if (reader.parse(s, configuration)) - { - return true; - } - else - { - OrthancPluginLogError(context, "Unable to parse the configuration"); - return false; - } - } - - - std::string GetStringValue(const Json::Value& configuration, - const std::string& key, - const std::string& defaultValue) - { - if (configuration.type() != Json::objectValue || - !configuration.isMember(key) || - configuration[key].type() != Json::stringValue) - { - return defaultValue; - } - else - { - return configuration[key].asString(); - } - } - - - bool GetBoolValue(const Json::Value& configuration, - const std::string& key, - bool defaultValue) - { - if (configuration.type() != Json::objectValue || - !configuration.isMember(key) || - configuration[key].type() != Json::booleanValue) - { - return defaultValue; - } - else - { - return configuration[key].asBool(); - } - } - - - std::string GetRoot(const Json::Value& configuration) - { - std::string root; - - if (configuration.isMember("DicomWeb")) - { - root = GetStringValue(configuration["DicomWeb"], "Root", ""); - } - - if (root.empty()) - { - root = "/dicom-web/"; - } - - // Make sure the root URI starts and ends with a slash - if (root[0] != '/') - { - root = "/" + root; - } - - if (root[root.length() - 1] != '/') - { - root += "/"; - } - - return root; - } - - - std::string GetBaseUrl(const Json::Value& configuration, - const OrthancPluginHttpRequest* request) - { - std::string host; - bool ssl = false; - - if (configuration.isMember("DicomWeb")) - { - host = GetStringValue(configuration["DicomWeb"], "Host", ""); - ssl = GetBoolValue(configuration["DicomWeb"], "Ssl", false); - } - - if (host.empty() && - !LookupHttpHeader(host, request, "host")) - { - // Should never happen: The "host" header should always be present - // in HTTP requests. Provide a default value anyway. - host = "localhost:8042"; - } - - return (ssl ? "https://" : "http://") + host + GetRoot(configuration); - } - } -}
--- a/Core/Configuration.h Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include <orthanc/OrthancCPlugin.h> -#include <json/value.h> - -namespace OrthancPlugins -{ - namespace Configuration - { - bool Read(Json::Value& configuration, - OrthancPluginContext* context); - - std::string GetStringValue(const Json::Value& configuration, - const std::string& key, - const std::string& defaultValue); - - bool GetBoolValue(const Json::Value& configuration, - const std::string& key, - bool defaultValue); - - std::string GetRoot(const Json::Value& configuration); - - std::string GetBaseUrl(const Json::Value& configuration, - const OrthancPluginHttpRequest* request); - } -}
--- a/Core/Dicom.cpp Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,563 +0,0 @@ -/** - * 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 <gdcmStringFilter.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); - } - - - static bool GetTag(std::string& result, - const gdcm::DataSet& dataset, - const gdcm::Tag& tag, - bool stripSpaces) - { - 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; - } - - - static std::string GetTagWithDefault(const gdcm::DataSet& dataset, - const gdcm::Tag& tag, - const std::string& defaultValue, - bool stripSpaces) - { - std::string result; - if (!GetTag(result, dataset, tag, false)) - { - result = defaultValue; - } - - if (stripSpaces) - { - result = StripSpaces(result); - } - - return result; - } - - - bool ParsedDicomFile::GetTag(std::string& result, - const gdcm::Tag& tag, - bool stripSpaces) const - { - return OrthancPlugins::GetTag(result, GetDataSet(), tag, stripSpaces); - } - - - std::string ParsedDicomFile::GetTagWithDefault(const gdcm::Tag& tag, - const std::string& defaultValue, - bool stripSpaces) const - { - return OrthancPlugins::GetTagWithDefault(GetDataSet(), tag, defaultValue, stripSpaces); - } - - - 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)); - return NULL; - } - - - - 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(); - - if (vr == gdcm::VR::OB_OW) - { - vr = gdcm::VR::OB; - } - } - - isSequence = (vr == gdcm::VR::SQ); - - const char* str = gdcm::VR::GetVRString(vr); - if (isSequence) - { - return str; - } - - if (str == NULL || - strlen(str) != 2 || - !(str[0] >= 'A' && str[0] <= 'Z') || - !(str[1] >= 'A' && str[1] <= 'Z')) - { - return "UN"; - } - else - { - return str; - } - } - - - static bool IsBulkData(const std::string& vr) - { - /** - * Full list of VR (Value Representations) that are admissible for - * being retrieved as bulk data. We commented out some of them, as - * they correspond to strings and not to binary data. - **/ - return (//vr == "FL" || - //vr == "FD" || - //vr == "IS" || - vr == "LT" || - vr == "OB" || - vr == "OD" || - vr == "OF" || - vr == "OW" || - //vr == "SL" || - //vr == "SS" || - //vr == "ST" || - //vr == "UL" || - vr == "UN" || - //vr == "US" || - vr == "UT"); - } - - - static std::string GetBulkUriRoot(const std::string& wadoBase, - const gdcm::DataSet& dicom) - { - std::string study, series, instance; - - if (!GetTag(study, dicom, DICOM_TAG_STUDY_INSTANCE_UID, true) || - !GetTag(series, dicom, DICOM_TAG_SERIES_INSTANCE_UID, true) || - !GetTag(instance, dicom, DICOM_TAG_SOP_INSTANCE_UID, true)) - { - return ""; - } - else - { - return wadoBase + "studies/" + study + "/series/" + series + "/instances/" + instance + "/bulk/"; - } - } - - - 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()); - tmp = StripSpaces(tmp); - - return GetDicomEncoding(tmp.c_str()); - } - - - static bool ConvertDicomStringToUf8(std::string& result, - const gdcm::Dict& dictionary, - const gdcm::File* file, - const gdcm::DataElement& element, - const Encoding sourceEncoding) - { - const gdcm::ByteValue* data = element.GetByteValue(); - if (!data) - { - return false; - } - - if (file != NULL) - { - bool isSequence; - std::string vr = GetVRName(isSequence, dictionary, element); - if (!isSequence && ( - vr == "FL" || - vr == "FD" || - vr == "SL" || - vr == "SS" || - vr == "UL" || - vr == "US" - )) - { - gdcm::StringFilter f; - f.SetFile(*file); - result = f.ToString(element.GetTag()); - return true; - } - } - - if (sourceEncoding == Encoding_Utf8) - { - result.assign(data->GetPointer(), data->GetLength()); - } - else - { - std::string tmp(data->GetPointer(), data->GetLength()); - result = ConvertToUtf8(tmp, sourceEncoding); - } - - result = StripSpaces(result); - return true; - } - - - static void DicomToXmlInternal(pugi::xml_node& target, - const gdcm::Dict& dictionary, - const gdcm::File* file, - const gdcm::DataSet& dicom, - const Encoding sourceEncoding, - const std::string& bulkUri) - { - for (gdcm::DataSet::ConstIterator it = dicom.Begin(); - it != dicom.End(); ++it) // "*it" represents a "gdcm::DataElement" - { - char path[16]; - sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement()); - - pugi::xml_node node = target.append_child("DicomAttribute"); - node.append_attribute("tag").set_value(FormatTag(it->GetTag()).c_str()); - - const char* keyword = GetKeyword(dictionary, it->GetTag()); - if (keyword != NULL) - { - node.append_attribute("keyword").set_value(keyword); - } - - bool isSequence = false; - std::string vr; - if (it->GetTag() == DICOM_TAG_RETRIEVE_URL) - { - // The VR of this attribute has changed from UT to UR. - vr = "UR"; - } - else - { - vr = GetVRName(isSequence, dictionary, *it); - } - - node.append_attribute("vr").set_value(vr.c_str()); - - if (isSequence) - { - gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ(); - if (seq.GetPointer() != NULL) - { - 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()); - - std::string childUri; - if (!bulkUri.empty()) - { - childUri = bulkUri + std::string(path) + "/" + number + "/"; - } - - DicomToXmlInternal(item, dictionary, file, seq->GetItem(i).GetNestedDataSet(), sourceEncoding, childUri); - } - } - } - else if (IsBulkData(vr)) - { - // Bulk data - if (!bulkUri.empty()) - { - pugi::xml_node value = node.append_child("BulkData"); - std::string uri = bulkUri + std::string(path); - value.append_attribute("uri").set_value(uri.c_str()); - } - } - 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, dictionary, file, *it, sourceEncoding)) - { - value.append_child(pugi::node_pcdata).set_value(tmp.c_str()); - } - } - } - } - - - static void DicomToXml(pugi::xml_document& target, - const gdcm::Dict& dictionary, - const gdcm::File* file, - const gdcm::DataSet& dicom, - const std::string& bulkUriRoot) - { - 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, file, dicom, encoding, bulkUriRoot); - - 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::File* file, - const gdcm::DataSet& dicom, - const std::string& bulkUri, - Encoding sourceEncoding) - { - target = Json::objectValue; - - for (gdcm::DataSet::ConstIterator it = dicom.Begin(); - it != dicom.End(); ++it) // "*it" represents a "gdcm::DataElement" - { - char path[16]; - sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement()); - - Json::Value node = Json::objectValue; - - bool isSequence = false; - std::string vr; - if (it->GetTag() == DICOM_TAG_RETRIEVE_URL) - { - // The VR of this attribute has changed from UT to UR. - vr = "UR"; - } - else - { - vr = GetVRName(isSequence, dictionary, *it); - } - - node["vr"] = vr.c_str(); - - if (isSequence) - { - // Deal with sequences - node["Value"] = Json::arrayValue; - - gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ(); - if (seq.GetPointer() != NULL) - { - for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++) - { - Json::Value child; - - std::string childUri; - if (!bulkUri.empty()) - { - std::string number = boost::lexical_cast<std::string>(i); - childUri = bulkUri + std::string(path) + "/" + number + "/"; - } - - DicomToJsonInternal(child, dictionary, file, seq->GetItem(i).GetNestedDataSet(), childUri, sourceEncoding); - node["Value"].append(child); - } - } - } - else if (IsBulkData(vr)) - { - // Bulk data - if (!bulkUri.empty()) - { - node["BulkDataURI"] = bulkUri + std::string(path); - } - } - else - { - // Deal with other value representations - node["Value"] = Json::arrayValue; - - std::string value; - if (ConvertDicomStringToUf8(value, dictionary, file, *it, sourceEncoding)) - { - node["Value"].append(value.c_str()); - } - } - - target[FormatTag(it->GetTag())] = node; - } - } - - - static void DicomToJson(Json::Value& target, - const gdcm::Dict& dictionary, - const gdcm::File* file, - const gdcm::DataSet& dicom, - const std::string& bulkUriRoot) - { - Encoding encoding = DetectEncoding(dicom); - DicomToJsonInternal(target, dictionary, file, dicom, bulkUriRoot, encoding); - } - - - void GenerateSingleDicomAnswer(std::string& result, - const std::string& wadoBase, - const gdcm::Dict& dictionary, - const gdcm::File* file, // Can be NULL - const gdcm::DataSet& dicom, - bool isXml, - bool isBulkAccessible) - { - std::string bulkUriRoot; - if (isBulkAccessible) - { - bulkUriRoot = GetBulkUriRoot(wadoBase, dicom); - } - - if (isXml) - { - pugi::xml_document doc; - DicomToXml(doc, dictionary, file, dicom, bulkUriRoot); - - ChunkedBufferWriter writer; - doc.save(writer, " ", pugi::format_default, pugi::encoding_utf8); - - writer.Flatten(result); - } - else - { - Json::Value v; - DicomToJson(v, dictionary, file, dicom, bulkUriRoot); - - Json::FastWriter writer; - result = writer.write(v); - } - } - - - void AnswerDicom(OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const std::string& wadoBase, - const gdcm::Dict& dictionary, - const gdcm::DataSet& dicom, - bool isXml, - bool isBulkAccessible) - { - std::string answer; - GenerateSingleDicomAnswer(answer, wadoBase, dictionary, NULL, dicom, isXml, isBulkAccessible); - OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), - isXml ? "application/dicom+xml" : "application/json"); - } -}
--- a/Core/Dicom.h Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include "Toolbox.h" - -#include <gdcmReader.h> -#include <gdcmDataSet.h> -#include <pugixml.hpp> -#include <gdcmDict.h> -#include <list> - - -namespace OrthancPlugins -{ - static const gdcm::Tag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); - static const gdcm::Tag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018); - static const gdcm::Tag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d); - static const gdcm::Tag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e); - static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150); - static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); - static const gdcm::Tag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190); - static const gdcm::Tag DICOM_TAG_FAILED_SOP_SEQUENCE(0x0008, 0x1198); - static const gdcm::Tag DICOM_TAG_FAILURE_REASON(0x0008, 0x1197); - static const gdcm::Tag DICOM_TAG_WARNING_REASON(0x0008, 0x1196); - static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_SEQUENCE(0x0008, 0x1199); - static const gdcm::Tag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050); - static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); - - class ParsedDicomFile - { - private: - gdcm::Reader reader_; - - void Setup(const std::string& dicom); - - public: - ParsedDicomFile(const OrthancPlugins::MultipartItem& item); - - ParsedDicomFile(const std::string& dicom) - { - Setup(dicom); - } - - const gdcm::File& GetFile() const - { - return reader_.GetFile(); - } - - const gdcm::DataSet& GetDataSet() const - { - return reader_.GetFile().GetDataSet(); - } - - bool GetTag(std::string& result, - const gdcm::Tag& tag, - bool stripSpaces) const; - - std::string GetTagWithDefault(const gdcm::Tag& tag, - const std::string& defaultValue, - bool stripSpaces) const; - }; - - - void GenerateSingleDicomAnswer(std::string& result, - const std::string& wadoBase, - const gdcm::Dict& dictionary, - const gdcm::File* file, // Can be NULL - const gdcm::DataSet& dicom, - bool isXml, - bool isBulkAccessible); - - void AnswerDicom(OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const std::string& wadoBase, - const gdcm::Dict& dictionary, - const gdcm::DataSet& dicom, - bool isXml, - bool isBulkAccessible); -}
--- a/Core/DicomResults.cpp Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/** - * 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 "DicomResults.h" - -#include "Dicom.h" - -namespace OrthancPlugins -{ - DicomResults::DicomResults(OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const std::string& wadoBase, - const gdcm::Dict& dictionary, - bool isXml, - bool isBulkAccessible) : - context_(context), - output_(output), - wadoBase_(wadoBase), - dictionary_(dictionary), - isFirst_(true), - isXml_(isXml), - isBulkAccessible_(isBulkAccessible) - { - if (isXml_ && - OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml") != 0) - { - throw std::runtime_error("Unable to create a multipart stream of DICOM+XML answers"); - } - - jsonWriter_.AddChunk("[\n"); - } - - - void DicomResults::AddInternal(const gdcm::File* file, - const gdcm::DataSet& dicom) - { - if (isXml_) - { - std::string answer; - GenerateSingleDicomAnswer(answer, wadoBase_, dictionary_, file, dicom, true, isBulkAccessible_); - - if (OrthancPluginSendMultipartItem(context_, output_, answer.c_str(), answer.size()) != 0) - { - throw std::runtime_error("Unable to write an item to a multipart stream of DICOM+XML answers"); - } - } - else - { - if (!isFirst_) - { - jsonWriter_.AddChunk(",\n"); - } - - std::string item; - GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, false, isBulkAccessible_); - jsonWriter_.AddChunk(item); - } - - isFirst_ = false; - } - - void DicomResults::Answer() - { - if (isXml_) - { - // Nothing to do in this case - } - else - { - jsonWriter_.AddChunk("]\n"); - - std::string answer; - jsonWriter_.Flatten(answer); - OrthancPluginAnswerBuffer(context_, output_, answer.c_str(), answer.size(), "application/json"); - } - } -}
--- a/Core/DicomResults.h Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include "ChunkedBuffer.h" - -#include <orthanc/OrthancCPlugin.h> -#include <gdcmDataSet.h> -#include <gdcmDict.h> -#include <gdcmFile.h> - -namespace OrthancPlugins -{ - class DicomResults - { - private: - OrthancPluginContext* context_; - OrthancPluginRestOutput* output_; - std::string wadoBase_; - const gdcm::Dict& dictionary_; - ChunkedBuffer jsonWriter_; // Used for JSON output - bool isFirst_; - bool isXml_; - bool isBulkAccessible_; - - void AddInternal(const gdcm::File* file, - const gdcm::DataSet& dicom); - - public: - DicomResults(OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const std::string& wadoBase, - const gdcm::Dict& dictionary, - bool isXml, - bool isBulkAccessible); - - void Add(const gdcm::File& file) - { - AddInternal(&file, file.GetDataSet()); - } - - void Add(const gdcm::File& file, - const gdcm::DataSet& subset) - { - AddInternal(&file, subset); - } - - void Answer(); - }; -}
--- a/Core/Toolbox.cpp Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,433 +0,0 @@ -/** - * 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 "Toolbox.h" - -#include <string> -#include <algorithm> -#include <boost/regex.hpp> -#include <boost/locale.hpp> -#include <json/reader.h> - - -namespace OrthancPlugins -{ - void ToLowerCase(std::string& s) - { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - } - - - void ToUpperCase(std::string& s) - { - std::transform(s.begin(), s.end(), s.begin(), ::toupper); - } - - - std::string StripSpaces(const std::string& source) - { - size_t first = 0; - - while (first < source.length() && - isspace(source[first])) - { - first++; - } - - if (first == source.length()) - { - // String containing only spaces - return ""; - } - - size_t last = source.length(); - while (last > first && - (isspace(source[last - 1]) || - source[last - 1] == '\0')) - { - last--; - } - - assert(first <= last); - return source.substr(first, last - first); - } - - - - void TokenizeString(std::vector<std::string>& result, - const std::string& value, - char separator) - { - result.clear(); - - std::string currentItem; - - for (size_t i = 0; i < value.size(); i++) - { - if (value[i] == separator) - { - result.push_back(currentItem); - currentItem.clear(); - } - else - { - currentItem.push_back(value[i]); - } - } - - result.push_back(currentItem); - } - - - - void ParseContentType(std::string& application, - std::map<std::string, std::string>& attributes, - const std::string& header) - { - application.clear(); - attributes.clear(); - - std::vector<std::string> tokens; - TokenizeString(tokens, header, ';'); - - assert(tokens.size() > 0); - application = tokens[0]; - StripSpaces(application); - ToLowerCase(application); - - boost::regex pattern("\\s*([^=]+)\\s*=\\s*([^=]+)\\s*"); - - for (size_t i = 1; i < tokens.size(); i++) - { - boost::cmatch what; - if (boost::regex_match(tokens[i].c_str(), what, pattern)) - { - std::string key(what[1]); - std::string value(what[2]); - ToLowerCase(key); - attributes[key] = value; - } - } - } - - - bool LookupHttpHeader(std::string& value, - const OrthancPluginHttpRequest* request, - const std::string& header) - { - value.clear(); - - for (uint32_t i = 0; i < request->headersCount; i++) - { - std::string s = request->headersKeys[i]; - ToLowerCase(s); - if (s == header) - { - value = request->headersValues[i]; - return true; - } - } - - return false; - } - - - - void ParseMultipartBody(std::vector<MultipartItem>& result, - const char* body, - const uint64_t bodySize, - const std::string& boundary) - { - result.clear(); - - boost::regex header("\r?(\n?)--" + boundary + "(--|.*\r?\n\r?\n)"); - boost::regex pattern(".*^Content-Type\\s*:\\s*([^\\s]*).*", - boost::regex::icase /* case insensitive */); - - boost::cmatch what; - boost::match_flag_type flags = (boost::match_perl | - boost::match_not_dot_null); - const char* start = body; - const char* end = body + bodySize; - std::string currentType; - - while (boost::regex_search(start, end, what, header, flags)) - { - if (start != body) - { - MultipartItem item; - item.data_ = start; - item.size_ = what[0].first - start; - item.contentType_ = currentType; - - result.push_back(item); - } - - boost::cmatch contentType; - if (boost::regex_match(what[0].first, what[0].second, contentType, pattern)) - { - currentType = contentType[1]; - } - else - { - currentType.clear(); - } - - start = what[0].second; - flags |= boost::match_prev_avail; - } - } - - - bool RestApiGetString(std::string& result, - OrthancPluginContext* context, - const std::string& uri) - { - OrthancPluginMemoryBuffer buffer; - int code = OrthancPluginRestApiGet(context, &buffer, uri.c_str()); - if (code) - { - // Error - return false; - } - - bool ok = true; - - try - { - if (buffer.size) - { - result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size); - } - else - { - result.clear(); - } - } - catch (std::bad_alloc&) - { - ok = false; - } - - OrthancPluginFreeMemoryBuffer(context, &buffer); - - return ok; - } - - - bool RestApiGetJson(Json::Value& result, - OrthancPluginContext* context, - const std::string& uri) - { - std::string content; - RestApiGetString(content, context, uri); - - Json::Reader reader; - return reader.parse(content, result); - } - - - std::string ConvertToAscii(const std::string& source) - { - std::string result; - - result.reserve(source.size() + 1); - for (size_t i = 0; i < source.size(); i++) - { - if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i])) - { - result.push_back(source[i]); - } - } - - return result; - } - - - std::string ConvertToUtf8(const std::string& source, - const Encoding sourceEncoding) - { - const char* encoding; - - // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html - switch (sourceEncoding) - { - case Encoding_Utf8: - // Already in UTF-8: No conversion is required - return source; - - case Encoding_Unknown: - case Encoding_Ascii: - return ConvertToAscii(source); - - case Encoding_Latin1: - encoding = "ISO-8859-1"; - break; - - case Encoding_Latin2: - encoding = "ISO-8859-2"; - break; - - case Encoding_Latin3: - encoding = "ISO-8859-3"; - break; - - case Encoding_Latin4: - encoding = "ISO-8859-4"; - break; - - case Encoding_Latin5: - encoding = "ISO-8859-9"; - break; - - case Encoding_Cyrillic: - encoding = "ISO-8859-5"; - break; - - case Encoding_Arabic: - encoding = "ISO-8859-6"; - break; - - case Encoding_Greek: - encoding = "ISO-8859-7"; - break; - - case Encoding_Hebrew: - encoding = "ISO-8859-8"; - break; - - case Encoding_Japanese: - encoding = "SHIFT-JIS"; - break; - - case Encoding_Chinese: - encoding = "GB18030"; - break; - - case Encoding_Thai: - encoding = "TIS620.2533-0"; - break; - - default: - throw std::runtime_error("Unsupported encoding"); - } - - try - { - return boost::locale::conv::to_utf<char>(source, encoding); - } - catch (std::runtime_error&) - { - // Bad input string or bad encoding - return ConvertToAscii(source); - } - } - - - Encoding GetDicomEncoding(const char* specificCharacterSet) - { - std::string s = specificCharacterSet; - ToUpperCase(s); - - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java - if (s == "ISO_IR 6" || - s == "ISO_IR 192" || - s == "ISO 2022 IR 6") - { - return Encoding_Utf8; - } - else if (s == "ISO_IR 100" || - s == "ISO 2022 IR 100") - { - return Encoding_Latin1; - } - else if (s == "ISO_IR 101" || - s == "ISO 2022 IR 101") - { - return Encoding_Latin2; - } - else if (s == "ISO_IR 109" || - s == "ISO 2022 IR 109") - { - return Encoding_Latin3; - } - else if (s == "ISO_IR 110" || - s == "ISO 2022 IR 110") - { - return Encoding_Latin4; - } - else if (s == "ISO_IR 148" || - s == "ISO 2022 IR 148") - { - return Encoding_Latin5; - } - else if (s == "ISO_IR 144" || - s == "ISO 2022 IR 144") - { - return Encoding_Cyrillic; - } - else if (s == "ISO_IR 127" || - s == "ISO 2022 IR 127") - { - return Encoding_Arabic; - } - else if (s == "ISO_IR 126" || - s == "ISO 2022 IR 126") - { - return Encoding_Greek; - } - else if (s == "ISO_IR 138" || - s == "ISO 2022 IR 138") - { - return Encoding_Hebrew; - } - else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166") - { - return Encoding_Thai; - } - else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13") - { - return Encoding_Japanese; - } - else if (s == "GB18030") - { - return Encoding_Chinese; - } - /* - else if (s == "ISO 2022 IR 149") - { - TODO - } - else if (s == "ISO 2022 IR 159") - { - TODO - } - else if (s == "ISO 2022 IR 87") - { - TODO - } - */ - else - { - return Encoding_Unknown; - } - } -}
--- a/Core/Toolbox.h Fri Jul 31 13:36:02 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/** - * 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/>. - **/ - - -#pragma once - -#include <orthanc/OrthancCPlugin.h> -#include <string> -#include <vector> -#include <json/value.h> -#include <map> - -namespace OrthancPlugins -{ - struct MultipartItem - { - const char* data_; - size_t size_; - std::string contentType_; - }; - - // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ - enum Encoding - { - Encoding_Unknown, - Encoding_Ascii, - Encoding_Utf8, - Encoding_Latin1, - Encoding_Latin2, - Encoding_Latin3, - Encoding_Latin4, - Encoding_Latin5, // Turkish - Encoding_Cyrillic, - Encoding_Arabic, - Encoding_Greek, - Encoding_Hebrew, - Encoding_Thai, // TIS 620-2533 - Encoding_Japanese, // JIS X 0201 (Shift JIS): Katakana - Encoding_Chinese // GB18030 - Chinese simplified - //Encoding_JapaneseKanji, // Multibyte - JIS X 0208: Kanji - //Encoding_JapaneseSupplementaryKanji, // Multibyte - JIS X 0212: Supplementary Kanji set - //Encoding_Korean, // Multibyte - KS X 1001: Hangul and Hanja - }; - - void ToLowerCase(std::string& s); - - void ToUpperCase(std::string& s); - - std::string StripSpaces(const std::string& source); - - void TokenizeString(std::vector<std::string>& result, - const std::string& source, - char separator); - - void ParseContentType(std::string& application, - std::map<std::string, std::string>& attributes, - const std::string& header); - - bool LookupHttpHeader(std::string& value, - const OrthancPluginHttpRequest* request, - const std::string& header); - - void ParseMultipartBody(std::vector<MultipartItem>& result, - const char* body, - const uint64_t bodySize, - const std::string& boundary); - - bool RestApiGetString(std::string& result, - OrthancPluginContext* context, - const std::string& uri); - - bool RestApiGetJson(Json::Value& result, - OrthancPluginContext* context, - const std::string& uri); - - std::string ConvertToAscii(const std::string& source); - - std::string ConvertToUtf8(const std::string& source, - const Encoding sourceEncoding); - - Encoding GetDicomEncoding(const char* specificCharacterSet); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ChunkedBuffer.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,86 @@ +/** + * 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 "ChunkedBuffer.h" + +#include <cassert> +#include <string.h> + + +namespace OrthancPlugins +{ + void ChunkedBuffer::Clear() + { + numBytes_ = 0; + + for (Chunks::iterator it = chunks_.begin(); + it != chunks_.end(); ++it) + { + delete *it; + } + } + + + void ChunkedBuffer::AddChunk(const char* chunkData, + size_t chunkSize) + { + if (chunkSize == 0) + { + return; + } + + assert(chunkData != NULL); + chunks_.push_back(new std::string(chunkData, chunkSize)); + numBytes_ += chunkSize; + } + + + void ChunkedBuffer::AddChunk(const std::string& chunk) + { + if (chunk.size() > 0) + { + AddChunk(&chunk[0], chunk.size()); + } + } + + + void ChunkedBuffer::Flatten(std::string& result) + { + result.resize(numBytes_); + + size_t pos = 0; + for (Chunks::iterator it = chunks_.begin(); + it != chunks_.end(); ++it) + { + assert(*it != NULL); + + size_t s = (*it)->size(); + if (s != 0) + { + memcpy(&result[pos], (*it)->c_str(), s); + pos += s; + } + + delete *it; + } + + chunks_.clear(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/ChunkedBuffer.h Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,59 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include <list> +#include <string> + +namespace OrthancPlugins +{ + class ChunkedBuffer + { + private: + typedef std::list<std::string*> Chunks; + size_t numBytes_; + Chunks chunks_; + + void Clear(); + + public: + ChunkedBuffer() : numBytes_(0) + { + } + + ~ChunkedBuffer() + { + Clear(); + } + + size_t GetNumBytes() const + { + return numBytes_; + } + + void AddChunk(const char* chunkData, + size_t chunkSize); + + void AddChunk(const std::string& chunk); + + void Flatten(std::string& result); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Configuration.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,148 @@ +/** + * 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 "Configuration.h" + +#include "Toolbox.h" + +#include <fstream> +#include <json/reader.h> + +namespace OrthancPlugins +{ + namespace Configuration + { + bool Read(Json::Value& configuration, + OrthancPluginContext* context) + { + std::string s; + + { + char* tmp = OrthancPluginGetConfiguration(context); + if (tmp == NULL) + { + OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc"); + return false; + } + + s.assign(tmp); + OrthancPluginFreeString(context, tmp); + } + + Json::Reader reader; + if (reader.parse(s, configuration)) + { + return true; + } + else + { + OrthancPluginLogError(context, "Unable to parse the configuration"); + return false; + } + } + + + std::string GetStringValue(const Json::Value& configuration, + const std::string& key, + const std::string& defaultValue) + { + if (configuration.type() != Json::objectValue || + !configuration.isMember(key) || + configuration[key].type() != Json::stringValue) + { + return defaultValue; + } + else + { + return configuration[key].asString(); + } + } + + + bool GetBoolValue(const Json::Value& configuration, + const std::string& key, + bool defaultValue) + { + if (configuration.type() != Json::objectValue || + !configuration.isMember(key) || + configuration[key].type() != Json::booleanValue) + { + return defaultValue; + } + else + { + return configuration[key].asBool(); + } + } + + + std::string GetRoot(const Json::Value& configuration) + { + std::string root; + + if (configuration.isMember("DicomWeb")) + { + root = GetStringValue(configuration["DicomWeb"], "Root", ""); + } + + if (root.empty()) + { + root = "/dicom-web/"; + } + + // Make sure the root URI starts and ends with a slash + if (root[0] != '/') + { + root = "/" + root; + } + + if (root[root.length() - 1] != '/') + { + root += "/"; + } + + return root; + } + + + std::string GetBaseUrl(const Json::Value& configuration, + const OrthancPluginHttpRequest* request) + { + std::string host; + bool ssl = false; + + if (configuration.isMember("DicomWeb")) + { + host = GetStringValue(configuration["DicomWeb"], "Host", ""); + ssl = GetBoolValue(configuration["DicomWeb"], "Ssl", false); + } + + if (host.empty() && + !LookupHttpHeader(host, request, "host")) + { + // Should never happen: The "host" header should always be present + // in HTTP requests. Provide a default value anyway. + host = "localhost:8042"; + } + + return (ssl ? "https://" : "http://") + host + GetRoot(configuration); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Configuration.h Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,46 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include <orthanc/OrthancCPlugin.h> +#include <json/value.h> + +namespace OrthancPlugins +{ + namespace Configuration + { + bool Read(Json::Value& configuration, + OrthancPluginContext* context); + + std::string GetStringValue(const Json::Value& configuration, + const std::string& key, + const std::string& defaultValue); + + bool GetBoolValue(const Json::Value& configuration, + const std::string& key, + bool defaultValue); + + std::string GetRoot(const Json::Value& configuration); + + std::string GetBaseUrl(const Json::Value& configuration, + const OrthancPluginHttpRequest* request); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Dicom.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,563 @@ +/** + * 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 <gdcmStringFilter.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); + } + + + static bool GetTag(std::string& result, + const gdcm::DataSet& dataset, + const gdcm::Tag& tag, + bool stripSpaces) + { + 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; + } + + + static std::string GetTagWithDefault(const gdcm::DataSet& dataset, + const gdcm::Tag& tag, + const std::string& defaultValue, + bool stripSpaces) + { + std::string result; + if (!GetTag(result, dataset, tag, false)) + { + result = defaultValue; + } + + if (stripSpaces) + { + result = StripSpaces(result); + } + + return result; + } + + + bool ParsedDicomFile::GetTag(std::string& result, + const gdcm::Tag& tag, + bool stripSpaces) const + { + return OrthancPlugins::GetTag(result, GetDataSet(), tag, stripSpaces); + } + + + std::string ParsedDicomFile::GetTagWithDefault(const gdcm::Tag& tag, + const std::string& defaultValue, + bool stripSpaces) const + { + return OrthancPlugins::GetTagWithDefault(GetDataSet(), tag, defaultValue, stripSpaces); + } + + + 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)); + return NULL; + } + + + + 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(); + + if (vr == gdcm::VR::OB_OW) + { + vr = gdcm::VR::OB; + } + } + + isSequence = (vr == gdcm::VR::SQ); + + const char* str = gdcm::VR::GetVRString(vr); + if (isSequence) + { + return str; + } + + if (str == NULL || + strlen(str) != 2 || + !(str[0] >= 'A' && str[0] <= 'Z') || + !(str[1] >= 'A' && str[1] <= 'Z')) + { + return "UN"; + } + else + { + return str; + } + } + + + static bool IsBulkData(const std::string& vr) + { + /** + * Full list of VR (Value Representations) that are admissible for + * being retrieved as bulk data. We commented out some of them, as + * they correspond to strings and not to binary data. + **/ + return (//vr == "FL" || + //vr == "FD" || + //vr == "IS" || + vr == "LT" || + vr == "OB" || + vr == "OD" || + vr == "OF" || + vr == "OW" || + //vr == "SL" || + //vr == "SS" || + //vr == "ST" || + //vr == "UL" || + vr == "UN" || + //vr == "US" || + vr == "UT"); + } + + + static std::string GetBulkUriRoot(const std::string& wadoBase, + const gdcm::DataSet& dicom) + { + std::string study, series, instance; + + if (!GetTag(study, dicom, DICOM_TAG_STUDY_INSTANCE_UID, true) || + !GetTag(series, dicom, DICOM_TAG_SERIES_INSTANCE_UID, true) || + !GetTag(instance, dicom, DICOM_TAG_SOP_INSTANCE_UID, true)) + { + return ""; + } + else + { + return wadoBase + "studies/" + study + "/series/" + series + "/instances/" + instance + "/bulk/"; + } + } + + + 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()); + tmp = StripSpaces(tmp); + + return GetDicomEncoding(tmp.c_str()); + } + + + static bool ConvertDicomStringToUf8(std::string& result, + const gdcm::Dict& dictionary, + const gdcm::File* file, + const gdcm::DataElement& element, + const Encoding sourceEncoding) + { + const gdcm::ByteValue* data = element.GetByteValue(); + if (!data) + { + return false; + } + + if (file != NULL) + { + bool isSequence; + std::string vr = GetVRName(isSequence, dictionary, element); + if (!isSequence && ( + vr == "FL" || + vr == "FD" || + vr == "SL" || + vr == "SS" || + vr == "UL" || + vr == "US" + )) + { + gdcm::StringFilter f; + f.SetFile(*file); + result = f.ToString(element.GetTag()); + return true; + } + } + + if (sourceEncoding == Encoding_Utf8) + { + result.assign(data->GetPointer(), data->GetLength()); + } + else + { + std::string tmp(data->GetPointer(), data->GetLength()); + result = ConvertToUtf8(tmp, sourceEncoding); + } + + result = StripSpaces(result); + return true; + } + + + static void DicomToXmlInternal(pugi::xml_node& target, + const gdcm::Dict& dictionary, + const gdcm::File* file, + const gdcm::DataSet& dicom, + const Encoding sourceEncoding, + const std::string& bulkUri) + { + for (gdcm::DataSet::ConstIterator it = dicom.Begin(); + it != dicom.End(); ++it) // "*it" represents a "gdcm::DataElement" + { + char path[16]; + sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement()); + + pugi::xml_node node = target.append_child("DicomAttribute"); + node.append_attribute("tag").set_value(FormatTag(it->GetTag()).c_str()); + + const char* keyword = GetKeyword(dictionary, it->GetTag()); + if (keyword != NULL) + { + node.append_attribute("keyword").set_value(keyword); + } + + bool isSequence = false; + std::string vr; + if (it->GetTag() == DICOM_TAG_RETRIEVE_URL) + { + // The VR of this attribute has changed from UT to UR. + vr = "UR"; + } + else + { + vr = GetVRName(isSequence, dictionary, *it); + } + + node.append_attribute("vr").set_value(vr.c_str()); + + if (isSequence) + { + gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ(); + if (seq.GetPointer() != NULL) + { + 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()); + + std::string childUri; + if (!bulkUri.empty()) + { + childUri = bulkUri + std::string(path) + "/" + number + "/"; + } + + DicomToXmlInternal(item, dictionary, file, seq->GetItem(i).GetNestedDataSet(), sourceEncoding, childUri); + } + } + } + else if (IsBulkData(vr)) + { + // Bulk data + if (!bulkUri.empty()) + { + pugi::xml_node value = node.append_child("BulkData"); + std::string uri = bulkUri + std::string(path); + value.append_attribute("uri").set_value(uri.c_str()); + } + } + 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, dictionary, file, *it, sourceEncoding)) + { + value.append_child(pugi::node_pcdata).set_value(tmp.c_str()); + } + } + } + } + + + static void DicomToXml(pugi::xml_document& target, + const gdcm::Dict& dictionary, + const gdcm::File* file, + const gdcm::DataSet& dicom, + const std::string& bulkUriRoot) + { + 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, file, dicom, encoding, bulkUriRoot); + + 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::File* file, + const gdcm::DataSet& dicom, + const std::string& bulkUri, + Encoding sourceEncoding) + { + target = Json::objectValue; + + for (gdcm::DataSet::ConstIterator it = dicom.Begin(); + it != dicom.End(); ++it) // "*it" represents a "gdcm::DataElement" + { + char path[16]; + sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement()); + + Json::Value node = Json::objectValue; + + bool isSequence = false; + std::string vr; + if (it->GetTag() == DICOM_TAG_RETRIEVE_URL) + { + // The VR of this attribute has changed from UT to UR. + vr = "UR"; + } + else + { + vr = GetVRName(isSequence, dictionary, *it); + } + + node["vr"] = vr.c_str(); + + if (isSequence) + { + // Deal with sequences + node["Value"] = Json::arrayValue; + + gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ(); + if (seq.GetPointer() != NULL) + { + for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++) + { + Json::Value child; + + std::string childUri; + if (!bulkUri.empty()) + { + std::string number = boost::lexical_cast<std::string>(i); + childUri = bulkUri + std::string(path) + "/" + number + "/"; + } + + DicomToJsonInternal(child, dictionary, file, seq->GetItem(i).GetNestedDataSet(), childUri, sourceEncoding); + node["Value"].append(child); + } + } + } + else if (IsBulkData(vr)) + { + // Bulk data + if (!bulkUri.empty()) + { + node["BulkDataURI"] = bulkUri + std::string(path); + } + } + else + { + // Deal with other value representations + node["Value"] = Json::arrayValue; + + std::string value; + if (ConvertDicomStringToUf8(value, dictionary, file, *it, sourceEncoding)) + { + node["Value"].append(value.c_str()); + } + } + + target[FormatTag(it->GetTag())] = node; + } + } + + + static void DicomToJson(Json::Value& target, + const gdcm::Dict& dictionary, + const gdcm::File* file, + const gdcm::DataSet& dicom, + const std::string& bulkUriRoot) + { + Encoding encoding = DetectEncoding(dicom); + DicomToJsonInternal(target, dictionary, file, dicom, bulkUriRoot, encoding); + } + + + void GenerateSingleDicomAnswer(std::string& result, + const std::string& wadoBase, + const gdcm::Dict& dictionary, + const gdcm::File* file, // Can be NULL + const gdcm::DataSet& dicom, + bool isXml, + bool isBulkAccessible) + { + std::string bulkUriRoot; + if (isBulkAccessible) + { + bulkUriRoot = GetBulkUriRoot(wadoBase, dicom); + } + + if (isXml) + { + pugi::xml_document doc; + DicomToXml(doc, dictionary, file, dicom, bulkUriRoot); + + ChunkedBufferWriter writer; + doc.save(writer, " ", pugi::format_default, pugi::encoding_utf8); + + writer.Flatten(result); + } + else + { + Json::Value v; + DicomToJson(v, dictionary, file, dicom, bulkUriRoot); + + Json::FastWriter writer; + result = writer.write(v); + } + } + + + void AnswerDicom(OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const std::string& wadoBase, + const gdcm::Dict& dictionary, + const gdcm::DataSet& dicom, + bool isXml, + bool isBulkAccessible) + { + std::string answer; + GenerateSingleDicomAnswer(answer, wadoBase, dictionary, NULL, dicom, isXml, isBulkAccessible); + OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), + isXml ? "application/dicom+xml" : "application/json"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Dicom.h Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,98 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include "Toolbox.h" + +#include <gdcmReader.h> +#include <gdcmDataSet.h> +#include <pugixml.hpp> +#include <gdcmDict.h> +#include <list> + + +namespace OrthancPlugins +{ + static const gdcm::Tag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); + static const gdcm::Tag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018); + static const gdcm::Tag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d); + static const gdcm::Tag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e); + static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150); + static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); + static const gdcm::Tag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190); + static const gdcm::Tag DICOM_TAG_FAILED_SOP_SEQUENCE(0x0008, 0x1198); + static const gdcm::Tag DICOM_TAG_FAILURE_REASON(0x0008, 0x1197); + static const gdcm::Tag DICOM_TAG_WARNING_REASON(0x0008, 0x1196); + static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_SEQUENCE(0x0008, 0x1199); + static const gdcm::Tag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050); + static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); + + class ParsedDicomFile + { + private: + gdcm::Reader reader_; + + void Setup(const std::string& dicom); + + public: + ParsedDicomFile(const OrthancPlugins::MultipartItem& item); + + ParsedDicomFile(const std::string& dicom) + { + Setup(dicom); + } + + const gdcm::File& GetFile() const + { + return reader_.GetFile(); + } + + const gdcm::DataSet& GetDataSet() const + { + return reader_.GetFile().GetDataSet(); + } + + bool GetTag(std::string& result, + const gdcm::Tag& tag, + bool stripSpaces) const; + + std::string GetTagWithDefault(const gdcm::Tag& tag, + const std::string& defaultValue, + bool stripSpaces) const; + }; + + + void GenerateSingleDicomAnswer(std::string& result, + const std::string& wadoBase, + const gdcm::Dict& dictionary, + const gdcm::File* file, // Can be NULL + const gdcm::DataSet& dicom, + bool isXml, + bool isBulkAccessible); + + void AnswerDicom(OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const std::string& wadoBase, + const gdcm::Dict& dictionary, + const gdcm::DataSet& dicom, + bool isXml, + bool isBulkAccessible); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/DicomResults.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,94 @@ +/** + * 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 "DicomResults.h" + +#include "Dicom.h" + +namespace OrthancPlugins +{ + DicomResults::DicomResults(OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const std::string& wadoBase, + const gdcm::Dict& dictionary, + bool isXml, + bool isBulkAccessible) : + context_(context), + output_(output), + wadoBase_(wadoBase), + dictionary_(dictionary), + isFirst_(true), + isXml_(isXml), + isBulkAccessible_(isBulkAccessible) + { + if (isXml_ && + OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml") != 0) + { + throw std::runtime_error("Unable to create a multipart stream of DICOM+XML answers"); + } + + jsonWriter_.AddChunk("[\n"); + } + + + void DicomResults::AddInternal(const gdcm::File* file, + const gdcm::DataSet& dicom) + { + if (isXml_) + { + std::string answer; + GenerateSingleDicomAnswer(answer, wadoBase_, dictionary_, file, dicom, true, isBulkAccessible_); + + if (OrthancPluginSendMultipartItem(context_, output_, answer.c_str(), answer.size()) != 0) + { + throw std::runtime_error("Unable to write an item to a multipart stream of DICOM+XML answers"); + } + } + else + { + if (!isFirst_) + { + jsonWriter_.AddChunk(",\n"); + } + + std::string item; + GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, false, isBulkAccessible_); + jsonWriter_.AddChunk(item); + } + + isFirst_ = false; + } + + void DicomResults::Answer() + { + if (isXml_) + { + // Nothing to do in this case + } + else + { + jsonWriter_.AddChunk("]\n"); + + std::string answer; + jsonWriter_.Flatten(answer); + OrthancPluginAnswerBuffer(context_, output_, answer.c_str(), answer.size(), "application/json"); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/DicomResults.h Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,68 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include "ChunkedBuffer.h" + +#include <orthanc/OrthancCPlugin.h> +#include <gdcmDataSet.h> +#include <gdcmDict.h> +#include <gdcmFile.h> + +namespace OrthancPlugins +{ + class DicomResults + { + private: + OrthancPluginContext* context_; + OrthancPluginRestOutput* output_; + std::string wadoBase_; + const gdcm::Dict& dictionary_; + ChunkedBuffer jsonWriter_; // Used for JSON output + bool isFirst_; + bool isXml_; + bool isBulkAccessible_; + + void AddInternal(const gdcm::File* file, + const gdcm::DataSet& dicom); + + public: + DicomResults(OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const std::string& wadoBase, + const gdcm::Dict& dictionary, + bool isXml, + bool isBulkAccessible); + + void Add(const gdcm::File& file) + { + AddInternal(&file, file.GetDataSet()); + } + + void Add(const gdcm::File& file, + const gdcm::DataSet& subset) + { + AddInternal(&file, subset); + } + + void Answer(); + }; +}
--- a/Plugin/Plugin.cpp Fri Jul 31 13:36:02 2015 +0200 +++ b/Plugin/Plugin.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -23,7 +23,7 @@ #include "QidoRs.h" #include "StowRs.h" #include "WadoRs.h" -#include "../Core/Configuration.h" +#include "Configuration.h" #include <gdcmDictEntry.h>
--- a/Plugin/QidoRs.cpp Fri Jul 31 13:36:02 2015 +0200 +++ b/Plugin/QidoRs.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -22,10 +22,10 @@ #include "Plugin.h" #include "StowRs.h" // For IsXmlExpected() -#include "../Core/Dicom.h" -#include "../Core/DicomResults.h" -#include "../Core/Toolbox.h" -#include "../Core/Configuration.h" +#include "Dicom.h" +#include "DicomResults.h" +#include "Toolbox.h" +#include "Configuration.h" #include <gdcmTag.h> #include <list>
--- a/Plugin/StowRs.cpp Fri Jul 31 13:36:02 2015 +0200 +++ b/Plugin/StowRs.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -21,8 +21,8 @@ #include "StowRs.h" #include "Plugin.h" -#include "../Core/Configuration.h" -#include "../Core/Dicom.h" +#include "Configuration.h" +#include "Dicom.h" static void SetTag(gdcm::DataSet& dataset,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Toolbox.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,433 @@ +/** + * 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 "Toolbox.h" + +#include <string> +#include <algorithm> +#include <boost/regex.hpp> +#include <boost/locale.hpp> +#include <json/reader.h> + + +namespace OrthancPlugins +{ + void ToLowerCase(std::string& s) + { + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + } + + + void ToUpperCase(std::string& s) + { + std::transform(s.begin(), s.end(), s.begin(), ::toupper); + } + + + std::string StripSpaces(const std::string& source) + { + size_t first = 0; + + while (first < source.length() && + isspace(source[first])) + { + first++; + } + + if (first == source.length()) + { + // String containing only spaces + return ""; + } + + size_t last = source.length(); + while (last > first && + (isspace(source[last - 1]) || + source[last - 1] == '\0')) + { + last--; + } + + assert(first <= last); + return source.substr(first, last - first); + } + + + + void TokenizeString(std::vector<std::string>& result, + const std::string& value, + char separator) + { + result.clear(); + + std::string currentItem; + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == separator) + { + result.push_back(currentItem); + currentItem.clear(); + } + else + { + currentItem.push_back(value[i]); + } + } + + result.push_back(currentItem); + } + + + + void ParseContentType(std::string& application, + std::map<std::string, std::string>& attributes, + const std::string& header) + { + application.clear(); + attributes.clear(); + + std::vector<std::string> tokens; + TokenizeString(tokens, header, ';'); + + assert(tokens.size() > 0); + application = tokens[0]; + StripSpaces(application); + ToLowerCase(application); + + boost::regex pattern("\\s*([^=]+)\\s*=\\s*([^=]+)\\s*"); + + for (size_t i = 1; i < tokens.size(); i++) + { + boost::cmatch what; + if (boost::regex_match(tokens[i].c_str(), what, pattern)) + { + std::string key(what[1]); + std::string value(what[2]); + ToLowerCase(key); + attributes[key] = value; + } + } + } + + + bool LookupHttpHeader(std::string& value, + const OrthancPluginHttpRequest* request, + const std::string& header) + { + value.clear(); + + for (uint32_t i = 0; i < request->headersCount; i++) + { + std::string s = request->headersKeys[i]; + ToLowerCase(s); + if (s == header) + { + value = request->headersValues[i]; + return true; + } + } + + return false; + } + + + + void ParseMultipartBody(std::vector<MultipartItem>& result, + const char* body, + const uint64_t bodySize, + const std::string& boundary) + { + result.clear(); + + boost::regex header("\r?(\n?)--" + boundary + "(--|.*\r?\n\r?\n)"); + boost::regex pattern(".*^Content-Type\\s*:\\s*([^\\s]*).*", + boost::regex::icase /* case insensitive */); + + boost::cmatch what; + boost::match_flag_type flags = (boost::match_perl | + boost::match_not_dot_null); + const char* start = body; + const char* end = body + bodySize; + std::string currentType; + + while (boost::regex_search(start, end, what, header, flags)) + { + if (start != body) + { + MultipartItem item; + item.data_ = start; + item.size_ = what[0].first - start; + item.contentType_ = currentType; + + result.push_back(item); + } + + boost::cmatch contentType; + if (boost::regex_match(what[0].first, what[0].second, contentType, pattern)) + { + currentType = contentType[1]; + } + else + { + currentType.clear(); + } + + start = what[0].second; + flags |= boost::match_prev_avail; + } + } + + + bool RestApiGetString(std::string& result, + OrthancPluginContext* context, + const std::string& uri) + { + OrthancPluginMemoryBuffer buffer; + int code = OrthancPluginRestApiGet(context, &buffer, uri.c_str()); + if (code) + { + // Error + return false; + } + + bool ok = true; + + try + { + if (buffer.size) + { + result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size); + } + else + { + result.clear(); + } + } + catch (std::bad_alloc&) + { + ok = false; + } + + OrthancPluginFreeMemoryBuffer(context, &buffer); + + return ok; + } + + + bool RestApiGetJson(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri) + { + std::string content; + RestApiGetString(content, context, uri); + + Json::Reader reader; + return reader.parse(content, result); + } + + + std::string ConvertToAscii(const std::string& source) + { + std::string result; + + result.reserve(source.size() + 1); + for (size_t i = 0; i < source.size(); i++) + { + if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i])) + { + result.push_back(source[i]); + } + } + + return result; + } + + + std::string ConvertToUtf8(const std::string& source, + const Encoding sourceEncoding) + { + const char* encoding; + + // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html + switch (sourceEncoding) + { + case Encoding_Utf8: + // Already in UTF-8: No conversion is required + return source; + + case Encoding_Unknown: + case Encoding_Ascii: + return ConvertToAscii(source); + + case Encoding_Latin1: + encoding = "ISO-8859-1"; + break; + + case Encoding_Latin2: + encoding = "ISO-8859-2"; + break; + + case Encoding_Latin3: + encoding = "ISO-8859-3"; + break; + + case Encoding_Latin4: + encoding = "ISO-8859-4"; + break; + + case Encoding_Latin5: + encoding = "ISO-8859-9"; + break; + + case Encoding_Cyrillic: + encoding = "ISO-8859-5"; + break; + + case Encoding_Arabic: + encoding = "ISO-8859-6"; + break; + + case Encoding_Greek: + encoding = "ISO-8859-7"; + break; + + case Encoding_Hebrew: + encoding = "ISO-8859-8"; + break; + + case Encoding_Japanese: + encoding = "SHIFT-JIS"; + break; + + case Encoding_Chinese: + encoding = "GB18030"; + break; + + case Encoding_Thai: + encoding = "TIS620.2533-0"; + break; + + default: + throw std::runtime_error("Unsupported encoding"); + } + + try + { + return boost::locale::conv::to_utf<char>(source, encoding); + } + catch (std::runtime_error&) + { + // Bad input string or bad encoding + return ConvertToAscii(source); + } + } + + + Encoding GetDicomEncoding(const char* specificCharacterSet) + { + std::string s = specificCharacterSet; + ToUpperCase(s); + + // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java + if (s == "ISO_IR 6" || + s == "ISO_IR 192" || + s == "ISO 2022 IR 6") + { + return Encoding_Utf8; + } + else if (s == "ISO_IR 100" || + s == "ISO 2022 IR 100") + { + return Encoding_Latin1; + } + else if (s == "ISO_IR 101" || + s == "ISO 2022 IR 101") + { + return Encoding_Latin2; + } + else if (s == "ISO_IR 109" || + s == "ISO 2022 IR 109") + { + return Encoding_Latin3; + } + else if (s == "ISO_IR 110" || + s == "ISO 2022 IR 110") + { + return Encoding_Latin4; + } + else if (s == "ISO_IR 148" || + s == "ISO 2022 IR 148") + { + return Encoding_Latin5; + } + else if (s == "ISO_IR 144" || + s == "ISO 2022 IR 144") + { + return Encoding_Cyrillic; + } + else if (s == "ISO_IR 127" || + s == "ISO 2022 IR 127") + { + return Encoding_Arabic; + } + else if (s == "ISO_IR 126" || + s == "ISO 2022 IR 126") + { + return Encoding_Greek; + } + else if (s == "ISO_IR 138" || + s == "ISO 2022 IR 138") + { + return Encoding_Hebrew; + } + else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166") + { + return Encoding_Thai; + } + else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13") + { + return Encoding_Japanese; + } + else if (s == "GB18030") + { + return Encoding_Chinese; + } + /* + else if (s == "ISO 2022 IR 149") + { + TODO + } + else if (s == "ISO 2022 IR 159") + { + TODO + } + else if (s == "ISO 2022 IR 87") + { + TODO + } + */ + else + { + return Encoding_Unknown; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/Toolbox.h Sat Aug 01 17:18:21 2015 +0200 @@ -0,0 +1,98 @@ +/** + * 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/>. + **/ + + +#pragma once + +#include <orthanc/OrthancCPlugin.h> +#include <string> +#include <vector> +#include <json/value.h> +#include <map> + +namespace OrthancPlugins +{ + struct MultipartItem + { + const char* data_; + size_t size_; + std::string contentType_; + }; + + // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/ + enum Encoding + { + Encoding_Unknown, + Encoding_Ascii, + Encoding_Utf8, + Encoding_Latin1, + Encoding_Latin2, + Encoding_Latin3, + Encoding_Latin4, + Encoding_Latin5, // Turkish + Encoding_Cyrillic, + Encoding_Arabic, + Encoding_Greek, + Encoding_Hebrew, + Encoding_Thai, // TIS 620-2533 + Encoding_Japanese, // JIS X 0201 (Shift JIS): Katakana + Encoding_Chinese // GB18030 - Chinese simplified + //Encoding_JapaneseKanji, // Multibyte - JIS X 0208: Kanji + //Encoding_JapaneseSupplementaryKanji, // Multibyte - JIS X 0212: Supplementary Kanji set + //Encoding_Korean, // Multibyte - KS X 1001: Hangul and Hanja + }; + + void ToLowerCase(std::string& s); + + void ToUpperCase(std::string& s); + + std::string StripSpaces(const std::string& source); + + void TokenizeString(std::vector<std::string>& result, + const std::string& source, + char separator); + + void ParseContentType(std::string& application, + std::map<std::string, std::string>& attributes, + const std::string& header); + + bool LookupHttpHeader(std::string& value, + const OrthancPluginHttpRequest* request, + const std::string& header); + + void ParseMultipartBody(std::vector<MultipartItem>& result, + const char* body, + const uint64_t bodySize, + const std::string& boundary); + + bool RestApiGetString(std::string& result, + OrthancPluginContext* context, + const std::string& uri); + + bool RestApiGetJson(Json::Value& result, + OrthancPluginContext* context, + const std::string& uri); + + std::string ConvertToAscii(const std::string& source); + + std::string ConvertToUtf8(const std::string& source, + const Encoding sourceEncoding); + + Encoding GetDicomEncoding(const char* specificCharacterSet); +}
--- a/Plugin/WadoRs.cpp Fri Jul 31 13:36:02 2015 +0200 +++ b/Plugin/WadoRs.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -22,9 +22,9 @@ #include <boost/lexical_cast.hpp> -#include "../Core/Configuration.h" -#include "../Core/Dicom.h" -#include "../Core/DicomResults.h" +#include "Configuration.h" +#include "Dicom.h" +#include "DicomResults.h" static bool AcceptMultipartDicom(const OrthancPluginHttpRequest* request) {
--- a/UnitTestsSources/UnitTestsMain.cpp Fri Jul 31 13:36:02 2015 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Sat Aug 01 17:18:21 2015 +0200 @@ -21,7 +21,7 @@ #include <gtest/gtest.h> #include <boost/lexical_cast.hpp> -#include "../Core/Toolbox.h" +#include "../Plugin/Toolbox.h" #include "../Plugin/Plugin.h" using namespace OrthancPlugins;