Mercurial > hg > orthanc
changeset 1744:b3de74dec2d5 db-changes
integration mainline->db-changes
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 26 Oct 2015 12:30:34 +0100 |
parents | 54d78925cbb6 (current diff) 8fc1d096aa38 (diff) |
children | 38dda23c7d7d |
files | Core/DicomFormat/DicomNullValue.h Core/DicomFormat/DicomString.h OrthancServer/OrthancMoveRequestHandler.cpp OrthancServer/ServerIndex.cpp OrthancServer/ServerToolbox.cpp Plugins/Engine/OrthancPlugins.cpp UnitTestsSources/ServerIndexTests.cpp |
diffstat | 45 files changed, 1040 insertions(+), 345 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Tue Oct 20 17:39:58 2015 +0200 +++ b/CMakeLists.txt Mon Oct 26 12:30:34 2015 +0100 @@ -96,6 +96,7 @@ Core/DicomFormat/DicomImageInformation.cpp Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomInstanceHasher.cpp + Core/DicomFormat/DicomValue.cpp Core/Enumerations.cpp Core/FileStorage/FilesystemStorage.cpp Core/FileStorage/StorageAccessor.cpp
--- a/Core/DicomFormat/DicomArray.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomArray.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -63,7 +63,8 @@ for (size_t i = 0; i < elements_.size(); i++) { DicomTag t = elements_[i]->GetTag(); - std::string s = elements_[i]->GetValue().AsString(); + const DicomValue& v = elements_[i]->GetValue(); + std::string s = v.IsNull() ? "(null)" : v.GetContent(); printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str()); } }
--- a/Core/DicomFormat/DicomImageInformation.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomImageInformation.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -54,7 +54,7 @@ try { - std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).AsString(); + std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).GetContent(); Toolbox::ToUpperCase(p); if (p == "RGB") @@ -114,13 +114,13 @@ photometric_ = PhotometricInterpretation_Unknown; } - width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).AsString()); - height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).AsString()); - bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString()); + width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).GetContent()); + height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).GetContent()); + bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).GetContent()); try { - samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).AsString()); + samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).GetContent()); } catch (OrthancException&) { @@ -129,7 +129,7 @@ try { - bitsStored_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).AsString()); + bitsStored_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).GetContent()); } catch (OrthancException&) { @@ -138,7 +138,7 @@ try { - highBit_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).AsString()); + highBit_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).GetContent()); } catch (OrthancException&) { @@ -147,7 +147,7 @@ try { - pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).AsString()); + pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).GetContent()); } catch (OrthancException&) { @@ -160,7 +160,7 @@ // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/ try { - planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).AsString()); + planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).GetContent()); } catch (OrthancException&) { @@ -179,9 +179,9 @@ try { - numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString()); + numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent()); } - catch (OrthancException) + catch (OrthancException&) { // If the tag "NumberOfFrames" is absent, assume there is a single frame numberOfFrames_ = 1;
--- a/Core/DicomFormat/DicomInstanceHasher.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomInstanceHasher.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -60,10 +60,10 @@ { const DicomValue* patientId = instance.TestAndGetValue(DICOM_TAG_PATIENT_ID); - Setup(patientId == NULL ? "" : patientId->AsString(), - instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(), - instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(), - instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString()); + Setup(patientId == NULL ? "" : patientId->GetContent(), + instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(), + instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(), + instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent()); } const std::string& DicomInstanceHasher::HashPatient()
--- a/Core/DicomFormat/DicomMap.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -35,7 +35,6 @@ #include <stdio.h> #include <memory> -#include "DicomString.h" #include "DicomArray.h" #include "../OrthancException.h"
--- a/Core/DicomFormat/DicomMap.h Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomMap.h Mon Oct 26 12:30:34 2015 +0100 @@ -34,7 +34,6 @@ #include "DicomTag.h" #include "DicomValue.h" -#include "DicomString.h" #include "../Enumerations.h" #include <set> @@ -105,14 +104,14 @@ void SetValue(const DicomTag& tag, const std::string& str) { - SetValue(tag, new DicomString(str)); + SetValue(tag, new DicomValue(str, false)); } void SetValue(uint16_t group, uint16_t element, const std::string& str) { - SetValue(group, element, new DicomString(str)); + SetValue(group, element, new DicomValue(str, false)); } bool HasTag(uint16_t group, uint16_t element) const
--- a/Core/DicomFormat/DicomNullValue.h Tue Oct 20 17:39:58 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +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 General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomValue.h" - -namespace Orthanc -{ - class DicomNullValue : public DicomValue - { - public: - DicomNullValue() - { - } - - virtual DicomValue* Clone() const - { - return new DicomNullValue(); - } - - virtual std::string AsString() const - { - return "(null)"; - } - - virtual bool IsNull() const - { - return true; - } - }; -}
--- a/Core/DicomFormat/DicomString.h Tue Oct 20 17:39:58 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +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 General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomValue.h" - -namespace Orthanc -{ - class DicomString : public DicomValue - { - private: - std::string value_; - - public: - DicomString(const std::string& v) : value_(v) - { - } - - DicomString(const char* v) - { - if (v) - value_ = v; - else - value_ = ""; - } - - virtual DicomValue* Clone() const - { - return new DicomString(value_); - } - - virtual std::string AsString() const - { - return value_; - } - }; -}
--- a/Core/DicomFormat/DicomTag.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomTag.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -114,6 +114,12 @@ if (*this == DICOM_TAG_PATIENT_NAME) return "PatientName"; + if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT) + return "ImagePositionPatient"; + + if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT) + return "ImageOrientationPatient"; + return ""; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomFormat/DicomValue.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -0,0 +1,90 @@ +/** + * 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 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 "DicomValue.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +namespace Orthanc +{ + DicomValue::DicomValue(const DicomValue& other) : + type_(other.type_), + content_(other.content_) + { + } + + + DicomValue::DicomValue(const std::string& content, + bool isBinary) : + type_(isBinary ? Type_Binary : Type_String), + content_(content) + { + } + + + DicomValue::DicomValue(const char* data, + size_t size, + bool isBinary) : + type_(isBinary ? Type_Binary : Type_String) + { + content_.assign(data, size); + } + + + const std::string& DicomValue::GetContent() const + { + if (type_ == Type_Null) + { + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return content_; + } + } + + + DicomValue* DicomValue::Clone() const + { + return new DicomValue(*this); + } + + + void DicomValue::FormatDataUriScheme(std::string& target, + const std::string& mime) const + { + Toolbox::EncodeBase64(target, GetContent()); + target.insert(0, "data:" + mime + ";base64,"); + } +}
--- a/Core/DicomFormat/DicomValue.h Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/DicomFormat/DicomValue.h Mon Oct 26 12:30:34 2015 +0100 @@ -32,22 +32,58 @@ #pragma once -#include "../IDynamicObject.h" - #include <string> +#include <boost/noncopyable.hpp> namespace Orthanc { - class DicomValue : public IDynamicObject + class DicomValue : public boost::noncopyable { - public: - virtual DicomValue* Clone() const = 0; + private: + enum Type + { + Type_Null, + Type_String, + Type_Binary + }; + + Type type_; + std::string content_; + + DicomValue(const DicomValue& other); - virtual std::string AsString() const = 0; + public: + DicomValue() : type_(Type_Null) + { + } + + DicomValue(const std::string& content, + bool isBinary); + + DicomValue(const char* data, + size_t size, + bool isBinary); + + const std::string& GetContent() const; - virtual bool IsNull() const + bool IsNull() const + { + return type_ == Type_Null; + } + + bool IsBinary() const { - return false; + return type_ == Type_Binary; + } + + DicomValue* Clone() const; + + void FormatDataUriScheme(std::string& target, + const std::string& mime) const; + + void FormatDataUriScheme(std::string& target) const + { + FormatDataUriScheme(target, "application/octet-stream"); } }; }
--- a/Core/Logging.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Core/Logging.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -144,9 +144,7 @@ std::ostream* warning_; std::ostream* info_; - std::auto_ptr<std::ofstream> errorFile_; - std::auto_ptr<std::ofstream> warningFile_; - std::auto_ptr<std::ofstream> infoFile_; + std::auto_ptr<std::ofstream> file_; LoggingState() : infoEnabled_(false), @@ -172,14 +170,14 @@ { static void GetLogPath(boost::filesystem::path& log, boost::filesystem::path& link, - const char* level, + const std::string& suffix, const std::string& directory) { /** From Google Log documentation: Unless otherwise specified, logs will be written to the filename - "<program name>.<hostname>.<user name>.log.<severity level>.", + "<program name>.<hostname>.<user name>.log<suffix>.", followed by the date, time, and pid (you can't prevent the date, time, and pid from being in the filename). @@ -208,21 +206,17 @@ std::string programName = exe.filename().replace_extension("").string(); - log = (root / (programName + ".log." + - std::string(level) + "." + - std::string(date))); - - link = (root / (programName + "." + std::string(level))); + log = (root / (programName + ".log" + suffix + "." + std::string(date))); + link = (root / (programName + ".log" + suffix)); } - static void PrepareLogFile(std::ostream*& stream, - std::auto_ptr<std::ofstream>& file, - const char* level, + static void PrepareLogFile(std::auto_ptr<std::ofstream>& file, + const std::string& suffix, const std::string& directory) { boost::filesystem::path log, link; - GetLogPath(log, link, level, directory); + GetLogPath(log, link, suffix, directory); #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) boost::filesystem::remove(link); @@ -230,7 +224,6 @@ #endif file.reset(new std::ofstream(log.string().c_str())); - stream = file.get(); } @@ -273,9 +266,11 @@ boost::mutex::scoped_lock lock(loggingMutex_); assert(loggingState_.get() != NULL); - PrepareLogFile(loggingState_->error_, loggingState_->errorFile_, "ERROR", path); - PrepareLogFile(loggingState_->warning_, loggingState_->warningFile_, "WARNING", path); - PrepareLogFile(loggingState_->info_, loggingState_->infoFile_, "INFO", path); + PrepareLogFile(loggingState_->file_, "" /* no suffix */, path); + + loggingState_->warning_ = loggingState_->file_.get(); + loggingState_->error_ = loggingState_->file_.get(); + loggingState_->info_ = loggingState_->file_.get(); } InternalLogger::InternalLogger(const char* level,
--- a/NEWS Tue Oct 20 17:39:58 2015 +0200 +++ b/NEWS Mon Oct 26 12:30:34 2015 +0100 @@ -6,6 +6,7 @@ * "/tools/create-dicom": Support of hierarchical structures (creation of sequences) * "/modify" can insert/modify sequences * "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image +* New URI "/tools/shutdown" to stop Orthanc from the REST API * New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed" Plugins @@ -13,6 +14,7 @@ * New function "OrthancPluginRegisterErrorCode()" to declare custom error codes * New function "OrthancPluginRegisterDictionaryTag()" to declare DICOM tags +* New "OrthancStarted" and "OrthancStopped" events in change callbacks Lua --- @@ -27,6 +29,9 @@ * "/system" URI gives information about the plugins used for storage area and DB back-end * Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers * "/tools/create-dicom" can create tags with unknown VR +* "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos +* "--errors" flag lists the error codes that could be returned by Orthanc +* Under Windows, the exit status of Orthanc corresponds to the encountered error code Version 0.9.4 (2015/09/16)
--- a/OrthancExplorer/explorer.js Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancExplorer/explorer.js Mon Oct 26 12:30:34 2015 +0100 @@ -236,8 +236,8 @@ ).format (patient.MainDicomTags.PatientName, FormatMainDicomTags(patient.MainDicomTags, [ - "PatientName", - "OtherPatientIDs" + "PatientName" + /*"OtherPatientIDs" */ ]), patient.Studies.length ); @@ -288,7 +288,8 @@ "SeriesTime", "Manufacturer", "ImagesInAcquisition", - "SeriesDate" + "SeriesDate", + "ImageOrientationPatient" ]), c ); @@ -305,7 +306,8 @@ "AcquisitionNumber", "InstanceNumber", "InstanceCreationDate", - "InstanceCreationTime" + "InstanceCreationTime", + "ImagePositionPatient" ]) );
--- a/OrthancServer/DicomFindQuery.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/DicomFindQuery.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -349,9 +349,15 @@ for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) { + const DicomValue& value = mainTags.GetValue(*it); + if (value.IsBinary() || value.IsNull()) + { + return false; + } + Constraints::const_iterator constraint = constraints_.find(*it); if (constraint != constraints_.end() && - !constraint->second->Apply(mainTags.GetValue(*it).AsString())) + !constraint->second->Apply(value.GetContent())) { return false; }
--- a/OrthancServer/DicomInstanceToStore.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/DicomInstanceToStore.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -110,7 +110,9 @@ { json_.Allocate(); FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()), - DicomToJsonFormat_Full, 256 /* max string length */); + DicomToJsonFormat_Full, + DicomToJsonFlags_Default, + 256 /* max string length */); } }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -457,7 +457,8 @@ const DicomValue* value = fix->TestAndGetValue(*it); if (value != NULL && - value->AsString() == "*") + !value->IsNull() && + value->GetContent() == "*") { fix->SetValue(*it, ""); } @@ -948,7 +949,7 @@ throw OrthancException(ErrorCode_InternalError); } - const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).AsString(); + const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); ResourceType level = StringToResourceType(tmp.c_str()); DicomMap move;
--- a/OrthancServer/FromDcmtkBridge.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -47,8 +47,6 @@ #include "../Core/OrthancException.h" #include "../Core/Images/PngWriter.h" #include "../Core/Uuid.h" -#include "../Core/DicomFormat/DicomString.h" -#include "../Core/DicomFormat/DicomNullValue.h" #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" #include <list> @@ -332,7 +330,7 @@ { target.SetValue(element->getTag().getGTag(), element->getTag().getETag(), - ConvertLeafElement(*element, encoding)); + ConvertLeafElement(*element, DicomToJsonFlags_Default, encoding)); } } } @@ -364,6 +362,7 @@ DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, Encoding encoding) { if (!element.isLeaf()) @@ -379,18 +378,18 @@ { if (c == NULL) // This case corresponds to the empty string { - return new DicomString(""); + return new DicomValue("", false); } else { std::string s(c); std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); - return new DicomString(utf8); + return new DicomValue(utf8, false); } } else { - return new DicomNullValue; + return new DicomValue; } } @@ -401,14 +400,26 @@ { /** - * TODO. + * Deal with binary data (including PixelData). **/ case EVR_OB: // other byte case EVR_OF: // other float case EVR_OW: // other word case EVR_UN: // unknown value representation - return new DicomNullValue; + case EVR_ox: // OB or OW depending on context + { + if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) + { + Uint8* data = NULL; + if (element.getUint8Array(data) == EC_Normal) + { + return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); + } + } + + return new DicomValue; + } /** * String types, should never happen at this point because of @@ -430,7 +441,7 @@ case EVR_UT: // unlimited text case EVR_PN: // person name case EVR_UI: // unique identifier - return new DicomNullValue; + return new DicomValue; /** @@ -441,54 +452,54 @@ { Sint32 f; if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good()) - return new DicomString(boost::lexical_cast<std::string>(f)); + return new DicomValue(boost::lexical_cast<std::string>(f), false); else - return new DicomNullValue; + return new DicomValue; } case EVR_SS: // signed short { Sint16 f; if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good()) - return new DicomString(boost::lexical_cast<std::string>(f)); + return new DicomValue(boost::lexical_cast<std::string>(f), false); else - return new DicomNullValue; + return new DicomValue; } case EVR_UL: // unsigned long { Uint32 f; if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good()) - return new DicomString(boost::lexical_cast<std::string>(f)); + return new DicomValue(boost::lexical_cast<std::string>(f), false); else - return new DicomNullValue; + return new DicomValue; } case EVR_US: // unsigned short { Uint16 f; if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good()) - return new DicomString(boost::lexical_cast<std::string>(f)); + return new DicomValue(boost::lexical_cast<std::string>(f), false); else - return new DicomNullValue; + return new DicomValue; } case EVR_FL: // float single-precision { Float32 f; if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good()) - return new DicomString(boost::lexical_cast<std::string>(f)); + return new DicomValue(boost::lexical_cast<std::string>(f), false); else - return new DicomNullValue; + return new DicomValue; } case EVR_FD: // float double-precision { Float64 f; if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good()) - return new DicomString(boost::lexical_cast<std::string>(f)); + return new DicomValue(boost::lexical_cast<std::string>(f), false); else - return new DicomNullValue; + return new DicomValue; } @@ -502,11 +513,11 @@ if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) { DicomTag t(tag.getGroup(), tag.getElement()); - return new DicomString(t.Format()); + return new DicomValue(t.Format(), false); } else { - return new DicomNullValue; + return new DicomValue; } } @@ -517,14 +528,13 @@ **/ case EVR_SQ: // sequence of items - return new DicomNullValue; + return new DicomValue; /** * Internal to DCMTK. **/ - case EVR_ox: // OB or OW depending on context case EVR_xs: // SS or US depending on context case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) case EVR_na: // na="not applicable", for data which has no VR @@ -541,7 +551,7 @@ case EVR_PixelData: // used internally for uncompressed pixeld data case EVR_OverlayData: // used internally for overlay data case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - return new DicomNullValue; + return new DicomValue; /** @@ -549,16 +559,16 @@ **/ default: - return new DicomNullValue; + return new DicomValue; } } catch (boost::bad_lexical_cast) { - return new DicomNullValue; + return new DicomValue; } catch (std::bad_cast) { - return new DicomNullValue; + return new DicomValue; } } @@ -622,9 +632,11 @@ static void LeafValueToJson(Json::Value& target, const DicomValue& value, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength) { - std::string content = value.AsString(); + Json::Value* targetValue = NULL; + Json::Value* targetType = NULL; switch (format) { @@ -632,52 +644,77 @@ case DicomToJsonFormat_Simple: { assert(target.type() == Json::nullValue); - - if (!value.IsNull() && - (maxStringLength == 0 || - content.size() <= maxStringLength)) - { - target = content; - } - + targetValue = ⌖ break; } case DicomToJsonFormat_Full: { assert(target.type() == Json::objectValue); - - if (value.IsNull()) - { - target["Type"] = "Null"; - target["Value"] = Json::nullValue; - } - else - { - if (maxStringLength == 0 || - content.size() <= maxStringLength) - { - target["Type"] = "String"; - target["Value"] = content; - } - else - { - target["Type"] = "TooLong"; - target["Value"] = Json::nullValue; - } - } + target["Value"] = Json::nullValue; + target["Type"] = Json::nullValue; + targetType = &target["Type"]; + targetValue = &target["Value"]; break; } default: throw OrthancException(ErrorCode_ParameterOutOfRange); } + + assert(targetValue != NULL); + assert(targetValue->type() == Json::nullValue); + assert(targetType == NULL || targetType->type() == Json::nullValue); + + if (value.IsNull()) + { + if (targetType != NULL) + { + *targetType = "Null"; + } + } + else if (value.IsBinary()) + { + if (flags & DicomToJsonFlags_ConvertBinaryToAscii) + { + *targetValue = Toolbox::ConvertToAscii(value.GetContent()); + } + else + { + std::string s; + value.FormatDataUriScheme(s); + *targetValue = s; + } + + if (targetType != NULL) + { + *targetType = "Binary"; + } + } + else if (maxStringLength == 0 || + value.GetContent().size() <= maxStringLength) + { + *targetValue = value.GetContent(); + + if (targetType != NULL) + { + *targetType = "String"; + } + } + else + { + if (targetType != NULL) + { + *targetType = "TooLong"; + } + } } static void DatasetToJson(Json::Value& parent, DcmItem& item, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding); @@ -685,6 +722,7 @@ void FromDcmtkBridge::ToJson(Json::Value& parent, DcmElement& element, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding) { @@ -698,8 +736,8 @@ if (element.isLeaf()) { - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, encoding)); - LeafValueToJson(target, *v, format, maxStringLength); + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, encoding)); + LeafValueToJson(target, *v, format, flags, maxStringLength); } else { @@ -715,7 +753,7 @@ { DcmItem* child = sequence.getItem(i); Json::Value& v = target.append(Json::objectValue); - DatasetToJson(v, *child, format, maxStringLength, encoding); + DatasetToJson(v, *child, format, flags, maxStringLength, encoding); } } } @@ -724,6 +762,7 @@ static void DatasetToJson(Json::Value& parent, DcmItem& item, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding) { @@ -732,7 +771,44 @@ for (unsigned long i = 0; i < item.card(); i++) { DcmElement* element = item.getElement(i); - FromDcmtkBridge::ToJson(parent, *element, format, maxStringLength, encoding); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (!(flags & DicomToJsonFlags_IncludePrivateTags) && + element->getTag().isPrivate()) + { + continue; + } + + if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) + { + DictionaryLocker locker; + if (locker->findEntry(element->getTag(), NULL) == NULL) + { + continue; + } + } + + DcmEVR evr = element->getTag().getEVR(); + if (evr == EVR_OB || + evr == EVR_OF || + evr == EVR_OW || + evr == EVR_UN || + evr == EVR_ox) + { + // This is a binary tag + DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + + if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || + (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) + { + continue; + } + } + + FromDcmtkBridge::ToJson(parent, *element, format, flags, maxStringLength, encoding); } } @@ -740,10 +816,11 @@ void FromDcmtkBridge::ToJson(Json::Value& target, DcmDataset& dataset, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength) { target = Json::objectValue; - DatasetToJson(target, dataset, format, maxStringLength, DetectEncoding(dataset)); + DatasetToJson(target, dataset, format, flags, maxStringLength, DetectEncoding(dataset)); } @@ -841,18 +918,6 @@ } - void FromDcmtkBridge::Print(FILE* fp, const DicomMap& m) - { - for (DicomMap::Map::const_iterator - it = m.map_.begin(); it != m.map_.end(); ++it) - { - DicomTag t = it->first; - std::string s = it->second->AsString(); - fprintf(fp, "0x%04x 0x%04x (%s) [%s]\n", t.GetGroup(), t.GetElement(), GetName(t).c_str(), s.c_str()); - } - } - - void FromDcmtkBridge::ToJson(Json::Value& result, const DicomMap& values, bool simplify) @@ -869,7 +934,15 @@ { if (simplify) { - result[GetName(it->first)] = it->second->AsString(); + if (it->second->IsNull()) + { + result[GetName(it->first)] = Json::nullValue; + } + else + { + // TODO IsBinary + result[GetName(it->first)] = it->second->GetContent(); + } } else { @@ -884,8 +957,9 @@ } else { + // TODO IsBinary value["Type"] = "String"; - value["Value"] = it->second->AsString(); + value["Value"] = it->second->GetContent(); } result[it->first.Format()] = value; @@ -1001,7 +1075,13 @@ static bool IsBinaryTag(const DcmTag& key) { - return key.isPrivate() || key.isUnknownVR(); + return (key.isPrivate() || + key.isUnknownVR() || + key.getEVR() == EVR_OB || + key.getEVR() == EVR_OF || + key.getEVR() == EVR_OW || + key.getEVR() == EVR_UN || + key.getEVR() == EVR_ox); } @@ -1019,17 +1099,15 @@ // http://support.dcmtk.org/docs/dcvr_8h-source.html /** - * TODO. + * Binary types, handled above **/ case EVR_OB: // other byte case EVR_OF: // other float case EVR_OW: // other word - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - case EVR_UN: // unknown value representation - throw OrthancException(ErrorCode_ParameterOutOfRange); + case EVR_ox: // OB or OW depending on context + throw OrthancException(ErrorCode_InternalError); /** @@ -1115,10 +1193,17 @@ /** + * TODO + **/ + + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + + /** * Internal to DCMTK. **/ - case EVR_ox: // OB or OW depending on context case EVR_xs: // SS or US depending on context case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) case EVR_na: // na="not applicable", for data which has no VR
--- a/OrthancServer/FromDcmtkBridge.h Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Mon Oct 26 12:30:34 2015 +0100 @@ -65,17 +65,20 @@ static bool IsUnknownTag(const DicomTag& tag); static DicomValue* ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, Encoding encoding); static void ToJson(Json::Value& parent, DcmElement& element, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength, - Encoding encoding); + Encoding dicomEncoding); static void ToJson(Json::Value& target, DcmDataset& dataset, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength); static std::string GetName(const DicomTag& tag); @@ -106,9 +109,6 @@ target.SetValue(ParseTag(tagName), value); } - static void Print(FILE* fp, - const DicomMap& m); - static void ToJson(Json::Value& result, const DicomMap& values, bool simplify);
--- a/OrthancServer/Internals/StoreScp.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/Internals/StoreScp.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -169,7 +169,9 @@ { FromDcmtkBridge::Convert(summary, **imageDataSet); FromDcmtkBridge::ToJson(dicomJson, **imageDataSet, - DicomToJsonFormat_Full, 256 /* max string length */); + DicomToJsonFormat_Full, + DicomToJsonFlags_Default, + 256 /* max string length */); if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) {
--- a/OrthancServer/OrthancFindRequestHandler.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -240,12 +240,14 @@ **/ const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); - if (levelTmp == NULL) + if (levelTmp == NULL || + levelTmp->IsNull() || + levelTmp->IsBinary()) { throw OrthancException(ErrorCode_BadRequest); } - ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); + ResourceType level = StringToResourceType(levelTmp->GetContent().c_str()); if (level != ResourceType_Patient && level != ResourceType_Study && @@ -265,7 +267,7 @@ { LOG(INFO) << " " << query.GetElement(i).GetTag() << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) - << " = " << query.GetElement(i).GetValue().AsString(); + << " = " << query.GetElement(i).GetValue().GetContent(); } } @@ -288,7 +290,7 @@ continue; } - std::string value = query.GetElement(i).GetValue().AsString(); + std::string value = query.GetElement(i).GetValue().GetContent(); if (value.size() == 0) { // An empty string corresponds to a "*" wildcard constraint, so we ignore it
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -138,10 +138,17 @@ return false; } - std::string value = input.GetValue(tag).AsString(); + const DicomValue& value = input.GetValue(tag); + if (value.IsNull() || + value.IsBinary()) + { + return false; + } + + const std::string& content = value.GetContent(); std::list<std::string> ids; - context_.GetIndex().LookupIdentifierExact(ids, level, tag, value); + context_.GetIndex().LookupIdentifierExact(ids, level, tag, content); if (ids.size() != 1) { @@ -170,7 +177,7 @@ { LOG(INFO) << " " << query.GetElement(i).GetTag() << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) - << " = " << query.GetElement(i).GetValue().AsString(); + << " = " << query.GetElement(i).GetValue().GetContent(); } } } @@ -183,7 +190,9 @@ const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); - if (levelTmp == NULL) + if (levelTmp == NULL || + levelTmp->IsNull() || + levelTmp->IsBinary()) { // The query level is not present in the C-Move request, which // does not follow the DICOM standard. This is for instance the @@ -208,7 +217,7 @@ } assert(levelTmp != NULL); - ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); + ResourceType level = StringToResourceType(levelTmp->GetContent().c_str()); /**
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -59,11 +59,20 @@ void OrthancRestApi::ResetOrthanc(RestApiPostCall& call) { + OrthancRestApi::GetApi(call).leaveBarrier_ = true; OrthancRestApi::GetApi(call).resetRequestReceived_ = true; call.GetOutput().AnswerBuffer("{}", "application/json"); } + void OrthancRestApi::ShutdownOrthanc(RestApiPostCall& call) + { + OrthancRestApi::GetApi(call).leaveBarrier_ = true; + call.GetOutput().AnswerBuffer("{}", "application/json"); + LOG(WARNING) << "Shutdown request received"; + } + + @@ -99,6 +108,7 @@ OrthancRestApi::OrthancRestApi(ServerContext& context) : context_(context), + leaveBarrier_(false), resetRequestReceived_(false) { RegisterSystem(); @@ -114,6 +124,7 @@ // Auto-generated directories Register("/tools", RestApi::AutoListChildren); Register("/tools/reset", ResetOrthanc); + Register("/tools/shutdown", ShutdownOrthanc); Register("/instances/{id}/frames/{frame}", RestApi::AutoListChildren); }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Mon Oct 26 12:30:34 2015 +0100 @@ -49,6 +49,7 @@ private: ServerContext& context_; + bool leaveBarrier_; bool resetRequestReceived_; void RegisterSystem(); @@ -65,10 +66,17 @@ static void ResetOrthanc(RestApiPostCall& call); + static void ShutdownOrthanc(RestApiPostCall& call); + public: OrthancRestApi(ServerContext& context); - const bool& ResetRequestReceivedFlag() const + const bool& LeaveBarrierFlag() const + { + return leaveBarrier_; + } + + bool IsResetRequestReceived() const { return resetRequestReceived_; }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -198,8 +198,8 @@ return; } - if (fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) + if (fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).GetContent().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).GetContent().size() <= 2) { return; } @@ -228,9 +228,9 @@ return; } - if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) + if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).GetContent().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).GetContent().size() <= 2) || + fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent().size() <= 2) { return; } @@ -259,10 +259,10 @@ return; } - if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || - fields.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) + if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).GetContent().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).GetContent().size() <= 2) || + fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent().size() <= 2 || + fields.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent().size() <= 2) { return; }
--- a/OrthancServer/ParsedDicomFile.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -90,8 +90,6 @@ #include "../Core/Images/ImageBuffer.h" #include "../Core/Images/PngWriter.h" #include "../Core/Uuid.h" -#include "../Core/DicomFormat/DicomString.h" -#include "../Core/DicomFormat/DicomNullValue.h" #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" #include "../Core/Images/PngReader.h" @@ -763,15 +761,18 @@ return false; } - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, GetEncoding())); + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_Default, GetEncoding())); - if (v.get() == NULL) + if (v.get() == NULL || + v->IsNull()) { value = ""; } else { - value = v->AsString(); + // TODO v->IsBinary() + value = v->GetContent(); } return true; @@ -1120,9 +1121,10 @@ void ParsedDicomFile::ToJson(Json::Value& target, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength) { - FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset(), format, maxStringLength); + FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset(), format, flags, maxStringLength); }
--- a/OrthancServer/ParsedDicomFile.h Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ParsedDicomFile.h Mon Oct 26 12:30:34 2015 +0100 @@ -37,6 +37,7 @@ #include "ServerEnumerations.h" #include "../Core/Images/ImageAccessor.h" #include "../Core/Images/ImageBuffer.h" +#include "../Core/IDynamicObject.h" namespace Orthanc { @@ -134,6 +135,7 @@ void ToJson(Json::Value& target, DicomToJsonFormat format, + DicomToJsonFlags flags, unsigned int maxStringLength); bool HasTag(const DicomTag& tag) const;
--- a/OrthancServer/ServerContext.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ServerContext.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -520,6 +520,18 @@ } } + OrthancPlugins& ServerContext::GetPlugins() + { + if (HasPlugins()) + { + return *plugins_; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + #endif
--- a/OrthancServer/ServerContext.h Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ServerContext.h Mon Oct 26 12:30:34 2015 +0100 @@ -256,6 +256,8 @@ void ResetPlugins(); const OrthancPlugins& GetPlugins() const; + + OrthancPlugins& GetPlugins(); #endif bool HasPlugins() const;
--- a/OrthancServer/ServerEnumerations.h Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ServerEnumerations.h Mon Oct 26 12:30:34 2015 +0100 @@ -108,6 +108,23 @@ DicomToJsonFormat_Simple }; + enum DicomToJsonFlags + { + DicomToJsonFlags_IncludeBinary = (1 << 0), + DicomToJsonFlags_IncludePrivateTags = (1 << 1), + DicomToJsonFlags_IncludeUnknownTags = (1 << 2), + DicomToJsonFlags_IncludePixelData = (1 << 3), + DicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), + DicomToJsonFlags_ConvertBinaryToNull = (1 << 5), + + // Some predefined combinations + DicomToJsonFlags_None = 0, + DicomToJsonFlags_Default = (DicomToJsonFlags_IncludePrivateTags | + DicomToJsonFlags_IncludeUnknownTags | + DicomToJsonFlags_IncludePixelData | + DicomToJsonFlags_ConvertBinaryToNull) + }; + /** * WARNING: Do not change the explicit values in the enumerations
--- a/OrthancServer/ServerIndex.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ServerIndex.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -394,8 +394,8 @@ (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL) { // Patch for series with temporal positions thanks to Will Ryder - int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->AsString()); - int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->AsString()); + int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent()); + int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent()); std::string expected = boost::lexical_cast<std::string>(imagesInAcquisition * countTemporalPositions); db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); } @@ -404,18 +404,21 @@ (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL) { // Support of Cardio-PET images - int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->AsString()); - int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->AsString()); + int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent()); + int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent()); std::string expected = boost::lexical_cast<std::string>(numberOfSlices * numberOfTimeSlices); db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected); } else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL) { - db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString()); + db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->GetContent()); } } - catch (boost::bad_lexical_cast) + catch (OrthancException&) + { + } + catch (boost::bad_lexical_cast&) { } } @@ -768,8 +771,12 @@ if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) { - db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString()); - instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString(); + if (!value->IsNull() && + !value->IsBinary()) + { + db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->GetContent()); + instanceMetadata[MetadataType_Instance_IndexInSeries] = value->GetContent(); + } } // Check whether the series of this new instance is now completed @@ -1197,22 +1204,22 @@ switch (currentType) { case ResourceType_Patient: - patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString(); + patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent(); done = true; break; case ResourceType_Study: - studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(); + studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(); currentType = ResourceType_Patient; break; case ResourceType_Series: - seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(); + seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(); currentType = ResourceType_Study; break; case ResourceType_Instance: - sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString(); + sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent(); currentType = ResourceType_Series; break;
--- a/OrthancServer/ServerIndexChange.h Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ServerIndexChange.h Mon Oct 26 12:30:34 2015 +0100 @@ -33,6 +33,7 @@ #pragma once #include "ServerEnumerations.h" +#include "../Core/IDynamicObject.h" #include "../Core/Toolbox.h" #include <string>
--- a/OrthancServer/ServerToolbox.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ServerToolbox.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -91,6 +91,21 @@ } + static std::string ValueAsString(const DicomMap& summary, + const DicomTag& tag) + { + const DicomValue& value = summary.GetValue(tag); + if (value.IsNull()) + { + return "(null)"; + } + else + { + return value.GetContent(); + } + } + + void LogMissingRequiredTag(const DicomMap& summary) { std::string s, t; @@ -99,7 +114,7 @@ { if (t.size() > 0) t += ", "; - t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString(); + t += "PatientID=" + ValueAsString(summary, DICOM_TAG_PATIENT_ID); } else { @@ -112,7 +127,7 @@ { if (t.size() > 0) t += ", "; - t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(); + t += "StudyInstanceUID=" + ValueAsString(summary, DICOM_TAG_STUDY_INSTANCE_UID); } else { @@ -125,7 +140,7 @@ { if (t.size() > 0) t += ", "; - t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(); + t += "SeriesInstanceUID=" + ValueAsString(summary, DICOM_TAG_SERIES_INSTANCE_UID); } else { @@ -138,7 +153,7 @@ { if (t.size() > 0) t += ", "; - t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString(); + t += "SOPInstanceUID=" + ValueAsString(summary, DICOM_TAG_SOP_INSTANCE_UID); } else { @@ -168,7 +183,12 @@ { const DicomElement& element = flattened.GetElement(i); const DicomTag& tag = element.GetTag(); - database.SetMainDicomTag(resource, tag, element.GetValue().AsString()); + const DicomValue& value = element.GetValue(); + if (!value.IsNull() && + !value.IsBinary()) + { + database.SetMainDicomTag(resource, tag, element.GetValue().GetContent()); + } } } @@ -180,9 +200,10 @@ { const DicomValue* value = tags.TestAndGetValue(tag); if (value != NULL && - !value->IsNull()) + !value->IsNull() && + !value->IsBinary()) { - std::string s = value->AsString(); + std::string s = value->GetContent(); if (tag != DICOM_TAG_PATIENT_ID && tag != DICOM_TAG_STUDY_INSTANCE_UID &&
--- a/OrthancServer/SliceOrdering.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/SliceOrdering.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -82,13 +82,14 @@ const DicomValue* value = map.TestAndGetValue(tag); if (value == NULL || - value->IsNull()) + value->IsNull() || + value->IsBinary()) { return false; } else { - return TokenizeVector(result, value->AsString(), expectedSize); + return TokenizeVector(result, value->GetContent(), expectedSize); } } @@ -117,11 +118,12 @@ const DicomValue* frames = instance.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES); if (frames != NULL && - !frames->IsNull()) + !frames->IsNull() && + !frames->IsBinary()) { try { - framesCount_ = boost::lexical_cast<unsigned int>(frames->AsString()); + framesCount_ = boost::lexical_cast<unsigned int>(frames->GetContent()); } catch (boost::bad_lexical_cast&) {
--- a/OrthancServer/ToDcmtkBridge.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/ToDcmtkBridge.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -52,8 +52,11 @@ for (DicomMap::Map::const_iterator it = map.map_.begin(); it != map.map_.end(); ++it) { - std::string s = it->second->AsString(); - DU_putStringDOElement(result.get(), Convert(it->first), s.c_str()); + if (!it->second->IsNull()) + { + std::string s = it->second->GetContent(); + DU_putStringDOElement(result.get(), Convert(it->first), s.c_str()); + } } return result.release();
--- a/OrthancServer/main.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/OrthancServer/main.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -387,7 +387,7 @@ -static void PrintHelp(char* path) +static void PrintHelp(const char* path) { std::cout << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl @@ -404,6 +404,7 @@ << " --logdir=[dir]\tdirectory where to store the log files" << std::endl << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl << " --config=[file]\tcreate a sample configuration file and exit" << std::endl + << " --errors\t\tprint the supported error codes and exit" << std::endl << " --verbose\t\tbe verbose in logs" << std::endl << " --trace\t\thighest verbosity in logs (for debug)" << std::endl << " --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl @@ -413,12 +414,16 @@ << std::endl << "Exit status:" << std::endl << " 0 if success," << std::endl +#if defined(_WIN32) + << "!= 0 if error (use the --errors option to get the list of possible errors)." << std::endl +#else << " -1 if error (have a look at the logs)." << std::endl +#endif << std::endl; } -static void PrintVersion(char* path) +static void PrintVersion(const char* path) { std::cout << path << " " << ORTHANC_VERSION << std::endl @@ -431,6 +436,124 @@ } +static void PrintErrorCode(ErrorCode code, const char* description) +{ + std::cout + << std::right << std::setw(16) + << static_cast<int>(code) + << " " << description << std::endl; +} + + +static void PrintErrors(const char* path) +{ + std::cout + << path << " " << ORTHANC_VERSION << std::endl + << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." + << std::endl << std::endl + << "List of error codes that could be returned by Orthanc:" + << std::endl << std::endl; + + // The content of the following brackets is automatically generated + // by the "GenerateErrorCodes.py" script + { + PrintErrorCode(ErrorCode_InternalError, "Internal error"); + PrintErrorCode(ErrorCode_Success, "Success"); + PrintErrorCode(ErrorCode_Plugin, "Error encountered within the plugin engine"); + PrintErrorCode(ErrorCode_NotImplemented, "Not implemented yet"); + PrintErrorCode(ErrorCode_ParameterOutOfRange, "Parameter out of range"); + PrintErrorCode(ErrorCode_NotEnoughMemory, "Not enough memory"); + PrintErrorCode(ErrorCode_BadParameterType, "Bad type for a parameter"); + PrintErrorCode(ErrorCode_BadSequenceOfCalls, "Bad sequence of calls"); + PrintErrorCode(ErrorCode_InexistentItem, "Accessing an inexistent item"); + PrintErrorCode(ErrorCode_BadRequest, "Bad request"); + PrintErrorCode(ErrorCode_NetworkProtocol, "Error in the network protocol"); + PrintErrorCode(ErrorCode_SystemCommand, "Error while calling a system command"); + PrintErrorCode(ErrorCode_Database, "Error with the database engine"); + PrintErrorCode(ErrorCode_UriSyntax, "Badly formatted URI"); + PrintErrorCode(ErrorCode_InexistentFile, "Inexistent file"); + PrintErrorCode(ErrorCode_CannotWriteFile, "Cannot write to file"); + PrintErrorCode(ErrorCode_BadFileFormat, "Bad file format"); + PrintErrorCode(ErrorCode_Timeout, "Timeout"); + PrintErrorCode(ErrorCode_UnknownResource, "Unknown resource"); + PrintErrorCode(ErrorCode_IncompatibleDatabaseVersion, "Incompatible version of the database"); + PrintErrorCode(ErrorCode_FullStorage, "The file storage is full"); + PrintErrorCode(ErrorCode_CorruptedFile, "Corrupted file (e.g. inconsistent MD5 hash)"); + PrintErrorCode(ErrorCode_InexistentTag, "Inexistent tag"); + PrintErrorCode(ErrorCode_ReadOnly, "Cannot modify a read-only data structure"); + PrintErrorCode(ErrorCode_IncompatibleImageFormat, "Incompatible format of the images"); + PrintErrorCode(ErrorCode_IncompatibleImageSize, "Incompatible size of the images"); + PrintErrorCode(ErrorCode_SharedLibrary, "Error while using a shared library (plugin)"); + PrintErrorCode(ErrorCode_UnknownPluginService, "Plugin invoking an unknown service"); + PrintErrorCode(ErrorCode_UnknownDicomTag, "Unknown DICOM tag"); + PrintErrorCode(ErrorCode_BadJson, "Cannot parse a JSON document"); + PrintErrorCode(ErrorCode_Unauthorized, "Bad credentials were provided to an HTTP request"); + PrintErrorCode(ErrorCode_BadFont, "Badly formatted font file"); + PrintErrorCode(ErrorCode_DatabasePlugin, "The plugin implementing a custom database back-end does not fulfill the proper interface"); + PrintErrorCode(ErrorCode_StorageAreaPlugin, "Error in the plugin implementing a custom storage area"); + PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); + PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); + PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database"); + PrintErrorCode(ErrorCode_SQLiteStatementAlreadyUsed, "SQLite: This cached statement is already being referred to"); + PrintErrorCode(ErrorCode_SQLiteExecute, "SQLite: Cannot execute a command"); + PrintErrorCode(ErrorCode_SQLiteRollbackWithoutTransaction, "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)"); + PrintErrorCode(ErrorCode_SQLiteCommitWithoutTransaction, "SQLite: Committing a nonexistent transaction"); + PrintErrorCode(ErrorCode_SQLiteRegisterFunction, "SQLite: Unable to register a function"); + PrintErrorCode(ErrorCode_SQLiteFlush, "SQLite: Unable to flush the database"); + PrintErrorCode(ErrorCode_SQLiteCannotRun, "SQLite: Cannot run a cached statement"); + PrintErrorCode(ErrorCode_SQLiteCannotStep, "SQLite: Cannot step over a cached statement"); + PrintErrorCode(ErrorCode_SQLiteBindOutOfRange, "SQLite: Bing a value while out of range (serious error)"); + PrintErrorCode(ErrorCode_SQLitePrepareStatement, "SQLite: Cannot prepare a cached statement"); + PrintErrorCode(ErrorCode_SQLiteTransactionAlreadyStarted, "SQLite: Beginning the same transaction twice"); + PrintErrorCode(ErrorCode_SQLiteTransactionCommit, "SQLite: Failure when committing the transaction"); + PrintErrorCode(ErrorCode_SQLiteTransactionBegin, "SQLite: Cannot start a transaction"); + PrintErrorCode(ErrorCode_DirectoryOverFile, "The directory to be created is already occupied by a regular file"); + PrintErrorCode(ErrorCode_FileStorageCannotWrite, "Unable to create a subdirectory or a file in the file storage"); + PrintErrorCode(ErrorCode_DirectoryExpected, "The specified path does not point to a directory"); + PrintErrorCode(ErrorCode_HttpPortInUse, "The TCP port of the HTTP server is already in use"); + PrintErrorCode(ErrorCode_DicomPortInUse, "The TCP port of the DICOM server is already in use"); + PrintErrorCode(ErrorCode_BadHttpStatusInRest, "This HTTP status is not allowed in a REST API"); + PrintErrorCode(ErrorCode_RegularFileExpected, "The specified path does not point to a regular file"); + PrintErrorCode(ErrorCode_PathToExecutable, "Unable to get the path to the executable"); + PrintErrorCode(ErrorCode_MakeDirectory, "Cannot create a directory"); + PrintErrorCode(ErrorCode_BadApplicationEntityTitle, "An application entity title (AET) cannot be empty or be longer than 16 characters"); + PrintErrorCode(ErrorCode_NoCFindHandler, "No request handler factory for DICOM C-FIND SCP"); + PrintErrorCode(ErrorCode_NoCMoveHandler, "No request handler factory for DICOM C-MOVE SCP"); + PrintErrorCode(ErrorCode_NoCStoreHandler, "No request handler factory for DICOM C-STORE SCP"); + PrintErrorCode(ErrorCode_NoApplicationEntityFilter, "No application entity filter"); + PrintErrorCode(ErrorCode_NoSopClassOrInstance, "DicomUserConnection: Unable to find the SOP class and instance"); + PrintErrorCode(ErrorCode_NoPresentationContext, "DicomUserConnection: No acceptable presentation context for modality"); + PrintErrorCode(ErrorCode_DicomFindUnavailable, "DicomUserConnection: The C-FIND command is not supported by the remote SCP"); + PrintErrorCode(ErrorCode_DicomMoveUnavailable, "DicomUserConnection: The C-MOVE command is not supported by the remote SCP"); + PrintErrorCode(ErrorCode_CannotStoreInstance, "Cannot store an instance"); + PrintErrorCode(ErrorCode_CreateDicomNotString, "Only string values are supported when creating DICOM instances"); + PrintErrorCode(ErrorCode_CreateDicomOverrideTag, "Trying to override a value inherited from a parent module"); + PrintErrorCode(ErrorCode_CreateDicomUseContent, "Use \"Content\" to inject an image into a new DICOM instance"); + PrintErrorCode(ErrorCode_CreateDicomNoPayload, "No payload is present for one instance in the series"); + PrintErrorCode(ErrorCode_CreateDicomUseDataUriScheme, "The payload of the DICOM instance must be specified according to Data URI scheme"); + PrintErrorCode(ErrorCode_CreateDicomBadParent, "Trying to attach a new DICOM instance to an inexistent resource"); + PrintErrorCode(ErrorCode_CreateDicomParentIsInstance, "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)"); + PrintErrorCode(ErrorCode_CreateDicomParentEncoding, "Unable to get the encoding of the parent resource"); + PrintErrorCode(ErrorCode_UnknownModality, "Unknown modality"); + PrintErrorCode(ErrorCode_BadJobOrdering, "Bad ordering of filters in a job"); + PrintErrorCode(ErrorCode_JsonToLuaTable, "Cannot convert the given JSON object to a Lua table"); + PrintErrorCode(ErrorCode_CannotCreateLua, "Cannot create the Lua context"); + PrintErrorCode(ErrorCode_CannotExecuteLua, "Cannot execute a Lua command"); + PrintErrorCode(ErrorCode_LuaAlreadyExecuted, "Arguments cannot be pushed after the Lua function is executed"); + PrintErrorCode(ErrorCode_LuaBadOutput, "The Lua function does not give the expected number of outputs"); + PrintErrorCode(ErrorCode_NotLuaPredicate, "The Lua function is not a predicate (only true/false outputs allowed)"); + PrintErrorCode(ErrorCode_LuaReturnsNoString, "The Lua function does not return a string"); + PrintErrorCode(ErrorCode_StorageAreaAlreadyRegistered, "Another plugin has already registered a custom storage area"); + PrintErrorCode(ErrorCode_DatabaseBackendAlreadyRegistered, "Another plugin has already registered a custom database back-end"); + PrintErrorCode(ErrorCode_DatabaseNotInitialized, "Plugin trying to call the database during its initialization"); + PrintErrorCode(ErrorCode_SslDisabled, "Orthanc has been built without SSL support"); + PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series"); + } + + std::cout << std::endl; +} + + static void LoadLuaScripts(ServerContext& context) { @@ -474,13 +597,27 @@ { LOG(WARNING) << "Orthanc has started"; +#if ORTHANC_PLUGINS_ENABLED == 1 + if (context.HasPlugins()) + { + context.GetPlugins().SignalOrthancStarted(); + } +#endif + context.GetLua().Execute("Initialize"); - Toolbox::ServerBarrier(restApi.ResetRequestReceivedFlag()); - bool restart = restApi.ResetRequestReceivedFlag(); + Toolbox::ServerBarrier(restApi.LeaveBarrierFlag()); + bool restart = restApi.IsResetRequestReceived(); context.GetLua().Execute("Finalize"); +#if ORTHANC_PLUGINS_ENABLED == 1 + if (context.HasPlugins()) + { + context.GetPlugins().SignalOrthancStopped(); + } +#endif + if (restart) { LOG(WARNING) << "Reset request received, restarting Orthanc"; @@ -853,6 +990,11 @@ configurationFile = argv[i]; } } + else if (argument == "--errors") + { + PrintErrors(argv[0]); + return 0; + } else if (argument == "--help") { PrintHelp(argv[0]); @@ -937,8 +1079,20 @@ } catch (const OrthancException& e) { - LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "]"; + LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "] (code " << e.GetErrorCode() << ")"; +#if defined(_WIN32) + if (e.GetErrorCode() >= ErrorCode_START_PLUGINS) + { + status = static_cast<int>(ErrorCode_Plugin); + } + else + { + status = static_cast<int>(e.GetErrorCode()); + } + +#else status = -1; +#endif } catch (const std::exception& e) {
--- a/Plugins/Engine/OrthancPlugins.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -299,7 +299,15 @@ sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || - sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType)) + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) || + static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) || + static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) || + static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) || + static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) || + static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) || + static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii)) { /* Sanity check of the compiler */ throw OrthancException(ErrorCode_Plugin); @@ -506,7 +514,9 @@ - void OrthancPlugins::SignalChange(const ServerIndexChange& change) + void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resource) { boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_); @@ -514,10 +524,7 @@ callback = pimpl_->onChangeCallbacks_.begin(); callback != pimpl_->onChangeCallbacks_.end(); ++callback) { - OrthancPluginErrorCode error = (*callback) - (Plugins::Convert(change.GetChangeType()), - Plugins::Convert(change.GetResourceType()), - change.GetPublicId().c_str()); + OrthancPluginErrorCode error = (*callback) (changeType, resourceType, resource); if (error != OrthancPluginErrorCode_Success) { @@ -529,6 +536,15 @@ + void OrthancPlugins::SignalChange(const ServerIndexChange& change) + { + SignalChangeInternal(Plugins::Convert(change.GetChangeType()), + Plugins::Convert(change.GetResourceType()), + change.GetPublicId().c_str()); + } + + + static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target, const void* data, size_t size) @@ -1229,6 +1245,39 @@ font.Draw(target, p.utf8Text, p.x, p.y, p.r, p.g, p.b); } + + + void OrthancPlugins::ApplyDicomToJson(_OrthancPluginService service, + const void* parameters) + { + const _OrthancPluginDicomToJson& p = + *reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters); + + std::auto_ptr<ParsedDicomFile> dicom; + + if (service == _OrthancPluginService_DicomBufferToJson) + { + dicom.reset(new ParsedDicomFile(p.buffer, p.size)); + } + else + { + if (p.instanceId == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string content; + pimpl_->context_->ReadFile(content, p.instanceId, FileContentType_Dicom); + dicom.reset(new ParsedDicomFile(content)); + } + + Json::Value json; + dicom->ToJson(json, Plugins::Convert(p.format), + static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength); + + Json::FastWriter writer; + *p.result = CopyString(writer.write(json)); + } bool OrthancPlugins::InvokeService(SharedLibrary& plugin, @@ -1726,6 +1775,11 @@ return true; } + case _OrthancPluginService_DicomBufferToJson: + case _OrthancPluginService_DicomInstanceToJson: + ApplyDicomToJson(service, parameters); + return true; + default: { // This service is unknown to the Orthanc plugin engine
--- a/Plugins/Engine/OrthancPlugins.h Tue Oct 20 17:39:58 2015 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Mon Oct 26 12:30:34 2015 +0100 @@ -127,6 +127,13 @@ void DrawText(const void* parameters); + void ApplyDicomToJson(_OrthancPluginService service, + const void* parameters); + + void SignalChangeInternal(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resource); + public: OrthancPlugins(); @@ -183,6 +190,16 @@ const PluginsManager& GetManager() const; PluginsErrorDictionary& GetErrorDictionary(); + + void SignalOrthancStarted() + { + SignalChangeInternal(OrthancPluginChangeType_OrthancStarted, OrthancPluginResourceType_None, NULL); + } + + void SignalOrthancStopped() + { + SignalChangeInternal(OrthancPluginChangeType_OrthancStopped, OrthancPluginResourceType_None, NULL); + } }; }
--- a/Plugins/Engine/PluginsEnumerations.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/Plugins/Engine/PluginsEnumerations.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -210,6 +210,25 @@ } + DicomToJsonFormat Convert(OrthancPluginDicomToJsonFormat format) + { + switch (format) + { + case OrthancPluginDicomToJsonFormat_Full: + return DicomToJsonFormat_Full; + + case OrthancPluginDicomToJsonFormat_Short: + return DicomToJsonFormat_Short; + + case OrthancPluginDicomToJsonFormat_Simple: + return DicomToJsonFormat_Simple; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + #if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 DcmEVR Convert(OrthancPluginValueRepresentation vr) {
--- a/Plugins/Engine/PluginsEnumerations.h Tue Oct 20 17:39:58 2015 +0200 +++ b/Plugins/Engine/PluginsEnumerations.h Mon Oct 26 12:30:34 2015 +0100 @@ -59,6 +59,8 @@ FileContentType Convert(OrthancPluginContentType type); + DicomToJsonFormat Convert(OrthancPluginDicomToJsonFormat format); + #if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 DcmEVR Convert(OrthancPluginValueRepresentation vr); #endif
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Tue Oct 20 17:39:58 2015 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Mon Oct 26 12:30:34 2015 +0100 @@ -387,6 +387,8 @@ _OrthancPluginService_CallHttpClient = 18, _OrthancPluginService_RegisterErrorCode = 19, _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -548,6 +550,7 @@ OrthancPluginResourceType_Study = 1, /*!< Study */ OrthancPluginResourceType_Series = 2, /*!< Series */ OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ _OrthancPluginResourceType_INTERNAL = 0x7fffffff } OrthancPluginResourceType; @@ -570,6 +573,8 @@ OrthancPluginChangeType_StablePatient = 7, /*!< Timeout: No new instance in this patient */ OrthancPluginChangeType_StableSeries = 8, /*!< Timeout: No new instance in this series */ OrthancPluginChangeType_StableStudy = 9, /*!< Timeout: No new instance in this study */ + OrthancPluginChangeType_OrthancStarted = 10, /*!< Orthanc has started */ + OrthancPluginChangeType_OrthancStopped = 11, /*!< Orthanc is stopping */ _OrthancPluginChangeType_INTERNAL = 0x7fffffff } OrthancPluginChangeType; @@ -642,6 +647,38 @@ /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Simple = 3, /*!< Human-readable JSON */ + + _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFormat; + + + /** + * Flags to customize a DICOM-to-JSON conversion. By default, binary + * tags are formatted using Data URI scheme. + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginDicomToJsonFlags_IncludeBinary = (1 << 0), /*!< Include the binary tags */ + OrthancPluginDicomToJsonFlags_IncludePrivateTags = (1 << 1), /*!< Include the private tags */ + OrthancPluginDicomToJsonFlags_IncludeUnknownTags = (1 << 2), /*!< Include the tags unknown by the dictionary */ + OrthancPluginDicomToJsonFlags_IncludePixelData = (1 << 3), /*!< Include the pixel data */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), /*!< Output binary tags as-is, dropping non-ASCII */ + OrthancPluginDicomToJsonFlags_ConvertBinaryToNull = (1 << 5), /*!< Signal binary tags as null values */ + + _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff + } OrthancPluginDicomToJsonFlags; + + + /** * @brief A memory buffer allocated by the core system of Orthanc. * * A memory buffer allocated by the core system of Orthanc. When the @@ -852,7 +889,9 @@ sizeof(int32_t) != sizeof(OrthancPluginChangeType) || sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || - sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation)) + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags)) { /* Mismatch in the size of the enumerations */ return 0; @@ -3806,6 +3845,115 @@ } + typedef struct + { + char** result; + const char* instanceId; + const char* buffer; + uint32_t size; + OrthancPluginDicomToJsonFormat format; + OrthancPluginDicomToJsonFlags flags; + uint32_t maxStringLength; + } _OrthancPluginDicomToJson; + + + /** + * @brief Format a DICOM memory buffer as a JSON string. + * + * This function takes as input a memory buffer containing a DICOM + * file, and outputs a JSON string representing the tags of this + * DICOM file. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer containing the DICOM file. + * @param size The size of the memory buffer. + * @param format The output format. + * @param flags The output flags. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson( + OrthancPluginContext* context, + const char* buffer, + uint32_t size, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.buffer = buffer; + params.size = size; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Format a DICOM instance as a JSON string. + * + * This function formats a DICOM instance that is stored in Orthanc, + * and outputs a JSON string representing the tags of this DICOM + * instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instanceId The Orthanc identifier of the instance. + * @param format The output format. + * @param flags The output flags. + * @param maxStringLength The maximum length of a field. Too long fields will + * be output as "null". The 0 value means no maximum length. + * @return The NULL value if the case of an error, or the JSON + * string. This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + * @see OrthancPluginDicomInstanceToJson + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson( + OrthancPluginContext* context, + const char* instanceId, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + char* result; + + _OrthancPluginDicomToJson params; + memset(¶ms, 0, sizeof(params)); + params.result = &result; + params.instanceId = instanceId; + params.format = format; + params.flags = flags; + params.maxStringLength = maxStringLength; + + if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + #ifdef __cplusplus } #endif
--- a/Resources/GenerateErrorCodes.py Tue Oct 20 17:39:58 2015 +0200 +++ b/Resources/GenerateErrorCodes.py Mon Oct 26 12:30:34 2015 +0100 @@ -141,3 +141,19 @@ with open(path, 'w') as f: f.write(a) + + + +## +## Generate the "PrintErrors" function in "main.cpp" +## + +path = os.path.join(BASE, 'OrthancServer', 'main.cpp') +with open(path, 'r') as f: + a = f.read() + +s = '\n'.join(map(lambda x: ' PrintErrorCode(ErrorCode_%s, "%s");' % (x['Name'], x['Description']), ERRORS)) +a = re.sub('(static void PrintErrors[^{}]*?{[^{}]*?{)([^}]*?)}', r'\1\n%s\n }' % s, a, re.DOTALL) + +with open(path, 'w') as f: + f.write(a)
--- a/UnitTestsSources/DicomMapTests.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/UnitTestsSources/DicomMapTests.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -36,7 +36,6 @@ #include "../Core/Uuid.h" #include "../Core/OrthancException.h" #include "../Core/DicomFormat/DicomMap.h" -#include "../Core/DicomFormat/DicomNullValue.h" #include "../OrthancServer/FromDcmtkBridge.h" #include <memory> @@ -103,7 +102,7 @@ m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID"); ASSERT_TRUE(m.HasTag(0x0010, 0x0020)); m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2"); - ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString()); + ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).GetContent()); m.GetTags(s); ASSERT_EQ(2u, s.size()); @@ -116,14 +115,14 @@ ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin()); std::auto_ptr<DicomMap> mm(m.Clone()); - ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString()); + ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).GetContent()); m.SetValue(DICOM_TAG_PATIENT_ID, "Hello"); ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException); mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID); - ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString()); + ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).GetContent()); - DicomNullValue v; + DicomValue v; ASSERT_TRUE(v.IsNull()); }
--- a/UnitTestsSources/FromDcmtkTests.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -339,7 +339,7 @@ element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)); Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii); ASSERT_EQ("Hello", b["0010,0010"].asString()); } @@ -363,7 +363,7 @@ element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8)); Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii); ASSERT_EQ("Hello", b["0010,0010"].asString()); } @@ -374,7 +374,7 @@ { Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii); ASSERT_EQ(Json::arrayValue, b["0008,1110"].type()); ASSERT_EQ(2, b["0008,1110"].size()); @@ -391,7 +391,7 @@ { Json::Value b; - FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, 0, Encoding_Ascii); + FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0, Encoding_Ascii); Json::Value c; Toolbox::SimplifyTags(c, b); @@ -470,7 +470,7 @@ { Json::Value b; - f.ToJson(b, DicomToJsonFormat_Full, 0); + f.ToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0); Json::Value c; Toolbox::SimplifyTags(c, b); @@ -515,8 +515,94 @@ f.Replace(DICOM_TAG_PATIENT_NAME, s, false); Json::Value v; - f.ToJson(v, DicomToJsonFormat_Simple, 0); + f.ToJson(v, DicomToJsonFormat_Simple, DicomToJsonFlags_Default, 0); ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i])); } } } + + +TEST(ParsedDicomFile, ToJsonFlags1) +{ + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), EVR_PN, "MyPrivateTag", 1, 1); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1); + + ParsedDicomFile f; + f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false); // Even group => public tag + f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false); // Even group => public, unknown tag + f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false); // Odd group => private tag + + Json::Value v; + f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_FALSE(v.isMember("7052,1000")); + ASSERT_FALSE(v.isMember("7053,1000")); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_EQ(Json::stringValue, v["7050,1000"].type()); + ASSERT_EQ("Some public tag", v["7050,1000"].asString()); + + f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(7, v.getMemberNames().size()); + ASSERT_FALSE(v.isMember("7052,1000")); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_TRUE(v.isMember("7053,1000")); + ASSERT_EQ("Some public tag", v["7050,1000"].asString()); + ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); // TODO SHOULD BE STRING + + f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludeUnknownTags, 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(7, v.getMemberNames().size()); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_TRUE(v.isMember("7052,1000")); + ASSERT_FALSE(v.isMember("7053,1000")); + ASSERT_EQ("Some public tag", v["7050,1000"].asString()); + ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); // TODO SHOULD BE STRING + + f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags), 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(8, v.getMemberNames().size()); + ASSERT_TRUE(v.isMember("7050,1000")); + ASSERT_TRUE(v.isMember("7052,1000")); + ASSERT_TRUE(v.isMember("7053,1000")); + ASSERT_EQ("Some public tag", v["7050,1000"].asString()); + ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); // TODO SHOULD BE STRING + ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); // TODO SHOULD BE STRING +} + + +TEST(ParsedDicomFile, ToJsonFlags2) +{ + ParsedDicomFile f; + f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false); + + Json::Value v; + f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(5, v.getMemberNames().size()); + ASSERT_FALSE(v.isMember("7fe0,0010")); + + f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_TRUE(v.isMember("7fe0,0010")); + ASSERT_EQ(Json::nullValue, v["7fe0,0010"].type()); + + f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_TRUE(v.isMember("7fe0,0010")); + ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type()); + ASSERT_EQ("Pixels", v["7fe0,0010"].asString()); + + f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0); + ASSERT_EQ(Json::objectValue, v.type()); + ASSERT_EQ(6, v.getMemberNames().size()); + ASSERT_TRUE(v.isMember("7fe0,0010")); + ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type()); + std::string mime, content; + Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString()); + ASSERT_EQ("application/octet-stream", mime); + ASSERT_EQ("Pixels", content); +}
--- a/UnitTestsSources/ServerIndexTests.cpp Tue Oct 20 17:39:58 2015 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Mon Oct 26 12:30:34 2015 +0100 @@ -33,7 +33,6 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../Core/DicomFormat/DicomNullValue.h" #include "../Core/FileStorage/FilesystemStorage.h" #include "../Core/Logging.h" #include "../Core/Uuid.h"