# HG changeset patch # User Sebastien Jodogne # Date 1597145078 -7200 # Node ID d1806b4e48394bb9d731c78fc1c8f07c50437efc # Parent de8cf5859e847bf4a7d3840b971828aa94331f21 moving OrthancStone/Samples/ as Applications/Samples/ diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,7 @@ +# This is the list of the symbols that must be exported by Orthanc +# plugins, if targeting OS X + +_OrthancPluginInitialize +_OrthancPluginFinalize +_OrthancPluginGetName +_OrthancPluginGetVersion diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,3383 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#include "OrthancPluginCppWrapper.h" + +#include +#include +#include +#include +#include + + +#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin; +#endif + + +namespace OrthancPlugins +{ + static OrthancPluginContext* globalContext_ = NULL; + + + void SetGlobalContext(OrthancPluginContext* context) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + else if (globalContext_ == NULL) + { + globalContext_ = context; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + } + + + bool HasGlobalContext() + { + return globalContext_ != NULL; + } + + + OrthancPluginContext* GetGlobalContext() + { + if (globalContext_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + else + { + return globalContext_; + } + } + + + void MemoryBuffer::Check(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + } + + if (code == OrthancPluginErrorCode_Success) + { + return true; + } + else if (code == OrthancPluginErrorCode_UnknownResource || + code == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + MemoryBuffer::MemoryBuffer() + { + buffer_.data = NULL; + buffer_.size = 0; + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + MemoryBuffer::MemoryBuffer(const void* buffer, + size_t size) + { + uint32_t s = static_cast(size); + if (static_cast(s) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != + OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else + { + memcpy(buffer_.data, buffer, size); + } + } +#endif + + + void MemoryBuffer::Clear() + { + if (buffer_.data != NULL) + { + OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_); + buffer_.data = NULL; + buffer_.size = 0; + } + } + + + void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) + { + Clear(); + + buffer_.data = other.data; + buffer_.size = other.size; + + other.data = NULL; + other.size = 0; + } + + + void MemoryBuffer::Swap(MemoryBuffer& other) + { + std::swap(buffer_.data, other.buffer_.data); + std::swap(buffer_.size, other.buffer_.size); + } + + + OrthancPluginMemoryBuffer MemoryBuffer::Release() + { + OrthancPluginMemoryBuffer result = buffer_; + + buffer_.data = NULL; + buffer_.size = 0; + + return result; + } + + + void MemoryBuffer::ToString(std::string& target) const + { + if (buffer_.size == 0) + { + target.clear(); + } + else + { + target.assign(reinterpret_cast(buffer_.data), buffer_.size); + } + } + + + void MemoryBuffer::ToJson(Json::Value& target) const + { + if (buffer_.data == NULL || + buffer_.size == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + const char* tmp = reinterpret_cast(buffer_.data); + + Json::Reader reader; + if (!reader.parse(tmp, tmp + buffer_.size, target)) + { + LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool MemoryBuffer::RestApiGet(const std::string& uri, + bool applyPlugins) + { + Clear(); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str())); + } + else + { + return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str())); + } + } + + bool MemoryBuffer::RestApiGet(const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + Clear(); + + std::vector headersKeys; + std::vector headersValues; + + for (std::map::const_iterator + it = httpHeaders.begin(); it != httpHeaders.end(); it++) + { + headersKeys.push_back(it->first.c_str()); + headersValues.push_back(it->second.c_str()); + } + + return CheckHttp(OrthancPluginRestApiGet2( + GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), + (headersKeys.empty() ? NULL : &headersKeys[0]), + (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); + } + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + // Cast for compatibility with Orthanc SDK <= 1.5.6 + const char* b = reinterpret_cast(body); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + // Cast for compatibility with Orthanc SDK <= 1.5.6 + const char* b = reinterpret_cast(body); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(uri, writer.write(body), applyPlugins); + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(uri, writer.write(body), applyPlugins); + } + + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags)); + } + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags)); + } + + + void MemoryBuffer::ReadFile(const std::string& path) + { + Clear(); + Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str())); + } + + + void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query) + { + Clear(); + Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query)); + } + + + void OrthancString::Assign(char* str) + { + Clear(); + + if (str != NULL) + { + str_ = str; + } + } + + + void OrthancString::Clear() + { + if (str_ != NULL) + { + OrthancPluginFreeString(GetGlobalContext(), str_); + str_ = NULL; + } + } + + + void OrthancString::ToString(std::string& target) const + { + if (str_ == NULL) + { + target.clear(); + } + else + { + target.assign(str_); + } + } + + + void OrthancString::ToJson(Json::Value& target) const + { + if (str_ == NULL) + { + LogError("Cannot convert an empty memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Reader reader; + if (!reader.parse(str_, target)) + { + LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void MemoryBuffer::DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + OrthancString str; + str.Assign(OrthancPluginDicomBufferToJson + (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); + str.ToJson(target); + } + + + bool MemoryBuffer::HttpGet(const std::string& url, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(), + body.c_str(), body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(), + body.empty() ? NULL : body.c_str(), + body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + void MemoryBuffer::GetDicomInstance(const std::string& instanceId) + { + Clear(); + Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str())); + } + + + bool HttpDelete(const std::string& url, + const std::string& username, + const std::string& password) + { + OrthancPluginErrorCode error = OrthancPluginHttpDelete + (GetGlobalContext(), url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str()); + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void LogError(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogError(GetGlobalContext(), message.c_str()); + } + } + + + void LogWarning(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); + } + } + + + void LogInfo(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); + } + } + + + void OrthancConfiguration::LoadConfiguration() + { + OrthancString str; + str.Assign(OrthancPluginGetConfiguration(GetGlobalContext())); + + if (str.GetContent() == NULL) + { + LogError("Cannot access the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + str.ToJson(configuration_); + + if (configuration_.type() != Json::objectValue) + { + LogError("Unable to read the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancConfiguration::OrthancConfiguration() + { + LoadConfiguration(); + } + + + OrthancConfiguration::OrthancConfiguration(bool loadConfiguration) + { + if (loadConfiguration) + { + LoadConfiguration(); + } + else + { + configuration_ = Json::objectValue; + } + } + + + std::string OrthancConfiguration::GetPath(const std::string& key) const + { + if (path_.empty()) + { + return key; + } + else + { + return path_ + "." + key; + } + } + + + bool OrthancConfiguration::IsSection(const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + return (configuration_.isMember(key) && + configuration_[key].type() == Json::objectValue); + } + + + void OrthancConfiguration::GetSection(OrthancConfiguration& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.path_ = GetPath(key); + + if (!configuration_.isMember(key)) + { + target.configuration_ = Json::objectValue; + } + else + { + if (configuration_[key].type() != Json::objectValue) + { + LogError("The configuration section \"" + target.path_ + + "\" is not an associative array as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target.configuration_ = configuration_[key]; + } + } + + + bool OrthancConfiguration::LookupStringValue(std::string& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::stringValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asString(); + return true; + } + + + bool OrthancConfiguration::LookupIntegerValue(int& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::intValue: + target = configuration_[key].asInt(); + return true; + + case Json::uintValue: + target = configuration_[key].asUInt(); + return true; + + default: + LogError("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const + { + int tmp; + if (!LookupIntegerValue(tmp, key)) + { + return false; + } + + if (tmp < 0) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a positive integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + target = static_cast(tmp); + return true; + } + } + + + bool OrthancConfiguration::LookupBooleanValue(bool& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::booleanValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a Boolean as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asBool(); + return true; + } + + + bool OrthancConfiguration::LookupFloatValue(float& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::realValue: + target = configuration_[key].asFloat(); + return true; + + case Json::intValue: + target = static_cast(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast(configuration_[key].asUInt()); + return true; + + default: + LogError("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupListOfStrings(std::list& target, + const std::string& key, + bool allowSingleString) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::arrayValue: + { + bool ok = true; + + for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++) + { + if (configuration_[key][i].type() == Json::stringValue) + { + target.push_back(configuration_[key][i].asString()); + } + else + { + ok = false; + } + } + + if (ok) + { + return true; + } + + break; + } + + case Json::stringValue: + if (allowSingleString) + { + target.push_back(configuration_[key].asString()); + return true; + } + + break; + + default: + break; + } + + LogError("The configuration option \"" + GetPath(key) + + "\" is not a list of strings as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + + bool OrthancConfiguration::LookupSetOfStrings(std::set& target, + const std::string& key, + bool allowSingleString) const + { + std::list lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list::const_iterator + it = lst.begin(); it != lst.end(); ++it) + { + target.insert(*it); + } + + return true; + } + else + { + return false; + } + } + + + std::string OrthancConfiguration::GetStringValue(const std::string& key, + const std::string& defaultValue) const + { + std::string tmp; + if (LookupStringValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + int OrthancConfiguration::GetIntegerValue(const std::string& key, + int defaultValue) const + { + int tmp; + if (LookupIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const + { + unsigned int tmp; + if (LookupUnsignedIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + bool OrthancConfiguration::GetBooleanValue(const std::string& key, + bool defaultValue) const + { + bool tmp; + if (LookupBooleanValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + float OrthancConfiguration::GetFloatValue(const std::string& key, + float defaultValue) const + { + float tmp; + if (LookupFloatValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + void OrthancConfiguration::GetDictionary(std::map& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return; + } + + if (configuration_[key].type() != Json::objectValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + Json::Value::Members members = configuration_[key].getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = configuration_[key][members[i]]; + + if (value.type() == Json::stringValue) + { + target[members[i]] = value.asString(); + } + else + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a dictionary mapping strings to strings"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + } + + + void OrthancImage::Clear() + { + if (image_ != NULL) + { + OrthancPluginFreeImage(GetGlobalContext(), image_); + image_ = NULL; + } + } + + + void OrthancImage::CheckImageAvailable() const + { + if (image_ == NULL) + { + LogError("Trying to access a NULL image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancImage::OrthancImage() : + image_(NULL) + { + } + + + OrthancImage::OrthancImage(OrthancPluginImage* image) : + image_(image) + { + } + + + OrthancImage::OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height); + + if (image_ == NULL) + { + LogError("Cannot create an image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancImage::OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + image_ = OrthancPluginCreateImageAccessor + (GetGlobalContext(), format, width, height, pitch, buffer); + + if (image_ == NULL) + { + LogError("Cannot create an image accessor"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + void OrthancImage::UncompressPngImage(const void* data, + size_t size) + { + Clear(); + + image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png); + + if (image_ == NULL) + { + LogError("Cannot uncompress a PNG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::UncompressJpegImage(const void* data, + size_t size) + { + Clear(); + image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg); + if (image_ == NULL) + { + LogError("Cannot uncompress a JPEG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::DecodeDicomImage(const void* data, + size_t size, + unsigned int frame) + { + Clear(); + image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame); + if (image_ == NULL) + { + LogError("Cannot uncompress a DICOM image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const + { + CheckImageAvailable(); + return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetWidth() const + { + CheckImageAvailable(); + return OrthancPluginGetImageWidth(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetHeight() const + { + CheckImageAvailable(); + return OrthancPluginGetImageHeight(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetPitch() const + { + CheckImageAvailable(); + return OrthancPluginGetImagePitch(GetGlobalContext(), image_); + } + + + void* OrthancImage::GetBuffer() const + { + CheckImageAvailable(); + return OrthancPluginGetImageBuffer(GetGlobalContext(), image_); + } + + + void OrthancImage::CompressPngImage(MemoryBuffer& target) const + { + CheckImageAvailable(); + + OrthancPlugins::MemoryBuffer answer; + OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + + target.Swap(answer); + } + + + void OrthancImage::CompressJpegImage(MemoryBuffer& target, + uint8_t quality) const + { + CheckImageAvailable(); + + OrthancPlugins::MemoryBuffer answer; + OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + + target.Swap(answer); + } + + + void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + } + + + void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) const + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + } + + + OrthancPluginImage* OrthancImage::Release() + { + CheckImageAvailable(); + OrthancPluginImage* tmp = image_; + image_ = NULL; + return tmp; + } + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) : + matcher_(NULL), + worklist_(worklist) + { + if (worklist_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void FindMatcher::SetupDicom(const void* query, + uint32_t size) + { + worklist_ = NULL; + + matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size); + if (matcher_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + FindMatcher::~FindMatcher() + { + // The "worklist_" field + + if (matcher_ != NULL) + { + OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_); + } + } + + + + bool FindMatcher::IsMatch(const void* dicom, + uint32_t size) const + { + int32_t result; + + if (matcher_ != NULL) + { + result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size); + } + else if (worklist_ != NULL) + { + result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + if (result == 0) + { + return false; + } + else if (result == 1) + { + return true; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + +#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ + + void AnswerJson(const Json::Value& value, + OrthancPluginRestOutput* output + ) + { + Json::StyledWriter writer; + std::string bodyString = writer.write(value); + + OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json"); + } + + void AnswerString(const std::string& answer, + const char* mimeType, + OrthancPluginRestOutput* output + ) + { + OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType); + } + + void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output) + { + OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError); + } + + void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods); + } + + bool RestApiGetString(std::string& result, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer; + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + answer.ToString(result); + return true; + } + } + + bool RestApiGetString(std::string& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answer; + if (!answer.RestApiGet(uri, httpHeaders, applyPlugins)) + { + return false; + } + else + { + answer.ToString(result); + return true; + } + } + + + + bool RestApiGet(Json::Value& result, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPost(std::string& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + result.assign(answer.GetData(), answer.GetSize()); + } + return true; + } + } + + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(result, uri, writer.write(body), applyPlugins); + } + + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPut(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(result, uri, writer.write(body), applyPlugins); + } + + + bool RestApiDelete(const std::string& uri, + bool applyPlugins) + { + OrthancPluginErrorCode error; + + if (applyPlugins) + { + error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str()); + } + else + { + error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str()); + } + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void ReportMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + LogError("Your version of the Orthanc core (" + + std::string(GetGlobalContext()->orthancVersion) + + ") is too old to run this plugin (version " + + boost::lexical_cast(major) + "." + + boost::lexical_cast(minor) + "." + + boost::lexical_cast(revision) + + " is required)"); + } + + + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + if (!HasGlobalContext()) + { + LogError("Bad Orthanc context in the plugin"); + return false; + } + + if (!strcmp(GetGlobalContext()->orthancVersion, "mainline")) + { + // Assume compatibility with the mainline + return true; + } + + // Parse the version of the Orthanc core + int aa, bb, cc; + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || + aa < 0 || + bb < 0 || + cc < 0) + { + return false; + } + + unsigned int a = static_cast(aa); + unsigned int b = static_cast(bb); + unsigned int c = static_cast(cc); + + // Check the major version number + + if (a > major) + { + return true; + } + + if (a < major) + { + return false; + } + + + // Check the minor version number + assert(a == major); + + if (b > minor) + { + return true; + } + + if (b < minor) + { + return false; + } + + // Check the patch level version number + assert(a == major && b == minor); + + if (c >= revision) + { + return true; + } + else + { + return false; + } + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) + const char* AutodetectMimeType(const std::string& path) + { + const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str()); + + if (mime == NULL) + { + // Should never happen, just for safety + return "application/octet-stream"; + } + else + { + return mime; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + size_t OrthancPeers::GetPeerIndex(const std::string& name) const + { + size_t index; + if (LookupName(index, name)) + { + return index; + } + else + { + LogError("Inexistent peer: " + name); + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + OrthancPeers::OrthancPeers() : + peers_(NULL), + timeout_(0) + { + peers_ = OrthancPluginGetPeers(GetGlobalContext()); + + if (peers_ == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_); + + for (uint32_t i = 0; i < count; i++) + { + const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i); + if (name == NULL) + { + OrthancPluginFreePeers(GetGlobalContext(), peers_); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + index_[name] = i; + } + } + + + OrthancPeers::~OrthancPeers() + { + if (peers_ != NULL) + { + OrthancPluginFreePeers(GetGlobalContext(), peers_); + } + } + + + bool OrthancPeers::LookupName(size_t& target, + const std::string& name) const + { + Index::const_iterator found = index_.find(name); + + if (found == index_.end()) + { + return false; + } + else + { + target = found->second; + return true; + } + } + + + std::string OrthancPeers::GetPeerName(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(const std::string& name) const + { + return GetPeerUrl(GetPeerIndex(name)); + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast(index), key.c_str()); + if (s == NULL) + { + return false; + } + else + { + value.assign(s); + return true; + } + } + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const + { + return LookupUserProperty(value, GetPeerIndex(peer), key); + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Get, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Swap(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoGet(target, index, uri)); + } + + + bool OrthancPeers::DoGet(Json::Value& target, + size_t index, + const std::string& uri) const + { + MemoryBuffer buffer; + + if (DoGet(buffer, index, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const + { + MemoryBuffer buffer; + + if (DoGet(buffer, name, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPost(target, index, uri, body)); + } + + + bool OrthancPeers::DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, index, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, name, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Post, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Swap(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Put, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPut(index, uri, body)); + } + + + bool OrthancPeers::DoDelete(size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Delete, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoDelete(const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoDelete(index, uri)); + } +#endif + + + + + + /****************************************************************** + ** JOBS + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + void OrthancJob::CallbackFinalize(void* job) + { + if (job != NULL) + { + delete reinterpret_cast(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast(job); + + if (tmp.hasSerialized_) + { + return tmp.serialized_.c_str(); + } + else + { + return NULL; + } + } + catch (...) + { + return 0; + } + } + + + OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->Step(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + return OrthancPluginJobStepStatus_Failure; + } + catch (...) + { + return OrthancPluginJobStepStatus_Failure; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackStop(void* job, + OrthancPluginJobStopReason reason) + { + assert(job != NULL); + + try + { + reinterpret_cast(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void OrthancJob::ClearContent() + { + Json::Value empty = Json::objectValue; + UpdateContent(empty); + } + + + void OrthancJob::UpdateContent(const Json::Value& content) + { + if (content.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + content_ = writer.write(content); + } + } + + + void OrthancJob::ClearSerialized() + { + hasSerialized_ = false; + serialized_.clear(); + } + + + void OrthancJob::UpdateSerialized(const Json::Value& serialized) + { + if (serialized.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + serialized_ = writer.write(serialized); + hasSerialized_ = true; + } + } + + + void OrthancJob::UpdateProgress(float progress) + { + if (progress < 0 || + progress > 1) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + progress_ = progress; + } + + + OrthancJob::OrthancJob(const std::string& jobType) : + jobType_(jobType), + progress_(0) + { + ClearContent(); + ClearSerialized(); + } + + + OrthancPluginJob* OrthancJob::Create(OrthancJob* job) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = OrthancPluginCreateJob( + GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), + CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, + CallbackStep, CallbackStop, CallbackReset); + + if (orthanc == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return orthanc; + } + } + + + std::string OrthancJob::Submit(OrthancJob* job, + int priority) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = Create(job); + + char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority); + + if (id == NULL) + { + LogError("Plugin cannot submit job"); + OrthancPluginFreeJob(GetGlobalContext(), orthanc); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + std::string tmp(id); + tmp.assign(id); + OrthancPluginFreeString(GetGlobalContext(), id); + + return tmp; + } + } + + + void OrthancJob::SubmitAndWait(Json::Value& result, + OrthancJob* job /* takes ownership */, + int priority) + { + std::string id = Submit(job, priority); + + for (;;) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + Json::Value status; + if (!RestApiGet(status, "/jobs/" + id, false) || + !status.isMember("State") || + status["State"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem); + } + + const std::string state = status["State"].asString(); + if (state == "Success") + { + if (status.isMember("Content")) + { + result = status["Content"]; + } + else + { + result = Json::objectValue; + } + + return; + } + else if (state == "Running") + { + continue; + } + else if (!status.isMember("ErrorCode") || + status["ErrorCode"].type() != Json::intValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); + } + else + { + if (!status.isMember("ErrorDescription") || + status["ErrorDescription"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); + } + else + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(static_cast(status["ErrorCode"].asInt()), + status["ErrorDescription"].asString()); +#else + LogError("Exception while executing the job: " + status["ErrorDescription"].asString()); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); +#endif + } + } + } + } + + + void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job) + { + static const char* KEY_SYNCHRONOUS = "Synchronous"; + static const char* KEY_ASYNCHRONOUS = "Asynchronous"; + static const char* KEY_PRIORITY = "Priority"; + + boost::movelib::unique_ptr protection(job); + + if (body.type() != Json::objectValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Expected a JSON object in the body"); +#else + LogError("Expected a JSON object in the body"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + + bool synchronous = true; + + if (body.isMember(KEY_SYNCHRONOUS)) + { + if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_SYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = body[KEY_SYNCHRONOUS].asBool(); + } + } + + if (body.isMember(KEY_ASYNCHRONOUS)) + { + if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_ASYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = !body[KEY_ASYNCHRONOUS].asBool(); + } + } + + int priority = 0; + + if (body.isMember(KEY_PRIORITY)) + { + if (body[KEY_PRIORITY].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_PRIORITY) + + "\" must be an integer"); +#else + LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + priority = !body[KEY_PRIORITY].asInt(); + } + } + + Json::Value result; + + if (synchronous) + { + OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority); + } + else + { + std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority); + + result = Json::objectValue; + result["ID"] = id; + result["Path"] = "/jobs/" + id; + } + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), + s.size(), "application/json"); + } + +#endif + + + + + /****************************************************************** + ** METRICS + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_METRICS == 1 + MetricsTimer::MetricsTimer(const char* name) : + name_(name) + { + start_ = boost::posix_time::microsec_clock::universal_time(); + } + + MetricsTimer::~MetricsTimer() + { + const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time(); + const boost::posix_time::time_duration diff = stop - start_; + OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast(diff.total_milliseconds()), + OrthancPluginMetricsType_Timer); + } +#endif + + + + + /****************************************************************** + ** HTTP CLIENT + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 + class HttpClient::RequestBodyWrapper : public boost::noncopyable + { + private: + static RequestBodyWrapper& GetObject(void* body) + { + assert(body != NULL); + return *reinterpret_cast(body); + } + + IRequestBody& body_; + bool done_; + std::string chunk_; + + public: + RequestBodyWrapper(IRequestBody& body) : + body_(body), + done_(false) + { + } + + static uint8_t IsDone(void* body) + { + return GetObject(body).done_; + } + + static const void* GetChunkData(void* body) + { + return GetObject(body).chunk_.c_str(); + } + + static uint32_t GetChunkSize(void* body) + { + return static_cast(GetObject(body).chunk_.size()); + } + + static OrthancPluginErrorCode Next(void* body) + { + RequestBodyWrapper& that = GetObject(body); + + if (that.done_) + { + return OrthancPluginErrorCode_BadSequenceOfCalls; + } + else + { + try + { + that.done_ = !that.body_.ReadNextChunk(that.chunk_); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } + } + }; + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer, + const char* key, + const char* value) + { + assert(answer != NULL && key != NULL && value != NULL); + + try + { + reinterpret_cast(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer, + const void* data, + uint32_t size) + { + assert(answer != NULL); + + try + { + reinterpret_cast(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + + HttpClient::HttpClient() : + httpStatus_(0), + method_(OrthancPluginHttpMethod_Get), + timeout_(0), + pkcs11_(false), + chunkedBody_(NULL), + allowChunkedTransfers_(true) + { + } + + + void HttpClient::AddHeaders(const HttpHeaders& headers) + { + for (HttpHeaders::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + headers_[it->first] = it->second; + } + } + + + void HttpClient::SetCredentials(const std::string& username, + const std::string& password) + { + username_ = username; + password_ = password; + } + + + void HttpClient::ClearCredentials() + { + username_.empty(); + password_.empty(); + } + + + void HttpClient::SetCertificate(const std::string& certificateFile, + const std::string& keyFile, + const std::string& keyPassword) + { + certificateFile_ = certificateFile; + certificateKeyFile_ = keyFile; + certificateKeyPassword_ = keyPassword; + } + + + void HttpClient::ClearCertificate() + { + certificateFile_.clear(); + certificateKeyFile_.clear(); + certificateKeyPassword_.clear(); + } + + + void HttpClient::ClearBody() + { + fullBody_.clear(); + chunkedBody_ = NULL; + } + + + void HttpClient::SwapBody(std::string& body) + { + fullBody_.swap(body); + chunkedBody_ = NULL; + } + + + void HttpClient::SetBody(const std::string& body) + { + fullBody_ = body; + chunkedBody_ = NULL; + } + + + void HttpClient::SetBody(IRequestBody& body) + { + fullBody_.clear(); + chunkedBody_ = &body; + } + + + namespace + { + class HeadersWrapper : public boost::noncopyable + { + private: + std::vector headersKeys_; + std::vector headersValues_; + + public: + HeadersWrapper(const HttpClient::HttpHeaders& headers) + { + headersKeys_.reserve(headers.size()); + headersValues_.reserve(headers.size()); + + for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headersKeys_.push_back(it->first.c_str()); + headersValues_.push_back(it->second.c_str()); + } + } + + void AddStaticString(const char* key, + const char* value) + { + headersKeys_.push_back(key); + headersValues_.push_back(value); + } + + uint32_t GetCount() const + { + return headersKeys_.size(); + } + + const char* const* GetKeys() const + { + return headersKeys_.empty() ? NULL : &headersKeys_[0]; + } + + const char* const* GetValues() const + { + return headersValues_.empty() ? NULL : &headersValues_[0]; + } + }; + + + class MemoryRequestBody : public HttpClient::IRequestBody + { + private: + std::string body_; + bool done_; + + public: + MemoryRequestBody(const std::string& body) : + body_(body), + done_(false) + { + if (body_.empty()) + { + done_ = true; + } + } + + virtual bool ReadNextChunk(std::string& chunk) + { + if (done_) + { + return false; + } + else + { + chunk.swap(body_); + done_ = true; + return true; + } + } + }; + + + // This class mimics Orthanc::ChunkedBuffer + class ChunkedBuffer : public boost::noncopyable + { + private: + typedef std::list Content; + + Content content_; + size_t size_; + + public: + ChunkedBuffer() : + size_(0) + { + } + + ~ChunkedBuffer() + { + Clear(); + } + + void Clear() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + + content_.clear(); + } + + void Flatten(std::string& target) const + { + target.resize(size_); + + size_t pos = 0; + + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + size_t s = (*it)->size(); + + if (s != 0) + { + memcpy(&target[pos], (*it)->c_str(), s); + pos += s; + } + } + + assert(size_ == 0 || + pos == target.size()); + } + + void AddChunk(const void* data, + size_t size) + { + content_.push_back(new std::string(reinterpret_cast(data), size)); + size_ += size; + } + + void AddChunk(const std::string& chunk) + { + content_.push_back(new std::string(chunk)); + size_ += chunk.size(); + } + }; + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + class MemoryAnswer : public HttpClient::IAnswer + { + private: + HttpClient::HttpHeaders headers_; + ChunkedBuffer body_; + + public: + const HttpClient::HttpHeaders& GetHeaders() const + { + return headers_; + } + + const ChunkedBuffer& GetBody() const + { + return body_; + } + + virtual void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + virtual void AddChunk(const void* data, + size_t size) + { + body_.AddChunk(data, size); + } + }; +#endif + } + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + void HttpClient::ExecuteWithStream(uint16_t& httpStatus, + IAnswer& answer, + IRequestBody& body) const + { + HeadersWrapper h(headers_); + + if (method_ == OrthancPluginHttpMethod_Post || + method_ == OrthancPluginHttpMethod_Put) + { + // Automatically set the "Transfer-Encoding" header if absent + bool found = false; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + if (boost::iequals(it->first, "Transfer-Encoding")) + { + found = true; + break; + } + } + + if (!found) + { + h.AddStaticString("Transfer-Encoding", "chunked"); + } + } + + RequestBodyWrapper request(body); + + OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient( + GetGlobalContext(), + &answer, + AnswerAddChunkCallback, + AnswerAddHeaderCallback, + &httpStatus, + method_, + url_.c_str(), + h.GetCount(), + h.GetKeys(), + h.GetValues(), + &request, + RequestBodyWrapper::IsDone, + RequestBodyWrapper::GetChunkData, + RequestBodyWrapper::GetChunkSize, + RequestBodyWrapper::Next, + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } +#endif + + + void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, + HttpHeaders& answerHeaders, + std::string& answerBody, + const std::string& body) const + { + HeadersWrapper headers(headers_); + + MemoryBuffer answerBodyBuffer, answerHeadersBuffer; + + OrthancPluginErrorCode error = OrthancPluginHttpClient( + GetGlobalContext(), + *answerBodyBuffer, + *answerHeadersBuffer, + &httpStatus, + method_, + url_.c_str(), + headers.GetCount(), + headers.GetKeys(), + headers.GetValues(), + body.empty() ? NULL : body.c_str(), + body.size(), + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + + Json::Value v; + answerHeadersBuffer.ToJson(v); + + if (v.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Value::Members members = v.getMemberNames(); + answerHeaders.clear(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& h = v[members[i]]; + if (h.type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + answerHeaders[members[i]] = h.asString(); + } + } + + answerBodyBuffer.ToString(answerBody); + } + + + void HttpClient::Execute(IAnswer& answer) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + if (allowChunkedTransfers_) + { + if (chunkedBody_ != NULL) + { + ExecuteWithStream(httpStatus_, answer, *chunkedBody_); + } + else + { + MemoryRequestBody wrapper(fullBody_); + ExecuteWithStream(httpStatus_, answer, wrapper); + } + + return; + } +#endif + + // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked + // transfers are disabled. This results in higher memory usage + // (all chunks from the answer body are sent at once) + + HttpHeaders answerHeaders; + std::string answerBody; + Execute(answerHeaders, answerBody); + + for (HttpHeaders::const_iterator it = answerHeaders.begin(); + it != answerHeaders.end(); ++it) + { + answer.AddHeader(it->first, it->second); + } + + if (!answerBody.empty()) + { + answer.AddChunk(answerBody.c_str(), answerBody.size()); + } + } + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + if (allowChunkedTransfers_) + { + MemoryAnswer answer; + Execute(answer); + answerHeaders = answer.GetHeaders(); + answer.GetBody().Flatten(answerBody); + return; + } +#endif + + // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked + // transfers are disabled. This results in higher memory usage + // (all chunks from the request body are sent at once) + + if (chunkedBody_ != NULL) + { + ChunkedBuffer buffer; + + std::string chunk; + while (chunkedBody_->ReadNextChunk(chunk)) + { + buffer.AddChunk(chunk); + } + + std::string body; + buffer.Flatten(body); + + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body); + } + else + { + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_); + } + } + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + Json::Value& answerBody /* out */) + { + std::string body; + Execute(answerHeaders, body); + + Json::Reader reader; + if (!reader.parse(body, answerBody)) + { + LogError("Cannot convert HTTP answer body to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void HttpClient::Execute() + { + HttpHeaders answerHeaders; + std::string body; + Execute(answerHeaders, body); + } + +#endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ + + + + + + /****************************************************************** + ** CHUNKED HTTP SERVER + ******************************************************************/ + + namespace Internals + { + void NullRestCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + } + + IChunkedRequestReader *NullChunkedRestCallback(const char* url, + const OrthancPluginHttpRequest* request) + { + return NULL; + } + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + + OrthancPluginErrorCode ChunkedRequestReaderAddChunk( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + + reinterpret_cast(reader)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode ChunkedRequestReaderExecute( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + + reinterpret_cast(reader)->Execute(output); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader) + { + if (reader != NULL) + { + delete reinterpret_cast(reader); + } + } + +#else + + OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler) + { + try + { + std::string allowed; + + if (GetHandler != Internals::NullRestCallback) + { + allowed += "GET"; + } + + if (PostHandler != Internals::NullChunkedRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "POST"; + } + + if (DeleteHandler != Internals::NullRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "DELETE"; + } + + if (PutHandler != Internals::NullChunkedRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "PUT"; + } + + switch (request->method) + { + case OrthancPluginHttpMethod_Get: + if (GetHandler == Internals::NullRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + GetHandler(output, url, request); + } + + break; + + case OrthancPluginHttpMethod_Post: + if (PostHandler == Internals::NullChunkedRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + boost::movelib::unique_ptr reader(PostHandler(url, request)); + if (reader.get() == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + reader->AddChunk(request->body, request->bodySize); + reader->Execute(output); + } + } + + break; + + case OrthancPluginHttpMethod_Delete: + if (DeleteHandler == Internals::NullRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + DeleteHandler(output, url, request); + } + + break; + + case OrthancPluginHttpMethod_Put: + if (PutHandler == Internals::NullChunkedRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + boost::movelib::unique_ptr reader(PutHandler(url, request)); + if (reader.get() == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + reader->AddChunk(request->body, request->bodySize); + reader->Execute(output); + } + } + + break; + + default: + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { +#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 + if (HasGlobalContext() && + e.HasDetails()) + { + // The "false" instructs Orthanc not to log the detailed + // error message. This is to avoid duplicating the details, + // because "OrthancException" already does it on construction. + OrthancPluginSetHttpErrorDetails + (GetGlobalContext(), output, e.GetDetails(), false); + } +#endif + + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + } + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup( + OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid) + { + assert(target != NULL && + rawHandler != NULL); + + try + { + IStorageCommitmentScpHandler& handler = *reinterpret_cast(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + void IStorageCommitmentScpHandler::Destructor(void* rawHandler) + { + assert(rawHandler != NULL); + delete reinterpret_cast(rawHandler); + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) : + toFree_(false), + instance_(instance) + { + } +#else + DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) : + toFree_(false), + instance_(instance) + { + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance::DicomInstance(const void* buffer, + size_t size) : + toFree_(true), + instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size)) + { + if (instance_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + } +#endif + + + DicomInstance::~DicomInstance() + { +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + if (toFree_ && + instance_ != NULL) + { + OrthancPluginFreeDicomInstance( + GetGlobalContext(), const_cast(instance_)); + } +#endif + } + + + std::string DicomInstance::GetRemoteAet() const + { + const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return std::string(s); + } + } + + + void DicomInstance::GetJson(Json::Value& target) const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_)); + s.ToJson(target); + } + + + void DicomInstance::GetSimplifiedJson(Json::Value& target) const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_)); + s.ToJson(target); + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + std::string DicomInstance::GetTransferSyntaxUid() const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_)); + + std::string result; + s.ToString(result); + return result; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + bool DicomInstance::HasPixelData() const + { + int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_); + if (result < 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return (result != 0); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void DicomInstance::GetRawFrame(std::string& target, + unsigned int frameIndex) const + { + MemoryBuffer buffer; + OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame( + GetGlobalContext(), *buffer, instance_, frameIndex); + + if (code == OrthancPluginErrorCode_Success) + { + buffer.ToString(target); + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const + { + OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame( + GetGlobalContext(), instance_, frameIndex); + + if (image == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return new OrthancImage(image); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void DicomInstance::Serialize(std::string& target) const + { + MemoryBuffer buffer; + OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance( + GetGlobalContext(), *buffer, instance_); + + if (code == OrthancPluginErrorCode_Success) + { + buffer.ToString(target); + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance* DicomInstance::Transcode(const void* buffer, + size_t size, + const std::string& transferSyntax) + { + OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance( + GetGlobalContext(), buffer, size, transferSyntax.c_str()); + + if (instance == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + boost::movelib::unique_ptr result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#endif +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,1228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#pragma once + +#include "OrthancPluginException.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for + * backward compatibility with Orthanc SDK <= 1.3.0. + * + * $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h + * + **/ +#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + +#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE) +#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_VERSION_MAJOR > major || \ + (ORTHANC_VERSION_MAJOR == major && \ + (ORTHANC_VERSION_MINOR > minor || \ + (ORTHANC_VERSION_MINOR == minor && \ + ORTHANC_VERSION_REVISION >= revision)))) +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0 +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1 +#else +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0 +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2) +# define HAS_ORTHANC_PLUGIN_PEERS 1 +# define HAS_ORTHANC_PLUGIN_JOB 1 +#else +# define HAS_ORTHANC_PLUGIN_PEERS 0 +# define HAS_ORTHANC_PLUGIN_JOB 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) +# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 1 +#else +# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) +# define HAS_ORTHANC_PLUGIN_METRICS 1 +#else +# define HAS_ORTHANC_PLUGIN_METRICS 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0) +# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 1 +#else +# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 1 +#else +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 1 +#else +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0) +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1 +#else +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 +#endif + + + +namespace OrthancPlugins +{ + typedef void (*RestCallback) (OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + void SetGlobalContext(OrthancPluginContext* context); + + bool HasGlobalContext(); + + OrthancPluginContext* GetGlobalContext(); + + + class OrthancImage; + + + class MemoryBuffer : public boost::noncopyable + { + private: + OrthancPluginMemoryBuffer buffer_; + + void Check(OrthancPluginErrorCode code); + + bool CheckHttp(OrthancPluginErrorCode code); + + public: + MemoryBuffer(); + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + // This constructor makes a copy of the given buffer in the memory + // handled by the Orthanc core + MemoryBuffer(const void* buffer, + size_t size); +#endif + + ~MemoryBuffer() + { + Clear(); + } + + OrthancPluginMemoryBuffer* operator*() + { + return &buffer_; + } + + // This transfers ownership from "other" to "this" + void Assign(OrthancPluginMemoryBuffer& other); + + void Swap(MemoryBuffer& other); + + OrthancPluginMemoryBuffer Release(); + + const char* GetData() const + { + if (buffer_.size > 0) + { + return reinterpret_cast(buffer_.data); + } + else + { + return NULL; + } + } + + size_t GetSize() const + { + return buffer_.size; + } + + bool IsEmpty() const + { + return GetSize() == 0 || GetData() == NULL; + } + + void Clear(); + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + + bool RestApiGet(const std::string& uri, + bool applyPlugins); + + bool RestApiGet(const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + bool RestApiPut(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + void CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags); + + void CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags); + + void ReadFile(const std::string& path); + + void GetDicomQuery(const OrthancPluginWorklistQuery* query); + + void DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength); + + bool HttpGet(const std::string& url, + const std::string& username, + const std::string& password); + + bool HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + bool HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + void GetDicomInstance(const std::string& instanceId); + }; + + + class OrthancString : public boost::noncopyable + { + private: + char* str_; + + void Clear(); + + public: + OrthancString() : + str_(NULL) + { + } + + ~OrthancString() + { + Clear(); + } + + // This transfers ownership, warning: The string must have been + // allocated by the Orthanc core + void Assign(char* str); + + const char* GetContent() const + { + return str_; + } + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + }; + + + class OrthancConfiguration : public boost::noncopyable + { + private: + Json::Value configuration_; // Necessarily a Json::objectValue + std::string path_; + + std::string GetPath(const std::string& key) const; + + void LoadConfiguration(); + + public: + OrthancConfiguration(); + + explicit OrthancConfiguration(bool load); + + const Json::Value& GetJson() const + { + return configuration_; + } + + bool IsSection(const std::string& key) const; + + void GetSection(OrthancConfiguration& target, + const std::string& key) const; + + bool LookupStringValue(std::string& target, + const std::string& key) const; + + bool LookupIntegerValue(int& target, + const std::string& key) const; + + bool LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const; + + bool LookupBooleanValue(bool& target, + const std::string& key) const; + + bool LookupFloatValue(float& target, + const std::string& key) const; + + bool LookupListOfStrings(std::list& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set& target, + const std::string& key, + bool allowSingleString) const; + + std::string GetStringValue(const std::string& key, + const std::string& defaultValue) const; + + int GetIntegerValue(const std::string& key, + int defaultValue) const; + + unsigned int GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const; + + bool GetBooleanValue(const std::string& key, + bool defaultValue) const; + + float GetFloatValue(const std::string& key, + float defaultValue) const; + + void GetDictionary(std::map& target, + const std::string& key) const; + }; + + class OrthancImage : public boost::noncopyable + { + private: + OrthancPluginImage* image_; + + void Clear(); + + void CheckImageAvailable() const; + + public: + OrthancImage(); + + explicit OrthancImage(OrthancPluginImage* image); + + OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height); + + OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer); + + ~OrthancImage() + { + Clear(); + } + + void UncompressPngImage(const void* data, + size_t size); + + void UncompressJpegImage(const void* data, + size_t size); + + void DecodeDicomImage(const void* data, + size_t size, + unsigned int frame); + + OrthancPluginPixelFormat GetPixelFormat() const; + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + unsigned int GetPitch() const; + + void* GetBuffer() const; + + const OrthancPluginImage* GetObject() const + { + return image_; + } + + void CompressPngImage(MemoryBuffer& target) const; + + void CompressJpegImage(MemoryBuffer& target, + uint8_t quality) const; + + void AnswerPngImage(OrthancPluginRestOutput* output) const; + + void AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) const; + + void* GetWriteableBuffer(); + + OrthancPluginImage* Release(); + }; + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + class FindMatcher : public boost::noncopyable + { + private: + OrthancPluginFindMatcher* matcher_; + const OrthancPluginWorklistQuery* worklist_; + + void SetupDicom(const void* query, + uint32_t size); + + public: + explicit FindMatcher(const OrthancPluginWorklistQuery* worklist); + + FindMatcher(const void* query, + uint32_t size) + { + SetupDicom(query, size); + } + + explicit FindMatcher(const MemoryBuffer& dicom) + { + SetupDicom(dicom.GetData(), dicom.GetSize()); + } + + ~FindMatcher(); + + bool IsMatch(const void* dicom, + uint32_t size) const; + + bool IsMatch(const MemoryBuffer& dicom) const + { + return IsMatch(dicom.GetData(), dicom.GetSize()); + } + }; +#endif + + + bool RestApiGet(Json::Value& result, + const std::string& uri, + bool applyPlugins); + + bool RestApiGetString(std::string& result, + const std::string& uri, + bool applyPlugins); + + bool RestApiGetString(std::string& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(std::string& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPost(Json::Value& result, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + inline bool RestApiPost(Json::Value& result, + const std::string& uri, + const MemoryBuffer& body, + bool applyPlugins) + { + return RestApiPost(result, uri, body.GetData(), + body.GetSize(), applyPlugins); + } + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPut(Json::Value& result, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + bool RestApiDelete(const std::string& uri, + bool applyPlugins); + + bool HttpDelete(const std::string& url, + const std::string& username, + const std::string& password); + + void AnswerJson(const Json::Value& value, + OrthancPluginRestOutput* output); + + void AnswerString(const std::string& answer, + const char* mimeType, + OrthancPluginRestOutput* output); + + void AnswerHttpError(uint16_t httpError, + OrthancPluginRestOutput* output); + + void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods); + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) + const char* AutodetectMimeType(const std::string& path); +#endif + + void LogError(const std::string& message); + + void LogWarning(const std::string& message); + + void LogInfo(const std::string& message); + + void ReportMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision); + + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision); + + + namespace Internals + { + template + static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + Callback(output, url, request); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { +#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 + if (HasGlobalContext() && + e.HasDetails()) + { + // The "false" instructs Orthanc not to log the detailed + // error message. This is to avoid duplicating the details, + // because "OrthancException" already does it on construction. + OrthancPluginSetHttpErrorDetails + (GetGlobalContext(), output, e.GetDetails(), false); + } +#endif + + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template + void RegisterRestCallback(const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock + (GetGlobalContext(), uri.c_str(), Internals::Protect); + } + else + { + OrthancPluginRegisterRestCallback + (GetGlobalContext(), uri.c_str(), Internals::Protect); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map Index; + + OrthancPluginPeers *peers_; + Index index_; + uint32_t timeout_; + + size_t GetPeerIndex(const std::string& name) const; + + public: + OrthancPeers(); + + ~OrthancPeers(); + + uint32_t GetTimeout() const + { + return timeout_; + } + + void SetTimeout(uint32_t timeout) + { + timeout_ = timeout; + } + + bool LookupName(size_t& target, + const std::string& name) const; + + std::string GetPeerName(size_t index) const; + + std::string GetPeerUrl(size_t index) const; + + std::string GetPeerUrl(const std::string& name) const; + + size_t GetPeersCount() const + { + return index_.size(); + } + + bool LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const; + + bool LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const; + + bool DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const; + + bool DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + size_t index, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const; + + bool DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPut(size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoDelete(size_t index, + const std::string& uri) const; + + bool DoDelete(const std::string& name, + const std::string& uri) const; + }; +#endif + + + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + class OrthancJob : public boost::noncopyable + { + private: + std::string jobType_; + std::string content_; + bool hasSerialized_; + std::string serialized_; + float progress_; + + static void CallbackFinalize(void* job); + + static float CallbackGetProgress(void* job); + + static const char* CallbackGetContent(void* job); + + static const char* CallbackGetSerialized(void* job); + + static OrthancPluginJobStepStatus CallbackStep(void* job); + + static OrthancPluginErrorCode CallbackStop(void* job, + OrthancPluginJobStopReason reason); + + static OrthancPluginErrorCode CallbackReset(void* job); + + protected: + void ClearContent(); + + void UpdateContent(const Json::Value& content); + + void ClearSerialized(); + + void UpdateSerialized(const Json::Value& serialized); + + void UpdateProgress(float progress); + + public: + OrthancJob(const std::string& jobType); + + virtual ~OrthancJob() + { + } + + virtual OrthancPluginJobStepStatus Step() = 0; + + virtual void Stop(OrthancPluginJobStopReason reason) = 0; + + virtual void Reset() = 0; + + static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */); + + static std::string Submit(OrthancJob* job /* takes ownership */, + int priority); + + static void SubmitAndWait(Json::Value& result, + OrthancJob* job /* takes ownership */, + int priority); + + // Submit a job from a POST on the REST API with the same + // conventions as in the Orthanc core (according to the + // "Synchronous" and "Priority" options) + static void SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_METRICS == 1 + inline void SetMetricsValue(char* name, + float value) + { + OrthancPluginSetMetricsValue(GetGlobalContext(), name, + value, OrthancPluginMetricsType_Default); + } + + class MetricsTimer : public boost::noncopyable + { + private: + std::string name_; + boost::posix_time::ptime start_; + + public: + explicit MetricsTimer(const char* name); + + ~MetricsTimer(); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 + class HttpClient : public boost::noncopyable + { + public: + typedef std::map HttpHeaders; + + class IRequestBody : public boost::noncopyable + { + public: + virtual ~IRequestBody() + { + } + + virtual bool ReadNextChunk(std::string& chunk) = 0; + }; + + + class IAnswer : public boost::noncopyable + { + public: + virtual ~IAnswer() + { + } + + virtual void AddHeader(const std::string& key, + const std::string& value) = 0; + + virtual void AddChunk(const void* data, + size_t size) = 0; + }; + + + private: + class RequestBodyWrapper; + + uint16_t httpStatus_; + OrthancPluginHttpMethod method_; + std::string url_; + HttpHeaders headers_; + std::string username_; + std::string password_; + uint32_t timeout_; + std::string certificateFile_; + std::string certificateKeyFile_; + std::string certificateKeyPassword_; + bool pkcs11_; + std::string fullBody_; + IRequestBody* chunkedBody_; + bool allowChunkedTransfers_; + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + void ExecuteWithStream(uint16_t& httpStatus, // out + IAnswer& answer, // out + IRequestBody& body) const; +#endif + + void ExecuteWithoutStream(uint16_t& httpStatus, // out + HttpHeaders& answerHeaders, // out + std::string& answerBody, // out + const std::string& body) const; + + public: + HttpClient(); + + uint16_t GetHttpStatus() const + { + return httpStatus_; + } + + void SetMethod(OrthancPluginHttpMethod method) + { + method_ = method; + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + void SetHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + void AddHeaders(const HttpHeaders& headers); + + void SetCredentials(const std::string& username, + const std::string& password); + + void ClearCredentials(); + + void SetTimeout(unsigned int timeout) // 0 for default timeout + { + timeout_ = timeout; + } + + void SetCertificate(const std::string& certificateFile, + const std::string& keyFile, + const std::string& keyPassword); + + void ClearCertificate(); + + void SetPkcs11(bool pkcs11) + { + pkcs11_ = pkcs11; + } + + void ClearBody(); + + void SwapBody(std::string& body); + + void SetBody(const std::string& body); + + void SetBody(IRequestBody& body); + + // This function can be used to disable chunked transfers if the + // remote server is Orthanc with a version <= 1.5.6. + void SetChunkedTransfersAllowed(bool allow) + { + allowChunkedTransfers_ = allow; + } + + bool IsChunkedTransfersAllowed() const + { + return allowChunkedTransfers_; + } + + void Execute(IAnswer& answer); + + void Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */); + + void Execute(HttpHeaders& answerHeaders /* out */, + Json::Value& answerBody /* out */); + + void Execute(); + }; +#endif + + + + class IChunkedRequestReader : public boost::noncopyable + { + public: + virtual ~IChunkedRequestReader() + { + } + + virtual void AddChunk(const void* data, + size_t size) = 0; + + virtual void Execute(OrthancPluginRestOutput* output) = 0; + }; + + + typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url, + const OrthancPluginHttpRequest* request); + + + namespace Internals + { + void NullRestCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + IChunkedRequestReader *NullChunkedRestCallback(const char* url, + const OrthancPluginHttpRequest* request); + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + template + static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + *reader = reinterpret_cast(Callback(url, request)); + if (*reader == NULL) + { + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + OrthancPluginErrorCode ChunkedRequestReaderAddChunk( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size); + + OrthancPluginErrorCode ChunkedRequestReaderExecute( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output); + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader); + +#else + + OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler); + + template< + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler + > + inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + return ChunkedRestCompatibility(output, url, request, GetHandler, + PostHandler, DeleteHandler, PutHandler); + } +#endif + } + + + + // NB: We use a templated class instead of a templated function, because + // default values are only available in functions since C++11 + template< + RestCallback GetHandler = Internals::NullRestCallback, + ChunkedRestCallback PostHandler = Internals::NullChunkedRestCallback, + RestCallback DeleteHandler = Internals::NullRestCallback, + ChunkedRestCallback PutHandler = Internals::NullChunkedRestCallback + > + class ChunkedRestRegistration : public boost::noncopyable + { + public: + static void Apply(const std::string& uri) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + OrthancPluginRegisterChunkedRestCallback( + GetGlobalContext(), uri.c_str(), + GetHandler == Internals::NullRestCallback ? NULL : Internals::Protect, + PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, + DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect, + PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, + Internals::ChunkedRequestReaderAddChunk, + Internals::ChunkedRequestReaderExecute, + Internals::ChunkedRequestReaderFinalize); +#else + OrthancPluginRegisterRestCallbackNoLock( + GetGlobalContext(), uri.c_str(), + Internals::ChunkedRestCompatibility); +#endif + } + }; + + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + class IStorageCommitmentScpHandler : public boost::noncopyable + { + public: + virtual ~IStorageCommitmentScpHandler() + { + } + + virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) = 0; + + static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid); + + static void Destructor(void* rawHandler); + }; +#endif + + + class DicomInstance : public boost::noncopyable + { + private: + bool toFree_; + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + const OrthancPluginDicomInstance* instance_; +#else + OrthancPluginDicomInstance* instance_; +#endif + + public: +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + explicit DicomInstance(const OrthancPluginDicomInstance* instance); +#else + explicit DicomInstance(OrthancPluginDicomInstance* instance); +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance(const void* buffer, + size_t size); +#endif + + ~DicomInstance(); + + std::string GetRemoteAet() const; + + const void* GetBuffer() const + { + return OrthancPluginGetInstanceData(GetGlobalContext(), instance_); + } + + size_t GetSize() const + { + return static_cast(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_)); + } + + void GetJson(Json::Value& target) const; + + void GetSimplifiedJson(Json::Value& target) const; + + OrthancPluginInstanceOrigin GetOrigin() const + { + return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_); + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + std::string GetTransferSyntaxUid() const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + bool HasPixelData() const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + unsigned int GetFramesCount() const + { + return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_); + } +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void GetRawFrame(std::string& target, + unsigned int frameIndex) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + OrthancImage* GetDecodedFrame(unsigned int frameIndex) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void Serialize(std::string& target) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + static DicomInstance* Transcode(const void* buffer, + size_t size, + const std::string& transferSyntax); +#endif + }; +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Plugins/OrthancPluginException.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginException.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#pragma once + +#if !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::Orthanc::ErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::Orthanc::OrthancException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::Orthanc::ErrorCode_ ## code +#else +# include +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::OrthancPluginErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::OrthancPlugins::PluginException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::OrthancPluginErrorCode_ ## code +#endif + + +#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast(code)); + + +#define ORTHANC_PLUGINS_THROW_EXCEPTION(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code)); + + +#define ORTHANC_PLUGINS_CHECK_ERROR(code) \ + if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success)) \ + { \ + ORTHANC_PLUGINS_THROW_EXCEPTION(code); \ + } + + +namespace OrthancPlugins +{ +#if HAS_ORTHANC_EXCEPTION == 0 + class PluginException + { + private: + OrthancPluginErrorCode code_; + + public: + explicit PluginException(OrthancPluginErrorCode code) : code_(code) + { + } + + OrthancPluginErrorCode GetErrorCode() const + { + return code_; + } + + const char* What(OrthancPluginContext* context) const + { + const char* description = OrthancPluginGetErrorDescription(context, code_); + if (description) + { + return description; + } + else + { + return "No description available"; + } + } + }; +#endif +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,31 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 . + + +# In Orthanc <= 1.7.1, the instructions below were part of +# "Compiler.cmake", and were protected by the (now unused) option +# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map") +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list") +endif() diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Plugins/VersionScriptPlugins.map --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Plugins/VersionScriptPlugins.map Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,12 @@ +# This is a version-script for Orthanc plugins + +{ +global: + OrthancPluginInitialize; + OrthancPluginFinalize; + OrthancPluginGetName; + OrthancPluginGetVersion; + +local: + *; +}; diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/README.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,3 @@ +This folder contains an excerpt of the source code of Orthanc. It is +automatically generated using the "../Resources/SyncOrthancFolder.py" +script. diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/Orthanc/Sdk-1.0.0/orthanc/OrthancCPlugin.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,4740 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * -# void OrthancPluginFinalize(): + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# const char* OrthancPluginGetName(): + * The plugin must return a short string to identify itself. + * -# const char* OrthancPluginGetVersion(): + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an extern "C" section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup Worklists Worklists + * @brief Functions to register and manage worklists. + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * 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 . + **/ + + + +#pragma once + + +#include +#include + +#ifdef WIN32 +#define ORTHANC_PLUGINS_API __declspec(dllexport) +#else +#define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include + +#include + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, + OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const char* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + + /* Sending answers to REST calls */ + _OrthancPluginService_AnswerBuffer = 2000, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can happen to DICOM resources. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + 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_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + * @see OrthancPluginDicomToJson() + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Human = 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; + + + /** + * Flags to the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** + * The constraints on the DICOM identifiers that must be supported + * by the database plugins. + **/ + typedef enum + { + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ + + _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff + } OrthancPluginIdentifierConstraint; + + + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * @brief A memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callback + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance received by Orthanc. + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup Worklists + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query. + * @ingroup Worklists + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** + * @brief Signature of a function to free dynamic memory. + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback to handle the C-Find SCP requests received by Orthanc. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* remoteAet, + const char* calledAet); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of this C header is + * compatible with the current version of Orthanc. The result of + * this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + int major, minor, revision; + + if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 1; + } + + if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 1; + } + + if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, it is up to the plugin to + * implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const char* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPostAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPost + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDeleteAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDelete + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPutAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPut + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. This + * function must be called during the initialization of the plugin, + * i.e. inside the OrthancPluginInitialize() public function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning If your change callback has to call the REST API of + * Orthanc, you should make these calls in a separate thread (with + * the events passing through a message queue). Otherwise, this + * could result in deadlocks in the presence of other plugins or Lua + * script. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const char* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const char* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const char* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new tag in the dictionary of DICOM tags + * that are known to Orthanc. This function should be used in the + * OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + OrthancPluginResourceType level; + } _OrthancPluginReconstructMainDicomTags; + + /** + * @brief Reconstruct the main DICOM tags. + * + * This function requests the Orthanc core to reconstruct the main + * DICOM tags of all the resources of the given type. This function + * can only be used as a part of the upgrade of a custom database + * back-end + * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A + * database transaction will be automatically setup. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param level The type of the resources of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + OrthancPluginResourceType level) + { + _OrthancPluginReconstructMainDicomTags params; + params.level = level; + params.storageArea = storageArea; + + return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + + 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 Flags governing the output. + * @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 Flags governing the output. + * @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; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to the decoding of + * DICOM images, replacing the built-in decoder of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Resources/SyncOrthancFolder.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Resources/SyncOrthancFolder.py Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,63 @@ +#!/usr/bin/python + +# +# This maintenance script updates the content of the "Orthanc" folder +# to match the latest version of the Orthanc source code. +# + +import multiprocessing +import os +import stat +import urllib2 + +TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc') +PLUGIN_SDK_VERSION = '1.0.0' +REPOSITORY = 'https://hg.orthanc-server.com/orthanc/raw-file' + +FILES = [ + ('OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h', 'Plugins'), + ('OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp', 'Plugins'), + ('OrthancServer/Plugins/Samples/Common/OrthancPluginException.h', 'Plugins'), + ('OrthancServer/Plugins/Samples/Common/ExportedSymbolsPlugins.list', 'Plugins'), + ('OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake', 'Plugins'), + ('OrthancServer/Plugins/Samples/Common/VersionScriptPlugins.map', 'Plugins'), +] + +SDK = [ + 'orthanc/OrthancCPlugin.h', +] + + +def Download(x): + branch = x[0] + source = x[1] + target = os.path.join(TARGET, x[2]) + print target + + try: + os.makedirs(os.path.dirname(target)) + except: + pass + + url = '%s/%s/%s' % (REPOSITORY, branch, source) + + with open(target, 'w') as f: + f.write(urllib2.urlopen(url).read()) + + +commands = [] + +for f in FILES: + commands.append([ 'default', + f[0], + os.path.join(f[1], os.path.basename(f[0])) ]) + +for f in SDK: + commands.append([ + 'Orthanc-%s' % PLUGIN_SDK_VERSION, + 'Plugins/Include/%s' % f, + 'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) + ]) + +pool = multiprocessing.Pool(10) # simultaneous downloads +pool.map(Download, commands) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Common/RtViewerApp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Common/RtViewerApp.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,274 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + +// Sample app +#include "RtViewerApp.h" +#include "RtViewerView.h" +#include "SampleHelpers.h" + +// Stone of Orthanc +#include "../../Sources/StoneInitialization.h" +#include "../../Sources/Scene2D/CairoCompositor.h" +#include "../../Sources/Scene2D/ColorTextureSceneLayer.h" +#include "../../Sources/Scene2D/OpenGLCompositor.h" +#include "../../Sources/Scene2D/PanSceneTracker.h" +#include "../../Sources/Scene2D/ZoomSceneTracker.h" +#include "../../Sources/Scene2D/RotateSceneTracker.h" +#include "../../Sources/Scene2DViewport/UndoStack.h" +#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h" +#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h" +#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Sources/Scene2DViewport/MeasureTool.h" +#include "../../Sources/Scene2DViewport/PredeclaredTypes.h" +#include "../../Sources/Volumes/VolumeSceneLayerSource.h" +#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h" +#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h" +#include "../../Sources/StoneException.h" + +// Orthanc +#include +#include + +// System +#include +#include +#include + +#include + + +namespace OrthancStone +{ + void RtViewerApp::InvalidateAllViewports() + { + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->Invalidate(); + } + } + + const VolumeImageGeometry& RtViewerApp::GetMainGeometry() + { + ORTHANC_ASSERT(geometryProvider_.get() != NULL); + ORTHANC_ASSERT(geometryProvider_->HasGeometry()); + const VolumeImageGeometry& geometry = geometryProvider_->GetImageGeometry(); + return geometry; + } + + RtViewerApp::RtViewerApp() + : undoStack_(new UndoStack) + { + // Create the volumes that will be filled later on + ctVolume_ = boost::make_shared(); + doseVolume_ = boost::make_shared(); + } + + boost::shared_ptr RtViewerApp::Create() + { + boost::shared_ptr thisOne(new RtViewerApp()); + return thisOne; + } + + void RtViewerApp::DisableTracker() + { + if (activeTracker_) + { + activeTracker_->Cancel(); + activeTracker_.reset(); + } + } + + void RtViewerApp::CreateView(const std::string& canvasId, VolumeProjection projection) + { + boost::shared_ptr + view(new RtViewerView(shared_from_this(), canvasId, projection)); + + view->RegisterMessages(); + + view->CreateLayers(ctLoader_, doseLoader_, doseVolume_, rtstructLoader_); + + views_.push_back(view); + } + + void RtViewerApp::CreateLoaders() + { + // the viewport hosts the scene + { + // "true" means use progressive quality (jpeg 50 --> jpeg 90 --> 16-bit raw) + // "false" means only using hi quality + // TODO: add flag for quality + ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, true); + + // better priority for CT vs dose and struct + ctLoader_->SetSchedulingPriority(-100); + + + // we need to store the CT loader to ask from geometry details later on when geometry is loaded + geometryProvider_ = ctLoader_; + + doseLoader_ = OrthancMultiframeVolumeLoader::Create(*loadersContext_, doseVolume_); + rtstructLoader_ = DicomStructureSetLoader::Create(*loadersContext_); + } + + /** + Register for notifications issued by the loaders + */ + + Register + (*ctLoader_, &RtViewerApp::HandleGeometryReady); + + Register + (*ctLoader_, &RtViewerApp::HandleCTLoaded); + + Register + (*ctLoader_, &RtViewerApp::HandleCTContentUpdated); + + Register + (*doseLoader_, &RtViewerApp::HandleDoseLoaded); + + Register + (*rtstructLoader_, &RtViewerApp::HandleStructuresReady); + + Register + (*rtstructLoader_, &RtViewerApp::HandleStructuresUpdated); + } + + void RtViewerApp::StartLoaders() + { + ORTHANC_ASSERT(HasArgument("ctseries") && HasArgument("rtdose") && HasArgument("rtstruct")); + + LOG(INFO) << "About to load:"; + + if (GetArgument("ctseries") == "") + { + LOG(INFO) << " CT : "; + } + else + { + LOG(INFO) << " CT : " << GetArgument("ctseries"); + ctLoader_->LoadSeries(GetArgument("ctseries")); + } + + if (GetArgument("rtdose") == "") + { + LOG(INFO) << " RTDOSE : "; + } + else + { + LOG(INFO) << " RTDOSE : " << GetArgument("rtdose"); + doseLoader_->LoadInstance(GetArgument("rtdose")); + } + + if (GetArgument("rtstruct") == "") + { + LOG(INFO) << " RTSTRUCT : : "; + } + else + { + LOG(INFO) << " RTSTRUCT : : " << GetArgument("rtstruct"); + rtstructLoader_->LoadInstanceFullVisibility(GetArgument("rtstruct")); + } + } + + void RtViewerApp::HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message) + { + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->RetrieveGeometry(); + } + FitContent(); + UpdateLayersInAllViews(); + } + + void RtViewerApp::FitContent() + { + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->FitContent(); + } + } + + void RtViewerApp::UpdateLayersInAllViews() + { + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->UpdateLayers(); + } + } + + void RtViewerApp::HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message) + { + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->RetrieveGeometry(); + } + UpdateLayersInAllViews(); + } + + void RtViewerApp::HandleCTContentUpdated(const DicomVolumeImage::ContentUpdatedMessage& message) + { + UpdateLayersInAllViews(); + } + + void RtViewerApp::HandleDoseLoaded(const DicomVolumeImage::ContentUpdatedMessage& message) + { + //TODO: compute dose extent, with outlier rejection + UpdateLayersInAllViews(); + } + + void RtViewerApp::HandleStructuresReady(const DicomStructureSetLoader::StructuresReady& message) + { + UpdateLayersInAllViews(); + } + + void RtViewerApp::HandleStructuresUpdated(const DicomStructureSetLoader::StructuresUpdated& message) + { + UpdateLayersInAllViews(); + } + + void RtViewerApp::SetArgument(const std::string& key, const std::string& value) + { + if (key == "loglevel") + OrthancStoneHelpers::SetLogLevel(value); + else + arguments_[key] = value; + } + + std::string RtViewerApp::GetArgument(const std::string& key) const + { + std::map::const_iterator found = arguments_.find(key); + if (found == arguments_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return found->second; + } + } + + bool RtViewerApp::HasArgument(const std::string& key) const + { + return (arguments_.find(key) != arguments_.end()); + } +} + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Common/RtViewerApp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Common/RtViewerApp.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,169 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#pragma once + +#include "../../Sources/Loaders/DicomStructureSetLoader.h" +#include "../../Sources/Loaders/ILoadersContext.h" +#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h" +#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Sources/Messages/IMessageEmitter.h" +#include "../../Sources/Messages/IObserver.h" +#include "../../Sources/Messages/ObserverBase.h" +#include "../../Sources/Oracle/OracleCommandExceptionMessage.h" +#include "../../Sources/Scene2DViewport/ViewportController.h" +#include "../../Sources/Viewport/IViewport.h" +#include "../../Sources/Volumes/DicomVolumeImage.h" + +#include +#include +#include + +#if ORTHANC_ENABLE_SDL +#include +#endif + +namespace OrthancStone +{ + class OpenGLCompositor; + class IVolumeSlicer; + class ILayerStyleConfigurator; + class DicomStructureSetLoader; + class IOracle; + class ThreadedOracle; + class VolumeSceneLayerSource; + class SdlOpenGLViewport; + class RtViewerView; + + static const unsigned int FONT_SIZE_0 = 32; + static const unsigned int FONT_SIZE_1 = 24; + + class Scene2D; + class UndoStack; + + /** + This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that + can be sent from multiple threads) + */ + class RtViewerApp : public ObserverBase + { + public: + + void PrepareScene(); + +#if ORTHANC_ENABLE_SDL + public: + void RunSdl(int argc, char* argv[]); + void SdlRunLoop(const std::vector >& views, + OrthancStone::DefaultViewportInteractor& interactor); + private: + void ProcessOptions(int argc, char* argv[]); + void HandleApplicationEvent(const SDL_Event& event); +#elif ORTHANC_ENABLE_WASM + public: + void RunWasm(); +#else +# error Either ORTHANC_ENABLE_SDL or ORTHANC_ENABLE_WASM must be enabled +#endif + + public: + void DisableTracker(); + + /** + Called by command-line option processing or when parsing the URL + parameters. + */ + void SetArgument(const std::string& key, const std::string& value); + + const VolumeImageGeometry& GetMainGeometry(); + + static boost::shared_ptr Create(); + + void CreateView(const std::string& canvasId, VolumeProjection projection); + + protected: + RtViewerApp(); + + private: + void CreateLoaders(); + void StartLoaders(); + void SelectNextTool(); + + // argument handling + // SetArgument is above (public section) + std::map arguments_; + + std::string GetArgument(const std::string& key) const; + bool HasArgument(const std::string& key) const; + + /** + This adds the command at the top of the undo stack + */ + //void Commit(boost::shared_ptr cmd); + void Undo(); + void Redo(); + + void HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message); + + // TODO: wire this + void HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message); + void HandleCTContentUpdated(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message); + void HandleDoseLoaded(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message); + void HandleStructuresReady(const OrthancStone::DicomStructureSetLoader::StructuresReady& message); + void HandleStructuresUpdated(const OrthancStone::DicomStructureSetLoader::StructuresUpdated& message); + + + private: + void RetrieveGeometry(); + void FitContent(); + void InvalidateAllViewports(); + void UpdateLayersInAllViews(); + + private: + boost::shared_ptr ctVolume_; + boost::shared_ptr doseVolume_; + + std::vector > views_; + + boost::shared_ptr ctLoader_; + boost::shared_ptr doseLoader_; + boost::shared_ptr rtstructLoader_; + + /** encapsulates resources shared by loaders */ + boost::shared_ptr loadersContext_; + + /** + another interface to the ctLoader object (that also implements the IVolumeSlicer interface), that serves as the + reference for the geometry (position and dimensions of the volume + size of each voxel). It could be changed to be + the dose instead, but the CT is chosen because it usually has a better spatial resolution. + */ + boost::shared_ptr geometryProvider_; + + + boost::shared_ptr activeTracker_; + + boost::shared_ptr undoStack_; + }; + +} + + + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Common/RtViewerView.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Common/RtViewerView.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,352 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +// Sample app +#include "RtViewerView.h" +#include "RtViewerApp.h" +#include "SampleHelpers.h" + +#include + +// Stone of Orthanc +#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Sources/Scene2D/CairoCompositor.h" +#include "../../Sources/Scene2D/ColorTextureSceneLayer.h" +#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h" +#include "../../Sources/Scene2D/OpenGLCompositor.h" +#include "../../Sources/Scene2D/PanSceneTracker.h" +#include "../../Sources/Scene2D/RotateSceneTracker.h" +#include "../../Sources/Scene2D/ZoomSceneTracker.h" +#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h" +#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h" +#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Sources/Scene2DViewport/MeasureTool.h" +#include "../../Sources/Scene2DViewport/PredeclaredTypes.h" +#include "../../Sources/Scene2DViewport/UndoStack.h" +#include "../../Sources/StoneException.h" +#include "../../Sources/StoneInitialization.h" +#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h" +#include "../../Sources/Volumes/VolumeSceneLayerSource.h" + +// Orthanc +#include // For std::unique_ptr<> +#include +#include + +// System +#include +#include +#include + +#include + + +namespace OrthancStone +{ + boost::shared_ptr RtViewerView::GetApp() + { + return app_.lock(); + } + + void RtViewerView::DisplayInfoText() + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + + // do not try to use stuff too early! + OrthancStone::ICompositor& compositor = lock->GetCompositor(); + + std::stringstream msg; + + for (std::map::const_iterator kv = infoTextMap_.begin(); + kv != infoTextMap_.end(); ++kv) + { + msg << kv->first << " : " << kv->second << std::endl; + } + std::string msgS = msg.str(); + + TextSceneLayer* layerP = NULL; + if (scene.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = dynamic_cast( + scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::unique_ptr layer(new TextSceneLayer); + layerP = layer.get(); + layer->SetColor(0, 255, 0); + layer->SetFontIndex(1); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_TopLeft); + //layer->SetPosition(0,0); + scene.SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + // position the fixed info text in the upper right corner + layerP->SetText(msgS.c_str()); + double cX = compositor.GetCanvasWidth() * (-0.5); + double cY = compositor.GetCanvasHeight() * (-0.5); + scene.GetCanvasToSceneTransform().Apply(cX, cY); + layerP->SetPosition(cX, cY); + lock->Invalidate(); + } + + void RtViewerView::DisplayFloatingCtrlInfoText(const PointerEvent& e) + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + + ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); + + char buf[128]; + sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", + p.GetX(), p.GetY(), + e.GetMainPosition().GetX(), e.GetMainPosition().GetY()); + + if (scene.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) + { + TextSceneLayer& layer = + dynamic_cast(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::unique_ptr layer(new TextSceneLayer); + layer->SetColor(0, 255, 0); + layer->SetText(buf); + layer->SetBorder(20); + layer->SetAnchor(BitmapAnchor_BottomCenter); + layer->SetPosition(p.GetX(), p.GetY()); + scene.SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); + } + } + + void RtViewerView::HideInfoText() + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + + scene.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); + } + + void RtViewerView::OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message) + { + DisplayInfoText(); + } + + void RtViewerView::Invalidate() + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + + void RtViewerView::FitContent() + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + + void RtViewerView::Scroll(int delta) + { + if (!planes_.empty()) + { + int next = 0; + int temp = static_cast(currentPlane_) + delta; + + if (temp < 0) + { + next = 0; + } + else if (temp >= static_cast(planes_.size())) + { + next = static_cast(planes_.size()) - 1; + } + else + { + next = static_cast(temp); + } + LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next; + + if (next != static_cast(currentPlane_)) + { + currentPlane_ = next; + UpdateLayers(); + } + } + } + + void RtViewerView::RetrieveGeometry() + { + const VolumeImageGeometry& geometry = GetApp()->GetMainGeometry(); + + const unsigned int depth = geometry.GetProjectionDepth(projection_); + currentPlane_ = depth / 2; + + planes_.resize(depth); + + for (unsigned int z = 0; z < depth; z++) + { + planes_[z] = geometry.GetProjectionSlice(projection_, z); + } + + UpdateLayers(); + } + + void RtViewerView::UpdateLayers() + { + std::unique_ptr lock(viewport_->Lock()); + + if (planes_.size() == 0) + { + RetrieveGeometry(); + } + + if (currentPlane_ < planes_.size()) + { + if (ctVolumeLayerSource_.get() != NULL) + { + ctVolumeLayerSource_->Update(planes_[currentPlane_]); + } + if (doseVolumeLayerSource_.get() != NULL) + { + doseVolumeLayerSource_->Update(planes_[currentPlane_]); + } + if (structLayerSource_.get() != NULL) + { + structLayerSource_->Update(planes_[currentPlane_]); + } + } + lock->Invalidate(); + } + + void RtViewerView::PrepareViewport() + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + ICompositor& compositor = lock->GetCompositor(); + + // False means we do NOT let a hi-DPI aware desktop managedr treat this as a legacy application that requires + // scaling. + controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight()); + + std::string ttf; + Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT); + compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor.SetFont(1, ttf, FONT_SIZE_1, Orthanc::Encoding_Latin1); + } + + void RtViewerView::SetInfoDisplayMessage( + std::string key, std::string value) + { + if (value == "") + infoTextMap_.erase(key); + else + infoTextMap_[key] = value; + DisplayInfoText(); + } + + void RtViewerView::RegisterMessages() + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Register(controller, &RtViewerView::OnSceneTransformChanged); + } + + void RtViewerView::CreateLayers(boost::shared_ptr ctLoader, + boost::shared_ptr doseLoader, + boost::shared_ptr doseVolume, + boost::shared_ptr rtstructLoader) + { + /** + Configure the CT + */ + std::unique_ptr style(new GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + + this->SetCtVolumeSlicer(ctLoader, style.release()); + + { + std::string lut; + Orthanc::EmbeddedResources::GetFileResource(lut, Orthanc::EmbeddedResources::COLORMAP_HOT); + + std::unique_ptr config(new LookupTableStyleConfigurator); + config->SetLookupTable(lut); + + boost::shared_ptr tmp(new DicomVolumeImageMPRSlicer(doseVolume)); + this->SetDoseVolumeSlicer(tmp, config.release()); + } + + this->SetStructureSet(rtstructLoader); + } + + void RtViewerView::SetCtVolumeSlicer(const boost::shared_ptr& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + int depth = scene.GetMaxDepth() + 1; + + ctVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); + + if (style != NULL) + { + ctVolumeLayerSource_->SetConfigurator(style); + } + + ctLayer_ = depth; + } + + void RtViewerView::SetDoseVolumeSlicer(const boost::shared_ptr& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + int depth = scene.GetMaxDepth() + 1; + + doseVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); + + if (style != NULL) + { + doseVolumeLayerSource_->SetConfigurator(style); + } + } + + void RtViewerView::SetStructureSet(const boost::shared_ptr& volume) + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + int depth = scene.GetMaxDepth() + 1; + + structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Common/RtViewerView.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Common/RtViewerView.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,144 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#pragma once + +#include "../../Sources/Loaders/DicomStructureSetLoader.h" +#include "../../Sources/Loaders/ILoadersContext.h" +#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h" +#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Sources/Messages/IMessageEmitter.h" +#include "../../Sources/Messages/IObserver.h" +#include "../../Sources/Messages/ObserverBase.h" +#include "../../Sources/Oracle/OracleCommandExceptionMessage.h" +#include "../../Sources/Scene2DViewport/ViewportController.h" +#include "../../Sources/Viewport/IViewport.h" +#include "../../Sources/Volumes/DicomVolumeImage.h" +#include "../../Sources/Volumes/VolumeSceneLayerSource.h" + +#include +#include +#include + +namespace OrthancStone +{ + class RtViewerApp; + + class RtViewerView : public ObserverBase + { + public: + RtViewerView(boost::weak_ptr app, + const std::string& canvasId, + VolumeProjection projection) + : app_(app) + , currentPlane_(0) + , projection_(projection) + , ctLayer_(0) + { + viewport_ = CreateViewport(canvasId); + FLOATING_INFOTEXT_LAYER_ZINDEX = 6; + FIXED_INFOTEXT_LAYER_ZINDEX = 7; + } + + /** + This method is called when the scene transform changes. It allows to + recompute the visual elements whose content depend upon the scene transform + */ + void OnSceneTransformChanged( + const ViewportController::SceneTransformChanged& message); + + /** + This method will ask the VolumeSceneLayerSource, that are responsible to + generated 2D content based on a volume and a cutting plane, to regenerate + it. This is required if the volume itself changes (during loading) or if + the cutting plane is changed + */ + void UpdateLayers(); + + void Refresh(); + + void TakeScreenshot( + const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight); + + void Scroll(int delta); + + void Invalidate(); + void FitContent(); + void RetrieveGeometry(); + void PrepareViewport(); + void RegisterMessages(); + +#if ORTHANC_ENABLE_SDL == 1 + void EnableGLDebugOutput(); +#endif + + void CreateLayers(boost::shared_ptr ctLoader, + boost::shared_ptr doseLoader, + boost::shared_ptr doseVolume, + boost::shared_ptr rtstructLoader); + + boost::shared_ptr GetViewport() + { + return viewport_; + } + + int GetCtLayerIndex() const + { + return ctLayer_; + } + + private: + void SetInfoDisplayMessage(std::string key, std::string value); + boost::shared_ptr GetApp(); + boost::shared_ptr CreateViewport(const std::string& canvasId); + void DisplayInfoText(); + void HideInfoText(); + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + + void SetCtVolumeSlicer(const boost::shared_ptr& volume, + ILayerStyleConfigurator* style); + + void SetDoseVolumeSlicer(const boost::shared_ptr& volume, + ILayerStyleConfigurator* style); + + void SetStructureSet(const boost::shared_ptr& volume); + + private: + boost::weak_ptr app_; + boost::shared_ptr ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_; + + // collection of cutting planes for this particular view + std::vector planes_; + size_t currentPlane_; + + VolumeProjection projection_; + + std::map infoTextMap_; + + int FLOATING_INFOTEXT_LAYER_ZINDEX; + int FIXED_INFOTEXT_LAYER_ZINDEX; + boost::shared_ptr viewport_; + + int ctLayer_; + }; +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Common/SampleHelpers.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Common/SampleHelpers.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,58 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#pragma once + +#include + +#include + +#include +#include + +namespace OrthancStoneHelpers +{ + inline void SetLogLevel(std::string logLevel) + { + boost::to_lower(logLevel); + if (logLevel == "warning") + { + Orthanc::Logging::EnableInfoLevel(false); + Orthanc::Logging::EnableTraceLevel(false); + } + else if (logLevel == "info") + { + Orthanc::Logging::EnableInfoLevel(true); + Orthanc::Logging::EnableTraceLevel(false); + } + else if (logLevel == "trace") + { + Orthanc::Logging::EnableInfoLevel(true); + Orthanc::Logging::EnableTraceLevel(true); + } + else + { + std::cerr << "Unknown log level \"" << logLevel << "\". Will use TRACE as default!"; + Orthanc::Logging::EnableInfoLevel(true); + Orthanc::Logging::EnableTraceLevel(true); + } + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/README.md Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,267 @@ +General +======= +These samples assume that a recent version of Orthanc is checked out in an +`orthanc` folder next to the `orthanc-stone` folder. Let's call the top folder +the `devroot` folder. This name does not matter and is not used anywhere. + +Here's the directory layout that we suggest: + +``` +devroot/ + | + +- orthanc/ + | + +- orthanc-stone/ + | + ... +``` + + Orthanc can be retrieved with: + ``` + hg clone https://hg.orthanc-server.com/orthanc + ``` + +Furthermore, the samples usually assume that an Orthanc is running locally, +without authentication, on port 8042. The samples can easily be tweaked if +your setup is different. + +When Dicom resources are to be displayed, their IDs can be supplied in the +various ways suitable for the platform (command-line arguments, URL parameters +or through the GUI) + + +This repo contains two sample projects: + +SingleFrameViewer +----------------- + +This sample application displays a single frame of a Dicom instance that can +be loaded from Orthanc, either by using the Orthanc REST API or through the +Dicomweb server functionality of Orthanc. + +RtViewer +-------- + +This sample application displays set of Radiotherapy data: +- a CT scan +- an RT-Dose +- an RT-Struct + +The WebAssembly version displays 3 viewports with MPR data +while the SDL sample displays a single viewport. + + +WebAssembly samples +=================== + +Building the WebAssembly samples require the Emscripten SDK +(https://emscripten.org/). This SDK goes far beyond the simple compilation to +the wasm (Web Assembly) bytecode and provides a comprehensive library that +eases porting native C and C++ programs and libraries. The Emscripten SDK also +makes it easy to generate the companion Javascript files requires to use a +wasm module in a web application. + +Although Emscripten runs on all major platforms, Stone of Orthanc is developed +and tested with the Linux version of Emscripten. + +Emscripten runs perfectly fine under the Windows Subsystem for Linux (that is +the environment used quite often by the Stone of Orthanc team) + +**Important note:** The following examples **and the build scripts** will +assume that you have installed the Emscripten SDK in `~/apps/emsdk`. + +The following packages should get you going (a Debian-like distribution such +as Debian or Ubuntu is assumed) + +``` +sudo apt-get update +sudo apt-get install -y build-essential curl wget git python cmake pkg-config +sudo apt-get install -y mercurial unzip npm ninja-build p7zip-full gettext-base +``` + +To build the Wasm samples, just launch the `build-wasm-samples.sh` script from +this folder. Optionaly, you can pass the build type as an argument. +We suggest that you do *not* use the `Debug` configuration unless you really +need it, for the additional checks that are made will lead to a very long +build time and much slower execution (more severe than with a native non-wasm +target) + +In order to run the sample, you may serve it with the ServeFolders plugin. +You can i.e: add such a section in your orthanc configuration file: + +``` +{ + "Plugins" : ["LibServeFolders.so], + "ServeFolders" : { + "/single-frame-viewer" : "..../out/install-stone-wasm-samples-RelWithDebInfo/SingleFrameViewer", + "/rt-viewer": "..../out/install-stone-wasm-samples-RelWithDebInfo/RtViewer" + } +} +``` + +You'll then be able to open the single-frame-viewer demo at `http://localhost:8042/single-frame-viewer/index.html` + +The rt-viewer demo at +`http://localhost:8044/rt-viewer/index.html?ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9`. Note that you must provide 3 ids in the url: + +- the CT series Orthanc ID +- the RT-Dose instance Orthanc ID +- the RT-Struct instance Orthanc ID + + +RtViewer +----------------- + +This sample application displays three MPR views of a CT+RTDOSE+RTSTRUCT dataset, loaded from Orthanc. The Orthanc IDs of the dataset must be supplied as URL parameters like: + +``` +http://localhost:9979/stone-rtviewer/index.html?loglevel=info&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 +``` + +(notice the loglevel parameter that can be `warning`, `info` or `trace`, in increasing verbosity order) + +This sample uses plain Javascript and requires the +Emscripten toolchain and cmake, in addition to a few standard packages. + +To build it, just launch the `build-wasm-RtViewer.sh` script from +this folder. Optionaly, you can pass the build type as an argument. +We suggest that you do *not* use the `Debug` configuration unless you really +need it, for the additional checks that are made will lead to a very long +build time and much slower execution (more severe than with a native non-wasm +target) + +In order to run the sample, you may serve it with the ServeFolders plugin. +You can i.e: add such a section in your orthanc configuration file: + +``` +{ + "Plugins" : ["LibServeFolders.so], + "ServeFolders" : { + "/rt-viewer" : "..../out/install-stone-wasm-RtViewer-RelWithDebInfo" + } +} +``` + +You'll then be able to open the demo at `http://localhost:8042/rt-viewer/index.html` + + +RtViewerPlugin +--------------- +This C++ plugin allows to extend the Orthanc Explorer to add a button labeled "Stone RT Viewer" +in the series page. + +It also embeds and serves the RT Viewer files and is thus a standalone way of using this viewer. + +Please note that building this plugin requires that the RtViewer be built inside the wasm-binaries +folder of the repo. + +This will automatically be the case if you use the `/OrthancStone/Samples/WebAssembly/docker-build.sh` script. + +If you use the `build-wasm-samples.sh` script above, you will have the copy `RtViewer` **folder** +from `/out/install-stone-wasm-RtViewer-RelWithDebInfo` to `/wasm-binaries/`. + +TL;DR: Build like this (assuming `~/orthanc-stone` is the repo ): + +``` +~/orthanc-stone/OrthancStone/Samples/WebAssembly/docker-build.sh +~/orthanc-stone/OrthancStone/Samples/RtViewerPlugin/docker-build.sh +``` + +Once this is done, the plugin can be found in: + +``` +~/orthanc-stone/wasm-binaries/share/orthanc/plugins/libRtViewerPlugin.so +``` + +Add this path to the `"Plugins"` section of your Orthanc configuration, start Orthanc, and you +should now see a "Stone MPR RT Viewer" button in the Orthanc Explorer, at the *series* level. + +Open it on a CT series, and the RTSTRUCT and RTDOSE series of the same study will be loaded in +the viewer. + +Native samples +================= + +### Windows build + +Here's how to build the SdlSimpleViewer example using Visual Studio 2019 +(the shell is Powershell, but the legacy shell can also be used with some +tweaks). This example is meant to be launched from the folder above +orthanc-stone. + +``` + # create the build folder and navigate to it + $buildDir = "build-stone-sdlviewer-msvc16-x64" + + if (-not (Test-Path $buildDir)) { + mkdir -p $buildDir | Out-Null + } + + cd $buildDir + + # perform the configuration + cmake -G "Visual Studio 16 2019" -A x64 ` + -DMSVC_MULTIPLE_PROCESSES=ON ` + -DALLOW_DOWNLOADS=ON ` + -DSTATIC_BUILD=ON ` + -DOPENSSL_NO_CAPIENG=ON ` + ../orthanc-stone/OrthancStone/Samples/Sdl + + $solutionPath = ls -filter *.sln + Write-Host "Solution file(s) available at: $solutionPath" +``` + +The initial configuration step will be quite lengthy, for CMake needs to +setup its internal cache based on your environment and build tools. + +Subsequent runs will be several orders of magnitude faster! + +One the solution (.sln) file is ready, you can open it using the Visual Studio +IDE and choose Build --> Build solution. + +An alternative is to execute `cmake --build .` in the build folder created by +the script. + +In order to run the sample, make sure you've an Orthanc server running i.e. on +port 8042 and launch: + +``` +./SdlSimpleViewer --orthanc http://localhost:8042 --instance 7fc84013-abef174e-3354ca83-b9cdb2a4-f1a74368 + +./RtViewerSdl --orthanc http://localhost:8042 --ctseries a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --rtdose 830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --rtstruct 54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 +``` + +RtViewer +--------------- + +### Windows build + +Here's how to build the SdlSimpleViewer example using Visual Studio 2019 +(the shell is Powershell, but the legacy shell can also be used with some +tweaks). This example is meant to be launched from the folder above +orthanc-stone. + +``` + $buildRootDir = "out" + $buildDirName = "build-stone-sdl-RtViewer-msvc16-x64" + $buildDir = Join-Path -Path $buildRootDir -ChildPath $buildDirName + + if (-not (Test-Path $buildDir)) { + mkdir -p $buildDir | Out-Null + } + + cd $buildDir + + cmake -G "Visual Studio 16 2019" -A x64 ` + -DMSVC_MULTIPLE_PROCESSES=ON ` + -DALLOW_DOWNLOADS=ON ` + -DSTATIC_BUILD=ON ` + -DOPENSSL_NO_CAPIENG=ON ` + ../../orthanc-stone/OrthancStone/Samples/Sdl/RtViewer +``` + +Executing `cmake --build .` in the build folder will launch the Microsoft +builder on the solution. + +Alternatively, you can open and build the solution using Visual Studio 2019. + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,98 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(StoneWebViewerPlugin) + +set(ORTHANC_PLUGIN_VERSION "mainline") + +if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + +set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/RtViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder") + +# Parameters of the build +set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +# Advanced parameters to fine-tune linking against system libraries +set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") +set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked") +mark_as_advanced(ORTHANC_FRAMEWORK_STATIC) + + +# Download and setup the Orthanc framework +include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake) + +include_directories(${ORTHANC_FRAMEWORK_ROOT}) + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES}) + +else() + include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake) + set(ENABLE_MODULE_IMAGES OFF) + set(ENABLE_MODULE_JOBS OFF) + set(ENABLE_MODULE_DICOM OFF) + include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake) +endif() + +include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake) + + +if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) + include_directories(${CMAKE_SOURCE_DIR}/Resources/OrthancSdk-1.0.0) +else () + CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) + if (NOT HAVE_ORTHANC_H) + message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") + endif() +endif() + + +add_definitions( + -DHAS_ORTHANC_EXCEPTION=1 + -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}" + -DPLUGIN_NAME="stone-rtviewer" + ) + + +EmbedResources( + # Web Viewer Folders + # IMAGES ${STONE_BINARIES_WEB_VIEWER}/img/ + # WEB_APPLICATION ${CMAKE_SOURCE_DIR}/../WebApplication + + # Explorer extension code + ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/OrthancExplorer.js + + # RtViewer individual files + RT_VIEWER_WASM_JS ${STONE_BINARIES}/RtViewerWasm.js + RT_VIEWER_WASM ${STONE_BINARIES}/RtViewerWasm.wasm + RT_VIEWER_WASM_APP_JS ${STONE_BINARIES}/RtViewerWasmApp.js + RT_VIEWER_INDEX_HTML ${STONE_BINARIES}/index.html + ) + +add_library(RtViewerPlugin SHARED + Plugin.cpp + ${AUTOGENERATED_SOURCES} + ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp + ${ORTHANC_CORE_SOURCES} + ) + +set_target_properties(RtViewerPlugin PROPERTIES + VERSION ${ORTHANC_PLUGIN_VERSION} + SOVERSION ${ORTHANC_PLUGIN_VERSION}) + +install( + TARGETS RtViewerPlugin + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/OrthancExplorer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/OrthancExplorer.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,87 @@ +$('#series').live('pagebeforecreate', function() { + var b = $('') + .attr('data-role', 'button') + .attr('href', '#') + .attr('data-icon', 'search') + .attr('data-theme', 'e') + .text('Stone MPR RT Sample Viewer');`` + + b.insertBefore($('#series-delete').parent().parent()); + b.click(function() { + if ($.mobile.pageData) { + $.ajax({ + url: '../series/' + $.mobile.pageData.uuid, + dataType: 'json', + cache: false, + success: function(series) { + + // we consider that the imaging series to display is the + // current one. + // we will look for RTDOSE and RTSTRUCT instances in the + // sibling series from the same study. The first one of + // each modality will be grabbed. + let ctSeries = $.mobile.pageData.uuid; + + $.ajax({ + url: '../studies/' + series.ParentStudy, + dataType: 'json', + cache: false, + success: function(study) { + // Loop on the study series and find the first RTSTRUCT and RTDOSE instances, + // if any. + let rtStructInstance = null; + let rtDoseInstance = null; + let rtPetInstance = null; + let seriesRequests = [] + + study.Series.forEach( function(studySeriesUuid) { + let request = $.ajax({ + url: '../series/' + studySeriesUuid, + dataType: 'json', + cache: false, + }); + seriesRequests.push(request); + }); + + $.when.apply($,seriesRequests).then(function() { + [].forEach.call(arguments, function(response) { + siblingSeries = response[0] + if (siblingSeries.MainDicomTags.Modality == "RTDOSE") { + // we have found an RTDOSE series. Let's grab the first instance + if (siblingSeries.Instances.length > 0) { + if(rtDoseInstance == null) { + rtDoseInstance = siblingSeries.Instances[0]; + } + } + } + if (siblingSeries.MainDicomTags.Modality == "PT") { + // we have found an RTDOSE series. Let's grab the first instance + if (siblingSeries.Instances.length > 0) { + if(rtPetInstance == null) { + rtPetInstance = siblingSeries.Instances[0]; + } + } + } + if (siblingSeries.MainDicomTags.Modality == "RTSTRUCT") { + // we have found an RTDOSE series. Let's grab the first instance + if (siblingSeries.Instances.length > 0) { + if(rtStructInstance == null) { + rtStructInstance = siblingSeries.Instances[0]; + } + } + } + }); + let mprViewerUrl = '../stone-rtviewer/index.html?ctseries=' + ctSeries + + '&rtdose=' + rtDoseInstance + + '&rtstruct=' + rtStructInstance; + //console.log("About to open: " + mprViewerUrl); + window.open(mprViewerUrl); + }); + } + }); + } + }); + } + }); +}); + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Plugin.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,185 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#include "Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#include + +#include +#include + +OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) +{ + try + { + if (changeType == OrthancPluginChangeType_OrthancStarted) + { + Json::Value info; + if (!OrthancPlugins::RestApiGet(info, "/plugins/web-viewer", false)) + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_InternalError, + "The Stone MPR RT viewer requires the Web Viewer plugin to be installed"); + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception: " << e.What(); + return static_cast(e.GetErrorCode()); + } + + return OrthancPluginErrorCode_Success; +} + +template +void ServeEmbeddedFolder(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + } + else + { + std::string path = "/" + std::string(request->groups[0]); + const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path)); + + std::string s; + Orthanc::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str()); + + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); + } +} + + +template +void ServeEmbeddedFile(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + } + else + { + const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url)); + + std::string s; + Orthanc::EmbeddedResources::GetFileResource(s, file); + + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); + } +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + OrthancPlugins::SetGlobalContext(context); + +#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) + Orthanc::Logging::InitializePluginContext(context); +#else + Orthanc::Logging::Initialize(context); +#endif + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context, info); + return -1; + } + + try + { + std::string explorer; + Orthanc::EmbeddedResources::GetFileResource( + explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); + OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); + + // RtViewer files below. + // --------------------- + // we do not serve the whole directory at once (with ServeEmbeddedFolder) + // because it contains uppercase characters that are forbidden by the + // resource embedding system + + OrthancPlugins::RegisterRestCallback + > + ("/stone-rtviewer/RtViewerWasm.js", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-rtviewer/RtViewerWasm.wasm", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-rtviewer/RtViewerWasmApp.js", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-rtviewer/index.html", true); + + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + } + catch (...) + { + OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin"); + return -1; + } + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return PLUGIN_NAME; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return PLUGIN_VERSION; + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,7 @@ +# This is the list of the symbols that must be exported by Orthanc +# plugins, if targeting OS X + +_OrthancPluginInitialize +_OrthancPluginFinalize +_OrthancPluginGetName +_OrthancPluginGetVersion diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,3383 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#include "OrthancPluginCppWrapper.h" + +#include +#include +#include +#include +#include + + +#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin; +#endif + + +namespace OrthancPlugins +{ + static OrthancPluginContext* globalContext_ = NULL; + + + void SetGlobalContext(OrthancPluginContext* context) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + else if (globalContext_ == NULL) + { + globalContext_ = context; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + } + + + bool HasGlobalContext() + { + return globalContext_ != NULL; + } + + + OrthancPluginContext* GetGlobalContext() + { + if (globalContext_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + else + { + return globalContext_; + } + } + + + void MemoryBuffer::Check(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + } + + if (code == OrthancPluginErrorCode_Success) + { + return true; + } + else if (code == OrthancPluginErrorCode_UnknownResource || + code == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + MemoryBuffer::MemoryBuffer() + { + buffer_.data = NULL; + buffer_.size = 0; + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + MemoryBuffer::MemoryBuffer(const void* buffer, + size_t size) + { + uint32_t s = static_cast(size); + if (static_cast(s) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != + OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else + { + memcpy(buffer_.data, buffer, size); + } + } +#endif + + + void MemoryBuffer::Clear() + { + if (buffer_.data != NULL) + { + OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_); + buffer_.data = NULL; + buffer_.size = 0; + } + } + + + void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) + { + Clear(); + + buffer_.data = other.data; + buffer_.size = other.size; + + other.data = NULL; + other.size = 0; + } + + + void MemoryBuffer::Swap(MemoryBuffer& other) + { + std::swap(buffer_.data, other.buffer_.data); + std::swap(buffer_.size, other.buffer_.size); + } + + + OrthancPluginMemoryBuffer MemoryBuffer::Release() + { + OrthancPluginMemoryBuffer result = buffer_; + + buffer_.data = NULL; + buffer_.size = 0; + + return result; + } + + + void MemoryBuffer::ToString(std::string& target) const + { + if (buffer_.size == 0) + { + target.clear(); + } + else + { + target.assign(reinterpret_cast(buffer_.data), buffer_.size); + } + } + + + void MemoryBuffer::ToJson(Json::Value& target) const + { + if (buffer_.data == NULL || + buffer_.size == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + const char* tmp = reinterpret_cast(buffer_.data); + + Json::Reader reader; + if (!reader.parse(tmp, tmp + buffer_.size, target)) + { + LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool MemoryBuffer::RestApiGet(const std::string& uri, + bool applyPlugins) + { + Clear(); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str())); + } + else + { + return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str())); + } + } + + bool MemoryBuffer::RestApiGet(const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + Clear(); + + std::vector headersKeys; + std::vector headersValues; + + for (std::map::const_iterator + it = httpHeaders.begin(); it != httpHeaders.end(); it++) + { + headersKeys.push_back(it->first.c_str()); + headersValues.push_back(it->second.c_str()); + } + + return CheckHttp(OrthancPluginRestApiGet2( + GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), + (headersKeys.empty() ? NULL : &headersKeys[0]), + (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); + } + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + // Cast for compatibility with Orthanc SDK <= 1.5.6 + const char* b = reinterpret_cast(body); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + // Cast for compatibility with Orthanc SDK <= 1.5.6 + const char* b = reinterpret_cast(body); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(uri, writer.write(body), applyPlugins); + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(uri, writer.write(body), applyPlugins); + } + + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags)); + } + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags)); + } + + + void MemoryBuffer::ReadFile(const std::string& path) + { + Clear(); + Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str())); + } + + + void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query) + { + Clear(); + Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query)); + } + + + void OrthancString::Assign(char* str) + { + Clear(); + + if (str != NULL) + { + str_ = str; + } + } + + + void OrthancString::Clear() + { + if (str_ != NULL) + { + OrthancPluginFreeString(GetGlobalContext(), str_); + str_ = NULL; + } + } + + + void OrthancString::ToString(std::string& target) const + { + if (str_ == NULL) + { + target.clear(); + } + else + { + target.assign(str_); + } + } + + + void OrthancString::ToJson(Json::Value& target) const + { + if (str_ == NULL) + { + LogError("Cannot convert an empty memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Reader reader; + if (!reader.parse(str_, target)) + { + LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void MemoryBuffer::DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + OrthancString str; + str.Assign(OrthancPluginDicomBufferToJson + (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); + str.ToJson(target); + } + + + bool MemoryBuffer::HttpGet(const std::string& url, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(), + body.c_str(), body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(), + body.empty() ? NULL : body.c_str(), + body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + void MemoryBuffer::GetDicomInstance(const std::string& instanceId) + { + Clear(); + Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str())); + } + + + bool HttpDelete(const std::string& url, + const std::string& username, + const std::string& password) + { + OrthancPluginErrorCode error = OrthancPluginHttpDelete + (GetGlobalContext(), url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str()); + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void LogError(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogError(GetGlobalContext(), message.c_str()); + } + } + + + void LogWarning(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); + } + } + + + void LogInfo(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); + } + } + + + void OrthancConfiguration::LoadConfiguration() + { + OrthancString str; + str.Assign(OrthancPluginGetConfiguration(GetGlobalContext())); + + if (str.GetContent() == NULL) + { + LogError("Cannot access the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + str.ToJson(configuration_); + + if (configuration_.type() != Json::objectValue) + { + LogError("Unable to read the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancConfiguration::OrthancConfiguration() + { + LoadConfiguration(); + } + + + OrthancConfiguration::OrthancConfiguration(bool loadConfiguration) + { + if (loadConfiguration) + { + LoadConfiguration(); + } + else + { + configuration_ = Json::objectValue; + } + } + + + std::string OrthancConfiguration::GetPath(const std::string& key) const + { + if (path_.empty()) + { + return key; + } + else + { + return path_ + "." + key; + } + } + + + bool OrthancConfiguration::IsSection(const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + return (configuration_.isMember(key) && + configuration_[key].type() == Json::objectValue); + } + + + void OrthancConfiguration::GetSection(OrthancConfiguration& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.path_ = GetPath(key); + + if (!configuration_.isMember(key)) + { + target.configuration_ = Json::objectValue; + } + else + { + if (configuration_[key].type() != Json::objectValue) + { + LogError("The configuration section \"" + target.path_ + + "\" is not an associative array as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target.configuration_ = configuration_[key]; + } + } + + + bool OrthancConfiguration::LookupStringValue(std::string& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::stringValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asString(); + return true; + } + + + bool OrthancConfiguration::LookupIntegerValue(int& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::intValue: + target = configuration_[key].asInt(); + return true; + + case Json::uintValue: + target = configuration_[key].asUInt(); + return true; + + default: + LogError("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const + { + int tmp; + if (!LookupIntegerValue(tmp, key)) + { + return false; + } + + if (tmp < 0) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a positive integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + target = static_cast(tmp); + return true; + } + } + + + bool OrthancConfiguration::LookupBooleanValue(bool& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::booleanValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a Boolean as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asBool(); + return true; + } + + + bool OrthancConfiguration::LookupFloatValue(float& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::realValue: + target = configuration_[key].asFloat(); + return true; + + case Json::intValue: + target = static_cast(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast(configuration_[key].asUInt()); + return true; + + default: + LogError("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupListOfStrings(std::list& target, + const std::string& key, + bool allowSingleString) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::arrayValue: + { + bool ok = true; + + for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++) + { + if (configuration_[key][i].type() == Json::stringValue) + { + target.push_back(configuration_[key][i].asString()); + } + else + { + ok = false; + } + } + + if (ok) + { + return true; + } + + break; + } + + case Json::stringValue: + if (allowSingleString) + { + target.push_back(configuration_[key].asString()); + return true; + } + + break; + + default: + break; + } + + LogError("The configuration option \"" + GetPath(key) + + "\" is not a list of strings as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + + bool OrthancConfiguration::LookupSetOfStrings(std::set& target, + const std::string& key, + bool allowSingleString) const + { + std::list lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list::const_iterator + it = lst.begin(); it != lst.end(); ++it) + { + target.insert(*it); + } + + return true; + } + else + { + return false; + } + } + + + std::string OrthancConfiguration::GetStringValue(const std::string& key, + const std::string& defaultValue) const + { + std::string tmp; + if (LookupStringValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + int OrthancConfiguration::GetIntegerValue(const std::string& key, + int defaultValue) const + { + int tmp; + if (LookupIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const + { + unsigned int tmp; + if (LookupUnsignedIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + bool OrthancConfiguration::GetBooleanValue(const std::string& key, + bool defaultValue) const + { + bool tmp; + if (LookupBooleanValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + float OrthancConfiguration::GetFloatValue(const std::string& key, + float defaultValue) const + { + float tmp; + if (LookupFloatValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + void OrthancConfiguration::GetDictionary(std::map& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return; + } + + if (configuration_[key].type() != Json::objectValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + Json::Value::Members members = configuration_[key].getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = configuration_[key][members[i]]; + + if (value.type() == Json::stringValue) + { + target[members[i]] = value.asString(); + } + else + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a dictionary mapping strings to strings"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + } + + + void OrthancImage::Clear() + { + if (image_ != NULL) + { + OrthancPluginFreeImage(GetGlobalContext(), image_); + image_ = NULL; + } + } + + + void OrthancImage::CheckImageAvailable() const + { + if (image_ == NULL) + { + LogError("Trying to access a NULL image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancImage::OrthancImage() : + image_(NULL) + { + } + + + OrthancImage::OrthancImage(OrthancPluginImage* image) : + image_(image) + { + } + + + OrthancImage::OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height); + + if (image_ == NULL) + { + LogError("Cannot create an image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancImage::OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + image_ = OrthancPluginCreateImageAccessor + (GetGlobalContext(), format, width, height, pitch, buffer); + + if (image_ == NULL) + { + LogError("Cannot create an image accessor"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + void OrthancImage::UncompressPngImage(const void* data, + size_t size) + { + Clear(); + + image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png); + + if (image_ == NULL) + { + LogError("Cannot uncompress a PNG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::UncompressJpegImage(const void* data, + size_t size) + { + Clear(); + image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg); + if (image_ == NULL) + { + LogError("Cannot uncompress a JPEG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::DecodeDicomImage(const void* data, + size_t size, + unsigned int frame) + { + Clear(); + image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame); + if (image_ == NULL) + { + LogError("Cannot uncompress a DICOM image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const + { + CheckImageAvailable(); + return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetWidth() const + { + CheckImageAvailable(); + return OrthancPluginGetImageWidth(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetHeight() const + { + CheckImageAvailable(); + return OrthancPluginGetImageHeight(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetPitch() const + { + CheckImageAvailable(); + return OrthancPluginGetImagePitch(GetGlobalContext(), image_); + } + + + void* OrthancImage::GetBuffer() const + { + CheckImageAvailable(); + return OrthancPluginGetImageBuffer(GetGlobalContext(), image_); + } + + + void OrthancImage::CompressPngImage(MemoryBuffer& target) const + { + CheckImageAvailable(); + + OrthancPlugins::MemoryBuffer answer; + OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + + target.Swap(answer); + } + + + void OrthancImage::CompressJpegImage(MemoryBuffer& target, + uint8_t quality) const + { + CheckImageAvailable(); + + OrthancPlugins::MemoryBuffer answer; + OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + + target.Swap(answer); + } + + + void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + } + + + void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) const + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + } + + + OrthancPluginImage* OrthancImage::Release() + { + CheckImageAvailable(); + OrthancPluginImage* tmp = image_; + image_ = NULL; + return tmp; + } + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) : + matcher_(NULL), + worklist_(worklist) + { + if (worklist_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void FindMatcher::SetupDicom(const void* query, + uint32_t size) + { + worklist_ = NULL; + + matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size); + if (matcher_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + FindMatcher::~FindMatcher() + { + // The "worklist_" field + + if (matcher_ != NULL) + { + OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_); + } + } + + + + bool FindMatcher::IsMatch(const void* dicom, + uint32_t size) const + { + int32_t result; + + if (matcher_ != NULL) + { + result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size); + } + else if (worklist_ != NULL) + { + result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + if (result == 0) + { + return false; + } + else if (result == 1) + { + return true; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + +#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ + + void AnswerJson(const Json::Value& value, + OrthancPluginRestOutput* output + ) + { + Json::StyledWriter writer; + std::string bodyString = writer.write(value); + + OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json"); + } + + void AnswerString(const std::string& answer, + const char* mimeType, + OrthancPluginRestOutput* output + ) + { + OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType); + } + + void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output) + { + OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError); + } + + void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods); + } + + bool RestApiGetString(std::string& result, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer; + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + answer.ToString(result); + return true; + } + } + + bool RestApiGetString(std::string& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answer; + if (!answer.RestApiGet(uri, httpHeaders, applyPlugins)) + { + return false; + } + else + { + answer.ToString(result); + return true; + } + } + + + + bool RestApiGet(Json::Value& result, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPost(std::string& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + result.assign(answer.GetData(), answer.GetSize()); + } + return true; + } + } + + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(result, uri, writer.write(body), applyPlugins); + } + + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPut(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(result, uri, writer.write(body), applyPlugins); + } + + + bool RestApiDelete(const std::string& uri, + bool applyPlugins) + { + OrthancPluginErrorCode error; + + if (applyPlugins) + { + error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str()); + } + else + { + error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str()); + } + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void ReportMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + LogError("Your version of the Orthanc core (" + + std::string(GetGlobalContext()->orthancVersion) + + ") is too old to run this plugin (version " + + boost::lexical_cast(major) + "." + + boost::lexical_cast(minor) + "." + + boost::lexical_cast(revision) + + " is required)"); + } + + + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + if (!HasGlobalContext()) + { + LogError("Bad Orthanc context in the plugin"); + return false; + } + + if (!strcmp(GetGlobalContext()->orthancVersion, "mainline")) + { + // Assume compatibility with the mainline + return true; + } + + // Parse the version of the Orthanc core + int aa, bb, cc; + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || + aa < 0 || + bb < 0 || + cc < 0) + { + return false; + } + + unsigned int a = static_cast(aa); + unsigned int b = static_cast(bb); + unsigned int c = static_cast(cc); + + // Check the major version number + + if (a > major) + { + return true; + } + + if (a < major) + { + return false; + } + + + // Check the minor version number + assert(a == major); + + if (b > minor) + { + return true; + } + + if (b < minor) + { + return false; + } + + // Check the patch level version number + assert(a == major && b == minor); + + if (c >= revision) + { + return true; + } + else + { + return false; + } + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) + const char* AutodetectMimeType(const std::string& path) + { + const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str()); + + if (mime == NULL) + { + // Should never happen, just for safety + return "application/octet-stream"; + } + else + { + return mime; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + size_t OrthancPeers::GetPeerIndex(const std::string& name) const + { + size_t index; + if (LookupName(index, name)) + { + return index; + } + else + { + LogError("Inexistent peer: " + name); + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + OrthancPeers::OrthancPeers() : + peers_(NULL), + timeout_(0) + { + peers_ = OrthancPluginGetPeers(GetGlobalContext()); + + if (peers_ == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_); + + for (uint32_t i = 0; i < count; i++) + { + const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i); + if (name == NULL) + { + OrthancPluginFreePeers(GetGlobalContext(), peers_); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + index_[name] = i; + } + } + + + OrthancPeers::~OrthancPeers() + { + if (peers_ != NULL) + { + OrthancPluginFreePeers(GetGlobalContext(), peers_); + } + } + + + bool OrthancPeers::LookupName(size_t& target, + const std::string& name) const + { + Index::const_iterator found = index_.find(name); + + if (found == index_.end()) + { + return false; + } + else + { + target = found->second; + return true; + } + } + + + std::string OrthancPeers::GetPeerName(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(const std::string& name) const + { + return GetPeerUrl(GetPeerIndex(name)); + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast(index), key.c_str()); + if (s == NULL) + { + return false; + } + else + { + value.assign(s); + return true; + } + } + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const + { + return LookupUserProperty(value, GetPeerIndex(peer), key); + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Get, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Swap(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoGet(target, index, uri)); + } + + + bool OrthancPeers::DoGet(Json::Value& target, + size_t index, + const std::string& uri) const + { + MemoryBuffer buffer; + + if (DoGet(buffer, index, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const + { + MemoryBuffer buffer; + + if (DoGet(buffer, name, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPost(target, index, uri, body)); + } + + + bool OrthancPeers::DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, index, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, name, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Post, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Swap(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Put, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPut(index, uri, body)); + } + + + bool OrthancPeers::DoDelete(size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Delete, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoDelete(const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoDelete(index, uri)); + } +#endif + + + + + + /****************************************************************** + ** JOBS + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + void OrthancJob::CallbackFinalize(void* job) + { + if (job != NULL) + { + delete reinterpret_cast(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast(job); + + if (tmp.hasSerialized_) + { + return tmp.serialized_.c_str(); + } + else + { + return NULL; + } + } + catch (...) + { + return 0; + } + } + + + OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->Step(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + return OrthancPluginJobStepStatus_Failure; + } + catch (...) + { + return OrthancPluginJobStepStatus_Failure; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackStop(void* job, + OrthancPluginJobStopReason reason) + { + assert(job != NULL); + + try + { + reinterpret_cast(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void OrthancJob::ClearContent() + { + Json::Value empty = Json::objectValue; + UpdateContent(empty); + } + + + void OrthancJob::UpdateContent(const Json::Value& content) + { + if (content.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + content_ = writer.write(content); + } + } + + + void OrthancJob::ClearSerialized() + { + hasSerialized_ = false; + serialized_.clear(); + } + + + void OrthancJob::UpdateSerialized(const Json::Value& serialized) + { + if (serialized.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + serialized_ = writer.write(serialized); + hasSerialized_ = true; + } + } + + + void OrthancJob::UpdateProgress(float progress) + { + if (progress < 0 || + progress > 1) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + progress_ = progress; + } + + + OrthancJob::OrthancJob(const std::string& jobType) : + jobType_(jobType), + progress_(0) + { + ClearContent(); + ClearSerialized(); + } + + + OrthancPluginJob* OrthancJob::Create(OrthancJob* job) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = OrthancPluginCreateJob( + GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), + CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, + CallbackStep, CallbackStop, CallbackReset); + + if (orthanc == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return orthanc; + } + } + + + std::string OrthancJob::Submit(OrthancJob* job, + int priority) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = Create(job); + + char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority); + + if (id == NULL) + { + LogError("Plugin cannot submit job"); + OrthancPluginFreeJob(GetGlobalContext(), orthanc); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + std::string tmp(id); + tmp.assign(id); + OrthancPluginFreeString(GetGlobalContext(), id); + + return tmp; + } + } + + + void OrthancJob::SubmitAndWait(Json::Value& result, + OrthancJob* job /* takes ownership */, + int priority) + { + std::string id = Submit(job, priority); + + for (;;) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + Json::Value status; + if (!RestApiGet(status, "/jobs/" + id, false) || + !status.isMember("State") || + status["State"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem); + } + + const std::string state = status["State"].asString(); + if (state == "Success") + { + if (status.isMember("Content")) + { + result = status["Content"]; + } + else + { + result = Json::objectValue; + } + + return; + } + else if (state == "Running") + { + continue; + } + else if (!status.isMember("ErrorCode") || + status["ErrorCode"].type() != Json::intValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); + } + else + { + if (!status.isMember("ErrorDescription") || + status["ErrorDescription"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); + } + else + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(static_cast(status["ErrorCode"].asInt()), + status["ErrorDescription"].asString()); +#else + LogError("Exception while executing the job: " + status["ErrorDescription"].asString()); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); +#endif + } + } + } + } + + + void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job) + { + static const char* KEY_SYNCHRONOUS = "Synchronous"; + static const char* KEY_ASYNCHRONOUS = "Asynchronous"; + static const char* KEY_PRIORITY = "Priority"; + + boost::movelib::unique_ptr protection(job); + + if (body.type() != Json::objectValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Expected a JSON object in the body"); +#else + LogError("Expected a JSON object in the body"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + + bool synchronous = true; + + if (body.isMember(KEY_SYNCHRONOUS)) + { + if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_SYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = body[KEY_SYNCHRONOUS].asBool(); + } + } + + if (body.isMember(KEY_ASYNCHRONOUS)) + { + if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_ASYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = !body[KEY_ASYNCHRONOUS].asBool(); + } + } + + int priority = 0; + + if (body.isMember(KEY_PRIORITY)) + { + if (body[KEY_PRIORITY].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_PRIORITY) + + "\" must be an integer"); +#else + LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + priority = !body[KEY_PRIORITY].asInt(); + } + } + + Json::Value result; + + if (synchronous) + { + OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority); + } + else + { + std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority); + + result = Json::objectValue; + result["ID"] = id; + result["Path"] = "/jobs/" + id; + } + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), + s.size(), "application/json"); + } + +#endif + + + + + /****************************************************************** + ** METRICS + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_METRICS == 1 + MetricsTimer::MetricsTimer(const char* name) : + name_(name) + { + start_ = boost::posix_time::microsec_clock::universal_time(); + } + + MetricsTimer::~MetricsTimer() + { + const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time(); + const boost::posix_time::time_duration diff = stop - start_; + OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast(diff.total_milliseconds()), + OrthancPluginMetricsType_Timer); + } +#endif + + + + + /****************************************************************** + ** HTTP CLIENT + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 + class HttpClient::RequestBodyWrapper : public boost::noncopyable + { + private: + static RequestBodyWrapper& GetObject(void* body) + { + assert(body != NULL); + return *reinterpret_cast(body); + } + + IRequestBody& body_; + bool done_; + std::string chunk_; + + public: + RequestBodyWrapper(IRequestBody& body) : + body_(body), + done_(false) + { + } + + static uint8_t IsDone(void* body) + { + return GetObject(body).done_; + } + + static const void* GetChunkData(void* body) + { + return GetObject(body).chunk_.c_str(); + } + + static uint32_t GetChunkSize(void* body) + { + return static_cast(GetObject(body).chunk_.size()); + } + + static OrthancPluginErrorCode Next(void* body) + { + RequestBodyWrapper& that = GetObject(body); + + if (that.done_) + { + return OrthancPluginErrorCode_BadSequenceOfCalls; + } + else + { + try + { + that.done_ = !that.body_.ReadNextChunk(that.chunk_); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } + } + }; + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer, + const char* key, + const char* value) + { + assert(answer != NULL && key != NULL && value != NULL); + + try + { + reinterpret_cast(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer, + const void* data, + uint32_t size) + { + assert(answer != NULL); + + try + { + reinterpret_cast(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + + HttpClient::HttpClient() : + httpStatus_(0), + method_(OrthancPluginHttpMethod_Get), + timeout_(0), + pkcs11_(false), + chunkedBody_(NULL), + allowChunkedTransfers_(true) + { + } + + + void HttpClient::AddHeaders(const HttpHeaders& headers) + { + for (HttpHeaders::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + headers_[it->first] = it->second; + } + } + + + void HttpClient::SetCredentials(const std::string& username, + const std::string& password) + { + username_ = username; + password_ = password; + } + + + void HttpClient::ClearCredentials() + { + username_.empty(); + password_.empty(); + } + + + void HttpClient::SetCertificate(const std::string& certificateFile, + const std::string& keyFile, + const std::string& keyPassword) + { + certificateFile_ = certificateFile; + certificateKeyFile_ = keyFile; + certificateKeyPassword_ = keyPassword; + } + + + void HttpClient::ClearCertificate() + { + certificateFile_.clear(); + certificateKeyFile_.clear(); + certificateKeyPassword_.clear(); + } + + + void HttpClient::ClearBody() + { + fullBody_.clear(); + chunkedBody_ = NULL; + } + + + void HttpClient::SwapBody(std::string& body) + { + fullBody_.swap(body); + chunkedBody_ = NULL; + } + + + void HttpClient::SetBody(const std::string& body) + { + fullBody_ = body; + chunkedBody_ = NULL; + } + + + void HttpClient::SetBody(IRequestBody& body) + { + fullBody_.clear(); + chunkedBody_ = &body; + } + + + namespace + { + class HeadersWrapper : public boost::noncopyable + { + private: + std::vector headersKeys_; + std::vector headersValues_; + + public: + HeadersWrapper(const HttpClient::HttpHeaders& headers) + { + headersKeys_.reserve(headers.size()); + headersValues_.reserve(headers.size()); + + for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headersKeys_.push_back(it->first.c_str()); + headersValues_.push_back(it->second.c_str()); + } + } + + void AddStaticString(const char* key, + const char* value) + { + headersKeys_.push_back(key); + headersValues_.push_back(value); + } + + uint32_t GetCount() const + { + return headersKeys_.size(); + } + + const char* const* GetKeys() const + { + return headersKeys_.empty() ? NULL : &headersKeys_[0]; + } + + const char* const* GetValues() const + { + return headersValues_.empty() ? NULL : &headersValues_[0]; + } + }; + + + class MemoryRequestBody : public HttpClient::IRequestBody + { + private: + std::string body_; + bool done_; + + public: + MemoryRequestBody(const std::string& body) : + body_(body), + done_(false) + { + if (body_.empty()) + { + done_ = true; + } + } + + virtual bool ReadNextChunk(std::string& chunk) + { + if (done_) + { + return false; + } + else + { + chunk.swap(body_); + done_ = true; + return true; + } + } + }; + + + // This class mimics Orthanc::ChunkedBuffer + class ChunkedBuffer : public boost::noncopyable + { + private: + typedef std::list Content; + + Content content_; + size_t size_; + + public: + ChunkedBuffer() : + size_(0) + { + } + + ~ChunkedBuffer() + { + Clear(); + } + + void Clear() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + + content_.clear(); + } + + void Flatten(std::string& target) const + { + target.resize(size_); + + size_t pos = 0; + + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + size_t s = (*it)->size(); + + if (s != 0) + { + memcpy(&target[pos], (*it)->c_str(), s); + pos += s; + } + } + + assert(size_ == 0 || + pos == target.size()); + } + + void AddChunk(const void* data, + size_t size) + { + content_.push_back(new std::string(reinterpret_cast(data), size)); + size_ += size; + } + + void AddChunk(const std::string& chunk) + { + content_.push_back(new std::string(chunk)); + size_ += chunk.size(); + } + }; + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + class MemoryAnswer : public HttpClient::IAnswer + { + private: + HttpClient::HttpHeaders headers_; + ChunkedBuffer body_; + + public: + const HttpClient::HttpHeaders& GetHeaders() const + { + return headers_; + } + + const ChunkedBuffer& GetBody() const + { + return body_; + } + + virtual void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + virtual void AddChunk(const void* data, + size_t size) + { + body_.AddChunk(data, size); + } + }; +#endif + } + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + void HttpClient::ExecuteWithStream(uint16_t& httpStatus, + IAnswer& answer, + IRequestBody& body) const + { + HeadersWrapper h(headers_); + + if (method_ == OrthancPluginHttpMethod_Post || + method_ == OrthancPluginHttpMethod_Put) + { + // Automatically set the "Transfer-Encoding" header if absent + bool found = false; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + if (boost::iequals(it->first, "Transfer-Encoding")) + { + found = true; + break; + } + } + + if (!found) + { + h.AddStaticString("Transfer-Encoding", "chunked"); + } + } + + RequestBodyWrapper request(body); + + OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient( + GetGlobalContext(), + &answer, + AnswerAddChunkCallback, + AnswerAddHeaderCallback, + &httpStatus, + method_, + url_.c_str(), + h.GetCount(), + h.GetKeys(), + h.GetValues(), + &request, + RequestBodyWrapper::IsDone, + RequestBodyWrapper::GetChunkData, + RequestBodyWrapper::GetChunkSize, + RequestBodyWrapper::Next, + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } +#endif + + + void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, + HttpHeaders& answerHeaders, + std::string& answerBody, + const std::string& body) const + { + HeadersWrapper headers(headers_); + + MemoryBuffer answerBodyBuffer, answerHeadersBuffer; + + OrthancPluginErrorCode error = OrthancPluginHttpClient( + GetGlobalContext(), + *answerBodyBuffer, + *answerHeadersBuffer, + &httpStatus, + method_, + url_.c_str(), + headers.GetCount(), + headers.GetKeys(), + headers.GetValues(), + body.empty() ? NULL : body.c_str(), + body.size(), + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + + Json::Value v; + answerHeadersBuffer.ToJson(v); + + if (v.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Value::Members members = v.getMemberNames(); + answerHeaders.clear(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& h = v[members[i]]; + if (h.type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + answerHeaders[members[i]] = h.asString(); + } + } + + answerBodyBuffer.ToString(answerBody); + } + + + void HttpClient::Execute(IAnswer& answer) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + if (allowChunkedTransfers_) + { + if (chunkedBody_ != NULL) + { + ExecuteWithStream(httpStatus_, answer, *chunkedBody_); + } + else + { + MemoryRequestBody wrapper(fullBody_); + ExecuteWithStream(httpStatus_, answer, wrapper); + } + + return; + } +#endif + + // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked + // transfers are disabled. This results in higher memory usage + // (all chunks from the answer body are sent at once) + + HttpHeaders answerHeaders; + std::string answerBody; + Execute(answerHeaders, answerBody); + + for (HttpHeaders::const_iterator it = answerHeaders.begin(); + it != answerHeaders.end(); ++it) + { + answer.AddHeader(it->first, it->second); + } + + if (!answerBody.empty()) + { + answer.AddChunk(answerBody.c_str(), answerBody.size()); + } + } + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + if (allowChunkedTransfers_) + { + MemoryAnswer answer; + Execute(answer); + answerHeaders = answer.GetHeaders(); + answer.GetBody().Flatten(answerBody); + return; + } +#endif + + // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked + // transfers are disabled. This results in higher memory usage + // (all chunks from the request body are sent at once) + + if (chunkedBody_ != NULL) + { + ChunkedBuffer buffer; + + std::string chunk; + while (chunkedBody_->ReadNextChunk(chunk)) + { + buffer.AddChunk(chunk); + } + + std::string body; + buffer.Flatten(body); + + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body); + } + else + { + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_); + } + } + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + Json::Value& answerBody /* out */) + { + std::string body; + Execute(answerHeaders, body); + + Json::Reader reader; + if (!reader.parse(body, answerBody)) + { + LogError("Cannot convert HTTP answer body to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void HttpClient::Execute() + { + HttpHeaders answerHeaders; + std::string body; + Execute(answerHeaders, body); + } + +#endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ + + + + + + /****************************************************************** + ** CHUNKED HTTP SERVER + ******************************************************************/ + + namespace Internals + { + void NullRestCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + } + + IChunkedRequestReader *NullChunkedRestCallback(const char* url, + const OrthancPluginHttpRequest* request) + { + return NULL; + } + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + + OrthancPluginErrorCode ChunkedRequestReaderAddChunk( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + + reinterpret_cast(reader)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode ChunkedRequestReaderExecute( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + + reinterpret_cast(reader)->Execute(output); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader) + { + if (reader != NULL) + { + delete reinterpret_cast(reader); + } + } + +#else + + OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler) + { + try + { + std::string allowed; + + if (GetHandler != Internals::NullRestCallback) + { + allowed += "GET"; + } + + if (PostHandler != Internals::NullChunkedRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "POST"; + } + + if (DeleteHandler != Internals::NullRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "DELETE"; + } + + if (PutHandler != Internals::NullChunkedRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "PUT"; + } + + switch (request->method) + { + case OrthancPluginHttpMethod_Get: + if (GetHandler == Internals::NullRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + GetHandler(output, url, request); + } + + break; + + case OrthancPluginHttpMethod_Post: + if (PostHandler == Internals::NullChunkedRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + boost::movelib::unique_ptr reader(PostHandler(url, request)); + if (reader.get() == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + reader->AddChunk(request->body, request->bodySize); + reader->Execute(output); + } + } + + break; + + case OrthancPluginHttpMethod_Delete: + if (DeleteHandler == Internals::NullRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + DeleteHandler(output, url, request); + } + + break; + + case OrthancPluginHttpMethod_Put: + if (PutHandler == Internals::NullChunkedRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + boost::movelib::unique_ptr reader(PutHandler(url, request)); + if (reader.get() == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + reader->AddChunk(request->body, request->bodySize); + reader->Execute(output); + } + } + + break; + + default: + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { +#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 + if (HasGlobalContext() && + e.HasDetails()) + { + // The "false" instructs Orthanc not to log the detailed + // error message. This is to avoid duplicating the details, + // because "OrthancException" already does it on construction. + OrthancPluginSetHttpErrorDetails + (GetGlobalContext(), output, e.GetDetails(), false); + } +#endif + + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + } + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup( + OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid) + { + assert(target != NULL && + rawHandler != NULL); + + try + { + IStorageCommitmentScpHandler& handler = *reinterpret_cast(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + void IStorageCommitmentScpHandler::Destructor(void* rawHandler) + { + assert(rawHandler != NULL); + delete reinterpret_cast(rawHandler); + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) : + toFree_(false), + instance_(instance) + { + } +#else + DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) : + toFree_(false), + instance_(instance) + { + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance::DicomInstance(const void* buffer, + size_t size) : + toFree_(true), + instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size)) + { + if (instance_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + } +#endif + + + DicomInstance::~DicomInstance() + { +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + if (toFree_ && + instance_ != NULL) + { + OrthancPluginFreeDicomInstance( + GetGlobalContext(), const_cast(instance_)); + } +#endif + } + + + std::string DicomInstance::GetRemoteAet() const + { + const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return std::string(s); + } + } + + + void DicomInstance::GetJson(Json::Value& target) const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_)); + s.ToJson(target); + } + + + void DicomInstance::GetSimplifiedJson(Json::Value& target) const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_)); + s.ToJson(target); + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + std::string DicomInstance::GetTransferSyntaxUid() const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_)); + + std::string result; + s.ToString(result); + return result; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + bool DicomInstance::HasPixelData() const + { + int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_); + if (result < 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return (result != 0); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void DicomInstance::GetRawFrame(std::string& target, + unsigned int frameIndex) const + { + MemoryBuffer buffer; + OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame( + GetGlobalContext(), *buffer, instance_, frameIndex); + + if (code == OrthancPluginErrorCode_Success) + { + buffer.ToString(target); + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const + { + OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame( + GetGlobalContext(), instance_, frameIndex); + + if (image == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return new OrthancImage(image); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void DicomInstance::Serialize(std::string& target) const + { + MemoryBuffer buffer; + OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance( + GetGlobalContext(), *buffer, instance_); + + if (code == OrthancPluginErrorCode_Success) + { + buffer.ToString(target); + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance* DicomInstance::Transcode(const void* buffer, + size_t size, + const std::string& transferSyntax) + { + OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance( + GetGlobalContext(), buffer, size, transferSyntax.c_str()); + + if (instance == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + boost::movelib::unique_ptr result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#endif +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,1228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#pragma once + +#include "OrthancPluginException.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for + * backward compatibility with Orthanc SDK <= 1.3.0. + * + * $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h + * + **/ +#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + +#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE) +#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_VERSION_MAJOR > major || \ + (ORTHANC_VERSION_MAJOR == major && \ + (ORTHANC_VERSION_MINOR > minor || \ + (ORTHANC_VERSION_MINOR == minor && \ + ORTHANC_VERSION_REVISION >= revision)))) +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0 +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1 +#else +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0 +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2) +# define HAS_ORTHANC_PLUGIN_PEERS 1 +# define HAS_ORTHANC_PLUGIN_JOB 1 +#else +# define HAS_ORTHANC_PLUGIN_PEERS 0 +# define HAS_ORTHANC_PLUGIN_JOB 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) +# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 1 +#else +# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) +# define HAS_ORTHANC_PLUGIN_METRICS 1 +#else +# define HAS_ORTHANC_PLUGIN_METRICS 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0) +# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 1 +#else +# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 1 +#else +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 1 +#else +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0) +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1 +#else +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 +#endif + + + +namespace OrthancPlugins +{ + typedef void (*RestCallback) (OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + void SetGlobalContext(OrthancPluginContext* context); + + bool HasGlobalContext(); + + OrthancPluginContext* GetGlobalContext(); + + + class OrthancImage; + + + class MemoryBuffer : public boost::noncopyable + { + private: + OrthancPluginMemoryBuffer buffer_; + + void Check(OrthancPluginErrorCode code); + + bool CheckHttp(OrthancPluginErrorCode code); + + public: + MemoryBuffer(); + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + // This constructor makes a copy of the given buffer in the memory + // handled by the Orthanc core + MemoryBuffer(const void* buffer, + size_t size); +#endif + + ~MemoryBuffer() + { + Clear(); + } + + OrthancPluginMemoryBuffer* operator*() + { + return &buffer_; + } + + // This transfers ownership from "other" to "this" + void Assign(OrthancPluginMemoryBuffer& other); + + void Swap(MemoryBuffer& other); + + OrthancPluginMemoryBuffer Release(); + + const char* GetData() const + { + if (buffer_.size > 0) + { + return reinterpret_cast(buffer_.data); + } + else + { + return NULL; + } + } + + size_t GetSize() const + { + return buffer_.size; + } + + bool IsEmpty() const + { + return GetSize() == 0 || GetData() == NULL; + } + + void Clear(); + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + + bool RestApiGet(const std::string& uri, + bool applyPlugins); + + bool RestApiGet(const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + bool RestApiPut(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + void CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags); + + void CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags); + + void ReadFile(const std::string& path); + + void GetDicomQuery(const OrthancPluginWorklistQuery* query); + + void DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength); + + bool HttpGet(const std::string& url, + const std::string& username, + const std::string& password); + + bool HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + bool HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + void GetDicomInstance(const std::string& instanceId); + }; + + + class OrthancString : public boost::noncopyable + { + private: + char* str_; + + void Clear(); + + public: + OrthancString() : + str_(NULL) + { + } + + ~OrthancString() + { + Clear(); + } + + // This transfers ownership, warning: The string must have been + // allocated by the Orthanc core + void Assign(char* str); + + const char* GetContent() const + { + return str_; + } + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + }; + + + class OrthancConfiguration : public boost::noncopyable + { + private: + Json::Value configuration_; // Necessarily a Json::objectValue + std::string path_; + + std::string GetPath(const std::string& key) const; + + void LoadConfiguration(); + + public: + OrthancConfiguration(); + + explicit OrthancConfiguration(bool load); + + const Json::Value& GetJson() const + { + return configuration_; + } + + bool IsSection(const std::string& key) const; + + void GetSection(OrthancConfiguration& target, + const std::string& key) const; + + bool LookupStringValue(std::string& target, + const std::string& key) const; + + bool LookupIntegerValue(int& target, + const std::string& key) const; + + bool LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const; + + bool LookupBooleanValue(bool& target, + const std::string& key) const; + + bool LookupFloatValue(float& target, + const std::string& key) const; + + bool LookupListOfStrings(std::list& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set& target, + const std::string& key, + bool allowSingleString) const; + + std::string GetStringValue(const std::string& key, + const std::string& defaultValue) const; + + int GetIntegerValue(const std::string& key, + int defaultValue) const; + + unsigned int GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const; + + bool GetBooleanValue(const std::string& key, + bool defaultValue) const; + + float GetFloatValue(const std::string& key, + float defaultValue) const; + + void GetDictionary(std::map& target, + const std::string& key) const; + }; + + class OrthancImage : public boost::noncopyable + { + private: + OrthancPluginImage* image_; + + void Clear(); + + void CheckImageAvailable() const; + + public: + OrthancImage(); + + explicit OrthancImage(OrthancPluginImage* image); + + OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height); + + OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer); + + ~OrthancImage() + { + Clear(); + } + + void UncompressPngImage(const void* data, + size_t size); + + void UncompressJpegImage(const void* data, + size_t size); + + void DecodeDicomImage(const void* data, + size_t size, + unsigned int frame); + + OrthancPluginPixelFormat GetPixelFormat() const; + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + unsigned int GetPitch() const; + + void* GetBuffer() const; + + const OrthancPluginImage* GetObject() const + { + return image_; + } + + void CompressPngImage(MemoryBuffer& target) const; + + void CompressJpegImage(MemoryBuffer& target, + uint8_t quality) const; + + void AnswerPngImage(OrthancPluginRestOutput* output) const; + + void AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) const; + + void* GetWriteableBuffer(); + + OrthancPluginImage* Release(); + }; + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + class FindMatcher : public boost::noncopyable + { + private: + OrthancPluginFindMatcher* matcher_; + const OrthancPluginWorklistQuery* worklist_; + + void SetupDicom(const void* query, + uint32_t size); + + public: + explicit FindMatcher(const OrthancPluginWorklistQuery* worklist); + + FindMatcher(const void* query, + uint32_t size) + { + SetupDicom(query, size); + } + + explicit FindMatcher(const MemoryBuffer& dicom) + { + SetupDicom(dicom.GetData(), dicom.GetSize()); + } + + ~FindMatcher(); + + bool IsMatch(const void* dicom, + uint32_t size) const; + + bool IsMatch(const MemoryBuffer& dicom) const + { + return IsMatch(dicom.GetData(), dicom.GetSize()); + } + }; +#endif + + + bool RestApiGet(Json::Value& result, + const std::string& uri, + bool applyPlugins); + + bool RestApiGetString(std::string& result, + const std::string& uri, + bool applyPlugins); + + bool RestApiGetString(std::string& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(std::string& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPost(Json::Value& result, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + inline bool RestApiPost(Json::Value& result, + const std::string& uri, + const MemoryBuffer& body, + bool applyPlugins) + { + return RestApiPost(result, uri, body.GetData(), + body.GetSize(), applyPlugins); + } + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPut(Json::Value& result, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + bool RestApiDelete(const std::string& uri, + bool applyPlugins); + + bool HttpDelete(const std::string& url, + const std::string& username, + const std::string& password); + + void AnswerJson(const Json::Value& value, + OrthancPluginRestOutput* output); + + void AnswerString(const std::string& answer, + const char* mimeType, + OrthancPluginRestOutput* output); + + void AnswerHttpError(uint16_t httpError, + OrthancPluginRestOutput* output); + + void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods); + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) + const char* AutodetectMimeType(const std::string& path); +#endif + + void LogError(const std::string& message); + + void LogWarning(const std::string& message); + + void LogInfo(const std::string& message); + + void ReportMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision); + + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision); + + + namespace Internals + { + template + static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + Callback(output, url, request); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { +#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 + if (HasGlobalContext() && + e.HasDetails()) + { + // The "false" instructs Orthanc not to log the detailed + // error message. This is to avoid duplicating the details, + // because "OrthancException" already does it on construction. + OrthancPluginSetHttpErrorDetails + (GetGlobalContext(), output, e.GetDetails(), false); + } +#endif + + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template + void RegisterRestCallback(const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock + (GetGlobalContext(), uri.c_str(), Internals::Protect); + } + else + { + OrthancPluginRegisterRestCallback + (GetGlobalContext(), uri.c_str(), Internals::Protect); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map Index; + + OrthancPluginPeers *peers_; + Index index_; + uint32_t timeout_; + + size_t GetPeerIndex(const std::string& name) const; + + public: + OrthancPeers(); + + ~OrthancPeers(); + + uint32_t GetTimeout() const + { + return timeout_; + } + + void SetTimeout(uint32_t timeout) + { + timeout_ = timeout; + } + + bool LookupName(size_t& target, + const std::string& name) const; + + std::string GetPeerName(size_t index) const; + + std::string GetPeerUrl(size_t index) const; + + std::string GetPeerUrl(const std::string& name) const; + + size_t GetPeersCount() const + { + return index_.size(); + } + + bool LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const; + + bool LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const; + + bool DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const; + + bool DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + size_t index, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const; + + bool DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPut(size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoDelete(size_t index, + const std::string& uri) const; + + bool DoDelete(const std::string& name, + const std::string& uri) const; + }; +#endif + + + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + class OrthancJob : public boost::noncopyable + { + private: + std::string jobType_; + std::string content_; + bool hasSerialized_; + std::string serialized_; + float progress_; + + static void CallbackFinalize(void* job); + + static float CallbackGetProgress(void* job); + + static const char* CallbackGetContent(void* job); + + static const char* CallbackGetSerialized(void* job); + + static OrthancPluginJobStepStatus CallbackStep(void* job); + + static OrthancPluginErrorCode CallbackStop(void* job, + OrthancPluginJobStopReason reason); + + static OrthancPluginErrorCode CallbackReset(void* job); + + protected: + void ClearContent(); + + void UpdateContent(const Json::Value& content); + + void ClearSerialized(); + + void UpdateSerialized(const Json::Value& serialized); + + void UpdateProgress(float progress); + + public: + OrthancJob(const std::string& jobType); + + virtual ~OrthancJob() + { + } + + virtual OrthancPluginJobStepStatus Step() = 0; + + virtual void Stop(OrthancPluginJobStopReason reason) = 0; + + virtual void Reset() = 0; + + static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */); + + static std::string Submit(OrthancJob* job /* takes ownership */, + int priority); + + static void SubmitAndWait(Json::Value& result, + OrthancJob* job /* takes ownership */, + int priority); + + // Submit a job from a POST on the REST API with the same + // conventions as in the Orthanc core (according to the + // "Synchronous" and "Priority" options) + static void SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_METRICS == 1 + inline void SetMetricsValue(char* name, + float value) + { + OrthancPluginSetMetricsValue(GetGlobalContext(), name, + value, OrthancPluginMetricsType_Default); + } + + class MetricsTimer : public boost::noncopyable + { + private: + std::string name_; + boost::posix_time::ptime start_; + + public: + explicit MetricsTimer(const char* name); + + ~MetricsTimer(); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 + class HttpClient : public boost::noncopyable + { + public: + typedef std::map HttpHeaders; + + class IRequestBody : public boost::noncopyable + { + public: + virtual ~IRequestBody() + { + } + + virtual bool ReadNextChunk(std::string& chunk) = 0; + }; + + + class IAnswer : public boost::noncopyable + { + public: + virtual ~IAnswer() + { + } + + virtual void AddHeader(const std::string& key, + const std::string& value) = 0; + + virtual void AddChunk(const void* data, + size_t size) = 0; + }; + + + private: + class RequestBodyWrapper; + + uint16_t httpStatus_; + OrthancPluginHttpMethod method_; + std::string url_; + HttpHeaders headers_; + std::string username_; + std::string password_; + uint32_t timeout_; + std::string certificateFile_; + std::string certificateKeyFile_; + std::string certificateKeyPassword_; + bool pkcs11_; + std::string fullBody_; + IRequestBody* chunkedBody_; + bool allowChunkedTransfers_; + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + void ExecuteWithStream(uint16_t& httpStatus, // out + IAnswer& answer, // out + IRequestBody& body) const; +#endif + + void ExecuteWithoutStream(uint16_t& httpStatus, // out + HttpHeaders& answerHeaders, // out + std::string& answerBody, // out + const std::string& body) const; + + public: + HttpClient(); + + uint16_t GetHttpStatus() const + { + return httpStatus_; + } + + void SetMethod(OrthancPluginHttpMethod method) + { + method_ = method; + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + void SetHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + void AddHeaders(const HttpHeaders& headers); + + void SetCredentials(const std::string& username, + const std::string& password); + + void ClearCredentials(); + + void SetTimeout(unsigned int timeout) // 0 for default timeout + { + timeout_ = timeout; + } + + void SetCertificate(const std::string& certificateFile, + const std::string& keyFile, + const std::string& keyPassword); + + void ClearCertificate(); + + void SetPkcs11(bool pkcs11) + { + pkcs11_ = pkcs11; + } + + void ClearBody(); + + void SwapBody(std::string& body); + + void SetBody(const std::string& body); + + void SetBody(IRequestBody& body); + + // This function can be used to disable chunked transfers if the + // remote server is Orthanc with a version <= 1.5.6. + void SetChunkedTransfersAllowed(bool allow) + { + allowChunkedTransfers_ = allow; + } + + bool IsChunkedTransfersAllowed() const + { + return allowChunkedTransfers_; + } + + void Execute(IAnswer& answer); + + void Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */); + + void Execute(HttpHeaders& answerHeaders /* out */, + Json::Value& answerBody /* out */); + + void Execute(); + }; +#endif + + + + class IChunkedRequestReader : public boost::noncopyable + { + public: + virtual ~IChunkedRequestReader() + { + } + + virtual void AddChunk(const void* data, + size_t size) = 0; + + virtual void Execute(OrthancPluginRestOutput* output) = 0; + }; + + + typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url, + const OrthancPluginHttpRequest* request); + + + namespace Internals + { + void NullRestCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + IChunkedRequestReader *NullChunkedRestCallback(const char* url, + const OrthancPluginHttpRequest* request); + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + template + static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + *reader = reinterpret_cast(Callback(url, request)); + if (*reader == NULL) + { + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + OrthancPluginErrorCode ChunkedRequestReaderAddChunk( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size); + + OrthancPluginErrorCode ChunkedRequestReaderExecute( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output); + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader); + +#else + + OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler); + + template< + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler + > + inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + return ChunkedRestCompatibility(output, url, request, GetHandler, + PostHandler, DeleteHandler, PutHandler); + } +#endif + } + + + + // NB: We use a templated class instead of a templated function, because + // default values are only available in functions since C++11 + template< + RestCallback GetHandler = Internals::NullRestCallback, + ChunkedRestCallback PostHandler = Internals::NullChunkedRestCallback, + RestCallback DeleteHandler = Internals::NullRestCallback, + ChunkedRestCallback PutHandler = Internals::NullChunkedRestCallback + > + class ChunkedRestRegistration : public boost::noncopyable + { + public: + static void Apply(const std::string& uri) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + OrthancPluginRegisterChunkedRestCallback( + GetGlobalContext(), uri.c_str(), + GetHandler == Internals::NullRestCallback ? NULL : Internals::Protect, + PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, + DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect, + PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, + Internals::ChunkedRequestReaderAddChunk, + Internals::ChunkedRequestReaderExecute, + Internals::ChunkedRequestReaderFinalize); +#else + OrthancPluginRegisterRestCallbackNoLock( + GetGlobalContext(), uri.c_str(), + Internals::ChunkedRestCompatibility); +#endif + } + }; + + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + class IStorageCommitmentScpHandler : public boost::noncopyable + { + public: + virtual ~IStorageCommitmentScpHandler() + { + } + + virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) = 0; + + static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid); + + static void Destructor(void* rawHandler); + }; +#endif + + + class DicomInstance : public boost::noncopyable + { + private: + bool toFree_; + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + const OrthancPluginDicomInstance* instance_; +#else + OrthancPluginDicomInstance* instance_; +#endif + + public: +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + DicomInstance(const OrthancPluginDicomInstance* instance); +#else + DicomInstance(OrthancPluginDicomInstance* instance); +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance(const void* buffer, + size_t size); +#endif + + ~DicomInstance(); + + std::string GetRemoteAet() const; + + const void* GetBuffer() const + { + return OrthancPluginGetInstanceData(GetGlobalContext(), instance_); + } + + size_t GetSize() const + { + return static_cast(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_)); + } + + void GetJson(Json::Value& target) const; + + void GetSimplifiedJson(Json::Value& target) const; + + OrthancPluginInstanceOrigin GetOrigin() const + { + return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_); + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + std::string GetTransferSyntaxUid() const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + bool HasPixelData() const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + unsigned int GetFramesCount() const + { + return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_); + } +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void GetRawFrame(std::string& target, + unsigned int frameIndex) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + OrthancImage* GetDecodedFrame(unsigned int frameIndex) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void Serialize(std::string& target) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + static DicomInstance* Transcode(const void* buffer, + size_t size, + const std::string& transferSyntax); +#endif + }; +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#pragma once + +#if !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::Orthanc::ErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::Orthanc::OrthancException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::Orthanc::ErrorCode_ ## code +#else +# include +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::OrthancPluginErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::OrthancPlugins::PluginException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::OrthancPluginErrorCode_ ## code +#endif + + +#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast(code)); + + +#define ORTHANC_PLUGINS_THROW_EXCEPTION(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code)); + + +#define ORTHANC_PLUGINS_CHECK_ERROR(code) \ + if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success)) \ + { \ + ORTHANC_PLUGINS_THROW_EXCEPTION(code); \ + } + + +namespace OrthancPlugins +{ +#if HAS_ORTHANC_EXCEPTION == 0 + class PluginException + { + private: + OrthancPluginErrorCode code_; + + public: + explicit PluginException(OrthancPluginErrorCode code) : code_(code) + { + } + + OrthancPluginErrorCode GetErrorCode() const + { + return code_; + } + + const char* What(OrthancPluginContext* context) const + { + const char* description = OrthancPluginGetErrorDescription(context, code_); + if (description) + { + return description; + } + else + { + return "No description available"; + } + } + }; +#endif +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,31 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 . + + +# In Orthanc <= 1.7.1, the instructions below were part of +# "Compiler.cmake", and were protected by the (now unused) option +# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map") +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list") +endif() diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,12 @@ +# This is a version-script for Orthanc plugins + +{ +global: + OrthancPluginInitialize; + OrthancPluginFinalize; + OrthancPluginGetName; + OrthancPluginGetVersion; + +local: + *; +}; diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,4740 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * -# void OrthancPluginFinalize(): + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# const char* OrthancPluginGetName(): + * The plugin must return a short string to identify itself. + * -# const char* OrthancPluginGetVersion(): + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an extern "C" section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup Worklists Worklists + * @brief Functions to register and manage worklists. + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * 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 . + **/ + + + +#pragma once + + +#include +#include + +#ifdef WIN32 +#define ORTHANC_PLUGINS_API __declspec(dllexport) +#else +#define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include + +#include + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, + OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const char* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + + /* Sending answers to REST calls */ + _OrthancPluginService_AnswerBuffer = 2000, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can happen to DICOM resources. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + 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_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + * @see OrthancPluginDicomToJson() + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Human = 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; + + + /** + * Flags to the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** + * The constraints on the DICOM identifiers that must be supported + * by the database plugins. + **/ + typedef enum + { + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ + + _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff + } OrthancPluginIdentifierConstraint; + + + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * @brief A memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callback + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance received by Orthanc. + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup Worklists + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query. + * @ingroup Worklists + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** + * @brief Signature of a function to free dynamic memory. + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback to handle the C-Find SCP requests received by Orthanc. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* remoteAet, + const char* calledAet); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of this C header is + * compatible with the current version of Orthanc. The result of + * this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + int major, minor, revision; + + if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 1; + } + + if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 1; + } + + if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, it is up to the plugin to + * implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const char* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPostAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPost + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDeleteAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDelete + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPutAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPut + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. This + * function must be called during the initialization of the plugin, + * i.e. inside the OrthancPluginInitialize() public function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning If your change callback has to call the REST API of + * Orthanc, you should make these calls in a separate thread (with + * the events passing through a message queue). Otherwise, this + * could result in deadlocks in the presence of other plugins or Lua + * script. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const char* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const char* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const char* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new tag in the dictionary of DICOM tags + * that are known to Orthanc. This function should be used in the + * OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + OrthancPluginResourceType level; + } _OrthancPluginReconstructMainDicomTags; + + /** + * @brief Reconstruct the main DICOM tags. + * + * This function requests the Orthanc core to reconstruct the main + * DICOM tags of all the resources of the given type. This function + * can only be used as a part of the upgrade of a custom database + * back-end + * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A + * database transaction will be automatically setup. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param level The type of the resources of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + OrthancPluginResourceType level) + { + _OrthancPluginReconstructMainDicomTags params; + params.level = level; + params.storageArea = storageArea; + + return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + + 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 Flags governing the output. + * @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 Flags governing the output. + * @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; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to the decoding of + * DICOM images, replacing the built-in decoder of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/BoostExtendedConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/BoostExtendedConfiguration.cmake Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,40 @@ +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., 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 . + + +if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST) + set(BOOST_EXTENDED_SOURCES + ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp + ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp + ${BOOST_SOURCES_DIR}/libs/chrono/src/thread_clock.cpp + ${BOOST_SOURCES_DIR}/libs/chrono/src/chrono.cpp + ${BOOST_SOURCES_DIR}/libs/chrono/src/process_cpu_clocks.cpp + #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp + ) + add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB) +else() + link_libraries(boost_program_options) +endif() diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,111 @@ +cmake_minimum_required(VERSION 2.8.10) + +project(OrthancStone) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + set(ORTHANC_BOOST_COMPONENTS program_options) + + set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") + set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") + mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) + +else() + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_LOCALE ON) # Necessary for text rendering + set(ENABLE_OPENGL ON) # <== + set(ENABLE_WEB_CLIENT ON) +endif() + +set(ENABLE_DCMTK ON) # <== +set(ENABLE_SDL ON) + +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/Utilities.cmake) + +if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + # This include must be after "OrthancStoneConfiguration.cmake" to + # have "BOOST_SOURCES_DIR" defined + include(${CMAKE_SOURCE_DIR}/BoostExtendedConfiguration.cmake) +endif() + + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +EmbedResources( + COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SortFilesInSourceGroups() + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + ${BOOST_EXTENDED_SOURCES} + ) + +message(${AUTOGENERATED_SOURCES}) + + + +############################# +project(RtViewerSdl) + +add_executable(RtViewerSdl + RtViewer/RtViewerSdl.cpp + SdlHelpers.h + ../Common/RtViewerApp.cpp + ../Common/RtViewerApp.h + ../Common/RtViewerView.cpp + ../Common/RtViewerView.h + ../Common/SampleHelpers.h + ) + +target_link_libraries(RtViewerSdl OrthancStone ${DCMTK_LIBRARIES}) + +############################# +project(SdlSimpleViewer) + +add_executable(SdlSimpleViewer + SdlHelpers.h + ../Common/SampleHelpers.h + SingleFrameViewer/SdlSimpleViewerApplication.h + SingleFrameViewer/SdlSimpleViewer.cpp + ) + +target_link_libraries(SdlSimpleViewer OrthancStone ${DCMTK_LIBRARIES}) + +############################# +project(UnitTests) + +add_executable(UnitTests + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/SortedFramesTests.cpp + ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp + ) + +target_link_libraries(UnitTests OrthancStone) + +add_custom_command( + TARGET UnitTests + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" + "$/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" +) + +target_link_libraries(UnitTests OrthancStone ${DCMTK_LIBRARIES}) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/RtViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/RtViewer/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 2.8.10) + +project(RtViewerSdl) + +include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + set(ORTHANC_BOOST_COMPONENTS program_options) + + set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") + set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") + mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) + +else() + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_LOCALE ON) # Necessary for text rendering + set(ENABLE_OPENGL ON) # <== + set(ENABLE_WEB_CLIENT ON) +endif() + +set(ENABLE_DCMTK ON) # <== +set(ENABLE_SDL ON) + +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../Utilities.cmake) + +if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + # This include must be after "OrthancStoneConfiguration.cmake" to + # have "BOOST_SOURCES_DIR" defined + include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake) +endif() + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +EmbedResources( + COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +SortFilesInSourceGroups() + +add_executable(RtViewerSdl + RtViewerSdl.cpp + ../SdlHelpers.h + ../../Common/RtViewerApp.cpp + ../../Common/RtViewerApp.h + ../../Common/RtViewerView.cpp + ../../Common/RtViewerView.h + ../../Common/SampleHelpers.h + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + ${BOOST_EXTENDED_SOURCES} + ) + +target_link_libraries(RtViewerSdl ${DCMTK_LIBRARIES}) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/RtViewer/CMakeSettings.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/RtViewer/CMakeSettings.json Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,34 @@ +{ + // this file is meant to be used with Visual Studio CMake support + // tested with VS 16.5.4+ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}/../../../../out/build-stone-sdl-RtViewer-ninja-msvc16-x64-Debug", + "installRoot": "${projectDir}/../../../../out/install-stone-sdl-RtViewer-ninja-msvc16-x64-Debug", + "cmakeCommandArgs": "", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "ALLOW_DOWNLOADS", + "value": "True", + "type": "BOOL" + }, + { + "name": "MSVC_MULTIPLE_PROCESSES", + "value": "True", + "type": "BOOL" + }, + { + "name": "STATIC_BUILD", + "value": "True", + "type": "BOOL" + } + ] + } + ] +} \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/RtViewer/RtViewerSdl.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,461 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + +#include "../../Common/RtViewerApp.h" +#include "../../Common/RtViewerView.h" +#include "../SdlHelpers.h" + +#include + +// Stone of Orthanc includes +#include "../../../Sources/Loaders/GenericLoadersContext.h" +#include "../../../Sources/OpenGL/OpenGLIncludes.h" +#include "../../../Sources/OpenGL/SdlOpenGLContext.h" +#include "../../../Sources/StoneException.h" +#include "../../../Sources/StoneInitialization.h" + +// Orthanc (a.o. for screenshot capture) +#include // For std::unique_ptr<> +#include +#include +#include + + +#include +#include + +// #include this include might be necessary in more recent boost versions + +#include + +#include + + +#if !defined(__APPLE__) +/** + * OpenGL: "OS X does not seem to support debug output functionality + * (as gathered online)." + * https://learnopengl.com/In-Practice/Debugging + **/ +static void GLAPIENTRY +OpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) +{ + if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) + { + fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); + } +} +#endif + +namespace OrthancStone +{ + void RtViewerView::EnableGLDebugOutput() + { +#if !defined(__APPLE__) + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(OpenGLMessageCallback, 0); +#endif + } + + boost::shared_ptr RtViewerView::CreateViewport(const std::string& canvasId) + { + // False means we do NOT let Windows treat this as a legacy application that needs to be scaled + return SdlOpenGLViewport::Create(canvasId, 1024, 1024, false); + } + + void RtViewerApp::ProcessOptions(int argc, char* argv[]) + { + namespace po = boost::program_options; + po::options_description desc("Usage"); + + desc.add_options() + ("loglevel", po::value()->default_value("WARNING"), + "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)") + + ("orthanc", po::value()->default_value("http://localhost:8042"), + "Base URL of the Orthanc instance") + + ("ctseries", po::value()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"), + "Orthanc ID of the CT series to load. This must be supplied.") + + ("rtdose", po::value()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"), + "Orthanc ID of the RTDOSE instance to load. This may be an empty string.") + + ("rtstruct", po::value()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"), + "Orthanc ID of the RTSTRUCT instance to load. This may be an empty string.") + ; + + std::cout << desc << std::endl; + + po::variables_map vm; + try + { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + } + catch (std::exception& e) + { + std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl; + } + + for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it) + { + std::string key = it->first; + const po::variable_value& value = it->second; + const std::string& strValue = value.as(); + SetArgument(key, strValue); + } + } + + void RtViewerApp::RunSdl(int argc, char* argv[]) + { + ProcessOptions(argc, argv); + + /** + Create the shared loaders context + */ + loadersContext_.reset(new GenericLoadersContext(1, 4, 1)); + + // we are in SDL --> downcast to concrete type + boost::shared_ptr loadersContext = boost::dynamic_pointer_cast(loadersContext_); + + /** + Url of the Orthanc instance + Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In + wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative + URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders + plugin that serves the main web application from an URL like "http://localhost:8042/stone-rtviewer" (with ".." + leading to the main Orthanc root URL) + */ + std::string orthancUrl = arguments_["orthanc"]; + + { + Orthanc::WebServiceParameters p; + if (HasArgument("orthanc")) + { + p.SetUrl(orthancUrl); + } + if (HasArgument("user")) + { + ORTHANC_ASSERT(HasArgument("password")); + p.SetCredentials(GetArgument("user"), GetArgument("password")); + } + else + { + ORTHANC_ASSERT(!HasArgument("password")); + } + loadersContext->SetOrthancParameters(p); + } + + loadersContext->StartOracle(); + + CreateLoaders(); + + /** + Create viewports + */ + CreateView("RtViewer Axial", VolumeProjection_Axial); + CreateView("RtViewer Coronal", VolumeProjection_Coronal); + CreateView("RtViewer Sagittal", VolumeProjection_Sagittal); + + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->PrepareViewport(); + views_[i]->EnableGLDebugOutput(); + } + + DefaultViewportInteractor interactor; + + /** + It is very important that the Oracle (responsible for network I/O) be started before creating and firing the + loaders, for any command scheduled by the loader before the oracle is started will be lost. + */ + StartLoaders(); + + + SdlRunLoop(views_, interactor); + loadersContext->StopOracle(); + } + + void RtViewerView::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + std::unique_ptr lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + + std::string ttf; + Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT); + + CairoCompositor compositor(canvasWidth, canvasHeight); + compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1); + compositor.Refresh(scene); + + Orthanc::ImageAccessor canvas; + compositor.GetCanvas().GetReadOnlyAccessor(canvas); + + Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); + Orthanc::ImageProcessing::Convert(png, canvas); + + Orthanc::PngWriter writer; + writer.WriteToFile(target, png); + } + + static boost::shared_ptr GetViewFromWindowId( + const std::vector >& views, + Uint32 windowID) + { + using namespace OrthancStone; + for (size_t i = 0; i < views.size(); ++i) + { + boost::shared_ptr view = views[i]; + boost::shared_ptr viewport = view->GetViewport(); + boost::shared_ptr sdlViewport = boost::dynamic_pointer_cast(viewport); + Uint32 curWindowID = sdlViewport->GetSdlWindowId(); + if (windowID == curWindowID) + return view; + } + return boost::shared_ptr(); + } + + void RtViewerApp::SdlRunLoop(const std::vector >& views, + OrthancStone::DefaultViewportInteractor& interactor) + { + using namespace OrthancStone; + + // const std::vector >& views + std::vector > viewports; + for (size_t i = 0; i < views.size(); ++i) + { + boost::shared_ptr view = views[i]; + boost::shared_ptr viewport = view->GetViewport(); + boost::shared_ptr sdlViewport = + boost::dynamic_pointer_cast(viewport); + viewports.push_back(sdlViewport); + } + + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + bool stop = false; + while (!stop) + { + std::vector sdlEvents; + std::map userEventsMap; + SDL_Event sdlEvent; + + // FIRST: collect all pending events + while (SDL_PollEvent(&sdlEvent) != 0) + { + if ( (sdlEvent.type >= SDL_USEREVENT) && + (sdlEvent.type < SDL_LASTEVENT) ) + { + // we don't want to have multiple refresh events , + // and since every refresh event is a user event with a special type, + // we use a map + userEventsMap[sdlEvent.type] = sdlEvent; + } + else + { + sdlEvents.push_back(sdlEvent); + } + } + + // SECOND: add all user events to sdlEvents + for (std::map::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it) + sdlEvents.push_back(it->second); + + // now process the events + for (std::vector::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it) + { + const SDL_Event& sdlEvent = *it; + + if (sdlEvent.type == SDL_QUIT) + { + stop = true; + break; + } + else if (sdlEvent.type == SDL_WINDOWEVENT && + (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED || + sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + boost::shared_ptr view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + boost::shared_ptr sdlViewport = + boost::dynamic_pointer_cast(view->GetViewport()); + + sdlViewport->UpdateSize(sdlEvent.window.data1, sdlEvent.window.data2); + } + else if (sdlEvent.type == SDL_WINDOWEVENT && + (sdlEvent.window.event == SDL_WINDOWEVENT_SHOWN || + sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED)) + { + boost::shared_ptr view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + boost::shared_ptr sdlViewport = + boost::dynamic_pointer_cast(view->GetViewport()); + sdlViewport->Paint(); + } + else if (sdlEvent.type == SDL_KEYDOWN && + sdlEvent.key.repeat == 0 /* Ignore key bounce */) + { + boost::shared_ptr view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + switch (sdlEvent.key.keysym.sym) + { + case SDLK_f: + { + boost::shared_ptr sdlViewport = + boost::dynamic_pointer_cast(view->GetViewport()); + sdlViewport->ToggleMaximize(); + } + break; + + case SDLK_s: + { + std::unique_ptr lock(view->GetViewport()->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN || + sdlEvent.type == SDL_MOUSEMOTION || + sdlEvent.type == SDL_MOUSEBUTTONUP) + { + boost::shared_ptr view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + std::unique_ptr lock(view->GetViewport()->Lock()); + if (lock->HasCompositor()) + { + OrthancStone::PointerEvent p; + OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), + sdlEvent, keyboardState, scancodeCount); + + switch (sdlEvent.type) + { + case SDL_MOUSEBUTTONDOWN: + interactor.SetWindowingLayer(view->GetCtLayerIndex()); + lock->GetController().HandleMousePress(interactor, p, + lock->GetCompositor().GetCanvasWidth(), + lock->GetCompositor().GetCanvasHeight()); + lock->Invalidate(); + break; + + case SDL_MOUSEMOTION: + if (lock->GetController().HandleMouseMove(p)) + { + lock->Invalidate(); + } + break; + + case SDL_MOUSEBUTTONUP: + lock->GetController().HandleMouseRelease(p); + lock->Invalidate(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + else if (sdlEvent.type == SDL_MOUSEWHEEL) + { + boost::shared_ptr view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + int delta = 0; + if (sdlEvent.wheel.y < 0) + delta = -1; + if (sdlEvent.wheel.y > 0) + delta = 1; + + view->Scroll(delta); + } + else + { + for (size_t i = 0; i < views.size(); ++i) + { + boost::shared_ptr sdlViewport = + boost::dynamic_pointer_cast(views[i]->GetViewport()); + if (sdlViewport->IsRefreshEvent(sdlEvent)) + { + sdlViewport->Paint(); + } + } + } + } + // Small delay to avoid using 100% of CPU + SDL_Delay(1); + } + } + } +} + +boost::weak_ptr g_app; + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + using namespace OrthancStone; + + StoneInitialize(); + + try + { + boost::shared_ptr app = RtViewerApp::Create(); + g_app = app; + app->RunSdl(argc,argv); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/SdlHelpers.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/SdlHelpers.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,142 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#pragma once + +#if ORTHANC_ENABLE_SDL != 1 +# error This file cannot be used if ORTHANC_ENABLE_SDL != 1 +#endif + +#include "../../Sources/Viewport/SdlViewport.h" + +#include + +#include + +#include +#include + +namespace OrthancStoneHelpers +{ + + inline OrthancStone::KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState, + const int scancodeCount) + { + using namespace OrthancStone; + int result = KeyboardModifiers_None; + + if (keyboardState != NULL) + { + if (SDL_SCANCODE_LSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_LSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_RSHIFT < scancodeCount && + keyboardState[SDL_SCANCODE_RSHIFT]) + { + result |= KeyboardModifiers_Shift; + } + + if (SDL_SCANCODE_LCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_LCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_RCTRL < scancodeCount && + keyboardState[SDL_SCANCODE_RCTRL]) + { + result |= KeyboardModifiers_Control; + } + + if (SDL_SCANCODE_LALT < scancodeCount && + keyboardState[SDL_SCANCODE_LALT]) + { + result |= KeyboardModifiers_Alt; + } + + if (SDL_SCANCODE_RALT < scancodeCount && + keyboardState[SDL_SCANCODE_RALT]) + { + result |= KeyboardModifiers_Alt; + } + } + + return static_cast(result); + } + + + inline void GetPointerEvent(OrthancStone::PointerEvent& p, + const OrthancStone::ICompositor& compositor, + SDL_Event event, + const uint8_t* keyboardState, + const int scancodeCount) + { + using namespace OrthancStone; + KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); + + switch (event.button.button) + { + case SDL_BUTTON_LEFT: + p.SetMouseButton(OrthancStone::MouseButton_Left); + break; + + case SDL_BUTTON_RIGHT: + p.SetMouseButton(OrthancStone::MouseButton_Right); + break; + + case SDL_BUTTON_MIDDLE: + p.SetMouseButton(OrthancStone::MouseButton_Middle); + break; + + default: + p.SetMouseButton(OrthancStone::MouseButton_None); + break; + } + + p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); + p.SetAltModifier( (modifiers & KeyboardModifiers_Alt) != 0); + p.SetControlModifier( (modifiers & KeyboardModifiers_Control) != 0); + p.SetShiftModifier( (modifiers & KeyboardModifiers_Shift) != 0); + } + + + inline boost::shared_ptr GetSdlViewportFromWindowId( + const std::vector >& viewports, + Uint32 windowID) + { + using namespace OrthancStone; + for (size_t i = 0; i < viewports.size(); ++i) + { + boost::shared_ptr viewport = viewports[i]; + boost::shared_ptr sdlViewport = boost::dynamic_pointer_cast(viewport); + Uint32 curWindowID = sdlViewport->GetSdlWindowId(); + if (windowID == curWindowID) + return sdlViewport; + } + + return boost::shared_ptr(); + } +} + + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/SingleFrameViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/SingleFrameViewer/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 2.8.10) + +project(SdlSimpleViewer) + +include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + set(ORTHANC_BOOST_COMPONENTS program_options) + + set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") + set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") + mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) + include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) + +else() + set(ENABLE_GOOGLE_TEST ON) + set(ENABLE_LOCALE ON) # Necessary for text rendering + set(ENABLE_OPENGL ON) # <== + set(ENABLE_WEB_CLIENT ON) +endif() + +set(ENABLE_DCMTK ON) # <== +set(ENABLE_SDL ON) + +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) +include(${CMAKE_SOURCE_DIR}/../Utilities.cmake) + +if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + # This include must be after "OrthancStoneConfiguration.cmake" to + # have "BOOST_SOURCES_DIR" defined + include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake) +endif() + +SortFilesInSourceGroups() + +add_executable(SdlSimpleViewer + ../SdlHelpers.h + ../../Common/SampleHelpers.h + SdlSimpleViewerApplication.h + SdlSimpleViewer.cpp + ${ORTHANC_STONE_SOURCES} + ) + +target_link_libraries(SdlSimpleViewer ${DCMTK_LIBRARIES}) + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/SingleFrameViewer/CMakeSettings.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/SingleFrameViewer/CMakeSettings.json Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,37 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "MSVC_MULTIPLE_PROCESSES", + "value": "True", + "type": "BOOL" + }, + { + "name": "ALLOW_DOWNLOADS", + "value": "True", + "type": "BOOL" + }, + { + "name": "STATIC_BUILD", + "value": "True", + "type": "BOOL" + }, + { + "name": "OPENSSL_NO_CAPIENG", + "value": "True", + "type": "BOOL" + }, + ] + } + ] +} \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,276 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#include "SdlSimpleViewerApplication.h" +#include "../SdlHelpers.h" +#include "../../Common/SampleHelpers.h" + +#include "../../../Sources/Loaders/GenericLoadersContext.h" +#include "../../../Sources/StoneException.h" +#include "../../../Sources/StoneEnumerations.h" +#include "../../../Sources/StoneInitialization.h" +#include "../../../Sources/Viewport/SdlViewport.h" + +#include // For std::unique_ptr<> +#include + +#include +#include + +#include + + +std::string orthancUrl; +std::string instanceId; +int frameIndex = 0; + + +static void ProcessOptions(int argc, char* argv[]) +{ + namespace po = boost::program_options; + po::options_description desc("Usage"); + + desc.add_options() + ("loglevel", po::value()->default_value("WARNING"), + "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)") + + ("orthanc", po::value()->default_value("http://localhost:8042"), + "Base URL of the Orthanc instance") + + ("instance", po::value()->default_value("285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"), + "Orthanc ID of the instance to display") + + ("frame_index", po::value()->default_value(0), + "The zero-based index of the frame (for multi-frame instances)") + ; + + std::cout << desc << std::endl; + + po::variables_map vm; + try + { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + } + catch (std::exception& e) + { + std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl; + } + + if (vm.count("loglevel") > 0) + { + std::string logLevel = vm["loglevel"].as(); + OrthancStoneHelpers::SetLogLevel(logLevel); + } + + if (vm.count("orthanc") > 0) + { + // maybe check URL validity here + orthancUrl = vm["orthanc"].as(); + } + + if (vm.count("instance") > 0) + { + instanceId = vm["instance"].as(); + } + + if (vm.count("frame_index") > 0) + { + frameIndex = vm["frame_index"].as(); + } + +} + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + try + { + OrthancStone::StoneInitialize(); + + ProcessOptions(argc, argv); + + //Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + + { + +#if 1 + boost::shared_ptr viewport = + OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); +#else + boost::shared_ptr viewport = + OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600); +#endif + + OrthancStone::GenericLoadersContext context(1, 4, 1); + + Orthanc::WebServiceParameters orthancWebService; + orthancWebService.SetUrl(orthancUrl); + context.SetOrthancParameters(orthancWebService); + + context.StartOracle(); + + { + + boost::shared_ptr application( + SdlSimpleViewerApplication::Create(context, viewport)); + + OrthancStone::DicomSource source; + + application->LoadOrthancFrame(source, instanceId, frameIndex); + + OrthancStone::DefaultViewportInteractor interactor; + interactor.SetWindowingLayer(0); + + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + bool stop = false; + while (!stop) + { + bool paint = false; + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) + { + stop = true; + break; + } + else if (viewport->IsRefreshEvent(event)) + { + paint = true; + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + viewport->UpdateSize(event.window.data1, event.window.data2); + } + else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_SHOWN || + event.window.event == SDL_WINDOWEVENT_EXPOSED)) + { + paint = true; + } + else if (event.type == SDL_KEYDOWN && + event.key.repeat == 0 /* Ignore key bounce */) + { + switch (event.key.keysym.sym) + { + case SDLK_f: + viewport->ToggleMaximize(); + break; + + case SDLK_s: + application->FitContent(); + break; + + case SDLK_q: + stop = true; + break; + + default: + break; + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN || + event.type == SDL_MOUSEMOTION || + event.type == SDL_MOUSEBUTTONUP) + { + std::unique_ptr lock(viewport->Lock()); + if (lock->HasCompositor()) + { + OrthancStone::PointerEvent p; + OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), + event, keyboardState, scancodeCount); + + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + lock->GetController().HandleMousePress(interactor, p, + lock->GetCompositor().GetCanvasWidth(), + lock->GetCompositor().GetCanvasHeight()); + lock->Invalidate(); + break; + + case SDL_MOUSEMOTION: + if (lock->GetController().HandleMouseMove(p)) + { + lock->Invalidate(); + } + break; + + case SDL_MOUSEBUTTONUP: + lock->GetController().HandleMouseRelease(p); + lock->Invalidate(); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + } + } + + if (paint) + { + viewport->Paint(); + } + + // Small delay to avoid using 100% of CPU + SDL_Delay(1); + } + } + context.StopOracle(); + } + } + + OrthancStone::StoneFinalize(); + return 0; + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "OrthancException: " << e.What(); + return -1; + } + catch (OrthancStone::StoneException& e) + { + LOG(ERROR) << "StoneException: " << e.What(); + return -1; + } + catch (std::runtime_error& e) + { + LOG(ERROR) << "Runtime error: " << e.what(); + return -1; + } + catch (...) + { + LOG(ERROR) << "Native exception"; + return -1; + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,168 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#pragma once + +#include "../../../Sources/Viewport/IViewport.h" +#include "../../../Sources/Loaders/DicomResourcesLoader.h" +#include "../../../Sources/Loaders/ILoadersContext.h" +#include "../../../Sources/Loaders/SeriesFramesLoader.h" +#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h" + +#include // For std::unique_ptr<> + +#include + + +using OrthancStone::ILoadersContext; +using OrthancStone::ObserverBase; +using OrthancStone::IViewport; +using OrthancStone::DicomResourcesLoader; +using OrthancStone::SeriesFramesLoader; +using OrthancStone::TextureBaseSceneLayer; +using OrthancStone::DicomSource; +using OrthancStone::SeriesThumbnailsLoader; +using OrthancStone::LoadedDicomResources; +using OrthancStone::SeriesThumbnailType; +using OrthancStone::OracleScheduler; +using OrthancStone::OrthancRestApiCommand; +using OrthancStone::OracleScheduler; +using OrthancStone::OracleScheduler; +using OrthancStone::OracleScheduler; + + +class SdlSimpleViewerApplication : public ObserverBase +{ + +public: + static boost::shared_ptr Create(ILoadersContext& context, boost::shared_ptr viewport) + { + boost::shared_ptr application(new SdlSimpleViewerApplication(context, viewport)); + + { + std::unique_ptr lock(context.Lock()); + application->dicomLoader_ = DicomResourcesLoader::Create(*lock); + } + + application->Register(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame) + { + std::unique_ptr lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject(frame)); + } + +#if 0 + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::unique_ptr lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject(frame)); + } +#endif + + void FitContent() + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + +private: + ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr dicomLoader_; + boost::shared_ptr framesLoader_; + + SdlSimpleViewerApplication(ILoadersContext& context, + boost::shared_ptr viewport) : + context_(context), + viewport_(viewport) + { + } + + void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + { + LOG(INFO) << "Frame decoded! " + << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() + << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); + + std::unique_ptr layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().SetLayer(0, layer.release()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + void Handle(const DicomResourcesLoader::SuccessMessage& message) + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + //message.GetResources()->GetResource(0).Print(stdout); + + { + std::unique_ptr lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast( + f.Create(*lock)); + + Register( + *framesLoader_, &SdlSimpleViewerApplication::Handle); + + assert(message.HasUserPayload()); + + const Orthanc::SingleValueObject& payload = + dynamic_cast&>( + message.GetUserPayload()); + + LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); + framesLoader_->ScheduleLoadFrame( + 0, message.GetDicomSource(), payload.GetValue(), + message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, + NULL); + } + } + +}; + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/Sdl/Utilities.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/Sdl/Utilities.cmake Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,84 @@ + + +macro(GetFilenameFromPath TargetVariable Path) +#message(STATUS "GetFilenameFromPath (1): Path = ${Path} TargetVariable = ${${TargetVariable}}") +string(REPLACE "\\" "/" PathWithFwdSlashes "${Path}") +string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${PathWithFwdSlashes}") +#message(STATUS "GetFilenameFromPath (2): Path = ${Path} Path = ${PathWithFwdSlashes} TargetVariable = ${TargetVariable}") +endmacro() + +macro(GetFilePathWithoutLastExtension TargetVariable FilePath) +string(REGEX REPLACE "(^.*)\\.([^\\.]+)" "\\1" ${TargetVariable} "${FilePath}") +#message(STATUS "GetFileNameWithoutLastExtension: FilePath = ${FilePath} TargetVariable = ${${TargetVariable}}") +endmacro() + +macro(Test_GetFilePathWithoutLastExtension) +set(tmp "/prout/zi/goui.goui.cpp") +GetFilePathWithoutLastExtension(TargetVariable "${tmp}") +if(NOT ("${TargetVariable}" STREQUAL "/prout/zi/goui.goui")) + message(FATAL_ERROR "Test_GetFilePathWithoutLastExtension failed (1)") +else() + #message(STATUS "Test_GetFilePathWithoutLastExtension: <>") +endif() +endmacro() + +Test_GetFilePathWithoutLastExtension() + +macro(Test_GetFilenameFromPath) +set(tmp "/prout/../../dada/zi/goui.goui.cpp") +GetFilenameFromPath(TargetVariable "${tmp}") +if(NOT ("${TargetVariable}" STREQUAL "goui.goui.cpp")) + message(FATAL_ERROR "Test_GetFilenameFromPath failed") +else() + #message(STATUS "Test_GetFilenameFromPath: <>") +endif() +endmacro() + +Test_GetFilenameFromPath() + +macro(SortFilesInSourceGroups) + if(FALSE) + foreach(source IN LISTS ORTHANC_STONE_SOURCES) + # if("${source}" MATCHES ".*/pixman.*\\.c") + # message("pixman source: ${source}") + # elseif("${source}" MATCHES ".*/pixman.*\\.c") + # message("pixman header: ${source}") + # endif() + + if("${source}" MATCHES ".*\\.\\./.*") + message("source raw: ${source}") + #file(TO_CMAKE_PATH ${source} sourceCMakePath) + get_filename_component(sourceCMakePath ${source} ABSOLUTE) + message("source CMake: ${sourceCMakePath}") + endif() + + # returns the containing directory with forward slashes + # get_filename_component(source_path "${source}" PATH) + + # converts / to \ + # string(REPLACE "/" "\\" source_path_msvc "${source_path}") + #source_group("Stone ${source_path_msvc}" FILES "${source}") + endforeach() + endif() + + source_group("Orthanc Framework\\Sources" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*cpp") + source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*hpp") + source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*h") + + source_group("Stone Library\\Sources" REGULAR_EXPRESSION ".*/orthanc-stone/.*cpp") + source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*hpp") + source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*h") + + source_group("Stone Samples\\Source" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.cpp") + source_group("Stone Samples\\Headers" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.h") + + source_group("ThirdParty\\cairo" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/cairo[^/]*/.*") + source_group("ThirdParty\\pixman" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/pixman[^/]*/.*") + source_group("ThirdParty\\base64" REGULAR_EXPRESSION ".*ThirdParty/base64.*") + source_group("ThirdParty\\SDL2" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/SDL2.*") + source_group("ThirdParty\\glew" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/glew.*") + source_group("AUTOGENERATED" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/AUTOGENERATED/.*") + source_group("ThirdParty\\minizip" REGULAR_EXPRESSION ".*ThirdParty/minizip/.*") + source_group("ThirdParty\\md5" REGULAR_EXPRESSION ".*ThirdParty/md5/.*") +endmacro() + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,133 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(OrthancStone) + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) + +include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_DCMTK OFF) # Not necessary +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_WASM ON) +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + + +# We embed a font to be used for on-screen overlays +# --------------------------------------------------------------- + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +EmbedResources( + COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +add_library(OrthancStone STATIC + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + ) + +################################################################################ + +# Define the WASM module +# --------------------------------------------------------------- + +project(RtViewerWasm) + +add_executable(RtViewerWasm + RtViewer/RtViewerWasm.cpp + ../Common/RtViewerApp.cpp + ../Common/RtViewerApp.h + ../Common/RtViewerView.cpp + ../Common/RtViewerView.h + ) + +target_link_libraries(RtViewerWasm OrthancStone) + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS RtViewerWasm + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/ + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above: it is considered to be the binary output of +# the linker. +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/RtViewer/RtViewerWasmApp.js + ${CMAKE_SOURCE_DIR}/RtViewer/index.html + ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm + DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/ + ) + +################################################################################ + +# Define the WASM module +# --------------------------------------------------------------- + +project(SingleFrameViewerWasm) + +add_executable(SingleFrameViewerWasm + SingleFrameViewer/SingleFrameViewer.cpp + ) + +target_link_libraries(SingleFrameViewerWasm OrthancStone) + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS SingleFrameViewerWasm + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/ + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above: it is considered to be the binary output of +# the linker. +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/SingleFrameViewer/SingleFrameViewerApp.js + ${CMAKE_SOURCE_DIR}/SingleFrameViewer/index.html + ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm + DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/ + ) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/NOTES.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,22 @@ + +Building WebAssembly samples using Docker +========================================= + +The script "./docker-build.sh" can be used to quickly build the +WebAssembly samples on any GNU/Linux distribution equipped with +Docker. This avoids newcomers to install Emscripten and learn the +CMake options. Just type: + +$ ./docker-build.sh Release + +After successful build, the binaries will be installed in the +following folder (i.e. in the folder "wasm-binaries" at the root of +the source distribution): + +$ ls -l ../../wasm-binaries + + +NB: The source code of the Docker build environment can be found at +the following location: +https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/RtViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/RtViewer/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(RtViewerWasm) + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) + +include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_DCMTK OFF) # Not necessary +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_WASM ON) +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + + +# We embed a font to be used for on-screen overlays +# --------------------------------------------------------------- + +DownloadPackage( + "a24b8136b8f3bb93f166baf97d9328de" + "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" + "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") + +EmbedResources( + COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut + UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf + ) + +# Define the WASM module +# --------------------------------------------------------------- +add_executable(RtViewerWasm + RtViewerWasm.cpp + ../../Common/RtViewerApp.cpp + ../../Common/RtViewerApp.h + ../../Common/RtViewerView.cpp + ../../Common/RtViewerView.h + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + ) + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS RtViewerWasm + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above: it is considered to be the binary output of +# the linker. +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/RtViewerWasmApp.js + ${CMAKE_SOURCE_DIR}/index.html + ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm + DESTINATION ${CMAKE_INSTALL_PREFIX} + ) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/RtViewer/OBSOLETE.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/RtViewer/OBSOLETE.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,559 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + +#include "../../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../../Framework/Oracle/SleepOracleCommand.h" +#include "../../../Framework/Oracle/WebAssemblyOracle.h" +#include "../../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../../Framework/Scene2D/PanSceneTracker.h" +#include "../../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../../Framework/Scene2DViewport/UndoStack.h" +#include "../../../Framework/Scene2DViewport/ViewportController.h" +#include "../../../Framework/StoneInitialization.h" +#include "../../../Framework/Viewport/WebAssemblyViewport.h" +#include "../../../Framework/Volumes/VolumeSceneLayerSource.h" + +#include + +#include +#include + +#include + + +class ViewportManager; + +static const unsigned int FONT_SIZE = 32; + +boost::shared_ptr ct_(new OrthancStone::DicomVolumeImage); +boost::shared_ptr loader_; +std::unique_ptr widget1_; +std::unique_ptr widget2_; +std::unique_ptr widget3_; +//OrthancStone::MessageBroker broker_; +//OrthancStone::WebAssemblyOracle oracle_(broker_); +std::unique_ptr tracker_; +static std::map arguments_; +static bool ctrlDown_ = false; + + +#if 0 + +// use the one from WebAssemblyViewport +static OrthancStone::PointerEvent* ConvertMouseEvent( + const EmscriptenMouseEvent& source, + OrthancStone::IViewport& viewport) +{ + + std::unique_ptr target( + new OrthancStone::PointerEvent); + + target->AddPosition(viewport.GetPixelCenterCoordinates( + source.targetX, source.targetY)); + target->SetAltModifier(source.altKey); + target->SetControlModifier(source.ctrlKey); + target->SetShiftModifier(source.shiftKey); + + return target.release(); +} +#endif + + +EM_BOOL OnMouseEvent(int eventType, + const EmscriptenMouseEvent *mouseEvent, + void *userData) +{ + if (mouseEvent != NULL && + userData != NULL) + { + boost::shared_ptr& viewport = + *reinterpret_cast*>(userData); + + std::unique_ptr lock = (*viewport)->Lock(); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + + switch (eventType) + { + case EMSCRIPTEN_EVENT_CLICK: + { + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "click %d", count++); + + std::unique_ptr layer(new OrthancStone::TextSceneLayer); + layer->SetText(buf); + scene.SetLayer(100, layer.release()); + lock->Invalidate(); + break; + } + + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + boost::shared_ptr t; + + { + std::unique_ptr event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + + switch (mouseEvent->button) + { + case 0: // Left button + emscripten_console_log("Creating RotateSceneTracker"); + t.reset(new OrthancStone::RotateSceneTracker( + viewport, *event)); + break; + + case 1: // Middle button + emscripten_console_log("Creating PanSceneTracker"); + LOG(INFO) << "Creating PanSceneTracker" ; + t.reset(new OrthancStone::PanSceneTracker( + viewport, *event)); + break; + + case 2: // Right button + emscripten_console_log("Creating ZoomSceneTracker"); + t.reset(new OrthancStone::ZoomSceneTracker( + viewport, *event, controller->GetViewport().GetCanvasWidth())); + break; + + default: + break; + } + } + + if (t.get() != NULL) + { + tracker_.reset( + new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier())); + controller->GetViewport().Refresh(); + } + + break; + } + + case EMSCRIPTEN_EVENT_MOUSEMOVE: + if (tracker_.get() != NULL) + { + std::unique_ptr event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + tracker_->PointerMove(*event); + controller->GetViewport().Refresh(); + } + break; + + case EMSCRIPTEN_EVENT_MOUSEUP: + if (tracker_.get() != NULL) + { + std::unique_ptr event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + tracker_->PointerUp(*event); + controller->GetViewport().Refresh(); + if (!tracker_->IsAlive()) + tracker_.reset(); + } + break; + + default: + break; + } + } + + return true; +} + + +void SetupEvents(const std::string& canvas, + boost::shared_ptr& viewport) +{ + emscripten_set_mousedown_callback(canvas.c_str(), &viewport, false, OnMouseEvent); + emscripten_set_mousemove_callback(canvas.c_str(), &viewport, false, OnMouseEvent); + emscripten_set_mouseup_callback(canvas.c_str(), &viewport, false, OnMouseEvent); +} + + class ViewportManager : public OrthanStone::ObserverBase + { + private: + OrthancStone::WebAssemblyViewport viewport_; + std::unique_ptr source_; + VolumeProjection projection_; + std::vector planes_; + size_t currentPlane_; + + void Handle(const DicomVolumeImage::GeometryReadyMessage& message) + { + LOG(INFO) << "Geometry is available"; + + const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry(); + + const unsigned int depth = geometry.GetProjectionDepth(projection_); + + // select an initial cutting plane halfway through the volume + currentPlane_ = depth / 2; + + planes_.resize(depth); + + for (unsigned int z = 0; z < depth; z++) + { + planes_[z] = geometry.GetProjectionSlice(projection_, z); + } + + Refresh(); + + viewport_.FitContent(); + } + + public: + ViewportManager(const std::string& canvas, + VolumeProjection projection) : + projection_(projection), + currentPlane_(0) + { + viewport_ = OrthancStone::WebGLViewport::Create(canvas); + } + + void UpdateSize() + { + viewport_.UpdateSize(); + } + + void SetSlicer(int layerDepth, + const boost::shared_ptr& slicer, + IObservable& loader, + ILayerStyleConfigurator* configurator) + { + if (source_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Only one slicer can be registered"); + } + + loader.RegisterObserverCallback( + new Callable + (*this, &ViewportManager::Handle)); + + source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer)); + + if (configurator != NULL) + { + source_->SetConfigurator(configurator); + } + } + + void Refresh() + { + if (source_.get() != NULL && + currentPlane_ < planes_.size()) + { + source_->Update(planes_[currentPlane_]); + viewport_.Refresh(); + } + } + + size_t GetSlicesCount() const + { + return planes_.size(); + } + + void Scroll(int delta) + { + if (!planes_.empty()) + { + int tmp = static_cast(currentPlane_) + delta; + unsigned int next; + + if (tmp < 0) + { + next = 0; + } + else if (tmp >= static_cast(planes_.size())) + { + next = planes_.size() - 1; + } + else + { + next = static_cast(tmp); + } + + if (next != currentPlane_) + { + currentPlane_ = next; + Refresh(); + } + } + } + }; +} + + +EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) +{ + try + { + if (widget1_.get() != NULL) + { + widget1_->UpdateSize(); + } + + if (widget2_.get() != NULL) + { + widget2_->UpdateSize(); + } + + if (widget3_.get() != NULL) + { + widget3_->UpdateSize(); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while updating canvas size: " << e.What(); + } + + return true; +} + +EM_BOOL OnAnimationFrame(double time, void *userData) +{ + try + { + if (widget1_.get() != NULL) + { + widget1_->Refresh(); + } + + if (widget2_.get() != NULL) + { + widget2_->Refresh(); + } + + if (widget3_.get() != NULL) + { + widget3_->Refresh(); + } + + return true; + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What(); + return false; + } +} + +EM_BOOL OnMouseWheel(int eventType, + const EmscriptenWheelEvent *wheelEvent, + void *userData) +{ + try + { + if (userData != NULL) + { + int delta = 0; + + if (wheelEvent->deltaY < 0) + { + delta = -1; + } + + if (wheelEvent->deltaY > 0) + { + delta = 1; + } + + OrthancStone::ViewportManager& widget = + *reinterpret_cast(userData); + + if (ctrlDown_) + { + delta *= static_cast(widget.GetSlicesCount() / 10); + } + + widget.Scroll(delta); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in the wheel event: " << e.What(); + } + + return true; +} + + +EM_BOOL OnKeyDown(int eventType, + const EmscriptenKeyboardEvent *keyEvent, + void *userData) +{ + ctrlDown_ = keyEvent->ctrlKey; + return false; +} + + +EM_BOOL OnKeyUp(int eventType, + const EmscriptenKeyboardEvent *keyEvent, + void *userData) +{ + ctrlDown_ = false; + return false; +} + + + +#if 0 +namespace OrthancStone +{ + class TestSleep : public IObserver + { + private: + WebAssemblyOracle& oracle_; + + void Schedule() + { + oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000)); + } + + void Handle(const SleepOracleCommand::TimeoutMessage& message) + { + LOG(INFO) << "TIMEOUT"; + Schedule(); + } + + public: + TestSleep(MessageBroker& broker, + WebAssemblyOracle& oracle) : + IObserver(broker), + oracle_(oracle) + { + oracle.RegisterObserverCallback( + new Callable + (*this, &TestSleep::Handle)); + + LOG(INFO) << "STARTING"; + Schedule(); + } + }; + + //static TestSleep testSleep(broker_, oracle_); +} +#endif + +static bool GetArgument(std::string& value, + const std::string& key) +{ + std::map::const_iterator found = arguments_.find(key); + + if (found == arguments_.end()) + { + return false; + } + else + { + value = found->second; + return true; + } +} + + +extern "C" +{ + int main(int argc, char const *argv[]) + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + // Orthanc::Logging::EnableTraceLevel(true); + EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); + } + + EMSCRIPTEN_KEEPALIVE + void SetArgument(const char* key, const char* value) + { + // This is called for each GET argument (cf. "app.js") + LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]"; + arguments_[key] = value; + } + + EMSCRIPTEN_KEEPALIVE + void Initialize() + { + try + { + oracle_.SetOrthancRoot(".."); + + loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_)); + + widget1_.reset(new OrthancStone::ViewportManager("mycanvas1", OrthancStone::VolumeProjection_Axial)); + { + std::unique_ptr style(new OrthancStone::GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + style->SetWindowing(OrthancStone::ImageWindowing_Bone); + widget1_->SetSlicer(0, loader_, *loader_, style.release()); + } + widget1_->UpdateSize(); + + widget2_.reset(new OrthancStone::ViewportManager("mycanvas2", OrthancStone::VolumeProjection_Coronal)); + { + std::unique_ptr style(new OrthancStone::GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + style->SetWindowing(OrthancStone::ImageWindowing_Bone); + widget2_->SetSlicer(0, loader_, *loader_, style.release()); + } + widget2_->UpdateSize(); + + widget3_.reset(new OrthancStone::ViewportManager("mycanvas3", OrthancStone::VolumeProjection_Sagittal)); + { + std::unique_ptr style(new OrthancStone::GrayscaleStyleConfigurator); + style->SetLinearInterpolation(true); + style->SetWindowing(OrthancStone::ImageWindowing_Bone); + widget3_->SetSlicer(0, loader_, *loader_, style.release()); + } + widget3_->UpdateSize(); + + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnWindowResize); // DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 !! + + emscripten_set_wheel_callback("#mycanvas1", widget1_.get(), false, OnMouseWheel); + emscripten_set_wheel_callback("#mycanvas2", widget2_.get(), false, OnMouseWheel); + emscripten_set_wheel_callback("#mycanvas3", widget3_.get(), false, OnMouseWheel); + + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyDown); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyUp); + + //emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); + + std::string ct; + if (GetArgument(ct, "ct")) + { + //loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); + loader_->LoadSeries(ct); + } + else + { + LOG(ERROR) << "No Orthanc identifier for the CT series was provided"; + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception during Initialize(): " << e.What(); + } + } +} + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,196 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + +#include "../../Common/RtViewerApp.h" +#include "../../Common/RtViewerView.h" +#include "../../Common/SampleHelpers.h" + +// Stone of Orthanc includes +#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h" +#include "../../../Sources/StoneException.h" +#include "../../../Sources/StoneInitialization.h" +#include "../../../Sources/Viewport/WebGLViewport.h" +//#include "../../../Sources/OpenGL/WebAssemblyOpenGLContext.h" + +#include + +#include +#include +// #include this include might be necessary in more recent boost versions + +#include +#include + + +#define DISPATCH_JAVASCRIPT_EVENT(name) \ + EM_ASM( \ + const customEvent = document.createEvent("CustomEvent"); \ + customEvent.initCustomEvent(name, false, false, undefined); \ + window.dispatchEvent(customEvent); \ + ); + +#define EXTERN_CATCH_EXCEPTIONS \ + catch (Orthanc::OrthancException& e) \ + { \ + LOG(ERROR) << "OrthancException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (OrthancStone::StoneException& e) \ + { \ + LOG(ERROR) << "StoneException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (std::exception& e) \ + { \ + LOG(ERROR) << "Runtime error: " << e.what(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (...) \ + { \ + LOG(ERROR) << "Native exception"; \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } + +namespace OrthancStone +{ + // typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData); + + EM_BOOL RtViewerView_Scroll(int eventType, + const EmscriptenWheelEvent* wheelEvent, + void* userData) + { + RtViewerView* that = reinterpret_cast(userData); + + int delta = 0; + if (wheelEvent->deltaY < 0) + delta = -1; + if (wheelEvent->deltaY > 0) + delta = 1; + + that->Scroll(delta); + + return 1; + } + + boost::shared_ptr RtViewerView::CreateViewport( + const std::string& canvasId) + { + boost::shared_ptr viewport = WebGLViewport::Create(canvasId); + + void* userData = reinterpret_cast(this); + + // manually add the mouse wheel handler + + std::string selector = "#" + canvasId; + + emscripten_set_wheel_callback_on_thread( + selector.c_str(), + userData, + false, + &RtViewerView_Scroll, + EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD); + + return viewport; + } + + void RtViewerView::TakeScreenshot(const std::string& target, + unsigned int canvasWidth, + unsigned int canvasHeight) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + + void RtViewerApp::RunWasm() + { + loadersContext_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); + + // we are in WASM --> downcast to concrete type + boost::shared_ptr loadersContext = + boost::dynamic_pointer_cast(loadersContext_); + + if (HasArgument("orthanc")) + loadersContext->SetLocalOrthanc(GetArgument("orthanc")); + else + loadersContext->SetLocalOrthanc(".."); + + loadersContext->SetDicomCacheSize(128 * 1024 * 1024); // 128MB + + CreateLoaders(); + + CreateView("RtViewer_Axial", VolumeProjection_Axial); + CreateView("RtViewer_Coronal", VolumeProjection_Coronal); + CreateView("RtViewer_Sagittal", VolumeProjection_Sagittal); + + for (size_t i = 0; i < views_.size(); ++i) + { + views_[i]->PrepareViewport(); + } + + StartLoaders(); + } +} + +extern "C" +{ + boost::shared_ptr g_app; + + int main(int argc, char const *argv[]) + { + try + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::Initialize(); + Orthanc::Logging::EnableTraceLevel(true); + + LOG(WARNING) << "Initializing native Stone"; + + LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__ + << "." << __EMSCRIPTEN_minor__ + << "." << __EMSCRIPTEN_tiny__; + + LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness()); + + g_app = OrthancStone::RtViewerApp::Create(); + + DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized"); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + void Initialize(const char* canvasId) + { + try + { + g_app->RunWasm(); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + void SetArgument(const char* key, const char* value) + { + // This is called for each GET argument (cf. "app.js") + LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]"; + g_app->SetArgument(key, value); + } + +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,85 @@ + +// This object wraps the functions exposed by the wasm module + +const WasmModuleWrapper = function() { + this._InitializeViewport = undefined; +}; + +WasmModuleWrapper.prototype.Setup = function(Module) { + this._SetArgument = Module.cwrap('SetArgument', null, [ 'string', 'string' ]); + this._Initialize = Module.cwrap('Initialize', null, [ 'string' ]); +}; + +WasmModuleWrapper.prototype.SetArgument = function(key, value) { + this._SetArgument(key, value); +}; + +WasmModuleWrapper.prototype.Initialize = function(canvasId) { + this._Initialize(canvasId); +}; + +var wasmModuleWrapper = new WasmModuleWrapper(); + +$(document).ready(function() { + + window.addEventListener('WasmModuleInitialized', function() { + + // bind the C++ global functions + wasmModuleWrapper.Setup(Module); + + console.warn('Native C++ module initialized'); + + // Loop over the GET arguments + var parameters = window.location.search.substr(1); + if (parameters != null && parameters != '') { + var tokens = parameters.split('&'); + for (var i = 0; i < tokens.length; i++) { + var arg = tokens[i].split('='); + if (arg.length == 2) { + // Send each GET argument to WebAssembly + wasmModuleWrapper.SetArgument(arg[0], decodeURIComponent(arg[1])); + } + } + } + wasmModuleWrapper.Initialize(); + }); + + window.addEventListener('StoneException', function() { + alert('Exception caught in C++ code'); + }); + + var scriptSource; + + if ('WebAssembly' in window) { + console.warn('Loading WebAssembly'); + scriptSource = 'RtViewerWasm.js'; + } else { + console.error('Your browser does not support WebAssembly!'); + } + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = scriptSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(scriptSource) + .then(function (response) { + var script = document.createElement('script'); + script.innerHTML = response.data; + script.type = 'text/javascript'; + document.body.appendChild(script); + }) + .catch(function (error) { + alert('Cannot load the WebAssembly framework'); + }); +}); + +// http://localhost:9979/stone-rtviewer/index.html?loglevel=trace&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,27 @@ + +const Stone = function() { + this._InitializeViewport = undefined; + this._LoadOrthanc = undefined; + this._LoadDicomWeb = undefined; +}; + +Stone.prototype.Setup = function(Module) { + this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); + this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]); + this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); +}; + +Stone.prototype.InitializeViewport = function(canvasId) { + this._InitializeViewport(canvasId); +}; + +Stone.prototype.LoadOrthanc = function(instance, frame) { + this._LoadOrthanc(instance, frame); +}; + +Stone.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { + this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); +}; + +var stone = new Stone(); + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/RtViewer/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/RtViewer/index.html Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,64 @@ + + + + Stone of Orthanc Single Frame Viewer + + + + + + + + + + + + + + + + + + + + + + + + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(SingleFrameViewer) + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) + +include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_DCMTK OFF) # Not necessary +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_WASM ON) +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +# Define the WASM module +# --------------------------------------------------------------- +add_executable(SingleFrameViewerWasm + SingleFrameViewer.cpp + ${ORTHANC_STONE_SOURCES} + ) + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS SingleFrameViewerWasm + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above: it is considered to be the binary output of +# the linker. +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/SingleFrameViewerApp.js + ${CMAKE_SOURCE_DIR}/index.html + ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm + DESTINATION ${CMAKE_INSTALL_PREFIX} + ) diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,39 @@ +{ + "configurations": [ + { + "name": "wasm32-RelWithDebInfo", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + //"inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "cmakeToolchain": "C:/osi/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "intelliSenseMode": "windows-clang-x64", + "variables": [ + { + "name": "CMAKE_BUILD_TYPE", + "value": "RelWithDebInfo", + "type": "STRING" + }, + { + "name": "ALLOW_DOWNLOADS", + "value": "True", + "type": "BOOL" + }, + { + "name": "STATIC_BUILD", + "value": "True", + "type": "BOOL" + }, + { + "name": "OPENSSL_NO_CAPIENG", + "value": "True", + "type": "BOOL" + } + ] + } + ] +} \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,169 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#include "SingleFrameViewerApplication.h" + +#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h" +#include "../../../Sources/StoneException.h" +#include "../../../Sources/StoneInitialization.h" + +#include // For std::unique_ptr<> +#include + +#include +#include + + +#define DISPATCH_JAVASCRIPT_EVENT(name) \ + EM_ASM( \ + const customEvent = document.createEvent("CustomEvent"); \ + customEvent.initCustomEvent(name, false, false, undefined); \ + window.dispatchEvent(customEvent); \ + ); + +#define EXTERN_CATCH_EXCEPTIONS \ + catch (Orthanc::OrthancException& e) \ + { \ + LOG(ERROR) << "OrthancException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (OrthancStone::StoneException& e) \ + { \ + LOG(ERROR) << "StoneException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (std::exception& e) \ + { \ + LOG(ERROR) << "Runtime error: " << e.what(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (...) \ + { \ + LOG(ERROR) << "Native exception"; \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } + + + +namespace OrthancStone +{ +} + +static std::unique_ptr context_; +static boost::shared_ptr application_; + +extern "C" +{ + int main(int argc, char const *argv[]) + { + try + { + Orthanc::Logging::Initialize(); + Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + LOG(WARNING) << "Initializing native Stone"; + + LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__ + << "." << __EMSCRIPTEN_minor__ + << "." << __EMSCRIPTEN_tiny__; + + LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness()); + context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); + context_->SetLocalOrthanc(".."); + context_->SetDicomCacheSize(128 * 1024 * 1024); // 128MB + + DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized"); + } + EXTERN_CATCH_EXCEPTIONS; + + return 0; + } + + EMSCRIPTEN_KEEPALIVE + void InitializeViewport(const char* canvasId) + { + try + { + if (context_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "The loaders context is not available yet"); + } + + if (application_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Only one single viewport is available for this application"); + } + + boost::shared_ptr viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); + application_ = OrthancStone::Application::Create(*context_, viewport); + + { + OrthancStone::WebGLViewportsRegistry::Accessor accessor( + OrthancStone::GetWebGLViewportsRegistry(), canvasId); + + if (accessor.IsValid()) + { + accessor.GetViewport().Invalidate(); + } + } + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void LoadFromOrthanc(const char* instance, + int frame) + { + try + { + if (application_.get() != NULL) + { + OrthancStone::DicomSource source; + application_->LoadOrthancFrame(source, instance, frame); + } + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void LoadFromDicomWeb(const char* server, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + int frame) + { + try + { + if (application_.get() != NULL) + { + OrthancStone::DicomSource source; + source.SetDicomWebThroughOrthancSource(server); + application_->LoadDicomWebFrame(source, studyInstanceUid, seriesInstanceUid, + sopInstanceUid, frame); + } + } + EXTERN_CATCH_EXCEPTIONS; + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,74 @@ + +// This object wraps the functions exposed by the wasm module + +const WasmModuleWrapper = function() { + this._InitializeViewport = undefined; + this._LoadFromOrthanc = undefined; +}; + +WasmModuleWrapper.prototype.Setup = function(Module) { + this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); + this._LoadFromOrthanc = Module.cwrap('LoadFromOrthanc', null, [ 'string', 'int' ]); +}; + +WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) { + this._InitializeViewport(canvasId); +}; + +WasmModuleWrapper.prototype.LoadFromOrthanc = function(instance, frame) { + this._LoadFromOrthanc(instance, frame); +}; + +var wasmModuleWrapper = new WasmModuleWrapper(); + +$(document).ready(function() { + + window.addEventListener('WasmModuleInitialized', function() { + wasmModuleWrapper.Setup(Module); + console.warn('Native C++ module initialized'); + + wasmModuleWrapper.InitializeViewport('viewport'); + }); + + window.addEventListener('StoneException', function() { + alert('Exception caught in C++ code'); + }); + + var scriptSource; + + if ('WebAssembly' in window) { + console.warn('Loading WebAssembly'); + scriptSource = 'SingleFrameViewerWasm.js'; + } else { + console.error('Your browser does not support WebAssembly!'); + } + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = scriptSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(scriptSource) + .then(function (response) { + var script = document.createElement('script'); + script.innerHTML = response.data; + script.type = 'text/javascript'; + document.body.appendChild(script); + }) + .catch(function (error) { + alert('Cannot load the WebAssembly framework'); + }); +}); + + +$('#orthancLoad').click(function() { + wasmModuleWrapper.LoadFromOrthanc($('#orthancInstance').val(), + $('#orthancFrame').val()); +}); diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,502 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#pragma once + +#include "../../../Sources/Loaders/DicomResourcesLoader.h" +#include "../../../Sources/Loaders/ILoadersContext.h" +#include "../../../Sources/Loaders/SeriesFramesLoader.h" +#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h" +#include "../../../Sources/Viewport/IViewport.h" + +#include // For std::unique_ptr<> + +#include + + +namespace OrthancStone +{ + class Application : public ObserverBase + { + private: + ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr dicomLoader_; + boost::shared_ptr framesLoader_; + + Application(ILoadersContext& context, + boost::shared_ptr viewport) : + context_(context), + viewport_(viewport) + { + } + + void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) + { + LOG(INFO) << "Frame decoded! " + << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() + << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); + + std::unique_ptr layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().SetLayer(0, layer.release()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + void Handle(const DicomResourcesLoader::SuccessMessage& message) + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + //message.GetResources()->GetResource(0).Print(stdout); + + { + std::unique_ptr lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + Register(*framesLoader_, &Application::Handle); + + assert(message.HasUserPayload()); + const Orthanc::SingleValueObject& payload = + dynamic_cast&>(message.GetUserPayload()); + + LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); + framesLoader_->ScheduleLoadFrame( + 0, message.GetDicomSource(), payload.GetValue(), + message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, + NULL); + } + } + + public: + static boost::shared_ptr Create(ILoadersContext& context, + boost::shared_ptr viewport) + { + boost::shared_ptr application(new Application(context, viewport)); + + { + std::unique_ptr lock(context.Lock()); + application->dicomLoader_ = DicomResourcesLoader::Create(*lock); + } + + application->Register(*application->dicomLoader_, &Application::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, + const std::string& instanceId, + unsigned int frame) + { + std::unique_ptr lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject(frame)); + } + + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::unique_ptr lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject(frame)); + } + + void FitContent() + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + }; + + + + class IWebViewerLoadersObserver : public boost::noncopyable + { + public: + virtual ~IWebViewerLoadersObserver() + { + } + + virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0; + + virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + SeriesThumbnailType type) = 0; + }; + + + class WebViewerLoaders : public ObserverBase + { + private: + static const int PRIORITY_ADD_RESOURCES = 0; + static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100; + + enum Type + { + Type_Orthanc = 1, + Type_DicomWeb = 2 + }; + + ILoadersContext& context_; + std::unique_ptr observer_; + bool loadThumbnails_; + DicomSource source_; + std::set scheduledSeries_; + std::set scheduledThumbnails_; + std::set scheduledStudies_; + boost::shared_ptr loadedSeries_; + boost::shared_ptr loadedStudies_; + boost::shared_ptr resourcesLoader_; + boost::shared_ptr thumbnailsLoader_; + + WebViewerLoaders(ILoadersContext& context, + IWebViewerLoadersObserver* observer) : + context_(context), + observer_(observer), + loadThumbnails_(false) + { + loadedSeries_ = boost::make_shared(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + loadedStudies_ = boost::make_shared(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); + } + + static Orthanc::IDynamicObject* CreatePayload(Type type) + { + return new Orthanc::SingleValueObject(type); + } + + void HandleThumbnail(const SeriesThumbnailsLoader::SuccessMessage& message) + { + if (observer_.get() != NULL) + { + observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(), + message.GetSeriesInstanceUid(), + message.GetType()); + } + } + + void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message) + { + LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + + switch (dynamic_cast&>(message.GetUserPayload()).GetValue()) + { + case Type_DicomWeb: + { + for (size_t i = 0; i < loadedSeries_->GetSize(); i++) + { + std::string study; + if (loadedSeries_->GetResource(i).LookupStringValue( + study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + loadedStudies_->HasResource(study)) + { + Orthanc::DicomMap m; + m.Assign(loadedSeries_->GetResource(i)); + loadedStudies_->MergeResource(m, study); + series.AddResource(m); + } + } + + break; + } + + case Type_Orthanc: + { + for (size_t i = 0; i < message.GetResources()->GetSize(); i++) + { + series.AddResource(message.GetResources()->GetResource(i)); + } + + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (loadThumbnails_ && + (!source_.IsDicomWeb() || + source_.HasDicomWebRendered())) + { + for (size_t i = 0; i < series.GetSize(); i++) + { + std::string patientId, studyInstanceUid, seriesInstanceUid; + if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) && + series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) && + scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end()) + { + scheduledThumbnails_.insert(seriesInstanceUid); + thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid); + } + } + } + + if (observer_.get() != NULL && + series.GetSize() > 0) + { + observer_->SignalSeriesUpdated(series); + } + } + + void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message) + { + Json::Value body; + message.ParseJsonBody(body); + + if (body.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < body.size(); i++) + { + if (body[i].type() == Json::stringValue) + { + AddOrthancSeries(body[i].asString()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + } + } + + public: + static boost::shared_ptr Create(ILoadersContext& context, + const DicomSource& source, + bool loadThumbnails, + IWebViewerLoadersObserver* observer) + { + boost::shared_ptr application(new WebViewerLoaders(context, observer)); + application->source_ = source; + application->loadThumbnails_ = loadThumbnails; + + { + std::unique_ptr lock(context.Lock()); + + application->resourcesLoader_ = DicomResourcesLoader::Create(*lock); + + { + SeriesThumbnailsLoader::Factory f; + f.SetPriority(PRIORITY_THUMBNAILS); + application->thumbnailsLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); + } + + application->Register( + lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); + + application->Register( + *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); + + application->Register( + *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); + + lock->AddLoader(application); + } + + return application; + } + + void AddDicomAllSeries() + { + std::unique_ptr lock(context_.Lock()); + + if (source_.IsDicomWeb()) + { + resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + "/series", CreatePayload(Type_DicomWeb)); + resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + "/studies", CreatePayload(Type_DicomWeb)); + } + else if (source_.IsOrthanc()) + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Get); + command->SetUri("/series"); + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + void AddDicomStudy(const std::string& studyInstanceUid) + { + // Avoid adding twice the same study + if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) + { + scheduledStudies_.insert(studyInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + std::set tags; + + { + std::unique_ptr lock(context_.Lock()); + + resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); + + resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); + } + } + else if (source_.IsOrthanc()) + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + + Json::Value body; + body["Level"] = "Series"; + body["Query"] = Json::objectValue; + body["Query"]["StudyInstanceUID"] = studyInstanceUid; + command->SetBody(body); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + void AddDicomSeries(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + std::set tags; + + std::unique_ptr lock(context_.Lock()); + + if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) + { + scheduledStudies_.insert(studyInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); + } + } + + if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end()) + { + scheduledSeries_.insert(seriesInstanceUid); + + if (source_.IsDicomWeb()) + { + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); + + resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); + } + else if (source_.IsOrthanc()) + { + std::unique_ptr command(new OrthancRestApiCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + + Json::Value body; + body["Level"] = "Series"; + body["Query"] = Json::objectValue; + body["Query"]["StudyInstanceUID"] = studyInstanceUid; + body["Query"]["SeriesInstanceUID"] = seriesInstanceUid; + command->SetBody(body); + + lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + + void AddOrthancStudy(const std::string& orthancId) + { + if (source_.IsOrthanc()) + { + std::unique_ptr lock(context_.Lock()); + resourcesLoader_->ScheduleLoadOrthancResources( + loadedSeries_, PRIORITY_ADD_RESOURCES, source_, + Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series, + CreatePayload(Type_Orthanc)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Only applicable to Orthanc DICOM sources"); + } + } + + void AddOrthancSeries(const std::string& orthancId) + { + if (source_.IsOrthanc()) + { + std::unique_ptr lock(context_.Lock()); + resourcesLoader_->ScheduleLoadOrthancResource( + loadedSeries_, PRIORITY_ADD_RESOURCES, + source_, Orthanc::ResourceType_Series, orthancId, + CreatePayload(Type_Orthanc)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, + "Only applicable to Orthanc DICOM sources"); + } + } + }; +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/SingleFrameViewer/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/SingleFrameViewer/index.html Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,43 @@ + + + + Stone of Orthanc Single Frame Viewer + + + + + + + + + + +

Viewport

+ + + + +

Load from Orthanc

+

+ Orthanc instance: +

+

+ Frame number: +

+

+ +

+ + + + + + + diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/docker-build.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/docker-build.sh Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,27 @@ +#!/bin/bash + +set -ex + +IMAGE=jodogne/wasm-builder:1.39.17-upstream + +if [ "$1" != "Debug" -a "$1" != "Release" ]; then + echo "Please provide build type: Debug or Release" + exit -1 +fi + +if [ -t 1 ]; then + # TTY is available => use interactive mode + DOCKER_FLAGS='-i' +fi + +ROOT_DIR=`dirname $(readlink -f $0)`/../../.. + +mkdir -p ${ROOT_DIR}/wasm-binaries + +docker run -t ${DOCKER_FLAGS} --rm \ + --user $(id -u):$(id -g) \ + -v ${ROOT_DIR}:/source:ro \ + -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \ + bash /source/OrthancStone/Samples/WebAssembly/docker-internal.sh $1 + +ls -lR ${ROOT_DIR}/wasm-binaries/ diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/WebAssembly/docker-internal.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/WebAssembly/docker-internal.sh Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,30 @@ +#!/bin/bash +set -ex + +source /opt/emsdk/emsdk_env.sh + +# Use a folder that is writeable by non-root users for the Emscripten cache +export EM_CACHE=/tmp/emscripten-cache + +# Get the Orthanc framework +cd /tmp/ +hg clone https://hg.orthanc-server.com/orthanc/ + +# Make a copy of the read-only folder containing the source code into +# a writeable folder, because of "DownloadPackage.cmake" that writes +# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt" +cd /source +hg clone /source /tmp/source-writeable + +mkdir /tmp/build +cd /tmp/build + +cmake /tmp/source-writeable/OrthancStone/Samples/WebAssembly \ + -DCMAKE_BUILD_TYPE=$1 \ + -DCMAKE_INSTALL_PREFIX=/target \ + -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ + -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \ + -DSTATIC_BUILD=ON \ + -G Ninja + +ninja -j2 install diff -r de8cf5859e84 -r d1806b4e4839 Applications/Samples/build-wasm-samples.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/Samples/build-wasm-samples.sh Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,48 @@ +#!/bin/bash +# +# usage: +# to build the samples in RelWithDebInfo: +# ./build-wasm-samples.sh +# +# to build the samples in Release: +# ./build-wasm-samples.sh Release + +set -e + +if [ ! -d "WebAssembly" ]; then + echo "This script must be run from the Samples folder one level below orthanc-stone" + exit 1 +fi + + +currentDir=$(pwd) +samplesRootDir=$(pwd) +devrootDir=$(pwd)/../../ + +buildType=${1:-RelWithDebInfo} +buildFolderName="$devrootDir/out/build-stone-wasm-samples-$buildType" +installFolderName="$devrootDir/out/install-stone-wasm-samples-$buildType" + +mkdir -p $buildFolderName +# change current folder to the build folder +pushd $buildFolderName + +# configure the environment to use Emscripten +source ~/apps/emsdk/emsdk_env.sh + +emcmake cmake -G "Ninja" \ + -DCMAKE_BUILD_TYPE=$buildType \ + -DCMAKE_INSTALL_PREFIX=$installFolderName \ + -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \ + $samplesRootDir/WebAssembly + +# perform build + installation +ninja +ninja install + +# restore the original working folder +popd + +echo "If all went well, the output files can be found in $installFolderName:" + +ls $installFolderName \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/COPYING Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Plugin/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Plugin/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(StoneWebViewerPlugin) + +set(ORTHANC_PLUGIN_VERSION "mainline") + +if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +else() + set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2") + set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") +endif() + + + +set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../wasm-binaries/StoneWebViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder") + +# Parameters of the build +set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") +set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")") +set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework") +set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") +set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") + + +# Advanced parameters to fine-tune linking against system libraries +set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") +set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked") +mark_as_advanced(ORTHANC_FRAMEWORK_STATIC) + + +# Download and setup the Orthanc framework +include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake) + +include_directories(${ORTHANC_FRAMEWORK_ROOT}) + +if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") + link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES}) + +else() + include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake) + set(ENABLE_MODULE_IMAGES OFF) + set(ENABLE_MODULE_JOBS OFF) + set(ENABLE_MODULE_DICOM OFF) + include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake) +endif() + +include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginsExports.cmake) + + +if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) + include_directories(${CMAKE_SOURCE_DIR}/../Resources/OrthancSdk-1.0.0) +else () + CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) + if (NOT HAVE_ORTHANC_H) + message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") + endif() +endif() + + +add_definitions( + -DHAS_ORTHANC_EXCEPTION=1 + -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}" + -DPLUGIN_NAME="stone-webviewer" + ) + + +EmbedResources( + # Folders + IMAGES ${STONE_BINARIES}/img/ + WEB_APPLICATION ${CMAKE_SOURCE_DIR}/../WebApplication + + # Individual files + ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/OrthancExplorer.js + STONE_WEB_VIEWER_JS ${STONE_BINARIES}/StoneWebViewer.js + STONE_WEB_VIEWER_WASM ${STONE_BINARIES}/StoneWebViewer.wasm + STONE_WRAPPER ${STONE_BINARIES}/stone.js + ) + +add_library(StoneWebViewer SHARED + Plugin.cpp + ${AUTOGENERATED_SOURCES} + ${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp + ${ORTHANC_CORE_SOURCES} + ) + +set_target_properties(StoneWebViewer PROPERTIES + VERSION ${ORTHANC_PLUGIN_VERSION} + SOVERSION ${ORTHANC_PLUGIN_VERSION}) + +install( + TARGETS StoneWebViewer + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Plugin/OrthancExplorer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Plugin/OrthancExplorer.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,57 @@ +$('#study').live('pagebeforecreate', function() { + var b = $('
') + .attr('data-role', 'button') + .attr('href', '#') + .attr('data-icon', 'search') + .attr('data-theme', 'e') + .text('Stone Web Viewer'); + + b.insertBefore($('#study-delete').parent().parent()); + b.click(function() { + if ($.mobile.pageData) { + $.ajax({ + url: '../studies/' + $.mobile.pageData.uuid, + dataType: 'json', + cache: false, + success: function(study) { + var studyInstanceUid = study.MainDicomTags.StudyInstanceUID; + window.open('../stone-webviewer/index.html?study=' + studyInstanceUid); + } + }); + } + }); +}); + + +$('#series').live('pagebeforecreate', function() { + var b = $('') + .attr('data-role', 'button') + .attr('href', '#') + .attr('data-icon', 'search') + .attr('data-theme', 'e') + .text('Stone Web Viewer'); + + b.insertBefore($('#series-delete').parent().parent()); + b.click(function() { + if ($.mobile.pageData) { + $.ajax({ + url: '../series/' + $.mobile.pageData.uuid, + dataType: 'json', + cache: false, + success: function(series) { + $.ajax({ + url: '../studies/' + series.ParentStudy, + dataType: 'json', + cache: false, + success: function(study) { + var studyInstanceUid = study.MainDicomTags.StudyInstanceUID; + var seriesInstanceUid = series.MainDicomTags.SeriesInstanceUID; + window.open('../stone-webviewer/index.html?study=' + studyInstanceUid + + '&series=' + seriesInstanceUid); + } + }); + } + }); + } + }); +}); diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Plugin/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Plugin/Plugin.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,241 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#include + +#include +#include + +OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) +{ + try + { + if (changeType == OrthancPluginChangeType_OrthancStarted) + { + Json::Value info; + if (!OrthancPlugins::RestApiGet(info, "/plugins/dicom-web", false)) + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_InternalError, + "The Stone Web viewer requires the DICOMweb plugin to be installed"); + } + + if (info.type() != Json::objectValue || + !info.isMember("ID") || + !info.isMember("Version") || + info["ID"].type() != Json::stringValue || + info["Version"].type() != Json::stringValue || + info["ID"].asString() != "dicom-web") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "The DICOMweb plugin is not properly installed"); + } + + std::string version = info["Version"].asString(); + if (version != "mainline") + { + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, version, '.'); + if (tokens.size() != 2) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Bad version of the DICOMweb plugin: " + version); + } + + int major, minor; + + try + { + major = boost::lexical_cast(tokens[0]); + minor = boost::lexical_cast(tokens[1]); + } + catch (boost::bad_lexical_cast&) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Bad version of the DICOMweb plugin: " + version); + } + + if (major <= 0 || + (major == 1 && minor <= 1)) + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_InternalError, + "The Stone Web viewer requires DICOMweb plugin with version >= 1.2, found: " + version); + } + + if (major <= 0 || + (major == 1 && minor == 2)) + { + /** + * DICOMweb 1.3 is better than 1.2 for 2 reasons: (1) + * MONOCHROME1 images are not properly rendered in DICOMweb + * 1.2, and (2) DICOMweb 1.2 cannot transcode images (this + * causes issues on JPEG2k images). + **/ + LOG(WARNING) << "The Stone Web viewer has some incompatibilities " + << "with DICOMweb plugin 1.2, consider upgrading the DICOMweb plugin"; + } + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception: " << e.What(); + return static_cast(e.GetErrorCode()); + } + + return OrthancPluginErrorCode_Success; +} + + +template +void ServeEmbeddedFolder(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + } + else + { + std::string path = "/" + std::string(request->groups[0]); + const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path)); + + std::string s; + Orthanc::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str()); + + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); + } +} + + +template +void ServeEmbeddedFile(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(context, output, "GET"); + } + else + { + const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url)); + + std::string s; + Orthanc::EmbeddedResources::GetFileResource(s, file); + + const char* resource = s.size() ? s.c_str() : NULL; + OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); + } +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + OrthancPlugins::SetGlobalContext(context); + +#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) + Orthanc::Logging::InitializePluginContext(context); +#else + Orthanc::Logging::Initialize(context); +#endif + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(context) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + context->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(context, info); + return -1; + } + + try + { + std::string explorer; + Orthanc::EmbeddedResources::GetFileResource( + explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); + OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-webviewer/StoneWebViewer.wasm", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-webviewer/StoneWebViewer.js", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-webviewer/stone.js", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-webviewer/img/(.*)", true); + + OrthancPlugins::RegisterRestCallback + > + ("/stone-webviewer/(.*)", true); + + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + } + catch (...) + { + OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin"); + return -1; + } + + return 0; + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return PLUGIN_NAME; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return PLUGIN_VERSION; + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/GenerateImages.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/GenerateImages.py Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., 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 . + + +import os +from PIL import Image + +SOURCE = os.path.dirname(os.path.abspath(__file__)) +TARGET = os.path.join(SOURCE, '..', 'WebApplication', 'img') + +try: + os.makedirs(TARGET) +except: # Directory already exists + pass + +color = (217, 217, 217, 255) +border = 3 +width = 32 +height = 32 + + + +image = Image.new('RGBA', (width, height)) + +for x in range(0, width): + for y in range(0, height): + image.putpixel((x, y), color) + +image.save(os.path.join(TARGET, 'grid1x1.png'), 'PNG') + + + +image = Image.new('RGBA', (width, height)) + +for x in range(0, width / 2 - border): + for y in range(0, height / 2 - border): + image.putpixel((x, y), color) + for y in range(height / 2 + border, height): + image.putpixel((x, y), color) + +for x in range(width / 2 + border, width): + for y in range(0, height / 2 - border): + image.putpixel((x, y), color) + for y in range(height / 2 + border, height): + image.putpixel((x, y), color) + +image.save(os.path.join(TARGET, 'grid2x2.png'), 'PNG') + + + +image = Image.new('RGBA', (width, height)) + +for y in range(0, height): + for x in range(0, width / 2 - border): + image.putpixel((x, y), color) + for x in range(width / 2 + border, width): + image.putpixel((x, y), color) + +image.save(os.path.join(TARGET, 'grid2x1.png'), 'PNG') + + + +image = Image.new('RGBA', (width, height)) + +for x in range(0, width): + for y in range(0, height / 2 - border): + image.putpixel((x, y), color) + for y in range(height / 2 + border, height): + image.putpixel((x, y), color) + +image.save(os.path.join(TARGET, 'grid1x2.png'), 'PNG') diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/NOTES.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,15 @@ + +Origin of SCSS +============== + +The "Styles" folder is a copy of: +https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/styles/ + + + +Generation of CSS from the SCSS +=============================== + +$ npm install node-sass +$ ./node_modules/node-sass/bin/node-sass ./Styles/styles.scss > ../WebApplication/app.css +$ ./GenerateImages.py diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,7 @@ +# This is the list of the symbols that must be exported by Orthanc +# plugins, if targeting OS X + +_OrthancPluginInitialize +_OrthancPluginFinalize +_OrthancPluginGetName +_OrthancPluginGetVersion diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,3383 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#include "OrthancPluginCppWrapper.h" + +#include +#include +#include +#include +#include + + +#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin; +#endif + + +namespace OrthancPlugins +{ + static OrthancPluginContext* globalContext_ = NULL; + + + void SetGlobalContext(OrthancPluginContext* context) + { + if (context == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + else if (globalContext_ == NULL) + { + globalContext_ = context; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + } + + + bool HasGlobalContext() + { + return globalContext_ != NULL; + } + + + OrthancPluginContext* GetGlobalContext() + { + if (globalContext_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); + } + else + { + return globalContext_; + } + } + + + void MemoryBuffer::Check(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + // Prevent using garbage information + buffer_.data = NULL; + buffer_.size = 0; + } + + if (code == OrthancPluginErrorCode_Success) + { + return true; + } + else if (code == OrthancPluginErrorCode_UnknownResource || + code == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } + + + MemoryBuffer::MemoryBuffer() + { + buffer_.data = NULL; + buffer_.size = 0; + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + MemoryBuffer::MemoryBuffer(const void* buffer, + size_t size) + { + uint32_t s = static_cast(size); + if (static_cast(s) != size) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != + OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); + } + else + { + memcpy(buffer_.data, buffer, size); + } + } +#endif + + + void MemoryBuffer::Clear() + { + if (buffer_.data != NULL) + { + OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_); + buffer_.data = NULL; + buffer_.size = 0; + } + } + + + void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) + { + Clear(); + + buffer_.data = other.data; + buffer_.size = other.size; + + other.data = NULL; + other.size = 0; + } + + + void MemoryBuffer::Swap(MemoryBuffer& other) + { + std::swap(buffer_.data, other.buffer_.data); + std::swap(buffer_.size, other.buffer_.size); + } + + + OrthancPluginMemoryBuffer MemoryBuffer::Release() + { + OrthancPluginMemoryBuffer result = buffer_; + + buffer_.data = NULL; + buffer_.size = 0; + + return result; + } + + + void MemoryBuffer::ToString(std::string& target) const + { + if (buffer_.size == 0) + { + target.clear(); + } + else + { + target.assign(reinterpret_cast(buffer_.data), buffer_.size); + } + } + + + void MemoryBuffer::ToJson(Json::Value& target) const + { + if (buffer_.data == NULL || + buffer_.size == 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + const char* tmp = reinterpret_cast(buffer_.data); + + Json::Reader reader; + if (!reader.parse(tmp, tmp + buffer_.size, target)) + { + LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool MemoryBuffer::RestApiGet(const std::string& uri, + bool applyPlugins) + { + Clear(); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str())); + } + else + { + return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str())); + } + } + + bool MemoryBuffer::RestApiGet(const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + Clear(); + + std::vector headersKeys; + std::vector headersValues; + + for (std::map::const_iterator + it = httpHeaders.begin(); it != httpHeaders.end(); it++) + { + headersKeys.push_back(it->first.c_str()); + headersValues.push_back(it->second.c_str()); + } + + return CheckHttp(OrthancPluginRestApiGet2( + GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), + (headersKeys.empty() ? NULL : &headersKeys[0]), + (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); + } + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + // Cast for compatibility with Orthanc SDK <= 1.5.6 + const char* b = reinterpret_cast(body); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + Clear(); + + // Cast for compatibility with Orthanc SDK <= 1.5.6 + const char* b = reinterpret_cast(body); + + if (applyPlugins) + { + return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + else + { + return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); + } + } + + + bool MemoryBuffer::RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(uri, writer.write(body), applyPlugins); + } + + + bool MemoryBuffer::RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(uri, writer.write(body), applyPlugins); + } + + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags)); + } + + void MemoryBuffer::CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags) + { + Clear(); + + Json::FastWriter writer; + std::string s = writer.write(tags); + + Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags)); + } + + + void MemoryBuffer::ReadFile(const std::string& path) + { + Clear(); + Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str())); + } + + + void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query) + { + Clear(); + Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query)); + } + + + void OrthancString::Assign(char* str) + { + Clear(); + + if (str != NULL) + { + str_ = str; + } + } + + + void OrthancString::Clear() + { + if (str_ != NULL) + { + OrthancPluginFreeString(GetGlobalContext(), str_); + str_ = NULL; + } + } + + + void OrthancString::ToString(std::string& target) const + { + if (str_ == NULL) + { + target.clear(); + } + else + { + target.assign(str_); + } + } + + + void OrthancString::ToJson(Json::Value& target) const + { + if (str_ == NULL) + { + LogError("Cannot convert an empty memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Reader reader; + if (!reader.parse(str_, target)) + { + LogError("Cannot convert some memory buffer to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void MemoryBuffer::DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength) + { + OrthancString str; + str.Assign(OrthancPluginDicomBufferToJson + (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); + str.ToJson(target); + } + + + bool MemoryBuffer::HttpGet(const std::string& url, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(), + body.c_str(), body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + bool MemoryBuffer::HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password) + { + Clear(); + return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(), + body.empty() ? NULL : body.c_str(), + body.size(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str())); + } + + + void MemoryBuffer::GetDicomInstance(const std::string& instanceId) + { + Clear(); + Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str())); + } + + + bool HttpDelete(const std::string& url, + const std::string& username, + const std::string& password) + { + OrthancPluginErrorCode error = OrthancPluginHttpDelete + (GetGlobalContext(), url.c_str(), + username.empty() ? NULL : username.c_str(), + password.empty() ? NULL : password.c_str()); + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void LogError(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogError(GetGlobalContext(), message.c_str()); + } + } + + + void LogWarning(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); + } + } + + + void LogInfo(const std::string& message) + { + if (HasGlobalContext()) + { + OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); + } + } + + + void OrthancConfiguration::LoadConfiguration() + { + OrthancString str; + str.Assign(OrthancPluginGetConfiguration(GetGlobalContext())); + + if (str.GetContent() == NULL) + { + LogError("Cannot access the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + str.ToJson(configuration_); + + if (configuration_.type() != Json::objectValue) + { + LogError("Unable to read the Orthanc configuration"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancConfiguration::OrthancConfiguration() + { + LoadConfiguration(); + } + + + OrthancConfiguration::OrthancConfiguration(bool loadConfiguration) + { + if (loadConfiguration) + { + LoadConfiguration(); + } + else + { + configuration_ = Json::objectValue; + } + } + + + std::string OrthancConfiguration::GetPath(const std::string& key) const + { + if (path_.empty()) + { + return key; + } + else + { + return path_ + "." + key; + } + } + + + bool OrthancConfiguration::IsSection(const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + return (configuration_.isMember(key) && + configuration_[key].type() == Json::objectValue); + } + + + void OrthancConfiguration::GetSection(OrthancConfiguration& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.path_ = GetPath(key); + + if (!configuration_.isMember(key)) + { + target.configuration_ = Json::objectValue; + } + else + { + if (configuration_[key].type() != Json::objectValue) + { + LogError("The configuration section \"" + target.path_ + + "\" is not an associative array as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target.configuration_ = configuration_[key]; + } + } + + + bool OrthancConfiguration::LookupStringValue(std::string& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::stringValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asString(); + return true; + } + + + bool OrthancConfiguration::LookupIntegerValue(int& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::intValue: + target = configuration_[key].asInt(); + return true; + + case Json::uintValue: + target = configuration_[key].asUInt(); + return true; + + default: + LogError("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const + { + int tmp; + if (!LookupIntegerValue(tmp, key)) + { + return false; + } + + if (tmp < 0) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a positive integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + else + { + target = static_cast(tmp); + return true; + } + } + + + bool OrthancConfiguration::LookupBooleanValue(bool& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + if (configuration_[key].type() != Json::booleanValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a Boolean as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + target = configuration_[key].asBool(); + return true; + } + + + bool OrthancConfiguration::LookupFloatValue(float& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::realValue: + target = configuration_[key].asFloat(); + return true; + + case Json::intValue: + target = static_cast(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast(configuration_[key].asUInt()); + return true; + + default: + LogError("The configuration option \"" + GetPath(key) + + "\" is not an integer as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + bool OrthancConfiguration::LookupListOfStrings(std::list& target, + const std::string& key, + bool allowSingleString) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return false; + } + + switch (configuration_[key].type()) + { + case Json::arrayValue: + { + bool ok = true; + + for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++) + { + if (configuration_[key][i].type() == Json::stringValue) + { + target.push_back(configuration_[key][i].asString()); + } + else + { + ok = false; + } + } + + if (ok) + { + return true; + } + + break; + } + + case Json::stringValue: + if (allowSingleString) + { + target.push_back(configuration_[key].asString()); + return true; + } + + break; + + default: + break; + } + + LogError("The configuration option \"" + GetPath(key) + + "\" is not a list of strings as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + + bool OrthancConfiguration::LookupSetOfStrings(std::set& target, + const std::string& key, + bool allowSingleString) const + { + std::list lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list::const_iterator + it = lst.begin(); it != lst.end(); ++it) + { + target.insert(*it); + } + + return true; + } + else + { + return false; + } + } + + + std::string OrthancConfiguration::GetStringValue(const std::string& key, + const std::string& defaultValue) const + { + std::string tmp; + if (LookupStringValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + int OrthancConfiguration::GetIntegerValue(const std::string& key, + int defaultValue) const + { + int tmp; + if (LookupIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const + { + unsigned int tmp; + if (LookupUnsignedIntegerValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + bool OrthancConfiguration::GetBooleanValue(const std::string& key, + bool defaultValue) const + { + bool tmp; + if (LookupBooleanValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + float OrthancConfiguration::GetFloatValue(const std::string& key, + float defaultValue) const + { + float tmp; + if (LookupFloatValue(tmp, key)) + { + return tmp; + } + else + { + return defaultValue; + } + } + + + void OrthancConfiguration::GetDictionary(std::map& target, + const std::string& key) const + { + assert(configuration_.type() == Json::objectValue); + + target.clear(); + + if (!configuration_.isMember(key)) + { + return; + } + + if (configuration_[key].type() != Json::objectValue) + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a string as expected"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + + Json::Value::Members members = configuration_[key].getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = configuration_[key][members[i]]; + + if (value.type() == Json::stringValue) + { + target[members[i]] = value.asString(); + } + else + { + LogError("The configuration option \"" + GetPath(key) + + "\" is not a dictionary mapping strings to strings"); + + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + } + + + void OrthancImage::Clear() + { + if (image_ != NULL) + { + OrthancPluginFreeImage(GetGlobalContext(), image_); + image_ = NULL; + } + } + + + void OrthancImage::CheckImageAvailable() const + { + if (image_ == NULL) + { + LogError("Trying to access a NULL image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancImage::OrthancImage() : + image_(NULL) + { + } + + + OrthancImage::OrthancImage(OrthancPluginImage* image) : + image_(image) + { + } + + + OrthancImage::OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height); + + if (image_ == NULL) + { + LogError("Cannot create an image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + OrthancImage::OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + image_ = OrthancPluginCreateImageAccessor + (GetGlobalContext(), format, width, height, pitch, buffer); + + if (image_ == NULL) + { + LogError("Cannot create an image accessor"); + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + void OrthancImage::UncompressPngImage(const void* data, + size_t size) + { + Clear(); + + image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png); + + if (image_ == NULL) + { + LogError("Cannot uncompress a PNG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::UncompressJpegImage(const void* data, + size_t size) + { + Clear(); + image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg); + if (image_ == NULL) + { + LogError("Cannot uncompress a JPEG image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void OrthancImage::DecodeDicomImage(const void* data, + size_t size, + unsigned int frame) + { + Clear(); + image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame); + if (image_ == NULL) + { + LogError("Cannot uncompress a DICOM image"); + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const + { + CheckImageAvailable(); + return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetWidth() const + { + CheckImageAvailable(); + return OrthancPluginGetImageWidth(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetHeight() const + { + CheckImageAvailable(); + return OrthancPluginGetImageHeight(GetGlobalContext(), image_); + } + + + unsigned int OrthancImage::GetPitch() const + { + CheckImageAvailable(); + return OrthancPluginGetImagePitch(GetGlobalContext(), image_); + } + + + void* OrthancImage::GetBuffer() const + { + CheckImageAvailable(); + return OrthancPluginGetImageBuffer(GetGlobalContext(), image_); + } + + + void OrthancImage::CompressPngImage(MemoryBuffer& target) const + { + CheckImageAvailable(); + + OrthancPlugins::MemoryBuffer answer; + OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + + target.Swap(answer); + } + + + void OrthancImage::CompressJpegImage(MemoryBuffer& target, + uint8_t quality) const + { + CheckImageAvailable(); + + OrthancPlugins::MemoryBuffer answer; + OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + + target.Swap(answer); + } + + + void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer()); + } + + + void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) const + { + CheckImageAvailable(); + OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(), + GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); + } + + + OrthancPluginImage* OrthancImage::Release() + { + CheckImageAvailable(); + OrthancPluginImage* tmp = image_; + image_ = NULL; + return tmp; + } + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) : + matcher_(NULL), + worklist_(worklist) + { + if (worklist_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); + } + } + + + void FindMatcher::SetupDicom(const void* query, + uint32_t size) + { + worklist_ = NULL; + + matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size); + if (matcher_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + + + FindMatcher::~FindMatcher() + { + // The "worklist_" field + + if (matcher_ != NULL) + { + OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_); + } + } + + + + bool FindMatcher::IsMatch(const void* dicom, + uint32_t size) const + { + int32_t result; + + if (matcher_ != NULL) + { + result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size); + } + else if (worklist_ != NULL) + { + result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size); + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + if (result == 0) + { + return false; + } + else if (result == 1) + { + return true; + } + else + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + } + +#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ + + void AnswerJson(const Json::Value& value, + OrthancPluginRestOutput* output + ) + { + Json::StyledWriter writer; + std::string bodyString = writer.write(value); + + OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json"); + } + + void AnswerString(const std::string& answer, + const char* mimeType, + OrthancPluginRestOutput* output + ) + { + OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType); + } + + void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output) + { + OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError); + } + + void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods); + } + + bool RestApiGetString(std::string& result, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer; + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + answer.ToString(result); + return true; + } + } + + bool RestApiGetString(std::string& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins) + { + MemoryBuffer answer; + if (!answer.RestApiGet(uri, httpHeaders, applyPlugins)) + { + return false; + } + else + { + answer.ToString(result); + return true; + } + } + + + + bool RestApiGet(Json::Value& result, + const std::string& uri, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiGet(uri, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPost(std::string& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + result.assign(answer.GetData(), answer.GetSize()); + } + return true; + } + } + + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPost(result, uri, writer.write(body), applyPlugins); + } + + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins) + { + MemoryBuffer answer; + + if (!answer.RestApiPut(uri, body, bodySize, applyPlugins)) + { + return false; + } + else + { + if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response + { + answer.ToJson(result); + } + return true; + } + } + + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins) + { + Json::FastWriter writer; + return RestApiPut(result, uri, writer.write(body), applyPlugins); + } + + + bool RestApiDelete(const std::string& uri, + bool applyPlugins) + { + OrthancPluginErrorCode error; + + if (applyPlugins) + { + error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str()); + } + else + { + error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str()); + } + + if (error == OrthancPluginErrorCode_Success) + { + return true; + } + else if (error == OrthancPluginErrorCode_UnknownResource || + error == OrthancPluginErrorCode_InexistentItem) + { + return false; + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } + + + void ReportMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + LogError("Your version of the Orthanc core (" + + std::string(GetGlobalContext()->orthancVersion) + + ") is too old to run this plugin (version " + + boost::lexical_cast(major) + "." + + boost::lexical_cast(minor) + "." + + boost::lexical_cast(revision) + + " is required)"); + } + + + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision) + { + if (!HasGlobalContext()) + { + LogError("Bad Orthanc context in the plugin"); + return false; + } + + if (!strcmp(GetGlobalContext()->orthancVersion, "mainline")) + { + // Assume compatibility with the mainline + return true; + } + + // Parse the version of the Orthanc core + int aa, bb, cc; + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || + aa < 0 || + bb < 0 || + cc < 0) + { + return false; + } + + unsigned int a = static_cast(aa); + unsigned int b = static_cast(bb); + unsigned int c = static_cast(cc); + + // Check the major version number + + if (a > major) + { + return true; + } + + if (a < major) + { + return false; + } + + + // Check the minor version number + assert(a == major); + + if (b > minor) + { + return true; + } + + if (b < minor) + { + return false; + } + + // Check the patch level version number + assert(a == major && b == minor); + + if (c >= revision) + { + return true; + } + else + { + return false; + } + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) + const char* AutodetectMimeType(const std::string& path) + { + const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str()); + + if (mime == NULL) + { + // Should never happen, just for safety + return "application/octet-stream"; + } + else + { + return mime; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + size_t OrthancPeers::GetPeerIndex(const std::string& name) const + { + size_t index; + if (LookupName(index, name)) + { + return index; + } + else + { + LogError("Inexistent peer: " + name); + ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); + } + } + + + OrthancPeers::OrthancPeers() : + peers_(NULL), + timeout_(0) + { + peers_ = OrthancPluginGetPeers(GetGlobalContext()); + + if (peers_ == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_); + + for (uint32_t i = 0; i < count; i++) + { + const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i); + if (name == NULL) + { + OrthancPluginFreePeers(GetGlobalContext(), peers_); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + + index_[name] = i; + } + } + + + OrthancPeers::~OrthancPeers() + { + if (peers_ != NULL) + { + OrthancPluginFreePeers(GetGlobalContext(), peers_); + } + } + + + bool OrthancPeers::LookupName(size_t& target, + const std::string& name) const + { + Index::const_iterator found = index_.find(name); + + if (found == index_.end()) + { + return false; + } + else + { + target = found->second; + return true; + } + } + + + std::string OrthancPeers::GetPeerName(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(size_t index) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast(index)); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return s; + } + } + } + + + std::string OrthancPeers::GetPeerUrl(const std::string& name) const + { + return GetPeerUrl(GetPeerIndex(name)); + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + else + { + const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast(index), key.c_str()); + if (s == NULL) + { + return false; + } + else + { + value.assign(s); + return true; + } + } + } + + + bool OrthancPeers::LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const + { + return LookupUserProperty(value, GetPeerIndex(peer), key); + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Get, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Swap(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoGet(target, index, uri)); + } + + + bool OrthancPeers::DoGet(Json::Value& target, + size_t index, + const std::string& uri) const + { + MemoryBuffer buffer; + + if (DoGet(buffer, index, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const + { + MemoryBuffer buffer; + + if (DoGet(buffer, name, uri)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPost(target, index, uri, body)); + } + + + bool OrthancPeers::DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, index, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const + { + MemoryBuffer buffer; + + if (DoPost(buffer, name, uri, body)) + { + buffer.ToJson(target); + return true; + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Post, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + target.Swap(answer); + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(size_t index, + const std::string& uri, + const std::string& body) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Put, uri.c_str(), + 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const + { + size_t index; + return (LookupName(index, name) && + DoPut(index, uri, body)); + } + + + bool OrthancPeers::DoDelete(size_t index, + const std::string& uri) const + { + if (index >= index_.size()) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + OrthancPlugins::MemoryBuffer answer; + uint16_t status; + OrthancPluginErrorCode code = OrthancPluginCallPeerApi + (GetGlobalContext(), *answer, NULL, &status, peers_, + static_cast(index), OrthancPluginHttpMethod_Delete, uri.c_str(), + 0, NULL, NULL, NULL, 0, timeout_); + + if (code == OrthancPluginErrorCode_Success) + { + return (status == 200); + } + else + { + return false; + } + } + + + bool OrthancPeers::DoDelete(const std::string& name, + const std::string& uri) const + { + size_t index; + return (LookupName(index, name) && + DoDelete(index, uri)); + } +#endif + + + + + + /****************************************************************** + ** JOBS + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + void OrthancJob::CallbackFinalize(void* job) + { + if (job != NULL) + { + delete reinterpret_cast(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast(job); + + if (tmp.hasSerialized_) + { + return tmp.serialized_.c_str(); + } + else + { + return NULL; + } + } + catch (...) + { + return 0; + } + } + + + OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast(job)->Step(); + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) + { + return OrthancPluginJobStepStatus_Failure; + } + catch (...) + { + return OrthancPluginJobStepStatus_Failure; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackStop(void* job, + OrthancPluginJobStopReason reason) + { + assert(job != NULL); + + try + { + reinterpret_cast(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void OrthancJob::ClearContent() + { + Json::Value empty = Json::objectValue; + UpdateContent(empty); + } + + + void OrthancJob::UpdateContent(const Json::Value& content) + { + if (content.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + content_ = writer.write(content); + } + } + + + void OrthancJob::ClearSerialized() + { + hasSerialized_ = false; + serialized_.clear(); + } + + + void OrthancJob::UpdateSerialized(const Json::Value& serialized) + { + if (serialized.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); + } + else + { + Json::FastWriter writer; + serialized_ = writer.write(serialized); + hasSerialized_ = true; + } + } + + + void OrthancJob::UpdateProgress(float progress) + { + if (progress < 0 || + progress > 1) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + } + + progress_ = progress; + } + + + OrthancJob::OrthancJob(const std::string& jobType) : + jobType_(jobType), + progress_(0) + { + ClearContent(); + ClearSerialized(); + } + + + OrthancPluginJob* OrthancJob::Create(OrthancJob* job) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = OrthancPluginCreateJob( + GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), + CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, + CallbackStep, CallbackStop, CallbackReset); + + if (orthanc == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + return orthanc; + } + } + + + std::string OrthancJob::Submit(OrthancJob* job, + int priority) + { + if (job == NULL) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); + } + + OrthancPluginJob* orthanc = Create(job); + + char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority); + + if (id == NULL) + { + LogError("Plugin cannot submit job"); + OrthancPluginFreeJob(GetGlobalContext(), orthanc); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); + } + else + { + std::string tmp(id); + tmp.assign(id); + OrthancPluginFreeString(GetGlobalContext(), id); + + return tmp; + } + } + + + void OrthancJob::SubmitAndWait(Json::Value& result, + OrthancJob* job /* takes ownership */, + int priority) + { + std::string id = Submit(job, priority); + + for (;;) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + + Json::Value status; + if (!RestApiGet(status, "/jobs/" + id, false) || + !status.isMember("State") || + status["State"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem); + } + + const std::string state = status["State"].asString(); + if (state == "Success") + { + if (status.isMember("Content")) + { + result = status["Content"]; + } + else + { + result = Json::objectValue; + } + + return; + } + else if (state == "Running") + { + continue; + } + else if (!status.isMember("ErrorCode") || + status["ErrorCode"].type() != Json::intValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); + } + else + { + if (!status.isMember("ErrorDescription") || + status["ErrorDescription"].type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); + } + else + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(static_cast(status["ErrorCode"].asInt()), + status["ErrorDescription"].asString()); +#else + LogError("Exception while executing the job: " + status["ErrorDescription"].asString()); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); +#endif + } + } + } + } + + + void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job) + { + static const char* KEY_SYNCHRONOUS = "Synchronous"; + static const char* KEY_ASYNCHRONOUS = "Asynchronous"; + static const char* KEY_PRIORITY = "Priority"; + + boost::movelib::unique_ptr protection(job); + + if (body.type() != Json::objectValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Expected a JSON object in the body"); +#else + LogError("Expected a JSON object in the body"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + + bool synchronous = true; + + if (body.isMember(KEY_SYNCHRONOUS)) + { + if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_SYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = body[KEY_SYNCHRONOUS].asBool(); + } + } + + if (body.isMember(KEY_ASYNCHRONOUS)) + { + if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_ASYNCHRONOUS) + + "\" must be Boolean"); +#else + LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + synchronous = !body[KEY_ASYNCHRONOUS].asBool(); + } + } + + int priority = 0; + + if (body.isMember(KEY_PRIORITY)) + { + if (body[KEY_PRIORITY].type() != Json::booleanValue) + { +#if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Option \"" + std::string(KEY_PRIORITY) + + "\" must be an integer"); +#else + LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); +#endif + } + else + { + priority = !body[KEY_PRIORITY].asInt(); + } + } + + Json::Value result; + + if (synchronous) + { + OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority); + } + else + { + std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority); + + result = Json::objectValue; + result["ID"] = id; + result["Path"] = "/jobs/" + id; + } + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), + s.size(), "application/json"); + } + +#endif + + + + + /****************************************************************** + ** METRICS + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_METRICS == 1 + MetricsTimer::MetricsTimer(const char* name) : + name_(name) + { + start_ = boost::posix_time::microsec_clock::universal_time(); + } + + MetricsTimer::~MetricsTimer() + { + const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time(); + const boost::posix_time::time_duration diff = stop - start_; + OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast(diff.total_milliseconds()), + OrthancPluginMetricsType_Timer); + } +#endif + + + + + /****************************************************************** + ** HTTP CLIENT + ******************************************************************/ + +#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 + class HttpClient::RequestBodyWrapper : public boost::noncopyable + { + private: + static RequestBodyWrapper& GetObject(void* body) + { + assert(body != NULL); + return *reinterpret_cast(body); + } + + IRequestBody& body_; + bool done_; + std::string chunk_; + + public: + RequestBodyWrapper(IRequestBody& body) : + body_(body), + done_(false) + { + } + + static uint8_t IsDone(void* body) + { + return GetObject(body).done_; + } + + static const void* GetChunkData(void* body) + { + return GetObject(body).chunk_.c_str(); + } + + static uint32_t GetChunkSize(void* body) + { + return static_cast(GetObject(body).chunk_.size()); + } + + static OrthancPluginErrorCode Next(void* body) + { + RequestBodyWrapper& that = GetObject(body); + + if (that.done_) + { + return OrthancPluginErrorCode_BadSequenceOfCalls; + } + else + { + try + { + that.done_ = !that.body_.ReadNextChunk(that.chunk_); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_InternalError; + } + } + } + }; + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer, + const char* key, + const char* value) + { + assert(answer != NULL && key != NULL && value != NULL); + + try + { + reinterpret_cast(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer, + const void* data, + uint32_t size) + { + assert(answer != NULL); + + try + { + reinterpret_cast(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + + HttpClient::HttpClient() : + httpStatus_(0), + method_(OrthancPluginHttpMethod_Get), + timeout_(0), + pkcs11_(false), + chunkedBody_(NULL), + allowChunkedTransfers_(true) + { + } + + + void HttpClient::AddHeaders(const HttpHeaders& headers) + { + for (HttpHeaders::const_iterator it = headers.begin(); + it != headers.end(); ++it) + { + headers_[it->first] = it->second; + } + } + + + void HttpClient::SetCredentials(const std::string& username, + const std::string& password) + { + username_ = username; + password_ = password; + } + + + void HttpClient::ClearCredentials() + { + username_.empty(); + password_.empty(); + } + + + void HttpClient::SetCertificate(const std::string& certificateFile, + const std::string& keyFile, + const std::string& keyPassword) + { + certificateFile_ = certificateFile; + certificateKeyFile_ = keyFile; + certificateKeyPassword_ = keyPassword; + } + + + void HttpClient::ClearCertificate() + { + certificateFile_.clear(); + certificateKeyFile_.clear(); + certificateKeyPassword_.clear(); + } + + + void HttpClient::ClearBody() + { + fullBody_.clear(); + chunkedBody_ = NULL; + } + + + void HttpClient::SwapBody(std::string& body) + { + fullBody_.swap(body); + chunkedBody_ = NULL; + } + + + void HttpClient::SetBody(const std::string& body) + { + fullBody_ = body; + chunkedBody_ = NULL; + } + + + void HttpClient::SetBody(IRequestBody& body) + { + fullBody_.clear(); + chunkedBody_ = &body; + } + + + namespace + { + class HeadersWrapper : public boost::noncopyable + { + private: + std::vector headersKeys_; + std::vector headersValues_; + + public: + HeadersWrapper(const HttpClient::HttpHeaders& headers) + { + headersKeys_.reserve(headers.size()); + headersValues_.reserve(headers.size()); + + for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headersKeys_.push_back(it->first.c_str()); + headersValues_.push_back(it->second.c_str()); + } + } + + void AddStaticString(const char* key, + const char* value) + { + headersKeys_.push_back(key); + headersValues_.push_back(value); + } + + uint32_t GetCount() const + { + return headersKeys_.size(); + } + + const char* const* GetKeys() const + { + return headersKeys_.empty() ? NULL : &headersKeys_[0]; + } + + const char* const* GetValues() const + { + return headersValues_.empty() ? NULL : &headersValues_[0]; + } + }; + + + class MemoryRequestBody : public HttpClient::IRequestBody + { + private: + std::string body_; + bool done_; + + public: + MemoryRequestBody(const std::string& body) : + body_(body), + done_(false) + { + if (body_.empty()) + { + done_ = true; + } + } + + virtual bool ReadNextChunk(std::string& chunk) + { + if (done_) + { + return false; + } + else + { + chunk.swap(body_); + done_ = true; + return true; + } + } + }; + + + // This class mimics Orthanc::ChunkedBuffer + class ChunkedBuffer : public boost::noncopyable + { + private: + typedef std::list Content; + + Content content_; + size_t size_; + + public: + ChunkedBuffer() : + size_(0) + { + } + + ~ChunkedBuffer() + { + Clear(); + } + + void Clear() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + + content_.clear(); + } + + void Flatten(std::string& target) const + { + target.resize(size_); + + size_t pos = 0; + + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + size_t s = (*it)->size(); + + if (s != 0) + { + memcpy(&target[pos], (*it)->c_str(), s); + pos += s; + } + } + + assert(size_ == 0 || + pos == target.size()); + } + + void AddChunk(const void* data, + size_t size) + { + content_.push_back(new std::string(reinterpret_cast(data), size)); + size_ += size; + } + + void AddChunk(const std::string& chunk) + { + content_.push_back(new std::string(chunk)); + size_ += chunk.size(); + } + }; + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + class MemoryAnswer : public HttpClient::IAnswer + { + private: + HttpClient::HttpHeaders headers_; + ChunkedBuffer body_; + + public: + const HttpClient::HttpHeaders& GetHeaders() const + { + return headers_; + } + + const ChunkedBuffer& GetBody() const + { + return body_; + } + + virtual void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + virtual void AddChunk(const void* data, + size_t size) + { + body_.AddChunk(data, size); + } + }; +#endif + } + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + void HttpClient::ExecuteWithStream(uint16_t& httpStatus, + IAnswer& answer, + IRequestBody& body) const + { + HeadersWrapper h(headers_); + + if (method_ == OrthancPluginHttpMethod_Post || + method_ == OrthancPluginHttpMethod_Put) + { + // Automatically set the "Transfer-Encoding" header if absent + bool found = false; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + if (boost::iequals(it->first, "Transfer-Encoding")) + { + found = true; + break; + } + } + + if (!found) + { + h.AddStaticString("Transfer-Encoding", "chunked"); + } + } + + RequestBodyWrapper request(body); + + OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient( + GetGlobalContext(), + &answer, + AnswerAddChunkCallback, + AnswerAddHeaderCallback, + &httpStatus, + method_, + url_.c_str(), + h.GetCount(), + h.GetKeys(), + h.GetValues(), + &request, + RequestBodyWrapper::IsDone, + RequestBodyWrapper::GetChunkData, + RequestBodyWrapper::GetChunkSize, + RequestBodyWrapper::Next, + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + } +#endif + + + void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, + HttpHeaders& answerHeaders, + std::string& answerBody, + const std::string& body) const + { + HeadersWrapper headers(headers_); + + MemoryBuffer answerBodyBuffer, answerHeadersBuffer; + + OrthancPluginErrorCode error = OrthancPluginHttpClient( + GetGlobalContext(), + *answerBodyBuffer, + *answerHeadersBuffer, + &httpStatus, + method_, + url_.c_str(), + headers.GetCount(), + headers.GetKeys(), + headers.GetValues(), + body.empty() ? NULL : body.c_str(), + body.size(), + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + + Json::Value v; + answerHeadersBuffer.ToJson(v); + + if (v.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Value::Members members = v.getMemberNames(); + answerHeaders.clear(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& h = v[members[i]]; + if (h.type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + answerHeaders[members[i]] = h.asString(); + } + } + + answerBodyBuffer.ToString(answerBody); + } + + + void HttpClient::Execute(IAnswer& answer) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + if (allowChunkedTransfers_) + { + if (chunkedBody_ != NULL) + { + ExecuteWithStream(httpStatus_, answer, *chunkedBody_); + } + else + { + MemoryRequestBody wrapper(fullBody_); + ExecuteWithStream(httpStatus_, answer, wrapper); + } + + return; + } +#endif + + // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked + // transfers are disabled. This results in higher memory usage + // (all chunks from the answer body are sent at once) + + HttpHeaders answerHeaders; + std::string answerBody; + Execute(answerHeaders, answerBody); + + for (HttpHeaders::const_iterator it = answerHeaders.begin(); + it != answerHeaders.end(); ++it) + { + answer.AddHeader(it->first, it->second); + } + + if (!answerBody.empty()) + { + answer.AddChunk(answerBody.c_str(), answerBody.size()); + } + } + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + if (allowChunkedTransfers_) + { + MemoryAnswer answer; + Execute(answer); + answerHeaders = answer.GetHeaders(); + answer.GetBody().Flatten(answerBody); + return; + } +#endif + + // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked + // transfers are disabled. This results in higher memory usage + // (all chunks from the request body are sent at once) + + if (chunkedBody_ != NULL) + { + ChunkedBuffer buffer; + + std::string chunk; + while (chunkedBody_->ReadNextChunk(chunk)) + { + buffer.AddChunk(chunk); + } + + std::string body; + buffer.Flatten(body); + + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body); + } + else + { + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_); + } + } + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + Json::Value& answerBody /* out */) + { + std::string body; + Execute(answerHeaders, body); + + Json::Reader reader; + if (!reader.parse(body, answerBody)) + { + LogError("Cannot convert HTTP answer body to JSON"); + ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); + } + } + + + void HttpClient::Execute() + { + HttpHeaders answerHeaders; + std::string body; + Execute(answerHeaders, body); + } + +#endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ + + + + + + /****************************************************************** + ** CHUNKED HTTP SERVER + ******************************************************************/ + + namespace Internals + { + void NullRestCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + } + + IChunkedRequestReader *NullChunkedRestCallback(const char* url, + const OrthancPluginHttpRequest* request) + { + return NULL; + } + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + + OrthancPluginErrorCode ChunkedRequestReaderAddChunk( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + + reinterpret_cast(reader)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode ChunkedRequestReaderExecute( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + + reinterpret_cast(reader)->Execute(output); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader) + { + if (reader != NULL) + { + delete reinterpret_cast(reader); + } + } + +#else + + OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler) + { + try + { + std::string allowed; + + if (GetHandler != Internals::NullRestCallback) + { + allowed += "GET"; + } + + if (PostHandler != Internals::NullChunkedRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "POST"; + } + + if (DeleteHandler != Internals::NullRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "DELETE"; + } + + if (PutHandler != Internals::NullChunkedRestCallback) + { + if (!allowed.empty()) + { + allowed += ","; + } + + allowed += "PUT"; + } + + switch (request->method) + { + case OrthancPluginHttpMethod_Get: + if (GetHandler == Internals::NullRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + GetHandler(output, url, request); + } + + break; + + case OrthancPluginHttpMethod_Post: + if (PostHandler == Internals::NullChunkedRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + boost::movelib::unique_ptr reader(PostHandler(url, request)); + if (reader.get() == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + reader->AddChunk(request->body, request->bodySize); + reader->Execute(output); + } + } + + break; + + case OrthancPluginHttpMethod_Delete: + if (DeleteHandler == Internals::NullRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + DeleteHandler(output, url, request); + } + + break; + + case OrthancPluginHttpMethod_Put: + if (PutHandler == Internals::NullChunkedRestCallback) + { + OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); + } + else + { + boost::movelib::unique_ptr reader(PutHandler(url, request)); + if (reader.get() == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + reader->AddChunk(request->body, request->bodySize); + reader->Execute(output); + } + } + + break; + + default: + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { +#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 + if (HasGlobalContext() && + e.HasDetails()) + { + // The "false" instructs Orthanc not to log the detailed + // error message. This is to avoid duplicating the details, + // because "OrthancException" already does it on construction. + OrthancPluginSetHttpErrorDetails + (GetGlobalContext(), output, e.GetDetails(), false); + } +#endif + + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + } + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup( + OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid) + { + assert(target != NULL && + rawHandler != NULL); + + try + { + IStorageCommitmentScpHandler& handler = *reinterpret_cast(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + void IStorageCommitmentScpHandler::Destructor(void* rawHandler) + { + assert(rawHandler != NULL); + delete reinterpret_cast(rawHandler); + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) : + toFree_(false), + instance_(instance) + { + } +#else + DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) : + toFree_(false), + instance_(instance) + { + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance::DicomInstance(const void* buffer, + size_t size) : + toFree_(true), + instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size)) + { + if (instance_ == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); + } + } +#endif + + + DicomInstance::~DicomInstance() + { +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + if (toFree_ && + instance_ != NULL) + { + OrthancPluginFreeDicomInstance( + GetGlobalContext(), const_cast(instance_)); + } +#endif + } + + + std::string DicomInstance::GetRemoteAet() const + { + const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_); + if (s == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return std::string(s); + } + } + + + void DicomInstance::GetJson(Json::Value& target) const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_)); + s.ToJson(target); + } + + + void DicomInstance::GetSimplifiedJson(Json::Value& target) const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_)); + s.ToJson(target); + } + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + std::string DicomInstance::GetTransferSyntaxUid() const + { + OrthancString s; + s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_)); + + std::string result; + s.ToString(result); + return result; + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + bool DicomInstance::HasPixelData() const + { + int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_); + if (result < 0) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return (result != 0); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void DicomInstance::GetRawFrame(std::string& target, + unsigned int frameIndex) const + { + MemoryBuffer buffer; + OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame( + GetGlobalContext(), *buffer, instance_, frameIndex); + + if (code == OrthancPluginErrorCode_Success) + { + buffer.ToString(target); + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const + { + OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame( + GetGlobalContext(), instance_, frameIndex); + + if (image == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + return new OrthancImage(image); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void DicomInstance::Serialize(std::string& target) const + { + MemoryBuffer buffer; + OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance( + GetGlobalContext(), *buffer, instance_); + + if (code == OrthancPluginErrorCode_Success) + { + buffer.ToString(target); + } + else + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); + } + } +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance* DicomInstance::Transcode(const void* buffer, + size_t size, + const std::string& transferSyntax) + { + OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance( + GetGlobalContext(), buffer, size, transferSyntax.c_str()); + + if (instance == NULL) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); + } + else + { + boost::movelib::unique_ptr result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#endif +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,1228 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#pragma once + +#include "OrthancPluginException.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for + * backward compatibility with Orthanc SDK <= 1.3.0. + * + * $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h + * + **/ +#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) +#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ + (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ + (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) +#endif + + +#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE) +#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision) \ + (ORTHANC_VERSION_MAJOR > major || \ + (ORTHANC_VERSION_MAJOR == major && \ + (ORTHANC_VERSION_MINOR > minor || \ + (ORTHANC_VERSION_MINOR == minor && \ + ORTHANC_VERSION_REVISION >= revision)))) +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) +// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0 +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1 +#else +# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0 +#endif + + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2) +# define HAS_ORTHANC_PLUGIN_PEERS 1 +# define HAS_ORTHANC_PLUGIN_JOB 1 +#else +# define HAS_ORTHANC_PLUGIN_PEERS 0 +# define HAS_ORTHANC_PLUGIN_JOB 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) +# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 1 +#else +# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) +# define HAS_ORTHANC_PLUGIN_METRICS 1 +#else +# define HAS_ORTHANC_PLUGIN_METRICS 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0) +# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 1 +#else +# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 1 +#else +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 1 +#else +# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0 +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0) +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1 +#else +# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 +#endif + + + +namespace OrthancPlugins +{ + typedef void (*RestCallback) (OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + void SetGlobalContext(OrthancPluginContext* context); + + bool HasGlobalContext(); + + OrthancPluginContext* GetGlobalContext(); + + + class OrthancImage; + + + class MemoryBuffer : public boost::noncopyable + { + private: + OrthancPluginMemoryBuffer buffer_; + + void Check(OrthancPluginErrorCode code); + + bool CheckHttp(OrthancPluginErrorCode code); + + public: + MemoryBuffer(); + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + // This constructor makes a copy of the given buffer in the memory + // handled by the Orthanc core + MemoryBuffer(const void* buffer, + size_t size); +#endif + + ~MemoryBuffer() + { + Clear(); + } + + OrthancPluginMemoryBuffer* operator*() + { + return &buffer_; + } + + // This transfers ownership from "other" to "this" + void Assign(OrthancPluginMemoryBuffer& other); + + void Swap(MemoryBuffer& other); + + OrthancPluginMemoryBuffer Release(); + + const char* GetData() const + { + if (buffer_.size > 0) + { + return reinterpret_cast(buffer_.data); + } + else + { + return NULL; + } + } + + size_t GetSize() const + { + return buffer_.size; + } + + bool IsEmpty() const + { + return GetSize() == 0 || GetData() == NULL; + } + + void Clear(); + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + + bool RestApiGet(const std::string& uri, + bool applyPlugins); + + bool RestApiGet(const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPut(const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + bool RestApiPost(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + bool RestApiPut(const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); + } + + void CreateDicom(const Json::Value& tags, + OrthancPluginCreateDicomFlags flags); + + void CreateDicom(const Json::Value& tags, + const OrthancImage& pixelData, + OrthancPluginCreateDicomFlags flags); + + void ReadFile(const std::string& path); + + void GetDicomQuery(const OrthancPluginWorklistQuery* query); + + void DicomToJson(Json::Value& target, + OrthancPluginDicomToJsonFormat format, + OrthancPluginDicomToJsonFlags flags, + uint32_t maxStringLength); + + bool HttpGet(const std::string& url, + const std::string& username, + const std::string& password); + + bool HttpPost(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + bool HttpPut(const std::string& url, + const std::string& body, + const std::string& username, + const std::string& password); + + void GetDicomInstance(const std::string& instanceId); + }; + + + class OrthancString : public boost::noncopyable + { + private: + char* str_; + + void Clear(); + + public: + OrthancString() : + str_(NULL) + { + } + + ~OrthancString() + { + Clear(); + } + + // This transfers ownership, warning: The string must have been + // allocated by the Orthanc core + void Assign(char* str); + + const char* GetContent() const + { + return str_; + } + + void ToString(std::string& target) const; + + void ToJson(Json::Value& target) const; + }; + + + class OrthancConfiguration : public boost::noncopyable + { + private: + Json::Value configuration_; // Necessarily a Json::objectValue + std::string path_; + + std::string GetPath(const std::string& key) const; + + void LoadConfiguration(); + + public: + OrthancConfiguration(); + + explicit OrthancConfiguration(bool load); + + const Json::Value& GetJson() const + { + return configuration_; + } + + bool IsSection(const std::string& key) const; + + void GetSection(OrthancConfiguration& target, + const std::string& key) const; + + bool LookupStringValue(std::string& target, + const std::string& key) const; + + bool LookupIntegerValue(int& target, + const std::string& key) const; + + bool LookupUnsignedIntegerValue(unsigned int& target, + const std::string& key) const; + + bool LookupBooleanValue(bool& target, + const std::string& key) const; + + bool LookupFloatValue(float& target, + const std::string& key) const; + + bool LookupListOfStrings(std::list& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set& target, + const std::string& key, + bool allowSingleString) const; + + std::string GetStringValue(const std::string& key, + const std::string& defaultValue) const; + + int GetIntegerValue(const std::string& key, + int defaultValue) const; + + unsigned int GetUnsignedIntegerValue(const std::string& key, + unsigned int defaultValue) const; + + bool GetBooleanValue(const std::string& key, + bool defaultValue) const; + + float GetFloatValue(const std::string& key, + float defaultValue) const; + + void GetDictionary(std::map& target, + const std::string& key) const; + }; + + class OrthancImage : public boost::noncopyable + { + private: + OrthancPluginImage* image_; + + void Clear(); + + void CheckImageAvailable() const; + + public: + OrthancImage(); + + explicit OrthancImage(OrthancPluginImage* image); + + OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height); + + OrthancImage(OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer); + + ~OrthancImage() + { + Clear(); + } + + void UncompressPngImage(const void* data, + size_t size); + + void UncompressJpegImage(const void* data, + size_t size); + + void DecodeDicomImage(const void* data, + size_t size, + unsigned int frame); + + OrthancPluginPixelFormat GetPixelFormat() const; + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + unsigned int GetPitch() const; + + void* GetBuffer() const; + + const OrthancPluginImage* GetObject() const + { + return image_; + } + + void CompressPngImage(MemoryBuffer& target) const; + + void CompressJpegImage(MemoryBuffer& target, + uint8_t quality) const; + + void AnswerPngImage(OrthancPluginRestOutput* output) const; + + void AnswerJpegImage(OrthancPluginRestOutput* output, + uint8_t quality) const; + + void* GetWriteableBuffer(); + + OrthancPluginImage* Release(); + }; + + +#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 + class FindMatcher : public boost::noncopyable + { + private: + OrthancPluginFindMatcher* matcher_; + const OrthancPluginWorklistQuery* worklist_; + + void SetupDicom(const void* query, + uint32_t size); + + public: + explicit FindMatcher(const OrthancPluginWorklistQuery* worklist); + + FindMatcher(const void* query, + uint32_t size) + { + SetupDicom(query, size); + } + + explicit FindMatcher(const MemoryBuffer& dicom) + { + SetupDicom(dicom.GetData(), dicom.GetSize()); + } + + ~FindMatcher(); + + bool IsMatch(const void* dicom, + uint32_t size) const; + + bool IsMatch(const MemoryBuffer& dicom) const + { + return IsMatch(dicom.GetData(), dicom.GetSize()); + } + }; +#endif + + + bool RestApiGet(Json::Value& result, + const std::string& uri, + bool applyPlugins); + + bool RestApiGetString(std::string& result, + const std::string& uri, + bool applyPlugins); + + bool RestApiGetString(std::string& result, + const std::string& uri, + const std::map& httpHeaders, + bool applyPlugins); + + bool RestApiPost(std::string& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPost(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPost(Json::Value& result, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + inline bool RestApiPost(Json::Value& result, + const std::string& uri, + const MemoryBuffer& body, + bool applyPlugins) + { + return RestApiPost(result, uri, body.GetData(), + body.GetSize(), applyPlugins); + } + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const void* body, + size_t bodySize, + bool applyPlugins); + + bool RestApiPut(Json::Value& result, + const std::string& uri, + const Json::Value& body, + bool applyPlugins); + + inline bool RestApiPut(Json::Value& result, + const std::string& uri, + const std::string& body, + bool applyPlugins) + { + return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(), + body.size(), applyPlugins); + } + + bool RestApiDelete(const std::string& uri, + bool applyPlugins); + + bool HttpDelete(const std::string& url, + const std::string& username, + const std::string& password); + + void AnswerJson(const Json::Value& value, + OrthancPluginRestOutput* output); + + void AnswerString(const std::string& answer, + const char* mimeType, + OrthancPluginRestOutput* output); + + void AnswerHttpError(uint16_t httpError, + OrthancPluginRestOutput* output); + + void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods); + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) + const char* AutodetectMimeType(const std::string& path); +#endif + + void LogError(const std::string& message); + + void LogWarning(const std::string& message); + + void LogInfo(const std::string& message); + + void ReportMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision); + + bool CheckMinimalOrthancVersion(unsigned int major, + unsigned int minor, + unsigned int revision); + + + namespace Internals + { + template + static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + Callback(output, url, request); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { +#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 + if (HasGlobalContext() && + e.HasDetails()) + { + // The "false" instructs Orthanc not to log the detailed + // error message. This is to avoid duplicating the details, + // because "OrthancException" already does it on construction. + OrthancPluginSetHttpErrorDetails + (GetGlobalContext(), output, e.GetDetails(), false); + } +#endif + + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template + void RegisterRestCallback(const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock + (GetGlobalContext(), uri.c_str(), Internals::Protect); + } + else + { + OrthancPluginRegisterRestCallback + (GetGlobalContext(), uri.c_str(), Internals::Protect); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map Index; + + OrthancPluginPeers *peers_; + Index index_; + uint32_t timeout_; + + size_t GetPeerIndex(const std::string& name) const; + + public: + OrthancPeers(); + + ~OrthancPeers(); + + uint32_t GetTimeout() const + { + return timeout_; + } + + void SetTimeout(uint32_t timeout) + { + timeout_ = timeout; + } + + bool LookupName(size_t& target, + const std::string& name) const; + + std::string GetPeerName(size_t index) const; + + std::string GetPeerUrl(size_t index) const; + + std::string GetPeerUrl(const std::string& name) const; + + size_t GetPeersCount() const + { + return index_.size(); + } + + bool LookupUserProperty(std::string& value, + size_t index, + const std::string& key) const; + + bool LookupUserProperty(std::string& value, + const std::string& peer, + const std::string& key) const; + + bool DoGet(MemoryBuffer& target, + size_t index, + const std::string& uri) const; + + bool DoGet(MemoryBuffer& target, + const std::string& name, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + size_t index, + const std::string& uri) const; + + bool DoGet(Json::Value& target, + const std::string& name, + const std::string& uri) const; + + bool DoPost(MemoryBuffer& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(MemoryBuffer& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPost(Json::Value& target, + const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoPut(size_t index, + const std::string& uri, + const std::string& body) const; + + bool DoPut(const std::string& name, + const std::string& uri, + const std::string& body) const; + + bool DoDelete(size_t index, + const std::string& uri) const; + + bool DoDelete(const std::string& name, + const std::string& uri) const; + }; +#endif + + + +#if HAS_ORTHANC_PLUGIN_JOB == 1 + class OrthancJob : public boost::noncopyable + { + private: + std::string jobType_; + std::string content_; + bool hasSerialized_; + std::string serialized_; + float progress_; + + static void CallbackFinalize(void* job); + + static float CallbackGetProgress(void* job); + + static const char* CallbackGetContent(void* job); + + static const char* CallbackGetSerialized(void* job); + + static OrthancPluginJobStepStatus CallbackStep(void* job); + + static OrthancPluginErrorCode CallbackStop(void* job, + OrthancPluginJobStopReason reason); + + static OrthancPluginErrorCode CallbackReset(void* job); + + protected: + void ClearContent(); + + void UpdateContent(const Json::Value& content); + + void ClearSerialized(); + + void UpdateSerialized(const Json::Value& serialized); + + void UpdateProgress(float progress); + + public: + OrthancJob(const std::string& jobType); + + virtual ~OrthancJob() + { + } + + virtual OrthancPluginJobStepStatus Step() = 0; + + virtual void Stop(OrthancPluginJobStopReason reason) = 0; + + virtual void Reset() = 0; + + static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */); + + static std::string Submit(OrthancJob* job /* takes ownership */, + int priority); + + static void SubmitAndWait(Json::Value& result, + OrthancJob* job /* takes ownership */, + int priority); + + // Submit a job from a POST on the REST API with the same + // conventions as in the Orthanc core (according to the + // "Synchronous" and "Priority" options) + static void SubmitFromRestApiPost(OrthancPluginRestOutput* output, + const Json::Value& body, + OrthancJob* job); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_METRICS == 1 + inline void SetMetricsValue(char* name, + float value) + { + OrthancPluginSetMetricsValue(GetGlobalContext(), name, + value, OrthancPluginMetricsType_Default); + } + + class MetricsTimer : public boost::noncopyable + { + private: + std::string name_; + boost::posix_time::ptime start_; + + public: + explicit MetricsTimer(const char* name); + + ~MetricsTimer(); + }; +#endif + + +#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 + class HttpClient : public boost::noncopyable + { + public: + typedef std::map HttpHeaders; + + class IRequestBody : public boost::noncopyable + { + public: + virtual ~IRequestBody() + { + } + + virtual bool ReadNextChunk(std::string& chunk) = 0; + }; + + + class IAnswer : public boost::noncopyable + { + public: + virtual ~IAnswer() + { + } + + virtual void AddHeader(const std::string& key, + const std::string& value) = 0; + + virtual void AddChunk(const void* data, + size_t size) = 0; + }; + + + private: + class RequestBodyWrapper; + + uint16_t httpStatus_; + OrthancPluginHttpMethod method_; + std::string url_; + HttpHeaders headers_; + std::string username_; + std::string password_; + uint32_t timeout_; + std::string certificateFile_; + std::string certificateKeyFile_; + std::string certificateKeyPassword_; + bool pkcs11_; + std::string fullBody_; + IRequestBody* chunkedBody_; + bool allowChunkedTransfers_; + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 + void ExecuteWithStream(uint16_t& httpStatus, // out + IAnswer& answer, // out + IRequestBody& body) const; +#endif + + void ExecuteWithoutStream(uint16_t& httpStatus, // out + HttpHeaders& answerHeaders, // out + std::string& answerBody, // out + const std::string& body) const; + + public: + HttpClient(); + + uint16_t GetHttpStatus() const + { + return httpStatus_; + } + + void SetMethod(OrthancPluginHttpMethod method) + { + method_ = method; + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + void SetHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + void AddHeaders(const HttpHeaders& headers); + + void SetCredentials(const std::string& username, + const std::string& password); + + void ClearCredentials(); + + void SetTimeout(unsigned int timeout) // 0 for default timeout + { + timeout_ = timeout; + } + + void SetCertificate(const std::string& certificateFile, + const std::string& keyFile, + const std::string& keyPassword); + + void ClearCertificate(); + + void SetPkcs11(bool pkcs11) + { + pkcs11_ = pkcs11; + } + + void ClearBody(); + + void SwapBody(std::string& body); + + void SetBody(const std::string& body); + + void SetBody(IRequestBody& body); + + // This function can be used to disable chunked transfers if the + // remote server is Orthanc with a version <= 1.5.6. + void SetChunkedTransfersAllowed(bool allow) + { + allowChunkedTransfers_ = allow; + } + + bool IsChunkedTransfersAllowed() const + { + return allowChunkedTransfers_; + } + + void Execute(IAnswer& answer); + + void Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */); + + void Execute(HttpHeaders& answerHeaders /* out */, + Json::Value& answerBody /* out */); + + void Execute(); + }; +#endif + + + + class IChunkedRequestReader : public boost::noncopyable + { + public: + virtual ~IChunkedRequestReader() + { + } + + virtual void AddChunk(const void* data, + size_t size) = 0; + + virtual void Execute(OrthancPluginRestOutput* output) = 0; + }; + + + typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url, + const OrthancPluginHttpRequest* request); + + + namespace Internals + { + void NullRestCallback(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + IChunkedRequestReader *NullChunkedRestCallback(const char* url, + const OrthancPluginHttpRequest* request); + + +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + template + static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + *reader = reinterpret_cast(Callback(url, request)); + if (*reader == NULL) + { + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + OrthancPluginErrorCode ChunkedRequestReaderAddChunk( + OrthancPluginServerChunkedRequestReader* reader, + const void* data, + uint32_t size); + + OrthancPluginErrorCode ChunkedRequestReaderExecute( + OrthancPluginServerChunkedRequestReader* reader, + OrthancPluginRestOutput* output); + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader); + +#else + + OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request, + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler); + + template< + RestCallback GetHandler, + ChunkedRestCallback PostHandler, + RestCallback DeleteHandler, + ChunkedRestCallback PutHandler + > + inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) + { + return ChunkedRestCompatibility(output, url, request, GetHandler, + PostHandler, DeleteHandler, PutHandler); + } +#endif + } + + + + // NB: We use a templated class instead of a templated function, because + // default values are only available in functions since C++11 + template< + RestCallback GetHandler = Internals::NullRestCallback, + ChunkedRestCallback PostHandler = Internals::NullChunkedRestCallback, + RestCallback DeleteHandler = Internals::NullRestCallback, + ChunkedRestCallback PutHandler = Internals::NullChunkedRestCallback + > + class ChunkedRestRegistration : public boost::noncopyable + { + public: + static void Apply(const std::string& uri) + { +#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 + OrthancPluginRegisterChunkedRestCallback( + GetGlobalContext(), uri.c_str(), + GetHandler == Internals::NullRestCallback ? NULL : Internals::Protect, + PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, + DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect, + PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, + Internals::ChunkedRequestReaderAddChunk, + Internals::ChunkedRequestReaderExecute, + Internals::ChunkedRequestReaderFinalize); +#else + OrthancPluginRegisterRestCallbackNoLock( + GetGlobalContext(), uri.c_str(), + Internals::ChunkedRestCompatibility); +#endif + } + }; + + + +#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 + class IStorageCommitmentScpHandler : public boost::noncopyable + { + public: + virtual ~IStorageCommitmentScpHandler() + { + } + + virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, + const std::string& sopInstanceUid) = 0; + + static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target, + void* rawHandler, + const char* sopClassUid, + const char* sopInstanceUid); + + static void Destructor(void* rawHandler); + }; +#endif + + + class DicomInstance : public boost::noncopyable + { + private: + bool toFree_; + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + const OrthancPluginDicomInstance* instance_; +#else + OrthancPluginDicomInstance* instance_; +#endif + + public: +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + explicit DicomInstance(const OrthancPluginDicomInstance* instance); +#else + explicit DicomInstance(OrthancPluginDicomInstance* instance); +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + DicomInstance(const void* buffer, + size_t size); +#endif + + ~DicomInstance(); + + std::string GetRemoteAet() const; + + const void* GetBuffer() const + { + return OrthancPluginGetInstanceData(GetGlobalContext(), instance_); + } + + size_t GetSize() const + { + return static_cast(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_)); + } + + void GetJson(Json::Value& target) const; + + void GetSimplifiedJson(Json::Value& target) const; + + OrthancPluginInstanceOrigin GetOrigin() const + { + return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_); + } + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + std::string GetTransferSyntaxUid() const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) + bool HasPixelData() const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + unsigned int GetFramesCount() const + { + return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_); + } +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void GetRawFrame(std::string& target, + unsigned int frameIndex) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + OrthancImage* GetDecodedFrame(unsigned int frameIndex) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + void Serialize(std::string& target) const; +#endif + +#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) + static DicomInstance* Transcode(const void* buffer, + size_t size, + const std::string& transferSyntax); +#endif + }; +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 . + **/ + + +#pragma once + +#if !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::Orthanc::ErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::Orthanc::OrthancException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::Orthanc::ErrorCode_ ## code +#else +# include +# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::OrthancPluginErrorCode +# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::OrthancPlugins::PluginException +# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::OrthancPluginErrorCode_ ## code +#endif + + +#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast(code)); + + +#define ORTHANC_PLUGINS_THROW_EXCEPTION(code) \ + throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code)); + + +#define ORTHANC_PLUGINS_CHECK_ERROR(code) \ + if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success)) \ + { \ + ORTHANC_PLUGINS_THROW_EXCEPTION(code); \ + } + + +namespace OrthancPlugins +{ +#if HAS_ORTHANC_EXCEPTION == 0 + class PluginException + { + private: + OrthancPluginErrorCode code_; + + public: + explicit PluginException(OrthancPluginErrorCode code) : code_(code) + { + } + + OrthancPluginErrorCode GetErrorCode() const + { + return code_; + } + + const char* What(OrthancPluginContext* context) const + { + const char* description = OrthancPluginGetErrorDescription(context, code_); + if (description) + { + return description; + } + else + { + return "No description available"; + } + } + }; +#endif +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,31 @@ +# Orthanc - A Lightweight, RESTful DICOM Store +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., Belgium +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 . + + +# In Orthanc <= 1.7.1, the instructions below were part of +# "Compiler.cmake", and were protected by the (now unused) option +# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR + ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map") +elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list") +endif() diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,12 @@ +# This is a version-script for Orthanc plugins + +{ +global: + OrthancPluginInitialize; + OrthancPluginFinalize; + OrthancPluginGetName; + OrthancPluginGetVersion; + +local: + *; +}; diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,4740 @@ +/** + * \mainpage + * + * This C/C++ SDK allows external developers to create plugins that + * can be loaded into Orthanc to extend its functionality. Each + * Orthanc plugin must expose 4 public functions with the following + * signatures: + * + * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): + * This function is invoked by Orthanc when it loads the plugin on startup. + * The plugin must: + * - Check its compatibility with the Orthanc version using + * ::OrthancPluginCheckVersion(). + * - Store the context pointer so that it can use the plugin + * services of Orthanc. + * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). + * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). + * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). + * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). + * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). + * -# void OrthancPluginFinalize(): + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# const char* OrthancPluginGetName(): + * The plugin must return a short string to identify itself. + * -# const char* OrthancPluginGetVersion(): + * The plugin must return a string containing its version number. + * + * The name and the version of a plugin is only used to prevent it + * from being loaded twice. Note that, in C++, it is mandatory to + * declare these functions within an extern "C" section. + * + * To ensure multi-threading safety, the various REST callbacks are + * guaranteed to be executed in mutual exclusion since Orthanc + * 0.8.5. If this feature is undesired (notably when developing + * high-performance plugins handling simultaneous requests), use + * ::OrthancPluginRegisterRestCallbackNoLock(). + **/ + + + +/** + * @defgroup Images Images and compression + * @brief Functions to deal with images and compressed buffers. + * + * @defgroup REST REST + * @brief Functions to answer REST requests in a callback. + * + * @defgroup Callbacks Callbacks + * @brief Functions to register and manage callbacks by the plugins. + * + * @defgroup Worklists Worklists + * @brief Functions to register and manage worklists. + * + * @defgroup Orthanc Orthanc + * @brief Functions to access the content of the Orthanc server. + **/ + + + +/** + * @defgroup Toolbox Toolbox + * @brief Generic functions to help with the creation of plugins. + **/ + + + +/** + * 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 . + **/ + + + +#pragma once + + +#include +#include + +#ifdef WIN32 +#define ORTHANC_PLUGINS_API __declspec(dllexport) +#else +#define ORTHANC_PLUGINS_API +#endif + +#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 + + + +/******************************************************************** + ** Check that function inlining is properly supported. The use of + ** inlining is required, to avoid the duplication of object code + ** between two compilation modules that would use the Orthanc Plugin + ** API. + ********************************************************************/ + +/* If the auto-detection of the "inline" keyword below does not work + automatically and that your compiler is known to properly support + inlining, uncomment the following #define and adapt the definition + of "static inline". */ + +/* #define ORTHANC_PLUGIN_INLINE static inline */ + +#ifndef ORTHANC_PLUGIN_INLINE +# if __STDC_VERSION__ >= 199901L +/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__cplusplus) +/* This is C++ */ +# define ORTHANC_PLUGIN_INLINE static inline +# elif defined(__GNUC__) +/* This is GCC running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# elif defined(_MSC_VER) +/* This is Visual Studio running in C89 mode */ +# define ORTHANC_PLUGIN_INLINE static __inline +# else +# error Your compiler is not known to support the "inline" keyword +# endif +#endif + + + +/******************************************************************** + ** Inclusion of standard libraries. + ********************************************************************/ + +/** + * For Microsoft Visual Studio, a compatibility "stdint.h" can be + * downloaded at the following URL: + * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h + **/ +#include + +#include + + + +/******************************************************************** + ** Definition of the Orthanc Plugin API. + ********************************************************************/ + +/** @{ */ + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * The various error codes that can be returned by the Orthanc core. + **/ + typedef enum + { + OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, + OrthancPluginErrorCode_Success = 0 /*!< Success */, + OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, + OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, + OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, + OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, + OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, + OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, + OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, + OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, + OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, + OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, + OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, + OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, + OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, + OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, + OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, + OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, + OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, + OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, + OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, + OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, + OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, + OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, + OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, + OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, + OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, + OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, + OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, + OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, + OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, + OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, + OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, + OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, + OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, + OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, + OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, + OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, + OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, + OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, + OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, + OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, + OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, + OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, + OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, + OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, + OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, + OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, + OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, + OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, + OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, + OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, + OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, + OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, + OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, + OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, + OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, + OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, + OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, + OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, + OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, + OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, + OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, + OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, + OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, + OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, + OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, + OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, + OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, + OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, + OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, + OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, + OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, + OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, + OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, + OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, + OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, + OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, + OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, + OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, + OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, + OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, + OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, + OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, + OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, + OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, + OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, + OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, + OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, + OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, + OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, + OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, + + _OrthancPluginErrorCode_INTERNAL = 0x7fffffff + } OrthancPluginErrorCode; + + + /** + * Forward declaration of one of the mandatory functions for Orthanc + * plugins. + **/ + ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); + + + /** + * The various HTTP methods for a REST call. + **/ + typedef enum + { + OrthancPluginHttpMethod_Get = 1, /*!< GET request */ + OrthancPluginHttpMethod_Post = 2, /*!< POST request */ + OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ + OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ + + _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff + } OrthancPluginHttpMethod; + + + /** + * @brief The parameters of a REST request. + * @ingroup Callbacks + **/ + typedef struct + { + /** + * @brief The HTTP method. + **/ + OrthancPluginHttpMethod method; + + /** + * @brief The number of groups of the regular expression. + **/ + uint32_t groupsCount; + + /** + * @brief The matched values for the groups of the regular expression. + **/ + const char* const* groups; + + /** + * @brief For a GET request, the number of GET parameters. + **/ + uint32_t getCount; + + /** + * @brief For a GET request, the keys of the GET parameters. + **/ + const char* const* getKeys; + + /** + * @brief For a GET request, the values of the GET parameters. + **/ + const char* const* getValues; + + /** + * @brief For a PUT or POST request, the content of the body. + **/ + const char* body; + + /** + * @brief For a PUT or POST request, the number of bytes of the body. + **/ + uint32_t bodySize; + + + /* -------------------------------------------------- + New in version 0.8.1 + -------------------------------------------------- */ + + /** + * @brief The number of HTTP headers. + **/ + uint32_t headersCount; + + /** + * @brief The keys of the HTTP headers (always converted to low-case). + **/ + const char* const* headersKeys; + + /** + * @brief The values of the HTTP headers. + **/ + const char* const* headersValues; + + } OrthancPluginHttpRequest; + + + typedef enum + { + /* Generic services */ + _OrthancPluginService_LogInfo = 1, + _OrthancPluginService_LogWarning = 2, + _OrthancPluginService_LogError = 3, + _OrthancPluginService_GetOrthancPath = 4, + _OrthancPluginService_GetOrthancDirectory = 5, + _OrthancPluginService_GetConfigurationPath = 6, + _OrthancPluginService_SetPluginProperty = 7, + _OrthancPluginService_GetGlobalProperty = 8, + _OrthancPluginService_SetGlobalProperty = 9, + _OrthancPluginService_GetCommandLineArgumentsCount = 10, + _OrthancPluginService_GetCommandLineArgument = 11, + _OrthancPluginService_GetExpectedDatabaseVersion = 12, + _OrthancPluginService_GetConfiguration = 13, + _OrthancPluginService_BufferCompression = 14, + _OrthancPluginService_ReadFile = 15, + _OrthancPluginService_WriteFile = 16, + _OrthancPluginService_GetErrorDescription = 17, + _OrthancPluginService_CallHttpClient = 18, + _OrthancPluginService_RegisterErrorCode = 19, + _OrthancPluginService_RegisterDictionaryTag = 20, + _OrthancPluginService_DicomBufferToJson = 21, + _OrthancPluginService_DicomInstanceToJson = 22, + _OrthancPluginService_CreateDicom = 23, + _OrthancPluginService_ComputeMd5 = 24, + _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, + + /* Registration of callbacks */ + _OrthancPluginService_RegisterRestCallback = 1000, + _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, + _OrthancPluginService_RegisterStorageArea = 1002, + _OrthancPluginService_RegisterOnChangeCallback = 1003, + _OrthancPluginService_RegisterRestCallbackNoLock = 1004, + _OrthancPluginService_RegisterWorklistCallback = 1005, + _OrthancPluginService_RegisterDecodeImageCallback = 1006, + + /* Sending answers to REST calls */ + _OrthancPluginService_AnswerBuffer = 2000, + _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ + _OrthancPluginService_Redirect = 2002, + _OrthancPluginService_SendHttpStatusCode = 2003, + _OrthancPluginService_SendUnauthorized = 2004, + _OrthancPluginService_SendMethodNotAllowed = 2005, + _OrthancPluginService_SetCookie = 2006, + _OrthancPluginService_SetHttpHeader = 2007, + _OrthancPluginService_StartMultipartAnswer = 2008, + _OrthancPluginService_SendMultipartItem = 2009, + _OrthancPluginService_SendHttpStatus = 2010, + _OrthancPluginService_CompressAndAnswerImage = 2011, + _OrthancPluginService_SendMultipartItem2 = 2012, + + /* Access to the Orthanc database and API */ + _OrthancPluginService_GetDicomForInstance = 3000, + _OrthancPluginService_RestApiGet = 3001, + _OrthancPluginService_RestApiPost = 3002, + _OrthancPluginService_RestApiDelete = 3003, + _OrthancPluginService_RestApiPut = 3004, + _OrthancPluginService_LookupPatient = 3005, + _OrthancPluginService_LookupStudy = 3006, + _OrthancPluginService_LookupSeries = 3007, + _OrthancPluginService_LookupInstance = 3008, + _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, + _OrthancPluginService_RestApiGetAfterPlugins = 3010, + _OrthancPluginService_RestApiPostAfterPlugins = 3011, + _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, + _OrthancPluginService_RestApiPutAfterPlugins = 3013, + _OrthancPluginService_ReconstructMainDicomTags = 3014, + _OrthancPluginService_RestApiGet2 = 3015, + + /* Access to DICOM instances */ + _OrthancPluginService_GetInstanceRemoteAet = 4000, + _OrthancPluginService_GetInstanceSize = 4001, + _OrthancPluginService_GetInstanceData = 4002, + _OrthancPluginService_GetInstanceJson = 4003, + _OrthancPluginService_GetInstanceSimplifiedJson = 4004, + _OrthancPluginService_HasInstanceMetadata = 4005, + _OrthancPluginService_GetInstanceMetadata = 4006, + _OrthancPluginService_GetInstanceOrigin = 4007, + + /* Services for plugins implementing a database back-end */ + _OrthancPluginService_RegisterDatabaseBackend = 5000, + _OrthancPluginService_DatabaseAnswer = 5001, + _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, + _OrthancPluginService_StorageAreaCreate = 5003, + _OrthancPluginService_StorageAreaRead = 5004, + _OrthancPluginService_StorageAreaRemove = 5005, + + /* Primitives for handling images */ + _OrthancPluginService_GetImagePixelFormat = 6000, + _OrthancPluginService_GetImageWidth = 6001, + _OrthancPluginService_GetImageHeight = 6002, + _OrthancPluginService_GetImagePitch = 6003, + _OrthancPluginService_GetImageBuffer = 6004, + _OrthancPluginService_UncompressImage = 6005, + _OrthancPluginService_FreeImage = 6006, + _OrthancPluginService_CompressImage = 6007, + _OrthancPluginService_ConvertPixelFormat = 6008, + _OrthancPluginService_GetFontsCount = 6009, + _OrthancPluginService_GetFontInfo = 6010, + _OrthancPluginService_DrawText = 6011, + _OrthancPluginService_CreateImage = 6012, + _OrthancPluginService_CreateImageAccessor = 6013, + _OrthancPluginService_DecodeDicomImage = 6014, + + /* Primitives for handling worklists */ + _OrthancPluginService_WorklistAddAnswer = 7000, + _OrthancPluginService_WorklistMarkIncomplete = 7001, + _OrthancPluginService_WorklistIsMatch = 7002, + _OrthancPluginService_WorklistGetDicomQuery = 7003, + + _OrthancPluginService_INTERNAL = 0x7fffffff + } _OrthancPluginService; + + + typedef enum + { + _OrthancPluginProperty_Description = 1, + _OrthancPluginProperty_RootUri = 2, + _OrthancPluginProperty_OrthancExplorer = 3, + + _OrthancPluginProperty_INTERNAL = 0x7fffffff + } _OrthancPluginProperty; + + + + /** + * The memory layout of the pixels of an image. + * @ingroup Images + **/ + typedef enum + { + /** + * @brief Graylevel 8bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * one byte. + **/ + OrthancPluginPixelFormat_Grayscale8 = 1, + + /** + * @brief Graylevel, unsigned 16bpp image. + * + * The image is graylevel. Each pixel is unsigned and stored in + * two bytes. + **/ + OrthancPluginPixelFormat_Grayscale16 = 2, + + /** + * @brief Graylevel, signed 16bpp image. + * + * The image is graylevel. Each pixel is signed and stored in two + * bytes. + **/ + OrthancPluginPixelFormat_SignedGrayscale16 = 3, + + /** + * @brief Color image in RGB24 format. + * + * This format describes a color image. The pixels are stored in 3 + * consecutive bytes. The memory layout is RGB. + **/ + OrthancPluginPixelFormat_RGB24 = 4, + + /** + * @brief Color image in RGBA32 format. + * + * This format describes a color image. The pixels are stored in 4 + * consecutive bytes. The memory layout is RGBA. + **/ + OrthancPluginPixelFormat_RGBA32 = 5, + + OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ + + _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff + } OrthancPluginPixelFormat; + + + + /** + * The content types that are supported by Orthanc plugins. + **/ + typedef enum + { + OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ + OrthancPluginContentType_Dicom = 1, /*!< DICOM */ + OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ + + _OrthancPluginContentType_INTERNAL = 0x7fffffff + } OrthancPluginContentType; + + + + /** + * The supported types of DICOM resources. + **/ + typedef enum + { + OrthancPluginResourceType_Patient = 0, /*!< Patient */ + OrthancPluginResourceType_Study = 1, /*!< Study */ + OrthancPluginResourceType_Series = 2, /*!< Series */ + OrthancPluginResourceType_Instance = 3, /*!< Instance */ + OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ + + _OrthancPluginResourceType_INTERNAL = 0x7fffffff + } OrthancPluginResourceType; + + + + /** + * The supported types of changes that can happen to DICOM resources. + * @ingroup Callbacks + **/ + typedef enum + { + OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ + OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ + OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ + OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ + OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ + OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ + OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ + 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_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ + OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ + + _OrthancPluginChangeType_INTERNAL = 0x7fffffff + } OrthancPluginChangeType; + + + /** + * The compression algorithms that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ + OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ + OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ + OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ + + _OrthancPluginCompressionType_INTERNAL = 0x7fffffff + } OrthancPluginCompressionType; + + + /** + * The image formats that are supported by the Orthanc core. + * @ingroup Images + **/ + typedef enum + { + OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ + OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ + OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ + + _OrthancPluginImageFormat_INTERNAL = 0x7fffffff + } OrthancPluginImageFormat; + + + /** + * The value representations present in the DICOM standard (version 2013). + * @ingroup Toolbox + **/ + typedef enum + { + OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ + OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ + OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ + OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ + OrthancPluginValueRepresentation_DA = 5, /*!< Date */ + OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ + OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ + OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ + OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ + OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ + OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ + OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ + OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ + OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ + OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ + OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ + OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ + OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ + OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ + OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ + OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ + OrthancPluginValueRepresentation_TM = 22, /*!< Time */ + OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ + OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ + OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ + OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ + OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ + + _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff + } OrthancPluginValueRepresentation; + + + /** + * The possible output formats for a DICOM-to-JSON conversion. + * @ingroup Toolbox + * @see OrthancPluginDicomToJson() + **/ + typedef enum + { + OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ + OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ + OrthancPluginDicomToJsonFormat_Human = 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; + + + /** + * Flags to the creation of a DICOM file. + * @ingroup Toolbox + * @see OrthancPluginCreateDicom() + **/ + typedef enum + { + OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ + OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ + + _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff + } OrthancPluginCreateDicomFlags; + + + /** + * The constraints on the DICOM identifiers that must be supported + * by the database plugins. + **/ + typedef enum + { + OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ + OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ + OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ + OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ + + _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff + } OrthancPluginIdentifierConstraint; + + + /** + * The origin of a DICOM instance that has been received by Orthanc. + **/ + typedef enum + { + OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ + OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ + OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ + OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ + OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ + + _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff + } OrthancPluginInstanceOrigin; + + + /** + * @brief A memory buffer allocated by the core system of Orthanc. + * + * A memory buffer allocated by the core system of Orthanc. When the + * content of the buffer is not useful anymore, it must be free by a + * call to ::OrthancPluginFreeMemoryBuffer(). + **/ + typedef struct + { + /** + * @brief The content of the buffer. + **/ + void* data; + + /** + * @brief The number of bytes in the buffer. + **/ + uint32_t size; + } OrthancPluginMemoryBuffer; + + + + + /** + * @brief Opaque structure that represents the HTTP connection to the client application. + * @ingroup Callback + **/ + typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; + + + + /** + * @brief Opaque structure that represents a DICOM instance received by Orthanc. + **/ + typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; + + + + /** + * @brief Opaque structure that represents an image that is uncompressed in memory. + * @ingroup Images + **/ + typedef struct _OrthancPluginImage_t OrthancPluginImage; + + + + /** + * @brief Opaque structure that represents the storage area that is actually used by Orthanc. + * @ingroup Images + **/ + typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; + + + + /** + * @brief Opaque structure to an object that represents a C-Find query. + * @ingroup Worklists + **/ + typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; + + + + /** + * @brief Opaque structure to an object that represents the answers to a C-Find query. + * @ingroup Worklists + **/ + typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; + + + + /** + * @brief Signature of a callback function that answers to a REST request. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( + OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request); + + + + /** + * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( + OrthancPluginDicomInstance* instance, + const char* instanceId); + + + + /** + * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( + OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId); + + + + /** + * @brief Signature of a callback function to decode a DICOM instance as an image. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( + OrthancPluginImage** target, + const void* dicom, + const uint32_t size, + uint32_t frameIndex); + + + + /** + * @brief Signature of a function to free dynamic memory. + **/ + typedef void (*OrthancPluginFree) (void* buffer); + + + + /** + * @brief Callback for writing to the storage area. + * + * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. + * + * @param uuid The UUID of the file. + * @param content The content of the file. + * @param size The size of the file. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( + const char* uuid, + const void* content, + int64_t size, + OrthancPluginContentType type); + + + + /** + * @brief Callback for reading from the storage area. + * + * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. + * + * @param content The content of the file (output). + * @param size The size of the file (output). + * @param uuid The UUID of the file of interest. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( + void** content, + int64_t* size, + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback for removing a file from the storage area. + * + * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. + * + * @param uuid The UUID of the file to be removed. + * @param type The content type corresponding to this file. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( + const char* uuid, + OrthancPluginContentType type); + + + + /** + * @brief Callback to handle the C-Find SCP requests received by Orthanc. + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. + * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const char* remoteAet, + const char* calledAet); + + + + /** + * @brief Data structure that contains information about the Orthanc core. + **/ + typedef struct _OrthancPluginContext_t + { + void* pluginsManager; + const char* orthancVersion; + OrthancPluginFree Free; + OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, + _OrthancPluginService service, + const void* params); + } OrthancPluginContext; + + + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + + + /** + * @brief Free a string. + * + * Free a string that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param str The string to be freed. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( + OrthancPluginContext* context, + char* str) + { + if (str != NULL) + { + context->Free(str); + } + } + + + /** + * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. + * + * This function checks whether the version of this C header is + * compatible with the current version of Orthanc. The result of + * this function should always be checked in the + * OrthancPluginInitialize() entry point of the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return 1 if and only if the versions are compatible. If the + * result is 0, the initialization of the plugin should fail. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( + OrthancPluginContext* context) + { + int major, minor, revision; + + if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || + sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || + sizeof(int32_t) != sizeof(_OrthancPluginService) || + sizeof(int32_t) != sizeof(_OrthancPluginProperty) || + sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || + sizeof(int32_t) != sizeof(OrthancPluginContentType) || + sizeof(int32_t) != sizeof(OrthancPluginResourceType) || + sizeof(int32_t) != sizeof(OrthancPluginChangeType) || + sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || + sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || + sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || + sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || + sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || + sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || + sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin)) + { + /* Mismatch in the size of the enumerations */ + return 0; + } + + /* Assume compatibility with the mainline */ + if (!strcmp(context->orthancVersion, "mainline")) + { + return 1; + } + + /* Parse the version of the Orthanc core */ + if ( +#ifdef _MSC_VER + sscanf_s +#else + sscanf +#endif + (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) + { + return 0; + } + + /* Check the major number of the version */ + + if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 1; + } + + if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) + { + return 0; + } + + /* Check the minor number of the version */ + + if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 1; + } + + if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) + { + return 0; + } + + /* Check the revision number of the version */ + + if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) + { + return 1; + } + else + { + return 0; + } + } + + + /** + * @brief Free a memory buffer. + * + * Free a memory buffer that was allocated by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The memory buffer to release. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* buffer) + { + context->Free(buffer->data); + } + + + /** + * @brief Log an error. + * + * Log an error message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogError, message); + } + + + /** + * @brief Log a warning. + * + * Log a warning message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogWarning, message); + } + + + /** + * @brief Log an information. + * + * Log an information message using the Orthanc logging system. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param message The message to be logged. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( + OrthancPluginContext* context, + const char* message) + { + context->InvokeService(context, _OrthancPluginService_LogInfo, message); + } + + + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginRestCallback callback; + } _OrthancPluginRestCallback; + + /** + * @brief Register a REST callback. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Each REST callback is guaranteed to run in mutual exclusion. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallbackNoLock() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); + } + + + + /** + * @brief Register a REST callback, without locking. + * + * This function registers a REST callback against a regular + * expression for a URI. This function must be called during the + * initialization of the plugin, i.e. inside the + * OrthancPluginInitialize() public function. + * + * Contrarily to OrthancPluginRegisterRestCallback(), the callback + * will NOT be invoked in mutual exclusion. This can be useful for + * high-performance plugins that must handle concurrent requests + * (Orthanc uses a pool of threads, one thread being assigned to + * each incoming HTTP request). Of course, it is up to the plugin to + * implement the required locking mechanisms. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param pathRegularExpression Regular expression for the URI. May contain groups. + * @param callback The callback function to handle the REST call. + * @see OrthancPluginRegisterRestCallback() + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginRestCallback callback) + { + _OrthancPluginRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.callback = callback; + context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); + } + + + + typedef struct + { + OrthancPluginOnStoredInstanceCallback callback; + } _OrthancPluginOnStoredInstanceCallback; + + /** + * @brief Register a callback for received instances. + * + * This function registers a callback function that is called + * whenever a new DICOM instance is stored into the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( + OrthancPluginContext* context, + OrthancPluginOnStoredInstanceCallback callback) + { + _OrthancPluginOnStoredInstanceCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + const char* mimeType; + } _OrthancPluginAnswerBuffer; + + /** + * @brief Answer to a REST request. + * + * This function answers to a REST request with the content of a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the answer. + * @param answerSize Number of bytes of the answer. + * @param mimeType The MIME type of the answer. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + const char* mimeType) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = mimeType; + context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + } _OrthancPluginCompressAndAnswerPngImage; + + typedef struct + { + OrthancPluginRestOutput* output; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressAndAnswerImage; + + + /** + * @brief Answer to a REST request with a PNG image. + * + * This function answers to a REST request with a PNG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a PNG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* No quality for PNG */ + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* instanceId; + } _OrthancPluginGetDicomForInstance; + + /** + * @brief Retrieve a DICOM instance using its Orthanc identifier. + * + * Retrieve a DICOM instance using its Orthanc identifier. The DICOM + * file is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param instanceId The Orthanc identifier of the DICOM instance of interest. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* instanceId) + { + _OrthancPluginGetDicomForInstance params; + params.target = target; + params.instanceId = instanceId; + return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + } _OrthancPluginRestApiGet; + + /** + * @brief Make a GET call to the built-in Orthanc REST API. + * + * Make a GET call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); + } + + + + /** + * @brief Make a GET call to the REST API, as tainted by the plugins. + * + * Make a GET call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri) + { + _OrthancPluginRestApiGet params; + params.target = target; + params.uri = uri; + return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + const char* body; + uint32_t bodySize; + } _OrthancPluginRestApiPostPut; + + /** + * @brief Make a POST call to the built-in Orthanc REST API. + * + * Make a POST call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPostAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); + } + + + /** + * @brief Make a POST call to the REST API, as tainted by the plugins. + * + * Make a POST call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the POST request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPost + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); + } + + + + /** + * @brief Make a DELETE call to the built-in Orthanc REST API. + * + * Make a DELETE call to the built-in Orthanc REST API. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDeleteAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); + } + + + /** + * @brief Make a DELETE call to the REST API, as tainted by the plugins. + * + * Make a DELETE call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The URI to delete in the built-in Orthanc API. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiDelete + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( + OrthancPluginContext* context, + const char* uri) + { + return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); + } + + + + /** + * @brief Make a PUT call to the built-in Orthanc REST API. + * + * Make a PUT call to the built-in Orthanc REST API. The result to + * the query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPutAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); + } + + + + /** + * @brief Make a PUT call to the REST API, as tainted by the plugins. + * + * Make a PUT call to the Orthanc REST API, after all the plugins + * are applied. In other words, if some plugin overrides or adds the + * called URI to the built-in Orthanc REST API, this call will + * return the result provided by this plugin. The result to the + * query is stored into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param body The body of the PUT request. + * @param bodySize The size of the body. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiPut + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + const char* body, + uint32_t bodySize) + { + _OrthancPluginRestApiPostPut params; + params.target = target; + params.uri = uri; + params.body = body; + params.bodySize = bodySize; + return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* argument; + } _OrthancPluginOutputPlusArgument; + + /** + * @brief Redirect a REST request. + * + * This function answers to a REST request by redirecting the user + * to another URI using HTTP status 301. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param redirection Where to redirect. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* redirection) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = redirection; + context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); + } + + + + typedef struct + { + char** result; + const char* argument; + } _OrthancPluginRetrieveDynamicString; + + /** + * @brief Look for a patient. + * + * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored patients). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param patientID The Patient ID of interest. + * @return The NULL value if the patient is non-existent, or a string containing the + * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( + OrthancPluginContext* context, + const char* patientID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = patientID; + + if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study. + * + * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param studyUID The Study Instance UID of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( + OrthancPluginContext* context, + const char* studyUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = studyUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a study, using the accession number. + * + * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored studies). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param accessionNumber The Accession Number of interest. + * @return The NULL value if the study is non-existent, or a string containing the + * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( + OrthancPluginContext* context, + const char* accessionNumber) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = accessionNumber; + + if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for a series. + * + * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored series). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param seriesUID The Series Instance UID of interest. + * @return The NULL value if the series is non-existent, or a string containing the + * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( + OrthancPluginContext* context, + const char* seriesUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = seriesUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Look for an instance. + * + * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). + * This function uses the database index to run as fast as possible (it does not loop + * over all the stored instances). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param sopInstanceUID The SOP Instance UID of interest. + * @return The NULL value if the instance is non-existent, or a string containing the + * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( + OrthancPluginContext* context, + const char* sopInstanceUID) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = sopInstanceUID; + + if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + } _OrthancPluginSendHttpStatusCode; + + /** + * @brief Send a HTTP status code. + * + * This function answers to a REST request by sending a HTTP status + * code (such as "400 - Bad Request"). Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @ingroup REST + * @see OrthancPluginSendHttpStatus() + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status) + { + _OrthancPluginSendHttpStatusCode params; + params.output = output; + params.status = status; + context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); + } + + + /** + * @brief Signal that a REST request is not authorized. + * + * This function answers to a REST request by signaling that it is + * not authorized. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param realm The realm for the authorization process. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* realm) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = realm; + context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); + } + + + /** + * @brief Signal that this URI does not support this HTTP method. + * + * This function answers to a REST request by signaling that the + * queried URI does not support this method. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* allowedMethods) + { + _OrthancPluginOutputPlusArgument params; + params.output = output; + params.argument = allowedMethods; + context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); + } + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* key; + const char* value; + } _OrthancPluginSetHttpHeader; + + /** + * @brief Set a cookie. + * + * This function sets a cookie in the HTTP client. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param cookie The cookie to be set. + * @param value The value of the cookie. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* cookie, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = cookie; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); + } + + + /** + * @brief Set some HTTP header. + * + * This function sets a HTTP header in the HTTP answer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param key The HTTP header to be set. + * @param value The value of the HTTP header. + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* key, + const char* value) + { + _OrthancPluginSetHttpHeader params; + params.output = output; + params.key = key; + params.value = value; + context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); + } + + + typedef struct + { + char** resultStringToFree; + const char** resultString; + int64_t* resultInt64; + const char* key; + OrthancPluginDicomInstance* instance; + OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ + } _OrthancPluginAccessDicomInstance; + + + /** + * @brief Get the AET of a DICOM instance. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM instance originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The AET if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the size of a DICOM file. + * + * This function returns the number of bytes of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The size of the file, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + int64_t size; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &size; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return size; + } + } + + + /** + * @brief Get the data of a DICOM file. + * + * This function returns a pointer to the content of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The pointer to the DICOM data, NULL in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file. + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). + * + * This function returns a pointer to a newly created string + * containing a JSON file. This JSON file encodes the tag hierarchy + * of the given DICOM instance. In contrast with + * ::OrthancPluginGetInstanceJson(), the returned JSON file is in + * its simplified version. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The NULL value in case of error, or a string containing the JSON file. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultStringToFree = &result; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Check whether a DICOM instance is associated with some metadata. + * + * This function checks whether the DICOM instance of interest is + * associated with some metadata. As of Orthanc 0.8.1, in the + * callbacks registered by + * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only + * possibly available metadata are "ReceptionDate", "RemoteAET" and + * "IndexInSeries". + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + int64_t result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultInt64 = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return -1; + } + else + { + return (result != 0); + } + } + + + /** + * @brief Get the value of some metadata associated with a given DICOM instance. + * + * This functions returns the value of some metadata that is associated with the DICOM instance of interest. + * Before calling this function, the existence of the metadata must have been checked with + * ::OrthancPluginHasInstanceMetadata(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @param metadata The metadata of interest. + * @return The metadata value if success, NULL if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance, + const char* metadata) + { + const char* result; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.instance = instance; + params.key = metadata; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginStorageCreate create; + OrthancPluginStorageRead read; + OrthancPluginStorageRemove remove; + OrthancPluginFree free; + } _OrthancPluginRegisterStorageArea; + + /** + * @brief Register a custom storage area. + * + * This function registers a custom storage area, to replace the + * built-in way Orthanc stores its files on the filesystem. This + * function must be called during the initialization of the plugin, + * i.e. inside the OrthancPluginInitialize() public function. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param create The callback function to store a file on the custom storage area. + * @param read The callback function to read a file from the custom storage area. + * @param remove The callback function to remove a file from the custom storage area. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( + OrthancPluginContext* context, + OrthancPluginStorageCreate create, + OrthancPluginStorageRead read, + OrthancPluginStorageRemove remove) + { + _OrthancPluginRegisterStorageArea params; + params.create = create; + params.read = read; + params.remove = remove; + +#ifdef __cplusplus + params.free = ::free; +#else + params.free = free; +#endif + + context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); + } + + + + /** + * @brief Return the path to the Orthanc executable. + * + * This function returns the path to the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the directory containing the Orthanc. + * + * This function returns the path to the directory containing the Orthanc executable. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the path to the configuration file(s). + * + * This function returns the path to the configuration file(s) that + * was specified when starting Orthanc. Since version 0.9.1, this + * path can refer to a folder that stores a set of configuration + * files. This function is deprecated in favor of + * OrthancPluginGetConfiguration(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the path. This string must be freed by + * OrthancPluginFreeString(). + * @see OrthancPluginGetConfiguration() + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginOnChangeCallback callback; + } _OrthancPluginOnChangeCallback; + + /** + * @brief Register a callback to monitor changes. + * + * This function registers a callback function that is called + * whenever a change happens to some DICOM resource. + * + * @warning If your change callback has to call the REST API of + * Orthanc, you should make these calls in a separate thread (with + * the events passing through a message queue). Otherwise, this + * could result in deadlocks in the presence of other plugins or Lua + * script. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback function. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( + OrthancPluginContext* context, + OrthancPluginOnChangeCallback callback) + { + _OrthancPluginOnChangeCallback params; + params.callback = callback; + + context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); + } + + + + typedef struct + { + const char* plugin; + _OrthancPluginProperty property; + const char* value; + } _OrthancPluginSetPluginProperty; + + + /** + * @brief Set the URI where the plugin provides its Web interface. + * + * For plugins that come with a Web interface, this function + * declares the entry path where to find this interface. This + * information is notably used in the "Plugins" page of Orthanc + * Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param uri The root URI for this plugin. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( + OrthancPluginContext* context, + const char* uri) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_RootUri; + params.value = uri; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Set a description for this plugin. + * + * Set a description for this plugin. It is displayed in the + * "Plugins" page of Orthanc Explorer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param description The description. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( + OrthancPluginContext* context, + const char* description) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_Description; + params.value = description; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + /** + * @brief Extend the JavaScript code of Orthanc Explorer. + * + * Add JavaScript code to customize the default behavior of Orthanc + * Explorer. This can for instance be used to add new buttons. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param javascript The custom JavaScript code. + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( + OrthancPluginContext* context, + const char* javascript) + { + _OrthancPluginSetPluginProperty params; + params.plugin = OrthancPluginGetName(); + params.property = _OrthancPluginProperty_OrthancExplorer; + params.value = javascript; + + context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); + } + + + typedef struct + { + char** result; + int32_t property; + const char* value; + } _OrthancPluginGlobalProperty; + + + /** + * @brief Get the value of a global property. + * + * Get the value of a global property that is stored in the Orthanc database. Global + * properties whose index is below 1024 are reserved by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param defaultValue The value to return, if the global property is unset. + * @return The value of the global property, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* defaultValue) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = property; + params.value = defaultValue; + + if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Set the value of a global property. + * + * Set the value of a global property into the Orthanc + * database. Setting a global property can be used by plugins to + * save their internal parameters. Plugins are only allowed to set + * properties whose index are above or equal to 1024 (properties + * below 1024 are read-only and reserved by Orthanc). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param property The global property of interest. + * @param value The value to be set in the global property. + * @return 0 if success, or the error code if failure. + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( + OrthancPluginContext* context, + int32_t property, + const char* value) + { + _OrthancPluginGlobalProperty params; + params.result = NULL; + params.property = property; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); + } + + + + typedef struct + { + int32_t *resultInt32; + uint32_t *resultUint32; + int64_t *resultInt64; + uint64_t *resultUint64; + } _OrthancPluginReturnSingleValue; + + /** + * @brief Get the number of command-line arguments. + * + * Retrieve the number of command-line arguments that were used to launch Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of arguments. + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Get the value of a command-line argument. + * + * Get the value of one of the command-line arguments that were used + * to launch Orthanc. The number of available arguments can be + * retrieved by OrthancPluginGetCommandLineArgumentsCount(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param argument The index of the argument. + * @return The value of the argument, or NULL in the case of an error. This + * string must be freed by OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( + OrthancPluginContext* context, + uint32_t argument) + { + char* result; + + _OrthancPluginGlobalProperty params; + params.result = &result; + params.property = (int32_t) argument; + params.value = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the expected version of the database schema. + * + * Retrieve the expected version of the database schema. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The version. + * @ingroup Callbacks + * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + /** + * @brief Return the content of the configuration file(s). + * + * This function returns the content of the configuration that is + * used by Orthanc, formatted as a JSON string. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL in the case of an error, or a newly allocated string + * containing the configuration. This string must be freed by + * OrthancPluginFreeString(). + **/ + ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) + { + char* result; + + _OrthancPluginRetrieveDynamicString params; + params.result = &result; + params.argument = NULL; + + if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* subType; + const char* contentType; + } _OrthancPluginStartMultipartAnswer; + + /** + * @brief Start an HTTP multipart answer. + * + * Initiates a HTTP multipart answer, as the result of a REST request. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param subType The sub-type of the multipart answer ("mixed" or "related"). + * @param contentType The MIME type of the items in the multipart answer. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* subType, + const char* contentType) + { + _OrthancPluginStartMultipartAnswer params; + params.output = output; + params.subType = subType; + params.contentType = contentType; + return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); + } + + + /** + * @brief Send an item as a part of some HTTP multipart answer. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem2() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize) + { + _OrthancPluginAnswerBuffer params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.mimeType = NULL; + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const void* source; + uint32_t size; + OrthancPluginCompressionType compression; + uint8_t uncompress; + } _OrthancPluginBufferCompression; + + + /** + * @brief Compress or decompress a buffer. + * + * This function compresses or decompresses a buffer, using the + * version of the zlib library that is used by the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param source The source buffer. + * @param size The size in bytes of the source buffer. + * @param compression The compression algorithm. + * @param uncompress If set to "0", the buffer must be compressed. + * If set to "1", the buffer must be uncompressed. + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const void* source, + uint32_t size, + OrthancPluginCompressionType compression, + uint8_t uncompress) + { + _OrthancPluginBufferCompression params; + params.target = target; + params.source = source; + params.size = size; + params.compression = compression; + params.uncompress = uncompress; + + return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* path; + } _OrthancPluginReadFile; + + /** + * @brief Read a file. + * + * Read the content of a file on the filesystem, and returns it into + * a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param path The path of the file to be read. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* path) + { + _OrthancPluginReadFile params; + params.target = target; + params.path = path; + return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); + } + + + + typedef struct + { + const char* path; + const void* data; + uint32_t size; + } _OrthancPluginWriteFile; + + /** + * @brief Write a file. + * + * Write the content of a memory buffer to the filesystem. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param path The path of the file to be written. + * @param data The content of the memory buffer. + * @param size The size of the memory buffer. + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( + OrthancPluginContext* context, + const char* path, + const void* data, + uint32_t size) + { + _OrthancPluginWriteFile params; + params.path = path; + params.data = data; + params.size = size; + return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); + } + + + + typedef struct + { + const char** target; + OrthancPluginErrorCode error; + } _OrthancPluginGetErrorDescription; + + /** + * @brief Get the description of a given error code. + * + * This function returns the description of a given error code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param error The error code of interest. + * @return The error description. This is a statically-allocated + * string, do not free it. + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( + OrthancPluginContext* context, + OrthancPluginErrorCode error) + { + const char* result = NULL; + + _OrthancPluginGetErrorDescription params; + params.target = &result; + params.error = error; + + if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || + result == NULL) + { + return "Unknown error code"; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + uint16_t status; + const char* body; + uint32_t bodySize; + } _OrthancPluginSendHttpStatus; + + /** + * @brief Send a HTTP status, with a custom body. + * + * This function answers to a HTTP request by sending a HTTP status + * code (such as "400 - Bad Request"), together with a body + * describing the error. The body will only be returned if the + * configuration option "HttpDescribeErrors" of Orthanc is set to "true". + * + * Note that: + * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). + * - Redirections (status 301) must use ::OrthancPluginRedirect(). + * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). + * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param status The HTTP status code to be sent. + * @param body The body of the answer. + * @param bodySize The size of the body. + * @see OrthancPluginSendHttpStatusCode() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + uint16_t status, + const char* body, + uint32_t bodySize) + { + _OrthancPluginSendHttpStatus params; + params.output = output; + params.status = status; + params.body = body; + params.bodySize = bodySize; + context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); + } + + + + typedef struct + { + const OrthancPluginImage* image; + uint32_t* resultUint32; + OrthancPluginPixelFormat* resultPixelFormat; + void** resultBuffer; + } _OrthancPluginGetImageInfo; + + + /** + * @brief Return the pixel format of an image. + * + * This function returns the type of memory layout for the pixels of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pixel format. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + OrthancPluginPixelFormat target; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultPixelFormat = ⌖ + + if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return OrthancPluginPixelFormat_Unknown; + } + else + { + return (OrthancPluginPixelFormat) target; + } + } + + + + /** + * @brief Return the width of an image. + * + * This function returns the width of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The width. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t width; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &width; + + if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return width; + } + } + + + + /** + * @brief Return the height of an image. + * + * This function returns the height of the given image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The height. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t height; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &height; + + if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return height; + } + } + + + + /** + * @brief Return the pitch of an image. + * + * This function returns the pitch of the given image. The pitch is + * defined as the number of bytes between 2 successive lines of the + * image in the memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pitch. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + uint32_t pitch; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.resultUint32 = &pitch; + + if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return pitch; + } + } + + + + /** + * @brief Return a pointer to the content of an image. + * + * This function returns a pointer to the memory buffer that + * contains the pixels of the image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image of interest. + * @return The pointer. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( + OrthancPluginContext* context, + const OrthancPluginImage* image) + { + void* target = NULL; + + _OrthancPluginGetImageInfo params; + memset(¶ms, 0, sizeof(params)); + params.resultBuffer = ⌖ + params.image = image; + + if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + typedef struct + { + OrthancPluginImage** target; + const void* data; + uint32_t size; + OrthancPluginImageFormat format; + } _OrthancPluginUncompressImage; + + + /** + * @brief Decode a compressed image. + * + * This function decodes a compressed image from a memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param data Pointer to a memory buffer containing the compressed image. + * @param size Size of the memory buffer containing the compressed image. + * @param format The file format of the compressed image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( + OrthancPluginContext* context, + const void* data, + uint32_t size, + OrthancPluginImageFormat format) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginUncompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.data = data; + params.size = size; + params.format = format; + + if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + + typedef struct + { + OrthancPluginImage* image; + } _OrthancPluginFreeImage; + + /** + * @brief Free an image. + * + * This function frees an image that was decoded with OrthancPluginUncompressImage(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( + OrthancPluginContext* context, + OrthancPluginImage* image) + { + _OrthancPluginFreeImage params; + params.image = image; + + context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginImageFormat imageFormat; + OrthancPluginPixelFormat pixelFormat; + uint32_t width; + uint32_t height; + uint32_t pitch; + const void* buffer; + uint8_t quality; + } _OrthancPluginCompressImage; + + + /** + * @brief Encode a PNG image. + * + * This function compresses the given memory buffer containing an + * image using the PNG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginCompressAndAnswerPngImage() + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Png; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = 0; /* Unused for PNG */ + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + /** + * @brief Encode a JPEG image. + * + * This function compresses the given memory buffer containing an + * image using the JPEG specification, and stores the result of the + * compression into a newly allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @return 0 if success, or the error code if failure. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressImage params; + memset(¶ms, 0, sizeof(params)); + params.target = target; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + + return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); + } + + + + /** + * @brief Answer to a REST request with a JPEG image. + * + * This function answers to a REST request with a JPEG image. The + * parameters of this function describe a memory buffer that + * contains an uncompressed image. The image will be automatically compressed + * as a JPEG image by the core system of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param format The memory layout of the uncompressed image. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer containing the uncompressed image. + * @param quality The quality of the JPEG encoding, between 1 (worst + * quality, best compression) and 100 (best quality, worst + * compression). + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + const void* buffer, + uint8_t quality) + { + _OrthancPluginCompressAndAnswerImage params; + params.output = output; + params.imageFormat = OrthancPluginImageFormat_Jpeg; + params.pixelFormat = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + params.quality = quality; + context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); + } + + + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginHttpMethod method; + const char* url; + const char* username; + const char* password; + const char* body; + uint32_t bodySize; + } _OrthancPluginCallHttpClient; + + + /** + * @brief Issue a HTTP GET call. + * + * Make a HTTP GET call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiGet() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Get; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP POST call. + * + * Make a HTTP POST call to the given URL. The result to the query + * is stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPost() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Post; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP PUT call. + * + * Make a HTTP PUT call to the given URL. The result to the query is + * stored into a newly allocated memory buffer. Favor + * OrthancPluginRestApiPut() if calling the built-in REST API of the + * Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param url The URL of interest. + * @param body The content of the body of the request. + * @param bodySize The size of the body of the request. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* url, + const char* body, + uint32_t bodySize, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.target = target; + params.method = OrthancPluginHttpMethod_Put; + params.url = url; + params.body = body; + params.bodySize = bodySize; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + /** + * @brief Issue a HTTP DELETE call. + * + * Make a HTTP DELETE call to the given URL. Favor + * OrthancPluginRestApiDelete() if calling the built-in REST API of + * the Orthanc instance that hosts this plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param url The URL of interest. + * @param username The username (can be NULL if no password protection). + * @param password The password (can be NULL if no password protection). + * @return 0 if success, or the error code if failure. + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( + OrthancPluginContext* context, + const char* url, + const char* username, + const char* password) + { + _OrthancPluginCallHttpClient params; + memset(¶ms, 0, sizeof(params)); + + params.method = OrthancPluginHttpMethod_Delete; + params.url = url; + params.username = username; + params.password = password; + + return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + const OrthancPluginImage* source; + OrthancPluginPixelFormat targetFormat; + } _OrthancPluginConvertPixelFormat; + + + /** + * @brief Change the pixel format of an image. + * + * This function creates a new image, changing the memory layout of the pixels. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param source The source image. + * @param targetFormat The target pixel format. + * @return The resulting image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( + OrthancPluginContext* context, + const OrthancPluginImage* source, + OrthancPluginPixelFormat targetFormat) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginConvertPixelFormat params; + params.target = ⌖ + params.source = source; + params.targetFormat = targetFormat; + + if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Return the number of available fonts. + * + * This function returns the number of fonts that are built in the + * Orthanc core. These fonts can be used to draw texts on images + * through OrthancPluginDrawText(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return The number of fonts. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( + OrthancPluginContext* context) + { + uint32_t count = 0; + + _OrthancPluginReturnSingleValue params; + memset(¶ms, 0, sizeof(params)); + params.resultUint32 = &count; + + if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return count; + } + } + + + + + typedef struct + { + uint32_t fontIndex; /* in */ + const char** name; /* out */ + uint32_t* size; /* out */ + } _OrthancPluginGetFontInfo; + + /** + * @brief Return the name of a font. + * + * This function returns the name of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font name. This is a statically-allocated string, do not free it. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( + OrthancPluginContext* context, + uint32_t fontIndex) + { + const char* result = NULL; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.name = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Return the size of a font. + * + * This function returns the size of a font that is built in the Orthanc core. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @return The font size. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( + OrthancPluginContext* context, + uint32_t fontIndex) + { + uint32_t result; + + _OrthancPluginGetFontInfo params; + memset(¶ms, 0, sizeof(params)); + params.size = &result; + params.fontIndex = fontIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) + { + return 0; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginImage* image; + uint32_t fontIndex; + const char* utf8Text; + int32_t x; + int32_t y; + uint8_t r; + uint8_t g; + uint8_t b; + } _OrthancPluginDrawText; + + + /** + * @brief Draw text on an image. + * + * This function draws some text on some image. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param image The image upon which to draw the text. + * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). + * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. + * @param x The X position of the text over the image. + * @param y The Y position of the text over the image. + * @param r The value of the red color channel of the text. + * @param g The value of the green color channel of the text. + * @param b The value of the blue color channel of the text. + * @return 0 if success, other value if error. + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( + OrthancPluginContext* context, + OrthancPluginImage* image, + uint32_t fontIndex, + const char* utf8Text, + int32_t x, + int32_t y, + uint8_t r, + uint8_t g, + uint8_t b) + { + _OrthancPluginDrawText params; + memset(¶ms, 0, sizeof(params)); + params.image = image; + params.fontIndex = fontIndex; + params.utf8Text = utf8Text; + params.x = x; + params.y = y; + params.r = r; + params.g = g; + params.b = b; + + return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); + } + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + const void* content; + uint64_t size; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaCreate; + + + /** + * @brief Create a file inside the storage area. + * + * This function creates a new file inside the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be created. + * @param content The content to store in the newly created file. + * @param size The size of the content. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + const void* content, + uint64_t size, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaCreate params; + params.storageArea = storageArea; + params.uuid = uuid; + params.content = content; + params.size = size; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRead; + + + /** + * @brief Read a file from the storage area. + * + * This function reads the content of a given file from the storage + * area that is currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be read. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRead params; + params.target = target; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); + } + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + const char* uuid; + OrthancPluginContentType type; + } _OrthancPluginStorageAreaRemove; + + /** + * @brief Remove a file from the storage area. + * + * This function removes a given file from the storage area that is + * currently used by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param uuid The identifier of the file to be removed. + * @param type The type of the file content. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + const char* uuid, + OrthancPluginContentType type) + { + _OrthancPluginStorageAreaRemove params; + params.storageArea = storageArea; + params.uuid = uuid; + params.type = type; + + return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); + } + + + + typedef struct + { + OrthancPluginErrorCode* target; + int32_t code; + uint16_t httpStatus; + const char* message; + } _OrthancPluginRegisterErrorCode; + + /** + * @brief Declare a custom error code for this plugin. + * + * This function declares a custom error code that can be generated + * by this plugin. This declaration is used to enrich the body of + * the HTTP answer in the case of an error, and to set the proper + * HTTP status code. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param code The error code that is internal to this plugin. + * @param httpStatus The HTTP status corresponding to this error. + * @param message The description of the error. + * @return The error code that has been assigned inside the Orthanc core. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( + OrthancPluginContext* context, + int32_t code, + uint16_t httpStatus, + const char* message) + { + OrthancPluginErrorCode target; + + _OrthancPluginRegisterErrorCode params; + params.target = ⌖ + params.code = code; + params.httpStatus = httpStatus; + params.message = message; + + if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) + { + return target; + } + else + { + /* There was an error while assigned the error. Use a generic code. */ + return OrthancPluginErrorCode_Plugin; + } + } + + + + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + } _OrthancPluginRegisterDictionaryTag; + + /** + * @brief Register a new tag into the DICOM dictionary. + * + * This function declares a new tag in the dictionary of DICOM tags + * that are known to Orthanc. This function should be used in the + * OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity) + { + _OrthancPluginRegisterDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + + return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); + } + + + + + typedef struct + { + OrthancPluginStorageArea* storageArea; + OrthancPluginResourceType level; + } _OrthancPluginReconstructMainDicomTags; + + /** + * @brief Reconstruct the main DICOM tags. + * + * This function requests the Orthanc core to reconstruct the main + * DICOM tags of all the resources of the given type. This function + * can only be used as a part of the upgrade of a custom database + * back-end + * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A + * database transaction will be automatically setup. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param storageArea The storage area. + * @param level The type of the resources of interest. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( + OrthancPluginContext* context, + OrthancPluginStorageArea* storageArea, + OrthancPluginResourceType level) + { + _OrthancPluginReconstructMainDicomTags params; + params.level = level; + params.storageArea = storageArea; + + return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); + } + + + 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 Flags governing the output. + * @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 Flags governing the output. + * @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; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* uri; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + int32_t afterPlugins; + } _OrthancPluginRestApiGet2; + + /** + * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. + * + * Make a GET call to the Orthanc REST API with extended + * parameters. The result to the query is stored into a newly + * allocated memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param uri The URI in the built-in Orthanc API. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @param afterPlugins If 0, the built-in API of Orthanc is used. + * If 1, the API is tainted by the plugins. + * @return 0 if success, or the error code if failure. + * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins + * @ingroup Orthanc + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* uri, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + int32_t afterPlugins) + { + _OrthancPluginRestApiGet2 params; + params.target = target; + params.uri = uri; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + params.afterPlugins = afterPlugins; + + return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistCallback callback; + } _OrthancPluginWorklistCallback; + + /** + * @brief Register a callback to handle modality worklists requests. + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( + OrthancPluginContext* context, + OrthancPluginWorklistCallback callback) + { + _OrthancPluginWorklistCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginWorklistAnswers* answers; + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + } _OrthancPluginWorklistAnswersOperation; + + /** + * @brief Add one answer to some modality worklist request. + * + * This function adds one worklist (encoded as a DICOM file) to the + * set of answers corresponding to some C-Find SCP request against + * modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = query; + params.dicom = dicom; + params.size = size; + + return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); + } + + + /** + * @brief Mark the set of worklist answers as incomplete. + * + * This function marks as incomplete the set of answers + * corresponding to some C-Find SCP request against modality + * worklists. This must be used if canceling the handling of a + * request when too many answers are to be returned. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answers The set of answers. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( + OrthancPluginContext* context, + OrthancPluginWorklistAnswers* answers) + { + _OrthancPluginWorklistAnswersOperation params; + params.answers = answers; + params.query = NULL; + params.dicom = NULL; + params.size = 0; + + return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); + } + + + typedef struct + { + const OrthancPluginWorklistQuery* query; + const void* dicom; + uint32_t size; + int32_t* isMatch; + OrthancPluginMemoryBuffer* target; + } _OrthancPluginWorklistQueryOperation; + + /** + * @brief Test whether a worklist matches the query. + * + * This function checks whether one worklist (encoded as a DICOM + * file) matches the C-Find SCP query against modality + * worklists. This function must be called before adding the + * worklist as an answer through OrthancPluginWorklistAddAnswer(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param query The worklist query, as received by the callback. + * @param dicom The worklist to answer, encoded as a DICOM file. + * @param size The size of the DICOM file. + * @return 1 if the worklist matches the query, 0 otherwise. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( + OrthancPluginContext* context, + const OrthancPluginWorklistQuery* query, + const void* dicom, + uint32_t size) + { + int32_t isMatch = 0; + + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = dicom; + params.size = size; + params.isMatch = &isMatch; + params.target = NULL; + + if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) + { + return isMatch; + } + else + { + /* Error: Assume non-match */ + return 0; + } + } + + + /** + * @brief Retrieve the worklist query as a DICOM file. + * + * This function retrieves the DICOM file that underlies a C-Find + * SCP query against modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param query The worklist query, as received by the callback. + * @return 0 if success, other value if error. + * @ingroup Worklists + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const OrthancPluginWorklistQuery* query) + { + _OrthancPluginWorklistQueryOperation params; + params.query = query; + params.dicom = NULL; + params.size = 0; + params.isMatch = NULL; + params.target = target; + + return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); + } + + + /** + * @brief Get the origin of a DICOM file. + * + * This function returns the origin of a DICOM instance that has been received by Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param instance The instance of interest. + * @return The origin of the instance. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( + OrthancPluginContext* context, + OrthancPluginDicomInstance* instance) + { + OrthancPluginInstanceOrigin origin; + + _OrthancPluginAccessDicomInstance params; + memset(¶ms, 0, sizeof(params)); + params.resultOrigin = &origin; + params.instance = instance; + + if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return OrthancPluginInstanceOrigin_Unknown; + } + else + { + return origin; + } + } + + + typedef struct + { + OrthancPluginMemoryBuffer* target; + const char* json; + const OrthancPluginImage* pixelData; + OrthancPluginCreateDicomFlags flags; + } _OrthancPluginCreateDicom; + + /** + * @brief Create a DICOM instance from a JSON string and an image. + * + * This function takes as input a string containing a JSON file + * describing the content of a DICOM instance. As an output, it + * writes the corresponding DICOM instance to a newly allocated + * memory buffer. Additionally, an image to be encoded within the + * DICOM instance can also be provided. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param json The input JSON file. + * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. + * @param flags Flags governing the output. + * @return 0 if success, other value if error. + * @ingroup Toolbox + * @see OrthancPluginDicomBufferToJson + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* target, + const char* json, + const OrthancPluginImage* pixelData, + OrthancPluginCreateDicomFlags flags) + { + _OrthancPluginCreateDicom params; + params.target = target; + params.json = json; + params.pixelData = pixelData; + params.flags = flags; + + return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); + } + + + typedef struct + { + OrthancPluginDecodeImageCallback callback; + } _OrthancPluginDecodeImageCallback; + + /** + * @brief Register a callback to handle the decoding of DICOM images. + * + * This function registers a custom callback to the decoding of + * DICOM images, replacing the built-in decoder of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup Callbacks + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( + OrthancPluginContext* context, + OrthancPluginDecodeImageCallback callback) + { + _OrthancPluginDecodeImageCallback params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); + } + + + + typedef struct + { + OrthancPluginImage** target; + OrthancPluginPixelFormat format; + uint32_t width; + uint32_t height; + uint32_t pitch; + void* buffer; + const void* constBuffer; + uint32_t bufferSize; + uint32_t frameIndex; + } _OrthancPluginCreateImage; + + + /** + * @brief Create an image. + * + * This function creates an image of given size and format. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + + if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Create an image pointing to a memory buffer. + * + * This function creates an image whose content points to a memory + * buffer managed by the plugin. Note that the buffer is directly + * accessed, no memory is allocated and no data is copied. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param format The format of the pixels. + * @param width The width of the image. + * @param height The height of the image. + * @param pitch The pitch of the image (i.e. the number of bytes + * between 2 successive lines of the image in the memory buffer). + * @param buffer The memory buffer. + * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( + OrthancPluginContext* context, + OrthancPluginPixelFormat format, + uint32_t width, + uint32_t height, + uint32_t pitch, + void* buffer) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.format = format; + params.width = width; + params.height = height; + params.pitch = pitch; + params.buffer = buffer; + + if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + /** + * @brief Decode one frame from a DICOM instance. + * + * This function decodes one frame of a DICOM image that is stored + * in a memory buffer. This function will give the same result as + * OrthancPluginUncompressImage() for single-frame DICOM images. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer Pointer to a memory buffer containing the DICOM image. + * @param bufferSize Size of the memory buffer containing the DICOM image. + * @param frameIndex The index of the frame of interest in a multi-frame image. + * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). + * @ingroup Images + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( + OrthancPluginContext* context, + const void* buffer, + uint32_t bufferSize, + uint32_t frameIndex) + { + OrthancPluginImage* target = NULL; + + _OrthancPluginCreateImage params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.constBuffer = buffer; + params.bufferSize = bufferSize; + params.frameIndex = frameIndex; + + if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + char** result; + const void* buffer; + uint32_t size; + } _OrthancPluginComputeHash; + + /** + * @brief Compute an MD5 hash. + * + * This functions computes the MD5 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Compute a SHA-1 hash. + * + * This functions computes the SHA-1 cryptographic hash of the given memory buffer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param buffer The source memory buffer. + * @param size The size in bytes of the source buffer. + * @return The NULL value in case of error, or a string containing the cryptographic hash. + * This string must be freed by OrthancPluginFreeString(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( + OrthancPluginContext* context, + const void* buffer, + uint32_t size) + { + char* result; + + _OrthancPluginComputeHash params; + params.result = &result; + params.buffer = buffer; + params.size = size; + + if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + + + typedef struct + { + OrthancPluginRestOutput* output; + const char* answer; + uint32_t answerSize; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + } _OrthancPluginSendMultipartItem2; + + /** + * @brief Send an item as a part of some HTTP multipart answer, with custom headers. + * + * This function sends an item as a part of some HTTP multipart + * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to + * OrthancPluginSendMultipartItem(), this function will set HTTP header associated + * with the item. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param output The HTTP connection to the client application. + * @param answer Pointer to the memory buffer containing the item. + * @param answerSize Number of bytes of the item. + * @param headersCount The number of HTTP headers. + * @param headersKeys Array containing the keys of the HTTP headers. + * @param headersValues Array containing the values of the HTTP headers. + * @return 0 if success, or the error code if failure (this notably happens + * if the connection is closed by the client). + * @see OrthancPluginSendMultipartItem() + * @ingroup REST + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( + OrthancPluginContext* context, + OrthancPluginRestOutput* output, + const char* answer, + uint32_t answerSize, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues) + { + _OrthancPluginSendMultipartItem2 params; + params.output = output; + params.answer = answer; + params.answerSize = answerSize; + params.headersCount = headersCount; + params.headersKeys = headersKeys; + params.headersValues = headersValues; + + return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); + } + + +#ifdef __cplusplus +} +#endif + + +/** @} */ + diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/Resources/Styles/_button.scss --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_button.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,205 @@ +// clean icon buttons +.wvButton { + // Remove default styles. Take care - this class may either be used + // with or + + + + +
+
+ +
+
+ + +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+ +
+ +

For patients, teachers and researchers.

+
+
+ +
+
+
+
+ + + + + {{ study.tags['0008,1030'] }} +
+ {{ study.tags['0008,0020'] }} +
+ +
+
    +
  • +
    +
    + +
    + + + + + + +
    + + + + +
    {{ series[seriesIndex].length }}
    +
    +
    + +
    +

    + [{{ series[seriesIndex].tags['0008,0060'] }}] + {{ series[seriesIndex].tags['0008,103e'] }} +

    +
    +
  • +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+
+ + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/WebAssembly/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/CMakeLists.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,129 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(OrthancStone) + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") + +set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) + +include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) + +SET(ENABLE_DCMTK ON) +SET(ENABLE_DCMTK_NETWORKING OFF) +SET(ENABLE_DCMTK_TRANSCODING OFF) +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_WASM ON) +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) + +if (CMAKE_BUILD_TYPE MATCHES Debug) + # specific flags go here +elseif (CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) + # specific flags go here +elseif (CMAKE_BUILD_TYPE MATCHES Release) + # specific flags go here +else() + message(FATAL_ERROR "CMAKE_BUILD_TYPE must match either Debug, RelWithDebInfo or Release" ) +endif() + +################################################################################ + +project(StoneWebViewer) + + +# Create the wrapper to call C++ from JavaScript +# --------------------------------------------------------------- + +set(LIBCLANG "libclang-4.0.so.1" CACHE PATH "Version of clang to generate the code model") +set(STONE_WRAPPER ${CMAKE_CURRENT_BINARY_DIR}/stone.js) + +add_custom_command( + COMMAND + ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py --libclang=${LIBCLANG} ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp > ${STONE_WRAPPER} + DEPENDS + ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp + ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py + OUTPUT + ${STONE_WRAPPER} + ) + +add_custom_target(StoneWrapper + DEPENDS + ${STONE_WRAPPER} + ) + + +# Define the WASM module +# --------------------------------------------------------------- + +add_executable(StoneWebViewer + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + StoneWebViewer.cpp + ) + +# Make sure to have the wrapper generated +add_dependencies(StoneWebViewer StoneWrapper) + + +# Declare installation files for the module +# --------------------------------------------------------------- + +install( + TARGETS StoneWebViewer + RUNTIME DESTINATION . + ) + + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.js +# (the generated JS loader for the WASM module) is handled by the `install` +# section above: it is considered to be the binary output of +# the linker. +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.wasm + ${CMAKE_SOURCE_DIR}/../WebApplication/app.css + ${CMAKE_SOURCE_DIR}/../WebApplication/app.js + ${CMAKE_SOURCE_DIR}/../WebApplication/index.html + ${STONE_WRAPPER} + DESTINATION . + ) + +install( + FILES + ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x1.png + ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x2.png + ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x1.png + ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x2.png + ${CMAKE_SOURCE_DIR}/../WebApplication/img/loading.gif + DESTINATION img + ) diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/WebAssembly/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/NOTES.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,33 @@ + +Building WebAssembly samples using Docker +========================================= + +The script "./docker-build.sh" can be used to quickly build the +WebAssembly samples on any GNU/Linux distribution equipped with +Docker. This avoids newcomers to install Emscripten and learn the +CMake options. Just type: + +$ ./docker-build.sh Release + +After successful build, the binaries will be installed in the +following folder (i.e. in the folder "wasm-binaries" at the root of +the source distribution): + +$ ls -l ../../wasm-binaries + + +NB: The source code of the Docker build environment can be found at +the following location: +https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder + + +Native compilation (without Docker) +=================================== + +Install Emscripten: +https://emscripten.org/docs/getting_started/downloads.html + +Then, if the installation path is "~/Downloads/emsdk/": + +# source ~/Downloads/emsdk/emsdk_env.sh +# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +# Stone of Orthanc +# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics +# Department, University Hospital of Liege, Belgium +# Copyright (C) 2017-2020 Osimis S.A., 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 . + + +# Ubuntu 20.04: +# sudo apt-get install python-clang-6.0 +# ./ParseWebAssemblyExports.py --libclang=libclang-6.0.so.1 ./Test.cpp + +# Ubuntu 18.04: +# sudo apt-get install python-clang-4.0 +# ./ParseWebAssemblyExports.py --libclang=libclang-4.0.so.1 ./Test.cpp + +# Ubuntu 14.04: +# ./ParseWebAssemblyExports.py --libclang=libclang-3.6.so.1 ./Test.cpp + + +import sys +import clang.cindex +import pystache +import argparse + +## +## Parse the command-line arguments +## + +parser = argparse.ArgumentParser(description = 'Parse WebAssembly C++ source file, and create a basic JavaScript wrapper.') +parser.add_argument('--libclang', + default = '', + help = 'manually provides the path to the libclang shared library') +parser.add_argument('source', + help = 'Input C++ file') + +args = parser.parse_args() + + + +if len(args.libclang) != 0: + clang.cindex.Config.set_library_file(args.libclang) + +index = clang.cindex.Index.create() + +# PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of +# undefined types, which prevents compilation of functions +tu = index.parse(args.source, + [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))' ], + options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) + + + +TEMPLATE = ''' +const Stone = function() { + {{#enumerations}} + this.{{name}} = { + {{#values}} + {{name}}: {{value}}{{separator}} + {{/values}} + }; + + {{/enumerations}} + {{#functions}} + this._{{name}} = undefined; + {{/functions}} +}; + +Stone.prototype.Setup = function(Module) { + {{#functions}} + this._{{name}} = Module.cwrap('{{name}}', {{{returnType}}}, [ {{#args}}{{{type}}}{{^last}}, {{/last}}{{/args}} ]); + {{/functions}} +}; + +{{#functions}} +Stone.prototype.{{name}} = function({{#args}}{{name}}{{^last}}, {{/last}}{{/args}}) { + {{#hasReturn}}return {{/hasReturn}}this._{{name}}({{#args}}{{name}}{{^last}}, {{/last}}{{/args}}); +}; + +{{/functions}} +var stone = new Stone(); +''' + + + + +# WARNING: Undefined types are mapped as "int" + +functions = [] +enumerations = [] + +def ToUpperCase(source): + target = source[0] + for c in source[1:]: + if c.isupper(): + target += '_' + target += c.upper() + return target + + + +def IsExported(node): + for child in node.get_children(): + if (child.kind == clang.cindex.CursorKind.ANNOTATE_ATTR and + child.displayname == 'WebAssembly'): + return True + + return False + + +def Explore(node): + if node.kind == clang.cindex.CursorKind.ENUM_DECL: + if IsExported(node): + name = node.spelling + values = [] + for value in node.get_children(): + if (value.spelling.startswith(name + '_')): + s = value.spelling[len(name) + 1:] + if len(values) > 0: + values[-1]['separator'] = ',' + values.append({ + 'name' : ToUpperCase(s), + 'value' : value.enum_value + }) + + enumerations.append({ + 'name' : name, + 'values' : values + }) + + if node.kind == clang.cindex.CursorKind.FUNCTION_DECL: + if IsExported(node): + f = { + 'name' : node.spelling, + 'args' : [], + } + + returnType = node.result_type.spelling + if returnType == 'void': + f['hasReturn'] = False + f['returnType'] = "null" + elif returnType == 'const char *': + f['hasReturn'] = True + f['returnType'] = "'string'" + elif returnType in [ 'int', 'unsigned int' ]: + f['hasReturn'] = True + f['returnType'] = "'int'" + else: + raise Exception('Unknown return type in function "%s()": %s' % (node.spelling, returnType)) + + for child in node.get_children(): + if child.kind == clang.cindex.CursorKind.PARM_DECL: + arg = { + 'name' : child.displayname, + } + + argType = child.type.spelling + if argType == 'int': + arg['type'] = "'int'" + elif argType == 'const char *': + arg['type'] = "'string'" + else: + raise Exception('Unknown type for argument "%s" in function "%s()": %s' % + (child.displayname, node.spelling, argType)) + + f['args'].append(arg) + + if len(f['args']) != 0: + f['args'][-1]['last'] = True + + functions.append(f) + + for child in node.get_children(): + Explore(child) + +Explore(tu.cursor) + + + +print(pystache.render(TEMPLATE, { + 'functions' : functions, + 'enumerations' : enumerations +})) diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,2266 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., 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 . + **/ + + +#include + + +#define DISPATCH_JAVASCRIPT_EVENT(name) \ + EM_ASM( \ + const customEvent = document.createEvent("CustomEvent"); \ + customEvent.initCustomEvent(name, false, false, undefined); \ + window.dispatchEvent(customEvent); \ + ); + + +#define EXTERN_CATCH_EXCEPTIONS \ + catch (Orthanc::OrthancException& e) \ + { \ + LOG(ERROR) << "OrthancException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (OrthancStone::StoneException& e) \ + { \ + LOG(ERROR) << "StoneException: " << e.What(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (std::exception& e) \ + { \ + LOG(ERROR) << "Runtime error: " << e.what(); \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } \ + catch (...) \ + { \ + LOG(ERROR) << "Native exception"; \ + DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ + } + + +#include +#include +#include +#include +#include +#include +#include + +#include "../../OrthancStone/Sources/Loaders/DicomResourcesLoader.h" +#include "../../OrthancStone/Sources/Loaders/SeriesMetadataLoader.h" +#include "../../OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h" +#include "../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h" +#include "../../OrthancStone/Sources/Messages/ObserverBase.h" +#include "../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h" +#include "../../OrthancStone/Sources/Scene2D/ColorTextureSceneLayer.h" +#include "../../OrthancStone/Sources/Scene2D/FloatTextureSceneLayer.h" +#include "../../OrthancStone/Sources/Scene2D/PolylineSceneLayer.h" +#include "../../OrthancStone/Sources/StoneException.h" +#include "../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h" +#include "../../OrthancStone/Sources/Toolbox/GeometryToolbox.h" +#include "../../OrthancStone/Sources/Toolbox/SortedFrames.h" +#include "../../OrthancStone/Sources/Viewport/WebGLViewport.h" + +#include +#include + + +enum EMSCRIPTEN_KEEPALIVE ThumbnailType +{ + ThumbnailType_Image, + ThumbnailType_NoPreview, + ThumbnailType_Pdf, + ThumbnailType_Video, + ThumbnailType_Loading, + ThumbnailType_Unknown +}; + + +enum EMSCRIPTEN_KEEPALIVE DisplayedFrameQuality +{ +DisplayedFrameQuality_None, + DisplayedFrameQuality_Low, + DisplayedFrameQuality_High + }; + + + +static const int PRIORITY_HIGH = -100; +static const int PRIORITY_LOW = 100; +static const int PRIORITY_NORMAL = 0; + +static const unsigned int QUALITY_JPEG = 0; +static const unsigned int QUALITY_FULL = 1; + +class ResourcesLoader : public OrthancStone::ObserverBase +{ +public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalResourcesLoaded() = 0; + + virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) = 0; + + virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) = 0; + }; + +private: + std::unique_ptr observer_; + OrthancStone::DicomSource source_; + size_t pending_; + boost::shared_ptr studies_; + boost::shared_ptr series_; + boost::shared_ptr resourcesLoader_; + boost::shared_ptr thumbnailsLoader_; + boost::shared_ptr metadataLoader_; + + ResourcesLoader(const OrthancStone::DicomSource& source) : + source_(source), + pending_(0), + studies_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID)), + series_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID)) + { + } + + void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) + { + const Orthanc::SingleValueObject& payload = + dynamic_cast&>(message.GetUserPayload()); + + OrthancStone::LoadedDicomResources& dicom = *message.GetResources(); + + LOG(INFO) << "resources loaded: " << dicom.GetSize() + << ", " << Orthanc::EnumerationToString(payload.GetValue()); + + if (payload.GetValue() == Orthanc::ResourceType_Series) + { + for (size_t i = 0; i < dicom.GetSize(); i++) + { + std::string studyInstanceUid, seriesInstanceUid; + if (dicom.GetResource(i).LookupStringValue( + studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + dicom.GetResource(i).LookupStringValue( + seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) + { + thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid); + metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid); + } + } + } + + if (pending_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + pending_ --; + if (pending_ == 0 && + observer_.get() != NULL) + { + observer_->SignalResourcesLoaded(); + } + } + } + + void Handle(const OrthancStone::SeriesThumbnailsLoader::SuccessMessage& message) + { + if (observer_.get() != NULL) + { + observer_->SignalSeriesThumbnailLoaded( + message.GetStudyInstanceUid(), message.GetSeriesInstanceUid()); + } + } + + void Handle(const OrthancStone::SeriesMetadataLoader::SuccessMessage& message) + { + if (observer_.get() != NULL) + { + observer_->SignalSeriesMetadataLoaded( + message.GetStudyInstanceUid(), message.GetSeriesInstanceUid()); + } + } + + void FetchInternal(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + // Firstly, load the study + Orthanc::DicomMap filter; + filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); + + std::set tags; + tags.insert(Orthanc::DICOM_TAG_STUDY_DESCRIPTION); // Necessary for Orthanc DICOMweb plugin + + resourcesLoader_->ScheduleQido( + studies_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Study, filter, tags, + new Orthanc::SingleValueObject(Orthanc::ResourceType_Study)); + + // Secondly, load the series + if (!seriesInstanceUid.empty()) + { + filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); + } + + tags.insert(Orthanc::DICOM_TAG_SERIES_NUMBER); // Necessary for Google Cloud Platform + + resourcesLoader_->ScheduleQido( + series_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Series, filter, tags, + new Orthanc::SingleValueObject(Orthanc::ResourceType_Series)); + + pending_ += 2; + } + +public: + static boost::shared_ptr Create(OrthancStone::ILoadersContext::ILock& lock, + const OrthancStone::DicomSource& source) + { + boost::shared_ptr loader(new ResourcesLoader(source)); + + loader->resourcesLoader_ = OrthancStone::DicomResourcesLoader::Create(lock); + loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(lock, PRIORITY_LOW); + loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(lock); + + loader->Register( + *loader->resourcesLoader_, &ResourcesLoader::Handle); + + loader->Register( + *loader->thumbnailsLoader_, &ResourcesLoader::Handle); + + loader->Register( + *loader->metadataLoader_, &ResourcesLoader::Handle); + + return loader; + } + + void FetchAllStudies() + { + FetchInternal("", ""); + } + + void FetchStudy(const std::string& studyInstanceUid) + { + FetchInternal(studyInstanceUid, ""); + } + + void FetchSeries(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + FetchInternal(studyInstanceUid, seriesInstanceUid); + } + + size_t GetStudiesCount() const + { + return studies_->GetSize(); + } + + size_t GetSeriesCount() const + { + return series_->GetSize(); + } + + void GetStudy(Orthanc::DicomMap& target, + size_t i) + { + target.Assign(studies_->GetResource(i)); + } + + void GetSeries(Orthanc::DicomMap& target, + size_t i) + { + target.Assign(series_->GetResource(i)); + + // Complement with the study-level tags + std::string studyInstanceUid; + if (target.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && + studies_->HasResource(studyInstanceUid)) + { + studies_->MergeResource(target, studyInstanceUid); + } + } + + OrthancStone::SeriesThumbnailType GetSeriesThumbnail(std::string& image, + std::string& mime, + const std::string& seriesInstanceUid) + { + return thumbnailsLoader_->GetSeriesThumbnail(image, mime, seriesInstanceUid); + } + + void FetchSeriesMetadata(int priority, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) + { + metadataLoader_->ScheduleLoadSeries(priority, source_, studyInstanceUid, seriesInstanceUid); + } + + bool IsSeriesComplete(const std::string& seriesInstanceUid) + { + OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); + return accessor.IsComplete(); + } + + bool SortSeriesFrames(OrthancStone::SortedFrames& target, + const std::string& seriesInstanceUid) + { + OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); + + if (accessor.IsComplete()) + { + target.Clear(); + + for (size_t i = 0; i < accessor.GetInstancesCount(); i++) + { + target.AddInstance(accessor.GetInstance(i)); + } + + target.Sort(); + + return true; + } + else + { + return false; + } + } + + void AcquireObserver(IObserver* observer) + { + observer_.reset(observer); + } +}; + + + +class FramesCache : public boost::noncopyable +{ +private: + class CachedImage : public Orthanc::ICacheable + { + private: + std::unique_ptr image_; + unsigned int quality_; + + public: + CachedImage(Orthanc::ImageAccessor* image, + unsigned int quality) : + image_(image), + quality_(quality) + { + assert(image != NULL); + } + + virtual size_t GetMemoryUsage() const + { + assert(image_.get() != NULL); + return (image_->GetBytesPerPixel() * image_->GetPitch() * image_->GetHeight()); + } + + const Orthanc::ImageAccessor& GetImage() const + { + assert(image_.get() != NULL); + return *image_; + } + + unsigned int GetQuality() const + { + return quality_; + } + }; + + + static std::string GetKey(const std::string& sopInstanceUid, + size_t frameIndex) + { + return sopInstanceUid + "|" + boost::lexical_cast(frameIndex); + } + + + Orthanc::MemoryObjectCache cache_; + +public: + FramesCache() + { + SetMaximumSize(100 * 1024 * 1024); // 100 MB + } + + size_t GetMaximumSize() + { + return cache_.GetMaximumSize(); + } + + void SetMaximumSize(size_t size) + { + cache_.SetMaximumSize(size); + } + + /** + * Returns "true" iff the provided image has better quality than the + * previously cached one, or if no cache was previously available. + **/ + bool Acquire(const std::string& sopInstanceUid, + size_t frameIndex, + Orthanc::ImageAccessor* image /* transfer ownership */, + unsigned int quality) + { + std::unique_ptr protection(image); + + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else if (image->GetFormat() != Orthanc::PixelFormat_Float32 && + image->GetFormat() != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + const std::string& key = GetKey(sopInstanceUid, frameIndex); + + bool invalidate = false; + + { + /** + * Access the previous cached entry, with side effect of tagging + * it as the most recently accessed frame (update of LRU recycling) + **/ + Orthanc::MemoryObjectCache::Accessor accessor(cache_, key, false /* unique lock */); + + if (accessor.IsValid()) + { + const CachedImage& previous = dynamic_cast(accessor.GetValue()); + + // There is already a cached image for this frame + if (previous.GetQuality() < quality) + { + // The previously stored image has poorer quality + invalidate = true; + } + else + { + // No update in the quality, don't change the cache + return false; + } + } + else + { + invalidate = false; + } + } + + if (invalidate) + { + cache_.Invalidate(key); + } + + cache_.Acquire(key, new CachedImage(protection.release(), quality)); + return true; + } + + class Accessor : public boost::noncopyable + { + private: + Orthanc::MemoryObjectCache::Accessor accessor_; + + const CachedImage& GetCachedImage() const + { + if (IsValid()) + { + return dynamic_cast(accessor_.GetValue()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + public: + Accessor(FramesCache& that, + const std::string& sopInstanceUid, + size_t frameIndex) : + accessor_(that.cache_, GetKey(sopInstanceUid, frameIndex), false /* shared lock */) + { + } + + bool IsValid() const + { + return accessor_.IsValid(); + } + + const Orthanc::ImageAccessor& GetImage() const + { + return GetCachedImage().GetImage(); + } + + unsigned int GetQuality() const + { + return GetCachedImage().GetQuality(); + } + }; +}; + + + +class SeriesCursor : public boost::noncopyable +{ +public: + enum Action + { + Action_FastPlus, + Action_Plus, + Action_None, + Action_Minus, + Action_FastMinus + }; + +private: + std::vector prefetch_; + int framesCount_; + int currentFrame_; + bool isCircular_; + int fastDelta_; + Action lastAction_; + + int ComputeNextFrame(int currentFrame, + Action action) const + { + if (framesCount_ == 0) + { + assert(currentFrame == 0); + return 0; + } + + int nextFrame = currentFrame; + + switch (action) + { + case Action_FastPlus: + nextFrame += fastDelta_; + break; + + case Action_Plus: + nextFrame += 1; + break; + + case Action_None: + break; + + case Action_Minus: + nextFrame -= 1; + break; + + case Action_FastMinus: + nextFrame -= fastDelta_; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (isCircular_) + { + while (nextFrame < 0) + { + nextFrame += framesCount_; + } + + while (nextFrame >= framesCount_) + { + nextFrame -= framesCount_; + } + } + else + { + if (nextFrame < 0) + { + nextFrame = 0; + } + else if (nextFrame >= framesCount_) + { + nextFrame = framesCount_ - 1; + } + } + + return nextFrame; + } + + void UpdatePrefetch() + { + /** + * This method will order the frames of the series according to + * the number of "actions" (i.e. mouse wheels) that are necessary + * to reach them, starting from the current frame. It is assumed + * that once one action is done, it is more likely that the user + * will do the same action just afterwards. + **/ + + prefetch_.clear(); + + if (framesCount_ == 0) + { + return; + } + + prefetch_.reserve(framesCount_); + + // Breadth-first search using a FIFO. The queue associates a frame + // and the action that is the most likely in this frame + typedef std::list< std::pair > Queue; + + Queue queue; + std::set visited; // Frames that have already been visited + + queue.push_back(std::make_pair(currentFrame_, lastAction_)); + + while (!queue.empty()) + { + int frame = queue.front().first; + Action previousAction = queue.front().second; + queue.pop_front(); + + if (visited.find(frame) == visited.end()) + { + visited.insert(frame); + prefetch_.push_back(frame); + + switch (previousAction) + { + case Action_None: + case Action_Plus: + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); + break; + + case Action_Minus: + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); + break; + + case Action_FastPlus: + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); + break; + + case Action_FastMinus: + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); + queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + } + + assert(prefetch_.size() == framesCount_); + } + + bool CheckFrameIndex(int frame) const + { + return ((framesCount_ == 0 && frame == 0) || + (framesCount_ > 0 && frame >= 0 && frame < framesCount_)); + } + +public: + SeriesCursor(size_t framesCount) : + framesCount_(framesCount), + currentFrame_(framesCount / 2), // Start at the middle frame + isCircular_(false), + lastAction_(Action_None) + { + SetFastDelta(framesCount / 20); + UpdatePrefetch(); + } + + void SetCircular(bool isCircular) + { + isCircular_ = isCircular; + UpdatePrefetch(); + } + + void SetFastDelta(int delta) + { + fastDelta_ = (delta < 0 ? -delta : delta); + + if (fastDelta_ <= 0) + { + fastDelta_ = 1; + } + } + + void SetCurrentIndex(size_t frame) + { + if (frame >= framesCount_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + currentFrame_ = frame; + lastAction_ = Action_None; + UpdatePrefetch(); + } + } + + size_t GetCurrentIndex() const + { + assert(CheckFrameIndex(currentFrame_)); + return static_cast(currentFrame_); + } + + void Apply(Action action) + { + currentFrame_ = ComputeNextFrame(currentFrame_, action); + lastAction_ = action; + UpdatePrefetch(); + } + + size_t GetPrefetchSize() const + { + assert(prefetch_.size() == framesCount_); + return prefetch_.size(); + } + + size_t GetPrefetchFrameIndex(size_t i) const + { + if (i >= prefetch_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(CheckFrameIndex(prefetch_[i])); + return static_cast(prefetch_[i]); + } + } +}; + + + + +class FrameGeometry +{ +private: + bool isValid_; + std::string frameOfReferenceUid_; + OrthancStone::CoordinateSystem3D coordinates_; + double pixelSpacingX_; + double pixelSpacingY_; + OrthancStone::Extent2D extent_; + +public: + FrameGeometry() : + isValid_(false) + { + } + + FrameGeometry(const Orthanc::DicomMap& tags) : + isValid_(false), + coordinates_(tags) + { + if (!tags.LookupStringValue( + frameOfReferenceUid_, Orthanc::DICOM_TAG_FRAME_OF_REFERENCE_UID, false)) + { + frameOfReferenceUid_.clear(); + } + + OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, tags); + + unsigned int rows, columns; + if (tags.HasTag(Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT) && + tags.HasTag(Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) && + tags.ParseUnsignedInteger32(rows, Orthanc::DICOM_TAG_ROWS) && + tags.ParseUnsignedInteger32(columns, Orthanc::DICOM_TAG_COLUMNS)) + { + double ox = -pixelSpacingX_ / 2.0; + double oy = -pixelSpacingY_ / 2.0; + extent_.AddPoint(ox, oy); + extent_.AddPoint(ox + pixelSpacingX_ * static_cast(columns), + oy + pixelSpacingY_ * static_cast(rows)); + + isValid_ = true; + } + } + + bool IsValid() const + { + return isValid_; + } + + const std::string& GetFrameOfReferenceUid() const + { + if (isValid_) + { + return frameOfReferenceUid_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + const OrthancStone::CoordinateSystem3D& GetCoordinates() const + { + if (isValid_) + { + return coordinates_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + double GetPixelSpacingX() const + { + if (isValid_) + { + return pixelSpacingX_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + double GetPixelSpacingY() const + { + if (isValid_) + { + return pixelSpacingY_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool Intersect(double& x1, // Coordinates of the clipped line (out) + double& y1, + double& x2, + double& y2, + const FrameGeometry& other) const + { + if (this == &other) + { + return false; + } + + OrthancStone::Vector direction, origin; + + if (IsValid() && + other.IsValid() && + !extent_.IsEmpty() && + frameOfReferenceUid_ == other.frameOfReferenceUid_ && + OrthancStone::GeometryToolbox::IntersectTwoPlanes( + origin, direction, + coordinates_.GetOrigin(), coordinates_.GetNormal(), + other.coordinates_.GetOrigin(), other.coordinates_.GetNormal())) + { + double ax, ay, bx, by; + coordinates_.ProjectPoint(ax, ay, origin); + coordinates_.ProjectPoint(bx, by, origin + 100.0 * direction); + + return OrthancStone::GeometryToolbox::ClipLineToRectangle( + x1, y1, x2, y2, + ax, ay, bx, by, + extent_.GetX1(), extent_.GetY1(), extent_.GetX2(), extent_.GetY2()); + } + else + { + return false; + } + } +}; + + + +class ViewerViewport : public OrthancStone::ObserverBase +{ +public: + class IObserver : public boost::noncopyable + { + public: + virtual ~IObserver() + { + } + + virtual void SignalFrameUpdated(const ViewerViewport& viewport, + size_t currentFrame, + size_t countFrames, + DisplayedFrameQuality quality) = 0; + }; + +private: + static const int LAYER_TEXTURE = 0; + static const int LAYER_REFERENCE_LINES = 1; + + + class ICommand : public Orthanc::IDynamicObject + { + private: + boost::shared_ptr viewport_; + + public: + ICommand(boost::shared_ptr viewport) : + viewport_(viewport) + { + if (viewport == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + virtual ~ICommand() + { + } + + ViewerViewport& GetViewport() const + { + assert(viewport_ != NULL); + return *viewport_; + } + + virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + }; + + class SetDefaultWindowingCommand : public ICommand + { + public: + SetDefaultWindowingCommand(boost::shared_ptr viewport) : + ICommand(viewport) + { + } + + virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE + { + if (message.GetResources()->GetSize() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + const Orthanc::DicomMap& dicom = message.GetResources()->GetResource(0); + + { + OrthancStone::DicomInstanceParameters params(dicom); + + if (params.HasDefaultWindowing()) + { + GetViewport().defaultWindowingCenter_ = params.GetDefaultWindowingCenter(); + GetViewport().defaultWindowingWidth_ = params.GetDefaultWindowingWidth(); + LOG(INFO) << "Default windowing: " << params.GetDefaultWindowingCenter() + << "," << params.GetDefaultWindowingWidth(); + + GetViewport().windowingCenter_ = params.GetDefaultWindowingCenter(); + GetViewport().windowingWidth_ = params.GetDefaultWindowingWidth(); + } + else + { + LOG(INFO) << "No default windowing"; + GetViewport().ResetDefaultWindowing(); + } + } + + GetViewport().DisplayCurrentFrame(); + } + }; + + class SetLowQualityFrame : public ICommand + { + private: + std::string sopInstanceUid_; + unsigned int frameIndex_; + float windowCenter_; + float windowWidth_; + bool isMonochrome1_; + bool isPrefetch_; + + public: + SetLowQualityFrame(boost::shared_ptr viewport, + const std::string& sopInstanceUid, + unsigned int frameIndex, + float windowCenter, + float windowWidth, + bool isMonochrome1, + bool isPrefetch) : + ICommand(viewport), + sopInstanceUid_(sopInstanceUid), + frameIndex_(frameIndex), + windowCenter_(windowCenter), + windowWidth_(windowWidth), + isMonochrome1_(isMonochrome1), + isPrefetch_(isPrefetch) + { + } + + virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const ORTHANC_OVERRIDE + { + std::unique_ptr jpeg(new Orthanc::JpegReader); + jpeg->ReadFromMemory(message.GetAnswer()); + + bool updatedCache; + + switch (jpeg->GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + updatedCache = GetViewport().cache_->Acquire( + sopInstanceUid_, frameIndex_, jpeg.release(), QUALITY_JPEG); + break; + + case Orthanc::PixelFormat_Grayscale8: + { + if (isMonochrome1_) + { + Orthanc::ImageProcessing::Invert(*jpeg); + } + + std::unique_ptr converted( + new Orthanc::Image(Orthanc::PixelFormat_Float32, jpeg->GetWidth(), + jpeg->GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*converted, *jpeg); + + /** + + Orthanc::ImageProcessing::ShiftScale() computes "(x + offset) * scaling". + The system to solve is thus: + + (0 + offset) * scaling = windowingCenter - windowingWidth / 2 [a] + (255 + offset) * scaling = windowingCenter + windowingWidth / 2 [b] + + Resolution: + + [b - a] => 255 * scaling = windowingWidth + [a] => offset = (windowingCenter - windowingWidth / 2) / scaling + + **/ + + const float scaling = windowWidth_ / 255.0f; + const float offset = (OrthancStone::LinearAlgebra::IsCloseToZero(scaling) ? 0 : + (windowCenter_ - windowWidth_ / 2.0f) / scaling); + + Orthanc::ImageProcessing::ShiftScale(*converted, offset, scaling, false); + updatedCache = GetViewport().cache_->Acquire( + sopInstanceUid_, frameIndex_, converted.release(), QUALITY_JPEG); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (updatedCache) + { + GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_); + } + + if (isPrefetch_) + { + GetViewport().ScheduleNextPrefetch(); + } + } + }; + + + class SetFullDicomFrame : public ICommand + { + private: + std::string sopInstanceUid_; + unsigned int frameIndex_; + bool isPrefetch_; + + public: + SetFullDicomFrame(boost::shared_ptr viewport, + const std::string& sopInstanceUid, + unsigned int frameIndex, + bool isPrefetch) : + ICommand(viewport), + sopInstanceUid_(sopInstanceUid), + frameIndex_(frameIndex), + isPrefetch_(isPrefetch) + { + } + + virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const ORTHANC_OVERRIDE + { + Orthanc::DicomMap tags; + message.GetDicom().ExtractDicomSummary(tags, ORTHANC_STONE_MAX_TAG_LENGTH); + + std::string s; + if (!tags.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + // Safety check + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::unique_ptr frame( + Orthanc::DicomImageDecoder::Decode(message.GetDicom(), frameIndex_)); + + if (frame.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + bool updatedCache; + + if (frame->GetFormat() == Orthanc::PixelFormat_RGB24) + { + updatedCache = GetViewport().cache_->Acquire( + sopInstanceUid_, frameIndex_, frame.release(), QUALITY_FULL); + } + else + { + double a = 1; + double b = 0; + + double doseScaling; + if (tags.ParseDouble(doseScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + a = doseScaling; + } + + double rescaleIntercept, rescaleSlope; + if (tags.ParseDouble(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + tags.ParseDouble(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + { + a *= rescaleSlope; + b = rescaleIntercept; + } + + std::unique_ptr converted( + new Orthanc::Image(Orthanc::PixelFormat_Float32, frame->GetWidth(), frame->GetHeight(), false)); + Orthanc::ImageProcessing::Convert(*converted, *frame); + Orthanc::ImageProcessing::ShiftScale2(*converted, b, a, false); + + updatedCache = GetViewport().cache_->Acquire( + sopInstanceUid_, frameIndex_, converted.release(), QUALITY_FULL); + } + + if (updatedCache) + { + GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_); + } + + if (isPrefetch_) + { + GetViewport().ScheduleNextPrefetch(); + } + } + }; + + + class PrefetchItem + { + private: + size_t frameIndex_; + bool isFull_; + + public: + PrefetchItem(size_t frameIndex, + bool isFull) : + frameIndex_(frameIndex), + isFull_(isFull) + { + } + + size_t GetFrameIndex() const + { + return frameIndex_; + } + + bool IsFull() const + { + return isFull_; + } + }; + + + std::unique_ptr observer_; + OrthancStone::ILoadersContext& context_; + boost::shared_ptr viewport_; + boost::shared_ptr loader_; + OrthancStone::DicomSource source_; + boost::shared_ptr cache_; + std::unique_ptr frames_; + std::unique_ptr cursor_; + float windowingCenter_; + float windowingWidth_; + float defaultWindowingCenter_; + float defaultWindowingWidth_; + bool inverted_; + bool fitNextContent_; + bool isCtrlDown_; + FrameGeometry currentFrameGeometry_; + std::list prefetchQueue_; + + void ScheduleNextPrefetch() + { + while (!prefetchQueue_.empty()) + { + size_t index = prefetchQueue_.front().GetFrameIndex(); + bool isFull = prefetchQueue_.front().IsFull(); + prefetchQueue_.pop_front(); + + const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); + unsigned int frame = frames_->GetFrameIndex(index); + + { + FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame); + if (!accessor.IsValid() || + (isFull && accessor.GetQuality() == 0)) + { + if (isFull) + { + ScheduleLoadFullDicomFrame(index, PRIORITY_NORMAL, true); + } + else + { + ScheduleLoadRenderedFrame(index, PRIORITY_NORMAL, true); + } + return; + } + } + } + } + + + void ResetDefaultWindowing() + { + defaultWindowingCenter_ = 128; + defaultWindowingWidth_ = 256; + + windowingCenter_ = defaultWindowingCenter_; + windowingWidth_ = defaultWindowingWidth_; + + inverted_ = false; + } + + void SignalUpdatedFrame(const std::string& sopInstanceUid, + unsigned int frameIndex) + { + if (cursor_.get() != NULL && + frames_.get() != NULL) + { + size_t index = cursor_->GetCurrentIndex(); + + if (frames_->GetFrameSopInstanceUid(index) == sopInstanceUid && + frames_->GetFrameIndex(index) == frameIndex) + { + DisplayCurrentFrame(); + } + } + } + + void DisplayCurrentFrame() + { + DisplayedFrameQuality quality = DisplayedFrameQuality_None; + + if (cursor_.get() != NULL && + frames_.get() != NULL) + { + const size_t index = cursor_->GetCurrentIndex(); + + unsigned int cachedQuality; + if (!DisplayFrame(cachedQuality, index)) + { + // This frame is not cached yet: Load it + if (source_.HasDicomWebRendered()) + { + ScheduleLoadRenderedFrame(index, PRIORITY_HIGH, false /* not a prefetch */); + } + else + { + ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */); + } + } + else if (cachedQuality < QUALITY_FULL) + { + // This frame is only available in low-res: Download the full DICOM + ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */); + quality = DisplayedFrameQuality_Low; + } + else + { + quality = DisplayedFrameQuality_High; + } + + currentFrameGeometry_ = FrameGeometry(frames_->GetFrameTags(index)); + + { + // Prepare prefetching + prefetchQueue_.clear(); + for (size_t i = 0; i < cursor_->GetPrefetchSize() && i < 16; i++) + { + size_t a = cursor_->GetPrefetchFrameIndex(i); + if (a != index) + { + prefetchQueue_.push_back(PrefetchItem(a, i < 2)); + } + } + + ScheduleNextPrefetch(); + } + + if (observer_.get() != NULL) + { + observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(), + frames_->GetFramesCount(), quality); + } + } + else + { + currentFrameGeometry_ = FrameGeometry(); + } + } + + void ClearViewport() + { + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().DeleteLayer(LAYER_TEXTURE); + //lock->GetCompositor().Refresh(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + bool DisplayFrame(unsigned int& quality, + size_t index) + { + if (frames_.get() == NULL) + { + return false; + } + + const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); + unsigned int frame = frames_->GetFrameIndex(index); + + FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame); + if (accessor.IsValid()) + { + { + std::unique_ptr lock(viewport_->Lock()); + + OrthancStone::Scene2D& scene = lock->GetController().GetScene(); + + // Save the current windowing (that could have been altered by + // GrayscaleWindowingSceneTracker), so that it can be reused + // by the next frames + if (scene.HasLayer(LAYER_TEXTURE) && + scene.GetLayer(LAYER_TEXTURE).GetType() == OrthancStone::ISceneLayer::Type_FloatTexture) + { + OrthancStone::FloatTextureSceneLayer& layer = + dynamic_cast(scene.GetLayer(LAYER_TEXTURE)); + layer.GetWindowing(windowingCenter_, windowingWidth_); + } + } + + quality = accessor.GetQuality(); + + std::unique_ptr layer; + + switch (accessor.GetImage().GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + layer.reset(new OrthancStone::ColorTextureSceneLayer(accessor.GetImage())); + break; + + case Orthanc::PixelFormat_Float32: + { + std::unique_ptr tmp( + new OrthancStone::FloatTextureSceneLayer(accessor.GetImage())); + tmp->SetCustomWindowing(windowingCenter_, windowingWidth_); + tmp->SetInverted(inverted_ ^ frames_->IsFrameMonochrome1(index)); + layer.reset(tmp.release()); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + layer->SetLinearInterpolation(true); + + double pixelSpacingX, pixelSpacingY; + OrthancStone::GeometryToolbox::GetPixelSpacing( + pixelSpacingX, pixelSpacingY, frames_->GetFrameTags(index)); + layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY); + + if (layer.get() == NULL) + { + return false; + } + else + { + std::unique_ptr lock(viewport_->Lock()); + + OrthancStone::Scene2D& scene = lock->GetController().GetScene(); + + scene.SetLayer(LAYER_TEXTURE, layer.release()); + + if (fitNextContent_) + { + lock->GetCompositor().RefreshCanvasSize(); + lock->GetCompositor().FitContent(scene); + fitNextContent_ = false; + } + + //lock->GetCompositor().Refresh(scene); + lock->Invalidate(); + return true; + } + } + else + { + return false; + } + } + + void ScheduleLoadFullDicomFrame(size_t index, + int priority, + bool isPrefetch) + { + if (frames_.get() != NULL) + { + std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); + unsigned int frame = frames_->GetFrameIndex(index); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule( + GetSharedObserver(), priority, OrthancStone::ParseDicomFromWadoCommand::Create( + source_, frames_->GetStudyInstanceUid(), frames_->GetSeriesInstanceUid(), + sopInstanceUid, false /* transcoding (TODO) */, + Orthanc::DicomTransferSyntax_LittleEndianExplicit /* TODO */, + new SetFullDicomFrame(GetSharedObserver(), sopInstanceUid, frame, isPrefetch))); + } + } + } + + void ScheduleLoadRenderedFrame(size_t index, + int priority, + bool isPrefetch) + { + if (!source_.HasDicomWebRendered()) + { + ScheduleLoadFullDicomFrame(index, priority, isPrefetch); + } + else if (frames_.get() != NULL) + { + std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); + unsigned int frame = frames_->GetFrameIndex(index); + bool isMonochrome1 = frames_->IsFrameMonochrome1(index); + + const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() + + "/series/" + frames_->GetSeriesInstanceUid() + + "/instances/" + sopInstanceUid + + "/frames/" + boost::lexical_cast(frame + 1) + "/rendered"); + + std::map headers, arguments; + arguments["window"] = ( + boost::lexical_cast(windowingCenter_) + "," + + boost::lexical_cast(windowingWidth_) + ",linear"); + + std::unique_ptr command( + source_.CreateDicomWebCommand( + uri, arguments, headers, new SetLowQualityFrame( + GetSharedObserver(), sopInstanceUid, frame, + windowingCenter_, windowingWidth_, isMonochrome1, isPrefetch))); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + } + + ViewerViewport(OrthancStone::ILoadersContext& context, + const OrthancStone::DicomSource& source, + const std::string& canvas, + boost::shared_ptr cache) : + context_(context), + source_(source), + viewport_(OrthancStone::WebGLViewport::Create(canvas)), + cache_(cache), + fitNextContent_(true), + isCtrlDown_(false) + { + if (!cache_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + emscripten_set_wheel_callback(viewport_->GetCanvasCssSelector().c_str(), this, true, OnWheel); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey); + + ResetDefaultWindowing(); + } + + static EM_BOOL OnKey(int eventType, + const EmscriptenKeyboardEvent *event, + void *userData) + { + /** + * WARNING: There is a problem with Firefox 71 that seems to mess + * the "ctrlKey" value. + **/ + + ViewerViewport& that = *reinterpret_cast(userData); + that.isCtrlDown_ = event->ctrlKey; + return false; + } + + + static EM_BOOL OnWheel(int eventType, + const EmscriptenWheelEvent *wheelEvent, + void *userData) + { + ViewerViewport& that = *reinterpret_cast(userData); + + if (that.cursor_.get() != NULL) + { + if (wheelEvent->deltaY < 0) + { + that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastMinus : SeriesCursor::Action_Minus); + } + else if (wheelEvent->deltaY > 0) + { + that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastPlus : SeriesCursor::Action_Plus); + } + } + + return true; + } + + void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) + { + dynamic_cast(message.GetUserPayload()).Handle(message); + } + + void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) + { + dynamic_cast(message.GetOrigin().GetPayload()).Handle(message); + } + + void Handle(const OrthancStone::ParseDicomSuccessMessage& message) + { + dynamic_cast(message.GetOrigin().GetPayload()).Handle(message); + } + +public: + static boost::shared_ptr Create(OrthancStone::ILoadersContext::ILock& lock, + const OrthancStone::DicomSource& source, + const std::string& canvas, + boost::shared_ptr cache) + { + boost::shared_ptr viewport( + new ViewerViewport(lock.GetContext(), source, canvas, cache)); + + viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(lock); + viewport->Register( + *viewport->loader_, &ViewerViewport::Handle); + + viewport->Register( + lock.GetOracleObservable(), &ViewerViewport::Handle); + + viewport->Register( + lock.GetOracleObservable(), &ViewerViewport::Handle); + + return viewport; + } + + void SetFrames(OrthancStone::SortedFrames* frames) + { + if (frames == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + fitNextContent_ = true; + + frames_.reset(frames); + cursor_.reset(new SeriesCursor(frames_->GetFramesCount())); + + LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount(); + + ResetDefaultWindowing(); + ClearViewport(); + prefetchQueue_.clear(); + currentFrameGeometry_ = FrameGeometry(); + + if (observer_.get() != NULL) + { + observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(), + frames_->GetFramesCount(), DisplayedFrameQuality_None); + } + + if (frames_->GetFramesCount() != 0) + { + const std::string& sopInstanceUid = frames_->GetFrameSopInstanceUid(cursor_->GetCurrentIndex()); + + { + // Fetch the default windowing for the central instance + const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() + + "/series/" + frames_->GetSeriesInstanceUid() + + "/instances/" + sopInstanceUid + "/metadata"); + + loader_->ScheduleGetDicomWeb( + boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source_, uri, new SetDefaultWindowingCommand(GetSharedObserver())); + } + } + } + + // This method is used when the layout of the HTML page changes, + // which does not trigger the "emscripten_set_resize_callback()" + void UpdateSize(bool fitContent) + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetCompositor().RefreshCanvasSize(); + + if (fitContent) + { + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + } + + lock->Invalidate(); + } + + void AcquireObserver(IObserver* observer) + { + observer_.reset(observer); + } + + const std::string& GetCanvasId() const + { + assert(viewport_); + return viewport_->GetCanvasId(); + } + + void ChangeFrame(SeriesCursor::Action action) + { + if (cursor_.get() != NULL) + { + size_t previous = cursor_->GetCurrentIndex(); + + cursor_->Apply(action); + + size_t current = cursor_->GetCurrentIndex(); + if (previous != current) + { + DisplayCurrentFrame(); + } + } + } + + const FrameGeometry& GetCurrentFrameGeometry() const + { + return currentFrameGeometry_; + } + + void UpdateReferenceLines(const std::list& planes) + { + std::unique_ptr layer(new OrthancStone::PolylineSceneLayer); + + if (GetCurrentFrameGeometry().IsValid()) + { + for (std::list::const_iterator + it = planes.begin(); it != planes.end(); ++it) + { + assert(*it != NULL); + + double x1, y1, x2, y2; + if (GetCurrentFrameGeometry().Intersect(x1, y1, x2, y2, **it)) + { + OrthancStone::PolylineSceneLayer::Chain chain; + chain.push_back(OrthancStone::ScenePoint2D(x1, y1)); + chain.push_back(OrthancStone::ScenePoint2D(x2, y2)); + layer->AddChain(chain, false, 0, 255, 0); + } + } + } + + { + std::unique_ptr lock(viewport_->Lock()); + + if (layer->GetChainsCount() == 0) + { + lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES); + } + else + { + lock->GetController().GetScene().SetLayer(LAYER_REFERENCE_LINES, layer.release()); + } + + //lock->GetCompositor().Refresh(lock->GetController().GetScene()); + lock->Invalidate(); + } + } + + + void ClearReferenceLines() + { + { + std::unique_ptr lock(viewport_->Lock()); + lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES); + lock->Invalidate(); + } + } + + + void SetDefaultWindowing() + { + SetWindowing(defaultWindowingCenter_, defaultWindowingWidth_); + } + + void SetWindowing(float windowingCenter, + float windowingWidth) + { + windowingCenter_ = windowingCenter; + windowingWidth_ = windowingWidth; + + { + std::unique_ptr lock(viewport_->Lock()); + + if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) && + lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() == + OrthancStone::ISceneLayer::Type_FloatTexture) + { + dynamic_cast( + lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)). + SetCustomWindowing(windowingCenter_, windowingWidth_); + lock->Invalidate(); + } + } + } + + void Invert() + { + inverted_ = !inverted_; + + { + std::unique_ptr lock(viewport_->Lock()); + + if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) && + lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() == + OrthancStone::ISceneLayer::Type_FloatTexture) + { + OrthancStone::FloatTextureSceneLayer& layer = + dynamic_cast( + lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)); + + // NB: Using "IsInverted()" instead of "inverted_" is for + // compatibility with MONOCHROME1 images + layer.SetInverted(!layer.IsInverted()); + lock->Invalidate(); + } + } + } +}; + + + + + +typedef std::map > Viewports; +static Viewports allViewports_; +static bool showReferenceLines_ = true; + + +static void UpdateReferenceLines() +{ + if (showReferenceLines_) + { + std::list planes; + + for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) + { + assert(it->second != NULL); + planes.push_back(&it->second->GetCurrentFrameGeometry()); + } + + for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) + { + assert(it->second != NULL); + it->second->UpdateReferenceLines(planes); + } + } + else + { + for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) + { + assert(it->second != NULL); + it->second->ClearReferenceLines(); + } + } +} + + +class WebAssemblyObserver : public ResourcesLoader::IObserver, + public ViewerViewport::IObserver +{ +public: + virtual void SignalResourcesLoaded() ORTHANC_OVERRIDE + { + DISPATCH_JAVASCRIPT_EVENT("ResourcesLoaded"); + } + + virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) ORTHANC_OVERRIDE + { + EM_ASM({ + const customEvent = document.createEvent("CustomEvent"); + customEvent.initCustomEvent("ThumbnailLoaded", false, false, + { "studyInstanceUid" : UTF8ToString($0), + "seriesInstanceUid" : UTF8ToString($1) }); + window.dispatchEvent(customEvent); + }, + studyInstanceUid.c_str(), + seriesInstanceUid.c_str()); + } + + virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid) ORTHANC_OVERRIDE + { + EM_ASM({ + const customEvent = document.createEvent("CustomEvent"); + customEvent.initCustomEvent("MetadataLoaded", false, false, + { "studyInstanceUid" : UTF8ToString($0), + "seriesInstanceUid" : UTF8ToString($1) }); + window.dispatchEvent(customEvent); + }, + studyInstanceUid.c_str(), + seriesInstanceUid.c_str()); + } + + virtual void SignalFrameUpdated(const ViewerViewport& viewport, + size_t currentFrame, + size_t countFrames, + DisplayedFrameQuality quality) ORTHANC_OVERRIDE + { + EM_ASM({ + const customEvent = document.createEvent("CustomEvent"); + customEvent.initCustomEvent("FrameUpdated", false, false, + { "canvasId" : UTF8ToString($0), + "currentFrame" : $1, + "framesCount" : $2, + "quality" : $3 }); + window.dispatchEvent(customEvent); + }, + viewport.GetCanvasId().c_str(), + static_cast(currentFrame), + static_cast(countFrames), + quality); + + + UpdateReferenceLines(); + }; +}; + + + +static OrthancStone::DicomSource source_; +static boost::shared_ptr cache_; +static boost::shared_ptr context_; +static std::string stringBuffer_; + + + +static void FormatTags(std::string& target, + const Orthanc::DicomMap& tags) +{ + Orthanc::DicomArray arr(tags); + Json::Value v = Json::objectValue; + + for (size_t i = 0; i < arr.GetSize(); i++) + { + const Orthanc::DicomElement& element = arr.GetElement(i); + if (!element.GetValue().IsBinary() && + !element.GetValue().IsNull()) + { + v[element.GetTag().Format()] = element.GetValue().GetContent(); + } + } + + target = v.toStyledString(); +} + + +static ResourcesLoader& GetResourcesLoader() +{ + static boost::shared_ptr resourcesLoader_; + + if (!resourcesLoader_) + { + std::unique_ptr lock(context_->Lock()); + resourcesLoader_ = ResourcesLoader::Create(*lock, source_); + resourcesLoader_->AcquireObserver(new WebAssemblyObserver); + } + + return *resourcesLoader_; +} + + +static boost::shared_ptr GetViewport(const std::string& canvas) +{ + Viewports::iterator found = allViewports_.find(canvas); + if (found == allViewports_.end()) + { + std::unique_ptr lock(context_->Lock()); + boost::shared_ptr viewport(ViewerViewport::Create(*lock, source_, canvas, cache_)); + viewport->AcquireObserver(new WebAssemblyObserver); + allViewports_[canvas] = viewport; + return viewport; + } + else + { + return found->second; + } +} + + +extern "C" +{ + int main(int argc, char const *argv[]) + { + printf("OK\n"); + Orthanc::InitializeFramework("", true); + Orthanc::Logging::EnableInfoLevel(true); + //Orthanc::Logging::EnableTraceLevel(true); + + context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); + cache_.reset(new FramesCache); + + DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); + } + + + EMSCRIPTEN_KEEPALIVE + void SetOrthancRoot(const char* uri, + int useRendered) + { + try + { + context_->SetLocalOrthanc(uri); // For "source_.SetDicomWebThroughOrthancSource()" + source_.SetDicomWebSource(std::string(uri) + "/dicom-web"); + source_.SetDicomWebRendered(useRendered != 0); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void SetDicomWebServer(const char* serverName, + int hasRendered) + { + try + { + source_.SetDicomWebThroughOrthancSource(serverName); + source_.SetDicomWebRendered(hasRendered != 0); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void FetchAllStudies() + { + try + { + GetResourcesLoader().FetchAllStudies(); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + void FetchStudy(const char* studyInstanceUid) + { + try + { + GetResourcesLoader().FetchStudy(studyInstanceUid); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + void FetchSeries(const char* studyInstanceUid, + const char* seriesInstanceUid) + { + try + { + GetResourcesLoader().FetchSeries(studyInstanceUid, seriesInstanceUid); + } + EXTERN_CATCH_EXCEPTIONS; + } + + EMSCRIPTEN_KEEPALIVE + int GetStudiesCount() + { + try + { + return GetResourcesLoader().GetStudiesCount(); + } + EXTERN_CATCH_EXCEPTIONS; + return 0; // on exception + } + + EMSCRIPTEN_KEEPALIVE + int GetSeriesCount() + { + try + { + return GetResourcesLoader().GetSeriesCount(); + } + EXTERN_CATCH_EXCEPTIONS; + return 0; // on exception + } + + + EMSCRIPTEN_KEEPALIVE + const char* GetStringBuffer() + { + return stringBuffer_.c_str(); + } + + + EMSCRIPTEN_KEEPALIVE + void LoadStudyTags(int i) + { + try + { + if (i < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + Orthanc::DicomMap dicom; + GetResourcesLoader().GetStudy(dicom, i); + FormatTags(stringBuffer_, dicom); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void LoadSeriesTags(int i) + { + try + { + if (i < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + Orthanc::DicomMap dicom; + GetResourcesLoader().GetSeries(dicom, i); + FormatTags(stringBuffer_, dicom); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + int LoadSeriesThumbnail(const char* seriesInstanceUid) + { + try + { + std::string image, mime; + switch (GetResourcesLoader().GetSeriesThumbnail(image, mime, seriesInstanceUid)) + { + case OrthancStone::SeriesThumbnailType_Image: + Orthanc::Toolbox::EncodeDataUriScheme(stringBuffer_, mime, image); + return ThumbnailType_Image; + + case OrthancStone::SeriesThumbnailType_Pdf: + return ThumbnailType_Pdf; + + case OrthancStone::SeriesThumbnailType_Video: + return ThumbnailType_Video; + + case OrthancStone::SeriesThumbnailType_NotLoaded: + return ThumbnailType_Loading; + + case OrthancStone::SeriesThumbnailType_Unsupported: + return ThumbnailType_NoPreview; + + default: + return ThumbnailType_Unknown; + } + } + EXTERN_CATCH_EXCEPTIONS; + return ThumbnailType_Unknown; + } + + + EMSCRIPTEN_KEEPALIVE + void SpeedUpFetchSeriesMetadata(const char* studyInstanceUid, + const char* seriesInstanceUid) + { + try + { + GetResourcesLoader().FetchSeriesMetadata(PRIORITY_HIGH, studyInstanceUid, seriesInstanceUid); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + int IsSeriesComplete(const char* seriesInstanceUid) + { + try + { + return GetResourcesLoader().IsSeriesComplete(seriesInstanceUid) ? 1 : 0; + } + EXTERN_CATCH_EXCEPTIONS; + return 0; + } + + EMSCRIPTEN_KEEPALIVE + int LoadSeriesInViewport(const char* canvas, + const char* seriesInstanceUid) + { + try + { + std::unique_ptr frames(new OrthancStone::SortedFrames); + + if (GetResourcesLoader().SortSeriesFrames(*frames, seriesInstanceUid)) + { + GetViewport(canvas)->SetFrames(frames.release()); + return 1; + } + else + { + return 0; + } + } + EXTERN_CATCH_EXCEPTIONS; + return 0; + } + + + EMSCRIPTEN_KEEPALIVE + void AllViewportsUpdateSize(int fitContent) + { + try + { + for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) + { + assert(it->second != NULL); + it->second->UpdateSize(fitContent != 0); + } + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void DecrementFrame(const char* canvas, + int fitContent) + { + try + { + GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Minus); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void IncrementFrame(const char* canvas, + int fitContent) + { + try + { + GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Plus); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void ShowReferenceLines(int show) + { + try + { + showReferenceLines_ = (show != 0); + UpdateReferenceLines(); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void SetDefaultWindowing(const char* canvas) + { + try + { + GetViewport(canvas)->SetDefaultWindowing(); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void SetWindowing(const char* canvas, + int center, + int width) + { + try + { + GetViewport(canvas)->SetWindowing(center, width); + } + EXTERN_CATCH_EXCEPTIONS; + } + + + EMSCRIPTEN_KEEPALIVE + void InvertContrast(const char* canvas) + { + try + { + GetViewport(canvas)->Invert(); + } + EXTERN_CATCH_EXCEPTIONS; + } +} diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/WebAssembly/docker-build.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/docker-build.sh Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,28 @@ +#!/bin/bash + +set -ex + +IMAGE=jodogne/wasm-builder:1.39.17-upstream +#IMAGE=wasm-builder + +if [ "$1" != "Debug" -a "$1" != "Release" ]; then + echo "Please provide build type: Debug or Release" + exit -1 +fi + +if [ -t 1 ]; then + # TTY is available => use interactive mode + DOCKER_FLAGS='-i' +fi + +ROOT_DIR=`dirname $(readlink -f $0)`/../.. + +mkdir -p ${ROOT_DIR}/wasm-binaries + +docker run -t ${DOCKER_FLAGS} --rm \ + --user $(id -u):$(id -g) \ + -v ${ROOT_DIR}:/source:ro \ + -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \ + bash /source/StoneWebViewer/WebAssembly/docker-internal.sh $1 + +ls -lR ${ROOT_DIR}/wasm-binaries/StoneWebViewer/ diff -r de8cf5859e84 -r d1806b4e4839 Applications/StoneWebViewer/WebAssembly/docker-internal.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebAssembly/docker-internal.sh Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,30 @@ +#!/bin/bash +set -ex + +source /opt/emsdk/emsdk_env.sh + +# Use a folder that is writeable by non-root users for the Emscripten cache +export EM_CACHE=/tmp/emscripten-cache + +# Get the Orthanc framework +cd /tmp/ +hg clone https://hg.orthanc-server.com/orthanc/ + +# Make a copy of the read-only folder containing the source code into +# a writeable folder, because of "DownloadPackage.cmake" that writes +# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt" +cd /source +hg clone /source /tmp/source-writeable + +mkdir /tmp/build +cd /tmp/build + +cmake /tmp/source-writeable/StoneWebViewer/WebAssembly \ + -DCMAKE_BUILD_TYPE=$1 \ + -DCMAKE_INSTALL_PREFIX=/target/StoneWebViewer \ + -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ + -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \ + -DSTATIC_BUILD=ON \ + -G Ninja + +ninja -j2 install diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Docs/Conventions.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Docs/Conventions.txt Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,88 @@ + +Some notes about the lifetime of objects +======================================== + +Stone applications +------------------ + +A typical Stone application can be split in 3 parts: + +1- The "loaders part" and the associated "IOracle", that communicate + through "IMessage" objects. The lifetime of these objects is + governed by the "IStoneContext". + +2- The "data part" holds the data loaded by the "loaders part". The + related objects must not be aware of the oracle, neither of the + messages. It is up to the user application to store these objects. + +3- The "viewport part" is based upon the "Scene2D" class. + + +Multithreading +-------------- + +* Stone makes the hypothesis that its objects live in a single thread. + All the content of the "Framework" folder (with the exception of + the "Oracle" stuff) must not use "boost::thread". + +* The "IOracleCommand" classes represent commands that must be + executed asynchronously from the Stone thread. Their actual + execution is done by the "IOracle". + +* In WebAssembly, the "IOracle" corresponds to the "html5.h" + facilities (notably for the Fetch API). There is no mutex here, as + JavaScript is inherently single-threaded. + +* In plain C++ applications, the "IOracle" corresponds to a FIFO queue + of commands that are executed by a pool of threads. The Stone + context holds a global mutex, that must be properly locked by the + user application, and by the "IOracle" when it sends back messages + to the Stone loaders (cf. class "IMessageEmitter"). + +* Multithreading is thus achieved by defining new oracle commands by + subclassing "IOracleCommand", then by defining a way to execute them + (cf. class "GenericCommandRunner"). + + +References between objects +-------------------------- + +* An object allocated on the heap must never store a reference/pointer + to another object. + +* A class designed to be allocated only on the stack can store a + reference/pointer to another object. Here is the list of + such classes: + + - IMessage and its derived classes: All the messages are allocated + on the stack. + + +Pointers +-------- + +* As we are targeting C++03 (for VS2008 and LSB compatibility), use + "std::unique_ptr<>" and "boost::shared_ptr<>" (*not* + "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for + pre-C++11 compilers. + +* The fact of transfering the ownership of one object to another must + be tagged by naming the method "Acquire...()", and by providing a + raw pointer. + +* Use "std::unique_ptr<>" if the goal is to internally store a pointer + whose lifetime corresponds to the host object. + +* The use of "boost::weak_ptr<>" should be restricted to + oracle/message handling. + +* The use of "boost::shared_ptr<>" should be minimized to avoid + clutter. The "loaders" and "data parts" objects must however + be created as "boost::shared_ptr<>". + + +Global context +-------------- + +* As the global Stone context can be created/destroyed by other + languages than C++, we don't use a "boost:shared_ptr<>". diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Resources/Conventions.txt --- a/OrthancStone/Resources/Conventions.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ - -Some notes about the lifetime of objects -======================================== - -Stone applications ------------------- - -A typical Stone application can be split in 3 parts: - -1- The "loaders part" and the associated "IOracle", that communicate - through "IMessage" objects. The lifetime of these objects is - governed by the "IStoneContext". - -2- The "data part" holds the data loaded by the "loaders part". The - related objects must not be aware of the oracle, neither of the - messages. It is up to the user application to store these objects. - -3- The "viewport part" is based upon the "Scene2D" class. - - -Multithreading --------------- - -* Stone makes the hypothesis that its objects live in a single thread. - All the content of the "Framework" folder (with the exception of - the "Oracle" stuff) must not use "boost::thread". - -* The "IOracleCommand" classes represent commands that must be - executed asynchronously from the Stone thread. Their actual - execution is done by the "IOracle". - -* In WebAssembly, the "IOracle" corresponds to the "html5.h" - facilities (notably for the Fetch API). There is no mutex here, as - JavaScript is inherently single-threaded. - -* In plain C++ applications, the "IOracle" corresponds to a FIFO queue - of commands that are executed by a pool of threads. The Stone - context holds a global mutex, that must be properly locked by the - user application, and by the "IOracle" when it sends back messages - to the Stone loaders (cf. class "IMessageEmitter"). - -* Multithreading is thus achieved by defining new oracle commands by - subclassing "IOracleCommand", then by defining a way to execute them - (cf. class "GenericCommandRunner"). - - -References between objects --------------------------- - -* An object allocated on the heap must never store a reference/pointer - to another object. - -* A class designed to be allocated only on the stack can store a - reference/pointer to another object. Here is the list of - such classes: - - - IMessage and its derived classes: All the messages are allocated - on the stack. - - -Pointers --------- - -* As we are targeting C++03 (for VS2008 and LSB compatibility), use - "std::unique_ptr<>" and "boost::shared_ptr<>" (*not* - "std::shared_ptr<>"). We provide an implementation of std::unique_ptr for - pre-C++11 compilers. - -* The fact of transfering the ownership of one object to another must - be tagged by naming the method "Acquire...()", and by providing a - raw pointer. - -* Use "std::unique_ptr<>" if the goal is to internally store a pointer - whose lifetime corresponds to the host object. - -* The use of "boost::weak_ptr<>" should be restricted to - oracle/message handling. - -* The use of "boost::shared_ptr<>" should be minimized to avoid - clutter. The "loaders" and "data parts" objects must however - be created as "boost::shared_ptr<>". - - -Global context --------------- - -* As the global Stone context can be created/destroyed by other - languages than C++, we don't use a "boost:shared_ptr<>". diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Common/RtViewerApp.cpp --- a/OrthancStone/Samples/Common/RtViewerApp.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - -// Sample app -#include "RtViewerApp.h" -#include "RtViewerView.h" -#include "SampleHelpers.h" - -// Stone of Orthanc -#include "../../Sources/StoneInitialization.h" -#include "../../Sources/Scene2D/CairoCompositor.h" -#include "../../Sources/Scene2D/ColorTextureSceneLayer.h" -#include "../../Sources/Scene2D/OpenGLCompositor.h" -#include "../../Sources/Scene2D/PanSceneTracker.h" -#include "../../Sources/Scene2D/ZoomSceneTracker.h" -#include "../../Sources/Scene2D/RotateSceneTracker.h" -#include "../../Sources/Scene2DViewport/UndoStack.h" -#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h" -#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h" -#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h" -#include "../../Sources/Scene2DViewport/MeasureTool.h" -#include "../../Sources/Scene2DViewport/PredeclaredTypes.h" -#include "../../Sources/Volumes/VolumeSceneLayerSource.h" -#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h" -#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h" -#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h" -#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h" -#include "../../Sources/StoneException.h" - -// Orthanc -#include -#include - -// System -#include -#include -#include - -#include - - -namespace OrthancStone -{ - void RtViewerApp::InvalidateAllViewports() - { - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->Invalidate(); - } - } - - const VolumeImageGeometry& RtViewerApp::GetMainGeometry() - { - ORTHANC_ASSERT(geometryProvider_.get() != NULL); - ORTHANC_ASSERT(geometryProvider_->HasGeometry()); - const VolumeImageGeometry& geometry = geometryProvider_->GetImageGeometry(); - return geometry; - } - - RtViewerApp::RtViewerApp() - : undoStack_(new UndoStack) - { - // Create the volumes that will be filled later on - ctVolume_ = boost::make_shared(); - doseVolume_ = boost::make_shared(); - } - - boost::shared_ptr RtViewerApp::Create() - { - boost::shared_ptr thisOne(new RtViewerApp()); - return thisOne; - } - - void RtViewerApp::DisableTracker() - { - if (activeTracker_) - { - activeTracker_->Cancel(); - activeTracker_.reset(); - } - } - - void RtViewerApp::CreateView(const std::string& canvasId, VolumeProjection projection) - { - boost::shared_ptr - view(new RtViewerView(shared_from_this(), canvasId, projection)); - - view->RegisterMessages(); - - view->CreateLayers(ctLoader_, doseLoader_, doseVolume_, rtstructLoader_); - - views_.push_back(view); - } - - void RtViewerApp::CreateLoaders() - { - // the viewport hosts the scene - { - // "true" means use progressive quality (jpeg 50 --> jpeg 90 --> 16-bit raw) - // "false" means only using hi quality - // TODO: add flag for quality - ctLoader_ = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume_, true); - - // better priority for CT vs dose and struct - ctLoader_->SetSchedulingPriority(-100); - - - // we need to store the CT loader to ask from geometry details later on when geometry is loaded - geometryProvider_ = ctLoader_; - - doseLoader_ = OrthancMultiframeVolumeLoader::Create(*loadersContext_, doseVolume_); - rtstructLoader_ = DicomStructureSetLoader::Create(*loadersContext_); - } - - /** - Register for notifications issued by the loaders - */ - - Register - (*ctLoader_, &RtViewerApp::HandleGeometryReady); - - Register - (*ctLoader_, &RtViewerApp::HandleCTLoaded); - - Register - (*ctLoader_, &RtViewerApp::HandleCTContentUpdated); - - Register - (*doseLoader_, &RtViewerApp::HandleDoseLoaded); - - Register - (*rtstructLoader_, &RtViewerApp::HandleStructuresReady); - - Register - (*rtstructLoader_, &RtViewerApp::HandleStructuresUpdated); - } - - void RtViewerApp::StartLoaders() - { - ORTHANC_ASSERT(HasArgument("ctseries") && HasArgument("rtdose") && HasArgument("rtstruct")); - - LOG(INFO) << "About to load:"; - - if (GetArgument("ctseries") == "") - { - LOG(INFO) << " CT : "; - } - else - { - LOG(INFO) << " CT : " << GetArgument("ctseries"); - ctLoader_->LoadSeries(GetArgument("ctseries")); - } - - if (GetArgument("rtdose") == "") - { - LOG(INFO) << " RTDOSE : "; - } - else - { - LOG(INFO) << " RTDOSE : " << GetArgument("rtdose"); - doseLoader_->LoadInstance(GetArgument("rtdose")); - } - - if (GetArgument("rtstruct") == "") - { - LOG(INFO) << " RTSTRUCT : : "; - } - else - { - LOG(INFO) << " RTSTRUCT : : " << GetArgument("rtstruct"); - rtstructLoader_->LoadInstanceFullVisibility(GetArgument("rtstruct")); - } - } - - void RtViewerApp::HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message) - { - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->RetrieveGeometry(); - } - FitContent(); - UpdateLayersInAllViews(); - } - - void RtViewerApp::FitContent() - { - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->FitContent(); - } - } - - void RtViewerApp::UpdateLayersInAllViews() - { - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->UpdateLayers(); - } - } - - void RtViewerApp::HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message) - { - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->RetrieveGeometry(); - } - UpdateLayersInAllViews(); - } - - void RtViewerApp::HandleCTContentUpdated(const DicomVolumeImage::ContentUpdatedMessage& message) - { - UpdateLayersInAllViews(); - } - - void RtViewerApp::HandleDoseLoaded(const DicomVolumeImage::ContentUpdatedMessage& message) - { - //TODO: compute dose extent, with outlier rejection - UpdateLayersInAllViews(); - } - - void RtViewerApp::HandleStructuresReady(const DicomStructureSetLoader::StructuresReady& message) - { - UpdateLayersInAllViews(); - } - - void RtViewerApp::HandleStructuresUpdated(const DicomStructureSetLoader::StructuresUpdated& message) - { - UpdateLayersInAllViews(); - } - - void RtViewerApp::SetArgument(const std::string& key, const std::string& value) - { - if (key == "loglevel") - OrthancStoneHelpers::SetLogLevel(value); - else - arguments_[key] = value; - } - - std::string RtViewerApp::GetArgument(const std::string& key) const - { - std::map::const_iterator found = arguments_.find(key); - if (found == arguments_.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return found->second; - } - } - - bool RtViewerApp::HasArgument(const std::string& key) const - { - return (arguments_.find(key) != arguments_.end()); - } -} - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Common/RtViewerApp.h --- a/OrthancStone/Samples/Common/RtViewerApp.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include "../../Sources/Loaders/DicomStructureSetLoader.h" -#include "../../Sources/Loaders/ILoadersContext.h" -#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h" -#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h" -#include "../../Sources/Messages/IMessageEmitter.h" -#include "../../Sources/Messages/IObserver.h" -#include "../../Sources/Messages/ObserverBase.h" -#include "../../Sources/Oracle/OracleCommandExceptionMessage.h" -#include "../../Sources/Scene2DViewport/ViewportController.h" -#include "../../Sources/Viewport/IViewport.h" -#include "../../Sources/Volumes/DicomVolumeImage.h" - -#include -#include -#include - -#if ORTHANC_ENABLE_SDL -#include -#endif - -namespace OrthancStone -{ - class OpenGLCompositor; - class IVolumeSlicer; - class ILayerStyleConfigurator; - class DicomStructureSetLoader; - class IOracle; - class ThreadedOracle; - class VolumeSceneLayerSource; - class SdlOpenGLViewport; - class RtViewerView; - - static const unsigned int FONT_SIZE_0 = 32; - static const unsigned int FONT_SIZE_1 = 24; - - class Scene2D; - class UndoStack; - - /** - This application subclasses IMessageEmitter to use a mutex before forwarding Oracle messages (that - can be sent from multiple threads) - */ - class RtViewerApp : public ObserverBase - { - public: - - void PrepareScene(); - -#if ORTHANC_ENABLE_SDL - public: - void RunSdl(int argc, char* argv[]); - void SdlRunLoop(const std::vector >& views, - OrthancStone::DefaultViewportInteractor& interactor); - private: - void ProcessOptions(int argc, char* argv[]); - void HandleApplicationEvent(const SDL_Event& event); -#elif ORTHANC_ENABLE_WASM - public: - void RunWasm(); -#else -# error Either ORTHANC_ENABLE_SDL or ORTHANC_ENABLE_WASM must be enabled -#endif - - public: - void DisableTracker(); - - /** - Called by command-line option processing or when parsing the URL - parameters. - */ - void SetArgument(const std::string& key, const std::string& value); - - const VolumeImageGeometry& GetMainGeometry(); - - static boost::shared_ptr Create(); - - void CreateView(const std::string& canvasId, VolumeProjection projection); - - protected: - RtViewerApp(); - - private: - void CreateLoaders(); - void StartLoaders(); - void SelectNextTool(); - - // argument handling - // SetArgument is above (public section) - std::map arguments_; - - std::string GetArgument(const std::string& key) const; - bool HasArgument(const std::string& key) const; - - /** - This adds the command at the top of the undo stack - */ - //void Commit(boost::shared_ptr cmd); - void Undo(); - void Redo(); - - void HandleGeometryReady(const DicomVolumeImage::GeometryReadyMessage& message); - - // TODO: wire this - void HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message); - void HandleCTContentUpdated(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message); - void HandleDoseLoaded(const OrthancStone::DicomVolumeImage::ContentUpdatedMessage& message); - void HandleStructuresReady(const OrthancStone::DicomStructureSetLoader::StructuresReady& message); - void HandleStructuresUpdated(const OrthancStone::DicomStructureSetLoader::StructuresUpdated& message); - - - private: - void RetrieveGeometry(); - void FitContent(); - void InvalidateAllViewports(); - void UpdateLayersInAllViews(); - - private: - boost::shared_ptr ctVolume_; - boost::shared_ptr doseVolume_; - - std::vector > views_; - - boost::shared_ptr ctLoader_; - boost::shared_ptr doseLoader_; - boost::shared_ptr rtstructLoader_; - - /** encapsulates resources shared by loaders */ - boost::shared_ptr loadersContext_; - - /** - another interface to the ctLoader object (that also implements the IVolumeSlicer interface), that serves as the - reference for the geometry (position and dimensions of the volume + size of each voxel). It could be changed to be - the dose instead, but the CT is chosen because it usually has a better spatial resolution. - */ - boost::shared_ptr geometryProvider_; - - - boost::shared_ptr activeTracker_; - - boost::shared_ptr undoStack_; - }; - -} - - - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Common/RtViewerView.cpp --- a/OrthancStone/Samples/Common/RtViewerView.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,352 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -// Sample app -#include "RtViewerView.h" -#include "RtViewerApp.h" -#include "SampleHelpers.h" - -#include - -// Stone of Orthanc -#include "../../Sources/Oracle/GetOrthancWebViewerJpegCommand.h" -#include "../../Sources/Scene2D/CairoCompositor.h" -#include "../../Sources/Scene2D/ColorTextureSceneLayer.h" -#include "../../Sources/Scene2D/GrayscaleStyleConfigurator.h" -#include "../../Sources/Scene2D/LookupTableStyleConfigurator.h" -#include "../../Sources/Scene2D/OpenGLCompositor.h" -#include "../../Sources/Scene2D/PanSceneTracker.h" -#include "../../Sources/Scene2D/RotateSceneTracker.h" -#include "../../Sources/Scene2D/ZoomSceneTracker.h" -#include "../../Sources/Scene2DViewport/CreateAngleMeasureTracker.h" -#include "../../Sources/Scene2DViewport/CreateLineMeasureTracker.h" -#include "../../Sources/Scene2DViewport/IFlexiblePointerTracker.h" -#include "../../Sources/Scene2DViewport/MeasureTool.h" -#include "../../Sources/Scene2DViewport/PredeclaredTypes.h" -#include "../../Sources/Scene2DViewport/UndoStack.h" -#include "../../Sources/StoneException.h" -#include "../../Sources/StoneInitialization.h" -#include "../../Sources/Volumes/DicomVolumeImageMPRSlicer.h" -#include "../../Sources/Volumes/VolumeSceneLayerSource.h" - -// Orthanc -#include // For std::unique_ptr<> -#include -#include - -// System -#include -#include -#include - -#include - - -namespace OrthancStone -{ - boost::shared_ptr RtViewerView::GetApp() - { - return app_.lock(); - } - - void RtViewerView::DisplayInfoText() - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - - // do not try to use stuff too early! - OrthancStone::ICompositor& compositor = lock->GetCompositor(); - - std::stringstream msg; - - for (std::map::const_iterator kv = infoTextMap_.begin(); - kv != infoTextMap_.end(); ++kv) - { - msg << kv->first << " : " << kv->second << std::endl; - } - std::string msgS = msg.str(); - - TextSceneLayer* layerP = NULL; - if (scene.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX)) - { - TextSceneLayer& layer = dynamic_cast( - scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); - layerP = &layer; - } - else - { - std::unique_ptr layer(new TextSceneLayer); - layerP = layer.get(); - layer->SetColor(0, 255, 0); - layer->SetFontIndex(1); - layer->SetBorder(20); - layer->SetAnchor(BitmapAnchor_TopLeft); - //layer->SetPosition(0,0); - scene.SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release()); - } - // position the fixed info text in the upper right corner - layerP->SetText(msgS.c_str()); - double cX = compositor.GetCanvasWidth() * (-0.5); - double cY = compositor.GetCanvasHeight() * (-0.5); - scene.GetCanvasToSceneTransform().Apply(cX, cY); - layerP->SetPosition(cX, cY); - lock->Invalidate(); - } - - void RtViewerView::DisplayFloatingCtrlInfoText(const PointerEvent& e) - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - - ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform()); - - char buf[128]; - sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", - p.GetX(), p.GetY(), - e.GetMainPosition().GetX(), e.GetMainPosition().GetY()); - - if (scene.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)) - { - TextSceneLayer& layer = - dynamic_cast(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); - layer.SetText(buf); - layer.SetPosition(p.GetX(), p.GetY()); - } - else - { - std::unique_ptr layer(new TextSceneLayer); - layer->SetColor(0, 255, 0); - layer->SetText(buf); - layer->SetBorder(20); - layer->SetAnchor(BitmapAnchor_BottomCenter); - layer->SetPosition(p.GetX(), p.GetY()); - scene.SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release()); - } - } - - void RtViewerView::HideInfoText() - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - - scene.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX); - } - - void RtViewerView::OnSceneTransformChanged( - const ViewportController::SceneTransformChanged& message) - { - DisplayInfoText(); - } - - void RtViewerView::Invalidate() - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - - void RtViewerView::FitContent() - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - - void RtViewerView::Scroll(int delta) - { - if (!planes_.empty()) - { - int next = 0; - int temp = static_cast(currentPlane_) + delta; - - if (temp < 0) - { - next = 0; - } - else if (temp >= static_cast(planes_.size())) - { - next = static_cast(planes_.size()) - 1; - } - else - { - next = static_cast(temp); - } - LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next; - - if (next != static_cast(currentPlane_)) - { - currentPlane_ = next; - UpdateLayers(); - } - } - } - - void RtViewerView::RetrieveGeometry() - { - const VolumeImageGeometry& geometry = GetApp()->GetMainGeometry(); - - const unsigned int depth = geometry.GetProjectionDepth(projection_); - currentPlane_ = depth / 2; - - planes_.resize(depth); - - for (unsigned int z = 0; z < depth; z++) - { - planes_[z] = geometry.GetProjectionSlice(projection_, z); - } - - UpdateLayers(); - } - - void RtViewerView::UpdateLayers() - { - std::unique_ptr lock(viewport_->Lock()); - - if (planes_.size() == 0) - { - RetrieveGeometry(); - } - - if (currentPlane_ < planes_.size()) - { - if (ctVolumeLayerSource_.get() != NULL) - { - ctVolumeLayerSource_->Update(planes_[currentPlane_]); - } - if (doseVolumeLayerSource_.get() != NULL) - { - doseVolumeLayerSource_->Update(planes_[currentPlane_]); - } - if (structLayerSource_.get() != NULL) - { - structLayerSource_->Update(planes_[currentPlane_]); - } - } - lock->Invalidate(); - } - - void RtViewerView::PrepareViewport() - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - ICompositor& compositor = lock->GetCompositor(); - - // False means we do NOT let a hi-DPI aware desktop managedr treat this as a legacy application that requires - // scaling. - controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight()); - - std::string ttf; - Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT); - compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1); - compositor.SetFont(1, ttf, FONT_SIZE_1, Orthanc::Encoding_Latin1); - } - - void RtViewerView::SetInfoDisplayMessage( - std::string key, std::string value) - { - if (value == "") - infoTextMap_.erase(key); - else - infoTextMap_[key] = value; - DisplayInfoText(); - } - - void RtViewerView::RegisterMessages() - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Register(controller, &RtViewerView::OnSceneTransformChanged); - } - - void RtViewerView::CreateLayers(boost::shared_ptr ctLoader, - boost::shared_ptr doseLoader, - boost::shared_ptr doseVolume, - boost::shared_ptr rtstructLoader) - { - /** - Configure the CT - */ - std::unique_ptr style(new GrayscaleStyleConfigurator); - style->SetLinearInterpolation(true); - - this->SetCtVolumeSlicer(ctLoader, style.release()); - - { - std::string lut; - Orthanc::EmbeddedResources::GetFileResource(lut, Orthanc::EmbeddedResources::COLORMAP_HOT); - - std::unique_ptr config(new LookupTableStyleConfigurator); - config->SetLookupTable(lut); - - boost::shared_ptr tmp(new DicomVolumeImageMPRSlicer(doseVolume)); - this->SetDoseVolumeSlicer(tmp, config.release()); - } - - this->SetStructureSet(rtstructLoader); - } - - void RtViewerView::SetCtVolumeSlicer(const boost::shared_ptr& volume, - OrthancStone::ILayerStyleConfigurator* style) - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - int depth = scene.GetMaxDepth() + 1; - - ctVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); - - if (style != NULL) - { - ctVolumeLayerSource_->SetConfigurator(style); - } - - ctLayer_ = depth; - } - - void RtViewerView::SetDoseVolumeSlicer(const boost::shared_ptr& volume, - OrthancStone::ILayerStyleConfigurator* style) - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - int depth = scene.GetMaxDepth() + 1; - - doseVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); - - if (style != NULL) - { - doseVolumeLayerSource_->SetConfigurator(style); - } - } - - void RtViewerView::SetStructureSet(const boost::shared_ptr& volume) - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - int depth = scene.GetMaxDepth() + 1; - - structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); - } -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Common/RtViewerView.h --- a/OrthancStone/Samples/Common/RtViewerView.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include "../../Sources/Loaders/DicomStructureSetLoader.h" -#include "../../Sources/Loaders/ILoadersContext.h" -#include "../../Sources/Loaders/OrthancMultiframeVolumeLoader.h" -#include "../../Sources/Loaders/OrthancSeriesVolumeProgressiveLoader.h" -#include "../../Sources/Messages/IMessageEmitter.h" -#include "../../Sources/Messages/IObserver.h" -#include "../../Sources/Messages/ObserverBase.h" -#include "../../Sources/Oracle/OracleCommandExceptionMessage.h" -#include "../../Sources/Scene2DViewport/ViewportController.h" -#include "../../Sources/Viewport/IViewport.h" -#include "../../Sources/Volumes/DicomVolumeImage.h" -#include "../../Sources/Volumes/VolumeSceneLayerSource.h" - -#include -#include -#include - -namespace OrthancStone -{ - class RtViewerApp; - - class RtViewerView : public ObserverBase - { - public: - RtViewerView(boost::weak_ptr app, - const std::string& canvasId, - VolumeProjection projection) - : app_(app) - , currentPlane_(0) - , projection_(projection) - , ctLayer_(0) - { - viewport_ = CreateViewport(canvasId); - FLOATING_INFOTEXT_LAYER_ZINDEX = 6; - FIXED_INFOTEXT_LAYER_ZINDEX = 7; - } - - /** - This method is called when the scene transform changes. It allows to - recompute the visual elements whose content depend upon the scene transform - */ - void OnSceneTransformChanged( - const ViewportController::SceneTransformChanged& message); - - /** - This method will ask the VolumeSceneLayerSource, that are responsible to - generated 2D content based on a volume and a cutting plane, to regenerate - it. This is required if the volume itself changes (during loading) or if - the cutting plane is changed - */ - void UpdateLayers(); - - void Refresh(); - - void TakeScreenshot( - const std::string& target, - unsigned int canvasWidth, - unsigned int canvasHeight); - - void Scroll(int delta); - - void Invalidate(); - void FitContent(); - void RetrieveGeometry(); - void PrepareViewport(); - void RegisterMessages(); - -#if ORTHANC_ENABLE_SDL == 1 - void EnableGLDebugOutput(); -#endif - - void CreateLayers(boost::shared_ptr ctLoader, - boost::shared_ptr doseLoader, - boost::shared_ptr doseVolume, - boost::shared_ptr rtstructLoader); - - boost::shared_ptr GetViewport() - { - return viewport_; - } - - int GetCtLayerIndex() const - { - return ctLayer_; - } - - private: - void SetInfoDisplayMessage(std::string key, std::string value); - boost::shared_ptr GetApp(); - boost::shared_ptr CreateViewport(const std::string& canvasId); - void DisplayInfoText(); - void HideInfoText(); - void DisplayFloatingCtrlInfoText(const PointerEvent& e); - - void SetCtVolumeSlicer(const boost::shared_ptr& volume, - ILayerStyleConfigurator* style); - - void SetDoseVolumeSlicer(const boost::shared_ptr& volume, - ILayerStyleConfigurator* style); - - void SetStructureSet(const boost::shared_ptr& volume); - - private: - boost::weak_ptr app_; - boost::shared_ptr ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_; - - // collection of cutting planes for this particular view - std::vector planes_; - size_t currentPlane_; - - VolumeProjection projection_; - - std::map infoTextMap_; - - int FLOATING_INFOTEXT_LAYER_ZINDEX; - int FIXED_INFOTEXT_LAYER_ZINDEX; - boost::shared_ptr viewport_; - - int ctLayer_; - }; -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Common/SampleHelpers.h --- a/OrthancStone/Samples/Common/SampleHelpers.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include - -#include - -#include -#include - -namespace OrthancStoneHelpers -{ - inline void SetLogLevel(std::string logLevel) - { - boost::to_lower(logLevel); - if (logLevel == "warning") - { - Orthanc::Logging::EnableInfoLevel(false); - Orthanc::Logging::EnableTraceLevel(false); - } - else if (logLevel == "info") - { - Orthanc::Logging::EnableInfoLevel(true); - Orthanc::Logging::EnableTraceLevel(false); - } - else if (logLevel == "trace") - { - Orthanc::Logging::EnableInfoLevel(true); - Orthanc::Logging::EnableTraceLevel(true); - } - else - { - std::cerr << "Unknown log level \"" << logLevel << "\". Will use TRACE as default!"; - Orthanc::Logging::EnableInfoLevel(true); - Orthanc::Logging::EnableTraceLevel(true); - } - } -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/README.md --- a/OrthancStone/Samples/README.md Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,267 +0,0 @@ -General -======= -These samples assume that a recent version of Orthanc is checked out in an -`orthanc` folder next to the `orthanc-stone` folder. Let's call the top folder -the `devroot` folder. This name does not matter and is not used anywhere. - -Here's the directory layout that we suggest: - -``` -devroot/ - | - +- orthanc/ - | - +- orthanc-stone/ - | - ... -``` - - Orthanc can be retrieved with: - ``` - hg clone https://hg.orthanc-server.com/orthanc - ``` - -Furthermore, the samples usually assume that an Orthanc is running locally, -without authentication, on port 8042. The samples can easily be tweaked if -your setup is different. - -When Dicom resources are to be displayed, their IDs can be supplied in the -various ways suitable for the platform (command-line arguments, URL parameters -or through the GUI) - - -This repo contains two sample projects: - -SingleFrameViewer ------------------ - -This sample application displays a single frame of a Dicom instance that can -be loaded from Orthanc, either by using the Orthanc REST API or through the -Dicomweb server functionality of Orthanc. - -RtViewer --------- - -This sample application displays set of Radiotherapy data: -- a CT scan -- an RT-Dose -- an RT-Struct - -The WebAssembly version displays 3 viewports with MPR data -while the SDL sample displays a single viewport. - - -WebAssembly samples -=================== - -Building the WebAssembly samples require the Emscripten SDK -(https://emscripten.org/). This SDK goes far beyond the simple compilation to -the wasm (Web Assembly) bytecode and provides a comprehensive library that -eases porting native C and C++ programs and libraries. The Emscripten SDK also -makes it easy to generate the companion Javascript files requires to use a -wasm module in a web application. - -Although Emscripten runs on all major platforms, Stone of Orthanc is developed -and tested with the Linux version of Emscripten. - -Emscripten runs perfectly fine under the Windows Subsystem for Linux (that is -the environment used quite often by the Stone of Orthanc team) - -**Important note:** The following examples **and the build scripts** will -assume that you have installed the Emscripten SDK in `~/apps/emsdk`. - -The following packages should get you going (a Debian-like distribution such -as Debian or Ubuntu is assumed) - -``` -sudo apt-get update -sudo apt-get install -y build-essential curl wget git python cmake pkg-config -sudo apt-get install -y mercurial unzip npm ninja-build p7zip-full gettext-base -``` - -To build the Wasm samples, just launch the `build-wasm-samples.sh` script from -this folder. Optionaly, you can pass the build type as an argument. -We suggest that you do *not* use the `Debug` configuration unless you really -need it, for the additional checks that are made will lead to a very long -build time and much slower execution (more severe than with a native non-wasm -target) - -In order to run the sample, you may serve it with the ServeFolders plugin. -You can i.e: add such a section in your orthanc configuration file: - -``` -{ - "Plugins" : ["LibServeFolders.so], - "ServeFolders" : { - "/single-frame-viewer" : "..../out/install-stone-wasm-samples-RelWithDebInfo/SingleFrameViewer", - "/rt-viewer": "..../out/install-stone-wasm-samples-RelWithDebInfo/RtViewer" - } -} -``` - -You'll then be able to open the single-frame-viewer demo at `http://localhost:8042/single-frame-viewer/index.html` - -The rt-viewer demo at -`http://localhost:8044/rt-viewer/index.html?ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9`. Note that you must provide 3 ids in the url: - -- the CT series Orthanc ID -- the RT-Dose instance Orthanc ID -- the RT-Struct instance Orthanc ID - - -RtViewer ------------------ - -This sample application displays three MPR views of a CT+RTDOSE+RTSTRUCT dataset, loaded from Orthanc. The Orthanc IDs of the dataset must be supplied as URL parameters like: - -``` -http://localhost:9979/stone-rtviewer/index.html?loglevel=info&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 -``` - -(notice the loglevel parameter that can be `warning`, `info` or `trace`, in increasing verbosity order) - -This sample uses plain Javascript and requires the -Emscripten toolchain and cmake, in addition to a few standard packages. - -To build it, just launch the `build-wasm-RtViewer.sh` script from -this folder. Optionaly, you can pass the build type as an argument. -We suggest that you do *not* use the `Debug` configuration unless you really -need it, for the additional checks that are made will lead to a very long -build time and much slower execution (more severe than with a native non-wasm -target) - -In order to run the sample, you may serve it with the ServeFolders plugin. -You can i.e: add such a section in your orthanc configuration file: - -``` -{ - "Plugins" : ["LibServeFolders.so], - "ServeFolders" : { - "/rt-viewer" : "..../out/install-stone-wasm-RtViewer-RelWithDebInfo" - } -} -``` - -You'll then be able to open the demo at `http://localhost:8042/rt-viewer/index.html` - - -RtViewerPlugin ---------------- -This C++ plugin allows to extend the Orthanc Explorer to add a button labeled "Stone RT Viewer" -in the series page. - -It also embeds and serves the RT Viewer files and is thus a standalone way of using this viewer. - -Please note that building this plugin requires that the RtViewer be built inside the wasm-binaries -folder of the repo. - -This will automatically be the case if you use the `/OrthancStone/Samples/WebAssembly/docker-build.sh` script. - -If you use the `build-wasm-samples.sh` script above, you will have the copy `RtViewer` **folder** -from `/out/install-stone-wasm-RtViewer-RelWithDebInfo` to `/wasm-binaries/`. - -TL;DR: Build like this (assuming `~/orthanc-stone` is the repo ): - -``` -~/orthanc-stone/OrthancStone/Samples/WebAssembly/docker-build.sh -~/orthanc-stone/OrthancStone/Samples/RtViewerPlugin/docker-build.sh -``` - -Once this is done, the plugin can be found in: - -``` -~/orthanc-stone/wasm-binaries/share/orthanc/plugins/libRtViewerPlugin.so -``` - -Add this path to the `"Plugins"` section of your Orthanc configuration, start Orthanc, and you -should now see a "Stone MPR RT Viewer" button in the Orthanc Explorer, at the *series* level. - -Open it on a CT series, and the RTSTRUCT and RTDOSE series of the same study will be loaded in -the viewer. - -Native samples -================= - -### Windows build - -Here's how to build the SdlSimpleViewer example using Visual Studio 2019 -(the shell is Powershell, but the legacy shell can also be used with some -tweaks). This example is meant to be launched from the folder above -orthanc-stone. - -``` - # create the build folder and navigate to it - $buildDir = "build-stone-sdlviewer-msvc16-x64" - - if (-not (Test-Path $buildDir)) { - mkdir -p $buildDir | Out-Null - } - - cd $buildDir - - # perform the configuration - cmake -G "Visual Studio 16 2019" -A x64 ` - -DMSVC_MULTIPLE_PROCESSES=ON ` - -DALLOW_DOWNLOADS=ON ` - -DSTATIC_BUILD=ON ` - -DOPENSSL_NO_CAPIENG=ON ` - ../orthanc-stone/OrthancStone/Samples/Sdl - - $solutionPath = ls -filter *.sln - Write-Host "Solution file(s) available at: $solutionPath" -``` - -The initial configuration step will be quite lengthy, for CMake needs to -setup its internal cache based on your environment and build tools. - -Subsequent runs will be several orders of magnitude faster! - -One the solution (.sln) file is ready, you can open it using the Visual Studio -IDE and choose Build --> Build solution. - -An alternative is to execute `cmake --build .` in the build folder created by -the script. - -In order to run the sample, make sure you've an Orthanc server running i.e. on -port 8042 and launch: - -``` -./SdlSimpleViewer --orthanc http://localhost:8042 --instance 7fc84013-abef174e-3354ca83-b9cdb2a4-f1a74368 - -./RtViewerSdl --orthanc http://localhost:8042 --ctseries a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --rtdose 830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --rtstruct 54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 -``` - -RtViewer ---------------- - -### Windows build - -Here's how to build the SdlSimpleViewer example using Visual Studio 2019 -(the shell is Powershell, but the legacy shell can also be used with some -tweaks). This example is meant to be launched from the folder above -orthanc-stone. - -``` - $buildRootDir = "out" - $buildDirName = "build-stone-sdl-RtViewer-msvc16-x64" - $buildDir = Join-Path -Path $buildRootDir -ChildPath $buildDirName - - if (-not (Test-Path $buildDir)) { - mkdir -p $buildDir | Out-Null - } - - cd $buildDir - - cmake -G "Visual Studio 16 2019" -A x64 ` - -DMSVC_MULTIPLE_PROCESSES=ON ` - -DALLOW_DOWNLOADS=ON ` - -DSTATIC_BUILD=ON ` - -DOPENSSL_NO_CAPIENG=ON ` - ../../orthanc-stone/OrthancStone/Samples/Sdl/RtViewer -``` - -Executing `cmake --build .` in the build folder will launch the Microsoft -builder on the solution. - -Alternatively, you can open and build the solution using Visual Studio 2019. - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/CMakeLists.txt --- a/OrthancStone/Samples/RtViewerPlugin/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(StoneWebViewerPlugin) - -set(ORTHANC_PLUGIN_VERSION "mainline") - -if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - -set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../../wasm-binaries/RtViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder") - -# Parameters of the build -set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - - -# Advanced parameters to fine-tune linking against system libraries -set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") -set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked") -mark_as_advanced(ORTHANC_FRAMEWORK_STATIC) - - -# Download and setup the Orthanc framework -include(${CMAKE_SOURCE_DIR}/../../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake) - -include_directories(${ORTHANC_FRAMEWORK_ROOT}) - -if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES}) - -else() - include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake) - set(ENABLE_MODULE_IMAGES OFF) - set(ENABLE_MODULE_JOBS OFF) - set(ENABLE_MODULE_DICOM OFF) - include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake) -endif() - -include(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake) - - -if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) - include_directories(${CMAKE_SOURCE_DIR}/Resources/OrthancSdk-1.0.0) -else () - CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) - if (NOT HAVE_ORTHANC_H) - message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") - endif() -endif() - - -add_definitions( - -DHAS_ORTHANC_EXCEPTION=1 - -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}" - -DPLUGIN_NAME="stone-rtviewer" - ) - - -EmbedResources( - # Web Viewer Folders - # IMAGES ${STONE_BINARIES_WEB_VIEWER}/img/ - # WEB_APPLICATION ${CMAKE_SOURCE_DIR}/../WebApplication - - # Explorer extension code - ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/OrthancExplorer.js - - # RtViewer individual files - RT_VIEWER_WASM_JS ${STONE_BINARIES}/RtViewerWasm.js - RT_VIEWER_WASM ${STONE_BINARIES}/RtViewerWasm.wasm - RT_VIEWER_WASM_APP_JS ${STONE_BINARIES}/RtViewerWasmApp.js - RT_VIEWER_INDEX_HTML ${STONE_BINARIES}/index.html - ) - -add_library(RtViewerPlugin SHARED - Plugin.cpp - ${AUTOGENERATED_SOURCES} - ${CMAKE_SOURCE_DIR}/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp - ${ORTHANC_CORE_SOURCES} - ) - -set_target_properties(RtViewerPlugin PROPERTIES - VERSION ${ORTHANC_PLUGIN_VERSION} - SOVERSION ${ORTHANC_PLUGIN_VERSION}) - -install( - TARGETS RtViewerPlugin - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - ) diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/OrthancExplorer.js --- a/OrthancStone/Samples/RtViewerPlugin/OrthancExplorer.js Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -$('#series').live('pagebeforecreate', function() { - var b = $('') - .attr('data-role', 'button') - .attr('href', '#') - .attr('data-icon', 'search') - .attr('data-theme', 'e') - .text('Stone MPR RT Sample Viewer');`` - - b.insertBefore($('#series-delete').parent().parent()); - b.click(function() { - if ($.mobile.pageData) { - $.ajax({ - url: '../series/' + $.mobile.pageData.uuid, - dataType: 'json', - cache: false, - success: function(series) { - - // we consider that the imaging series to display is the - // current one. - // we will look for RTDOSE and RTSTRUCT instances in the - // sibling series from the same study. The first one of - // each modality will be grabbed. - let ctSeries = $.mobile.pageData.uuid; - - $.ajax({ - url: '../studies/' + series.ParentStudy, - dataType: 'json', - cache: false, - success: function(study) { - // Loop on the study series and find the first RTSTRUCT and RTDOSE instances, - // if any. - let rtStructInstance = null; - let rtDoseInstance = null; - let rtPetInstance = null; - let seriesRequests = [] - - study.Series.forEach( function(studySeriesUuid) { - let request = $.ajax({ - url: '../series/' + studySeriesUuid, - dataType: 'json', - cache: false, - }); - seriesRequests.push(request); - }); - - $.when.apply($,seriesRequests).then(function() { - [].forEach.call(arguments, function(response) { - siblingSeries = response[0] - if (siblingSeries.MainDicomTags.Modality == "RTDOSE") { - // we have found an RTDOSE series. Let's grab the first instance - if (siblingSeries.Instances.length > 0) { - if(rtDoseInstance == null) { - rtDoseInstance = siblingSeries.Instances[0]; - } - } - } - if (siblingSeries.MainDicomTags.Modality == "PT") { - // we have found an RTDOSE series. Let's grab the first instance - if (siblingSeries.Instances.length > 0) { - if(rtPetInstance == null) { - rtPetInstance = siblingSeries.Instances[0]; - } - } - } - if (siblingSeries.MainDicomTags.Modality == "RTSTRUCT") { - // we have found an RTDOSE series. Let's grab the first instance - if (siblingSeries.Instances.length > 0) { - if(rtStructInstance == null) { - rtStructInstance = siblingSeries.Instances[0]; - } - } - } - }); - let mprViewerUrl = '../stone-rtviewer/index.html?ctseries=' + ctSeries + - '&rtdose=' + rtDoseInstance + - '&rtstruct=' + rtStructInstance; - //console.log("About to open: " + mprViewerUrl); - window.open(mprViewerUrl); - }); - } - }); - } - }); - } - }); -}); - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Plugin.cpp --- a/OrthancStone/Samples/RtViewerPlugin/Plugin.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,185 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" - -#include - -#include -#include - -OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId) -{ - try - { - if (changeType == OrthancPluginChangeType_OrthancStarted) - { - Json::Value info; - if (!OrthancPlugins::RestApiGet(info, "/plugins/web-viewer", false)) - { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_InternalError, - "The Stone MPR RT viewer requires the Web Viewer plugin to be installed"); - } - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception: " << e.What(); - return static_cast(e.GetErrorCode()); - } - - return OrthancPluginErrorCode_Success; -} - -template -void ServeEmbeddedFolder(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); - - if (request->method != OrthancPluginHttpMethod_Get) - { - OrthancPluginSendMethodNotAllowed(context, output, "GET"); - } - else - { - std::string path = "/" + std::string(request->groups[0]); - const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path)); - - std::string s; - Orthanc::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str()); - - const char* resource = s.size() ? s.c_str() : NULL; - OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); - } -} - - -template -void ServeEmbeddedFile(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); - - if (request->method != OrthancPluginHttpMethod_Get) - { - OrthancPluginSendMethodNotAllowed(context, output, "GET"); - } - else - { - const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url)); - - std::string s; - Orthanc::EmbeddedResources::GetFileResource(s, file); - - const char* resource = s.size() ? s.c_str() : NULL; - OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); - } -} - - -extern "C" -{ - ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) - { - OrthancPlugins::SetGlobalContext(context); - -#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) - Orthanc::Logging::InitializePluginContext(context); -#else - Orthanc::Logging::Initialize(context); -#endif - - /* Check the version of the Orthanc core */ - if (OrthancPluginCheckVersion(context) == 0) - { - char info[1024]; - sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", - context->orthancVersion, - ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); - OrthancPluginLogError(context, info); - return -1; - } - - try - { - std::string explorer; - Orthanc::EmbeddedResources::GetFileResource( - explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); - OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); - - // RtViewer files below. - // --------------------- - // we do not serve the whole directory at once (with ServeEmbeddedFolder) - // because it contains uppercase characters that are forbidden by the - // resource embedding system - - OrthancPlugins::RegisterRestCallback - > - ("/stone-rtviewer/RtViewerWasm.js", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-rtviewer/RtViewerWasm.wasm", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-rtviewer/RtViewerWasmApp.js", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-rtviewer/index.html", true); - - OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); - } - catch (...) - { - OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin"); - return -1; - } - - return 0; - } - - - ORTHANC_PLUGINS_API void OrthancPluginFinalize() - { - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetName() - { - return PLUGIN_NAME; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() - { - return PLUGIN_VERSION; - } -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list --- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -# This is the list of the symbols that must be exported by Orthanc -# plugins, if targeting OS X - -_OrthancPluginInitialize -_OrthancPluginFinalize -_OrthancPluginGetName -_OrthancPluginGetVersion diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3383 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 . - **/ - - -#include "OrthancPluginCppWrapper.h" - -#include -#include -#include -#include -#include - - -#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) -static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin; -#endif - - -namespace OrthancPlugins -{ - static OrthancPluginContext* globalContext_ = NULL; - - - void SetGlobalContext(OrthancPluginContext* context) - { - if (context == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); - } - else if (globalContext_ == NULL) - { - globalContext_ = context; - } - else - { - ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); - } - } - - - bool HasGlobalContext() - { - return globalContext_ != NULL; - } - - - OrthancPluginContext* GetGlobalContext() - { - if (globalContext_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); - } - else - { - return globalContext_; - } - } - - - void MemoryBuffer::Check(OrthancPluginErrorCode code) - { - if (code != OrthancPluginErrorCode_Success) - { - // Prevent using garbage information - buffer_.data = NULL; - buffer_.size = 0; - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } - - - bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code) - { - if (code != OrthancPluginErrorCode_Success) - { - // Prevent using garbage information - buffer_.data = NULL; - buffer_.size = 0; - } - - if (code == OrthancPluginErrorCode_Success) - { - return true; - } - else if (code == OrthancPluginErrorCode_UnknownResource || - code == OrthancPluginErrorCode_InexistentItem) - { - return false; - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } - - - MemoryBuffer::MemoryBuffer() - { - buffer_.data = NULL; - buffer_.size = 0; - } - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - MemoryBuffer::MemoryBuffer(const void* buffer, - size_t size) - { - uint32_t s = static_cast(size); - if (static_cast(s) != size) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != - OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else - { - memcpy(buffer_.data, buffer, size); - } - } -#endif - - - void MemoryBuffer::Clear() - { - if (buffer_.data != NULL) - { - OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_); - buffer_.data = NULL; - buffer_.size = 0; - } - } - - - void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) - { - Clear(); - - buffer_.data = other.data; - buffer_.size = other.size; - - other.data = NULL; - other.size = 0; - } - - - void MemoryBuffer::Swap(MemoryBuffer& other) - { - std::swap(buffer_.data, other.buffer_.data); - std::swap(buffer_.size, other.buffer_.size); - } - - - OrthancPluginMemoryBuffer MemoryBuffer::Release() - { - OrthancPluginMemoryBuffer result = buffer_; - - buffer_.data = NULL; - buffer_.size = 0; - - return result; - } - - - void MemoryBuffer::ToString(std::string& target) const - { - if (buffer_.size == 0) - { - target.clear(); - } - else - { - target.assign(reinterpret_cast(buffer_.data), buffer_.size); - } - } - - - void MemoryBuffer::ToJson(Json::Value& target) const - { - if (buffer_.data == NULL || - buffer_.size == 0) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - const char* tmp = reinterpret_cast(buffer_.data); - - Json::Reader reader; - if (!reader.parse(tmp, tmp + buffer_.size, target)) - { - LogError("Cannot convert some memory buffer to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - bool MemoryBuffer::RestApiGet(const std::string& uri, - bool applyPlugins) - { - Clear(); - - if (applyPlugins) - { - return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str())); - } - else - { - return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str())); - } - } - - bool MemoryBuffer::RestApiGet(const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins) - { - Clear(); - - std::vector headersKeys; - std::vector headersValues; - - for (std::map::const_iterator - it = httpHeaders.begin(); it != httpHeaders.end(); it++) - { - headersKeys.push_back(it->first.c_str()); - headersValues.push_back(it->second.c_str()); - } - - return CheckHttp(OrthancPluginRestApiGet2( - GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), - (headersKeys.empty() ? NULL : &headersKeys[0]), - (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); - } - - bool MemoryBuffer::RestApiPost(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - Clear(); - - // Cast for compatibility with Orthanc SDK <= 1.5.6 - const char* b = reinterpret_cast(body); - - if (applyPlugins) - { - return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - else - { - return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - } - - - bool MemoryBuffer::RestApiPut(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - Clear(); - - // Cast for compatibility with Orthanc SDK <= 1.5.6 - const char* b = reinterpret_cast(body); - - if (applyPlugins) - { - return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - else - { - return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - } - - - bool MemoryBuffer::RestApiPost(const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPost(uri, writer.write(body), applyPlugins); - } - - - bool MemoryBuffer::RestApiPut(const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPut(uri, writer.write(body), applyPlugins); - } - - - void MemoryBuffer::CreateDicom(const Json::Value& tags, - OrthancPluginCreateDicomFlags flags) - { - Clear(); - - Json::FastWriter writer; - std::string s = writer.write(tags); - - Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags)); - } - - void MemoryBuffer::CreateDicom(const Json::Value& tags, - const OrthancImage& pixelData, - OrthancPluginCreateDicomFlags flags) - { - Clear(); - - Json::FastWriter writer; - std::string s = writer.write(tags); - - Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags)); - } - - - void MemoryBuffer::ReadFile(const std::string& path) - { - Clear(); - Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str())); - } - - - void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query) - { - Clear(); - Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query)); - } - - - void OrthancString::Assign(char* str) - { - Clear(); - - if (str != NULL) - { - str_ = str; - } - } - - - void OrthancString::Clear() - { - if (str_ != NULL) - { - OrthancPluginFreeString(GetGlobalContext(), str_); - str_ = NULL; - } - } - - - void OrthancString::ToString(std::string& target) const - { - if (str_ == NULL) - { - target.clear(); - } - else - { - target.assign(str_); - } - } - - - void OrthancString::ToJson(Json::Value& target) const - { - if (str_ == NULL) - { - LogError("Cannot convert an empty memory buffer to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - Json::Reader reader; - if (!reader.parse(str_, target)) - { - LogError("Cannot convert some memory buffer to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - void MemoryBuffer::DicomToJson(Json::Value& target, - OrthancPluginDicomToJsonFormat format, - OrthancPluginDicomToJsonFlags flags, - uint32_t maxStringLength) - { - OrthancString str; - str.Assign(OrthancPluginDicomBufferToJson - (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); - str.ToJson(target); - } - - - bool MemoryBuffer::HttpGet(const std::string& url, - const std::string& username, - const std::string& password) - { - Clear(); - return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str())); - } - - - bool MemoryBuffer::HttpPost(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password) - { - Clear(); - return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(), - body.c_str(), body.size(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str())); - } - - - bool MemoryBuffer::HttpPut(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password) - { - Clear(); - return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(), - body.empty() ? NULL : body.c_str(), - body.size(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str())); - } - - - void MemoryBuffer::GetDicomInstance(const std::string& instanceId) - { - Clear(); - Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str())); - } - - - bool HttpDelete(const std::string& url, - const std::string& username, - const std::string& password) - { - OrthancPluginErrorCode error = OrthancPluginHttpDelete - (GetGlobalContext(), url.c_str(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str()); - - if (error == OrthancPluginErrorCode_Success) - { - return true; - } - else if (error == OrthancPluginErrorCode_UnknownResource || - error == OrthancPluginErrorCode_InexistentItem) - { - return false; - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - } - - - void LogError(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogError(GetGlobalContext(), message.c_str()); - } - } - - - void LogWarning(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); - } - } - - - void LogInfo(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); - } - } - - - void OrthancConfiguration::LoadConfiguration() - { - OrthancString str; - str.Assign(OrthancPluginGetConfiguration(GetGlobalContext())); - - if (str.GetContent() == NULL) - { - LogError("Cannot access the Orthanc configuration"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - str.ToJson(configuration_); - - if (configuration_.type() != Json::objectValue) - { - LogError("Unable to read the Orthanc configuration"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - - OrthancConfiguration::OrthancConfiguration() - { - LoadConfiguration(); - } - - - OrthancConfiguration::OrthancConfiguration(bool loadConfiguration) - { - if (loadConfiguration) - { - LoadConfiguration(); - } - else - { - configuration_ = Json::objectValue; - } - } - - - std::string OrthancConfiguration::GetPath(const std::string& key) const - { - if (path_.empty()) - { - return key; - } - else - { - return path_ + "." + key; - } - } - - - bool OrthancConfiguration::IsSection(const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - return (configuration_.isMember(key) && - configuration_[key].type() == Json::objectValue); - } - - - void OrthancConfiguration::GetSection(OrthancConfiguration& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - target.path_ = GetPath(key); - - if (!configuration_.isMember(key)) - { - target.configuration_ = Json::objectValue; - } - else - { - if (configuration_[key].type() != Json::objectValue) - { - LogError("The configuration section \"" + target.path_ + - "\" is not an associative array as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - target.configuration_ = configuration_[key]; - } - } - - - bool OrthancConfiguration::LookupStringValue(std::string& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - if (configuration_[key].type() != Json::stringValue) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a string as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - target = configuration_[key].asString(); - return true; - } - - - bool OrthancConfiguration::LookupIntegerValue(int& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - switch (configuration_[key].type()) - { - case Json::intValue: - target = configuration_[key].asInt(); - return true; - - case Json::uintValue: - target = configuration_[key].asUInt(); - return true; - - default: - LogError("The configuration option \"" + GetPath(key) + - "\" is not an integer as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target, - const std::string& key) const - { - int tmp; - if (!LookupIntegerValue(tmp, key)) - { - return false; - } - - if (tmp < 0) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a positive integer as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - else - { - target = static_cast(tmp); - return true; - } - } - - - bool OrthancConfiguration::LookupBooleanValue(bool& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - if (configuration_[key].type() != Json::booleanValue) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a Boolean as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - target = configuration_[key].asBool(); - return true; - } - - - bool OrthancConfiguration::LookupFloatValue(float& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - switch (configuration_[key].type()) - { - case Json::realValue: - target = configuration_[key].asFloat(); - return true; - - case Json::intValue: - target = static_cast(configuration_[key].asInt()); - return true; - - case Json::uintValue: - target = static_cast(configuration_[key].asUInt()); - return true; - - default: - LogError("The configuration option \"" + GetPath(key) + - "\" is not an integer as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - bool OrthancConfiguration::LookupListOfStrings(std::list& target, - const std::string& key, - bool allowSingleString) const - { - assert(configuration_.type() == Json::objectValue); - - target.clear(); - - if (!configuration_.isMember(key)) - { - return false; - } - - switch (configuration_[key].type()) - { - case Json::arrayValue: - { - bool ok = true; - - for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++) - { - if (configuration_[key][i].type() == Json::stringValue) - { - target.push_back(configuration_[key][i].asString()); - } - else - { - ok = false; - } - } - - if (ok) - { - return true; - } - - break; - } - - case Json::stringValue: - if (allowSingleString) - { - target.push_back(configuration_[key].asString()); - return true; - } - - break; - - default: - break; - } - - LogError("The configuration option \"" + GetPath(key) + - "\" is not a list of strings as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - - bool OrthancConfiguration::LookupSetOfStrings(std::set& target, - const std::string& key, - bool allowSingleString) const - { - std::list lst; - - if (LookupListOfStrings(lst, key, allowSingleString)) - { - target.clear(); - - for (std::list::const_iterator - it = lst.begin(); it != lst.end(); ++it) - { - target.insert(*it); - } - - return true; - } - else - { - return false; - } - } - - - std::string OrthancConfiguration::GetStringValue(const std::string& key, - const std::string& defaultValue) const - { - std::string tmp; - if (LookupStringValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - int OrthancConfiguration::GetIntegerValue(const std::string& key, - int defaultValue) const - { - int tmp; - if (LookupIntegerValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key, - unsigned int defaultValue) const - { - unsigned int tmp; - if (LookupUnsignedIntegerValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - bool OrthancConfiguration::GetBooleanValue(const std::string& key, - bool defaultValue) const - { - bool tmp; - if (LookupBooleanValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - float OrthancConfiguration::GetFloatValue(const std::string& key, - float defaultValue) const - { - float tmp; - if (LookupFloatValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - void OrthancConfiguration::GetDictionary(std::map& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - target.clear(); - - if (!configuration_.isMember(key)) - { - return; - } - - if (configuration_[key].type() != Json::objectValue) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a string as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - Json::Value::Members members = configuration_[key].getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& value = configuration_[key][members[i]]; - - if (value.type() == Json::stringValue) - { - target[members[i]] = value.asString(); - } - else - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a dictionary mapping strings to strings"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - } - - - void OrthancImage::Clear() - { - if (image_ != NULL) - { - OrthancPluginFreeImage(GetGlobalContext(), image_); - image_ = NULL; - } - } - - - void OrthancImage::CheckImageAvailable() const - { - if (image_ == NULL) - { - LogError("Trying to access a NULL image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - OrthancImage::OrthancImage() : - image_(NULL) - { - } - - - OrthancImage::OrthancImage(OrthancPluginImage* image) : - image_(image) - { - } - - - OrthancImage::OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height) - { - image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height); - - if (image_ == NULL) - { - LogError("Cannot create an image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - - OrthancImage::OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - void* buffer) - { - image_ = OrthancPluginCreateImageAccessor - (GetGlobalContext(), format, width, height, pitch, buffer); - - if (image_ == NULL) - { - LogError("Cannot create an image accessor"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - void OrthancImage::UncompressPngImage(const void* data, - size_t size) - { - Clear(); - - image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png); - - if (image_ == NULL) - { - LogError("Cannot uncompress a PNG image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - void OrthancImage::UncompressJpegImage(const void* data, - size_t size) - { - Clear(); - image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg); - if (image_ == NULL) - { - LogError("Cannot uncompress a JPEG image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - void OrthancImage::DecodeDicomImage(const void* data, - size_t size, - unsigned int frame) - { - Clear(); - image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame); - if (image_ == NULL) - { - LogError("Cannot uncompress a DICOM image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const - { - CheckImageAvailable(); - return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_); - } - - - unsigned int OrthancImage::GetWidth() const - { - CheckImageAvailable(); - return OrthancPluginGetImageWidth(GetGlobalContext(), image_); - } - - - unsigned int OrthancImage::GetHeight() const - { - CheckImageAvailable(); - return OrthancPluginGetImageHeight(GetGlobalContext(), image_); - } - - - unsigned int OrthancImage::GetPitch() const - { - CheckImageAvailable(); - return OrthancPluginGetImagePitch(GetGlobalContext(), image_); - } - - - void* OrthancImage::GetBuffer() const - { - CheckImageAvailable(); - return OrthancPluginGetImageBuffer(GetGlobalContext(), image_); - } - - - void OrthancImage::CompressPngImage(MemoryBuffer& target) const - { - CheckImageAvailable(); - - OrthancPlugins::MemoryBuffer answer; - OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer()); - - target.Swap(answer); - } - - - void OrthancImage::CompressJpegImage(MemoryBuffer& target, - uint8_t quality) const - { - CheckImageAvailable(); - - OrthancPlugins::MemoryBuffer answer; - OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); - - target.Swap(answer); - } - - - void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const - { - CheckImageAvailable(); - OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer()); - } - - - void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, - uint8_t quality) const - { - CheckImageAvailable(); - OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); - } - - - OrthancPluginImage* OrthancImage::Release() - { - CheckImageAvailable(); - OrthancPluginImage* tmp = image_; - image_ = NULL; - return tmp; - } - - -#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 - FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) : - matcher_(NULL), - worklist_(worklist) - { - if (worklist_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - void FindMatcher::SetupDicom(const void* query, - uint32_t size) - { - worklist_ = NULL; - - matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size); - if (matcher_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - - FindMatcher::~FindMatcher() - { - // The "worklist_" field - - if (matcher_ != NULL) - { - OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_); - } - } - - - - bool FindMatcher::IsMatch(const void* dicom, - uint32_t size) const - { - int32_t result; - - if (matcher_ != NULL) - { - result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size); - } - else if (worklist_ != NULL) - { - result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size); - } - else - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - if (result == 0) - { - return false; - } - else if (result == 1) - { - return true; - } - else - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - -#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ - - void AnswerJson(const Json::Value& value, - OrthancPluginRestOutput* output - ) - { - Json::StyledWriter writer; - std::string bodyString = writer.write(value); - - OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json"); - } - - void AnswerString(const std::string& answer, - const char* mimeType, - OrthancPluginRestOutput* output - ) - { - OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType); - } - - void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output) - { - OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError); - } - - void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods); - } - - bool RestApiGetString(std::string& result, - const std::string& uri, - bool applyPlugins) - { - MemoryBuffer answer; - if (!answer.RestApiGet(uri, applyPlugins)) - { - return false; - } - else - { - answer.ToString(result); - return true; - } - } - - bool RestApiGetString(std::string& result, - const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins) - { - MemoryBuffer answer; - if (!answer.RestApiGet(uri, httpHeaders, applyPlugins)) - { - return false; - } - else - { - answer.ToString(result); - return true; - } - } - - - - bool RestApiGet(Json::Value& result, - const std::string& uri, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiGet(uri, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) - { - answer.ToJson(result); - } - return true; - } - } - - - bool RestApiPost(std::string& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) - { - result.assign(answer.GetData(), answer.GetSize()); - } - return true; - } - } - - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) - { - answer.ToJson(result); - } - return true; - } - } - - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPost(result, uri, writer.write(body), applyPlugins); - } - - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiPut(uri, body, bodySize, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response - { - answer.ToJson(result); - } - return true; - } - } - - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPut(result, uri, writer.write(body), applyPlugins); - } - - - bool RestApiDelete(const std::string& uri, - bool applyPlugins) - { - OrthancPluginErrorCode error; - - if (applyPlugins) - { - error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str()); - } - else - { - error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str()); - } - - if (error == OrthancPluginErrorCode_Success) - { - return true; - } - else if (error == OrthancPluginErrorCode_UnknownResource || - error == OrthancPluginErrorCode_InexistentItem) - { - return false; - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - } - - - void ReportMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision) - { - LogError("Your version of the Orthanc core (" + - std::string(GetGlobalContext()->orthancVersion) + - ") is too old to run this plugin (version " + - boost::lexical_cast(major) + "." + - boost::lexical_cast(minor) + "." + - boost::lexical_cast(revision) + - " is required)"); - } - - - bool CheckMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision) - { - if (!HasGlobalContext()) - { - LogError("Bad Orthanc context in the plugin"); - return false; - } - - if (!strcmp(GetGlobalContext()->orthancVersion, "mainline")) - { - // Assume compatibility with the mainline - return true; - } - - // Parse the version of the Orthanc core - int aa, bb, cc; - if ( -#ifdef _MSC_VER - sscanf_s -#else - sscanf -#endif - (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || - aa < 0 || - bb < 0 || - cc < 0) - { - return false; - } - - unsigned int a = static_cast(aa); - unsigned int b = static_cast(bb); - unsigned int c = static_cast(cc); - - // Check the major version number - - if (a > major) - { - return true; - } - - if (a < major) - { - return false; - } - - - // Check the minor version number - assert(a == major); - - if (b > minor) - { - return true; - } - - if (b < minor) - { - return false; - } - - // Check the patch level version number - assert(a == major && b == minor); - - if (c >= revision) - { - return true; - } - else - { - return false; - } - } - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) - const char* AutodetectMimeType(const std::string& path) - { - const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str()); - - if (mime == NULL) - { - // Should never happen, just for safety - return "application/octet-stream"; - } - else - { - return mime; - } - } -#endif - - -#if HAS_ORTHANC_PLUGIN_PEERS == 1 - size_t OrthancPeers::GetPeerIndex(const std::string& name) const - { - size_t index; - if (LookupName(index, name)) - { - return index; - } - else - { - LogError("Inexistent peer: " + name); - ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); - } - } - - - OrthancPeers::OrthancPeers() : - peers_(NULL), - timeout_(0) - { - peers_ = OrthancPluginGetPeers(GetGlobalContext()); - - if (peers_ == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - - uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_); - - for (uint32_t i = 0; i < count; i++) - { - const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i); - if (name == NULL) - { - OrthancPluginFreePeers(GetGlobalContext(), peers_); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - - index_[name] = i; - } - } - - - OrthancPeers::~OrthancPeers() - { - if (peers_ != NULL) - { - OrthancPluginFreePeers(GetGlobalContext(), peers_); - } - } - - - bool OrthancPeers::LookupName(size_t& target, - const std::string& name) const - { - Index::const_iterator found = index_.find(name); - - if (found == index_.end()) - { - return false; - } - else - { - target = found->second; - return true; - } - } - - - std::string OrthancPeers::GetPeerName(size_t index) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - else - { - const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast(index)); - if (s == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - return s; - } - } - } - - - std::string OrthancPeers::GetPeerUrl(size_t index) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - else - { - const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast(index)); - if (s == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - return s; - } - } - } - - - std::string OrthancPeers::GetPeerUrl(const std::string& name) const - { - return GetPeerUrl(GetPeerIndex(name)); - } - - - bool OrthancPeers::LookupUserProperty(std::string& value, - size_t index, - const std::string& key) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - else - { - const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast(index), key.c_str()); - if (s == NULL) - { - return false; - } - else - { - value.assign(s); - return true; - } - } - } - - - bool OrthancPeers::LookupUserProperty(std::string& value, - const std::string& peer, - const std::string& key) const - { - return LookupUserProperty(value, GetPeerIndex(peer), key); - } - - - bool OrthancPeers::DoGet(MemoryBuffer& target, - size_t index, - const std::string& uri) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Get, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - target.Swap(answer); - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoGet(MemoryBuffer& target, - const std::string& name, - const std::string& uri) const - { - size_t index; - return (LookupName(index, name) && - DoGet(target, index, uri)); - } - - - bool OrthancPeers::DoGet(Json::Value& target, - size_t index, - const std::string& uri) const - { - MemoryBuffer buffer; - - if (DoGet(buffer, index, uri)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoGet(Json::Value& target, - const std::string& name, - const std::string& uri) const - { - MemoryBuffer buffer; - - if (DoGet(buffer, name, uri)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPost(MemoryBuffer& target, - const std::string& name, - const std::string& uri, - const std::string& body) const - { - size_t index; - return (LookupName(index, name) && - DoPost(target, index, uri, body)); - } - - - bool OrthancPeers::DoPost(Json::Value& target, - size_t index, - const std::string& uri, - const std::string& body) const - { - MemoryBuffer buffer; - - if (DoPost(buffer, index, uri, body)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPost(Json::Value& target, - const std::string& name, - const std::string& uri, - const std::string& body) const - { - MemoryBuffer buffer; - - if (DoPost(buffer, name, uri, body)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPost(MemoryBuffer& target, - size_t index, - const std::string& uri, - const std::string& body) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Post, uri.c_str(), - 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - target.Swap(answer); - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPut(size_t index, - const std::string& uri, - const std::string& body) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Put, uri.c_str(), - 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPut(const std::string& name, - const std::string& uri, - const std::string& body) const - { - size_t index; - return (LookupName(index, name) && - DoPut(index, uri, body)); - } - - - bool OrthancPeers::DoDelete(size_t index, - const std::string& uri) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Delete, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoDelete(const std::string& name, - const std::string& uri) const - { - size_t index; - return (LookupName(index, name) && - DoDelete(index, uri)); - } -#endif - - - - - - /****************************************************************** - ** JOBS - ******************************************************************/ - -#if HAS_ORTHANC_PLUGIN_JOB == 1 - void OrthancJob::CallbackFinalize(void* job) - { - if (job != NULL) - { - delete reinterpret_cast(job); - } - } - - - float OrthancJob::CallbackGetProgress(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast(job)->progress_; - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetContent(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast(job)->content_.c_str(); - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetSerialized(void* job) - { - assert(job != NULL); - - try - { - const OrthancJob& tmp = *reinterpret_cast(job); - - if (tmp.hasSerialized_) - { - return tmp.serialized_.c_str(); - } - else - { - return NULL; - } - } - catch (...) - { - return 0; - } - } - - - OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast(job)->Step(); - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) - { - return OrthancPluginJobStepStatus_Failure; - } - catch (...) - { - return OrthancPluginJobStepStatus_Failure; - } - } - - - OrthancPluginErrorCode OrthancJob::CallbackStop(void* job, - OrthancPluginJobStopReason reason) - { - assert(job != NULL); - - try - { - reinterpret_cast(job)->Stop(reason); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) - { - assert(job != NULL); - - try - { - reinterpret_cast(job)->Reset(); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - void OrthancJob::ClearContent() - { - Json::Value empty = Json::objectValue; - UpdateContent(empty); - } - - - void OrthancJob::UpdateContent(const Json::Value& content) - { - if (content.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); - } - else - { - Json::FastWriter writer; - content_ = writer.write(content); - } - } - - - void OrthancJob::ClearSerialized() - { - hasSerialized_ = false; - serialized_.clear(); - } - - - void OrthancJob::UpdateSerialized(const Json::Value& serialized) - { - if (serialized.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); - } - else - { - Json::FastWriter writer; - serialized_ = writer.write(serialized); - hasSerialized_ = true; - } - } - - - void OrthancJob::UpdateProgress(float progress) - { - if (progress < 0 || - progress > 1) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - progress_ = progress; - } - - - OrthancJob::OrthancJob(const std::string& jobType) : - jobType_(jobType), - progress_(0) - { - ClearContent(); - ClearSerialized(); - } - - - OrthancPluginJob* OrthancJob::Create(OrthancJob* job) - { - if (job == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); - } - - OrthancPluginJob* orthanc = OrthancPluginCreateJob( - GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), - CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, - CallbackStep, CallbackStop, CallbackReset); - - if (orthanc == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - return orthanc; - } - } - - - std::string OrthancJob::Submit(OrthancJob* job, - int priority) - { - if (job == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); - } - - OrthancPluginJob* orthanc = Create(job); - - char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority); - - if (id == NULL) - { - LogError("Plugin cannot submit job"); - OrthancPluginFreeJob(GetGlobalContext(), orthanc); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - std::string tmp(id); - tmp.assign(id); - OrthancPluginFreeString(GetGlobalContext(), id); - - return tmp; - } - } - - - void OrthancJob::SubmitAndWait(Json::Value& result, - OrthancJob* job /* takes ownership */, - int priority) - { - std::string id = Submit(job, priority); - - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - Json::Value status; - if (!RestApiGet(status, "/jobs/" + id, false) || - !status.isMember("State") || - status["State"].type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem); - } - - const std::string state = status["State"].asString(); - if (state == "Success") - { - if (status.isMember("Content")) - { - result = status["Content"]; - } - else - { - result = Json::objectValue; - } - - return; - } - else if (state == "Running") - { - continue; - } - else if (!status.isMember("ErrorCode") || - status["ErrorCode"].type() != Json::intValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); - } - else - { - if (!status.isMember("ErrorDescription") || - status["ErrorDescription"].type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); - } - else - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(static_cast(status["ErrorCode"].asInt()), - status["ErrorDescription"].asString()); -#else - LogError("Exception while executing the job: " + status["ErrorDescription"].asString()); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); -#endif - } - } - } - } - - - void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output, - const Json::Value& body, - OrthancJob* job) - { - static const char* KEY_SYNCHRONOUS = "Synchronous"; - static const char* KEY_ASYNCHRONOUS = "Asynchronous"; - static const char* KEY_PRIORITY = "Priority"; - - boost::movelib::unique_ptr protection(job); - - if (body.type() != Json::objectValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Expected a JSON object in the body"); -#else - LogError("Expected a JSON object in the body"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - - bool synchronous = true; - - if (body.isMember(KEY_SYNCHRONOUS)) - { - if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Option \"" + std::string(KEY_SYNCHRONOUS) + - "\" must be Boolean"); -#else - LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - else - { - synchronous = body[KEY_SYNCHRONOUS].asBool(); - } - } - - if (body.isMember(KEY_ASYNCHRONOUS)) - { - if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Option \"" + std::string(KEY_ASYNCHRONOUS) + - "\" must be Boolean"); -#else - LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - else - { - synchronous = !body[KEY_ASYNCHRONOUS].asBool(); - } - } - - int priority = 0; - - if (body.isMember(KEY_PRIORITY)) - { - if (body[KEY_PRIORITY].type() != Json::booleanValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Option \"" + std::string(KEY_PRIORITY) + - "\" must be an integer"); -#else - LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - else - { - priority = !body[KEY_PRIORITY].asInt(); - } - } - - Json::Value result; - - if (synchronous) - { - OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority); - } - else - { - std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority); - - result = Json::objectValue; - result["ID"] = id; - result["Path"] = "/jobs/" + id; - } - - std::string s = result.toStyledString(); - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), - s.size(), "application/json"); - } - -#endif - - - - - /****************************************************************** - ** METRICS - ******************************************************************/ - -#if HAS_ORTHANC_PLUGIN_METRICS == 1 - MetricsTimer::MetricsTimer(const char* name) : - name_(name) - { - start_ = boost::posix_time::microsec_clock::universal_time(); - } - - MetricsTimer::~MetricsTimer() - { - const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time(); - const boost::posix_time::time_duration diff = stop - start_; - OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast(diff.total_milliseconds()), - OrthancPluginMetricsType_Timer); - } -#endif - - - - - /****************************************************************** - ** HTTP CLIENT - ******************************************************************/ - -#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 - class HttpClient::RequestBodyWrapper : public boost::noncopyable - { - private: - static RequestBodyWrapper& GetObject(void* body) - { - assert(body != NULL); - return *reinterpret_cast(body); - } - - IRequestBody& body_; - bool done_; - std::string chunk_; - - public: - RequestBodyWrapper(IRequestBody& body) : - body_(body), - done_(false) - { - } - - static uint8_t IsDone(void* body) - { - return GetObject(body).done_; - } - - static const void* GetChunkData(void* body) - { - return GetObject(body).chunk_.c_str(); - } - - static uint32_t GetChunkSize(void* body) - { - return static_cast(GetObject(body).chunk_.size()); - } - - static OrthancPluginErrorCode Next(void* body) - { - RequestBodyWrapper& that = GetObject(body); - - if (that.done_) - { - return OrthancPluginErrorCode_BadSequenceOfCalls; - } - else - { - try - { - that.done_ = !that.body_.ReadNextChunk(that.chunk_); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_InternalError; - } - } - } - }; - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer, - const char* key, - const char* value) - { - assert(answer != NULL && key != NULL && value != NULL); - - try - { - reinterpret_cast(answer)->AddHeader(key, value); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer, - const void* data, - uint32_t size) - { - assert(answer != NULL); - - try - { - reinterpret_cast(answer)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - - - HttpClient::HttpClient() : - httpStatus_(0), - method_(OrthancPluginHttpMethod_Get), - timeout_(0), - pkcs11_(false), - chunkedBody_(NULL), - allowChunkedTransfers_(true) - { - } - - - void HttpClient::AddHeaders(const HttpHeaders& headers) - { - for (HttpHeaders::const_iterator it = headers.begin(); - it != headers.end(); ++it) - { - headers_[it->first] = it->second; - } - } - - - void HttpClient::SetCredentials(const std::string& username, - const std::string& password) - { - username_ = username; - password_ = password; - } - - - void HttpClient::ClearCredentials() - { - username_.empty(); - password_.empty(); - } - - - void HttpClient::SetCertificate(const std::string& certificateFile, - const std::string& keyFile, - const std::string& keyPassword) - { - certificateFile_ = certificateFile; - certificateKeyFile_ = keyFile; - certificateKeyPassword_ = keyPassword; - } - - - void HttpClient::ClearCertificate() - { - certificateFile_.clear(); - certificateKeyFile_.clear(); - certificateKeyPassword_.clear(); - } - - - void HttpClient::ClearBody() - { - fullBody_.clear(); - chunkedBody_ = NULL; - } - - - void HttpClient::SwapBody(std::string& body) - { - fullBody_.swap(body); - chunkedBody_ = NULL; - } - - - void HttpClient::SetBody(const std::string& body) - { - fullBody_ = body; - chunkedBody_ = NULL; - } - - - void HttpClient::SetBody(IRequestBody& body) - { - fullBody_.clear(); - chunkedBody_ = &body; - } - - - namespace - { - class HeadersWrapper : public boost::noncopyable - { - private: - std::vector headersKeys_; - std::vector headersValues_; - - public: - HeadersWrapper(const HttpClient::HttpHeaders& headers) - { - headersKeys_.reserve(headers.size()); - headersValues_.reserve(headers.size()); - - for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) - { - headersKeys_.push_back(it->first.c_str()); - headersValues_.push_back(it->second.c_str()); - } - } - - void AddStaticString(const char* key, - const char* value) - { - headersKeys_.push_back(key); - headersValues_.push_back(value); - } - - uint32_t GetCount() const - { - return headersKeys_.size(); - } - - const char* const* GetKeys() const - { - return headersKeys_.empty() ? NULL : &headersKeys_[0]; - } - - const char* const* GetValues() const - { - return headersValues_.empty() ? NULL : &headersValues_[0]; - } - }; - - - class MemoryRequestBody : public HttpClient::IRequestBody - { - private: - std::string body_; - bool done_; - - public: - MemoryRequestBody(const std::string& body) : - body_(body), - done_(false) - { - if (body_.empty()) - { - done_ = true; - } - } - - virtual bool ReadNextChunk(std::string& chunk) - { - if (done_) - { - return false; - } - else - { - chunk.swap(body_); - done_ = true; - return true; - } - } - }; - - - // This class mimics Orthanc::ChunkedBuffer - class ChunkedBuffer : public boost::noncopyable - { - private: - typedef std::list Content; - - Content content_; - size_t size_; - - public: - ChunkedBuffer() : - size_(0) - { - } - - ~ChunkedBuffer() - { - Clear(); - } - - void Clear() - { - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - content_.clear(); - } - - void Flatten(std::string& target) const - { - target.resize(size_); - - size_t pos = 0; - - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(*it != NULL); - size_t s = (*it)->size(); - - if (s != 0) - { - memcpy(&target[pos], (*it)->c_str(), s); - pos += s; - } - } - - assert(size_ == 0 || - pos == target.size()); - } - - void AddChunk(const void* data, - size_t size) - { - content_.push_back(new std::string(reinterpret_cast(data), size)); - size_ += size; - } - - void AddChunk(const std::string& chunk) - { - content_.push_back(new std::string(chunk)); - size_ += chunk.size(); - } - }; - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - class MemoryAnswer : public HttpClient::IAnswer - { - private: - HttpClient::HttpHeaders headers_; - ChunkedBuffer body_; - - public: - const HttpClient::HttpHeaders& GetHeaders() const - { - return headers_; - } - - const ChunkedBuffer& GetBody() const - { - return body_; - } - - virtual void AddHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - virtual void AddChunk(const void* data, - size_t size) - { - body_.AddChunk(data, size); - } - }; -#endif - } - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - void HttpClient::ExecuteWithStream(uint16_t& httpStatus, - IAnswer& answer, - IRequestBody& body) const - { - HeadersWrapper h(headers_); - - if (method_ == OrthancPluginHttpMethod_Post || - method_ == OrthancPluginHttpMethod_Put) - { - // Automatically set the "Transfer-Encoding" header if absent - bool found = false; - - for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) - { - if (boost::iequals(it->first, "Transfer-Encoding")) - { - found = true; - break; - } - } - - if (!found) - { - h.AddStaticString("Transfer-Encoding", "chunked"); - } - } - - RequestBodyWrapper request(body); - - OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient( - GetGlobalContext(), - &answer, - AnswerAddChunkCallback, - AnswerAddHeaderCallback, - &httpStatus, - method_, - url_.c_str(), - h.GetCount(), - h.GetKeys(), - h.GetValues(), - &request, - RequestBodyWrapper::IsDone, - RequestBodyWrapper::GetChunkData, - RequestBodyWrapper::GetChunkSize, - RequestBodyWrapper::Next, - username_.empty() ? NULL : username_.c_str(), - password_.empty() ? NULL : password_.c_str(), - timeout_, - certificateFile_.empty() ? NULL : certificateFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), - pkcs11_ ? 1 : 0); - - if (error != OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - } -#endif - - - void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, - HttpHeaders& answerHeaders, - std::string& answerBody, - const std::string& body) const - { - HeadersWrapper headers(headers_); - - MemoryBuffer answerBodyBuffer, answerHeadersBuffer; - - OrthancPluginErrorCode error = OrthancPluginHttpClient( - GetGlobalContext(), - *answerBodyBuffer, - *answerHeadersBuffer, - &httpStatus, - method_, - url_.c_str(), - headers.GetCount(), - headers.GetKeys(), - headers.GetValues(), - body.empty() ? NULL : body.c_str(), - body.size(), - username_.empty() ? NULL : username_.c_str(), - password_.empty() ? NULL : password_.c_str(), - timeout_, - certificateFile_.empty() ? NULL : certificateFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), - pkcs11_ ? 1 : 0); - - if (error != OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - - Json::Value v; - answerHeadersBuffer.ToJson(v); - - if (v.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - Json::Value::Members members = v.getMemberNames(); - answerHeaders.clear(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& h = v[members[i]]; - if (h.type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - else - { - answerHeaders[members[i]] = h.asString(); - } - } - - answerBodyBuffer.ToString(answerBody); - } - - - void HttpClient::Execute(IAnswer& answer) - { -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - if (allowChunkedTransfers_) - { - if (chunkedBody_ != NULL) - { - ExecuteWithStream(httpStatus_, answer, *chunkedBody_); - } - else - { - MemoryRequestBody wrapper(fullBody_); - ExecuteWithStream(httpStatus_, answer, wrapper); - } - - return; - } -#endif - - // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked - // transfers are disabled. This results in higher memory usage - // (all chunks from the answer body are sent at once) - - HttpHeaders answerHeaders; - std::string answerBody; - Execute(answerHeaders, answerBody); - - for (HttpHeaders::const_iterator it = answerHeaders.begin(); - it != answerHeaders.end(); ++it) - { - answer.AddHeader(it->first, it->second); - } - - if (!answerBody.empty()) - { - answer.AddChunk(answerBody.c_str(), answerBody.size()); - } - } - - - void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, - std::string& answerBody /* out */) - { -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - if (allowChunkedTransfers_) - { - MemoryAnswer answer; - Execute(answer); - answerHeaders = answer.GetHeaders(); - answer.GetBody().Flatten(answerBody); - return; - } -#endif - - // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked - // transfers are disabled. This results in higher memory usage - // (all chunks from the request body are sent at once) - - if (chunkedBody_ != NULL) - { - ChunkedBuffer buffer; - - std::string chunk; - while (chunkedBody_->ReadNextChunk(chunk)) - { - buffer.AddChunk(chunk); - } - - std::string body; - buffer.Flatten(body); - - ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body); - } - else - { - ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_); - } - } - - - void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, - Json::Value& answerBody /* out */) - { - std::string body; - Execute(answerHeaders, body); - - Json::Reader reader; - if (!reader.parse(body, answerBody)) - { - LogError("Cannot convert HTTP answer body to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - void HttpClient::Execute() - { - HttpHeaders answerHeaders; - std::string body; - Execute(answerHeaders, body); - } - -#endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ - - - - - - /****************************************************************** - ** CHUNKED HTTP SERVER - ******************************************************************/ - - namespace Internals - { - void NullRestCallback(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) - { - } - - IChunkedRequestReader *NullChunkedRestCallback(const char* url, - const OrthancPluginHttpRequest* request) - { - return NULL; - } - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 - - OrthancPluginErrorCode ChunkedRequestReaderAddChunk( - OrthancPluginServerChunkedRequestReader* reader, - const void* data, - uint32_t size) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - - reinterpret_cast(reader)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - OrthancPluginErrorCode ChunkedRequestReaderExecute( - OrthancPluginServerChunkedRequestReader* reader, - OrthancPluginRestOutput* output) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - - reinterpret_cast(reader)->Execute(output); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - void ChunkedRequestReaderFinalize( - OrthancPluginServerChunkedRequestReader* reader) - { - if (reader != NULL) - { - delete reinterpret_cast(reader); - } - } - -#else - - OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request, - RestCallback GetHandler, - ChunkedRestCallback PostHandler, - RestCallback DeleteHandler, - ChunkedRestCallback PutHandler) - { - try - { - std::string allowed; - - if (GetHandler != Internals::NullRestCallback) - { - allowed += "GET"; - } - - if (PostHandler != Internals::NullChunkedRestCallback) - { - if (!allowed.empty()) - { - allowed += ","; - } - - allowed += "POST"; - } - - if (DeleteHandler != Internals::NullRestCallback) - { - if (!allowed.empty()) - { - allowed += ","; - } - - allowed += "DELETE"; - } - - if (PutHandler != Internals::NullChunkedRestCallback) - { - if (!allowed.empty()) - { - allowed += ","; - } - - allowed += "PUT"; - } - - switch (request->method) - { - case OrthancPluginHttpMethod_Get: - if (GetHandler == Internals::NullRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - GetHandler(output, url, request); - } - - break; - - case OrthancPluginHttpMethod_Post: - if (PostHandler == Internals::NullChunkedRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - boost::movelib::unique_ptr reader(PostHandler(url, request)); - if (reader.get() == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - reader->AddChunk(request->body, request->bodySize); - reader->Execute(output); - } - } - - break; - - case OrthancPluginHttpMethod_Delete: - if (DeleteHandler == Internals::NullRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - DeleteHandler(output, url, request); - } - - break; - - case OrthancPluginHttpMethod_Put: - if (PutHandler == Internals::NullChunkedRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - boost::movelib::unique_ptr reader(PutHandler(url, request)); - if (reader.get() == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - reader->AddChunk(request->body, request->bodySize); - reader->Execute(output); - } - } - - break; - - default: - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { -#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 - if (HasGlobalContext() && - e.HasDetails()) - { - // The "false" instructs Orthanc not to log the detailed - // error message. This is to avoid duplicating the details, - // because "OrthancException" already does it on construction. - OrthancPluginSetHttpErrorDetails - (GetGlobalContext(), output, e.GetDetails(), false); - } -#endif - - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - } - - -#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 - OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup( - OrthancPluginStorageCommitmentFailureReason* target, - void* rawHandler, - const char* sopClassUid, - const char* sopInstanceUid) - { - assert(target != NULL && - rawHandler != NULL); - - try - { - IStorageCommitmentScpHandler& handler = *reinterpret_cast(rawHandler); - *target = handler.Lookup(sopClassUid, sopInstanceUid); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - - -#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 - void IStorageCommitmentScpHandler::Destructor(void* rawHandler) - { - assert(rawHandler != NULL); - delete reinterpret_cast(rawHandler); - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) : - toFree_(false), - instance_(instance) - { - } -#else - DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) : - toFree_(false), - instance_(instance) - { - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - DicomInstance::DicomInstance(const void* buffer, - size_t size) : - toFree_(true), - instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size)) - { - if (instance_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); - } - } -#endif - - - DicomInstance::~DicomInstance() - { -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - if (toFree_ && - instance_ != NULL) - { - OrthancPluginFreeDicomInstance( - GetGlobalContext(), const_cast(instance_)); - } -#endif - } - - - std::string DicomInstance::GetRemoteAet() const - { - const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_); - if (s == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - return std::string(s); - } - } - - - void DicomInstance::GetJson(Json::Value& target) const - { - OrthancString s; - s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_)); - s.ToJson(target); - } - - - void DicomInstance::GetSimplifiedJson(Json::Value& target) const - { - OrthancString s; - s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_)); - s.ToJson(target); - } - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - std::string DicomInstance::GetTransferSyntaxUid() const - { - OrthancString s; - s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_)); - - std::string result; - s.ToString(result); - return result; - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - bool DicomInstance::HasPixelData() const - { - int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_); - if (result < 0) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - return (result != 0); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void DicomInstance::GetRawFrame(std::string& target, - unsigned int frameIndex) const - { - MemoryBuffer buffer; - OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame( - GetGlobalContext(), *buffer, instance_, frameIndex); - - if (code == OrthancPluginErrorCode_Success) - { - buffer.ToString(target); - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const - { - OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame( - GetGlobalContext(), instance_, frameIndex); - - if (image == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - return new OrthancImage(image); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void DicomInstance::Serialize(std::string& target) const - { - MemoryBuffer buffer; - OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance( - GetGlobalContext(), *buffer, instance_); - - if (code == OrthancPluginErrorCode_Success) - { - buffer.ToString(target); - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - DicomInstance* DicomInstance::Transcode(const void* buffer, - size_t size, - const std::string& transferSyntax) - { - OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance( - GetGlobalContext(), buffer, size, transferSyntax.c_str()); - - if (instance == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - boost::movelib::unique_ptr result(new DicomInstance(instance)); - result->toFree_ = true; - return result.release(); - } - } -#endif -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1228 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 . - **/ - - -#pragma once - -#include "OrthancPluginException.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for - * backward compatibility with Orthanc SDK <= 1.3.0. - * - * $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h - * - **/ -#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) -#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ - (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ - (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ - (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ - (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ - ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) -#endif - - -#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE) -#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision) \ - (ORTHANC_VERSION_MAJOR > major || \ - (ORTHANC_VERSION_MAJOR == major && \ - (ORTHANC_VERSION_MINOR > minor || \ - (ORTHANC_VERSION_MINOR == minor && \ - ORTHANC_VERSION_REVISION >= revision)))) -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) -// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0 -# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1 -#else -# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0 -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2) -# define HAS_ORTHANC_PLUGIN_PEERS 1 -# define HAS_ORTHANC_PLUGIN_JOB 1 -#else -# define HAS_ORTHANC_PLUGIN_PEERS 0 -# define HAS_ORTHANC_PLUGIN_JOB 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) -# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 1 -#else -# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) -# define HAS_ORTHANC_PLUGIN_METRICS 1 -#else -# define HAS_ORTHANC_PLUGIN_METRICS 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0) -# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 1 -#else -# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 1 -#else -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 1 -#else -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0) -# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1 -#else -# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 -#endif - - - -namespace OrthancPlugins -{ - typedef void (*RestCallback) (OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - void SetGlobalContext(OrthancPluginContext* context); - - bool HasGlobalContext(); - - OrthancPluginContext* GetGlobalContext(); - - - class OrthancImage; - - - class MemoryBuffer : public boost::noncopyable - { - private: - OrthancPluginMemoryBuffer buffer_; - - void Check(OrthancPluginErrorCode code); - - bool CheckHttp(OrthancPluginErrorCode code); - - public: - MemoryBuffer(); - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - // This constructor makes a copy of the given buffer in the memory - // handled by the Orthanc core - MemoryBuffer(const void* buffer, - size_t size); -#endif - - ~MemoryBuffer() - { - Clear(); - } - - OrthancPluginMemoryBuffer* operator*() - { - return &buffer_; - } - - // This transfers ownership from "other" to "this" - void Assign(OrthancPluginMemoryBuffer& other); - - void Swap(MemoryBuffer& other); - - OrthancPluginMemoryBuffer Release(); - - const char* GetData() const - { - if (buffer_.size > 0) - { - return reinterpret_cast(buffer_.data); - } - else - { - return NULL; - } - } - - size_t GetSize() const - { - return buffer_.size; - } - - bool IsEmpty() const - { - return GetSize() == 0 || GetData() == NULL; - } - - void Clear(); - - void ToString(std::string& target) const; - - void ToJson(Json::Value& target) const; - - bool RestApiGet(const std::string& uri, - bool applyPlugins); - - bool RestApiGet(const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins); - - bool RestApiPost(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPut(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPost(const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - bool RestApiPut(const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - bool RestApiPost(const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); - } - - bool RestApiPut(const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); - } - - void CreateDicom(const Json::Value& tags, - OrthancPluginCreateDicomFlags flags); - - void CreateDicom(const Json::Value& tags, - const OrthancImage& pixelData, - OrthancPluginCreateDicomFlags flags); - - void ReadFile(const std::string& path); - - void GetDicomQuery(const OrthancPluginWorklistQuery* query); - - void DicomToJson(Json::Value& target, - OrthancPluginDicomToJsonFormat format, - OrthancPluginDicomToJsonFlags flags, - uint32_t maxStringLength); - - bool HttpGet(const std::string& url, - const std::string& username, - const std::string& password); - - bool HttpPost(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password); - - bool HttpPut(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password); - - void GetDicomInstance(const std::string& instanceId); - }; - - - class OrthancString : public boost::noncopyable - { - private: - char* str_; - - void Clear(); - - public: - OrthancString() : - str_(NULL) - { - } - - ~OrthancString() - { - Clear(); - } - - // This transfers ownership, warning: The string must have been - // allocated by the Orthanc core - void Assign(char* str); - - const char* GetContent() const - { - return str_; - } - - void ToString(std::string& target) const; - - void ToJson(Json::Value& target) const; - }; - - - class OrthancConfiguration : public boost::noncopyable - { - private: - Json::Value configuration_; // Necessarily a Json::objectValue - std::string path_; - - std::string GetPath(const std::string& key) const; - - void LoadConfiguration(); - - public: - OrthancConfiguration(); - - explicit OrthancConfiguration(bool load); - - const Json::Value& GetJson() const - { - return configuration_; - } - - bool IsSection(const std::string& key) const; - - void GetSection(OrthancConfiguration& target, - const std::string& key) const; - - bool LookupStringValue(std::string& target, - const std::string& key) const; - - bool LookupIntegerValue(int& target, - const std::string& key) const; - - bool LookupUnsignedIntegerValue(unsigned int& target, - const std::string& key) const; - - bool LookupBooleanValue(bool& target, - const std::string& key) const; - - bool LookupFloatValue(float& target, - const std::string& key) const; - - bool LookupListOfStrings(std::list& target, - const std::string& key, - bool allowSingleString) const; - - bool LookupSetOfStrings(std::set& target, - const std::string& key, - bool allowSingleString) const; - - std::string GetStringValue(const std::string& key, - const std::string& defaultValue) const; - - int GetIntegerValue(const std::string& key, - int defaultValue) const; - - unsigned int GetUnsignedIntegerValue(const std::string& key, - unsigned int defaultValue) const; - - bool GetBooleanValue(const std::string& key, - bool defaultValue) const; - - float GetFloatValue(const std::string& key, - float defaultValue) const; - - void GetDictionary(std::map& target, - const std::string& key) const; - }; - - class OrthancImage : public boost::noncopyable - { - private: - OrthancPluginImage* image_; - - void Clear(); - - void CheckImageAvailable() const; - - public: - OrthancImage(); - - explicit OrthancImage(OrthancPluginImage* image); - - OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height); - - OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - void* buffer); - - ~OrthancImage() - { - Clear(); - } - - void UncompressPngImage(const void* data, - size_t size); - - void UncompressJpegImage(const void* data, - size_t size); - - void DecodeDicomImage(const void* data, - size_t size, - unsigned int frame); - - OrthancPluginPixelFormat GetPixelFormat() const; - - unsigned int GetWidth() const; - - unsigned int GetHeight() const; - - unsigned int GetPitch() const; - - void* GetBuffer() const; - - const OrthancPluginImage* GetObject() const - { - return image_; - } - - void CompressPngImage(MemoryBuffer& target) const; - - void CompressJpegImage(MemoryBuffer& target, - uint8_t quality) const; - - void AnswerPngImage(OrthancPluginRestOutput* output) const; - - void AnswerJpegImage(OrthancPluginRestOutput* output, - uint8_t quality) const; - - void* GetWriteableBuffer(); - - OrthancPluginImage* Release(); - }; - - -#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 - class FindMatcher : public boost::noncopyable - { - private: - OrthancPluginFindMatcher* matcher_; - const OrthancPluginWorklistQuery* worklist_; - - void SetupDicom(const void* query, - uint32_t size); - - public: - explicit FindMatcher(const OrthancPluginWorklistQuery* worklist); - - FindMatcher(const void* query, - uint32_t size) - { - SetupDicom(query, size); - } - - explicit FindMatcher(const MemoryBuffer& dicom) - { - SetupDicom(dicom.GetData(), dicom.GetSize()); - } - - ~FindMatcher(); - - bool IsMatch(const void* dicom, - uint32_t size) const; - - bool IsMatch(const MemoryBuffer& dicom) const - { - return IsMatch(dicom.GetData(), dicom.GetSize()); - } - }; -#endif - - - bool RestApiGet(Json::Value& result, - const std::string& uri, - bool applyPlugins); - - bool RestApiGetString(std::string& result, - const std::string& uri, - bool applyPlugins); - - bool RestApiGetString(std::string& result, - const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins); - - bool RestApiPost(std::string& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - inline bool RestApiPost(Json::Value& result, - const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(), - body.size(), applyPlugins); - } - - inline bool RestApiPost(Json::Value& result, - const std::string& uri, - const MemoryBuffer& body, - bool applyPlugins) - { - return RestApiPost(result, uri, body.GetData(), - body.GetSize(), applyPlugins); - } - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - inline bool RestApiPut(Json::Value& result, - const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(), - body.size(), applyPlugins); - } - - bool RestApiDelete(const std::string& uri, - bool applyPlugins); - - bool HttpDelete(const std::string& url, - const std::string& username, - const std::string& password); - - void AnswerJson(const Json::Value& value, - OrthancPluginRestOutput* output); - - void AnswerString(const std::string& answer, - const char* mimeType, - OrthancPluginRestOutput* output); - - void AnswerHttpError(uint16_t httpError, - OrthancPluginRestOutput* output); - - void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods); - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) - const char* AutodetectMimeType(const std::string& path); -#endif - - void LogError(const std::string& message); - - void LogWarning(const std::string& message); - - void LogInfo(const std::string& message); - - void ReportMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision); - - bool CheckMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision); - - - namespace Internals - { - template - static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) - { - try - { - Callback(output, url, request); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { -#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 - if (HasGlobalContext() && - e.HasDetails()) - { - // The "false" instructs Orthanc not to log the detailed - // error message. This is to avoid duplicating the details, - // because "OrthancException" already does it on construction. - OrthancPluginSetHttpErrorDetails - (GetGlobalContext(), output, e.GetDetails(), false); - } -#endif - - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - } - - - template - void RegisterRestCallback(const std::string& uri, - bool isThreadSafe) - { - if (isThreadSafe) - { - OrthancPluginRegisterRestCallbackNoLock - (GetGlobalContext(), uri.c_str(), Internals::Protect); - } - else - { - OrthancPluginRegisterRestCallback - (GetGlobalContext(), uri.c_str(), Internals::Protect); - } - } - - -#if HAS_ORTHANC_PLUGIN_PEERS == 1 - class OrthancPeers : public boost::noncopyable - { - private: - typedef std::map Index; - - OrthancPluginPeers *peers_; - Index index_; - uint32_t timeout_; - - size_t GetPeerIndex(const std::string& name) const; - - public: - OrthancPeers(); - - ~OrthancPeers(); - - uint32_t GetTimeout() const - { - return timeout_; - } - - void SetTimeout(uint32_t timeout) - { - timeout_ = timeout; - } - - bool LookupName(size_t& target, - const std::string& name) const; - - std::string GetPeerName(size_t index) const; - - std::string GetPeerUrl(size_t index) const; - - std::string GetPeerUrl(const std::string& name) const; - - size_t GetPeersCount() const - { - return index_.size(); - } - - bool LookupUserProperty(std::string& value, - size_t index, - const std::string& key) const; - - bool LookupUserProperty(std::string& value, - const std::string& peer, - const std::string& key) const; - - bool DoGet(MemoryBuffer& target, - size_t index, - const std::string& uri) const; - - bool DoGet(MemoryBuffer& target, - const std::string& name, - const std::string& uri) const; - - bool DoGet(Json::Value& target, - size_t index, - const std::string& uri) const; - - bool DoGet(Json::Value& target, - const std::string& name, - const std::string& uri) const; - - bool DoPost(MemoryBuffer& target, - size_t index, - const std::string& uri, - const std::string& body) const; - - bool DoPost(MemoryBuffer& target, - const std::string& name, - const std::string& uri, - const std::string& body) const; - - bool DoPost(Json::Value& target, - size_t index, - const std::string& uri, - const std::string& body) const; - - bool DoPost(Json::Value& target, - const std::string& name, - const std::string& uri, - const std::string& body) const; - - bool DoPut(size_t index, - const std::string& uri, - const std::string& body) const; - - bool DoPut(const std::string& name, - const std::string& uri, - const std::string& body) const; - - bool DoDelete(size_t index, - const std::string& uri) const; - - bool DoDelete(const std::string& name, - const std::string& uri) const; - }; -#endif - - - -#if HAS_ORTHANC_PLUGIN_JOB == 1 - class OrthancJob : public boost::noncopyable - { - private: - std::string jobType_; - std::string content_; - bool hasSerialized_; - std::string serialized_; - float progress_; - - static void CallbackFinalize(void* job); - - static float CallbackGetProgress(void* job); - - static const char* CallbackGetContent(void* job); - - static const char* CallbackGetSerialized(void* job); - - static OrthancPluginJobStepStatus CallbackStep(void* job); - - static OrthancPluginErrorCode CallbackStop(void* job, - OrthancPluginJobStopReason reason); - - static OrthancPluginErrorCode CallbackReset(void* job); - - protected: - void ClearContent(); - - void UpdateContent(const Json::Value& content); - - void ClearSerialized(); - - void UpdateSerialized(const Json::Value& serialized); - - void UpdateProgress(float progress); - - public: - OrthancJob(const std::string& jobType); - - virtual ~OrthancJob() - { - } - - virtual OrthancPluginJobStepStatus Step() = 0; - - virtual void Stop(OrthancPluginJobStopReason reason) = 0; - - virtual void Reset() = 0; - - static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */); - - static std::string Submit(OrthancJob* job /* takes ownership */, - int priority); - - static void SubmitAndWait(Json::Value& result, - OrthancJob* job /* takes ownership */, - int priority); - - // Submit a job from a POST on the REST API with the same - // conventions as in the Orthanc core (according to the - // "Synchronous" and "Priority" options) - static void SubmitFromRestApiPost(OrthancPluginRestOutput* output, - const Json::Value& body, - OrthancJob* job); - }; -#endif - - -#if HAS_ORTHANC_PLUGIN_METRICS == 1 - inline void SetMetricsValue(char* name, - float value) - { - OrthancPluginSetMetricsValue(GetGlobalContext(), name, - value, OrthancPluginMetricsType_Default); - } - - class MetricsTimer : public boost::noncopyable - { - private: - std::string name_; - boost::posix_time::ptime start_; - - public: - explicit MetricsTimer(const char* name); - - ~MetricsTimer(); - }; -#endif - - -#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 - class HttpClient : public boost::noncopyable - { - public: - typedef std::map HttpHeaders; - - class IRequestBody : public boost::noncopyable - { - public: - virtual ~IRequestBody() - { - } - - virtual bool ReadNextChunk(std::string& chunk) = 0; - }; - - - class IAnswer : public boost::noncopyable - { - public: - virtual ~IAnswer() - { - } - - virtual void AddHeader(const std::string& key, - const std::string& value) = 0; - - virtual void AddChunk(const void* data, - size_t size) = 0; - }; - - - private: - class RequestBodyWrapper; - - uint16_t httpStatus_; - OrthancPluginHttpMethod method_; - std::string url_; - HttpHeaders headers_; - std::string username_; - std::string password_; - uint32_t timeout_; - std::string certificateFile_; - std::string certificateKeyFile_; - std::string certificateKeyPassword_; - bool pkcs11_; - std::string fullBody_; - IRequestBody* chunkedBody_; - bool allowChunkedTransfers_; - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - void ExecuteWithStream(uint16_t& httpStatus, // out - IAnswer& answer, // out - IRequestBody& body) const; -#endif - - void ExecuteWithoutStream(uint16_t& httpStatus, // out - HttpHeaders& answerHeaders, // out - std::string& answerBody, // out - const std::string& body) const; - - public: - HttpClient(); - - uint16_t GetHttpStatus() const - { - return httpStatus_; - } - - void SetMethod(OrthancPluginHttpMethod method) - { - method_ = method; - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - void SetHeaders(const HttpHeaders& headers) - { - headers_ = headers; - } - - void AddHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - void AddHeaders(const HttpHeaders& headers); - - void SetCredentials(const std::string& username, - const std::string& password); - - void ClearCredentials(); - - void SetTimeout(unsigned int timeout) // 0 for default timeout - { - timeout_ = timeout; - } - - void SetCertificate(const std::string& certificateFile, - const std::string& keyFile, - const std::string& keyPassword); - - void ClearCertificate(); - - void SetPkcs11(bool pkcs11) - { - pkcs11_ = pkcs11; - } - - void ClearBody(); - - void SwapBody(std::string& body); - - void SetBody(const std::string& body); - - void SetBody(IRequestBody& body); - - // This function can be used to disable chunked transfers if the - // remote server is Orthanc with a version <= 1.5.6. - void SetChunkedTransfersAllowed(bool allow) - { - allowChunkedTransfers_ = allow; - } - - bool IsChunkedTransfersAllowed() const - { - return allowChunkedTransfers_; - } - - void Execute(IAnswer& answer); - - void Execute(HttpHeaders& answerHeaders /* out */, - std::string& answerBody /* out */); - - void Execute(HttpHeaders& answerHeaders /* out */, - Json::Value& answerBody /* out */); - - void Execute(); - }; -#endif - - - - class IChunkedRequestReader : public boost::noncopyable - { - public: - virtual ~IChunkedRequestReader() - { - } - - virtual void AddChunk(const void* data, - size_t size) = 0; - - virtual void Execute(OrthancPluginRestOutput* output) = 0; - }; - - - typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url, - const OrthancPluginHttpRequest* request); - - - namespace Internals - { - void NullRestCallback(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - IChunkedRequestReader *NullChunkedRestCallback(const char* url, - const OrthancPluginHttpRequest* request); - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 - template - static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, - const char* url, - const OrthancPluginHttpRequest* request) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - else - { - *reader = reinterpret_cast(Callback(url, request)); - if (*reader == NULL) - { - return OrthancPluginErrorCode_Plugin; - } - else - { - return OrthancPluginErrorCode_Success; - } - } - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - OrthancPluginErrorCode ChunkedRequestReaderAddChunk( - OrthancPluginServerChunkedRequestReader* reader, - const void* data, - uint32_t size); - - OrthancPluginErrorCode ChunkedRequestReaderExecute( - OrthancPluginServerChunkedRequestReader* reader, - OrthancPluginRestOutput* output); - - void ChunkedRequestReaderFinalize( - OrthancPluginServerChunkedRequestReader* reader); - -#else - - OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request, - RestCallback GetHandler, - ChunkedRestCallback PostHandler, - RestCallback DeleteHandler, - ChunkedRestCallback PutHandler); - - template< - RestCallback GetHandler, - ChunkedRestCallback PostHandler, - RestCallback DeleteHandler, - ChunkedRestCallback PutHandler - > - inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) - { - return ChunkedRestCompatibility(output, url, request, GetHandler, - PostHandler, DeleteHandler, PutHandler); - } -#endif - } - - - - // NB: We use a templated class instead of a templated function, because - // default values are only available in functions since C++11 - template< - RestCallback GetHandler = Internals::NullRestCallback, - ChunkedRestCallback PostHandler = Internals::NullChunkedRestCallback, - RestCallback DeleteHandler = Internals::NullRestCallback, - ChunkedRestCallback PutHandler = Internals::NullChunkedRestCallback - > - class ChunkedRestRegistration : public boost::noncopyable - { - public: - static void Apply(const std::string& uri) - { -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 - OrthancPluginRegisterChunkedRestCallback( - GetGlobalContext(), uri.c_str(), - GetHandler == Internals::NullRestCallback ? NULL : Internals::Protect, - PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, - DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect, - PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, - Internals::ChunkedRequestReaderAddChunk, - Internals::ChunkedRequestReaderExecute, - Internals::ChunkedRequestReaderFinalize); -#else - OrthancPluginRegisterRestCallbackNoLock( - GetGlobalContext(), uri.c_str(), - Internals::ChunkedRestCompatibility); -#endif - } - }; - - - -#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 - class IStorageCommitmentScpHandler : public boost::noncopyable - { - public: - virtual ~IStorageCommitmentScpHandler() - { - } - - virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, - const std::string& sopInstanceUid) = 0; - - static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target, - void* rawHandler, - const char* sopClassUid, - const char* sopInstanceUid); - - static void Destructor(void* rawHandler); - }; -#endif - - - class DicomInstance : public boost::noncopyable - { - private: - bool toFree_; - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - const OrthancPluginDicomInstance* instance_; -#else - OrthancPluginDicomInstance* instance_; -#endif - - public: -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - DicomInstance(const OrthancPluginDicomInstance* instance); -#else - DicomInstance(OrthancPluginDicomInstance* instance); -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - DicomInstance(const void* buffer, - size_t size); -#endif - - ~DicomInstance(); - - std::string GetRemoteAet() const; - - const void* GetBuffer() const - { - return OrthancPluginGetInstanceData(GetGlobalContext(), instance_); - } - - size_t GetSize() const - { - return static_cast(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_)); - } - - void GetJson(Json::Value& target) const; - - void GetSimplifiedJson(Json::Value& target) const; - - OrthancPluginInstanceOrigin GetOrigin() const - { - return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_); - } - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - std::string GetTransferSyntaxUid() const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - bool HasPixelData() const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - unsigned int GetFramesCount() const - { - return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_); - } -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void GetRawFrame(std::string& target, - unsigned int frameIndex) const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - OrthancImage* GetDecodedFrame(unsigned int frameIndex) const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void Serialize(std::string& target) const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - static DicomInstance* Transcode(const void* buffer, - size_t size, - const std::string& transferSyntax); -#endif - }; -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h --- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginException.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 . - **/ - - -#pragma once - -#if !defined(HAS_ORTHANC_EXCEPTION) -# error The macro HAS_ORTHANC_EXCEPTION must be defined -#endif - - -#if HAS_ORTHANC_EXCEPTION == 1 -# include -# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::Orthanc::ErrorCode -# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::Orthanc::OrthancException -# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::Orthanc::ErrorCode_ ## code -#else -# include -# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::OrthancPluginErrorCode -# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::OrthancPlugins::PluginException -# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::OrthancPluginErrorCode_ ## code -#endif - - -#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code) \ - throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast(code)); - - -#define ORTHANC_PLUGINS_THROW_EXCEPTION(code) \ - throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code)); - - -#define ORTHANC_PLUGINS_CHECK_ERROR(code) \ - if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success)) \ - { \ - ORTHANC_PLUGINS_THROW_EXCEPTION(code); \ - } - - -namespace OrthancPlugins -{ -#if HAS_ORTHANC_EXCEPTION == 0 - class PluginException - { - private: - OrthancPluginErrorCode code_; - - public: - explicit PluginException(OrthancPluginErrorCode code) : code_(code) - { - } - - OrthancPluginErrorCode GetErrorCode() const - { - return code_; - } - - const char* What(OrthancPluginContext* context) const - { - const char* description = OrthancPluginGetErrorDescription(context, code_); - if (description) - { - return description; - } - else - { - return "No description available"; - } - } - }; -#endif -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake --- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., Belgium -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# 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 . - - -# In Orthanc <= 1.7.1, the instructions below were part of -# "Compiler.cmake", and were protected by the (now unused) option -# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map") -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list") -endif() diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map --- a/OrthancStone/Samples/RtViewerPlugin/Resources/Orthanc/Plugins/VersionScriptPlugins.map Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -# This is a version-script for Orthanc plugins - -{ -global: - OrthancPluginInitialize; - OrthancPluginFinalize; - OrthancPluginGetName; - OrthancPluginGetVersion; - -local: - *; -}; diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h --- a/OrthancStone/Samples/RtViewerPlugin/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4740 +0,0 @@ -/** - * \mainpage - * - * This C/C++ SDK allows external developers to create plugins that - * can be loaded into Orthanc to extend its functionality. Each - * Orthanc plugin must expose 4 public functions with the following - * signatures: - * - * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): - * This function is invoked by Orthanc when it loads the plugin on startup. - * The plugin must: - * - Check its compatibility with the Orthanc version using - * ::OrthancPluginCheckVersion(). - * - Store the context pointer so that it can use the plugin - * services of Orthanc. - * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). - * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). - * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). - * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). - * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). - * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). - * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). - * -# void OrthancPluginFinalize(): - * This function is invoked by Orthanc during its shutdown. The plugin - * must free all its memory. - * -# const char* OrthancPluginGetName(): - * The plugin must return a short string to identify itself. - * -# const char* OrthancPluginGetVersion(): - * The plugin must return a string containing its version number. - * - * The name and the version of a plugin is only used to prevent it - * from being loaded twice. Note that, in C++, it is mandatory to - * declare these functions within an extern "C" section. - * - * To ensure multi-threading safety, the various REST callbacks are - * guaranteed to be executed in mutual exclusion since Orthanc - * 0.8.5. If this feature is undesired (notably when developing - * high-performance plugins handling simultaneous requests), use - * ::OrthancPluginRegisterRestCallbackNoLock(). - **/ - - - -/** - * @defgroup Images Images and compression - * @brief Functions to deal with images and compressed buffers. - * - * @defgroup REST REST - * @brief Functions to answer REST requests in a callback. - * - * @defgroup Callbacks Callbacks - * @brief Functions to register and manage callbacks by the plugins. - * - * @defgroup Worklists Worklists - * @brief Functions to register and manage worklists. - * - * @defgroup Orthanc Orthanc - * @brief Functions to access the content of the Orthanc server. - **/ - - - -/** - * @defgroup Toolbox Toolbox - * @brief Generic functions to help with the creation of plugins. - **/ - - - -/** - * 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 . - **/ - - - -#pragma once - - -#include -#include - -#ifdef WIN32 -#define ORTHANC_PLUGINS_API __declspec(dllexport) -#else -#define ORTHANC_PLUGINS_API -#endif - -#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 0 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 - - - -/******************************************************************** - ** Check that function inlining is properly supported. The use of - ** inlining is required, to avoid the duplication of object code - ** between two compilation modules that would use the Orthanc Plugin - ** API. - ********************************************************************/ - -/* If the auto-detection of the "inline" keyword below does not work - automatically and that your compiler is known to properly support - inlining, uncomment the following #define and adapt the definition - of "static inline". */ - -/* #define ORTHANC_PLUGIN_INLINE static inline */ - -#ifndef ORTHANC_PLUGIN_INLINE -# if __STDC_VERSION__ >= 199901L -/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ -# define ORTHANC_PLUGIN_INLINE static inline -# elif defined(__cplusplus) -/* This is C++ */ -# define ORTHANC_PLUGIN_INLINE static inline -# elif defined(__GNUC__) -/* This is GCC running in C89 mode */ -# define ORTHANC_PLUGIN_INLINE static __inline -# elif defined(_MSC_VER) -/* This is Visual Studio running in C89 mode */ -# define ORTHANC_PLUGIN_INLINE static __inline -# else -# error Your compiler is not known to support the "inline" keyword -# endif -#endif - - - -/******************************************************************** - ** Inclusion of standard libraries. - ********************************************************************/ - -/** - * For Microsoft Visual Studio, a compatibility "stdint.h" can be - * downloaded at the following URL: - * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h - **/ -#include - -#include - - - -/******************************************************************** - ** Definition of the Orthanc Plugin API. - ********************************************************************/ - -/** @{ */ - -#ifdef __cplusplus -extern "C" -{ -#endif - - /** - * The various error codes that can be returned by the Orthanc core. - **/ - typedef enum - { - OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, - OrthancPluginErrorCode_Success = 0 /*!< Success */, - OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, - OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, - OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, - OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, - OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, - OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, - OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, - OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, - OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, - OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, - OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, - OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, - OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, - OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, - OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, - OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, - OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, - OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, - OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, - OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, - OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, - OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, - OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, - OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, - OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, - OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, - OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, - OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, - OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, - OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, - OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, - OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, - OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, - OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, - OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, - OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, - OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, - OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, - OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, - OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, - OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, - OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, - OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, - OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, - OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, - OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, - OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, - OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, - OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, - OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, - OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, - OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, - OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, - OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, - OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, - OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, - OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, - OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, - OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, - OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, - OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, - OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, - OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, - OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, - OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, - OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, - OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, - OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, - OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, - OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, - OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, - OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, - OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, - OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, - OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, - OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, - OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, - OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, - OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, - OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, - OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, - OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, - OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, - OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, - OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, - OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, - OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, - OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, - OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, - OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, - OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, - OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, - - _OrthancPluginErrorCode_INTERNAL = 0x7fffffff - } OrthancPluginErrorCode; - - - /** - * Forward declaration of one of the mandatory functions for Orthanc - * plugins. - **/ - ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); - - - /** - * The various HTTP methods for a REST call. - **/ - typedef enum - { - OrthancPluginHttpMethod_Get = 1, /*!< GET request */ - OrthancPluginHttpMethod_Post = 2, /*!< POST request */ - OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ - OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ - - _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff - } OrthancPluginHttpMethod; - - - /** - * @brief The parameters of a REST request. - * @ingroup Callbacks - **/ - typedef struct - { - /** - * @brief The HTTP method. - **/ - OrthancPluginHttpMethod method; - - /** - * @brief The number of groups of the regular expression. - **/ - uint32_t groupsCount; - - /** - * @brief The matched values for the groups of the regular expression. - **/ - const char* const* groups; - - /** - * @brief For a GET request, the number of GET parameters. - **/ - uint32_t getCount; - - /** - * @brief For a GET request, the keys of the GET parameters. - **/ - const char* const* getKeys; - - /** - * @brief For a GET request, the values of the GET parameters. - **/ - const char* const* getValues; - - /** - * @brief For a PUT or POST request, the content of the body. - **/ - const char* body; - - /** - * @brief For a PUT or POST request, the number of bytes of the body. - **/ - uint32_t bodySize; - - - /* -------------------------------------------------- - New in version 0.8.1 - -------------------------------------------------- */ - - /** - * @brief The number of HTTP headers. - **/ - uint32_t headersCount; - - /** - * @brief The keys of the HTTP headers (always converted to low-case). - **/ - const char* const* headersKeys; - - /** - * @brief The values of the HTTP headers. - **/ - const char* const* headersValues; - - } OrthancPluginHttpRequest; - - - typedef enum - { - /* Generic services */ - _OrthancPluginService_LogInfo = 1, - _OrthancPluginService_LogWarning = 2, - _OrthancPluginService_LogError = 3, - _OrthancPluginService_GetOrthancPath = 4, - _OrthancPluginService_GetOrthancDirectory = 5, - _OrthancPluginService_GetConfigurationPath = 6, - _OrthancPluginService_SetPluginProperty = 7, - _OrthancPluginService_GetGlobalProperty = 8, - _OrthancPluginService_SetGlobalProperty = 9, - _OrthancPluginService_GetCommandLineArgumentsCount = 10, - _OrthancPluginService_GetCommandLineArgument = 11, - _OrthancPluginService_GetExpectedDatabaseVersion = 12, - _OrthancPluginService_GetConfiguration = 13, - _OrthancPluginService_BufferCompression = 14, - _OrthancPluginService_ReadFile = 15, - _OrthancPluginService_WriteFile = 16, - _OrthancPluginService_GetErrorDescription = 17, - _OrthancPluginService_CallHttpClient = 18, - _OrthancPluginService_RegisterErrorCode = 19, - _OrthancPluginService_RegisterDictionaryTag = 20, - _OrthancPluginService_DicomBufferToJson = 21, - _OrthancPluginService_DicomInstanceToJson = 22, - _OrthancPluginService_CreateDicom = 23, - _OrthancPluginService_ComputeMd5 = 24, - _OrthancPluginService_ComputeSha1 = 25, - _OrthancPluginService_LookupDictionary = 26, - - /* Registration of callbacks */ - _OrthancPluginService_RegisterRestCallback = 1000, - _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, - _OrthancPluginService_RegisterStorageArea = 1002, - _OrthancPluginService_RegisterOnChangeCallback = 1003, - _OrthancPluginService_RegisterRestCallbackNoLock = 1004, - _OrthancPluginService_RegisterWorklistCallback = 1005, - _OrthancPluginService_RegisterDecodeImageCallback = 1006, - - /* Sending answers to REST calls */ - _OrthancPluginService_AnswerBuffer = 2000, - _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ - _OrthancPluginService_Redirect = 2002, - _OrthancPluginService_SendHttpStatusCode = 2003, - _OrthancPluginService_SendUnauthorized = 2004, - _OrthancPluginService_SendMethodNotAllowed = 2005, - _OrthancPluginService_SetCookie = 2006, - _OrthancPluginService_SetHttpHeader = 2007, - _OrthancPluginService_StartMultipartAnswer = 2008, - _OrthancPluginService_SendMultipartItem = 2009, - _OrthancPluginService_SendHttpStatus = 2010, - _OrthancPluginService_CompressAndAnswerImage = 2011, - _OrthancPluginService_SendMultipartItem2 = 2012, - - /* Access to the Orthanc database and API */ - _OrthancPluginService_GetDicomForInstance = 3000, - _OrthancPluginService_RestApiGet = 3001, - _OrthancPluginService_RestApiPost = 3002, - _OrthancPluginService_RestApiDelete = 3003, - _OrthancPluginService_RestApiPut = 3004, - _OrthancPluginService_LookupPatient = 3005, - _OrthancPluginService_LookupStudy = 3006, - _OrthancPluginService_LookupSeries = 3007, - _OrthancPluginService_LookupInstance = 3008, - _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, - _OrthancPluginService_RestApiGetAfterPlugins = 3010, - _OrthancPluginService_RestApiPostAfterPlugins = 3011, - _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, - _OrthancPluginService_RestApiPutAfterPlugins = 3013, - _OrthancPluginService_ReconstructMainDicomTags = 3014, - _OrthancPluginService_RestApiGet2 = 3015, - - /* Access to DICOM instances */ - _OrthancPluginService_GetInstanceRemoteAet = 4000, - _OrthancPluginService_GetInstanceSize = 4001, - _OrthancPluginService_GetInstanceData = 4002, - _OrthancPluginService_GetInstanceJson = 4003, - _OrthancPluginService_GetInstanceSimplifiedJson = 4004, - _OrthancPluginService_HasInstanceMetadata = 4005, - _OrthancPluginService_GetInstanceMetadata = 4006, - _OrthancPluginService_GetInstanceOrigin = 4007, - - /* Services for plugins implementing a database back-end */ - _OrthancPluginService_RegisterDatabaseBackend = 5000, - _OrthancPluginService_DatabaseAnswer = 5001, - _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, - _OrthancPluginService_StorageAreaCreate = 5003, - _OrthancPluginService_StorageAreaRead = 5004, - _OrthancPluginService_StorageAreaRemove = 5005, - - /* Primitives for handling images */ - _OrthancPluginService_GetImagePixelFormat = 6000, - _OrthancPluginService_GetImageWidth = 6001, - _OrthancPluginService_GetImageHeight = 6002, - _OrthancPluginService_GetImagePitch = 6003, - _OrthancPluginService_GetImageBuffer = 6004, - _OrthancPluginService_UncompressImage = 6005, - _OrthancPluginService_FreeImage = 6006, - _OrthancPluginService_CompressImage = 6007, - _OrthancPluginService_ConvertPixelFormat = 6008, - _OrthancPluginService_GetFontsCount = 6009, - _OrthancPluginService_GetFontInfo = 6010, - _OrthancPluginService_DrawText = 6011, - _OrthancPluginService_CreateImage = 6012, - _OrthancPluginService_CreateImageAccessor = 6013, - _OrthancPluginService_DecodeDicomImage = 6014, - - /* Primitives for handling worklists */ - _OrthancPluginService_WorklistAddAnswer = 7000, - _OrthancPluginService_WorklistMarkIncomplete = 7001, - _OrthancPluginService_WorklistIsMatch = 7002, - _OrthancPluginService_WorklistGetDicomQuery = 7003, - - _OrthancPluginService_INTERNAL = 0x7fffffff - } _OrthancPluginService; - - - typedef enum - { - _OrthancPluginProperty_Description = 1, - _OrthancPluginProperty_RootUri = 2, - _OrthancPluginProperty_OrthancExplorer = 3, - - _OrthancPluginProperty_INTERNAL = 0x7fffffff - } _OrthancPluginProperty; - - - - /** - * The memory layout of the pixels of an image. - * @ingroup Images - **/ - typedef enum - { - /** - * @brief Graylevel 8bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in - * one byte. - **/ - OrthancPluginPixelFormat_Grayscale8 = 1, - - /** - * @brief Graylevel, unsigned 16bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in - * two bytes. - **/ - OrthancPluginPixelFormat_Grayscale16 = 2, - - /** - * @brief Graylevel, signed 16bpp image. - * - * The image is graylevel. Each pixel is signed and stored in two - * bytes. - **/ - OrthancPluginPixelFormat_SignedGrayscale16 = 3, - - /** - * @brief Color image in RGB24 format. - * - * This format describes a color image. The pixels are stored in 3 - * consecutive bytes. The memory layout is RGB. - **/ - OrthancPluginPixelFormat_RGB24 = 4, - - /** - * @brief Color image in RGBA32 format. - * - * This format describes a color image. The pixels are stored in 4 - * consecutive bytes. The memory layout is RGBA. - **/ - OrthancPluginPixelFormat_RGBA32 = 5, - - OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ - - _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff - } OrthancPluginPixelFormat; - - - - /** - * The content types that are supported by Orthanc plugins. - **/ - typedef enum - { - OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ - OrthancPluginContentType_Dicom = 1, /*!< DICOM */ - OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ - - _OrthancPluginContentType_INTERNAL = 0x7fffffff - } OrthancPluginContentType; - - - - /** - * The supported types of DICOM resources. - **/ - typedef enum - { - OrthancPluginResourceType_Patient = 0, /*!< Patient */ - OrthancPluginResourceType_Study = 1, /*!< Study */ - OrthancPluginResourceType_Series = 2, /*!< Series */ - OrthancPluginResourceType_Instance = 3, /*!< Instance */ - OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ - - _OrthancPluginResourceType_INTERNAL = 0x7fffffff - } OrthancPluginResourceType; - - - - /** - * The supported types of changes that can happen to DICOM resources. - * @ingroup Callbacks - **/ - typedef enum - { - OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ - OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ - OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ - OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ - OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ - OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ - OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ - 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_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ - OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ - - _OrthancPluginChangeType_INTERNAL = 0x7fffffff - } OrthancPluginChangeType; - - - /** - * The compression algorithms that are supported by the Orthanc core. - * @ingroup Images - **/ - typedef enum - { - OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ - OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ - OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ - OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ - - _OrthancPluginCompressionType_INTERNAL = 0x7fffffff - } OrthancPluginCompressionType; - - - /** - * The image formats that are supported by the Orthanc core. - * @ingroup Images - **/ - typedef enum - { - OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ - OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ - OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ - - _OrthancPluginImageFormat_INTERNAL = 0x7fffffff - } OrthancPluginImageFormat; - - - /** - * The value representations present in the DICOM standard (version 2013). - * @ingroup Toolbox - **/ - typedef enum - { - OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ - OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ - OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ - OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ - OrthancPluginValueRepresentation_DA = 5, /*!< Date */ - OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ - OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ - OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ - OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ - OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ - OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ - OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ - OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ - OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ - OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ - OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ - OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ - OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ - OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ - OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ - OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ - OrthancPluginValueRepresentation_TM = 22, /*!< Time */ - OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ - OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ - OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ - OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ - OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ - - _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff - } OrthancPluginValueRepresentation; - - - /** - * The possible output formats for a DICOM-to-JSON conversion. - * @ingroup Toolbox - * @see OrthancPluginDicomToJson() - **/ - typedef enum - { - OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ - OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ - OrthancPluginDicomToJsonFormat_Human = 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; - - - /** - * Flags to the creation of a DICOM file. - * @ingroup Toolbox - * @see OrthancPluginCreateDicom() - **/ - typedef enum - { - OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ - OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ - - _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff - } OrthancPluginCreateDicomFlags; - - - /** - * The constraints on the DICOM identifiers that must be supported - * by the database plugins. - **/ - typedef enum - { - OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ - OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ - OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ - OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ - - _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff - } OrthancPluginIdentifierConstraint; - - - /** - * The origin of a DICOM instance that has been received by Orthanc. - **/ - typedef enum - { - OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ - OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ - OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ - OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ - OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ - - _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff - } OrthancPluginInstanceOrigin; - - - /** - * @brief A memory buffer allocated by the core system of Orthanc. - * - * A memory buffer allocated by the core system of Orthanc. When the - * content of the buffer is not useful anymore, it must be free by a - * call to ::OrthancPluginFreeMemoryBuffer(). - **/ - typedef struct - { - /** - * @brief The content of the buffer. - **/ - void* data; - - /** - * @brief The number of bytes in the buffer. - **/ - uint32_t size; - } OrthancPluginMemoryBuffer; - - - - - /** - * @brief Opaque structure that represents the HTTP connection to the client application. - * @ingroup Callback - **/ - typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; - - - - /** - * @brief Opaque structure that represents a DICOM instance received by Orthanc. - **/ - typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; - - - - /** - * @brief Opaque structure that represents an image that is uncompressed in memory. - * @ingroup Images - **/ - typedef struct _OrthancPluginImage_t OrthancPluginImage; - - - - /** - * @brief Opaque structure that represents the storage area that is actually used by Orthanc. - * @ingroup Images - **/ - typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; - - - - /** - * @brief Opaque structure to an object that represents a C-Find query. - * @ingroup Worklists - **/ - typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; - - - - /** - * @brief Opaque structure to an object that represents the answers to a C-Find query. - * @ingroup Worklists - **/ - typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; - - - - /** - * @brief Signature of a callback function that answers to a REST request. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( - OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - - - /** - * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( - OrthancPluginDicomInstance* instance, - const char* instanceId); - - - - /** - * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( - OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId); - - - - /** - * @brief Signature of a callback function to decode a DICOM instance as an image. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( - OrthancPluginImage** target, - const void* dicom, - const uint32_t size, - uint32_t frameIndex); - - - - /** - * @brief Signature of a function to free dynamic memory. - **/ - typedef void (*OrthancPluginFree) (void* buffer); - - - - /** - * @brief Callback for writing to the storage area. - * - * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. - * - * @param uuid The UUID of the file. - * @param content The content of the file. - * @param size The size of the file. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( - const char* uuid, - const void* content, - int64_t size, - OrthancPluginContentType type); - - - - /** - * @brief Callback for reading from the storage area. - * - * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. - * - * @param content The content of the file (output). - * @param size The size of the file (output). - * @param uuid The UUID of the file of interest. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( - void** content, - int64_t* size, - const char* uuid, - OrthancPluginContentType type); - - - - /** - * @brief Callback for removing a file from the storage area. - * - * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. - * - * @param uuid The UUID of the file to be removed. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( - const char* uuid, - OrthancPluginContentType type); - - - - /** - * @brief Callback to handle the C-Find SCP requests received by Orthanc. - * - * Signature of a callback function that is triggered when Orthanc - * receives a C-Find SCP request against modality worklists. - * - * @param answers The target structure where answers must be stored. - * @param query The worklist query. - * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. - * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( - OrthancPluginWorklistAnswers* answers, - const OrthancPluginWorklistQuery* query, - const char* remoteAet, - const char* calledAet); - - - - /** - * @brief Data structure that contains information about the Orthanc core. - **/ - typedef struct _OrthancPluginContext_t - { - void* pluginsManager; - const char* orthancVersion; - OrthancPluginFree Free; - OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, - _OrthancPluginService service, - const void* params); - } OrthancPluginContext; - - - - /** - * @brief An entry in the dictionary of DICOM tags. - **/ - typedef struct - { - uint16_t group; /*!< The group of the tag */ - uint16_t element; /*!< The element of the tag */ - OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ - uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ - uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ - } OrthancPluginDictionaryEntry; - - - - /** - * @brief Free a string. - * - * Free a string that was allocated by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param str The string to be freed. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( - OrthancPluginContext* context, - char* str) - { - if (str != NULL) - { - context->Free(str); - } - } - - - /** - * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. - * - * This function checks whether the version of this C header is - * compatible with the current version of Orthanc. The result of - * this function should always be checked in the - * OrthancPluginInitialize() entry point of the plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return 1 if and only if the versions are compatible. If the - * result is 0, the initialization of the plugin should fail. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( - OrthancPluginContext* context) - { - int major, minor, revision; - - if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || - sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || - sizeof(int32_t) != sizeof(_OrthancPluginService) || - sizeof(int32_t) != sizeof(_OrthancPluginProperty) || - sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || - sizeof(int32_t) != sizeof(OrthancPluginContentType) || - sizeof(int32_t) != sizeof(OrthancPluginResourceType) || - sizeof(int32_t) != sizeof(OrthancPluginChangeType) || - sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || - sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || - sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || - sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || - sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || - sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || - sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || - sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin)) - { - /* Mismatch in the size of the enumerations */ - return 0; - } - - /* Assume compatibility with the mainline */ - if (!strcmp(context->orthancVersion, "mainline")) - { - return 1; - } - - /* Parse the version of the Orthanc core */ - if ( -#ifdef _MSC_VER - sscanf_s -#else - sscanf -#endif - (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) - { - return 0; - } - - /* Check the major number of the version */ - - if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) - { - return 1; - } - - if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) - { - return 0; - } - - /* Check the minor number of the version */ - - if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) - { - return 1; - } - - if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) - { - return 0; - } - - /* Check the revision number of the version */ - - if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) - { - return 1; - } - else - { - return 0; - } - } - - - /** - * @brief Free a memory buffer. - * - * Free a memory buffer that was allocated by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The memory buffer to release. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* buffer) - { - context->Free(buffer->data); - } - - - /** - * @brief Log an error. - * - * Log an error message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogError, message); - } - - - /** - * @brief Log a warning. - * - * Log a warning message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogWarning, message); - } - - - /** - * @brief Log an information. - * - * Log an information message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogInfo, message); - } - - - - typedef struct - { - const char* pathRegularExpression; - OrthancPluginRestCallback callback; - } _OrthancPluginRestCallback; - - /** - * @brief Register a REST callback. - * - * This function registers a REST callback against a regular - * expression for a URI. This function must be called during the - * initialization of the plugin, i.e. inside the - * OrthancPluginInitialize() public function. - * - * Each REST callback is guaranteed to run in mutual exclusion. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param pathRegularExpression Regular expression for the URI. May contain groups. - * @param callback The callback function to handle the REST call. - * @see OrthancPluginRegisterRestCallbackNoLock() - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( - OrthancPluginContext* context, - const char* pathRegularExpression, - OrthancPluginRestCallback callback) - { - _OrthancPluginRestCallback params; - params.pathRegularExpression = pathRegularExpression; - params.callback = callback; - context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); - } - - - - /** - * @brief Register a REST callback, without locking. - * - * This function registers a REST callback against a regular - * expression for a URI. This function must be called during the - * initialization of the plugin, i.e. inside the - * OrthancPluginInitialize() public function. - * - * Contrarily to OrthancPluginRegisterRestCallback(), the callback - * will NOT be invoked in mutual exclusion. This can be useful for - * high-performance plugins that must handle concurrent requests - * (Orthanc uses a pool of threads, one thread being assigned to - * each incoming HTTP request). Of course, it is up to the plugin to - * implement the required locking mechanisms. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param pathRegularExpression Regular expression for the URI. May contain groups. - * @param callback The callback function to handle the REST call. - * @see OrthancPluginRegisterRestCallback() - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( - OrthancPluginContext* context, - const char* pathRegularExpression, - OrthancPluginRestCallback callback) - { - _OrthancPluginRestCallback params; - params.pathRegularExpression = pathRegularExpression; - params.callback = callback; - context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); - } - - - - typedef struct - { - OrthancPluginOnStoredInstanceCallback callback; - } _OrthancPluginOnStoredInstanceCallback; - - /** - * @brief Register a callback for received instances. - * - * This function registers a callback function that is called - * whenever a new DICOM instance is stored into the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback function. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( - OrthancPluginContext* context, - OrthancPluginOnStoredInstanceCallback callback) - { - _OrthancPluginOnStoredInstanceCallback params; - params.callback = callback; - - context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* answer; - uint32_t answerSize; - const char* mimeType; - } _OrthancPluginAnswerBuffer; - - /** - * @brief Answer to a REST request. - * - * This function answers to a REST request with the content of a memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the answer. - * @param answerSize Number of bytes of the answer. - * @param mimeType The MIME type of the answer. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize, - const char* mimeType) - { - _OrthancPluginAnswerBuffer params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.mimeType = mimeType; - context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); - } - - - typedef struct - { - OrthancPluginRestOutput* output; - OrthancPluginPixelFormat format; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - } _OrthancPluginCompressAndAnswerPngImage; - - typedef struct - { - OrthancPluginRestOutput* output; - OrthancPluginImageFormat imageFormat; - OrthancPluginPixelFormat pixelFormat; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - uint8_t quality; - } _OrthancPluginCompressAndAnswerImage; - - - /** - * @brief Answer to a REST request with a PNG image. - * - * This function answers to a REST request with a PNG image. The - * parameters of this function describe a memory buffer that - * contains an uncompressed image. The image will be automatically compressed - * as a PNG image by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer) - { - _OrthancPluginCompressAndAnswerImage params; - params.output = output; - params.imageFormat = OrthancPluginImageFormat_Png; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = 0; /* No quality for PNG */ - context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* instanceId; - } _OrthancPluginGetDicomForInstance; - - /** - * @brief Retrieve a DICOM instance using its Orthanc identifier. - * - * Retrieve a DICOM instance using its Orthanc identifier. The DICOM - * file is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param instanceId The Orthanc identifier of the DICOM instance of interest. - * @return 0 if success, or the error code if failure. - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* instanceId) - { - _OrthancPluginGetDicomForInstance params; - params.target = target; - params.instanceId = instanceId; - return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - } _OrthancPluginRestApiGet; - - /** - * @brief Make a GET call to the built-in Orthanc REST API. - * - * Make a GET call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiGetAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri) - { - _OrthancPluginRestApiGet params; - params.target = target; - params.uri = uri; - return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); - } - - - - /** - * @brief Make a GET call to the REST API, as tainted by the plugins. - * - * Make a GET call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiGet - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri) - { - _OrthancPluginRestApiGet params; - params.target = target; - params.uri = uri; - return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - const char* body; - uint32_t bodySize; - } _OrthancPluginRestApiPostPut; - - /** - * @brief Make a POST call to the built-in Orthanc REST API. - * - * Make a POST call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the POST request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPostAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); - } - - - /** - * @brief Make a POST call to the REST API, as tainted by the plugins. - * - * Make a POST call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the POST request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPost - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); - } - - - - /** - * @brief Make a DELETE call to the built-in Orthanc REST API. - * - * Make a DELETE call to the built-in Orthanc REST API. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The URI to delete in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiDeleteAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( - OrthancPluginContext* context, - const char* uri) - { - return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); - } - - - /** - * @brief Make a DELETE call to the REST API, as tainted by the plugins. - * - * Make a DELETE call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The URI to delete in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiDelete - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( - OrthancPluginContext* context, - const char* uri) - { - return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); - } - - - - /** - * @brief Make a PUT call to the built-in Orthanc REST API. - * - * Make a PUT call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the PUT request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPutAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); - } - - - - /** - * @brief Make a PUT call to the REST API, as tainted by the plugins. - * - * Make a PUT call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the PUT request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPut - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* argument; - } _OrthancPluginOutputPlusArgument; - - /** - * @brief Redirect a REST request. - * - * This function answers to a REST request by redirecting the user - * to another URI using HTTP status 301. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param redirection Where to redirect. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* redirection) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = redirection; - context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); - } - - - - typedef struct - { - char** result; - const char* argument; - } _OrthancPluginRetrieveDynamicString; - - /** - * @brief Look for a patient. - * - * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored patients). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param patientID The Patient ID of interest. - * @return The NULL value if the patient is non-existent, or a string containing the - * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( - OrthancPluginContext* context, - const char* patientID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = patientID; - - if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a study. - * - * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored studies). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param studyUID The Study Instance UID of interest. - * @return The NULL value if the study is non-existent, or a string containing the - * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( - OrthancPluginContext* context, - const char* studyUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = studyUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a study, using the accession number. - * - * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored studies). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param accessionNumber The Accession Number of interest. - * @return The NULL value if the study is non-existent, or a string containing the - * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( - OrthancPluginContext* context, - const char* accessionNumber) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = accessionNumber; - - if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a series. - * - * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored series). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param seriesUID The Series Instance UID of interest. - * @return The NULL value if the series is non-existent, or a string containing the - * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( - OrthancPluginContext* context, - const char* seriesUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = seriesUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for an instance. - * - * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored instances). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param sopInstanceUID The SOP Instance UID of interest. - * @return The NULL value if the instance is non-existent, or a string containing the - * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( - OrthancPluginContext* context, - const char* sopInstanceUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = sopInstanceUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - uint16_t status; - } _OrthancPluginSendHttpStatusCode; - - /** - * @brief Send a HTTP status code. - * - * This function answers to a REST request by sending a HTTP status - * code (such as "400 - Bad Request"). Note that: - * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). - * - Redirections (status 301) must use ::OrthancPluginRedirect(). - * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). - * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param status The HTTP status code to be sent. - * @ingroup REST - * @see OrthancPluginSendHttpStatus() - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - uint16_t status) - { - _OrthancPluginSendHttpStatusCode params; - params.output = output; - params.status = status; - context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); - } - - - /** - * @brief Signal that a REST request is not authorized. - * - * This function answers to a REST request by signaling that it is - * not authorized. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param realm The realm for the authorization process. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* realm) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = realm; - context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); - } - - - /** - * @brief Signal that this URI does not support this HTTP method. - * - * This function answers to a REST request by signaling that the - * queried URI does not support this method. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* allowedMethods) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = allowedMethods; - context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); - } - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* key; - const char* value; - } _OrthancPluginSetHttpHeader; - - /** - * @brief Set a cookie. - * - * This function sets a cookie in the HTTP client. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param cookie The cookie to be set. - * @param value The value of the cookie. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* cookie, - const char* value) - { - _OrthancPluginSetHttpHeader params; - params.output = output; - params.key = cookie; - params.value = value; - context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); - } - - - /** - * @brief Set some HTTP header. - * - * This function sets a HTTP header in the HTTP answer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param key The HTTP header to be set. - * @param value The value of the HTTP header. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* key, - const char* value) - { - _OrthancPluginSetHttpHeader params; - params.output = output; - params.key = key; - params.value = value; - context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); - } - - - typedef struct - { - char** resultStringToFree; - const char** resultString; - int64_t* resultInt64; - const char* key; - OrthancPluginDicomInstance* instance; - OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ - } _OrthancPluginAccessDicomInstance; - - - /** - * @brief Get the AET of a DICOM instance. - * - * This function returns the Application Entity Title (AET) of the - * DICOM modality from which a DICOM instance originates. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The AET if success, NULL if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the size of a DICOM file. - * - * This function returns the number of bytes of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The size of the file, -1 in case of error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - int64_t size; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultInt64 = &size; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return -1; - } - else - { - return size; - } - } - - - /** - * @brief Get the data of a DICOM file. - * - * This function returns a pointer to the content of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The pointer to the DICOM data, NULL in case of error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the DICOM tag hierarchy as a JSON file. - * - * This function returns a pointer to a newly created string - * containing a JSON file. This JSON file encodes the tag hierarchy - * of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The NULL value in case of error, or a string containing the JSON file. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultStringToFree = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). - * - * This function returns a pointer to a newly created string - * containing a JSON file. This JSON file encodes the tag hierarchy - * of the given DICOM instance. In contrast with - * ::OrthancPluginGetInstanceJson(), the returned JSON file is in - * its simplified version. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The NULL value in case of error, or a string containing the JSON file. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultStringToFree = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Check whether a DICOM instance is associated with some metadata. - * - * This function checks whether the DICOM instance of interest is - * associated with some metadata. As of Orthanc 0.8.1, in the - * callbacks registered by - * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only - * possibly available metadata are "ReceptionDate", "RemoteAET" and - * "IndexInSeries". - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @param metadata The metadata of interest. - * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) - { - int64_t result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultInt64 = &result; - params.instance = instance; - params.key = metadata; - - if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return -1; - } - else - { - return (result != 0); - } - } - - - /** - * @brief Get the value of some metadata associated with a given DICOM instance. - * - * This functions returns the value of some metadata that is associated with the DICOM instance of interest. - * Before calling this function, the existence of the metadata must have been checked with - * ::OrthancPluginHasInstanceMetadata(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @param metadata The metadata of interest. - * @return The metadata value if success, NULL if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - params.key = metadata; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginStorageCreate create; - OrthancPluginStorageRead read; - OrthancPluginStorageRemove remove; - OrthancPluginFree free; - } _OrthancPluginRegisterStorageArea; - - /** - * @brief Register a custom storage area. - * - * This function registers a custom storage area, to replace the - * built-in way Orthanc stores its files on the filesystem. This - * function must be called during the initialization of the plugin, - * i.e. inside the OrthancPluginInitialize() public function. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param create The callback function to store a file on the custom storage area. - * @param read The callback function to read a file from the custom storage area. - * @param remove The callback function to remove a file from the custom storage area. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( - OrthancPluginContext* context, - OrthancPluginStorageCreate create, - OrthancPluginStorageRead read, - OrthancPluginStorageRemove remove) - { - _OrthancPluginRegisterStorageArea params; - params.create = create; - params.read = read; - params.remove = remove; - -#ifdef __cplusplus - params.free = ::free; -#else - params.free = free; -#endif - - context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); - } - - - - /** - * @brief Return the path to the Orthanc executable. - * - * This function returns the path to the Orthanc executable. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the directory containing the Orthanc. - * - * This function returns the path to the directory containing the Orthanc executable. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the path to the configuration file(s). - * - * This function returns the path to the configuration file(s) that - * was specified when starting Orthanc. Since version 0.9.1, this - * path can refer to a folder that stores a set of configuration - * files. This function is deprecated in favor of - * OrthancPluginGetConfiguration(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - * @see OrthancPluginGetConfiguration() - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginOnChangeCallback callback; - } _OrthancPluginOnChangeCallback; - - /** - * @brief Register a callback to monitor changes. - * - * This function registers a callback function that is called - * whenever a change happens to some DICOM resource. - * - * @warning If your change callback has to call the REST API of - * Orthanc, you should make these calls in a separate thread (with - * the events passing through a message queue). Otherwise, this - * could result in deadlocks in the presence of other plugins or Lua - * script. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback function. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( - OrthancPluginContext* context, - OrthancPluginOnChangeCallback callback) - { - _OrthancPluginOnChangeCallback params; - params.callback = callback; - - context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); - } - - - - typedef struct - { - const char* plugin; - _OrthancPluginProperty property; - const char* value; - } _OrthancPluginSetPluginProperty; - - - /** - * @brief Set the URI where the plugin provides its Web interface. - * - * For plugins that come with a Web interface, this function - * declares the entry path where to find this interface. This - * information is notably used in the "Plugins" page of Orthanc - * Explorer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The root URI for this plugin. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( - OrthancPluginContext* context, - const char* uri) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_RootUri; - params.value = uri; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - /** - * @brief Set a description for this plugin. - * - * Set a description for this plugin. It is displayed in the - * "Plugins" page of Orthanc Explorer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param description The description. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( - OrthancPluginContext* context, - const char* description) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_Description; - params.value = description; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - /** - * @brief Extend the JavaScript code of Orthanc Explorer. - * - * Add JavaScript code to customize the default behavior of Orthanc - * Explorer. This can for instance be used to add new buttons. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param javascript The custom JavaScript code. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( - OrthancPluginContext* context, - const char* javascript) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_OrthancExplorer; - params.value = javascript; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - typedef struct - { - char** result; - int32_t property; - const char* value; - } _OrthancPluginGlobalProperty; - - - /** - * @brief Get the value of a global property. - * - * Get the value of a global property that is stored in the Orthanc database. Global - * properties whose index is below 1024 are reserved by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param property The global property of interest. - * @param defaultValue The value to return, if the global property is unset. - * @return The value of the global property, or NULL in the case of an error. This - * string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( - OrthancPluginContext* context, - int32_t property, - const char* defaultValue) - { - char* result; - - _OrthancPluginGlobalProperty params; - params.result = &result; - params.property = property; - params.value = defaultValue; - - if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Set the value of a global property. - * - * Set the value of a global property into the Orthanc - * database. Setting a global property can be used by plugins to - * save their internal parameters. Plugins are only allowed to set - * properties whose index are above or equal to 1024 (properties - * below 1024 are read-only and reserved by Orthanc). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param property The global property of interest. - * @param value The value to be set in the global property. - * @return 0 if success, or the error code if failure. - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( - OrthancPluginContext* context, - int32_t property, - const char* value) - { - _OrthancPluginGlobalProperty params; - params.result = NULL; - params.property = property; - params.value = value; - - return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); - } - - - - typedef struct - { - int32_t *resultInt32; - uint32_t *resultUint32; - int64_t *resultInt64; - uint64_t *resultUint64; - } _OrthancPluginReturnSingleValue; - - /** - * @brief Get the number of command-line arguments. - * - * Retrieve the number of command-line arguments that were used to launch Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The number of arguments. - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - /** - * @brief Get the value of a command-line argument. - * - * Get the value of one of the command-line arguments that were used - * to launch Orthanc. The number of available arguments can be - * retrieved by OrthancPluginGetCommandLineArgumentsCount(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param argument The index of the argument. - * @return The value of the argument, or NULL in the case of an error. This - * string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( - OrthancPluginContext* context, - uint32_t argument) - { - char* result; - - _OrthancPluginGlobalProperty params; - params.result = &result; - params.property = (int32_t) argument; - params.value = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the expected version of the database schema. - * - * Retrieve the expected version of the database schema. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The version. - * @ingroup Callbacks - * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - /** - * @brief Return the content of the configuration file(s). - * - * This function returns the content of the configuration that is - * used by Orthanc, formatted as a JSON string. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the configuration. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* subType; - const char* contentType; - } _OrthancPluginStartMultipartAnswer; - - /** - * @brief Start an HTTP multipart answer. - * - * Initiates a HTTP multipart answer, as the result of a REST request. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param subType The sub-type of the multipart answer ("mixed" or "related"). - * @param contentType The MIME type of the items in the multipart answer. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* subType, - const char* contentType) - { - _OrthancPluginStartMultipartAnswer params; - params.output = output; - params.subType = subType; - params.contentType = contentType; - return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); - } - - - /** - * @brief Send an item as a part of some HTTP multipart answer. - * - * This function sends an item as a part of some HTTP multipart - * answer that was initiated by OrthancPluginStartMultipartAnswer(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the item. - * @param answerSize Number of bytes of the item. - * @return 0 if success, or the error code if failure (this notably happens - * if the connection is closed by the client). - * @see OrthancPluginSendMultipartItem2() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize) - { - _OrthancPluginAnswerBuffer params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.mimeType = NULL; - return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const void* source; - uint32_t size; - OrthancPluginCompressionType compression; - uint8_t uncompress; - } _OrthancPluginBufferCompression; - - - /** - * @brief Compress or decompress a buffer. - * - * This function compresses or decompresses a buffer, using the - * version of the zlib library that is used by the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param source The source buffer. - * @param size The size in bytes of the source buffer. - * @param compression The compression algorithm. - * @param uncompress If set to "0", the buffer must be compressed. - * If set to "1", the buffer must be uncompressed. - * @return 0 if success, or the error code if failure. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const void* source, - uint32_t size, - OrthancPluginCompressionType compression, - uint8_t uncompress) - { - _OrthancPluginBufferCompression params; - params.target = target; - params.source = source; - params.size = size; - params.compression = compression; - params.uncompress = uncompress; - - return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* path; - } _OrthancPluginReadFile; - - /** - * @brief Read a file. - * - * Read the content of a file on the filesystem, and returns it into - * a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param path The path of the file to be read. - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* path) - { - _OrthancPluginReadFile params; - params.target = target; - params.path = path; - return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); - } - - - - typedef struct - { - const char* path; - const void* data; - uint32_t size; - } _OrthancPluginWriteFile; - - /** - * @brief Write a file. - * - * Write the content of a memory buffer to the filesystem. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param path The path of the file to be written. - * @param data The content of the memory buffer. - * @param size The size of the memory buffer. - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( - OrthancPluginContext* context, - const char* path, - const void* data, - uint32_t size) - { - _OrthancPluginWriteFile params; - params.path = path; - params.data = data; - params.size = size; - return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); - } - - - - typedef struct - { - const char** target; - OrthancPluginErrorCode error; - } _OrthancPluginGetErrorDescription; - - /** - * @brief Get the description of a given error code. - * - * This function returns the description of a given error code. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param error The error code of interest. - * @return The error description. This is a statically-allocated - * string, do not free it. - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( - OrthancPluginContext* context, - OrthancPluginErrorCode error) - { - const char* result = NULL; - - _OrthancPluginGetErrorDescription params; - params.target = &result; - params.error = error; - - if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || - result == NULL) - { - return "Unknown error code"; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - uint16_t status; - const char* body; - uint32_t bodySize; - } _OrthancPluginSendHttpStatus; - - /** - * @brief Send a HTTP status, with a custom body. - * - * This function answers to a HTTP request by sending a HTTP status - * code (such as "400 - Bad Request"), together with a body - * describing the error. The body will only be returned if the - * configuration option "HttpDescribeErrors" of Orthanc is set to "true". - * - * Note that: - * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). - * - Redirections (status 301) must use ::OrthancPluginRedirect(). - * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). - * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param status The HTTP status code to be sent. - * @param body The body of the answer. - * @param bodySize The size of the body. - * @see OrthancPluginSendHttpStatusCode() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - uint16_t status, - const char* body, - uint32_t bodySize) - { - _OrthancPluginSendHttpStatus params; - params.output = output; - params.status = status; - params.body = body; - params.bodySize = bodySize; - context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); - } - - - - typedef struct - { - const OrthancPluginImage* image; - uint32_t* resultUint32; - OrthancPluginPixelFormat* resultPixelFormat; - void** resultBuffer; - } _OrthancPluginGetImageInfo; - - - /** - * @brief Return the pixel format of an image. - * - * This function returns the type of memory layout for the pixels of the given image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The pixel format. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - OrthancPluginPixelFormat target; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultPixelFormat = ⌖ - - if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) - { - return OrthancPluginPixelFormat_Unknown; - } - else - { - return (OrthancPluginPixelFormat) target; - } - } - - - - /** - * @brief Return the width of an image. - * - * This function returns the width of the given image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The width. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - uint32_t width; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultUint32 = &width; - - if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return width; - } - } - - - - /** - * @brief Return the height of an image. - * - * This function returns the height of the given image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The height. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - uint32_t height; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultUint32 = &height; - - if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return height; - } - } - - - - /** - * @brief Return the pitch of an image. - * - * This function returns the pitch of the given image. The pitch is - * defined as the number of bytes between 2 successive lines of the - * image in the memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The pitch. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - uint32_t pitch; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultUint32 = &pitch; - - if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return pitch; - } - } - - - - /** - * @brief Return a pointer to the content of an image. - * - * This function returns a pointer to the memory buffer that - * contains the pixels of the image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The pointer. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - void* target = NULL; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.resultBuffer = ⌖ - params.image = image; - - if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - typedef struct - { - OrthancPluginImage** target; - const void* data; - uint32_t size; - OrthancPluginImageFormat format; - } _OrthancPluginUncompressImage; - - - /** - * @brief Decode a compressed image. - * - * This function decodes a compressed image from a memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param data Pointer to a memory buffer containing the compressed image. - * @param size Size of the memory buffer containing the compressed image. - * @param format The file format of the compressed image. - * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( - OrthancPluginContext* context, - const void* data, - uint32_t size, - OrthancPluginImageFormat format) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginUncompressImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.data = data; - params.size = size; - params.format = format; - - if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - - typedef struct - { - OrthancPluginImage* image; - } _OrthancPluginFreeImage; - - /** - * @brief Free an image. - * - * This function frees an image that was decoded with OrthancPluginUncompressImage(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( - OrthancPluginContext* context, - OrthancPluginImage* image) - { - _OrthancPluginFreeImage params; - params.image = image; - - context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); - } - - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - OrthancPluginImageFormat imageFormat; - OrthancPluginPixelFormat pixelFormat; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - uint8_t quality; - } _OrthancPluginCompressImage; - - - /** - * @brief Encode a PNG image. - * - * This function compresses the given memory buffer containing an - * image using the PNG specification, and stores the result of the - * compression into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginCompressAndAnswerPngImage() - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer) - { - _OrthancPluginCompressImage params; - memset(¶ms, 0, sizeof(params)); - params.target = target; - params.imageFormat = OrthancPluginImageFormat_Png; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = 0; /* Unused for PNG */ - - return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); - } - - - /** - * @brief Encode a JPEG image. - * - * This function compresses the given memory buffer containing an - * image using the JPEG specification, and stores the result of the - * compression into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @param quality The quality of the JPEG encoding, between 1 (worst - * quality, best compression) and 100 (best quality, worst - * compression). - * @return 0 if success, or the error code if failure. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer, - uint8_t quality) - { - _OrthancPluginCompressImage params; - memset(¶ms, 0, sizeof(params)); - params.target = target; - params.imageFormat = OrthancPluginImageFormat_Jpeg; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = quality; - - return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); - } - - - - /** - * @brief Answer to a REST request with a JPEG image. - * - * This function answers to a REST request with a JPEG image. The - * parameters of this function describe a memory buffer that - * contains an uncompressed image. The image will be automatically compressed - * as a JPEG image by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @param quality The quality of the JPEG encoding, between 1 (worst - * quality, best compression) and 100 (best quality, worst - * compression). - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer, - uint8_t quality) - { - _OrthancPluginCompressAndAnswerImage params; - params.output = output; - params.imageFormat = OrthancPluginImageFormat_Jpeg; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = quality; - context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); - } - - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - OrthancPluginHttpMethod method; - const char* url; - const char* username; - const char* password; - const char* body; - uint32_t bodySize; - } _OrthancPluginCallHttpClient; - - - /** - * @brief Issue a HTTP GET call. - * - * Make a HTTP GET call to the given URL. The result to the query is - * stored into a newly allocated memory buffer. Favor - * OrthancPluginRestApiGet() if calling the built-in REST API of the - * Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param url The URL of interest. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* url, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.target = target; - params.method = OrthancPluginHttpMethod_Get; - params.url = url; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - /** - * @brief Issue a HTTP POST call. - * - * Make a HTTP POST call to the given URL. The result to the query - * is stored into a newly allocated memory buffer. Favor - * OrthancPluginRestApiPost() if calling the built-in REST API of - * the Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param url The URL of interest. - * @param body The content of the body of the request. - * @param bodySize The size of the body of the request. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* url, - const char* body, - uint32_t bodySize, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.target = target; - params.method = OrthancPluginHttpMethod_Post; - params.url = url; - params.body = body; - params.bodySize = bodySize; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - /** - * @brief Issue a HTTP PUT call. - * - * Make a HTTP PUT call to the given URL. The result to the query is - * stored into a newly allocated memory buffer. Favor - * OrthancPluginRestApiPut() if calling the built-in REST API of the - * Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param url The URL of interest. - * @param body The content of the body of the request. - * @param bodySize The size of the body of the request. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* url, - const char* body, - uint32_t bodySize, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.target = target; - params.method = OrthancPluginHttpMethod_Put; - params.url = url; - params.body = body; - params.bodySize = bodySize; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - /** - * @brief Issue a HTTP DELETE call. - * - * Make a HTTP DELETE call to the given URL. Favor - * OrthancPluginRestApiDelete() if calling the built-in REST API of - * the Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param url The URL of interest. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( - OrthancPluginContext* context, - const char* url, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.method = OrthancPluginHttpMethod_Delete; - params.url = url; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - - typedef struct - { - OrthancPluginImage** target; - const OrthancPluginImage* source; - OrthancPluginPixelFormat targetFormat; - } _OrthancPluginConvertPixelFormat; - - - /** - * @brief Change the pixel format of an image. - * - * This function creates a new image, changing the memory layout of the pixels. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param source The source image. - * @param targetFormat The target pixel format. - * @return The resulting image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( - OrthancPluginContext* context, - const OrthancPluginImage* source, - OrthancPluginPixelFormat targetFormat) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginConvertPixelFormat params; - params.target = ⌖ - params.source = source; - params.targetFormat = targetFormat; - - if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - /** - * @brief Return the number of available fonts. - * - * This function returns the number of fonts that are built in the - * Orthanc core. These fonts can be used to draw texts on images - * through OrthancPluginDrawText(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The number of fonts. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - - typedef struct - { - uint32_t fontIndex; /* in */ - const char** name; /* out */ - uint32_t* size; /* out */ - } _OrthancPluginGetFontInfo; - - /** - * @brief Return the name of a font. - * - * This function returns the name of a font that is built in the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). - * @return The font name. This is a statically-allocated string, do not free it. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( - OrthancPluginContext* context, - uint32_t fontIndex) - { - const char* result = NULL; - - _OrthancPluginGetFontInfo params; - memset(¶ms, 0, sizeof(params)); - params.name = &result; - params.fontIndex = fontIndex; - - if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the size of a font. - * - * This function returns the size of a font that is built in the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). - * @return The font size. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( - OrthancPluginContext* context, - uint32_t fontIndex) - { - uint32_t result; - - _OrthancPluginGetFontInfo params; - memset(¶ms, 0, sizeof(params)); - params.size = &result; - params.fontIndex = fontIndex; - - if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginImage* image; - uint32_t fontIndex; - const char* utf8Text; - int32_t x; - int32_t y; - uint8_t r; - uint8_t g; - uint8_t b; - } _OrthancPluginDrawText; - - - /** - * @brief Draw text on an image. - * - * This function draws some text on some image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image upon which to draw the text. - * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). - * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. - * @param x The X position of the text over the image. - * @param y The Y position of the text over the image. - * @param r The value of the red color channel of the text. - * @param g The value of the green color channel of the text. - * @param b The value of the blue color channel of the text. - * @return 0 if success, other value if error. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( - OrthancPluginContext* context, - OrthancPluginImage* image, - uint32_t fontIndex, - const char* utf8Text, - int32_t x, - int32_t y, - uint8_t r, - uint8_t g, - uint8_t b) - { - _OrthancPluginDrawText params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.fontIndex = fontIndex; - params.utf8Text = utf8Text; - params.x = x; - params.y = y; - params.r = r; - params.g = g; - params.b = b; - - return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); - } - - - - typedef struct - { - OrthancPluginStorageArea* storageArea; - const char* uuid; - const void* content; - uint64_t size; - OrthancPluginContentType type; - } _OrthancPluginStorageAreaCreate; - - - /** - * @brief Create a file inside the storage area. - * - * This function creates a new file inside the storage area that is - * currently used by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param storageArea The storage area. - * @param uuid The identifier of the file to be created. - * @param content The content to store in the newly created file. - * @param size The size of the content. - * @param type The type of the file content. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( - OrthancPluginContext* context, - OrthancPluginStorageArea* storageArea, - const char* uuid, - const void* content, - uint64_t size, - OrthancPluginContentType type) - { - _OrthancPluginStorageAreaCreate params; - params.storageArea = storageArea; - params.uuid = uuid; - params.content = content; - params.size = size; - params.type = type; - - return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); - } - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - OrthancPluginStorageArea* storageArea; - const char* uuid; - OrthancPluginContentType type; - } _OrthancPluginStorageAreaRead; - - - /** - * @brief Read a file from the storage area. - * - * This function reads the content of a given file from the storage - * area that is currently used by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param storageArea The storage area. - * @param uuid The identifier of the file to be read. - * @param type The type of the file content. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - OrthancPluginStorageArea* storageArea, - const char* uuid, - OrthancPluginContentType type) - { - _OrthancPluginStorageAreaRead params; - params.target = target; - params.storageArea = storageArea; - params.uuid = uuid; - params.type = type; - - return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); - } - - - typedef struct - { - OrthancPluginStorageArea* storageArea; - const char* uuid; - OrthancPluginContentType type; - } _OrthancPluginStorageAreaRemove; - - /** - * @brief Remove a file from the storage area. - * - * This function removes a given file from the storage area that is - * currently used by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param storageArea The storage area. - * @param uuid The identifier of the file to be removed. - * @param type The type of the file content. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( - OrthancPluginContext* context, - OrthancPluginStorageArea* storageArea, - const char* uuid, - OrthancPluginContentType type) - { - _OrthancPluginStorageAreaRemove params; - params.storageArea = storageArea; - params.uuid = uuid; - params.type = type; - - return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); - } - - - - typedef struct - { - OrthancPluginErrorCode* target; - int32_t code; - uint16_t httpStatus; - const char* message; - } _OrthancPluginRegisterErrorCode; - - /** - * @brief Declare a custom error code for this plugin. - * - * This function declares a custom error code that can be generated - * by this plugin. This declaration is used to enrich the body of - * the HTTP answer in the case of an error, and to set the proper - * HTTP status code. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param code The error code that is internal to this plugin. - * @param httpStatus The HTTP status corresponding to this error. - * @param message The description of the error. - * @return The error code that has been assigned inside the Orthanc core. - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( - OrthancPluginContext* context, - int32_t code, - uint16_t httpStatus, - const char* message) - { - OrthancPluginErrorCode target; - - _OrthancPluginRegisterErrorCode params; - params.target = ⌖ - params.code = code; - params.httpStatus = httpStatus; - params.message = message; - - if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) - { - return target; - } - else - { - /* There was an error while assigned the error. Use a generic code. */ - return OrthancPluginErrorCode_Plugin; - } - } - - - - typedef struct - { - uint16_t group; - uint16_t element; - OrthancPluginValueRepresentation vr; - const char* name; - uint32_t minMultiplicity; - uint32_t maxMultiplicity; - } _OrthancPluginRegisterDictionaryTag; - - /** - * @brief Register a new tag into the DICOM dictionary. - * - * This function declares a new tag in the dictionary of DICOM tags - * that are known to Orthanc. This function should be used in the - * OrthancPluginInitialize() callback. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param group The group of the tag. - * @param element The element of the tag. - * @param vr The value representation of the tag. - * @param name The nickname of the tag. - * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). - * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means - * an arbitrary multiplicity ("n"). - * @return 0 if success, other value if error. - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( - OrthancPluginContext* context, - uint16_t group, - uint16_t element, - OrthancPluginValueRepresentation vr, - const char* name, - uint32_t minMultiplicity, - uint32_t maxMultiplicity) - { - _OrthancPluginRegisterDictionaryTag params; - params.group = group; - params.element = element; - params.vr = vr; - params.name = name; - params.minMultiplicity = minMultiplicity; - params.maxMultiplicity = maxMultiplicity; - - return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); - } - - - - - typedef struct - { - OrthancPluginStorageArea* storageArea; - OrthancPluginResourceType level; - } _OrthancPluginReconstructMainDicomTags; - - /** - * @brief Reconstruct the main DICOM tags. - * - * This function requests the Orthanc core to reconstruct the main - * DICOM tags of all the resources of the given type. This function - * can only be used as a part of the upgrade of a custom database - * back-end - * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A - * database transaction will be automatically setup. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param storageArea The storage area. - * @param level The type of the resources of interest. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( - OrthancPluginContext* context, - OrthancPluginStorageArea* storageArea, - OrthancPluginResourceType level) - { - _OrthancPluginReconstructMainDicomTags params; - params.level = level; - params.storageArea = storageArea; - - return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); - } - - - 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 Flags governing the output. - * @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 Flags governing the output. - * @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; - } - } - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - uint32_t headersCount; - const char* const* headersKeys; - const char* const* headersValues; - int32_t afterPlugins; - } _OrthancPluginRestApiGet2; - - /** - * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. - * - * Make a GET call to the Orthanc REST API with extended - * parameters. The result to the query is stored into a newly - * allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param headersCount The number of HTTP headers. - * @param headersKeys Array containing the keys of the HTTP headers. - * @param headersValues Array containing the values of the HTTP headers. - * @param afterPlugins If 0, the built-in API of Orthanc is used. - * If 1, the API is tainted by the plugins. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - uint32_t headersCount, - const char* const* headersKeys, - const char* const* headersValues, - int32_t afterPlugins) - { - _OrthancPluginRestApiGet2 params; - params.target = target; - params.uri = uri; - params.headersCount = headersCount; - params.headersKeys = headersKeys; - params.headersValues = headersValues; - params.afterPlugins = afterPlugins; - - return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); - } - - - - typedef struct - { - OrthancPluginWorklistCallback callback; - } _OrthancPluginWorklistCallback; - - /** - * @brief Register a callback to handle modality worklists requests. - * - * This function registers a callback to handle C-Find SCP requests - * on modality worklists. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( - OrthancPluginContext* context, - OrthancPluginWorklistCallback callback) - { - _OrthancPluginWorklistCallback params; - params.callback = callback; - - return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginWorklistAnswers* answers; - const OrthancPluginWorklistQuery* query; - const void* dicom; - uint32_t size; - } _OrthancPluginWorklistAnswersOperation; - - /** - * @brief Add one answer to some modality worklist request. - * - * This function adds one worklist (encoded as a DICOM file) to the - * set of answers corresponding to some C-Find SCP request against - * modality worklists. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param answers The set of answers. - * @param query The worklist query, as received by the callback. - * @param dicom The worklist to answer, encoded as a DICOM file. - * @param size The size of the DICOM file. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( - OrthancPluginContext* context, - OrthancPluginWorklistAnswers* answers, - const OrthancPluginWorklistQuery* query, - const void* dicom, - uint32_t size) - { - _OrthancPluginWorklistAnswersOperation params; - params.answers = answers; - params.query = query; - params.dicom = dicom; - params.size = size; - - return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); - } - - - /** - * @brief Mark the set of worklist answers as incomplete. - * - * This function marks as incomplete the set of answers - * corresponding to some C-Find SCP request against modality - * worklists. This must be used if canceling the handling of a - * request when too many answers are to be returned. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param answers The set of answers. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( - OrthancPluginContext* context, - OrthancPluginWorklistAnswers* answers) - { - _OrthancPluginWorklistAnswersOperation params; - params.answers = answers; - params.query = NULL; - params.dicom = NULL; - params.size = 0; - - return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); - } - - - typedef struct - { - const OrthancPluginWorklistQuery* query; - const void* dicom; - uint32_t size; - int32_t* isMatch; - OrthancPluginMemoryBuffer* target; - } _OrthancPluginWorklistQueryOperation; - - /** - * @brief Test whether a worklist matches the query. - * - * This function checks whether one worklist (encoded as a DICOM - * file) matches the C-Find SCP query against modality - * worklists. This function must be called before adding the - * worklist as an answer through OrthancPluginWorklistAddAnswer(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param query The worklist query, as received by the callback. - * @param dicom The worklist to answer, encoded as a DICOM file. - * @param size The size of the DICOM file. - * @return 1 if the worklist matches the query, 0 otherwise. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( - OrthancPluginContext* context, - const OrthancPluginWorklistQuery* query, - const void* dicom, - uint32_t size) - { - int32_t isMatch = 0; - - _OrthancPluginWorklistQueryOperation params; - params.query = query; - params.dicom = dicom; - params.size = size; - params.isMatch = &isMatch; - params.target = NULL; - - if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) - { - return isMatch; - } - else - { - /* Error: Assume non-match */ - return 0; - } - } - - - /** - * @brief Retrieve the worklist query as a DICOM file. - * - * This function retrieves the DICOM file that underlies a C-Find - * SCP query against modality worklists. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param query The worklist query, as received by the callback. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const OrthancPluginWorklistQuery* query) - { - _OrthancPluginWorklistQueryOperation params; - params.query = query; - params.dicom = NULL; - params.size = 0; - params.isMatch = NULL; - params.target = target; - - return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); - } - - - /** - * @brief Get the origin of a DICOM file. - * - * This function returns the origin of a DICOM instance that has been received by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The origin of the instance. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - OrthancPluginInstanceOrigin origin; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultOrigin = &origin; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return OrthancPluginInstanceOrigin_Unknown; - } - else - { - return origin; - } - } - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* json; - const OrthancPluginImage* pixelData; - OrthancPluginCreateDicomFlags flags; - } _OrthancPluginCreateDicom; - - /** - * @brief Create a DICOM instance from a JSON string and an image. - * - * This function takes as input a string containing a JSON file - * describing the content of a DICOM instance. As an output, it - * writes the corresponding DICOM instance to a newly allocated - * memory buffer. Additionally, an image to be encoded within the - * DICOM instance can also be provided. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param json The input JSON file. - * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. - * @param flags Flags governing the output. - * @return 0 if success, other value if error. - * @ingroup Toolbox - * @see OrthancPluginDicomBufferToJson - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* json, - const OrthancPluginImage* pixelData, - OrthancPluginCreateDicomFlags flags) - { - _OrthancPluginCreateDicom params; - params.target = target; - params.json = json; - params.pixelData = pixelData; - params.flags = flags; - - return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); - } - - - typedef struct - { - OrthancPluginDecodeImageCallback callback; - } _OrthancPluginDecodeImageCallback; - - /** - * @brief Register a callback to handle the decoding of DICOM images. - * - * This function registers a custom callback to the decoding of - * DICOM images, replacing the built-in decoder of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( - OrthancPluginContext* context, - OrthancPluginDecodeImageCallback callback) - { - _OrthancPluginDecodeImageCallback params; - params.callback = callback; - - return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginImage** target; - OrthancPluginPixelFormat format; - uint32_t width; - uint32_t height; - uint32_t pitch; - void* buffer; - const void* constBuffer; - uint32_t bufferSize; - uint32_t frameIndex; - } _OrthancPluginCreateImage; - - - /** - * @brief Create an image. - * - * This function creates an image of given size and format. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param format The format of the pixels. - * @param width The width of the image. - * @param height The height of the image. - * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( - OrthancPluginContext* context, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginCreateImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.format = format; - params.width = width; - params.height = height; - - if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - /** - * @brief Create an image pointing to a memory buffer. - * - * This function creates an image whose content points to a memory - * buffer managed by the plugin. Note that the buffer is directly - * accessed, no memory is allocated and no data is copied. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param format The format of the pixels. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer. - * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( - OrthancPluginContext* context, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - void* buffer) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginCreateImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.format = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - - if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - /** - * @brief Decode one frame from a DICOM instance. - * - * This function decodes one frame of a DICOM image that is stored - * in a memory buffer. This function will give the same result as - * OrthancPluginUncompressImage() for single-frame DICOM images. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer Pointer to a memory buffer containing the DICOM image. - * @param bufferSize Size of the memory buffer containing the DICOM image. - * @param frameIndex The index of the frame of interest in a multi-frame image. - * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( - OrthancPluginContext* context, - const void* buffer, - uint32_t bufferSize, - uint32_t frameIndex) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginCreateImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.constBuffer = buffer; - params.bufferSize = bufferSize; - params.frameIndex = frameIndex; - - if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - typedef struct - { - char** result; - const void* buffer; - uint32_t size; - } _OrthancPluginComputeHash; - - /** - * @brief Compute an MD5 hash. - * - * This functions computes the MD5 cryptographic hash of the given memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The source memory buffer. - * @param size The size in bytes of the source buffer. - * @return The NULL value in case of error, or a string containing the cryptographic hash. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( - OrthancPluginContext* context, - const void* buffer, - uint32_t size) - { - char* result; - - _OrthancPluginComputeHash params; - params.result = &result; - params.buffer = buffer; - params.size = size; - - if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Compute a SHA-1 hash. - * - * This functions computes the SHA-1 cryptographic hash of the given memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The source memory buffer. - * @param size The size in bytes of the source buffer. - * @return The NULL value in case of error, or a string containing the cryptographic hash. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( - OrthancPluginContext* context, - const void* buffer, - uint32_t size) - { - char* result; - - _OrthancPluginComputeHash params; - params.result = &result; - params.buffer = buffer; - params.size = size; - - if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginDictionaryEntry* target; - const char* name; - } _OrthancPluginLookupDictionary; - - /** - * @brief Get information about the given DICOM tag. - * - * This functions makes a lookup in the dictionary of DICOM tags - * that are known to Orthanc, and returns information about this - * tag. The tag can be specified using its human-readable name - * (e.g. "PatientName") or a set of two hexadecimal numbers - * (e.g. "0010-0020"). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target Where to store the information about the tag. - * @param name The name of the DICOM tag. - * @return 0 if success, other value if error. - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( - OrthancPluginContext* context, - OrthancPluginDictionaryEntry* target, - const char* name) - { - _OrthancPluginLookupDictionary params; - params.target = target; - params.name = name; - return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* answer; - uint32_t answerSize; - uint32_t headersCount; - const char* const* headersKeys; - const char* const* headersValues; - } _OrthancPluginSendMultipartItem2; - - /** - * @brief Send an item as a part of some HTTP multipart answer, with custom headers. - * - * This function sends an item as a part of some HTTP multipart - * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to - * OrthancPluginSendMultipartItem(), this function will set HTTP header associated - * with the item. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the item. - * @param answerSize Number of bytes of the item. - * @param headersCount The number of HTTP headers. - * @param headersKeys Array containing the keys of the HTTP headers. - * @param headersValues Array containing the values of the HTTP headers. - * @return 0 if success, or the error code if failure (this notably happens - * if the connection is closed by the client). - * @see OrthancPluginSendMultipartItem() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize, - uint32_t headersCount, - const char* const* headersKeys, - const char* const* headersValues) - { - _OrthancPluginSendMultipartItem2 params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.headersCount = headersCount; - params.headersKeys = headersKeys; - params.headersValues = headersValues; - - return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); - } - - -#ifdef __cplusplus -} -#endif - - -/** @} */ - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/BoostExtendedConfiguration.cmake --- a/OrthancStone/Samples/Sdl/BoostExtendedConfiguration.cmake Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., 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 . - - -if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST) - set(BOOST_EXTENDED_SOURCES - ${BOOST_SOURCES_DIR}/libs/program_options/src/cmdline.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/config_file.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/convert.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/options_description.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/parsers.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/positional_options.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/split.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/utf8_codecvt_facet.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/value_semantic.cpp - ${BOOST_SOURCES_DIR}/libs/program_options/src/variables_map.cpp - ${BOOST_SOURCES_DIR}/libs/chrono/src/thread_clock.cpp - ${BOOST_SOURCES_DIR}/libs/chrono/src/chrono.cpp - ${BOOST_SOURCES_DIR}/libs/chrono/src/process_cpu_clocks.cpp - #${BOOST_SOURCES_DIR}/libs/program_options/src/winmain.cpp - ) - add_definitions(-DBOOST_PROGRAM_OPTIONS_NO_LIB) -else() - link_libraries(boost_program_options) -endif() diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/CMakeLists.txt --- a/OrthancStone/Samples/Sdl/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -cmake_minimum_required(VERSION 2.8.10) - -project(OrthancStone) - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) - -if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - set(ORTHANC_BOOST_COMPONENTS program_options) - - set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") - set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") - mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) - include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) - include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) - -else() - set(ENABLE_GOOGLE_TEST ON) - set(ENABLE_LOCALE ON) # Necessary for text rendering - set(ENABLE_OPENGL ON) # <== - set(ENABLE_WEB_CLIENT ON) -endif() - -set(ENABLE_DCMTK ON) # <== -set(ENABLE_SDL ON) - -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/Utilities.cmake) - -if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - # This include must be after "OrthancStoneConfiguration.cmake" to - # have "BOOST_SOURCES_DIR" defined - include(${CMAKE_SOURCE_DIR}/BoostExtendedConfiguration.cmake) -endif() - - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -EmbedResources( - COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -SortFilesInSourceGroups() - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - ${BOOST_EXTENDED_SOURCES} - ) - -message(${AUTOGENERATED_SOURCES}) - - - -############################# -project(RtViewerSdl) - -add_executable(RtViewerSdl - RtViewer/RtViewerSdl.cpp - SdlHelpers.h - ../Common/RtViewerApp.cpp - ../Common/RtViewerApp.h - ../Common/RtViewerView.cpp - ../Common/RtViewerView.h - ../Common/SampleHelpers.h - ) - -target_link_libraries(RtViewerSdl OrthancStone ${DCMTK_LIBRARIES}) - -############################# -project(SdlSimpleViewer) - -add_executable(SdlSimpleViewer - SdlHelpers.h - ../Common/SampleHelpers.h - SingleFrameViewer/SdlSimpleViewerApplication.h - SingleFrameViewer/SdlSimpleViewer.cpp - ) - -target_link_libraries(SdlSimpleViewer OrthancStone ${DCMTK_LIBRARIES}) - -############################# -project(UnitTests) - -add_executable(UnitTests - ${GOOGLE_TEST_SOURCES} - ${ORTHANC_STONE_ROOT}/UnitTestsSources/GenericToolboxTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/ImageToolboxTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/PixelTestPatternsTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStrategy.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestStructureSet.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/SortedFramesTests.cpp - ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp - ) - -target_link_libraries(UnitTests OrthancStone) - -add_custom_command( - TARGET UnitTests - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - "${ORTHANC_STONE_ROOT}/UnitTestsSources/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" - "$/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" -) - -target_link_libraries(UnitTests OrthancStone ${DCMTK_LIBRARIES}) diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/RtViewer/CMakeLists.txt --- a/OrthancStone/Samples/Sdl/RtViewer/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -cmake_minimum_required(VERSION 2.8.10) - -project(RtViewerSdl) - -include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) - -if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - set(ORTHANC_BOOST_COMPONENTS program_options) - - set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") - set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") - mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) - include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) - include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) - -else() - set(ENABLE_GOOGLE_TEST ON) - set(ENABLE_LOCALE ON) # Necessary for text rendering - set(ENABLE_OPENGL ON) # <== - set(ENABLE_WEB_CLIENT ON) -endif() - -set(ENABLE_DCMTK ON) # <== -set(ENABLE_SDL ON) - -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/../Utilities.cmake) - -if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - # This include must be after "OrthancStoneConfiguration.cmake" to - # have "BOOST_SOURCES_DIR" defined - include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake) -endif() - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -EmbedResources( - COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -SortFilesInSourceGroups() - -add_executable(RtViewerSdl - RtViewerSdl.cpp - ../SdlHelpers.h - ../../Common/RtViewerApp.cpp - ../../Common/RtViewerApp.h - ../../Common/RtViewerView.cpp - ../../Common/RtViewerView.h - ../../Common/SampleHelpers.h - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - ${BOOST_EXTENDED_SOURCES} - ) - -target_link_libraries(RtViewerSdl ${DCMTK_LIBRARIES}) diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/RtViewer/CMakeSettings.json --- a/OrthancStone/Samples/Sdl/RtViewer/CMakeSettings.json Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -{ - // this file is meant to be used with Visual Studio CMake support - // tested with VS 16.5.4+ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}/../../../../out/build-stone-sdl-RtViewer-ninja-msvc16-x64-Debug", - "installRoot": "${projectDir}/../../../../out/install-stone-sdl-RtViewer-ninja-msvc16-x64-Debug", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [ - { - "name": "ALLOW_DOWNLOADS", - "value": "True", - "type": "BOOL" - }, - { - "name": "MSVC_MULTIPLE_PROCESSES", - "value": "True", - "type": "BOOL" - }, - { - "name": "STATIC_BUILD", - "value": "True", - "type": "BOOL" - } - ] - } - ] -} \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/RtViewer/RtViewerSdl.cpp --- a/OrthancStone/Samples/Sdl/RtViewer/RtViewerSdl.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,461 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - -#include "../../Common/RtViewerApp.h" -#include "../../Common/RtViewerView.h" -#include "../SdlHelpers.h" - -#include - -// Stone of Orthanc includes -#include "../../../Sources/Loaders/GenericLoadersContext.h" -#include "../../../Sources/OpenGL/OpenGLIncludes.h" -#include "../../../Sources/OpenGL/SdlOpenGLContext.h" -#include "../../../Sources/StoneException.h" -#include "../../../Sources/StoneInitialization.h" - -// Orthanc (a.o. for screenshot capture) -#include // For std::unique_ptr<> -#include -#include -#include - - -#include -#include - -// #include this include might be necessary in more recent boost versions - -#include - -#include - - -#if !defined(__APPLE__) -/** - * OpenGL: "OS X does not seem to support debug output functionality - * (as gathered online)." - * https://learnopengl.com/In-Practice/Debugging - **/ -static void GLAPIENTRY -OpenGLMessageCallback(GLenum source, - GLenum type, - GLuint id, - GLenum severity, - GLsizei length, - const GLchar* message, - const void* userParam) -{ - if (severity != GL_DEBUG_SEVERITY_NOTIFICATION) - { - fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", - (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), - type, severity, message); - } -} -#endif - -namespace OrthancStone -{ - void RtViewerView::EnableGLDebugOutput() - { -#if !defined(__APPLE__) - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(OpenGLMessageCallback, 0); -#endif - } - - boost::shared_ptr RtViewerView::CreateViewport(const std::string& canvasId) - { - // False means we do NOT let Windows treat this as a legacy application that needs to be scaled - return SdlOpenGLViewport::Create(canvasId, 1024, 1024, false); - } - - void RtViewerApp::ProcessOptions(int argc, char* argv[]) - { - namespace po = boost::program_options; - po::options_description desc("Usage"); - - desc.add_options() - ("loglevel", po::value()->default_value("WARNING"), - "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)") - - ("orthanc", po::value()->default_value("http://localhost:8042"), - "Base URL of the Orthanc instance") - - ("ctseries", po::value()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"), - "Orthanc ID of the CT series to load. This must be supplied.") - - ("rtdose", po::value()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"), - "Orthanc ID of the RTDOSE instance to load. This may be an empty string.") - - ("rtstruct", po::value()->default_value("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"), - "Orthanc ID of the RTSTRUCT instance to load. This may be an empty string.") - ; - - std::cout << desc << std::endl; - - po::variables_map vm; - try - { - po::store(po::parse_command_line(argc, argv, desc), vm); - po::notify(vm); - } - catch (std::exception& e) - { - std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl; - } - - for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it) - { - std::string key = it->first; - const po::variable_value& value = it->second; - const std::string& strValue = value.as(); - SetArgument(key, strValue); - } - } - - void RtViewerApp::RunSdl(int argc, char* argv[]) - { - ProcessOptions(argc, argv); - - /** - Create the shared loaders context - */ - loadersContext_.reset(new GenericLoadersContext(1, 4, 1)); - - // we are in SDL --> downcast to concrete type - boost::shared_ptr loadersContext = boost::dynamic_pointer_cast(loadersContext_); - - /** - Url of the Orthanc instance - Typically, in a native application (Qt, SDL), it will be an absolute URL like "http://localhost:8042". In - wasm on the browser, it could be an absolute URL, provided you do not have cross-origin problems, or a relative - URL. In our wasm samples, it is set to "..", because we set up either a reverse proxy or an Orthanc ServeFolders - plugin that serves the main web application from an URL like "http://localhost:8042/stone-rtviewer" (with ".." - leading to the main Orthanc root URL) - */ - std::string orthancUrl = arguments_["orthanc"]; - - { - Orthanc::WebServiceParameters p; - if (HasArgument("orthanc")) - { - p.SetUrl(orthancUrl); - } - if (HasArgument("user")) - { - ORTHANC_ASSERT(HasArgument("password")); - p.SetCredentials(GetArgument("user"), GetArgument("password")); - } - else - { - ORTHANC_ASSERT(!HasArgument("password")); - } - loadersContext->SetOrthancParameters(p); - } - - loadersContext->StartOracle(); - - CreateLoaders(); - - /** - Create viewports - */ - CreateView("RtViewer Axial", VolumeProjection_Axial); - CreateView("RtViewer Coronal", VolumeProjection_Coronal); - CreateView("RtViewer Sagittal", VolumeProjection_Sagittal); - - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->PrepareViewport(); - views_[i]->EnableGLDebugOutput(); - } - - DefaultViewportInteractor interactor; - - /** - It is very important that the Oracle (responsible for network I/O) be started before creating and firing the - loaders, for any command scheduled by the loader before the oracle is started will be lost. - */ - StartLoaders(); - - - SdlRunLoop(views_, interactor); - loadersContext->StopOracle(); - } - - void RtViewerView::TakeScreenshot(const std::string& target, - unsigned int canvasWidth, - unsigned int canvasHeight) - { - std::unique_ptr lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - - std::string ttf; - Orthanc::EmbeddedResources::GetFileResource(ttf, Orthanc::EmbeddedResources::UBUNTU_FONT); - - CairoCompositor compositor(canvasWidth, canvasHeight); - compositor.SetFont(0, ttf, FONT_SIZE_0, Orthanc::Encoding_Latin1); - compositor.Refresh(scene); - - Orthanc::ImageAccessor canvas; - compositor.GetCanvas().GetReadOnlyAccessor(canvas); - - Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false); - Orthanc::ImageProcessing::Convert(png, canvas); - - Orthanc::PngWriter writer; - writer.WriteToFile(target, png); - } - - static boost::shared_ptr GetViewFromWindowId( - const std::vector >& views, - Uint32 windowID) - { - using namespace OrthancStone; - for (size_t i = 0; i < views.size(); ++i) - { - boost::shared_ptr view = views[i]; - boost::shared_ptr viewport = view->GetViewport(); - boost::shared_ptr sdlViewport = boost::dynamic_pointer_cast(viewport); - Uint32 curWindowID = sdlViewport->GetSdlWindowId(); - if (windowID == curWindowID) - return view; - } - return boost::shared_ptr(); - } - - void RtViewerApp::SdlRunLoop(const std::vector >& views, - OrthancStone::DefaultViewportInteractor& interactor) - { - using namespace OrthancStone; - - // const std::vector >& views - std::vector > viewports; - for (size_t i = 0; i < views.size(); ++i) - { - boost::shared_ptr view = views[i]; - boost::shared_ptr viewport = view->GetViewport(); - boost::shared_ptr sdlViewport = - boost::dynamic_pointer_cast(viewport); - viewports.push_back(sdlViewport); - } - - { - int scancodeCount = 0; - const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); - - bool stop = false; - while (!stop) - { - std::vector sdlEvents; - std::map userEventsMap; - SDL_Event sdlEvent; - - // FIRST: collect all pending events - while (SDL_PollEvent(&sdlEvent) != 0) - { - if ( (sdlEvent.type >= SDL_USEREVENT) && - (sdlEvent.type < SDL_LASTEVENT) ) - { - // we don't want to have multiple refresh events , - // and since every refresh event is a user event with a special type, - // we use a map - userEventsMap[sdlEvent.type] = sdlEvent; - } - else - { - sdlEvents.push_back(sdlEvent); - } - } - - // SECOND: add all user events to sdlEvents - for (std::map::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it) - sdlEvents.push_back(it->second); - - // now process the events - for (std::vector::const_iterator it = sdlEvents.begin(); it != sdlEvents.end(); ++it) - { - const SDL_Event& sdlEvent = *it; - - if (sdlEvent.type == SDL_QUIT) - { - stop = true; - break; - } - else if (sdlEvent.type == SDL_WINDOWEVENT && - (sdlEvent.window.event == SDL_WINDOWEVENT_RESIZED || - sdlEvent.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) - { - boost::shared_ptr view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - boost::shared_ptr sdlViewport = - boost::dynamic_pointer_cast(view->GetViewport()); - - sdlViewport->UpdateSize(sdlEvent.window.data1, sdlEvent.window.data2); - } - else if (sdlEvent.type == SDL_WINDOWEVENT && - (sdlEvent.window.event == SDL_WINDOWEVENT_SHOWN || - sdlEvent.window.event == SDL_WINDOWEVENT_EXPOSED)) - { - boost::shared_ptr view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - boost::shared_ptr sdlViewport = - boost::dynamic_pointer_cast(view->GetViewport()); - sdlViewport->Paint(); - } - else if (sdlEvent.type == SDL_KEYDOWN && - sdlEvent.key.repeat == 0 /* Ignore key bounce */) - { - boost::shared_ptr view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - switch (sdlEvent.key.keysym.sym) - { - case SDLK_f: - { - boost::shared_ptr sdlViewport = - boost::dynamic_pointer_cast(view->GetViewport()); - sdlViewport->ToggleMaximize(); - } - break; - - case SDLK_s: - { - std::unique_ptr lock(view->GetViewport()->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - break; - - case SDLK_q: - stop = true; - break; - - default: - break; - } - } - else if (sdlEvent.type == SDL_MOUSEBUTTONDOWN || - sdlEvent.type == SDL_MOUSEMOTION || - sdlEvent.type == SDL_MOUSEBUTTONUP) - { - boost::shared_ptr view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - std::unique_ptr lock(view->GetViewport()->Lock()); - if (lock->HasCompositor()) - { - OrthancStone::PointerEvent p; - OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), - sdlEvent, keyboardState, scancodeCount); - - switch (sdlEvent.type) - { - case SDL_MOUSEBUTTONDOWN: - interactor.SetWindowingLayer(view->GetCtLayerIndex()); - lock->GetController().HandleMousePress(interactor, p, - lock->GetCompositor().GetCanvasWidth(), - lock->GetCompositor().GetCanvasHeight()); - lock->Invalidate(); - break; - - case SDL_MOUSEMOTION: - if (lock->GetController().HandleMouseMove(p)) - { - lock->Invalidate(); - } - break; - - case SDL_MOUSEBUTTONUP: - lock->GetController().HandleMouseRelease(p); - lock->Invalidate(); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - } - else if (sdlEvent.type == SDL_MOUSEWHEEL) - { - boost::shared_ptr view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - int delta = 0; - if (sdlEvent.wheel.y < 0) - delta = -1; - if (sdlEvent.wheel.y > 0) - delta = 1; - - view->Scroll(delta); - } - else - { - for (size_t i = 0; i < views.size(); ++i) - { - boost::shared_ptr sdlViewport = - boost::dynamic_pointer_cast(views[i]->GetViewport()); - if (sdlViewport->IsRefreshEvent(sdlEvent)) - { - sdlViewport->Paint(); - } - } - } - } - // Small delay to avoid using 100% of CPU - SDL_Delay(1); - } - } - } -} - -boost::weak_ptr g_app; - -/** - * IMPORTANT: The full arguments to "main()" are needed for SDL on - * Windows. Otherwise, one gets the linking error "undefined reference - * to `SDL_main'". https://wiki.libsdl.org/FAQWindows - **/ -int main(int argc, char* argv[]) -{ - using namespace OrthancStone; - - StoneInitialize(); - - try - { - boost::shared_ptr app = RtViewerApp::Create(); - g_app = app; - app->RunSdl(argc,argv); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - StoneFinalize(); - - return 0; -} - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/SdlHelpers.h --- a/OrthancStone/Samples/Sdl/SdlHelpers.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_SDL != 1 -# error This file cannot be used if ORTHANC_ENABLE_SDL != 1 -#endif - -#include "../../Sources/Viewport/SdlViewport.h" - -#include - -#include - -#include -#include - -namespace OrthancStoneHelpers -{ - - inline OrthancStone::KeyboardModifiers GetKeyboardModifiers(const uint8_t* keyboardState, - const int scancodeCount) - { - using namespace OrthancStone; - int result = KeyboardModifiers_None; - - if (keyboardState != NULL) - { - if (SDL_SCANCODE_LSHIFT < scancodeCount && - keyboardState[SDL_SCANCODE_LSHIFT]) - { - result |= KeyboardModifiers_Shift; - } - - if (SDL_SCANCODE_RSHIFT < scancodeCount && - keyboardState[SDL_SCANCODE_RSHIFT]) - { - result |= KeyboardModifiers_Shift; - } - - if (SDL_SCANCODE_LCTRL < scancodeCount && - keyboardState[SDL_SCANCODE_LCTRL]) - { - result |= KeyboardModifiers_Control; - } - - if (SDL_SCANCODE_RCTRL < scancodeCount && - keyboardState[SDL_SCANCODE_RCTRL]) - { - result |= KeyboardModifiers_Control; - } - - if (SDL_SCANCODE_LALT < scancodeCount && - keyboardState[SDL_SCANCODE_LALT]) - { - result |= KeyboardModifiers_Alt; - } - - if (SDL_SCANCODE_RALT < scancodeCount && - keyboardState[SDL_SCANCODE_RALT]) - { - result |= KeyboardModifiers_Alt; - } - } - - return static_cast(result); - } - - - inline void GetPointerEvent(OrthancStone::PointerEvent& p, - const OrthancStone::ICompositor& compositor, - SDL_Event event, - const uint8_t* keyboardState, - const int scancodeCount) - { - using namespace OrthancStone; - KeyboardModifiers modifiers = GetKeyboardModifiers(keyboardState, scancodeCount); - - switch (event.button.button) - { - case SDL_BUTTON_LEFT: - p.SetMouseButton(OrthancStone::MouseButton_Left); - break; - - case SDL_BUTTON_RIGHT: - p.SetMouseButton(OrthancStone::MouseButton_Right); - break; - - case SDL_BUTTON_MIDDLE: - p.SetMouseButton(OrthancStone::MouseButton_Middle); - break; - - default: - p.SetMouseButton(OrthancStone::MouseButton_None); - break; - } - - p.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y)); - p.SetAltModifier( (modifiers & KeyboardModifiers_Alt) != 0); - p.SetControlModifier( (modifiers & KeyboardModifiers_Control) != 0); - p.SetShiftModifier( (modifiers & KeyboardModifiers_Shift) != 0); - } - - - inline boost::shared_ptr GetSdlViewportFromWindowId( - const std::vector >& viewports, - Uint32 windowID) - { - using namespace OrthancStone; - for (size_t i = 0; i < viewports.size(); ++i) - { - boost::shared_ptr viewport = viewports[i]; - boost::shared_ptr sdlViewport = boost::dynamic_pointer_cast(viewport); - Uint32 curWindowID = sdlViewport->GetSdlWindowId(); - if (windowID == curWindowID) - return sdlViewport; - } - - return boost::shared_ptr(); - } -} - - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeLists.txt --- a/OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -cmake_minimum_required(VERSION 2.8.10) - -project(SdlSimpleViewer) - -include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) - -if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - set(ORTHANC_BOOST_COMPONENTS program_options) - - set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") - set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") - mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) - include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/DownloadPackage.cmake) - include(${ORTHANC_STONE_ROOT}/Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) - -else() - set(ENABLE_GOOGLE_TEST ON) - set(ENABLE_LOCALE ON) # Necessary for text rendering - set(ENABLE_OPENGL ON) # <== - set(ENABLE_WEB_CLIENT ON) -endif() - -set(ENABLE_DCMTK ON) # <== -set(ENABLE_SDL ON) - -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) -include(${CMAKE_SOURCE_DIR}/../Utilities.cmake) - -if (NOT ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - # This include must be after "OrthancStoneConfiguration.cmake" to - # have "BOOST_SOURCES_DIR" defined - include(${CMAKE_SOURCE_DIR}/../BoostExtendedConfiguration.cmake) -endif() - -SortFilesInSourceGroups() - -add_executable(SdlSimpleViewer - ../SdlHelpers.h - ../../Common/SampleHelpers.h - SdlSimpleViewerApplication.h - SdlSimpleViewer.cpp - ${ORTHANC_STONE_SOURCES} - ) - -target_link_libraries(SdlSimpleViewer ${DCMTK_LIBRARIES}) - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeSettings.json --- a/OrthancStone/Samples/Sdl/SingleFrameViewer/CMakeSettings.json Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [ - { - "name": "MSVC_MULTIPLE_PROCESSES", - "value": "True", - "type": "BOOL" - }, - { - "name": "ALLOW_DOWNLOADS", - "value": "True", - "type": "BOOL" - }, - { - "name": "STATIC_BUILD", - "value": "True", - "type": "BOOL" - }, - { - "name": "OPENSSL_NO_CAPIENG", - "value": "True", - "type": "BOOL" - }, - ] - } - ] -} \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp --- a/OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewer.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "SdlSimpleViewerApplication.h" -#include "../SdlHelpers.h" -#include "../../Common/SampleHelpers.h" - -#include "../../../Sources/Loaders/GenericLoadersContext.h" -#include "../../../Sources/StoneException.h" -#include "../../../Sources/StoneEnumerations.h" -#include "../../../Sources/StoneInitialization.h" -#include "../../../Sources/Viewport/SdlViewport.h" - -#include // For std::unique_ptr<> -#include - -#include -#include - -#include - - -std::string orthancUrl; -std::string instanceId; -int frameIndex = 0; - - -static void ProcessOptions(int argc, char* argv[]) -{ - namespace po = boost::program_options; - po::options_description desc("Usage"); - - desc.add_options() - ("loglevel", po::value()->default_value("WARNING"), - "You can choose WARNING, INFO or TRACE for the logging level: Errors and warnings will always be displayed. (default: WARNING)") - - ("orthanc", po::value()->default_value("http://localhost:8042"), - "Base URL of the Orthanc instance") - - ("instance", po::value()->default_value("285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"), - "Orthanc ID of the instance to display") - - ("frame_index", po::value()->default_value(0), - "The zero-based index of the frame (for multi-frame instances)") - ; - - std::cout << desc << std::endl; - - po::variables_map vm; - try - { - po::store(po::parse_command_line(argc, argv, desc), vm); - po::notify(vm); - } - catch (std::exception& e) - { - std::cerr << "Please check your command line options! (\"" << e.what() << "\")" << std::endl; - } - - if (vm.count("loglevel") > 0) - { - std::string logLevel = vm["loglevel"].as(); - OrthancStoneHelpers::SetLogLevel(logLevel); - } - - if (vm.count("orthanc") > 0) - { - // maybe check URL validity here - orthancUrl = vm["orthanc"].as(); - } - - if (vm.count("instance") > 0) - { - instanceId = vm["instance"].as(); - } - - if (vm.count("frame_index") > 0) - { - frameIndex = vm["frame_index"].as(); - } - -} - -/** - * IMPORTANT: The full arguments to "main()" are needed for SDL on - * Windows. Otherwise, one gets the linking error "undefined reference - * to `SDL_main'". https://wiki.libsdl.org/FAQWindows - **/ -int main(int argc, char* argv[]) -{ - try - { - OrthancStone::StoneInitialize(); - - ProcessOptions(argc, argv); - - //Orthanc::Logging::EnableInfoLevel(true); - //Orthanc::Logging::EnableTraceLevel(true); - - { - -#if 1 - boost::shared_ptr viewport = - OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); -#else - boost::shared_ptr viewport = - OrthancStone::SdlCairoViewport::Create("Stone of Orthanc", 800, 600); -#endif - - OrthancStone::GenericLoadersContext context(1, 4, 1); - - Orthanc::WebServiceParameters orthancWebService; - orthancWebService.SetUrl(orthancUrl); - context.SetOrthancParameters(orthancWebService); - - context.StartOracle(); - - { - - boost::shared_ptr application( - SdlSimpleViewerApplication::Create(context, viewport)); - - OrthancStone::DicomSource source; - - application->LoadOrthancFrame(source, instanceId, frameIndex); - - OrthancStone::DefaultViewportInteractor interactor; - interactor.SetWindowingLayer(0); - - { - int scancodeCount = 0; - const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); - - bool stop = false; - while (!stop) - { - bool paint = false; - SDL_Event event; - while (SDL_PollEvent(&event)) - { - if (event.type == SDL_QUIT) - { - stop = true; - break; - } - else if (viewport->IsRefreshEvent(event)) - { - paint = true; - } - else if (event.type == SDL_WINDOWEVENT && - (event.window.event == SDL_WINDOWEVENT_RESIZED || - event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) - { - viewport->UpdateSize(event.window.data1, event.window.data2); - } - else if (event.type == SDL_WINDOWEVENT && - (event.window.event == SDL_WINDOWEVENT_SHOWN || - event.window.event == SDL_WINDOWEVENT_EXPOSED)) - { - paint = true; - } - else if (event.type == SDL_KEYDOWN && - event.key.repeat == 0 /* Ignore key bounce */) - { - switch (event.key.keysym.sym) - { - case SDLK_f: - viewport->ToggleMaximize(); - break; - - case SDLK_s: - application->FitContent(); - break; - - case SDLK_q: - stop = true; - break; - - default: - break; - } - } - else if (event.type == SDL_MOUSEBUTTONDOWN || - event.type == SDL_MOUSEMOTION || - event.type == SDL_MOUSEBUTTONUP) - { - std::unique_ptr lock(viewport->Lock()); - if (lock->HasCompositor()) - { - OrthancStone::PointerEvent p; - OrthancStoneHelpers::GetPointerEvent(p, lock->GetCompositor(), - event, keyboardState, scancodeCount); - - switch (event.type) - { - case SDL_MOUSEBUTTONDOWN: - lock->GetController().HandleMousePress(interactor, p, - lock->GetCompositor().GetCanvasWidth(), - lock->GetCompositor().GetCanvasHeight()); - lock->Invalidate(); - break; - - case SDL_MOUSEMOTION: - if (lock->GetController().HandleMouseMove(p)) - { - lock->Invalidate(); - } - break; - - case SDL_MOUSEBUTTONUP: - lock->GetController().HandleMouseRelease(p); - lock->Invalidate(); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - } - } - - if (paint) - { - viewport->Paint(); - } - - // Small delay to avoid using 100% of CPU - SDL_Delay(1); - } - } - context.StopOracle(); - } - } - - OrthancStone::StoneFinalize(); - return 0; - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "OrthancException: " << e.What(); - return -1; - } - catch (OrthancStone::StoneException& e) - { - LOG(ERROR) << "StoneException: " << e.What(); - return -1; - } - catch (std::runtime_error& e) - { - LOG(ERROR) << "Runtime error: " << e.what(); - return -1; - } - catch (...) - { - LOG(ERROR) << "Native exception"; - return -1; - } -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h --- a/OrthancStone/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include "../../../Sources/Viewport/IViewport.h" -#include "../../../Sources/Loaders/DicomResourcesLoader.h" -#include "../../../Sources/Loaders/ILoadersContext.h" -#include "../../../Sources/Loaders/SeriesFramesLoader.h" -#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h" - -#include // For std::unique_ptr<> - -#include - - -using OrthancStone::ILoadersContext; -using OrthancStone::ObserverBase; -using OrthancStone::IViewport; -using OrthancStone::DicomResourcesLoader; -using OrthancStone::SeriesFramesLoader; -using OrthancStone::TextureBaseSceneLayer; -using OrthancStone::DicomSource; -using OrthancStone::SeriesThumbnailsLoader; -using OrthancStone::LoadedDicomResources; -using OrthancStone::SeriesThumbnailType; -using OrthancStone::OracleScheduler; -using OrthancStone::OrthancRestApiCommand; -using OrthancStone::OracleScheduler; -using OrthancStone::OracleScheduler; -using OrthancStone::OracleScheduler; - - -class SdlSimpleViewerApplication : public ObserverBase -{ - -public: - static boost::shared_ptr Create(ILoadersContext& context, boost::shared_ptr viewport) - { - boost::shared_ptr application(new SdlSimpleViewerApplication(context, viewport)); - - { - std::unique_ptr lock(context.Lock()); - application->dicomLoader_ = DicomResourcesLoader::Create(*lock); - } - - application->Register(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle); - - return application; - } - - void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame) - { - std::unique_ptr lock(context_.Lock()); - - dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), - 0, source, Orthanc::ResourceType_Instance, instanceId, - new Orthanc::SingleValueObject(frame)); - } - -#if 0 - void LoadDicomWebFrame(const DicomSource& source, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid, - unsigned int frame) - { - std::unique_ptr lock(context_.Lock()); - - // We first must load the "/metadata" to know the number of frames - dicomLoader_->ScheduleGetDicomWeb( - boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, - "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", - new Orthanc::SingleValueObject(frame)); - } -#endif - - void FitContent() - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - -private: - ILoadersContext& context_; - boost::shared_ptr viewport_; - boost::shared_ptr dicomLoader_; - boost::shared_ptr framesLoader_; - - SdlSimpleViewerApplication(ILoadersContext& context, - boost::shared_ptr viewport) : - context_(context), - viewport_(viewport) - { - } - - void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) - { - LOG(INFO) << "Frame decoded! " - << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() - << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); - - std::unique_ptr layer( - message.GetInstanceParameters().CreateTexture(message.GetImage())); - layer->SetLinearInterpolation(true); - - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetController().GetScene().SetLayer(0, layer.release()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - } - - void Handle(const DicomResourcesLoader::SuccessMessage& message) - { - if (message.GetResources()->GetSize() != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - //message.GetResources()->GetResource(0).Print(stdout); - - { - std::unique_ptr lock(context_.Lock()); - SeriesFramesLoader::Factory f(*message.GetResources()); - - framesLoader_ = boost::dynamic_pointer_cast( - f.Create(*lock)); - - Register( - *framesLoader_, &SdlSimpleViewerApplication::Handle); - - assert(message.HasUserPayload()); - - const Orthanc::SingleValueObject& payload = - dynamic_cast&>( - message.GetUserPayload()); - - LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); - framesLoader_->ScheduleLoadFrame( - 0, message.GetDicomSource(), payload.GetValue(), - message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, - NULL); - } - } - -}; - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/Sdl/Utilities.cmake --- a/OrthancStone/Samples/Sdl/Utilities.cmake Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ - - -macro(GetFilenameFromPath TargetVariable Path) -#message(STATUS "GetFilenameFromPath (1): Path = ${Path} TargetVariable = ${${TargetVariable}}") -string(REPLACE "\\" "/" PathWithFwdSlashes "${Path}") -string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${PathWithFwdSlashes}") -#message(STATUS "GetFilenameFromPath (2): Path = ${Path} Path = ${PathWithFwdSlashes} TargetVariable = ${TargetVariable}") -endmacro() - -macro(GetFilePathWithoutLastExtension TargetVariable FilePath) -string(REGEX REPLACE "(^.*)\\.([^\\.]+)" "\\1" ${TargetVariable} "${FilePath}") -#message(STATUS "GetFileNameWithoutLastExtension: FilePath = ${FilePath} TargetVariable = ${${TargetVariable}}") -endmacro() - -macro(Test_GetFilePathWithoutLastExtension) -set(tmp "/prout/zi/goui.goui.cpp") -GetFilePathWithoutLastExtension(TargetVariable "${tmp}") -if(NOT ("${TargetVariable}" STREQUAL "/prout/zi/goui.goui")) - message(FATAL_ERROR "Test_GetFilePathWithoutLastExtension failed (1)") -else() - #message(STATUS "Test_GetFilePathWithoutLastExtension: <>") -endif() -endmacro() - -Test_GetFilePathWithoutLastExtension() - -macro(Test_GetFilenameFromPath) -set(tmp "/prout/../../dada/zi/goui.goui.cpp") -GetFilenameFromPath(TargetVariable "${tmp}") -if(NOT ("${TargetVariable}" STREQUAL "goui.goui.cpp")) - message(FATAL_ERROR "Test_GetFilenameFromPath failed") -else() - #message(STATUS "Test_GetFilenameFromPath: <>") -endif() -endmacro() - -Test_GetFilenameFromPath() - -macro(SortFilesInSourceGroups) - if(FALSE) - foreach(source IN LISTS ORTHANC_STONE_SOURCES) - # if("${source}" MATCHES ".*/pixman.*\\.c") - # message("pixman source: ${source}") - # elseif("${source}" MATCHES ".*/pixman.*\\.c") - # message("pixman header: ${source}") - # endif() - - if("${source}" MATCHES ".*\\.\\./.*") - message("source raw: ${source}") - #file(TO_CMAKE_PATH ${source} sourceCMakePath) - get_filename_component(sourceCMakePath ${source} ABSOLUTE) - message("source CMake: ${sourceCMakePath}") - endif() - - # returns the containing directory with forward slashes - # get_filename_component(source_path "${source}" PATH) - - # converts / to \ - # string(REPLACE "/" "\\" source_path_msvc "${source_path}") - #source_group("Stone ${source_path_msvc}" FILES "${source}") - endforeach() - endif() - - source_group("Orthanc Framework\\Sources" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*cpp") - source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*hpp") - source_group("Orthanc Framework\\Headers" REGULAR_EXPRESSION ".*/orthanc/(Core|Plugins)/.*h") - - source_group("Stone Library\\Sources" REGULAR_EXPRESSION ".*/orthanc-stone/.*cpp") - source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*hpp") - source_group("Stone Library\\Headers" REGULAR_EXPRESSION ".*/orthanc-stone/.*h") - - source_group("Stone Samples\\Source" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.cpp") - source_group("Stone Samples\\Headers" REGULAR_EXPRESSION ".*orthanc-stone/OrthancStone/Samples/.*\\.h") - - source_group("ThirdParty\\cairo" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/cairo[^/]*/.*") - source_group("ThirdParty\\pixman" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/pixman[^/]*/.*") - source_group("ThirdParty\\base64" REGULAR_EXPRESSION ".*ThirdParty/base64.*") - source_group("ThirdParty\\SDL2" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/SDL2.*") - source_group("ThirdParty\\glew" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/glew.*") - source_group("AUTOGENERATED" REGULAR_EXPRESSION ".*${CMAKE_BINARY_DIR}/AUTOGENERATED/.*") - source_group("ThirdParty\\minizip" REGULAR_EXPRESSION ".*ThirdParty/minizip/.*") - source_group("ThirdParty\\md5" REGULAR_EXPRESSION ".*ThirdParty/md5/.*") -endmacro() - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/CMakeLists.txt --- a/OrthancStone/Samples/WebAssembly/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(OrthancStone) - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- -set(USE_WASM ON CACHE BOOL "") - -set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") - -set(WASM_FLAGS "-s WASM=1 -s FETCH=1") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - -# Stone of Orthanc configuration -# --------------------------------------------------------------- -set(ALLOW_DOWNLOADS ON) - -include(${CMAKE_SOURCE_DIR}/../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ENABLE_DCMTK OFF) # Not necessary -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) -SET(ORTHANC_SANDBOXED ON) - -# this will set up the build system for Stone of Orthanc and will -# populate the ORTHANC_STONE_SOURCES CMake variable -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) - - -# We embed a font to be used for on-screen overlays -# --------------------------------------------------------------- - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -EmbedResources( - COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -add_library(OrthancStone STATIC - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - ) - -################################################################################ - -# Define the WASM module -# --------------------------------------------------------------- - -project(RtViewerWasm) - -add_executable(RtViewerWasm - RtViewer/RtViewerWasm.cpp - ../Common/RtViewerApp.cpp - ../Common/RtViewerApp.h - ../Common/RtViewerView.cpp - ../Common/RtViewerView.h - ) - -target_link_libraries(RtViewerWasm OrthancStone) - -# Declare installation files for the module -# --------------------------------------------------------------- -install( - TARGETS RtViewerWasm - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/ - ) - -# Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js -# (the generated JS loader for the WASM module) is handled by the `install1` -# section above: it is considered to be the binary output of -# the linker. -# --------------------------------------------------------------- -install( - FILES - ${CMAKE_SOURCE_DIR}/RtViewer/RtViewerWasmApp.js - ${CMAKE_SOURCE_DIR}/RtViewer/index.html - ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm - DESTINATION ${CMAKE_INSTALL_PREFIX}/RtViewer/ - ) - -################################################################################ - -# Define the WASM module -# --------------------------------------------------------------- - -project(SingleFrameViewerWasm) - -add_executable(SingleFrameViewerWasm - SingleFrameViewer/SingleFrameViewer.cpp - ) - -target_link_libraries(SingleFrameViewerWasm OrthancStone) - -# Declare installation files for the module -# --------------------------------------------------------------- -install( - TARGETS SingleFrameViewerWasm - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/ - ) - -# Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js -# (the generated JS loader for the WASM module) is handled by the `install1` -# section above: it is considered to be the binary output of -# the linker. -# --------------------------------------------------------------- -install( - FILES - ${CMAKE_SOURCE_DIR}/SingleFrameViewer/SingleFrameViewerApp.js - ${CMAKE_SOURCE_DIR}/SingleFrameViewer/index.html - ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm - DESTINATION ${CMAKE_INSTALL_PREFIX}/SingleFrameViewer/ - ) diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/NOTES.txt --- a/OrthancStone/Samples/WebAssembly/NOTES.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - -Building WebAssembly samples using Docker -========================================= - -The script "./docker-build.sh" can be used to quickly build the -WebAssembly samples on any GNU/Linux distribution equipped with -Docker. This avoids newcomers to install Emscripten and learn the -CMake options. Just type: - -$ ./docker-build.sh Release - -After successful build, the binaries will be installed in the -following folder (i.e. in the folder "wasm-binaries" at the root of -the source distribution): - -$ ls -l ../../wasm-binaries - - -NB: The source code of the Docker build environment can be found at -the following location: -https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/RtViewer/CMakeLists.txt --- a/OrthancStone/Samples/WebAssembly/RtViewer/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(RtViewerWasm) - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- -set(USE_WASM ON CACHE BOOL "") - -set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") - -set(WASM_FLAGS "-s WASM=1 -s FETCH=1") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - -# Stone of Orthanc configuration -# --------------------------------------------------------------- -set(ALLOW_DOWNLOADS ON) - -include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ENABLE_DCMTK OFF) # Not necessary -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) -SET(ORTHANC_SANDBOXED ON) - -# this will set up the build system for Stone of Orthanc and will -# populate the ORTHANC_STONE_SOURCES CMake variable -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) - - -# We embed a font to be used for on-screen overlays -# --------------------------------------------------------------- - -DownloadPackage( - "a24b8136b8f3bb93f166baf97d9328de" - "http://orthanc.osimis.io/ThirdPartyDownloads/ubuntu-font-family-0.83.zip" - "${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83") - -EmbedResources( - COLORMAP_HOT ${ORTHANC_STONE_ROOT}/Resources/Colormaps/hot.lut - UBUNTU_FONT ${CMAKE_BINARY_DIR}/ubuntu-font-family-0.83/Ubuntu-R.ttf - ) - -# Define the WASM module -# --------------------------------------------------------------- -add_executable(RtViewerWasm - RtViewerWasm.cpp - ../../Common/RtViewerApp.cpp - ../../Common/RtViewerApp.h - ../../Common/RtViewerView.cpp - ../../Common/RtViewerView.h - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - ) - -# Declare installation files for the module -# --------------------------------------------------------------- -install( - TARGETS RtViewerWasm - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} - ) - -# Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js -# (the generated JS loader for the WASM module) is handled by the `install1` -# section above: it is considered to be the binary output of -# the linker. -# --------------------------------------------------------------- -install( - FILES - ${CMAKE_SOURCE_DIR}/RtViewerWasmApp.js - ${CMAKE_SOURCE_DIR}/index.html - ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.wasm - DESTINATION ${CMAKE_INSTALL_PREFIX} - ) diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/RtViewer/OBSOLETE.cpp --- a/OrthancStone/Samples/WebAssembly/RtViewer/OBSOLETE.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,559 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - -#include "../../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" -#include "../../../Framework/Oracle/SleepOracleCommand.h" -#include "../../../Framework/Oracle/WebAssemblyOracle.h" -#include "../../../Framework/Scene2D/GrayscaleStyleConfigurator.h" -#include "../../../Framework/Scene2D/OpenGLCompositor.h" -#include "../../../Framework/Scene2D/PanSceneTracker.h" -#include "../../../Framework/Scene2D/RotateSceneTracker.h" -#include "../../../Framework/Scene2D/ZoomSceneTracker.h" -#include "../../../Framework/Scene2DViewport/UndoStack.h" -#include "../../../Framework/Scene2DViewport/ViewportController.h" -#include "../../../Framework/StoneInitialization.h" -#include "../../../Framework/Viewport/WebAssemblyViewport.h" -#include "../../../Framework/Volumes/VolumeSceneLayerSource.h" - -#include - -#include -#include - -#include - - -class ViewportManager; - -static const unsigned int FONT_SIZE = 32; - -boost::shared_ptr ct_(new OrthancStone::DicomVolumeImage); -boost::shared_ptr loader_; -std::unique_ptr widget1_; -std::unique_ptr widget2_; -std::unique_ptr widget3_; -//OrthancStone::MessageBroker broker_; -//OrthancStone::WebAssemblyOracle oracle_(broker_); -std::unique_ptr tracker_; -static std::map arguments_; -static bool ctrlDown_ = false; - - -#if 0 - -// use the one from WebAssemblyViewport -static OrthancStone::PointerEvent* ConvertMouseEvent( - const EmscriptenMouseEvent& source, - OrthancStone::IViewport& viewport) -{ - - std::unique_ptr target( - new OrthancStone::PointerEvent); - - target->AddPosition(viewport.GetPixelCenterCoordinates( - source.targetX, source.targetY)); - target->SetAltModifier(source.altKey); - target->SetControlModifier(source.ctrlKey); - target->SetShiftModifier(source.shiftKey); - - return target.release(); -} -#endif - - -EM_BOOL OnMouseEvent(int eventType, - const EmscriptenMouseEvent *mouseEvent, - void *userData) -{ - if (mouseEvent != NULL && - userData != NULL) - { - boost::shared_ptr& viewport = - *reinterpret_cast*>(userData); - - std::unique_ptr lock = (*viewport)->Lock(); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - - switch (eventType) - { - case EMSCRIPTEN_EVENT_CLICK: - { - static unsigned int count = 0; - char buf[64]; - sprintf(buf, "click %d", count++); - - std::unique_ptr layer(new OrthancStone::TextSceneLayer); - layer->SetText(buf); - scene.SetLayer(100, layer.release()); - lock->Invalidate(); - break; - } - - case EMSCRIPTEN_EVENT_MOUSEDOWN: - { - boost::shared_ptr t; - - { - std::unique_ptr event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - - switch (mouseEvent->button) - { - case 0: // Left button - emscripten_console_log("Creating RotateSceneTracker"); - t.reset(new OrthancStone::RotateSceneTracker( - viewport, *event)); - break; - - case 1: // Middle button - emscripten_console_log("Creating PanSceneTracker"); - LOG(INFO) << "Creating PanSceneTracker" ; - t.reset(new OrthancStone::PanSceneTracker( - viewport, *event)); - break; - - case 2: // Right button - emscripten_console_log("Creating ZoomSceneTracker"); - t.reset(new OrthancStone::ZoomSceneTracker( - viewport, *event, controller->GetViewport().GetCanvasWidth())); - break; - - default: - break; - } - } - - if (t.get() != NULL) - { - tracker_.reset( - new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier())); - controller->GetViewport().Refresh(); - } - - break; - } - - case EMSCRIPTEN_EVENT_MOUSEMOVE: - if (tracker_.get() != NULL) - { - std::unique_ptr event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - tracker_->PointerMove(*event); - controller->GetViewport().Refresh(); - } - break; - - case EMSCRIPTEN_EVENT_MOUSEUP: - if (tracker_.get() != NULL) - { - std::unique_ptr event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - tracker_->PointerUp(*event); - controller->GetViewport().Refresh(); - if (!tracker_->IsAlive()) - tracker_.reset(); - } - break; - - default: - break; - } - } - - return true; -} - - -void SetupEvents(const std::string& canvas, - boost::shared_ptr& viewport) -{ - emscripten_set_mousedown_callback(canvas.c_str(), &viewport, false, OnMouseEvent); - emscripten_set_mousemove_callback(canvas.c_str(), &viewport, false, OnMouseEvent); - emscripten_set_mouseup_callback(canvas.c_str(), &viewport, false, OnMouseEvent); -} - - class ViewportManager : public OrthanStone::ObserverBase - { - private: - OrthancStone::WebAssemblyViewport viewport_; - std::unique_ptr source_; - VolumeProjection projection_; - std::vector planes_; - size_t currentPlane_; - - void Handle(const DicomVolumeImage::GeometryReadyMessage& message) - { - LOG(INFO) << "Geometry is available"; - - const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry(); - - const unsigned int depth = geometry.GetProjectionDepth(projection_); - - // select an initial cutting plane halfway through the volume - currentPlane_ = depth / 2; - - planes_.resize(depth); - - for (unsigned int z = 0; z < depth; z++) - { - planes_[z] = geometry.GetProjectionSlice(projection_, z); - } - - Refresh(); - - viewport_.FitContent(); - } - - public: - ViewportManager(const std::string& canvas, - VolumeProjection projection) : - projection_(projection), - currentPlane_(0) - { - viewport_ = OrthancStone::WebGLViewport::Create(canvas); - } - - void UpdateSize() - { - viewport_.UpdateSize(); - } - - void SetSlicer(int layerDepth, - const boost::shared_ptr& slicer, - IObservable& loader, - ILayerStyleConfigurator* configurator) - { - if (source_.get() != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, - "Only one slicer can be registered"); - } - - loader.RegisterObserverCallback( - new Callable - (*this, &ViewportManager::Handle)); - - source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer)); - - if (configurator != NULL) - { - source_->SetConfigurator(configurator); - } - } - - void Refresh() - { - if (source_.get() != NULL && - currentPlane_ < planes_.size()) - { - source_->Update(planes_[currentPlane_]); - viewport_.Refresh(); - } - } - - size_t GetSlicesCount() const - { - return planes_.size(); - } - - void Scroll(int delta) - { - if (!planes_.empty()) - { - int tmp = static_cast(currentPlane_) + delta; - unsigned int next; - - if (tmp < 0) - { - next = 0; - } - else if (tmp >= static_cast(planes_.size())) - { - next = planes_.size() - 1; - } - else - { - next = static_cast(tmp); - } - - if (next != currentPlane_) - { - currentPlane_ = next; - Refresh(); - } - } - } - }; -} - - -EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) -{ - try - { - if (widget1_.get() != NULL) - { - widget1_->UpdateSize(); - } - - if (widget2_.get() != NULL) - { - widget2_->UpdateSize(); - } - - if (widget3_.get() != NULL) - { - widget3_->UpdateSize(); - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception while updating canvas size: " << e.What(); - } - - return true; -} - -EM_BOOL OnAnimationFrame(double time, void *userData) -{ - try - { - if (widget1_.get() != NULL) - { - widget1_->Refresh(); - } - - if (widget2_.get() != NULL) - { - widget2_->Refresh(); - } - - if (widget3_.get() != NULL) - { - widget3_->Refresh(); - } - - return true; - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What(); - return false; - } -} - -EM_BOOL OnMouseWheel(int eventType, - const EmscriptenWheelEvent *wheelEvent, - void *userData) -{ - try - { - if (userData != NULL) - { - int delta = 0; - - if (wheelEvent->deltaY < 0) - { - delta = -1; - } - - if (wheelEvent->deltaY > 0) - { - delta = 1; - } - - OrthancStone::ViewportManager& widget = - *reinterpret_cast(userData); - - if (ctrlDown_) - { - delta *= static_cast(widget.GetSlicesCount() / 10); - } - - widget.Scroll(delta); - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception in the wheel event: " << e.What(); - } - - return true; -} - - -EM_BOOL OnKeyDown(int eventType, - const EmscriptenKeyboardEvent *keyEvent, - void *userData) -{ - ctrlDown_ = keyEvent->ctrlKey; - return false; -} - - -EM_BOOL OnKeyUp(int eventType, - const EmscriptenKeyboardEvent *keyEvent, - void *userData) -{ - ctrlDown_ = false; - return false; -} - - - -#if 0 -namespace OrthancStone -{ - class TestSleep : public IObserver - { - private: - WebAssemblyOracle& oracle_; - - void Schedule() - { - oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(2000)); - } - - void Handle(const SleepOracleCommand::TimeoutMessage& message) - { - LOG(INFO) << "TIMEOUT"; - Schedule(); - } - - public: - TestSleep(MessageBroker& broker, - WebAssemblyOracle& oracle) : - IObserver(broker), - oracle_(oracle) - { - oracle.RegisterObserverCallback( - new Callable - (*this, &TestSleep::Handle)); - - LOG(INFO) << "STARTING"; - Schedule(); - } - }; - - //static TestSleep testSleep(broker_, oracle_); -} -#endif - -static bool GetArgument(std::string& value, - const std::string& key) -{ - std::map::const_iterator found = arguments_.find(key); - - if (found == arguments_.end()) - { - return false; - } - else - { - value = found->second; - return true; - } -} - - -extern "C" -{ - int main(int argc, char const *argv[]) - { - OrthancStone::StoneInitialize(); - Orthanc::Logging::EnableInfoLevel(true); - // Orthanc::Logging::EnableTraceLevel(true); - EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); - } - - EMSCRIPTEN_KEEPALIVE - void SetArgument(const char* key, const char* value) - { - // This is called for each GET argument (cf. "app.js") - LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]"; - arguments_[key] = value; - } - - EMSCRIPTEN_KEEPALIVE - void Initialize() - { - try - { - oracle_.SetOrthancRoot(".."); - - loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_)); - - widget1_.reset(new OrthancStone::ViewportManager("mycanvas1", OrthancStone::VolumeProjection_Axial)); - { - std::unique_ptr style(new OrthancStone::GrayscaleStyleConfigurator); - style->SetLinearInterpolation(true); - style->SetWindowing(OrthancStone::ImageWindowing_Bone); - widget1_->SetSlicer(0, loader_, *loader_, style.release()); - } - widget1_->UpdateSize(); - - widget2_.reset(new OrthancStone::ViewportManager("mycanvas2", OrthancStone::VolumeProjection_Coronal)); - { - std::unique_ptr style(new OrthancStone::GrayscaleStyleConfigurator); - style->SetLinearInterpolation(true); - style->SetWindowing(OrthancStone::ImageWindowing_Bone); - widget2_->SetSlicer(0, loader_, *loader_, style.release()); - } - widget2_->UpdateSize(); - - widget3_.reset(new OrthancStone::ViewportManager("mycanvas3", OrthancStone::VolumeProjection_Sagittal)); - { - std::unique_ptr style(new OrthancStone::GrayscaleStyleConfigurator); - style->SetLinearInterpolation(true); - style->SetWindowing(OrthancStone::ImageWindowing_Bone); - widget3_->SetSlicer(0, loader_, *loader_, style.release()); - } - widget3_->UpdateSize(); - - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnWindowResize); // DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 !! - - emscripten_set_wheel_callback("#mycanvas1", widget1_.get(), false, OnMouseWheel); - emscripten_set_wheel_callback("#mycanvas2", widget2_.get(), false, OnMouseWheel); - emscripten_set_wheel_callback("#mycanvas3", widget3_.get(), false, OnMouseWheel); - - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyDown); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, OnKeyUp); - - //emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); - - std::string ct; - if (GetArgument(ct, "ct")) - { - //loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); - loader_->LoadSeries(ct); - } - else - { - LOG(ERROR) << "No Orthanc identifier for the CT series was provided"; - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception during Initialize(): " << e.What(); - } - } -} - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp --- a/OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasm.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - -#include "../../Common/RtViewerApp.h" -#include "../../Common/RtViewerView.h" -#include "../../Common/SampleHelpers.h" - -// Stone of Orthanc includes -#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h" -#include "../../../Sources/StoneException.h" -#include "../../../Sources/StoneInitialization.h" -#include "../../../Sources/Viewport/WebGLViewport.h" -//#include "../../../Sources/OpenGL/WebAssemblyOpenGLContext.h" - -#include - -#include -#include -// #include this include might be necessary in more recent boost versions - -#include -#include - - -#define DISPATCH_JAVASCRIPT_EVENT(name) \ - EM_ASM( \ - const customEvent = document.createEvent("CustomEvent"); \ - customEvent.initCustomEvent(name, false, false, undefined); \ - window.dispatchEvent(customEvent); \ - ); - -#define EXTERN_CATCH_EXCEPTIONS \ - catch (Orthanc::OrthancException& e) \ - { \ - LOG(ERROR) << "OrthancException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (OrthancStone::StoneException& e) \ - { \ - LOG(ERROR) << "StoneException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (std::exception& e) \ - { \ - LOG(ERROR) << "Runtime error: " << e.what(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (...) \ - { \ - LOG(ERROR) << "Native exception"; \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } - -namespace OrthancStone -{ - // typedef EM_BOOL (*OnMouseWheelFunc)(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData); - - EM_BOOL RtViewerView_Scroll(int eventType, - const EmscriptenWheelEvent* wheelEvent, - void* userData) - { - RtViewerView* that = reinterpret_cast(userData); - - int delta = 0; - if (wheelEvent->deltaY < 0) - delta = -1; - if (wheelEvent->deltaY > 0) - delta = 1; - - that->Scroll(delta); - - return 1; - } - - boost::shared_ptr RtViewerView::CreateViewport( - const std::string& canvasId) - { - boost::shared_ptr viewport = WebGLViewport::Create(canvasId); - - void* userData = reinterpret_cast(this); - - // manually add the mouse wheel handler - - std::string selector = "#" + canvasId; - - emscripten_set_wheel_callback_on_thread( - selector.c_str(), - userData, - false, - &RtViewerView_Scroll, - EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD); - - return viewport; - } - - void RtViewerView::TakeScreenshot(const std::string& target, - unsigned int canvasWidth, - unsigned int canvasHeight) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - - void RtViewerApp::RunWasm() - { - loadersContext_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); - - // we are in WASM --> downcast to concrete type - boost::shared_ptr loadersContext = - boost::dynamic_pointer_cast(loadersContext_); - - if (HasArgument("orthanc")) - loadersContext->SetLocalOrthanc(GetArgument("orthanc")); - else - loadersContext->SetLocalOrthanc(".."); - - loadersContext->SetDicomCacheSize(128 * 1024 * 1024); // 128MB - - CreateLoaders(); - - CreateView("RtViewer_Axial", VolumeProjection_Axial); - CreateView("RtViewer_Coronal", VolumeProjection_Coronal); - CreateView("RtViewer_Sagittal", VolumeProjection_Sagittal); - - for (size_t i = 0; i < views_.size(); ++i) - { - views_[i]->PrepareViewport(); - } - - StartLoaders(); - } -} - -extern "C" -{ - boost::shared_ptr g_app; - - int main(int argc, char const *argv[]) - { - try - { - OrthancStone::StoneInitialize(); - Orthanc::Logging::Initialize(); - Orthanc::Logging::EnableTraceLevel(true); - - LOG(WARNING) << "Initializing native Stone"; - - LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__ - << "." << __EMSCRIPTEN_minor__ - << "." << __EMSCRIPTEN_tiny__; - - LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness()); - - g_app = OrthancStone::RtViewerApp::Create(); - - DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized"); - } - EXTERN_CATCH_EXCEPTIONS; - } - - EMSCRIPTEN_KEEPALIVE - void Initialize(const char* canvasId) - { - try - { - g_app->RunWasm(); - } - EXTERN_CATCH_EXCEPTIONS; - } - - EMSCRIPTEN_KEEPALIVE - void SetArgument(const char* key, const char* value) - { - // This is called for each GET argument (cf. "app.js") - LOG(INFO) << "Received GET argument: [" << key << "] = [" << value << "]"; - g_app->SetArgument(key, value); - } - -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js --- a/OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmApp.js Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ - -// This object wraps the functions exposed by the wasm module - -const WasmModuleWrapper = function() { - this._InitializeViewport = undefined; -}; - -WasmModuleWrapper.prototype.Setup = function(Module) { - this._SetArgument = Module.cwrap('SetArgument', null, [ 'string', 'string' ]); - this._Initialize = Module.cwrap('Initialize', null, [ 'string' ]); -}; - -WasmModuleWrapper.prototype.SetArgument = function(key, value) { - this._SetArgument(key, value); -}; - -WasmModuleWrapper.prototype.Initialize = function(canvasId) { - this._Initialize(canvasId); -}; - -var wasmModuleWrapper = new WasmModuleWrapper(); - -$(document).ready(function() { - - window.addEventListener('WasmModuleInitialized', function() { - - // bind the C++ global functions - wasmModuleWrapper.Setup(Module); - - console.warn('Native C++ module initialized'); - - // Loop over the GET arguments - var parameters = window.location.search.substr(1); - if (parameters != null && parameters != '') { - var tokens = parameters.split('&'); - for (var i = 0; i < tokens.length; i++) { - var arg = tokens[i].split('='); - if (arg.length == 2) { - // Send each GET argument to WebAssembly - wasmModuleWrapper.SetArgument(arg[0], decodeURIComponent(arg[1])); - } - } - } - wasmModuleWrapper.Initialize(); - }); - - window.addEventListener('StoneException', function() { - alert('Exception caught in C++ code'); - }); - - var scriptSource; - - if ('WebAssembly' in window) { - console.warn('Loading WebAssembly'); - scriptSource = 'RtViewerWasm.js'; - } else { - console.error('Your browser does not support WebAssembly!'); - } - - // Option 1: Loading script using plain HTML - - /* - var script = document.createElement('script'); - script.src = scriptSource; - script.type = 'text/javascript'; - document.body.appendChild(script); - */ - - // Option 2: Loading script using AJAX (gives the opportunity to - // report explicit errors) - - axios.get(scriptSource) - .then(function (response) { - var script = document.createElement('script'); - script.innerHTML = response.data; - script.type = 'text/javascript'; - document.body.appendChild(script); - }) - .catch(function (error) { - alert('Cannot load the WebAssembly framework'); - }); -}); - -// http://localhost:9979/stone-rtviewer/index.html?loglevel=trace&ctseries=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&rtdose=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&rtstruct=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9 - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js --- a/OrthancStone/Samples/WebAssembly/RtViewer/RtViewerWasmWrapper.js Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ - -const Stone = function() { - this._InitializeViewport = undefined; - this._LoadOrthanc = undefined; - this._LoadDicomWeb = undefined; -}; - -Stone.prototype.Setup = function(Module) { - this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); - this._LoadOrthanc = Module.cwrap('LoadOrthanc', null, [ 'string', 'int' ]); - this._LoadDicomWeb = Module.cwrap('LoadDicomWeb', null, [ 'string', 'string', 'string', 'string', 'int' ]); -}; - -Stone.prototype.InitializeViewport = function(canvasId) { - this._InitializeViewport(canvasId); -}; - -Stone.prototype.LoadOrthanc = function(instance, frame) { - this._LoadOrthanc(instance, frame); -}; - -Stone.prototype.LoadDicomWeb = function(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) { - this._LoadDicomWeb(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame); -}; - -var stone = new Stone(); - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/RtViewer/index.html --- a/OrthancStone/Samples/WebAssembly/RtViewer/index.html Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ - - - - Stone of Orthanc Single Frame Viewer - - - - - - - - - - - - - - - - - - - - - - - - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt --- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(SingleFrameViewer) - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- -set(USE_WASM ON CACHE BOOL "") - -set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") - -set(WASM_FLAGS "-s WASM=1 -s FETCH=1") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - -# Stone of Orthanc configuration -# --------------------------------------------------------------- -set(ALLOW_DOWNLOADS ON) - -include(${CMAKE_SOURCE_DIR}/../../../Resources/CMake/OrthancStoneParameters.cmake) - -SET(ENABLE_DCMTK OFF) # Not necessary -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) -SET(ORTHANC_SANDBOXED ON) - -# this will set up the build system for Stone of Orthanc and will -# populate the ORTHANC_STONE_SOURCES CMake variable -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) - -# Define the WASM module -# --------------------------------------------------------------- -add_executable(SingleFrameViewerWasm - SingleFrameViewer.cpp - ${ORTHANC_STONE_SOURCES} - ) - -# Declare installation files for the module -# --------------------------------------------------------------- -install( - TARGETS SingleFrameViewerWasm - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} - ) - -# Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js -# (the generated JS loader for the WASM module) is handled by the `install1` -# section above: it is considered to be the binary output of -# the linker. -# --------------------------------------------------------------- -install( - FILES - ${CMAKE_SOURCE_DIR}/SingleFrameViewerApp.js - ${CMAKE_SOURCE_DIR}/index.html - ${CMAKE_CURRENT_BINARY_DIR}/SingleFrameViewerWasm.wasm - DESTINATION ${CMAKE_INSTALL_PREFIX} - ) diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json --- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/CMakeSettings.json Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -{ - "configurations": [ - { - "name": "wasm32-RelWithDebInfo", - "generator": "Ninja", - "configurationType": "RelWithDebInfo", - //"inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "cmakeToolchain": "C:/osi/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", - "intelliSenseMode": "windows-clang-x64", - "variables": [ - { - "name": "CMAKE_BUILD_TYPE", - "value": "RelWithDebInfo", - "type": "STRING" - }, - { - "name": "ALLOW_DOWNLOADS", - "value": "True", - "type": "BOOL" - }, - { - "name": "STATIC_BUILD", - "value": "True", - "type": "BOOL" - }, - { - "name": "OPENSSL_NO_CAPIENG", - "value": "True", - "type": "BOOL" - } - ] - } - ] -} \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp --- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewer.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "SingleFrameViewerApplication.h" - -#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h" -#include "../../../Sources/StoneException.h" -#include "../../../Sources/StoneInitialization.h" - -#include // For std::unique_ptr<> -#include - -#include -#include - - -#define DISPATCH_JAVASCRIPT_EVENT(name) \ - EM_ASM( \ - const customEvent = document.createEvent("CustomEvent"); \ - customEvent.initCustomEvent(name, false, false, undefined); \ - window.dispatchEvent(customEvent); \ - ); - -#define EXTERN_CATCH_EXCEPTIONS \ - catch (Orthanc::OrthancException& e) \ - { \ - LOG(ERROR) << "OrthancException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (OrthancStone::StoneException& e) \ - { \ - LOG(ERROR) << "StoneException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (std::exception& e) \ - { \ - LOG(ERROR) << "Runtime error: " << e.what(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (...) \ - { \ - LOG(ERROR) << "Native exception"; \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } - - - -namespace OrthancStone -{ -} - -static std::unique_ptr context_; -static boost::shared_ptr application_; - -extern "C" -{ - int main(int argc, char const *argv[]) - { - try - { - Orthanc::Logging::Initialize(); - Orthanc::Logging::EnableInfoLevel(true); - //Orthanc::Logging::EnableTraceLevel(true); - LOG(WARNING) << "Initializing native Stone"; - - LOG(WARNING) << "Compiled with Emscripten " << __EMSCRIPTEN_major__ - << "." << __EMSCRIPTEN_minor__ - << "." << __EMSCRIPTEN_tiny__; - - LOG(INFO) << "Endianness: " << Orthanc::EnumerationToString(Orthanc::Toolbox::DetectEndianness()); - context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); - context_->SetLocalOrthanc(".."); - context_->SetDicomCacheSize(128 * 1024 * 1024); // 128MB - - DISPATCH_JAVASCRIPT_EVENT("WasmModuleInitialized"); - } - EXTERN_CATCH_EXCEPTIONS; - - return 0; - } - - EMSCRIPTEN_KEEPALIVE - void InitializeViewport(const char* canvasId) - { - try - { - if (context_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, - "The loaders context is not available yet"); - } - - if (application_.get() != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, - "Only one single viewport is available for this application"); - } - - boost::shared_ptr viewport(OrthancStone::GetWebGLViewportsRegistry().Add(canvasId)); - application_ = OrthancStone::Application::Create(*context_, viewport); - - { - OrthancStone::WebGLViewportsRegistry::Accessor accessor( - OrthancStone::GetWebGLViewportsRegistry(), canvasId); - - if (accessor.IsValid()) - { - accessor.GetViewport().Invalidate(); - } - } - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void LoadFromOrthanc(const char* instance, - int frame) - { - try - { - if (application_.get() != NULL) - { - OrthancStone::DicomSource source; - application_->LoadOrthancFrame(source, instance, frame); - } - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void LoadFromDicomWeb(const char* server, - const char* studyInstanceUid, - const char* seriesInstanceUid, - const char* sopInstanceUid, - int frame) - { - try - { - if (application_.get() != NULL) - { - OrthancStone::DicomSource source; - source.SetDicomWebThroughOrthancSource(server); - application_->LoadDicomWebFrame(source, studyInstanceUid, seriesInstanceUid, - sopInstanceUid, frame); - } - } - EXTERN_CATCH_EXCEPTIONS; - } -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js --- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApp.js Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ - -// This object wraps the functions exposed by the wasm module - -const WasmModuleWrapper = function() { - this._InitializeViewport = undefined; - this._LoadFromOrthanc = undefined; -}; - -WasmModuleWrapper.prototype.Setup = function(Module) { - this._InitializeViewport = Module.cwrap('InitializeViewport', null, [ 'string' ]); - this._LoadFromOrthanc = Module.cwrap('LoadFromOrthanc', null, [ 'string', 'int' ]); -}; - -WasmModuleWrapper.prototype.InitializeViewport = function(canvasId) { - this._InitializeViewport(canvasId); -}; - -WasmModuleWrapper.prototype.LoadFromOrthanc = function(instance, frame) { - this._LoadFromOrthanc(instance, frame); -}; - -var wasmModuleWrapper = new WasmModuleWrapper(); - -$(document).ready(function() { - - window.addEventListener('WasmModuleInitialized', function() { - wasmModuleWrapper.Setup(Module); - console.warn('Native C++ module initialized'); - - wasmModuleWrapper.InitializeViewport('viewport'); - }); - - window.addEventListener('StoneException', function() { - alert('Exception caught in C++ code'); - }); - - var scriptSource; - - if ('WebAssembly' in window) { - console.warn('Loading WebAssembly'); - scriptSource = 'SingleFrameViewerWasm.js'; - } else { - console.error('Your browser does not support WebAssembly!'); - } - - // Option 1: Loading script using plain HTML - - /* - var script = document.createElement('script'); - script.src = scriptSource; - script.type = 'text/javascript'; - document.body.appendChild(script); - */ - - // Option 2: Loading script using AJAX (gives the opportunity to - // report explicit errors) - - axios.get(scriptSource) - .then(function (response) { - var script = document.createElement('script'); - script.innerHTML = response.data; - script.type = 'text/javascript'; - document.body.appendChild(script); - }) - .catch(function (error) { - alert('Cannot load the WebAssembly framework'); - }); -}); - - -$('#orthancLoad').click(function() { - wasmModuleWrapper.LoadFromOrthanc($('#orthancInstance').val(), - $('#orthancFrame').val()); -}); diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h --- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,502 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#pragma once - -#include "../../../Sources/Loaders/DicomResourcesLoader.h" -#include "../../../Sources/Loaders/ILoadersContext.h" -#include "../../../Sources/Loaders/SeriesFramesLoader.h" -#include "../../../Sources/Loaders/SeriesThumbnailsLoader.h" -#include "../../../Sources/Viewport/IViewport.h" - -#include // For std::unique_ptr<> - -#include - - -namespace OrthancStone -{ - class Application : public ObserverBase - { - private: - ILoadersContext& context_; - boost::shared_ptr viewport_; - boost::shared_ptr dicomLoader_; - boost::shared_ptr framesLoader_; - - Application(ILoadersContext& context, - boost::shared_ptr viewport) : - context_(context), - viewport_(viewport) - { - } - - void Handle(const SeriesFramesLoader::FrameLoadedMessage& message) - { - LOG(INFO) << "Frame decoded! " - << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight() - << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat()); - - std::unique_ptr layer( - message.GetInstanceParameters().CreateTexture(message.GetImage())); - layer->SetLinearInterpolation(true); - - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetController().GetScene().SetLayer(0, layer.release()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - } - - void Handle(const DicomResourcesLoader::SuccessMessage& message) - { - if (message.GetResources()->GetSize() != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - //message.GetResources()->GetResource(0).Print(stdout); - - { - std::unique_ptr lock(context_.Lock()); - SeriesFramesLoader::Factory f(*message.GetResources()); - - framesLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); - Register(*framesLoader_, &Application::Handle); - - assert(message.HasUserPayload()); - const Orthanc::SingleValueObject& payload = - dynamic_cast&>(message.GetUserPayload()); - - LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue(); - framesLoader_->ScheduleLoadFrame( - 0, message.GetDicomSource(), payload.GetValue(), - message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */, - NULL); - } - } - - public: - static boost::shared_ptr Create(ILoadersContext& context, - boost::shared_ptr viewport) - { - boost::shared_ptr application(new Application(context, viewport)); - - { - std::unique_ptr lock(context.Lock()); - application->dicomLoader_ = DicomResourcesLoader::Create(*lock); - } - - application->Register(*application->dicomLoader_, &Application::Handle); - - return application; - } - - void LoadOrthancFrame(const DicomSource& source, - const std::string& instanceId, - unsigned int frame) - { - std::unique_ptr lock(context_.Lock()); - - dicomLoader_->ScheduleLoadOrthancResource( - boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), - 0, source, Orthanc::ResourceType_Instance, instanceId, - new Orthanc::SingleValueObject(frame)); - } - - void LoadDicomWebFrame(const DicomSource& source, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid, - unsigned int frame) - { - std::unique_ptr lock(context_.Lock()); - - // We first must load the "/metadata" to know the number of frames - dicomLoader_->ScheduleGetDicomWeb( - boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, - "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", - new Orthanc::SingleValueObject(frame)); - } - - void FitContent() - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - }; - - - - class IWebViewerLoadersObserver : public boost::noncopyable - { - public: - virtual ~IWebViewerLoadersObserver() - { - } - - virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0; - - virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - SeriesThumbnailType type) = 0; - }; - - - class WebViewerLoaders : public ObserverBase - { - private: - static const int PRIORITY_ADD_RESOURCES = 0; - static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100; - - enum Type - { - Type_Orthanc = 1, - Type_DicomWeb = 2 - }; - - ILoadersContext& context_; - std::unique_ptr observer_; - bool loadThumbnails_; - DicomSource source_; - std::set scheduledSeries_; - std::set scheduledThumbnails_; - std::set scheduledStudies_; - boost::shared_ptr loadedSeries_; - boost::shared_ptr loadedStudies_; - boost::shared_ptr resourcesLoader_; - boost::shared_ptr thumbnailsLoader_; - - WebViewerLoaders(ILoadersContext& context, - IWebViewerLoadersObserver* observer) : - context_(context), - observer_(observer), - loadThumbnails_(false) - { - loadedSeries_ = boost::make_shared(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); - loadedStudies_ = boost::make_shared(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); - } - - static Orthanc::IDynamicObject* CreatePayload(Type type) - { - return new Orthanc::SingleValueObject(type); - } - - void HandleThumbnail(const SeriesThumbnailsLoader::SuccessMessage& message) - { - if (observer_.get() != NULL) - { - observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(), - message.GetSeriesInstanceUid(), - message.GetType()); - } - } - - void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message) - { - LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); - - switch (dynamic_cast&>(message.GetUserPayload()).GetValue()) - { - case Type_DicomWeb: - { - for (size_t i = 0; i < loadedSeries_->GetSize(); i++) - { - std::string study; - if (loadedSeries_->GetResource(i).LookupStringValue( - study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && - loadedStudies_->HasResource(study)) - { - Orthanc::DicomMap m; - m.Assign(loadedSeries_->GetResource(i)); - loadedStudies_->MergeResource(m, study); - series.AddResource(m); - } - } - - break; - } - - case Type_Orthanc: - { - for (size_t i = 0; i < message.GetResources()->GetSize(); i++) - { - series.AddResource(message.GetResources()->GetResource(i)); - } - - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - if (loadThumbnails_ && - (!source_.IsDicomWeb() || - source_.HasDicomWebRendered())) - { - for (size_t i = 0; i < series.GetSize(); i++) - { - std::string patientId, studyInstanceUid, seriesInstanceUid; - if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) && - series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && - series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) && - scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end()) - { - scheduledThumbnails_.insert(seriesInstanceUid); - thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid); - } - } - } - - if (observer_.get() != NULL && - series.GetSize() > 0) - { - observer_->SignalSeriesUpdated(series); - } - } - - void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message) - { - Json::Value body; - message.ParseJsonBody(body); - - if (body.type() != Json::arrayValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - else - { - for (Json::Value::ArrayIndex i = 0; i < body.size(); i++) - { - if (body[i].type() == Json::stringValue) - { - AddOrthancSeries(body[i].asString()); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - } - } - } - - public: - static boost::shared_ptr Create(ILoadersContext& context, - const DicomSource& source, - bool loadThumbnails, - IWebViewerLoadersObserver* observer) - { - boost::shared_ptr application(new WebViewerLoaders(context, observer)); - application->source_ = source; - application->loadThumbnails_ = loadThumbnails; - - { - std::unique_ptr lock(context.Lock()); - - application->resourcesLoader_ = DicomResourcesLoader::Create(*lock); - - { - SeriesThumbnailsLoader::Factory f; - f.SetPriority(PRIORITY_THUMBNAILS); - application->thumbnailsLoader_ = boost::dynamic_pointer_cast(f.Create(*lock)); - } - - application->Register( - lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); - - application->Register( - *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); - - application->Register( - *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); - - lock->AddLoader(application); - } - - return application; - } - - void AddDicomAllSeries() - { - std::unique_ptr lock(context_.Lock()); - - if (source_.IsDicomWeb()) - { - resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, - "/series", CreatePayload(Type_DicomWeb)); - resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, - "/studies", CreatePayload(Type_DicomWeb)); - } - else if (source_.IsOrthanc()) - { - std::unique_ptr command(new OrthancRestApiCommand); - command->SetMethod(Orthanc::HttpMethod_Get); - command->SetUri("/series"); - lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - void AddDicomStudy(const std::string& studyInstanceUid) - { - // Avoid adding twice the same study - if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) - { - scheduledStudies_.insert(studyInstanceUid); - - if (source_.IsDicomWeb()) - { - Orthanc::DicomMap filter; - filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); - - std::set tags; - - { - std::unique_ptr lock(context_.Lock()); - - resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, - Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); - - resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, - Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); - } - } - else if (source_.IsOrthanc()) - { - std::unique_ptr command(new OrthancRestApiCommand); - command->SetMethod(Orthanc::HttpMethod_Post); - command->SetUri("/tools/find"); - - Json::Value body; - body["Level"] = "Series"; - body["Query"] = Json::objectValue; - body["Query"]["StudyInstanceUID"] = studyInstanceUid; - command->SetBody(body); - - { - std::unique_ptr lock(context_.Lock()); - lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - } - - void AddDicomSeries(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) - { - std::set tags; - - std::unique_ptr lock(context_.Lock()); - - if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end()) - { - scheduledStudies_.insert(studyInstanceUid); - - if (source_.IsDicomWeb()) - { - Orthanc::DicomMap filter; - filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); - - resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_, - Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb)); - } - } - - if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end()) - { - scheduledSeries_.insert(seriesInstanceUid); - - if (source_.IsDicomWeb()) - { - Orthanc::DicomMap filter; - filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); - filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); - - resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_, - Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb)); - } - else if (source_.IsOrthanc()) - { - std::unique_ptr command(new OrthancRestApiCommand); - command->SetMethod(Orthanc::HttpMethod_Post); - command->SetUri("/tools/find"); - - Json::Value body; - body["Level"] = "Series"; - body["Query"] = Json::objectValue; - body["Query"]["StudyInstanceUID"] = studyInstanceUid; - body["Query"]["SeriesInstanceUID"] = seriesInstanceUid; - command->SetBody(body); - - lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release()); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - } - - void AddOrthancStudy(const std::string& orthancId) - { - if (source_.IsOrthanc()) - { - std::unique_ptr lock(context_.Lock()); - resourcesLoader_->ScheduleLoadOrthancResources( - loadedSeries_, PRIORITY_ADD_RESOURCES, source_, - Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series, - CreatePayload(Type_Orthanc)); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, - "Only applicable to Orthanc DICOM sources"); - } - } - - void AddOrthancSeries(const std::string& orthancId) - { - if (source_.IsOrthanc()) - { - std::unique_ptr lock(context_.Lock()); - resourcesLoader_->ScheduleLoadOrthancResource( - loadedSeries_, PRIORITY_ADD_RESOURCES, - source_, Orthanc::ResourceType_Series, orthancId, - CreatePayload(Type_Orthanc)); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, - "Only applicable to Orthanc DICOM sources"); - } - } - }; -} diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/SingleFrameViewer/index.html --- a/OrthancStone/Samples/WebAssembly/SingleFrameViewer/index.html Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ - - - - Stone of Orthanc Single Frame Viewer - - - - - - - - - - -

Viewport

- - - - -

Load from Orthanc

-

- Orthanc instance: -

-

- Frame number: -

-

- -

- - - - - - - diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/docker-build.sh --- a/OrthancStone/Samples/WebAssembly/docker-build.sh Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#!/bin/bash - -set -ex - -IMAGE=jodogne/wasm-builder:1.39.17-upstream - -if [ "$1" != "Debug" -a "$1" != "Release" ]; then - echo "Please provide build type: Debug or Release" - exit -1 -fi - -if [ -t 1 ]; then - # TTY is available => use interactive mode - DOCKER_FLAGS='-i' -fi - -ROOT_DIR=`dirname $(readlink -f $0)`/../../.. - -mkdir -p ${ROOT_DIR}/wasm-binaries - -docker run -t ${DOCKER_FLAGS} --rm \ - --user $(id -u):$(id -g) \ - -v ${ROOT_DIR}:/source:ro \ - -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \ - bash /source/OrthancStone/Samples/WebAssembly/docker-internal.sh $1 - -ls -lR ${ROOT_DIR}/wasm-binaries/ diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/WebAssembly/docker-internal.sh --- a/OrthancStone/Samples/WebAssembly/docker-internal.sh Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -#!/bin/bash -set -ex - -source /opt/emsdk/emsdk_env.sh - -# Use a folder that is writeable by non-root users for the Emscripten cache -export EM_CACHE=/tmp/emscripten-cache - -# Get the Orthanc framework -cd /tmp/ -hg clone https://hg.orthanc-server.com/orthanc/ - -# Make a copy of the read-only folder containing the source code into -# a writeable folder, because of "DownloadPackage.cmake" that writes -# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt" -cd /source -hg clone /source /tmp/source-writeable - -mkdir /tmp/build -cd /tmp/build - -cmake /tmp/source-writeable/OrthancStone/Samples/WebAssembly \ - -DCMAKE_BUILD_TYPE=$1 \ - -DCMAKE_INSTALL_PREFIX=/target \ - -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ - -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \ - -DSTATIC_BUILD=ON \ - -G Ninja - -ninja -j2 install diff -r de8cf5859e84 -r d1806b4e4839 OrthancStone/Samples/build-wasm-samples.sh --- a/OrthancStone/Samples/build-wasm-samples.sh Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -#!/bin/bash -# -# usage: -# to build the samples in RelWithDebInfo: -# ./build-wasm-samples.sh -# -# to build the samples in Release: -# ./build-wasm-samples.sh Release - -set -e - -if [ ! -d "WebAssembly" ]; then - echo "This script must be run from the Samples folder one level below orthanc-stone" - exit 1 -fi - - -currentDir=$(pwd) -samplesRootDir=$(pwd) -devrootDir=$(pwd)/../../ - -buildType=${1:-RelWithDebInfo} -buildFolderName="$devrootDir/out/build-stone-wasm-samples-$buildType" -installFolderName="$devrootDir/out/install-stone-wasm-samples-$buildType" - -mkdir -p $buildFolderName -# change current folder to the build folder -pushd $buildFolderName - -# configure the environment to use Emscripten -source ~/apps/emsdk/emsdk_env.sh - -emcmake cmake -G "Ninja" \ - -DCMAKE_BUILD_TYPE=$buildType \ - -DCMAKE_INSTALL_PREFIX=$installFolderName \ - -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \ - $samplesRootDir/WebAssembly - -# perform build + installation -ninja -ninja install - -# restore the original working folder -popd - -echo "If all went well, the output files can be found in $installFolderName:" - -ls $installFolderName \ No newline at end of file diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/COPYING --- a/StoneWebViewer/COPYING Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Plugin/CMakeLists.txt --- a/StoneWebViewer/Plugin/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(StoneWebViewerPlugin) - -set(ORTHANC_PLUGIN_VERSION "mainline") - -if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -else() - set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "1.7.2") - set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") -endif() - - - -set(STONE_BINARIES "${CMAKE_SOURCE_DIR}/../../wasm-binaries/StoneWebViewer/" CACHE PATH "Path to the binaries of the \"../WebAssembly\" folder") - -# Parameters of the build -set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") -set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") -set(ORTHANC_FRAMEWORK_SOURCE "${ORTHANC_FRAMEWORK_DEFAULT_SOURCE}" CACHE STRING "Source of the Orthanc framework (can be \"system\", \"hg\", \"archive\", \"web\" or \"path\")") -set(ORTHANC_FRAMEWORK_VERSION "${ORTHANC_FRAMEWORK_DEFAULT_VERSION}" CACHE STRING "Version of the Orthanc framework") -set(ORTHANC_FRAMEWORK_ARCHIVE "" CACHE STRING "Path to the Orthanc archive, if ORTHANC_FRAMEWORK_SOURCE is \"archive\"") -set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory, if ORTHANC_FRAMEWORK_SOURCE is \"path\"") - - -# Advanced parameters to fine-tune linking against system libraries -set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") -set(ORTHANC_FRAMEWORK_STATIC OFF CACHE BOOL "If linking against the Orthanc framework system library, indicates whether this library was statically linked") -mark_as_advanced(ORTHANC_FRAMEWORK_STATIC) - - -# Download and setup the Orthanc framework -include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake) - -include_directories(${ORTHANC_FRAMEWORK_ROOT}) - -if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system") - link_libraries(${ORTHANC_FRAMEWORK_LIBRARIES}) - -else() - include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake) - set(ENABLE_MODULE_IMAGES OFF) - set(ENABLE_MODULE_JOBS OFF) - set(ENABLE_MODULE_DICOM OFF) - include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake) -endif() - -include(${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginsExports.cmake) - - -if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) - include_directories(${CMAKE_SOURCE_DIR}/../Resources/OrthancSdk-1.0.0) -else () - CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H) - if (NOT HAVE_ORTHANC_H) - message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") - endif() -endif() - - -add_definitions( - -DHAS_ORTHANC_EXCEPTION=1 - -DPLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}" - -DPLUGIN_NAME="stone-webviewer" - ) - - -EmbedResources( - # Folders - IMAGES ${STONE_BINARIES}/img/ - WEB_APPLICATION ${CMAKE_SOURCE_DIR}/../WebApplication - - # Individual files - ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/OrthancExplorer.js - STONE_WEB_VIEWER_JS ${STONE_BINARIES}/StoneWebViewer.js - STONE_WEB_VIEWER_WASM ${STONE_BINARIES}/StoneWebViewer.wasm - STONE_WRAPPER ${STONE_BINARIES}/stone.js - ) - -add_library(StoneWebViewer SHARED - Plugin.cpp - ${AUTOGENERATED_SOURCES} - ${CMAKE_SOURCE_DIR}/../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp - ${ORTHANC_CORE_SOURCES} - ) - -set_target_properties(StoneWebViewer PROPERTIES - VERSION ${ORTHANC_PLUGIN_VERSION} - SOVERSION ${ORTHANC_PLUGIN_VERSION}) - -install( - TARGETS StoneWebViewer - RUNTIME DESTINATION lib # Destination for Windows - LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux - ) diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Plugin/OrthancExplorer.js --- a/StoneWebViewer/Plugin/OrthancExplorer.js Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -$('#study').live('pagebeforecreate', function() { - var b = $('
') - .attr('data-role', 'button') - .attr('href', '#') - .attr('data-icon', 'search') - .attr('data-theme', 'e') - .text('Stone Web Viewer'); - - b.insertBefore($('#study-delete').parent().parent()); - b.click(function() { - if ($.mobile.pageData) { - $.ajax({ - url: '../studies/' + $.mobile.pageData.uuid, - dataType: 'json', - cache: false, - success: function(study) { - var studyInstanceUid = study.MainDicomTags.StudyInstanceUID; - window.open('../stone-webviewer/index.html?study=' + studyInstanceUid); - } - }); - } - }); -}); - - -$('#series').live('pagebeforecreate', function() { - var b = $('') - .attr('data-role', 'button') - .attr('href', '#') - .attr('data-icon', 'search') - .attr('data-theme', 'e') - .text('Stone Web Viewer'); - - b.insertBefore($('#series-delete').parent().parent()); - b.click(function() { - if ($.mobile.pageData) { - $.ajax({ - url: '../series/' + $.mobile.pageData.uuid, - dataType: 'json', - cache: false, - success: function(series) { - $.ajax({ - url: '../studies/' + series.ParentStudy, - dataType: 'json', - cache: false, - success: function(study) { - var studyInstanceUid = study.MainDicomTags.StudyInstanceUID; - var seriesInstanceUid = series.MainDicomTags.SeriesInstanceUID; - window.open('../stone-webviewer/index.html?study=' + studyInstanceUid + - '&series=' + seriesInstanceUid); - } - }); - } - }); - } - }); -}); diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Plugin/Plugin.cpp --- a/StoneWebViewer/Plugin/Plugin.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" - -#include - -#include -#include - -OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId) -{ - try - { - if (changeType == OrthancPluginChangeType_OrthancStarted) - { - Json::Value info; - if (!OrthancPlugins::RestApiGet(info, "/plugins/dicom-web", false)) - { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_InternalError, - "The Stone Web viewer requires the DICOMweb plugin to be installed"); - } - - if (info.type() != Json::objectValue || - !info.isMember("ID") || - !info.isMember("Version") || - info["ID"].type() != Json::stringValue || - info["Version"].type() != Json::stringValue || - info["ID"].asString() != "dicom-web") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "The DICOMweb plugin is not properly installed"); - } - - std::string version = info["Version"].asString(); - if (version != "mainline") - { - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, version, '.'); - if (tokens.size() != 2) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Bad version of the DICOMweb plugin: " + version); - } - - int major, minor; - - try - { - major = boost::lexical_cast(tokens[0]); - minor = boost::lexical_cast(tokens[1]); - } - catch (boost::bad_lexical_cast&) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Bad version of the DICOMweb plugin: " + version); - } - - if (major <= 0 || - (major == 1 && minor <= 1)) - { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_InternalError, - "The Stone Web viewer requires DICOMweb plugin with version >= 1.2, found: " + version); - } - - if (major <= 0 || - (major == 1 && minor == 2)) - { - /** - * DICOMweb 1.3 is better than 1.2 for 2 reasons: (1) - * MONOCHROME1 images are not properly rendered in DICOMweb - * 1.2, and (2) DICOMweb 1.2 cannot transcode images (this - * causes issues on JPEG2k images). - **/ - LOG(WARNING) << "The Stone Web viewer has some incompatibilities " - << "with DICOMweb plugin 1.2, consider upgrading the DICOMweb plugin"; - } - } - } - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "Exception: " << e.What(); - return static_cast(e.GetErrorCode()); - } - - return OrthancPluginErrorCode_Success; -} - - -template -void ServeEmbeddedFolder(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); - - if (request->method != OrthancPluginHttpMethod_Get) - { - OrthancPluginSendMethodNotAllowed(context, output, "GET"); - } - else - { - std::string path = "/" + std::string(request->groups[0]); - const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path)); - - std::string s; - Orthanc::EmbeddedResources::GetDirectoryResource(s, folder, path.c_str()); - - const char* resource = s.size() ? s.c_str() : NULL; - OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); - } -} - - -template -void ServeEmbeddedFile(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); - - if (request->method != OrthancPluginHttpMethod_Get) - { - OrthancPluginSendMethodNotAllowed(context, output, "GET"); - } - else - { - const char* mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(url)); - - std::string s; - Orthanc::EmbeddedResources::GetFileResource(s, file); - - const char* resource = s.size() ? s.c_str() : NULL; - OrthancPluginAnswerBuffer(context, output, resource, s.size(), mime); - } -} - - -extern "C" -{ - ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) - { - OrthancPlugins::SetGlobalContext(context); - -#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) - Orthanc::Logging::InitializePluginContext(context); -#else - Orthanc::Logging::Initialize(context); -#endif - - /* Check the version of the Orthanc core */ - if (OrthancPluginCheckVersion(context) == 0) - { - char info[1024]; - sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", - context->orthancVersion, - ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, - ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); - OrthancPluginLogError(context, info); - return -1; - } - - try - { - std::string explorer; - Orthanc::EmbeddedResources::GetFileResource( - explorer, Orthanc::EmbeddedResources::ORTHANC_EXPLORER); - OrthancPluginExtendOrthancExplorer(OrthancPlugins::GetGlobalContext(), explorer.c_str()); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-webviewer/StoneWebViewer.wasm", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-webviewer/StoneWebViewer.js", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-webviewer/stone.js", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-webviewer/img/(.*)", true); - - OrthancPlugins::RegisterRestCallback - > - ("/stone-webviewer/(.*)", true); - - OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); - } - catch (...) - { - OrthancPlugins::LogError("Exception while initializing the Stone Web viewer plugin"); - return -1; - } - - return 0; - } - - - ORTHANC_PLUGINS_API void OrthancPluginFinalize() - { - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetName() - { - return PLUGIN_NAME; - } - - - ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() - { - return PLUGIN_VERSION; - } -} diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/GenerateImages.py --- a/StoneWebViewer/Resources/GenerateImages.py Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -#!/usr/bin/env python - -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., 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 . - - -import os -from PIL import Image - -SOURCE = os.path.dirname(os.path.abspath(__file__)) -TARGET = os.path.join(SOURCE, '..', 'WebApplication', 'img') - -try: - os.makedirs(TARGET) -except: # Directory already exists - pass - -color = (217, 217, 217, 255) -border = 3 -width = 32 -height = 32 - - - -image = Image.new('RGBA', (width, height)) - -for x in range(0, width): - for y in range(0, height): - image.putpixel((x, y), color) - -image.save(os.path.join(TARGET, 'grid1x1.png'), 'PNG') - - - -image = Image.new('RGBA', (width, height)) - -for x in range(0, width / 2 - border): - for y in range(0, height / 2 - border): - image.putpixel((x, y), color) - for y in range(height / 2 + border, height): - image.putpixel((x, y), color) - -for x in range(width / 2 + border, width): - for y in range(0, height / 2 - border): - image.putpixel((x, y), color) - for y in range(height / 2 + border, height): - image.putpixel((x, y), color) - -image.save(os.path.join(TARGET, 'grid2x2.png'), 'PNG') - - - -image = Image.new('RGBA', (width, height)) - -for y in range(0, height): - for x in range(0, width / 2 - border): - image.putpixel((x, y), color) - for x in range(width / 2 + border, width): - image.putpixel((x, y), color) - -image.save(os.path.join(TARGET, 'grid2x1.png'), 'PNG') - - - -image = Image.new('RGBA', (width, height)) - -for x in range(0, width): - for y in range(0, height / 2 - border): - image.putpixel((x, y), color) - for y in range(height / 2 + border, height): - image.putpixel((x, y), color) - -image.save(os.path.join(TARGET, 'grid1x2.png'), 'PNG') diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/NOTES.txt --- a/StoneWebViewer/Resources/NOTES.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ - -Origin of SCSS -============== - -The "Styles" folder is a copy of: -https://bitbucket.org/osimis/osimis-webviewer-plugin/src/master/frontend/src/styles/ - - - -Generation of CSS from the SCSS -=============================== - -$ npm install node-sass -$ ./node_modules/node-sass/bin/node-sass ./Styles/styles.scss > ../WebApplication/app.css -$ ./GenerateImages.py diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list --- a/StoneWebViewer/Resources/Orthanc/Plugins/ExportedSymbolsPlugins.list Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -# This is the list of the symbols that must be exported by Orthanc -# plugins, if targeting OS X - -_OrthancPluginInitialize -_OrthancPluginFinalize -_OrthancPluginGetName -_OrthancPluginGetVersion diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp --- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3383 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 . - **/ - - -#include "OrthancPluginCppWrapper.h" - -#include -#include -#include -#include -#include - - -#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) -static const OrthancPluginErrorCode OrthancPluginErrorCode_NullPointer = OrthancPluginErrorCode_Plugin; -#endif - - -namespace OrthancPlugins -{ - static OrthancPluginContext* globalContext_ = NULL; - - - void SetGlobalContext(OrthancPluginContext* context) - { - if (context == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); - } - else if (globalContext_ == NULL) - { - globalContext_ = context; - } - else - { - ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); - } - } - - - bool HasGlobalContext() - { - return globalContext_ != NULL; - } - - - OrthancPluginContext* GetGlobalContext() - { - if (globalContext_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(BadSequenceOfCalls); - } - else - { - return globalContext_; - } - } - - - void MemoryBuffer::Check(OrthancPluginErrorCode code) - { - if (code != OrthancPluginErrorCode_Success) - { - // Prevent using garbage information - buffer_.data = NULL; - buffer_.size = 0; - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } - - - bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code) - { - if (code != OrthancPluginErrorCode_Success) - { - // Prevent using garbage information - buffer_.data = NULL; - buffer_.size = 0; - } - - if (code == OrthancPluginErrorCode_Success) - { - return true; - } - else if (code == OrthancPluginErrorCode_UnknownResource || - code == OrthancPluginErrorCode_InexistentItem) - { - return false; - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } - - - MemoryBuffer::MemoryBuffer() - { - buffer_.data = NULL; - buffer_.size = 0; - } - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - MemoryBuffer::MemoryBuffer(const void* buffer, - size_t size) - { - uint32_t s = static_cast(size); - if (static_cast(s) != size) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) != - OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory); - } - else - { - memcpy(buffer_.data, buffer, size); - } - } -#endif - - - void MemoryBuffer::Clear() - { - if (buffer_.data != NULL) - { - OrthancPluginFreeMemoryBuffer(GetGlobalContext(), &buffer_); - buffer_.data = NULL; - buffer_.size = 0; - } - } - - - void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other) - { - Clear(); - - buffer_.data = other.data; - buffer_.size = other.size; - - other.data = NULL; - other.size = 0; - } - - - void MemoryBuffer::Swap(MemoryBuffer& other) - { - std::swap(buffer_.data, other.buffer_.data); - std::swap(buffer_.size, other.buffer_.size); - } - - - OrthancPluginMemoryBuffer MemoryBuffer::Release() - { - OrthancPluginMemoryBuffer result = buffer_; - - buffer_.data = NULL; - buffer_.size = 0; - - return result; - } - - - void MemoryBuffer::ToString(std::string& target) const - { - if (buffer_.size == 0) - { - target.clear(); - } - else - { - target.assign(reinterpret_cast(buffer_.data), buffer_.size); - } - } - - - void MemoryBuffer::ToJson(Json::Value& target) const - { - if (buffer_.data == NULL || - buffer_.size == 0) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - const char* tmp = reinterpret_cast(buffer_.data); - - Json::Reader reader; - if (!reader.parse(tmp, tmp + buffer_.size, target)) - { - LogError("Cannot convert some memory buffer to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - bool MemoryBuffer::RestApiGet(const std::string& uri, - bool applyPlugins) - { - Clear(); - - if (applyPlugins) - { - return CheckHttp(OrthancPluginRestApiGetAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str())); - } - else - { - return CheckHttp(OrthancPluginRestApiGet(GetGlobalContext(), &buffer_, uri.c_str())); - } - } - - bool MemoryBuffer::RestApiGet(const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins) - { - Clear(); - - std::vector headersKeys; - std::vector headersValues; - - for (std::map::const_iterator - it = httpHeaders.begin(); it != httpHeaders.end(); it++) - { - headersKeys.push_back(it->first.c_str()); - headersValues.push_back(it->second.c_str()); - } - - return CheckHttp(OrthancPluginRestApiGet2( - GetGlobalContext(), &buffer_, uri.c_str(), httpHeaders.size(), - (headersKeys.empty() ? NULL : &headersKeys[0]), - (headersValues.empty() ? NULL : &headersValues[0]), applyPlugins)); - } - - bool MemoryBuffer::RestApiPost(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - Clear(); - - // Cast for compatibility with Orthanc SDK <= 1.5.6 - const char* b = reinterpret_cast(body); - - if (applyPlugins) - { - return CheckHttp(OrthancPluginRestApiPostAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - else - { - return CheckHttp(OrthancPluginRestApiPost(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - } - - - bool MemoryBuffer::RestApiPut(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - Clear(); - - // Cast for compatibility with Orthanc SDK <= 1.5.6 - const char* b = reinterpret_cast(body); - - if (applyPlugins) - { - return CheckHttp(OrthancPluginRestApiPutAfterPlugins(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - else - { - return CheckHttp(OrthancPluginRestApiPut(GetGlobalContext(), &buffer_, uri.c_str(), b, bodySize)); - } - } - - - bool MemoryBuffer::RestApiPost(const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPost(uri, writer.write(body), applyPlugins); - } - - - bool MemoryBuffer::RestApiPut(const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPut(uri, writer.write(body), applyPlugins); - } - - - void MemoryBuffer::CreateDicom(const Json::Value& tags, - OrthancPluginCreateDicomFlags flags) - { - Clear(); - - Json::FastWriter writer; - std::string s = writer.write(tags); - - Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), NULL, flags)); - } - - void MemoryBuffer::CreateDicom(const Json::Value& tags, - const OrthancImage& pixelData, - OrthancPluginCreateDicomFlags flags) - { - Clear(); - - Json::FastWriter writer; - std::string s = writer.write(tags); - - Check(OrthancPluginCreateDicom(GetGlobalContext(), &buffer_, s.c_str(), pixelData.GetObject(), flags)); - } - - - void MemoryBuffer::ReadFile(const std::string& path) - { - Clear(); - Check(OrthancPluginReadFile(GetGlobalContext(), &buffer_, path.c_str())); - } - - - void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query) - { - Clear(); - Check(OrthancPluginWorklistGetDicomQuery(GetGlobalContext(), &buffer_, query)); - } - - - void OrthancString::Assign(char* str) - { - Clear(); - - if (str != NULL) - { - str_ = str; - } - } - - - void OrthancString::Clear() - { - if (str_ != NULL) - { - OrthancPluginFreeString(GetGlobalContext(), str_); - str_ = NULL; - } - } - - - void OrthancString::ToString(std::string& target) const - { - if (str_ == NULL) - { - target.clear(); - } - else - { - target.assign(str_); - } - } - - - void OrthancString::ToJson(Json::Value& target) const - { - if (str_ == NULL) - { - LogError("Cannot convert an empty memory buffer to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - Json::Reader reader; - if (!reader.parse(str_, target)) - { - LogError("Cannot convert some memory buffer to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - void MemoryBuffer::DicomToJson(Json::Value& target, - OrthancPluginDicomToJsonFormat format, - OrthancPluginDicomToJsonFlags flags, - uint32_t maxStringLength) - { - OrthancString str; - str.Assign(OrthancPluginDicomBufferToJson - (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength)); - str.ToJson(target); - } - - - bool MemoryBuffer::HttpGet(const std::string& url, - const std::string& username, - const std::string& password) - { - Clear(); - return CheckHttp(OrthancPluginHttpGet(GetGlobalContext(), &buffer_, url.c_str(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str())); - } - - - bool MemoryBuffer::HttpPost(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password) - { - Clear(); - return CheckHttp(OrthancPluginHttpPost(GetGlobalContext(), &buffer_, url.c_str(), - body.c_str(), body.size(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str())); - } - - - bool MemoryBuffer::HttpPut(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password) - { - Clear(); - return CheckHttp(OrthancPluginHttpPut(GetGlobalContext(), &buffer_, url.c_str(), - body.empty() ? NULL : body.c_str(), - body.size(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str())); - } - - - void MemoryBuffer::GetDicomInstance(const std::string& instanceId) - { - Clear(); - Check(OrthancPluginGetDicomForInstance(GetGlobalContext(), &buffer_, instanceId.c_str())); - } - - - bool HttpDelete(const std::string& url, - const std::string& username, - const std::string& password) - { - OrthancPluginErrorCode error = OrthancPluginHttpDelete - (GetGlobalContext(), url.c_str(), - username.empty() ? NULL : username.c_str(), - password.empty() ? NULL : password.c_str()); - - if (error == OrthancPluginErrorCode_Success) - { - return true; - } - else if (error == OrthancPluginErrorCode_UnknownResource || - error == OrthancPluginErrorCode_InexistentItem) - { - return false; - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - } - - - void LogError(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogError(GetGlobalContext(), message.c_str()); - } - } - - - void LogWarning(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogWarning(GetGlobalContext(), message.c_str()); - } - } - - - void LogInfo(const std::string& message) - { - if (HasGlobalContext()) - { - OrthancPluginLogInfo(GetGlobalContext(), message.c_str()); - } - } - - - void OrthancConfiguration::LoadConfiguration() - { - OrthancString str; - str.Assign(OrthancPluginGetConfiguration(GetGlobalContext())); - - if (str.GetContent() == NULL) - { - LogError("Cannot access the Orthanc configuration"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - str.ToJson(configuration_); - - if (configuration_.type() != Json::objectValue) - { - LogError("Unable to read the Orthanc configuration"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - - OrthancConfiguration::OrthancConfiguration() - { - LoadConfiguration(); - } - - - OrthancConfiguration::OrthancConfiguration(bool loadConfiguration) - { - if (loadConfiguration) - { - LoadConfiguration(); - } - else - { - configuration_ = Json::objectValue; - } - } - - - std::string OrthancConfiguration::GetPath(const std::string& key) const - { - if (path_.empty()) - { - return key; - } - else - { - return path_ + "." + key; - } - } - - - bool OrthancConfiguration::IsSection(const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - return (configuration_.isMember(key) && - configuration_[key].type() == Json::objectValue); - } - - - void OrthancConfiguration::GetSection(OrthancConfiguration& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - target.path_ = GetPath(key); - - if (!configuration_.isMember(key)) - { - target.configuration_ = Json::objectValue; - } - else - { - if (configuration_[key].type() != Json::objectValue) - { - LogError("The configuration section \"" + target.path_ + - "\" is not an associative array as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - target.configuration_ = configuration_[key]; - } - } - - - bool OrthancConfiguration::LookupStringValue(std::string& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - if (configuration_[key].type() != Json::stringValue) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a string as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - target = configuration_[key].asString(); - return true; - } - - - bool OrthancConfiguration::LookupIntegerValue(int& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - switch (configuration_[key].type()) - { - case Json::intValue: - target = configuration_[key].asInt(); - return true; - - case Json::uintValue: - target = configuration_[key].asUInt(); - return true; - - default: - LogError("The configuration option \"" + GetPath(key) + - "\" is not an integer as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target, - const std::string& key) const - { - int tmp; - if (!LookupIntegerValue(tmp, key)) - { - return false; - } - - if (tmp < 0) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a positive integer as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - else - { - target = static_cast(tmp); - return true; - } - } - - - bool OrthancConfiguration::LookupBooleanValue(bool& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - if (configuration_[key].type() != Json::booleanValue) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a Boolean as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - target = configuration_[key].asBool(); - return true; - } - - - bool OrthancConfiguration::LookupFloatValue(float& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - if (!configuration_.isMember(key)) - { - return false; - } - - switch (configuration_[key].type()) - { - case Json::realValue: - target = configuration_[key].asFloat(); - return true; - - case Json::intValue: - target = static_cast(configuration_[key].asInt()); - return true; - - case Json::uintValue: - target = static_cast(configuration_[key].asUInt()); - return true; - - default: - LogError("The configuration option \"" + GetPath(key) + - "\" is not an integer as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - bool OrthancConfiguration::LookupListOfStrings(std::list& target, - const std::string& key, - bool allowSingleString) const - { - assert(configuration_.type() == Json::objectValue); - - target.clear(); - - if (!configuration_.isMember(key)) - { - return false; - } - - switch (configuration_[key].type()) - { - case Json::arrayValue: - { - bool ok = true; - - for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++) - { - if (configuration_[key][i].type() == Json::stringValue) - { - target.push_back(configuration_[key][i].asString()); - } - else - { - ok = false; - } - } - - if (ok) - { - return true; - } - - break; - } - - case Json::stringValue: - if (allowSingleString) - { - target.push_back(configuration_[key].asString()); - return true; - } - - break; - - default: - break; - } - - LogError("The configuration option \"" + GetPath(key) + - "\" is not a list of strings as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - - bool OrthancConfiguration::LookupSetOfStrings(std::set& target, - const std::string& key, - bool allowSingleString) const - { - std::list lst; - - if (LookupListOfStrings(lst, key, allowSingleString)) - { - target.clear(); - - for (std::list::const_iterator - it = lst.begin(); it != lst.end(); ++it) - { - target.insert(*it); - } - - return true; - } - else - { - return false; - } - } - - - std::string OrthancConfiguration::GetStringValue(const std::string& key, - const std::string& defaultValue) const - { - std::string tmp; - if (LookupStringValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - int OrthancConfiguration::GetIntegerValue(const std::string& key, - int defaultValue) const - { - int tmp; - if (LookupIntegerValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key, - unsigned int defaultValue) const - { - unsigned int tmp; - if (LookupUnsignedIntegerValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - bool OrthancConfiguration::GetBooleanValue(const std::string& key, - bool defaultValue) const - { - bool tmp; - if (LookupBooleanValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - float OrthancConfiguration::GetFloatValue(const std::string& key, - float defaultValue) const - { - float tmp; - if (LookupFloatValue(tmp, key)) - { - return tmp; - } - else - { - return defaultValue; - } - } - - - void OrthancConfiguration::GetDictionary(std::map& target, - const std::string& key) const - { - assert(configuration_.type() == Json::objectValue); - - target.clear(); - - if (!configuration_.isMember(key)) - { - return; - } - - if (configuration_[key].type() != Json::objectValue) - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a string as expected"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - - Json::Value::Members members = configuration_[key].getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& value = configuration_[key][members[i]]; - - if (value.type() == Json::stringValue) - { - target[members[i]] = value.asString(); - } - else - { - LogError("The configuration option \"" + GetPath(key) + - "\" is not a dictionary mapping strings to strings"); - - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - } - - - void OrthancImage::Clear() - { - if (image_ != NULL) - { - OrthancPluginFreeImage(GetGlobalContext(), image_); - image_ = NULL; - } - } - - - void OrthancImage::CheckImageAvailable() const - { - if (image_ == NULL) - { - LogError("Trying to access a NULL image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - OrthancImage::OrthancImage() : - image_(NULL) - { - } - - - OrthancImage::OrthancImage(OrthancPluginImage* image) : - image_(image) - { - } - - - OrthancImage::OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height) - { - image_ = OrthancPluginCreateImage(GetGlobalContext(), format, width, height); - - if (image_ == NULL) - { - LogError("Cannot create an image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - - OrthancImage::OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - void* buffer) - { - image_ = OrthancPluginCreateImageAccessor - (GetGlobalContext(), format, width, height, pitch, buffer); - - if (image_ == NULL) - { - LogError("Cannot create an image accessor"); - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - void OrthancImage::UncompressPngImage(const void* data, - size_t size) - { - Clear(); - - image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Png); - - if (image_ == NULL) - { - LogError("Cannot uncompress a PNG image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - void OrthancImage::UncompressJpegImage(const void* data, - size_t size) - { - Clear(); - image_ = OrthancPluginUncompressImage(GetGlobalContext(), data, size, OrthancPluginImageFormat_Jpeg); - if (image_ == NULL) - { - LogError("Cannot uncompress a JPEG image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - void OrthancImage::DecodeDicomImage(const void* data, - size_t size, - unsigned int frame) - { - Clear(); - image_ = OrthancPluginDecodeDicomImage(GetGlobalContext(), data, size, frame); - if (image_ == NULL) - { - LogError("Cannot uncompress a DICOM image"); - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const - { - CheckImageAvailable(); - return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_); - } - - - unsigned int OrthancImage::GetWidth() const - { - CheckImageAvailable(); - return OrthancPluginGetImageWidth(GetGlobalContext(), image_); - } - - - unsigned int OrthancImage::GetHeight() const - { - CheckImageAvailable(); - return OrthancPluginGetImageHeight(GetGlobalContext(), image_); - } - - - unsigned int OrthancImage::GetPitch() const - { - CheckImageAvailable(); - return OrthancPluginGetImagePitch(GetGlobalContext(), image_); - } - - - void* OrthancImage::GetBuffer() const - { - CheckImageAvailable(); - return OrthancPluginGetImageBuffer(GetGlobalContext(), image_); - } - - - void OrthancImage::CompressPngImage(MemoryBuffer& target) const - { - CheckImageAvailable(); - - OrthancPlugins::MemoryBuffer answer; - OrthancPluginCompressPngImage(GetGlobalContext(), *answer, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer()); - - target.Swap(answer); - } - - - void OrthancImage::CompressJpegImage(MemoryBuffer& target, - uint8_t quality) const - { - CheckImageAvailable(); - - OrthancPlugins::MemoryBuffer answer; - OrthancPluginCompressJpegImage(GetGlobalContext(), *answer, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); - - target.Swap(answer); - } - - - void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const - { - CheckImageAvailable(); - OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer()); - } - - - void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, - uint8_t quality) const - { - CheckImageAvailable(); - OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(), - GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality); - } - - - OrthancPluginImage* OrthancImage::Release() - { - CheckImageAvailable(); - OrthancPluginImage* tmp = image_; - image_ = NULL; - return tmp; - } - - -#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 - FindMatcher::FindMatcher(const OrthancPluginWorklistQuery* worklist) : - matcher_(NULL), - worklist_(worklist) - { - if (worklist_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); - } - } - - - void FindMatcher::SetupDicom(const void* query, - uint32_t size) - { - worklist_ = NULL; - - matcher_ = OrthancPluginCreateFindMatcher(GetGlobalContext(), query, size); - if (matcher_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - - - FindMatcher::~FindMatcher() - { - // The "worklist_" field - - if (matcher_ != NULL) - { - OrthancPluginFreeFindMatcher(GetGlobalContext(), matcher_); - } - } - - - - bool FindMatcher::IsMatch(const void* dicom, - uint32_t size) const - { - int32_t result; - - if (matcher_ != NULL) - { - result = OrthancPluginFindMatcherIsMatch(GetGlobalContext(), matcher_, dicom, size); - } - else if (worklist_ != NULL) - { - result = OrthancPluginWorklistIsMatch(GetGlobalContext(), worklist_, dicom, size); - } - else - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - if (result == 0) - { - return false; - } - else if (result == 1) - { - return true; - } - else - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - } - -#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ - - void AnswerJson(const Json::Value& value, - OrthancPluginRestOutput* output - ) - { - Json::StyledWriter writer; - std::string bodyString = writer.write(value); - - OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json"); - } - - void AnswerString(const std::string& answer, - const char* mimeType, - OrthancPluginRestOutput* output - ) - { - OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType); - } - - void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput *output) - { - OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError); - } - - void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods); - } - - bool RestApiGetString(std::string& result, - const std::string& uri, - bool applyPlugins) - { - MemoryBuffer answer; - if (!answer.RestApiGet(uri, applyPlugins)) - { - return false; - } - else - { - answer.ToString(result); - return true; - } - } - - bool RestApiGetString(std::string& result, - const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins) - { - MemoryBuffer answer; - if (!answer.RestApiGet(uri, httpHeaders, applyPlugins)) - { - return false; - } - else - { - answer.ToString(result); - return true; - } - } - - - - bool RestApiGet(Json::Value& result, - const std::string& uri, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiGet(uri, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) - { - answer.ToJson(result); - } - return true; - } - } - - - bool RestApiPost(std::string& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) - { - result.assign(answer.GetData(), answer.GetSize()); - } - return true; - } - } - - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiPost(uri, body, bodySize, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) - { - answer.ToJson(result); - } - return true; - } - } - - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPost(result, uri, writer.write(body), applyPlugins); - } - - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins) - { - MemoryBuffer answer; - - if (!answer.RestApiPut(uri, body, bodySize, applyPlugins)) - { - return false; - } - else - { - if (!answer.IsEmpty()) // i.e, on a PUT to metadata/..., orthanc returns an empty response - { - answer.ToJson(result); - } - return true; - } - } - - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins) - { - Json::FastWriter writer; - return RestApiPut(result, uri, writer.write(body), applyPlugins); - } - - - bool RestApiDelete(const std::string& uri, - bool applyPlugins) - { - OrthancPluginErrorCode error; - - if (applyPlugins) - { - error = OrthancPluginRestApiDeleteAfterPlugins(GetGlobalContext(), uri.c_str()); - } - else - { - error = OrthancPluginRestApiDelete(GetGlobalContext(), uri.c_str()); - } - - if (error == OrthancPluginErrorCode_Success) - { - return true; - } - else if (error == OrthancPluginErrorCode_UnknownResource || - error == OrthancPluginErrorCode_InexistentItem) - { - return false; - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - } - - - void ReportMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision) - { - LogError("Your version of the Orthanc core (" + - std::string(GetGlobalContext()->orthancVersion) + - ") is too old to run this plugin (version " + - boost::lexical_cast(major) + "." + - boost::lexical_cast(minor) + "." + - boost::lexical_cast(revision) + - " is required)"); - } - - - bool CheckMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision) - { - if (!HasGlobalContext()) - { - LogError("Bad Orthanc context in the plugin"); - return false; - } - - if (!strcmp(GetGlobalContext()->orthancVersion, "mainline")) - { - // Assume compatibility with the mainline - return true; - } - - // Parse the version of the Orthanc core - int aa, bb, cc; - if ( -#ifdef _MSC_VER - sscanf_s -#else - sscanf -#endif - (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 || - aa < 0 || - bb < 0 || - cc < 0) - { - return false; - } - - unsigned int a = static_cast(aa); - unsigned int b = static_cast(bb); - unsigned int c = static_cast(cc); - - // Check the major version number - - if (a > major) - { - return true; - } - - if (a < major) - { - return false; - } - - - // Check the minor version number - assert(a == major); - - if (b > minor) - { - return true; - } - - if (b < minor) - { - return false; - } - - // Check the patch level version number - assert(a == major && b == minor); - - if (c >= revision) - { - return true; - } - else - { - return false; - } - } - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) - const char* AutodetectMimeType(const std::string& path) - { - const char* mime = OrthancPluginAutodetectMimeType(GetGlobalContext(), path.c_str()); - - if (mime == NULL) - { - // Should never happen, just for safety - return "application/octet-stream"; - } - else - { - return mime; - } - } -#endif - - -#if HAS_ORTHANC_PLUGIN_PEERS == 1 - size_t OrthancPeers::GetPeerIndex(const std::string& name) const - { - size_t index; - if (LookupName(index, name)) - { - return index; - } - else - { - LogError("Inexistent peer: " + name); - ORTHANC_PLUGINS_THROW_EXCEPTION(UnknownResource); - } - } - - - OrthancPeers::OrthancPeers() : - peers_(NULL), - timeout_(0) - { - peers_ = OrthancPluginGetPeers(GetGlobalContext()); - - if (peers_ == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - - uint32_t count = OrthancPluginGetPeersCount(GetGlobalContext(), peers_); - - for (uint32_t i = 0; i < count; i++) - { - const char* name = OrthancPluginGetPeerName(GetGlobalContext(), peers_, i); - if (name == NULL) - { - OrthancPluginFreePeers(GetGlobalContext(), peers_); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - - index_[name] = i; - } - } - - - OrthancPeers::~OrthancPeers() - { - if (peers_ != NULL) - { - OrthancPluginFreePeers(GetGlobalContext(), peers_); - } - } - - - bool OrthancPeers::LookupName(size_t& target, - const std::string& name) const - { - Index::const_iterator found = index_.find(name); - - if (found == index_.end()) - { - return false; - } - else - { - target = found->second; - return true; - } - } - - - std::string OrthancPeers::GetPeerName(size_t index) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - else - { - const char* s = OrthancPluginGetPeerName(GetGlobalContext(), peers_, static_cast(index)); - if (s == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - return s; - } - } - } - - - std::string OrthancPeers::GetPeerUrl(size_t index) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - else - { - const char* s = OrthancPluginGetPeerUrl(GetGlobalContext(), peers_, static_cast(index)); - if (s == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - return s; - } - } - } - - - std::string OrthancPeers::GetPeerUrl(const std::string& name) const - { - return GetPeerUrl(GetPeerIndex(name)); - } - - - bool OrthancPeers::LookupUserProperty(std::string& value, - size_t index, - const std::string& key) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - else - { - const char* s = OrthancPluginGetPeerUserProperty(GetGlobalContext(), peers_, static_cast(index), key.c_str()); - if (s == NULL) - { - return false; - } - else - { - value.assign(s); - return true; - } - } - } - - - bool OrthancPeers::LookupUserProperty(std::string& value, - const std::string& peer, - const std::string& key) const - { - return LookupUserProperty(value, GetPeerIndex(peer), key); - } - - - bool OrthancPeers::DoGet(MemoryBuffer& target, - size_t index, - const std::string& uri) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Get, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - target.Swap(answer); - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoGet(MemoryBuffer& target, - const std::string& name, - const std::string& uri) const - { - size_t index; - return (LookupName(index, name) && - DoGet(target, index, uri)); - } - - - bool OrthancPeers::DoGet(Json::Value& target, - size_t index, - const std::string& uri) const - { - MemoryBuffer buffer; - - if (DoGet(buffer, index, uri)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoGet(Json::Value& target, - const std::string& name, - const std::string& uri) const - { - MemoryBuffer buffer; - - if (DoGet(buffer, name, uri)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPost(MemoryBuffer& target, - const std::string& name, - const std::string& uri, - const std::string& body) const - { - size_t index; - return (LookupName(index, name) && - DoPost(target, index, uri, body)); - } - - - bool OrthancPeers::DoPost(Json::Value& target, - size_t index, - const std::string& uri, - const std::string& body) const - { - MemoryBuffer buffer; - - if (DoPost(buffer, index, uri, body)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPost(Json::Value& target, - const std::string& name, - const std::string& uri, - const std::string& body) const - { - MemoryBuffer buffer; - - if (DoPost(buffer, name, uri, body)) - { - buffer.ToJson(target); - return true; - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPost(MemoryBuffer& target, - size_t index, - const std::string& uri, - const std::string& body) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Post, uri.c_str(), - 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - target.Swap(answer); - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPut(size_t index, - const std::string& uri, - const std::string& body) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Put, uri.c_str(), - 0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoPut(const std::string& name, - const std::string& uri, - const std::string& body) const - { - size_t index; - return (LookupName(index, name) && - DoPut(index, uri, body)); - } - - - bool OrthancPeers::DoDelete(size_t index, - const std::string& uri) const - { - if (index >= index_.size()) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - OrthancPlugins::MemoryBuffer answer; - uint16_t status; - OrthancPluginErrorCode code = OrthancPluginCallPeerApi - (GetGlobalContext(), *answer, NULL, &status, peers_, - static_cast(index), OrthancPluginHttpMethod_Delete, uri.c_str(), - 0, NULL, NULL, NULL, 0, timeout_); - - if (code == OrthancPluginErrorCode_Success) - { - return (status == 200); - } - else - { - return false; - } - } - - - bool OrthancPeers::DoDelete(const std::string& name, - const std::string& uri) const - { - size_t index; - return (LookupName(index, name) && - DoDelete(index, uri)); - } -#endif - - - - - - /****************************************************************** - ** JOBS - ******************************************************************/ - -#if HAS_ORTHANC_PLUGIN_JOB == 1 - void OrthancJob::CallbackFinalize(void* job) - { - if (job != NULL) - { - delete reinterpret_cast(job); - } - } - - - float OrthancJob::CallbackGetProgress(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast(job)->progress_; - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetContent(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast(job)->content_.c_str(); - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetSerialized(void* job) - { - assert(job != NULL); - - try - { - const OrthancJob& tmp = *reinterpret_cast(job); - - if (tmp.hasSerialized_) - { - return tmp.serialized_.c_str(); - } - else - { - return NULL; - } - } - catch (...) - { - return 0; - } - } - - - OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast(job)->Step(); - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&) - { - return OrthancPluginJobStepStatus_Failure; - } - catch (...) - { - return OrthancPluginJobStepStatus_Failure; - } - } - - - OrthancPluginErrorCode OrthancJob::CallbackStop(void* job, - OrthancPluginJobStopReason reason) - { - assert(job != NULL); - - try - { - reinterpret_cast(job)->Stop(reason); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) - { - assert(job != NULL); - - try - { - reinterpret_cast(job)->Reset(); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - void OrthancJob::ClearContent() - { - Json::Value empty = Json::objectValue; - UpdateContent(empty); - } - - - void OrthancJob::UpdateContent(const Json::Value& content) - { - if (content.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); - } - else - { - Json::FastWriter writer; - content_ = writer.write(content); - } - } - - - void OrthancJob::ClearSerialized() - { - hasSerialized_ = false; - serialized_.clear(); - } - - - void OrthancJob::UpdateSerialized(const Json::Value& serialized) - { - if (serialized.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat); - } - else - { - Json::FastWriter writer; - serialized_ = writer.write(serialized); - hasSerialized_ = true; - } - } - - - void OrthancJob::UpdateProgress(float progress) - { - if (progress < 0 || - progress > 1) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); - } - - progress_ = progress; - } - - - OrthancJob::OrthancJob(const std::string& jobType) : - jobType_(jobType), - progress_(0) - { - ClearContent(); - ClearSerialized(); - } - - - OrthancPluginJob* OrthancJob::Create(OrthancJob* job) - { - if (job == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); - } - - OrthancPluginJob* orthanc = OrthancPluginCreateJob( - GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(), - CallbackGetProgress, CallbackGetContent, CallbackGetSerialized, - CallbackStep, CallbackStop, CallbackReset); - - if (orthanc == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - return orthanc; - } - } - - - std::string OrthancJob::Submit(OrthancJob* job, - int priority) - { - if (job == NULL) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer); - } - - OrthancPluginJob* orthanc = Create(job); - - char* id = OrthancPluginSubmitJob(GetGlobalContext(), orthanc, priority); - - if (id == NULL) - { - LogError("Plugin cannot submit job"); - OrthancPluginFreeJob(GetGlobalContext(), orthanc); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_Plugin); - } - else - { - std::string tmp(id); - tmp.assign(id); - OrthancPluginFreeString(GetGlobalContext(), id); - - return tmp; - } - } - - - void OrthancJob::SubmitAndWait(Json::Value& result, - OrthancJob* job /* takes ownership */, - int priority) - { - std::string id = Submit(job, priority); - - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - Json::Value status; - if (!RestApiGet(status, "/jobs/" + id, false) || - !status.isMember("State") || - status["State"].type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InexistentItem); - } - - const std::string state = status["State"].asString(); - if (state == "Success") - { - if (status.isMember("Content")) - { - result = status["Content"]; - } - else - { - result = Json::objectValue; - } - - return; - } - else if (state == "Running") - { - continue; - } - else if (!status.isMember("ErrorCode") || - status["ErrorCode"].type() != Json::intValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError); - } - else - { - if (!status.isMember("ErrorDescription") || - status["ErrorDescription"].type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); - } - else - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(static_cast(status["ErrorCode"].asInt()), - status["ErrorDescription"].asString()); -#else - LogError("Exception while executing the job: " + status["ErrorDescription"].asString()); - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt()); -#endif - } - } - } - } - - - void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output, - const Json::Value& body, - OrthancJob* job) - { - static const char* KEY_SYNCHRONOUS = "Synchronous"; - static const char* KEY_ASYNCHRONOUS = "Asynchronous"; - static const char* KEY_PRIORITY = "Priority"; - - boost::movelib::unique_ptr protection(job); - - if (body.type() != Json::objectValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Expected a JSON object in the body"); -#else - LogError("Expected a JSON object in the body"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - - bool synchronous = true; - - if (body.isMember(KEY_SYNCHRONOUS)) - { - if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Option \"" + std::string(KEY_SYNCHRONOUS) + - "\" must be Boolean"); -#else - LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - else - { - synchronous = body[KEY_SYNCHRONOUS].asBool(); - } - } - - if (body.isMember(KEY_ASYNCHRONOUS)) - { - if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Option \"" + std::string(KEY_ASYNCHRONOUS) + - "\" must be Boolean"); -#else - LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - else - { - synchronous = !body[KEY_ASYNCHRONOUS].asBool(); - } - } - - int priority = 0; - - if (body.isMember(KEY_PRIORITY)) - { - if (body[KEY_PRIORITY].type() != Json::booleanValue) - { -#if HAS_ORTHANC_EXCEPTION == 1 - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Option \"" + std::string(KEY_PRIORITY) + - "\" must be an integer"); -#else - LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); -#endif - } - else - { - priority = !body[KEY_PRIORITY].asInt(); - } - } - - Json::Value result; - - if (synchronous) - { - OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority); - } - else - { - std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority); - - result = Json::objectValue; - result["ID"] = id; - result["Path"] = "/jobs/" + id; - } - - std::string s = result.toStyledString(); - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), - s.size(), "application/json"); - } - -#endif - - - - - /****************************************************************** - ** METRICS - ******************************************************************/ - -#if HAS_ORTHANC_PLUGIN_METRICS == 1 - MetricsTimer::MetricsTimer(const char* name) : - name_(name) - { - start_ = boost::posix_time::microsec_clock::universal_time(); - } - - MetricsTimer::~MetricsTimer() - { - const boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time(); - const boost::posix_time::time_duration diff = stop - start_; - OrthancPluginSetMetricsValue(GetGlobalContext(), name_.c_str(), static_cast(diff.total_milliseconds()), - OrthancPluginMetricsType_Timer); - } -#endif - - - - - /****************************************************************** - ** HTTP CLIENT - ******************************************************************/ - -#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 - class HttpClient::RequestBodyWrapper : public boost::noncopyable - { - private: - static RequestBodyWrapper& GetObject(void* body) - { - assert(body != NULL); - return *reinterpret_cast(body); - } - - IRequestBody& body_; - bool done_; - std::string chunk_; - - public: - RequestBodyWrapper(IRequestBody& body) : - body_(body), - done_(false) - { - } - - static uint8_t IsDone(void* body) - { - return GetObject(body).done_; - } - - static const void* GetChunkData(void* body) - { - return GetObject(body).chunk_.c_str(); - } - - static uint32_t GetChunkSize(void* body) - { - return static_cast(GetObject(body).chunk_.size()); - } - - static OrthancPluginErrorCode Next(void* body) - { - RequestBodyWrapper& that = GetObject(body); - - if (that.done_) - { - return OrthancPluginErrorCode_BadSequenceOfCalls; - } - else - { - try - { - that.done_ = !that.body_.ReadNextChunk(that.chunk_); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_InternalError; - } - } - } - }; - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer, - const char* key, - const char* value) - { - assert(answer != NULL && key != NULL && value != NULL); - - try - { - reinterpret_cast(answer)->AddHeader(key, value); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer, - const void* data, - uint32_t size) - { - assert(answer != NULL); - - try - { - reinterpret_cast(answer)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - - - HttpClient::HttpClient() : - httpStatus_(0), - method_(OrthancPluginHttpMethod_Get), - timeout_(0), - pkcs11_(false), - chunkedBody_(NULL), - allowChunkedTransfers_(true) - { - } - - - void HttpClient::AddHeaders(const HttpHeaders& headers) - { - for (HttpHeaders::const_iterator it = headers.begin(); - it != headers.end(); ++it) - { - headers_[it->first] = it->second; - } - } - - - void HttpClient::SetCredentials(const std::string& username, - const std::string& password) - { - username_ = username; - password_ = password; - } - - - void HttpClient::ClearCredentials() - { - username_.empty(); - password_.empty(); - } - - - void HttpClient::SetCertificate(const std::string& certificateFile, - const std::string& keyFile, - const std::string& keyPassword) - { - certificateFile_ = certificateFile; - certificateKeyFile_ = keyFile; - certificateKeyPassword_ = keyPassword; - } - - - void HttpClient::ClearCertificate() - { - certificateFile_.clear(); - certificateKeyFile_.clear(); - certificateKeyPassword_.clear(); - } - - - void HttpClient::ClearBody() - { - fullBody_.clear(); - chunkedBody_ = NULL; - } - - - void HttpClient::SwapBody(std::string& body) - { - fullBody_.swap(body); - chunkedBody_ = NULL; - } - - - void HttpClient::SetBody(const std::string& body) - { - fullBody_ = body; - chunkedBody_ = NULL; - } - - - void HttpClient::SetBody(IRequestBody& body) - { - fullBody_.clear(); - chunkedBody_ = &body; - } - - - namespace - { - class HeadersWrapper : public boost::noncopyable - { - private: - std::vector headersKeys_; - std::vector headersValues_; - - public: - HeadersWrapper(const HttpClient::HttpHeaders& headers) - { - headersKeys_.reserve(headers.size()); - headersValues_.reserve(headers.size()); - - for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) - { - headersKeys_.push_back(it->first.c_str()); - headersValues_.push_back(it->second.c_str()); - } - } - - void AddStaticString(const char* key, - const char* value) - { - headersKeys_.push_back(key); - headersValues_.push_back(value); - } - - uint32_t GetCount() const - { - return headersKeys_.size(); - } - - const char* const* GetKeys() const - { - return headersKeys_.empty() ? NULL : &headersKeys_[0]; - } - - const char* const* GetValues() const - { - return headersValues_.empty() ? NULL : &headersValues_[0]; - } - }; - - - class MemoryRequestBody : public HttpClient::IRequestBody - { - private: - std::string body_; - bool done_; - - public: - MemoryRequestBody(const std::string& body) : - body_(body), - done_(false) - { - if (body_.empty()) - { - done_ = true; - } - } - - virtual bool ReadNextChunk(std::string& chunk) - { - if (done_) - { - return false; - } - else - { - chunk.swap(body_); - done_ = true; - return true; - } - } - }; - - - // This class mimics Orthanc::ChunkedBuffer - class ChunkedBuffer : public boost::noncopyable - { - private: - typedef std::list Content; - - Content content_; - size_t size_; - - public: - ChunkedBuffer() : - size_(0) - { - } - - ~ChunkedBuffer() - { - Clear(); - } - - void Clear() - { - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - content_.clear(); - } - - void Flatten(std::string& target) const - { - target.resize(size_); - - size_t pos = 0; - - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(*it != NULL); - size_t s = (*it)->size(); - - if (s != 0) - { - memcpy(&target[pos], (*it)->c_str(), s); - pos += s; - } - } - - assert(size_ == 0 || - pos == target.size()); - } - - void AddChunk(const void* data, - size_t size) - { - content_.push_back(new std::string(reinterpret_cast(data), size)); - size_ += size; - } - - void AddChunk(const std::string& chunk) - { - content_.push_back(new std::string(chunk)); - size_ += chunk.size(); - } - }; - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - class MemoryAnswer : public HttpClient::IAnswer - { - private: - HttpClient::HttpHeaders headers_; - ChunkedBuffer body_; - - public: - const HttpClient::HttpHeaders& GetHeaders() const - { - return headers_; - } - - const ChunkedBuffer& GetBody() const - { - return body_; - } - - virtual void AddHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - virtual void AddChunk(const void* data, - size_t size) - { - body_.AddChunk(data, size); - } - }; -#endif - } - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - void HttpClient::ExecuteWithStream(uint16_t& httpStatus, - IAnswer& answer, - IRequestBody& body) const - { - HeadersWrapper h(headers_); - - if (method_ == OrthancPluginHttpMethod_Post || - method_ == OrthancPluginHttpMethod_Put) - { - // Automatically set the "Transfer-Encoding" header if absent - bool found = false; - - for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) - { - if (boost::iequals(it->first, "Transfer-Encoding")) - { - found = true; - break; - } - } - - if (!found) - { - h.AddStaticString("Transfer-Encoding", "chunked"); - } - } - - RequestBodyWrapper request(body); - - OrthancPluginErrorCode error = OrthancPluginChunkedHttpClient( - GetGlobalContext(), - &answer, - AnswerAddChunkCallback, - AnswerAddHeaderCallback, - &httpStatus, - method_, - url_.c_str(), - h.GetCount(), - h.GetKeys(), - h.GetValues(), - &request, - RequestBodyWrapper::IsDone, - RequestBodyWrapper::GetChunkData, - RequestBodyWrapper::GetChunkSize, - RequestBodyWrapper::Next, - username_.empty() ? NULL : username_.c_str(), - password_.empty() ? NULL : password_.c_str(), - timeout_, - certificateFile_.empty() ? NULL : certificateFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), - pkcs11_ ? 1 : 0); - - if (error != OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - } -#endif - - - void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, - HttpHeaders& answerHeaders, - std::string& answerBody, - const std::string& body) const - { - HeadersWrapper headers(headers_); - - MemoryBuffer answerBodyBuffer, answerHeadersBuffer; - - OrthancPluginErrorCode error = OrthancPluginHttpClient( - GetGlobalContext(), - *answerBodyBuffer, - *answerHeadersBuffer, - &httpStatus, - method_, - url_.c_str(), - headers.GetCount(), - headers.GetKeys(), - headers.GetValues(), - body.empty() ? NULL : body.c_str(), - body.size(), - username_.empty() ? NULL : username_.c_str(), - password_.empty() ? NULL : password_.c_str(), - timeout_, - certificateFile_.empty() ? NULL : certificateFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), - pkcs11_ ? 1 : 0); - - if (error != OrthancPluginErrorCode_Success) - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); - } - - Json::Value v; - answerHeadersBuffer.ToJson(v); - - if (v.type() != Json::objectValue) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - Json::Value::Members members = v.getMemberNames(); - answerHeaders.clear(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& h = v[members[i]]; - if (h.type() != Json::stringValue) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - else - { - answerHeaders[members[i]] = h.asString(); - } - } - - answerBodyBuffer.ToString(answerBody); - } - - - void HttpClient::Execute(IAnswer& answer) - { -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - if (allowChunkedTransfers_) - { - if (chunkedBody_ != NULL) - { - ExecuteWithStream(httpStatus_, answer, *chunkedBody_); - } - else - { - MemoryRequestBody wrapper(fullBody_); - ExecuteWithStream(httpStatus_, answer, wrapper); - } - - return; - } -#endif - - // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked - // transfers are disabled. This results in higher memory usage - // (all chunks from the answer body are sent at once) - - HttpHeaders answerHeaders; - std::string answerBody; - Execute(answerHeaders, answerBody); - - for (HttpHeaders::const_iterator it = answerHeaders.begin(); - it != answerHeaders.end(); ++it) - { - answer.AddHeader(it->first, it->second); - } - - if (!answerBody.empty()) - { - answer.AddChunk(answerBody.c_str(), answerBody.size()); - } - } - - - void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, - std::string& answerBody /* out */) - { -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - if (allowChunkedTransfers_) - { - MemoryAnswer answer; - Execute(answer); - answerHeaders = answer.GetHeaders(); - answer.GetBody().Flatten(answerBody); - return; - } -#endif - - // Compatibility mode for Orthanc SDK <= 1.5.6 or if chunked - // transfers are disabled. This results in higher memory usage - // (all chunks from the request body are sent at once) - - if (chunkedBody_ != NULL) - { - ChunkedBuffer buffer; - - std::string chunk; - while (chunkedBody_->ReadNextChunk(chunk)) - { - buffer.AddChunk(chunk); - } - - std::string body; - buffer.Flatten(body); - - ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body); - } - else - { - ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, fullBody_); - } - } - - - void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, - Json::Value& answerBody /* out */) - { - std::string body; - Execute(answerHeaders, body); - - Json::Reader reader; - if (!reader.parse(body, answerBody)) - { - LogError("Cannot convert HTTP answer body to JSON"); - ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat); - } - } - - - void HttpClient::Execute() - { - HttpHeaders answerHeaders; - std::string body; - Execute(answerHeaders, body); - } - -#endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ - - - - - - /****************************************************************** - ** CHUNKED HTTP SERVER - ******************************************************************/ - - namespace Internals - { - void NullRestCallback(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) - { - } - - IChunkedRequestReader *NullChunkedRestCallback(const char* url, - const OrthancPluginHttpRequest* request) - { - return NULL; - } - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 - - OrthancPluginErrorCode ChunkedRequestReaderAddChunk( - OrthancPluginServerChunkedRequestReader* reader, - const void* data, - uint32_t size) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - - reinterpret_cast(reader)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - OrthancPluginErrorCode ChunkedRequestReaderExecute( - OrthancPluginServerChunkedRequestReader* reader, - OrthancPluginRestOutput* output) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - - reinterpret_cast(reader)->Execute(output); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - void ChunkedRequestReaderFinalize( - OrthancPluginServerChunkedRequestReader* reader) - { - if (reader != NULL) - { - delete reinterpret_cast(reader); - } - } - -#else - - OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request, - RestCallback GetHandler, - ChunkedRestCallback PostHandler, - RestCallback DeleteHandler, - ChunkedRestCallback PutHandler) - { - try - { - std::string allowed; - - if (GetHandler != Internals::NullRestCallback) - { - allowed += "GET"; - } - - if (PostHandler != Internals::NullChunkedRestCallback) - { - if (!allowed.empty()) - { - allowed += ","; - } - - allowed += "POST"; - } - - if (DeleteHandler != Internals::NullRestCallback) - { - if (!allowed.empty()) - { - allowed += ","; - } - - allowed += "DELETE"; - } - - if (PutHandler != Internals::NullChunkedRestCallback) - { - if (!allowed.empty()) - { - allowed += ","; - } - - allowed += "PUT"; - } - - switch (request->method) - { - case OrthancPluginHttpMethod_Get: - if (GetHandler == Internals::NullRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - GetHandler(output, url, request); - } - - break; - - case OrthancPluginHttpMethod_Post: - if (PostHandler == Internals::NullChunkedRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - boost::movelib::unique_ptr reader(PostHandler(url, request)); - if (reader.get() == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - reader->AddChunk(request->body, request->bodySize); - reader->Execute(output); - } - } - - break; - - case OrthancPluginHttpMethod_Delete: - if (DeleteHandler == Internals::NullRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - DeleteHandler(output, url, request); - } - - break; - - case OrthancPluginHttpMethod_Put: - if (PutHandler == Internals::NullChunkedRestCallback) - { - OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowed.c_str()); - } - else - { - boost::movelib::unique_ptr reader(PutHandler(url, request)); - if (reader.get() == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - reader->AddChunk(request->body, request->bodySize); - reader->Execute(output); - } - } - - break; - - default: - ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); - } - - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { -#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 - if (HasGlobalContext() && - e.HasDetails()) - { - // The "false" instructs Orthanc not to log the detailed - // error message. This is to avoid duplicating the details, - // because "OrthancException" already does it on construction. - OrthancPluginSetHttpErrorDetails - (GetGlobalContext(), output, e.GetDetails(), false); - } -#endif - - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - } - - -#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 - OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup( - OrthancPluginStorageCommitmentFailureReason* target, - void* rawHandler, - const char* sopClassUid, - const char* sopInstanceUid) - { - assert(target != NULL && - rawHandler != NULL); - - try - { - IStorageCommitmentScpHandler& handler = *reinterpret_cast(rawHandler); - *target = handler.Lookup(sopClassUid, sopInstanceUid); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } -#endif - - -#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 - void IStorageCommitmentScpHandler::Destructor(void* rawHandler) - { - assert(rawHandler != NULL); - delete reinterpret_cast(rawHandler); - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - DicomInstance::DicomInstance(const OrthancPluginDicomInstance* instance) : - toFree_(false), - instance_(instance) - { - } -#else - DicomInstance::DicomInstance(OrthancPluginDicomInstance* instance) : - toFree_(false), - instance_(instance) - { - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - DicomInstance::DicomInstance(const void* buffer, - size_t size) : - toFree_(true), - instance_(OrthancPluginCreateDicomInstance(GetGlobalContext(), buffer, size)) - { - if (instance_ == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(NullPointer); - } - } -#endif - - - DicomInstance::~DicomInstance() - { -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - if (toFree_ && - instance_ != NULL) - { - OrthancPluginFreeDicomInstance( - GetGlobalContext(), const_cast(instance_)); - } -#endif - } - - - std::string DicomInstance::GetRemoteAet() const - { - const char* s = OrthancPluginGetInstanceRemoteAet(GetGlobalContext(), instance_); - if (s == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - return std::string(s); - } - } - - - void DicomInstance::GetJson(Json::Value& target) const - { - OrthancString s; - s.Assign(OrthancPluginGetInstanceJson(GetGlobalContext(), instance_)); - s.ToJson(target); - } - - - void DicomInstance::GetSimplifiedJson(Json::Value& target) const - { - OrthancString s; - s.Assign(OrthancPluginGetInstanceSimplifiedJson(GetGlobalContext(), instance_)); - s.ToJson(target); - } - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - std::string DicomInstance::GetTransferSyntaxUid() const - { - OrthancString s; - s.Assign(OrthancPluginGetInstanceTransferSyntaxUid(GetGlobalContext(), instance_)); - - std::string result; - s.ToString(result); - return result; - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - bool DicomInstance::HasPixelData() const - { - int32_t result = OrthancPluginHasInstancePixelData(GetGlobalContext(), instance_); - if (result < 0) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - return (result != 0); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void DicomInstance::GetRawFrame(std::string& target, - unsigned int frameIndex) const - { - MemoryBuffer buffer; - OrthancPluginErrorCode code = OrthancPluginGetInstanceRawFrame( - GetGlobalContext(), *buffer, instance_, frameIndex); - - if (code == OrthancPluginErrorCode_Success) - { - buffer.ToString(target); - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - OrthancImage* DicomInstance::GetDecodedFrame(unsigned int frameIndex) const - { - OrthancPluginImage* image = OrthancPluginGetInstanceDecodedFrame( - GetGlobalContext(), instance_, frameIndex); - - if (image == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - return new OrthancImage(image); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void DicomInstance::Serialize(std::string& target) const - { - MemoryBuffer buffer; - OrthancPluginErrorCode code = OrthancPluginSerializeDicomInstance( - GetGlobalContext(), *buffer, instance_); - - if (code == OrthancPluginErrorCode_Success) - { - buffer.ToString(target); - } - else - { - ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code); - } - } -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - DicomInstance* DicomInstance::Transcode(const void* buffer, - size_t size, - const std::string& transferSyntax) - { - OrthancPluginDicomInstance* instance = OrthancPluginTranscodeDicomInstance( - GetGlobalContext(), buffer, size, transferSyntax.c_str()); - - if (instance == NULL) - { - ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); - } - else - { - boost::movelib::unique_ptr result(new DicomInstance(instance)); - result->toFree_ = true; - return result.release(); - } - } -#endif -} diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h --- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1228 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 . - **/ - - -#pragma once - -#include "OrthancPluginException.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for - * backward compatibility with Orthanc SDK <= 1.3.0. - * - * $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h - * - **/ -#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) -#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \ - (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \ - (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \ - (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \ - (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \ - ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision)))) -#endif - - -#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE) -#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision) \ - (ORTHANC_VERSION_MAJOR > major || \ - (ORTHANC_VERSION_MAJOR == major && \ - (ORTHANC_VERSION_MINOR > minor || \ - (ORTHANC_VERSION_MINOR == minor && \ - ORTHANC_VERSION_REVISION >= revision)))) -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0) -// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0 -# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1 -#else -# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0 -#endif - - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2) -# define HAS_ORTHANC_PLUGIN_PEERS 1 -# define HAS_ORTHANC_PLUGIN_JOB 1 -#else -# define HAS_ORTHANC_PLUGIN_PEERS 0 -# define HAS_ORTHANC_PLUGIN_JOB 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) -# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 1 -#else -# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) -# define HAS_ORTHANC_PLUGIN_METRICS 1 -#else -# define HAS_ORTHANC_PLUGIN_METRICS 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0) -# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 1 -#else -# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 1 -#else -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 1 -#else -# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0 -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0) -# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1 -#else -# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0 -#endif - - - -namespace OrthancPlugins -{ - typedef void (*RestCallback) (OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - void SetGlobalContext(OrthancPluginContext* context); - - bool HasGlobalContext(); - - OrthancPluginContext* GetGlobalContext(); - - - class OrthancImage; - - - class MemoryBuffer : public boost::noncopyable - { - private: - OrthancPluginMemoryBuffer buffer_; - - void Check(OrthancPluginErrorCode code); - - bool CheckHttp(OrthancPluginErrorCode code); - - public: - MemoryBuffer(); - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - // This constructor makes a copy of the given buffer in the memory - // handled by the Orthanc core - MemoryBuffer(const void* buffer, - size_t size); -#endif - - ~MemoryBuffer() - { - Clear(); - } - - OrthancPluginMemoryBuffer* operator*() - { - return &buffer_; - } - - // This transfers ownership from "other" to "this" - void Assign(OrthancPluginMemoryBuffer& other); - - void Swap(MemoryBuffer& other); - - OrthancPluginMemoryBuffer Release(); - - const char* GetData() const - { - if (buffer_.size > 0) - { - return reinterpret_cast(buffer_.data); - } - else - { - return NULL; - } - } - - size_t GetSize() const - { - return buffer_.size; - } - - bool IsEmpty() const - { - return GetSize() == 0 || GetData() == NULL; - } - - void Clear(); - - void ToString(std::string& target) const; - - void ToJson(Json::Value& target) const; - - bool RestApiGet(const std::string& uri, - bool applyPlugins); - - bool RestApiGet(const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins); - - bool RestApiPost(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPut(const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPost(const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - bool RestApiPut(const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - bool RestApiPost(const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); - } - - bool RestApiPut(const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins); - } - - void CreateDicom(const Json::Value& tags, - OrthancPluginCreateDicomFlags flags); - - void CreateDicom(const Json::Value& tags, - const OrthancImage& pixelData, - OrthancPluginCreateDicomFlags flags); - - void ReadFile(const std::string& path); - - void GetDicomQuery(const OrthancPluginWorklistQuery* query); - - void DicomToJson(Json::Value& target, - OrthancPluginDicomToJsonFormat format, - OrthancPluginDicomToJsonFlags flags, - uint32_t maxStringLength); - - bool HttpGet(const std::string& url, - const std::string& username, - const std::string& password); - - bool HttpPost(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password); - - bool HttpPut(const std::string& url, - const std::string& body, - const std::string& username, - const std::string& password); - - void GetDicomInstance(const std::string& instanceId); - }; - - - class OrthancString : public boost::noncopyable - { - private: - char* str_; - - void Clear(); - - public: - OrthancString() : - str_(NULL) - { - } - - ~OrthancString() - { - Clear(); - } - - // This transfers ownership, warning: The string must have been - // allocated by the Orthanc core - void Assign(char* str); - - const char* GetContent() const - { - return str_; - } - - void ToString(std::string& target) const; - - void ToJson(Json::Value& target) const; - }; - - - class OrthancConfiguration : public boost::noncopyable - { - private: - Json::Value configuration_; // Necessarily a Json::objectValue - std::string path_; - - std::string GetPath(const std::string& key) const; - - void LoadConfiguration(); - - public: - OrthancConfiguration(); - - explicit OrthancConfiguration(bool load); - - const Json::Value& GetJson() const - { - return configuration_; - } - - bool IsSection(const std::string& key) const; - - void GetSection(OrthancConfiguration& target, - const std::string& key) const; - - bool LookupStringValue(std::string& target, - const std::string& key) const; - - bool LookupIntegerValue(int& target, - const std::string& key) const; - - bool LookupUnsignedIntegerValue(unsigned int& target, - const std::string& key) const; - - bool LookupBooleanValue(bool& target, - const std::string& key) const; - - bool LookupFloatValue(float& target, - const std::string& key) const; - - bool LookupListOfStrings(std::list& target, - const std::string& key, - bool allowSingleString) const; - - bool LookupSetOfStrings(std::set& target, - const std::string& key, - bool allowSingleString) const; - - std::string GetStringValue(const std::string& key, - const std::string& defaultValue) const; - - int GetIntegerValue(const std::string& key, - int defaultValue) const; - - unsigned int GetUnsignedIntegerValue(const std::string& key, - unsigned int defaultValue) const; - - bool GetBooleanValue(const std::string& key, - bool defaultValue) const; - - float GetFloatValue(const std::string& key, - float defaultValue) const; - - void GetDictionary(std::map& target, - const std::string& key) const; - }; - - class OrthancImage : public boost::noncopyable - { - private: - OrthancPluginImage* image_; - - void Clear(); - - void CheckImageAvailable() const; - - public: - OrthancImage(); - - explicit OrthancImage(OrthancPluginImage* image); - - OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height); - - OrthancImage(OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - void* buffer); - - ~OrthancImage() - { - Clear(); - } - - void UncompressPngImage(const void* data, - size_t size); - - void UncompressJpegImage(const void* data, - size_t size); - - void DecodeDicomImage(const void* data, - size_t size, - unsigned int frame); - - OrthancPluginPixelFormat GetPixelFormat() const; - - unsigned int GetWidth() const; - - unsigned int GetHeight() const; - - unsigned int GetPitch() const; - - void* GetBuffer() const; - - const OrthancPluginImage* GetObject() const - { - return image_; - } - - void CompressPngImage(MemoryBuffer& target) const; - - void CompressJpegImage(MemoryBuffer& target, - uint8_t quality) const; - - void AnswerPngImage(OrthancPluginRestOutput* output) const; - - void AnswerJpegImage(OrthancPluginRestOutput* output, - uint8_t quality) const; - - void* GetWriteableBuffer(); - - OrthancPluginImage* Release(); - }; - - -#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 - class FindMatcher : public boost::noncopyable - { - private: - OrthancPluginFindMatcher* matcher_; - const OrthancPluginWorklistQuery* worklist_; - - void SetupDicom(const void* query, - uint32_t size); - - public: - explicit FindMatcher(const OrthancPluginWorklistQuery* worklist); - - FindMatcher(const void* query, - uint32_t size) - { - SetupDicom(query, size); - } - - explicit FindMatcher(const MemoryBuffer& dicom) - { - SetupDicom(dicom.GetData(), dicom.GetSize()); - } - - ~FindMatcher(); - - bool IsMatch(const void* dicom, - uint32_t size) const; - - bool IsMatch(const MemoryBuffer& dicom) const - { - return IsMatch(dicom.GetData(), dicom.GetSize()); - } - }; -#endif - - - bool RestApiGet(Json::Value& result, - const std::string& uri, - bool applyPlugins); - - bool RestApiGetString(std::string& result, - const std::string& uri, - bool applyPlugins); - - bool RestApiGetString(std::string& result, - const std::string& uri, - const std::map& httpHeaders, - bool applyPlugins); - - bool RestApiPost(std::string& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPost(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - inline bool RestApiPost(Json::Value& result, - const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(), - body.size(), applyPlugins); - } - - inline bool RestApiPost(Json::Value& result, - const std::string& uri, - const MemoryBuffer& body, - bool applyPlugins) - { - return RestApiPost(result, uri, body.GetData(), - body.GetSize(), applyPlugins); - } - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const void* body, - size_t bodySize, - bool applyPlugins); - - bool RestApiPut(Json::Value& result, - const std::string& uri, - const Json::Value& body, - bool applyPlugins); - - inline bool RestApiPut(Json::Value& result, - const std::string& uri, - const std::string& body, - bool applyPlugins) - { - return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(), - body.size(), applyPlugins); - } - - bool RestApiDelete(const std::string& uri, - bool applyPlugins); - - bool HttpDelete(const std::string& url, - const std::string& username, - const std::string& password); - - void AnswerJson(const Json::Value& value, - OrthancPluginRestOutput* output); - - void AnswerString(const std::string& answer, - const char* mimeType, - OrthancPluginRestOutput* output); - - void AnswerHttpError(uint16_t httpError, - OrthancPluginRestOutput* output); - - void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods); - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) - const char* AutodetectMimeType(const std::string& path); -#endif - - void LogError(const std::string& message); - - void LogWarning(const std::string& message); - - void LogInfo(const std::string& message); - - void ReportMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision); - - bool CheckMinimalOrthancVersion(unsigned int major, - unsigned int minor, - unsigned int revision); - - - namespace Internals - { - template - static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) - { - try - { - Callback(output, url, request); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { -#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1 - if (HasGlobalContext() && - e.HasDetails()) - { - // The "false" instructs Orthanc not to log the detailed - // error message. This is to avoid duplicating the details, - // because "OrthancException" already does it on construction. - OrthancPluginSetHttpErrorDetails - (GetGlobalContext(), output, e.GetDetails(), false); - } -#endif - - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - } - - - template - void RegisterRestCallback(const std::string& uri, - bool isThreadSafe) - { - if (isThreadSafe) - { - OrthancPluginRegisterRestCallbackNoLock - (GetGlobalContext(), uri.c_str(), Internals::Protect); - } - else - { - OrthancPluginRegisterRestCallback - (GetGlobalContext(), uri.c_str(), Internals::Protect); - } - } - - -#if HAS_ORTHANC_PLUGIN_PEERS == 1 - class OrthancPeers : public boost::noncopyable - { - private: - typedef std::map Index; - - OrthancPluginPeers *peers_; - Index index_; - uint32_t timeout_; - - size_t GetPeerIndex(const std::string& name) const; - - public: - OrthancPeers(); - - ~OrthancPeers(); - - uint32_t GetTimeout() const - { - return timeout_; - } - - void SetTimeout(uint32_t timeout) - { - timeout_ = timeout; - } - - bool LookupName(size_t& target, - const std::string& name) const; - - std::string GetPeerName(size_t index) const; - - std::string GetPeerUrl(size_t index) const; - - std::string GetPeerUrl(const std::string& name) const; - - size_t GetPeersCount() const - { - return index_.size(); - } - - bool LookupUserProperty(std::string& value, - size_t index, - const std::string& key) const; - - bool LookupUserProperty(std::string& value, - const std::string& peer, - const std::string& key) const; - - bool DoGet(MemoryBuffer& target, - size_t index, - const std::string& uri) const; - - bool DoGet(MemoryBuffer& target, - const std::string& name, - const std::string& uri) const; - - bool DoGet(Json::Value& target, - size_t index, - const std::string& uri) const; - - bool DoGet(Json::Value& target, - const std::string& name, - const std::string& uri) const; - - bool DoPost(MemoryBuffer& target, - size_t index, - const std::string& uri, - const std::string& body) const; - - bool DoPost(MemoryBuffer& target, - const std::string& name, - const std::string& uri, - const std::string& body) const; - - bool DoPost(Json::Value& target, - size_t index, - const std::string& uri, - const std::string& body) const; - - bool DoPost(Json::Value& target, - const std::string& name, - const std::string& uri, - const std::string& body) const; - - bool DoPut(size_t index, - const std::string& uri, - const std::string& body) const; - - bool DoPut(const std::string& name, - const std::string& uri, - const std::string& body) const; - - bool DoDelete(size_t index, - const std::string& uri) const; - - bool DoDelete(const std::string& name, - const std::string& uri) const; - }; -#endif - - - -#if HAS_ORTHANC_PLUGIN_JOB == 1 - class OrthancJob : public boost::noncopyable - { - private: - std::string jobType_; - std::string content_; - bool hasSerialized_; - std::string serialized_; - float progress_; - - static void CallbackFinalize(void* job); - - static float CallbackGetProgress(void* job); - - static const char* CallbackGetContent(void* job); - - static const char* CallbackGetSerialized(void* job); - - static OrthancPluginJobStepStatus CallbackStep(void* job); - - static OrthancPluginErrorCode CallbackStop(void* job, - OrthancPluginJobStopReason reason); - - static OrthancPluginErrorCode CallbackReset(void* job); - - protected: - void ClearContent(); - - void UpdateContent(const Json::Value& content); - - void ClearSerialized(); - - void UpdateSerialized(const Json::Value& serialized); - - void UpdateProgress(float progress); - - public: - OrthancJob(const std::string& jobType); - - virtual ~OrthancJob() - { - } - - virtual OrthancPluginJobStepStatus Step() = 0; - - virtual void Stop(OrthancPluginJobStopReason reason) = 0; - - virtual void Reset() = 0; - - static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */); - - static std::string Submit(OrthancJob* job /* takes ownership */, - int priority); - - static void SubmitAndWait(Json::Value& result, - OrthancJob* job /* takes ownership */, - int priority); - - // Submit a job from a POST on the REST API with the same - // conventions as in the Orthanc core (according to the - // "Synchronous" and "Priority" options) - static void SubmitFromRestApiPost(OrthancPluginRestOutput* output, - const Json::Value& body, - OrthancJob* job); - }; -#endif - - -#if HAS_ORTHANC_PLUGIN_METRICS == 1 - inline void SetMetricsValue(char* name, - float value) - { - OrthancPluginSetMetricsValue(GetGlobalContext(), name, - value, OrthancPluginMetricsType_Default); - } - - class MetricsTimer : public boost::noncopyable - { - private: - std::string name_; - boost::posix_time::ptime start_; - - public: - explicit MetricsTimer(const char* name); - - ~MetricsTimer(); - }; -#endif - - -#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 - class HttpClient : public boost::noncopyable - { - public: - typedef std::map HttpHeaders; - - class IRequestBody : public boost::noncopyable - { - public: - virtual ~IRequestBody() - { - } - - virtual bool ReadNextChunk(std::string& chunk) = 0; - }; - - - class IAnswer : public boost::noncopyable - { - public: - virtual ~IAnswer() - { - } - - virtual void AddHeader(const std::string& key, - const std::string& value) = 0; - - virtual void AddChunk(const void* data, - size_t size) = 0; - }; - - - private: - class RequestBodyWrapper; - - uint16_t httpStatus_; - OrthancPluginHttpMethod method_; - std::string url_; - HttpHeaders headers_; - std::string username_; - std::string password_; - uint32_t timeout_; - std::string certificateFile_; - std::string certificateKeyFile_; - std::string certificateKeyPassword_; - bool pkcs11_; - std::string fullBody_; - IRequestBody* chunkedBody_; - bool allowChunkedTransfers_; - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1 - void ExecuteWithStream(uint16_t& httpStatus, // out - IAnswer& answer, // out - IRequestBody& body) const; -#endif - - void ExecuteWithoutStream(uint16_t& httpStatus, // out - HttpHeaders& answerHeaders, // out - std::string& answerBody, // out - const std::string& body) const; - - public: - HttpClient(); - - uint16_t GetHttpStatus() const - { - return httpStatus_; - } - - void SetMethod(OrthancPluginHttpMethod method) - { - method_ = method; - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - void SetHeaders(const HttpHeaders& headers) - { - headers_ = headers; - } - - void AddHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - void AddHeaders(const HttpHeaders& headers); - - void SetCredentials(const std::string& username, - const std::string& password); - - void ClearCredentials(); - - void SetTimeout(unsigned int timeout) // 0 for default timeout - { - timeout_ = timeout; - } - - void SetCertificate(const std::string& certificateFile, - const std::string& keyFile, - const std::string& keyPassword); - - void ClearCertificate(); - - void SetPkcs11(bool pkcs11) - { - pkcs11_ = pkcs11; - } - - void ClearBody(); - - void SwapBody(std::string& body); - - void SetBody(const std::string& body); - - void SetBody(IRequestBody& body); - - // This function can be used to disable chunked transfers if the - // remote server is Orthanc with a version <= 1.5.6. - void SetChunkedTransfersAllowed(bool allow) - { - allowChunkedTransfers_ = allow; - } - - bool IsChunkedTransfersAllowed() const - { - return allowChunkedTransfers_; - } - - void Execute(IAnswer& answer); - - void Execute(HttpHeaders& answerHeaders /* out */, - std::string& answerBody /* out */); - - void Execute(HttpHeaders& answerHeaders /* out */, - Json::Value& answerBody /* out */); - - void Execute(); - }; -#endif - - - - class IChunkedRequestReader : public boost::noncopyable - { - public: - virtual ~IChunkedRequestReader() - { - } - - virtual void AddChunk(const void* data, - size_t size) = 0; - - virtual void Execute(OrthancPluginRestOutput* output) = 0; - }; - - - typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url, - const OrthancPluginHttpRequest* request); - - - namespace Internals - { - void NullRestCallback(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - IChunkedRequestReader *NullChunkedRestCallback(const char* url, - const OrthancPluginHttpRequest* request); - - -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 - template - static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, - const char* url, - const OrthancPluginHttpRequest* request) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - else - { - *reader = reinterpret_cast(Callback(url, request)); - if (*reader == NULL) - { - return OrthancPluginErrorCode_Plugin; - } - else - { - return OrthancPluginErrorCode_Success; - } - } - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - OrthancPluginErrorCode ChunkedRequestReaderAddChunk( - OrthancPluginServerChunkedRequestReader* reader, - const void* data, - uint32_t size); - - OrthancPluginErrorCode ChunkedRequestReaderExecute( - OrthancPluginServerChunkedRequestReader* reader, - OrthancPluginRestOutput* output); - - void ChunkedRequestReaderFinalize( - OrthancPluginServerChunkedRequestReader* reader); - -#else - - OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request, - RestCallback GetHandler, - ChunkedRestCallback PostHandler, - RestCallback DeleteHandler, - ChunkedRestCallback PutHandler); - - template< - RestCallback GetHandler, - ChunkedRestCallback PostHandler, - RestCallback DeleteHandler, - ChunkedRestCallback PutHandler - > - inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) - { - return ChunkedRestCompatibility(output, url, request, GetHandler, - PostHandler, DeleteHandler, PutHandler); - } -#endif - } - - - - // NB: We use a templated class instead of a templated function, because - // default values are only available in functions since C++11 - template< - RestCallback GetHandler = Internals::NullRestCallback, - ChunkedRestCallback PostHandler = Internals::NullChunkedRestCallback, - RestCallback DeleteHandler = Internals::NullRestCallback, - ChunkedRestCallback PutHandler = Internals::NullChunkedRestCallback - > - class ChunkedRestRegistration : public boost::noncopyable - { - public: - static void Apply(const std::string& uri) - { -#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1 - OrthancPluginRegisterChunkedRestCallback( - GetGlobalContext(), uri.c_str(), - GetHandler == Internals::NullRestCallback ? NULL : Internals::Protect, - PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, - DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect, - PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect, - Internals::ChunkedRequestReaderAddChunk, - Internals::ChunkedRequestReaderExecute, - Internals::ChunkedRequestReaderFinalize); -#else - OrthancPluginRegisterRestCallbackNoLock( - GetGlobalContext(), uri.c_str(), - Internals::ChunkedRestCompatibility); -#endif - } - }; - - - -#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1 - class IStorageCommitmentScpHandler : public boost::noncopyable - { - public: - virtual ~IStorageCommitmentScpHandler() - { - } - - virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid, - const std::string& sopInstanceUid) = 0; - - static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target, - void* rawHandler, - const char* sopClassUid, - const char* sopInstanceUid); - - static void Destructor(void* rawHandler); - }; -#endif - - - class DicomInstance : public boost::noncopyable - { - private: - bool toFree_; - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - const OrthancPluginDicomInstance* instance_; -#else - OrthancPluginDicomInstance* instance_; -#endif - - public: -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - explicit DicomInstance(const OrthancPluginDicomInstance* instance); -#else - explicit DicomInstance(OrthancPluginDicomInstance* instance); -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - DicomInstance(const void* buffer, - size_t size); -#endif - - ~DicomInstance(); - - std::string GetRemoteAet() const; - - const void* GetBuffer() const - { - return OrthancPluginGetInstanceData(GetGlobalContext(), instance_); - } - - size_t GetSize() const - { - return static_cast(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_)); - } - - void GetJson(Json::Value& target) const; - - void GetSimplifiedJson(Json::Value& target) const; - - OrthancPluginInstanceOrigin GetOrigin() const - { - return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_); - } - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - std::string GetTransferSyntaxUid() const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1) - bool HasPixelData() const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - unsigned int GetFramesCount() const - { - return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_); - } -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void GetRawFrame(std::string& target, - unsigned int frameIndex) const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - OrthancImage* GetDecodedFrame(unsigned int frameIndex) const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - void Serialize(std::string& target) const; -#endif - -#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0) - static DicomInstance* Transcode(const void* buffer, - size_t size, - const std::string& transferSyntax); -#endif - }; -} diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h --- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginException.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * 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 . - **/ - - -#pragma once - -#if !defined(HAS_ORTHANC_EXCEPTION) -# error The macro HAS_ORTHANC_EXCEPTION must be defined -#endif - - -#if HAS_ORTHANC_EXCEPTION == 1 -# include -# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::Orthanc::ErrorCode -# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::Orthanc::OrthancException -# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::Orthanc::ErrorCode_ ## code -#else -# include -# define ORTHANC_PLUGINS_ERROR_ENUMERATION ::OrthancPluginErrorCode -# define ORTHANC_PLUGINS_EXCEPTION_CLASS ::OrthancPlugins::PluginException -# define ORTHANC_PLUGINS_GET_ERROR_CODE(code) ::OrthancPluginErrorCode_ ## code -#endif - - -#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code) \ - throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast(code)); - - -#define ORTHANC_PLUGINS_THROW_EXCEPTION(code) \ - throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code)); - - -#define ORTHANC_PLUGINS_CHECK_ERROR(code) \ - if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success)) \ - { \ - ORTHANC_PLUGINS_THROW_EXCEPTION(code); \ - } - - -namespace OrthancPlugins -{ -#if HAS_ORTHANC_EXCEPTION == 0 - class PluginException - { - private: - OrthancPluginErrorCode code_; - - public: - explicit PluginException(OrthancPluginErrorCode code) : code_(code) - { - } - - OrthancPluginErrorCode GetErrorCode() const - { - return code_; - } - - const char* What(OrthancPluginContext* context) const - { - const char* description = OrthancPluginGetErrorDescription(context, code_); - if (description) - { - return description; - } - else - { - return "No description available"; - } - } - }; -#endif -} diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake --- a/StoneWebViewer/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# Orthanc - A Lightweight, RESTful DICOM Store -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., Belgium -# -# This program is free software: you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# 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 . - - -# In Orthanc <= 1.7.1, the instructions below were part of -# "Compiler.cmake", and were protected by the (now unused) option -# "ENABLE_PLUGINS_VERSION_SCRIPT" in CMake - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR - ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${CMAKE_CURRENT_LIST_DIR}/VersionScriptPlugins.map") -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_CURRENT_LIST_DIR}/ExportedSymbolsPlugins.list") -endif() diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map --- a/StoneWebViewer/Resources/Orthanc/Plugins/VersionScriptPlugins.map Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -# This is a version-script for Orthanc plugins - -{ -global: - OrthancPluginInitialize; - OrthancPluginFinalize; - OrthancPluginGetName; - OrthancPluginGetVersion; - -local: - *; -}; diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h --- a/StoneWebViewer/Resources/OrthancSdk-1.0.0/orthanc/OrthancCPlugin.h Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4740 +0,0 @@ -/** - * \mainpage - * - * This C/C++ SDK allows external developers to create plugins that - * can be loaded into Orthanc to extend its functionality. Each - * Orthanc plugin must expose 4 public functions with the following - * signatures: - * - * -# int32_t OrthancPluginInitialize(const OrthancPluginContext* context): - * This function is invoked by Orthanc when it loads the plugin on startup. - * The plugin must: - * - Check its compatibility with the Orthanc version using - * ::OrthancPluginCheckVersion(). - * - Store the context pointer so that it can use the plugin - * services of Orthanc. - * - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback(). - * - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback(). - * - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback(). - * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea(). - * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2(). - * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). - * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). - * -# void OrthancPluginFinalize(): - * This function is invoked by Orthanc during its shutdown. The plugin - * must free all its memory. - * -# const char* OrthancPluginGetName(): - * The plugin must return a short string to identify itself. - * -# const char* OrthancPluginGetVersion(): - * The plugin must return a string containing its version number. - * - * The name and the version of a plugin is only used to prevent it - * from being loaded twice. Note that, in C++, it is mandatory to - * declare these functions within an extern "C" section. - * - * To ensure multi-threading safety, the various REST callbacks are - * guaranteed to be executed in mutual exclusion since Orthanc - * 0.8.5. If this feature is undesired (notably when developing - * high-performance plugins handling simultaneous requests), use - * ::OrthancPluginRegisterRestCallbackNoLock(). - **/ - - - -/** - * @defgroup Images Images and compression - * @brief Functions to deal with images and compressed buffers. - * - * @defgroup REST REST - * @brief Functions to answer REST requests in a callback. - * - * @defgroup Callbacks Callbacks - * @brief Functions to register and manage callbacks by the plugins. - * - * @defgroup Worklists Worklists - * @brief Functions to register and manage worklists. - * - * @defgroup Orthanc Orthanc - * @brief Functions to access the content of the Orthanc server. - **/ - - - -/** - * @defgroup Toolbox Toolbox - * @brief Generic functions to help with the creation of plugins. - **/ - - - -/** - * 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 . - **/ - - - -#pragma once - - -#include -#include - -#ifdef WIN32 -#define ORTHANC_PLUGINS_API __declspec(dllexport) -#else -#define ORTHANC_PLUGINS_API -#endif - -#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 0 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 - - - -/******************************************************************** - ** Check that function inlining is properly supported. The use of - ** inlining is required, to avoid the duplication of object code - ** between two compilation modules that would use the Orthanc Plugin - ** API. - ********************************************************************/ - -/* If the auto-detection of the "inline" keyword below does not work - automatically and that your compiler is known to properly support - inlining, uncomment the following #define and adapt the definition - of "static inline". */ - -/* #define ORTHANC_PLUGIN_INLINE static inline */ - -#ifndef ORTHANC_PLUGIN_INLINE -# if __STDC_VERSION__ >= 199901L -/* This is C99 or above: http://predef.sourceforge.net/prestd.html */ -# define ORTHANC_PLUGIN_INLINE static inline -# elif defined(__cplusplus) -/* This is C++ */ -# define ORTHANC_PLUGIN_INLINE static inline -# elif defined(__GNUC__) -/* This is GCC running in C89 mode */ -# define ORTHANC_PLUGIN_INLINE static __inline -# elif defined(_MSC_VER) -/* This is Visual Studio running in C89 mode */ -# define ORTHANC_PLUGIN_INLINE static __inline -# else -# error Your compiler is not known to support the "inline" keyword -# endif -#endif - - - -/******************************************************************** - ** Inclusion of standard libraries. - ********************************************************************/ - -/** - * For Microsoft Visual Studio, a compatibility "stdint.h" can be - * downloaded at the following URL: - * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h - **/ -#include - -#include - - - -/******************************************************************** - ** Definition of the Orthanc Plugin API. - ********************************************************************/ - -/** @{ */ - -#ifdef __cplusplus -extern "C" -{ -#endif - - /** - * The various error codes that can be returned by the Orthanc core. - **/ - typedef enum - { - OrthancPluginErrorCode_InternalError = -1 /*!< Internal error */, - OrthancPluginErrorCode_Success = 0 /*!< Success */, - OrthancPluginErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, - OrthancPluginErrorCode_NotImplemented = 2 /*!< Not implemented yet */, - OrthancPluginErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, - OrthancPluginErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, - OrthancPluginErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, - OrthancPluginErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, - OrthancPluginErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, - OrthancPluginErrorCode_BadRequest = 8 /*!< Bad request */, - OrthancPluginErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, - OrthancPluginErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, - OrthancPluginErrorCode_Database = 11 /*!< Error with the database engine */, - OrthancPluginErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, - OrthancPluginErrorCode_InexistentFile = 13 /*!< Inexistent file */, - OrthancPluginErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, - OrthancPluginErrorCode_BadFileFormat = 15 /*!< Bad file format */, - OrthancPluginErrorCode_Timeout = 16 /*!< Timeout */, - OrthancPluginErrorCode_UnknownResource = 17 /*!< Unknown resource */, - OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, - OrthancPluginErrorCode_FullStorage = 19 /*!< The file storage is full */, - OrthancPluginErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, - OrthancPluginErrorCode_InexistentTag = 21 /*!< Inexistent tag */, - OrthancPluginErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, - OrthancPluginErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, - OrthancPluginErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, - OrthancPluginErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, - OrthancPluginErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, - OrthancPluginErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, - OrthancPluginErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, - OrthancPluginErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, - OrthancPluginErrorCode_BadFont = 30 /*!< Badly formatted font file */, - OrthancPluginErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, - OrthancPluginErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, - OrthancPluginErrorCode_EmptyRequest = 33 /*!< The request is empty */, - OrthancPluginErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, - OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, - OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, - OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, - OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, - OrthancPluginErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, - OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, - OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, - OrthancPluginErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, - OrthancPluginErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, - OrthancPluginErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, - OrthancPluginErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, - OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, - OrthancPluginErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, - OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, - OrthancPluginErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, - OrthancPluginErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, - OrthancPluginErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, - OrthancPluginErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, - OrthancPluginErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, - OrthancPluginErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is already in use */, - OrthancPluginErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is already in use */, - OrthancPluginErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, - OrthancPluginErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, - OrthancPluginErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, - OrthancPluginErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, - OrthancPluginErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, - OrthancPluginErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, - OrthancPluginErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, - OrthancPluginErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, - OrthancPluginErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, - OrthancPluginErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, - OrthancPluginErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, - OrthancPluginErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, - OrthancPluginErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, - OrthancPluginErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, - OrthancPluginErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, - OrthancPluginErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, - OrthancPluginErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, - OrthancPluginErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, - OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, - OrthancPluginErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, - OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, - OrthancPluginErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, - OrthancPluginErrorCode_UnknownModality = 2027 /*!< Unknown modality */, - OrthancPluginErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, - OrthancPluginErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, - OrthancPluginErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, - OrthancPluginErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, - OrthancPluginErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, - OrthancPluginErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, - OrthancPluginErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, - OrthancPluginErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, - OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, - OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, - OrthancPluginErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, - OrthancPluginErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, - OrthancPluginErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, - OrthancPluginErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, - - _OrthancPluginErrorCode_INTERNAL = 0x7fffffff - } OrthancPluginErrorCode; - - - /** - * Forward declaration of one of the mandatory functions for Orthanc - * plugins. - **/ - ORTHANC_PLUGINS_API const char* OrthancPluginGetName(); - - - /** - * The various HTTP methods for a REST call. - **/ - typedef enum - { - OrthancPluginHttpMethod_Get = 1, /*!< GET request */ - OrthancPluginHttpMethod_Post = 2, /*!< POST request */ - OrthancPluginHttpMethod_Put = 3, /*!< PUT request */ - OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */ - - _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff - } OrthancPluginHttpMethod; - - - /** - * @brief The parameters of a REST request. - * @ingroup Callbacks - **/ - typedef struct - { - /** - * @brief The HTTP method. - **/ - OrthancPluginHttpMethod method; - - /** - * @brief The number of groups of the regular expression. - **/ - uint32_t groupsCount; - - /** - * @brief The matched values for the groups of the regular expression. - **/ - const char* const* groups; - - /** - * @brief For a GET request, the number of GET parameters. - **/ - uint32_t getCount; - - /** - * @brief For a GET request, the keys of the GET parameters. - **/ - const char* const* getKeys; - - /** - * @brief For a GET request, the values of the GET parameters. - **/ - const char* const* getValues; - - /** - * @brief For a PUT or POST request, the content of the body. - **/ - const char* body; - - /** - * @brief For a PUT or POST request, the number of bytes of the body. - **/ - uint32_t bodySize; - - - /* -------------------------------------------------- - New in version 0.8.1 - -------------------------------------------------- */ - - /** - * @brief The number of HTTP headers. - **/ - uint32_t headersCount; - - /** - * @brief The keys of the HTTP headers (always converted to low-case). - **/ - const char* const* headersKeys; - - /** - * @brief The values of the HTTP headers. - **/ - const char* const* headersValues; - - } OrthancPluginHttpRequest; - - - typedef enum - { - /* Generic services */ - _OrthancPluginService_LogInfo = 1, - _OrthancPluginService_LogWarning = 2, - _OrthancPluginService_LogError = 3, - _OrthancPluginService_GetOrthancPath = 4, - _OrthancPluginService_GetOrthancDirectory = 5, - _OrthancPluginService_GetConfigurationPath = 6, - _OrthancPluginService_SetPluginProperty = 7, - _OrthancPluginService_GetGlobalProperty = 8, - _OrthancPluginService_SetGlobalProperty = 9, - _OrthancPluginService_GetCommandLineArgumentsCount = 10, - _OrthancPluginService_GetCommandLineArgument = 11, - _OrthancPluginService_GetExpectedDatabaseVersion = 12, - _OrthancPluginService_GetConfiguration = 13, - _OrthancPluginService_BufferCompression = 14, - _OrthancPluginService_ReadFile = 15, - _OrthancPluginService_WriteFile = 16, - _OrthancPluginService_GetErrorDescription = 17, - _OrthancPluginService_CallHttpClient = 18, - _OrthancPluginService_RegisterErrorCode = 19, - _OrthancPluginService_RegisterDictionaryTag = 20, - _OrthancPluginService_DicomBufferToJson = 21, - _OrthancPluginService_DicomInstanceToJson = 22, - _OrthancPluginService_CreateDicom = 23, - _OrthancPluginService_ComputeMd5 = 24, - _OrthancPluginService_ComputeSha1 = 25, - _OrthancPluginService_LookupDictionary = 26, - - /* Registration of callbacks */ - _OrthancPluginService_RegisterRestCallback = 1000, - _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001, - _OrthancPluginService_RegisterStorageArea = 1002, - _OrthancPluginService_RegisterOnChangeCallback = 1003, - _OrthancPluginService_RegisterRestCallbackNoLock = 1004, - _OrthancPluginService_RegisterWorklistCallback = 1005, - _OrthancPluginService_RegisterDecodeImageCallback = 1006, - - /* Sending answers to REST calls */ - _OrthancPluginService_AnswerBuffer = 2000, - _OrthancPluginService_CompressAndAnswerPngImage = 2001, /* Unused as of Orthanc 0.9.4 */ - _OrthancPluginService_Redirect = 2002, - _OrthancPluginService_SendHttpStatusCode = 2003, - _OrthancPluginService_SendUnauthorized = 2004, - _OrthancPluginService_SendMethodNotAllowed = 2005, - _OrthancPluginService_SetCookie = 2006, - _OrthancPluginService_SetHttpHeader = 2007, - _OrthancPluginService_StartMultipartAnswer = 2008, - _OrthancPluginService_SendMultipartItem = 2009, - _OrthancPluginService_SendHttpStatus = 2010, - _OrthancPluginService_CompressAndAnswerImage = 2011, - _OrthancPluginService_SendMultipartItem2 = 2012, - - /* Access to the Orthanc database and API */ - _OrthancPluginService_GetDicomForInstance = 3000, - _OrthancPluginService_RestApiGet = 3001, - _OrthancPluginService_RestApiPost = 3002, - _OrthancPluginService_RestApiDelete = 3003, - _OrthancPluginService_RestApiPut = 3004, - _OrthancPluginService_LookupPatient = 3005, - _OrthancPluginService_LookupStudy = 3006, - _OrthancPluginService_LookupSeries = 3007, - _OrthancPluginService_LookupInstance = 3008, - _OrthancPluginService_LookupStudyWithAccessionNumber = 3009, - _OrthancPluginService_RestApiGetAfterPlugins = 3010, - _OrthancPluginService_RestApiPostAfterPlugins = 3011, - _OrthancPluginService_RestApiDeleteAfterPlugins = 3012, - _OrthancPluginService_RestApiPutAfterPlugins = 3013, - _OrthancPluginService_ReconstructMainDicomTags = 3014, - _OrthancPluginService_RestApiGet2 = 3015, - - /* Access to DICOM instances */ - _OrthancPluginService_GetInstanceRemoteAet = 4000, - _OrthancPluginService_GetInstanceSize = 4001, - _OrthancPluginService_GetInstanceData = 4002, - _OrthancPluginService_GetInstanceJson = 4003, - _OrthancPluginService_GetInstanceSimplifiedJson = 4004, - _OrthancPluginService_HasInstanceMetadata = 4005, - _OrthancPluginService_GetInstanceMetadata = 4006, - _OrthancPluginService_GetInstanceOrigin = 4007, - - /* Services for plugins implementing a database back-end */ - _OrthancPluginService_RegisterDatabaseBackend = 5000, - _OrthancPluginService_DatabaseAnswer = 5001, - _OrthancPluginService_RegisterDatabaseBackendV2 = 5002, - _OrthancPluginService_StorageAreaCreate = 5003, - _OrthancPluginService_StorageAreaRead = 5004, - _OrthancPluginService_StorageAreaRemove = 5005, - - /* Primitives for handling images */ - _OrthancPluginService_GetImagePixelFormat = 6000, - _OrthancPluginService_GetImageWidth = 6001, - _OrthancPluginService_GetImageHeight = 6002, - _OrthancPluginService_GetImagePitch = 6003, - _OrthancPluginService_GetImageBuffer = 6004, - _OrthancPluginService_UncompressImage = 6005, - _OrthancPluginService_FreeImage = 6006, - _OrthancPluginService_CompressImage = 6007, - _OrthancPluginService_ConvertPixelFormat = 6008, - _OrthancPluginService_GetFontsCount = 6009, - _OrthancPluginService_GetFontInfo = 6010, - _OrthancPluginService_DrawText = 6011, - _OrthancPluginService_CreateImage = 6012, - _OrthancPluginService_CreateImageAccessor = 6013, - _OrthancPluginService_DecodeDicomImage = 6014, - - /* Primitives for handling worklists */ - _OrthancPluginService_WorklistAddAnswer = 7000, - _OrthancPluginService_WorklistMarkIncomplete = 7001, - _OrthancPluginService_WorklistIsMatch = 7002, - _OrthancPluginService_WorklistGetDicomQuery = 7003, - - _OrthancPluginService_INTERNAL = 0x7fffffff - } _OrthancPluginService; - - - typedef enum - { - _OrthancPluginProperty_Description = 1, - _OrthancPluginProperty_RootUri = 2, - _OrthancPluginProperty_OrthancExplorer = 3, - - _OrthancPluginProperty_INTERNAL = 0x7fffffff - } _OrthancPluginProperty; - - - - /** - * The memory layout of the pixels of an image. - * @ingroup Images - **/ - typedef enum - { - /** - * @brief Graylevel 8bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in - * one byte. - **/ - OrthancPluginPixelFormat_Grayscale8 = 1, - - /** - * @brief Graylevel, unsigned 16bpp image. - * - * The image is graylevel. Each pixel is unsigned and stored in - * two bytes. - **/ - OrthancPluginPixelFormat_Grayscale16 = 2, - - /** - * @brief Graylevel, signed 16bpp image. - * - * The image is graylevel. Each pixel is signed and stored in two - * bytes. - **/ - OrthancPluginPixelFormat_SignedGrayscale16 = 3, - - /** - * @brief Color image in RGB24 format. - * - * This format describes a color image. The pixels are stored in 3 - * consecutive bytes. The memory layout is RGB. - **/ - OrthancPluginPixelFormat_RGB24 = 4, - - /** - * @brief Color image in RGBA32 format. - * - * This format describes a color image. The pixels are stored in 4 - * consecutive bytes. The memory layout is RGBA. - **/ - OrthancPluginPixelFormat_RGBA32 = 5, - - OrthancPluginPixelFormat_Unknown = 6, /*!< Unknown pixel format */ - - _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff - } OrthancPluginPixelFormat; - - - - /** - * The content types that are supported by Orthanc plugins. - **/ - typedef enum - { - OrthancPluginContentType_Unknown = 0, /*!< Unknown content type */ - OrthancPluginContentType_Dicom = 1, /*!< DICOM */ - OrthancPluginContentType_DicomAsJson = 2, /*!< JSON summary of a DICOM file */ - - _OrthancPluginContentType_INTERNAL = 0x7fffffff - } OrthancPluginContentType; - - - - /** - * The supported types of DICOM resources. - **/ - typedef enum - { - OrthancPluginResourceType_Patient = 0, /*!< Patient */ - OrthancPluginResourceType_Study = 1, /*!< Study */ - OrthancPluginResourceType_Series = 2, /*!< Series */ - OrthancPluginResourceType_Instance = 3, /*!< Instance */ - OrthancPluginResourceType_None = 4, /*!< Unavailable resource type */ - - _OrthancPluginResourceType_INTERNAL = 0x7fffffff - } OrthancPluginResourceType; - - - - /** - * The supported types of changes that can happen to DICOM resources. - * @ingroup Callbacks - **/ - typedef enum - { - OrthancPluginChangeType_CompletedSeries = 0, /*!< Series is now complete */ - OrthancPluginChangeType_Deleted = 1, /*!< Deleted resource */ - OrthancPluginChangeType_NewChildInstance = 2, /*!< A new instance was added to this resource */ - OrthancPluginChangeType_NewInstance = 3, /*!< New instance received */ - OrthancPluginChangeType_NewPatient = 4, /*!< New patient created */ - OrthancPluginChangeType_NewSeries = 5, /*!< New series created */ - OrthancPluginChangeType_NewStudy = 6, /*!< New study created */ - 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_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */ - OrthancPluginChangeType_UpdatedMetadata = 13, /*!< Some user-defined metadata has changed for this resource */ - - _OrthancPluginChangeType_INTERNAL = 0x7fffffff - } OrthancPluginChangeType; - - - /** - * The compression algorithms that are supported by the Orthanc core. - * @ingroup Images - **/ - typedef enum - { - OrthancPluginCompressionType_Zlib = 0, /*!< Standard zlib compression */ - OrthancPluginCompressionType_ZlibWithSize = 1, /*!< zlib, prefixed with uncompressed size (uint64_t) */ - OrthancPluginCompressionType_Gzip = 2, /*!< Standard gzip compression */ - OrthancPluginCompressionType_GzipWithSize = 3, /*!< gzip, prefixed with uncompressed size (uint64_t) */ - - _OrthancPluginCompressionType_INTERNAL = 0x7fffffff - } OrthancPluginCompressionType; - - - /** - * The image formats that are supported by the Orthanc core. - * @ingroup Images - **/ - typedef enum - { - OrthancPluginImageFormat_Png = 0, /*!< Image compressed using PNG */ - OrthancPluginImageFormat_Jpeg = 1, /*!< Image compressed using JPEG */ - OrthancPluginImageFormat_Dicom = 2, /*!< Image compressed using DICOM */ - - _OrthancPluginImageFormat_INTERNAL = 0x7fffffff - } OrthancPluginImageFormat; - - - /** - * The value representations present in the DICOM standard (version 2013). - * @ingroup Toolbox - **/ - typedef enum - { - OrthancPluginValueRepresentation_AE = 1, /*!< Application Entity */ - OrthancPluginValueRepresentation_AS = 2, /*!< Age String */ - OrthancPluginValueRepresentation_AT = 3, /*!< Attribute Tag */ - OrthancPluginValueRepresentation_CS = 4, /*!< Code String */ - OrthancPluginValueRepresentation_DA = 5, /*!< Date */ - OrthancPluginValueRepresentation_DS = 6, /*!< Decimal String */ - OrthancPluginValueRepresentation_DT = 7, /*!< Date Time */ - OrthancPluginValueRepresentation_FD = 8, /*!< Floating Point Double */ - OrthancPluginValueRepresentation_FL = 9, /*!< Floating Point Single */ - OrthancPluginValueRepresentation_IS = 10, /*!< Integer String */ - OrthancPluginValueRepresentation_LO = 11, /*!< Long String */ - OrthancPluginValueRepresentation_LT = 12, /*!< Long Text */ - OrthancPluginValueRepresentation_OB = 13, /*!< Other Byte String */ - OrthancPluginValueRepresentation_OF = 14, /*!< Other Float String */ - OrthancPluginValueRepresentation_OW = 15, /*!< Other Word String */ - OrthancPluginValueRepresentation_PN = 16, /*!< Person Name */ - OrthancPluginValueRepresentation_SH = 17, /*!< Short String */ - OrthancPluginValueRepresentation_SL = 18, /*!< Signed Long */ - OrthancPluginValueRepresentation_SQ = 19, /*!< Sequence of Items */ - OrthancPluginValueRepresentation_SS = 20, /*!< Signed Short */ - OrthancPluginValueRepresentation_ST = 21, /*!< Short Text */ - OrthancPluginValueRepresentation_TM = 22, /*!< Time */ - OrthancPluginValueRepresentation_UI = 23, /*!< Unique Identifier (UID) */ - OrthancPluginValueRepresentation_UL = 24, /*!< Unsigned Long */ - OrthancPluginValueRepresentation_UN = 25, /*!< Unknown */ - OrthancPluginValueRepresentation_US = 26, /*!< Unsigned Short */ - OrthancPluginValueRepresentation_UT = 27, /*!< Unlimited Text */ - - _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff - } OrthancPluginValueRepresentation; - - - /** - * The possible output formats for a DICOM-to-JSON conversion. - * @ingroup Toolbox - * @see OrthancPluginDicomToJson() - **/ - typedef enum - { - OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ - OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ - OrthancPluginDicomToJsonFormat_Human = 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; - - - /** - * Flags to the creation of a DICOM file. - * @ingroup Toolbox - * @see OrthancPluginCreateDicom() - **/ - typedef enum - { - OrthancPluginCreateDicomFlags_DecodeDataUriScheme = (1 << 0), /*!< Decode fields encoded using data URI scheme */ - OrthancPluginCreateDicomFlags_GenerateIdentifiers = (1 << 1), /*!< Automatically generate DICOM identifiers */ - - _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff - } OrthancPluginCreateDicomFlags; - - - /** - * The constraints on the DICOM identifiers that must be supported - * by the database plugins. - **/ - typedef enum - { - OrthancPluginIdentifierConstraint_Equal = 1, /*!< Equal */ - OrthancPluginIdentifierConstraint_SmallerOrEqual = 2, /*!< Less or equal */ - OrthancPluginIdentifierConstraint_GreaterOrEqual = 3, /*!< More or equal */ - OrthancPluginIdentifierConstraint_Wildcard = 4, /*!< Case-sensitive wildcard matching (with * and ?) */ - - _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff - } OrthancPluginIdentifierConstraint; - - - /** - * The origin of a DICOM instance that has been received by Orthanc. - **/ - typedef enum - { - OrthancPluginInstanceOrigin_Unknown = 1, /*!< Unknown origin */ - OrthancPluginInstanceOrigin_DicomProtocol = 2, /*!< Instance received through DICOM protocol */ - OrthancPluginInstanceOrigin_RestApi = 3, /*!< Instance received through REST API of Orthanc */ - OrthancPluginInstanceOrigin_Plugin = 4, /*!< Instance added to Orthanc by a plugin */ - OrthancPluginInstanceOrigin_Lua = 5, /*!< Instance added to Orthanc by a Lua script */ - - _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff - } OrthancPluginInstanceOrigin; - - - /** - * @brief A memory buffer allocated by the core system of Orthanc. - * - * A memory buffer allocated by the core system of Orthanc. When the - * content of the buffer is not useful anymore, it must be free by a - * call to ::OrthancPluginFreeMemoryBuffer(). - **/ - typedef struct - { - /** - * @brief The content of the buffer. - **/ - void* data; - - /** - * @brief The number of bytes in the buffer. - **/ - uint32_t size; - } OrthancPluginMemoryBuffer; - - - - - /** - * @brief Opaque structure that represents the HTTP connection to the client application. - * @ingroup Callback - **/ - typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput; - - - - /** - * @brief Opaque structure that represents a DICOM instance received by Orthanc. - **/ - typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance; - - - - /** - * @brief Opaque structure that represents an image that is uncompressed in memory. - * @ingroup Images - **/ - typedef struct _OrthancPluginImage_t OrthancPluginImage; - - - - /** - * @brief Opaque structure that represents the storage area that is actually used by Orthanc. - * @ingroup Images - **/ - typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea; - - - - /** - * @brief Opaque structure to an object that represents a C-Find query. - * @ingroup Worklists - **/ - typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery; - - - - /** - * @brief Opaque structure to an object that represents the answers to a C-Find query. - * @ingroup Worklists - **/ - typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers; - - - - /** - * @brief Signature of a callback function that answers to a REST request. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) ( - OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request); - - - - /** - * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) ( - OrthancPluginDicomInstance* instance, - const char* instanceId); - - - - /** - * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) ( - OrthancPluginChangeType changeType, - OrthancPluginResourceType resourceType, - const char* resourceId); - - - - /** - * @brief Signature of a callback function to decode a DICOM instance as an image. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) ( - OrthancPluginImage** target, - const void* dicom, - const uint32_t size, - uint32_t frameIndex); - - - - /** - * @brief Signature of a function to free dynamic memory. - **/ - typedef void (*OrthancPluginFree) (void* buffer); - - - - /** - * @brief Callback for writing to the storage area. - * - * Signature of a callback function that is triggered when Orthanc writes a file to the storage area. - * - * @param uuid The UUID of the file. - * @param content The content of the file. - * @param size The size of the file. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) ( - const char* uuid, - const void* content, - int64_t size, - OrthancPluginContentType type); - - - - /** - * @brief Callback for reading from the storage area. - * - * Signature of a callback function that is triggered when Orthanc reads a file from the storage area. - * - * @param content The content of the file (output). - * @param size The size of the file (output). - * @param uuid The UUID of the file of interest. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) ( - void** content, - int64_t* size, - const char* uuid, - OrthancPluginContentType type); - - - - /** - * @brief Callback for removing a file from the storage area. - * - * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area. - * - * @param uuid The UUID of the file to be removed. - * @param type The content type corresponding to this file. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) ( - const char* uuid, - OrthancPluginContentType type); - - - - /** - * @brief Callback to handle the C-Find SCP requests received by Orthanc. - * - * Signature of a callback function that is triggered when Orthanc - * receives a C-Find SCP request against modality worklists. - * - * @param answers The target structure where answers must be stored. - * @param query The worklist query. - * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates. - * @param calledAet The Application Entity Title (AET) of the modality that is called by the request. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) ( - OrthancPluginWorklistAnswers* answers, - const OrthancPluginWorklistQuery* query, - const char* remoteAet, - const char* calledAet); - - - - /** - * @brief Data structure that contains information about the Orthanc core. - **/ - typedef struct _OrthancPluginContext_t - { - void* pluginsManager; - const char* orthancVersion; - OrthancPluginFree Free; - OrthancPluginErrorCode (*InvokeService) (struct _OrthancPluginContext_t* context, - _OrthancPluginService service, - const void* params); - } OrthancPluginContext; - - - - /** - * @brief An entry in the dictionary of DICOM tags. - **/ - typedef struct - { - uint16_t group; /*!< The group of the tag */ - uint16_t element; /*!< The element of the tag */ - OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ - uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ - uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ - } OrthancPluginDictionaryEntry; - - - - /** - * @brief Free a string. - * - * Free a string that was allocated by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param str The string to be freed. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeString( - OrthancPluginContext* context, - char* str) - { - if (str != NULL) - { - context->Free(str); - } - } - - - /** - * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc. - * - * This function checks whether the version of this C header is - * compatible with the current version of Orthanc. The result of - * this function should always be checked in the - * OrthancPluginInitialize() entry point of the plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return 1 if and only if the versions are compatible. If the - * result is 0, the initialization of the plugin should fail. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginCheckVersion( - OrthancPluginContext* context) - { - int major, minor, revision; - - if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) || - sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) || - sizeof(int32_t) != sizeof(_OrthancPluginService) || - sizeof(int32_t) != sizeof(_OrthancPluginProperty) || - sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) || - sizeof(int32_t) != sizeof(OrthancPluginContentType) || - sizeof(int32_t) != sizeof(OrthancPluginResourceType) || - sizeof(int32_t) != sizeof(OrthancPluginChangeType) || - sizeof(int32_t) != sizeof(OrthancPluginCompressionType) || - sizeof(int32_t) != sizeof(OrthancPluginImageFormat) || - sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) || - sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) || - sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) || - sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) || - sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) || - sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin)) - { - /* Mismatch in the size of the enumerations */ - return 0; - } - - /* Assume compatibility with the mainline */ - if (!strcmp(context->orthancVersion, "mainline")) - { - return 1; - } - - /* Parse the version of the Orthanc core */ - if ( -#ifdef _MSC_VER - sscanf_s -#else - sscanf -#endif - (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3) - { - return 0; - } - - /* Check the major number of the version */ - - if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) - { - return 1; - } - - if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER) - { - return 0; - } - - /* Check the minor number of the version */ - - if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) - { - return 1; - } - - if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER) - { - return 0; - } - - /* Check the revision number of the version */ - - if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER) - { - return 1; - } - else - { - return 0; - } - } - - - /** - * @brief Free a memory buffer. - * - * Free a memory buffer that was allocated by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The memory buffer to release. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeMemoryBuffer( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* buffer) - { - context->Free(buffer->data); - } - - - /** - * @brief Log an error. - * - * Log an error message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogError( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogError, message); - } - - - /** - * @brief Log a warning. - * - * Log a warning message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogWarning, message); - } - - - /** - * @brief Log an information. - * - * Log an information message using the Orthanc logging system. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param message The message to be logged. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo( - OrthancPluginContext* context, - const char* message) - { - context->InvokeService(context, _OrthancPluginService_LogInfo, message); - } - - - - typedef struct - { - const char* pathRegularExpression; - OrthancPluginRestCallback callback; - } _OrthancPluginRestCallback; - - /** - * @brief Register a REST callback. - * - * This function registers a REST callback against a regular - * expression for a URI. This function must be called during the - * initialization of the plugin, i.e. inside the - * OrthancPluginInitialize() public function. - * - * Each REST callback is guaranteed to run in mutual exclusion. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param pathRegularExpression Regular expression for the URI. May contain groups. - * @param callback The callback function to handle the REST call. - * @see OrthancPluginRegisterRestCallbackNoLock() - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback( - OrthancPluginContext* context, - const char* pathRegularExpression, - OrthancPluginRestCallback callback) - { - _OrthancPluginRestCallback params; - params.pathRegularExpression = pathRegularExpression; - params.callback = callback; - context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, ¶ms); - } - - - - /** - * @brief Register a REST callback, without locking. - * - * This function registers a REST callback against a regular - * expression for a URI. This function must be called during the - * initialization of the plugin, i.e. inside the - * OrthancPluginInitialize() public function. - * - * Contrarily to OrthancPluginRegisterRestCallback(), the callback - * will NOT be invoked in mutual exclusion. This can be useful for - * high-performance plugins that must handle concurrent requests - * (Orthanc uses a pool of threads, one thread being assigned to - * each incoming HTTP request). Of course, it is up to the plugin to - * implement the required locking mechanisms. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param pathRegularExpression Regular expression for the URI. May contain groups. - * @param callback The callback function to handle the REST call. - * @see OrthancPluginRegisterRestCallback() - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock( - OrthancPluginContext* context, - const char* pathRegularExpression, - OrthancPluginRestCallback callback) - { - _OrthancPluginRestCallback params; - params.pathRegularExpression = pathRegularExpression; - params.callback = callback; - context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, ¶ms); - } - - - - typedef struct - { - OrthancPluginOnStoredInstanceCallback callback; - } _OrthancPluginOnStoredInstanceCallback; - - /** - * @brief Register a callback for received instances. - * - * This function registers a callback function that is called - * whenever a new DICOM instance is stored into the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback function. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback( - OrthancPluginContext* context, - OrthancPluginOnStoredInstanceCallback callback) - { - _OrthancPluginOnStoredInstanceCallback params; - params.callback = callback; - - context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* answer; - uint32_t answerSize; - const char* mimeType; - } _OrthancPluginAnswerBuffer; - - /** - * @brief Answer to a REST request. - * - * This function answers to a REST request with the content of a memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the answer. - * @param answerSize Number of bytes of the answer. - * @param mimeType The MIME type of the answer. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize, - const char* mimeType) - { - _OrthancPluginAnswerBuffer params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.mimeType = mimeType; - context->InvokeService(context, _OrthancPluginService_AnswerBuffer, ¶ms); - } - - - typedef struct - { - OrthancPluginRestOutput* output; - OrthancPluginPixelFormat format; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - } _OrthancPluginCompressAndAnswerPngImage; - - typedef struct - { - OrthancPluginRestOutput* output; - OrthancPluginImageFormat imageFormat; - OrthancPluginPixelFormat pixelFormat; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - uint8_t quality; - } _OrthancPluginCompressAndAnswerImage; - - - /** - * @brief Answer to a REST request with a PNG image. - * - * This function answers to a REST request with a PNG image. The - * parameters of this function describe a memory buffer that - * contains an uncompressed image. The image will be automatically compressed - * as a PNG image by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer) - { - _OrthancPluginCompressAndAnswerImage params; - params.output = output; - params.imageFormat = OrthancPluginImageFormat_Png; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = 0; /* No quality for PNG */ - context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* instanceId; - } _OrthancPluginGetDicomForInstance; - - /** - * @brief Retrieve a DICOM instance using its Orthanc identifier. - * - * Retrieve a DICOM instance using its Orthanc identifier. The DICOM - * file is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param instanceId The Orthanc identifier of the DICOM instance of interest. - * @return 0 if success, or the error code if failure. - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetDicomForInstance( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* instanceId) - { - _OrthancPluginGetDicomForInstance params; - params.target = target; - params.instanceId = instanceId; - return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - } _OrthancPluginRestApiGet; - - /** - * @brief Make a GET call to the built-in Orthanc REST API. - * - * Make a GET call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiGetAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri) - { - _OrthancPluginRestApiGet params; - params.target = target; - params.uri = uri; - return context->InvokeService(context, _OrthancPluginService_RestApiGet, ¶ms); - } - - - - /** - * @brief Make a GET call to the REST API, as tainted by the plugins. - * - * Make a GET call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiGet - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGetAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri) - { - _OrthancPluginRestApiGet params; - params.target = target; - params.uri = uri; - return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - const char* body; - uint32_t bodySize; - } _OrthancPluginRestApiPostPut; - - /** - * @brief Make a POST call to the built-in Orthanc REST API. - * - * Make a POST call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the POST request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPostAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPost( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPost, ¶ms); - } - - - /** - * @brief Make a POST call to the REST API, as tainted by the plugins. - * - * Make a POST call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the POST request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPost - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPostAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, ¶ms); - } - - - - /** - * @brief Make a DELETE call to the built-in Orthanc REST API. - * - * Make a DELETE call to the built-in Orthanc REST API. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The URI to delete in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiDeleteAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDelete( - OrthancPluginContext* context, - const char* uri) - { - return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri); - } - - - /** - * @brief Make a DELETE call to the REST API, as tainted by the plugins. - * - * Make a DELETE call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The URI to delete in the built-in Orthanc API. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiDelete - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiDeleteAfterPlugins( - OrthancPluginContext* context, - const char* uri) - { - return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri); - } - - - - /** - * @brief Make a PUT call to the built-in Orthanc REST API. - * - * Make a PUT call to the built-in Orthanc REST API. The result to - * the query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the PUT request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPutAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPut( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPut, ¶ms); - } - - - - /** - * @brief Make a PUT call to the REST API, as tainted by the plugins. - * - * Make a PUT call to the Orthanc REST API, after all the plugins - * are applied. In other words, if some plugin overrides or adds the - * called URI to the built-in Orthanc REST API, this call will - * return the result provided by this plugin. The result to the - * query is stored into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param body The body of the PUT request. - * @param bodySize The size of the body. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiPut - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiPutAfterPlugins( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - const char* body, - uint32_t bodySize) - { - _OrthancPluginRestApiPostPut params; - params.target = target; - params.uri = uri; - params.body = body; - params.bodySize = bodySize; - return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* argument; - } _OrthancPluginOutputPlusArgument; - - /** - * @brief Redirect a REST request. - * - * This function answers to a REST request by redirecting the user - * to another URI using HTTP status 301. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param redirection Where to redirect. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* redirection) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = redirection; - context->InvokeService(context, _OrthancPluginService_Redirect, ¶ms); - } - - - - typedef struct - { - char** result; - const char* argument; - } _OrthancPluginRetrieveDynamicString; - - /** - * @brief Look for a patient. - * - * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored patients). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param patientID The Patient ID of interest. - * @return The NULL value if the patient is non-existent, or a string containing the - * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient( - OrthancPluginContext* context, - const char* patientID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = patientID; - - if (context->InvokeService(context, _OrthancPluginService_LookupPatient, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a study. - * - * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored studies). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param studyUID The Study Instance UID of interest. - * @return The NULL value if the study is non-existent, or a string containing the - * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy( - OrthancPluginContext* context, - const char* studyUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = studyUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupStudy, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a study, using the accession number. - * - * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored studies). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param accessionNumber The Accession Number of interest. - * @return The NULL value if the study is non-existent, or a string containing the - * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber( - OrthancPluginContext* context, - const char* accessionNumber) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = accessionNumber; - - if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for a series. - * - * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored series). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param seriesUID The Series Instance UID of interest. - * @return The NULL value if the series is non-existent, or a string containing the - * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries( - OrthancPluginContext* context, - const char* seriesUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = seriesUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupSeries, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Look for an instance. - * - * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018). - * This function uses the database index to run as fast as possible (it does not loop - * over all the stored instances). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param sopInstanceUID The SOP Instance UID of interest. - * @return The NULL value if the instance is non-existent, or a string containing the - * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance( - OrthancPluginContext* context, - const char* sopInstanceUID) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = sopInstanceUID; - - if (context->InvokeService(context, _OrthancPluginService_LookupInstance, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - uint16_t status; - } _OrthancPluginSendHttpStatusCode; - - /** - * @brief Send a HTTP status code. - * - * This function answers to a REST request by sending a HTTP status - * code (such as "400 - Bad Request"). Note that: - * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). - * - Redirections (status 301) must use ::OrthancPluginRedirect(). - * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). - * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param status The HTTP status code to be sent. - * @ingroup REST - * @see OrthancPluginSendHttpStatus() - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - uint16_t status) - { - _OrthancPluginSendHttpStatusCode params; - params.output = output; - params.status = status; - context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, ¶ms); - } - - - /** - * @brief Signal that a REST request is not authorized. - * - * This function answers to a REST request by signaling that it is - * not authorized. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param realm The realm for the authorization process. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* realm) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = realm; - context->InvokeService(context, _OrthancPluginService_SendUnauthorized, ¶ms); - } - - - /** - * @brief Signal that this URI does not support this HTTP method. - * - * This function answers to a REST request by signaling that the - * queried URI does not support this method. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request). - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* allowedMethods) - { - _OrthancPluginOutputPlusArgument params; - params.output = output; - params.argument = allowedMethods; - context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, ¶ms); - } - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* key; - const char* value; - } _OrthancPluginSetHttpHeader; - - /** - * @brief Set a cookie. - * - * This function sets a cookie in the HTTP client. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param cookie The cookie to be set. - * @param value The value of the cookie. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* cookie, - const char* value) - { - _OrthancPluginSetHttpHeader params; - params.output = output; - params.key = cookie; - params.value = value; - context->InvokeService(context, _OrthancPluginService_SetCookie, ¶ms); - } - - - /** - * @brief Set some HTTP header. - * - * This function sets a HTTP header in the HTTP answer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param key The HTTP header to be set. - * @param value The value of the HTTP header. - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* key, - const char* value) - { - _OrthancPluginSetHttpHeader params; - params.output = output; - params.key = key; - params.value = value; - context->InvokeService(context, _OrthancPluginService_SetHttpHeader, ¶ms); - } - - - typedef struct - { - char** resultStringToFree; - const char** resultString; - int64_t* resultInt64; - const char* key; - OrthancPluginDicomInstance* instance; - OrthancPluginInstanceOrigin* resultOrigin; /* New in Orthanc 0.9.5 SDK */ - } _OrthancPluginAccessDicomInstance; - - - /** - * @brief Get the AET of a DICOM instance. - * - * This function returns the Application Entity Title (AET) of the - * DICOM modality from which a DICOM instance originates. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The AET if success, NULL if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the size of a DICOM file. - * - * This function returns the number of bytes of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The size of the file, -1 in case of error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - int64_t size; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultInt64 = &size; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return -1; - } - else - { - return size; - } - } - - - /** - * @brief Get the data of a DICOM file. - * - * This function returns a pointer to the content of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The pointer to the DICOM data, NULL in case of error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the DICOM tag hierarchy as a JSON file. - * - * This function returns a pointer to a newly created string - * containing a JSON file. This JSON file encodes the tag hierarchy - * of the given DICOM instance. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The NULL value in case of error, or a string containing the JSON file. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultStringToFree = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the DICOM tag hierarchy as a JSON file (with simplification). - * - * This function returns a pointer to a newly created string - * containing a JSON file. This JSON file encodes the tag hierarchy - * of the given DICOM instance. In contrast with - * ::OrthancPluginGetInstanceJson(), the returned JSON file is in - * its simplified version. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The NULL value in case of error, or a string containing the JSON file. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultStringToFree = &result; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Check whether a DICOM instance is associated with some metadata. - * - * This function checks whether the DICOM instance of interest is - * associated with some metadata. As of Orthanc 0.8.1, in the - * callbacks registered by - * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only - * possibly available metadata are "ReceptionDate", "RemoteAET" and - * "IndexInSeries". - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @param metadata The metadata of interest. - * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE int OrthancPluginHasInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) - { - int64_t result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultInt64 = &result; - params.instance = instance; - params.key = metadata; - - if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return -1; - } - else - { - return (result != 0); - } - } - - - /** - * @brief Get the value of some metadata associated with a given DICOM instance. - * - * This functions returns the value of some metadata that is associated with the DICOM instance of interest. - * Before calling this function, the existence of the metadata must have been checked with - * ::OrthancPluginHasInstanceMetadata(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @param metadata The metadata of interest. - * @return The metadata value if success, NULL if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance, - const char* metadata) - { - const char* result; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultString = &result; - params.instance = instance; - params.key = metadata; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginStorageCreate create; - OrthancPluginStorageRead read; - OrthancPluginStorageRemove remove; - OrthancPluginFree free; - } _OrthancPluginRegisterStorageArea; - - /** - * @brief Register a custom storage area. - * - * This function registers a custom storage area, to replace the - * built-in way Orthanc stores its files on the filesystem. This - * function must be called during the initialization of the plugin, - * i.e. inside the OrthancPluginInitialize() public function. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param create The callback function to store a file on the custom storage area. - * @param read The callback function to read a file from the custom storage area. - * @param remove The callback function to remove a file from the custom storage area. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea( - OrthancPluginContext* context, - OrthancPluginStorageCreate create, - OrthancPluginStorageRead read, - OrthancPluginStorageRemove remove) - { - _OrthancPluginRegisterStorageArea params; - params.create = create; - params.read = read; - params.remove = remove; - -#ifdef __cplusplus - params.free = ::free; -#else - params.free = free; -#endif - - context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, ¶ms); - } - - - - /** - * @brief Return the path to the Orthanc executable. - * - * This function returns the path to the Orthanc executable. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the directory containing the Orthanc. - * - * This function returns the path to the directory containing the Orthanc executable. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the path to the configuration file(s). - * - * This function returns the path to the configuration file(s) that - * was specified when starting Orthanc. Since version 0.9.1, this - * path can refer to a folder that stores a set of configuration - * files. This function is deprecated in favor of - * OrthancPluginGetConfiguration(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the path. This string must be freed by - * OrthancPluginFreeString(). - * @see OrthancPluginGetConfiguration() - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginOnChangeCallback callback; - } _OrthancPluginOnChangeCallback; - - /** - * @brief Register a callback to monitor changes. - * - * This function registers a callback function that is called - * whenever a change happens to some DICOM resource. - * - * @warning If your change callback has to call the REST API of - * Orthanc, you should make these calls in a separate thread (with - * the events passing through a message queue). Otherwise, this - * could result in deadlocks in the presence of other plugins or Lua - * script. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback function. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback( - OrthancPluginContext* context, - OrthancPluginOnChangeCallback callback) - { - _OrthancPluginOnChangeCallback params; - params.callback = callback; - - context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, ¶ms); - } - - - - typedef struct - { - const char* plugin; - _OrthancPluginProperty property; - const char* value; - } _OrthancPluginSetPluginProperty; - - - /** - * @brief Set the URI where the plugin provides its Web interface. - * - * For plugins that come with a Web interface, this function - * declares the entry path where to find this interface. This - * information is notably used in the "Plugins" page of Orthanc - * Explorer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param uri The root URI for this plugin. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri( - OrthancPluginContext* context, - const char* uri) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_RootUri; - params.value = uri; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - /** - * @brief Set a description for this plugin. - * - * Set a description for this plugin. It is displayed in the - * "Plugins" page of Orthanc Explorer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param description The description. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription( - OrthancPluginContext* context, - const char* description) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_Description; - params.value = description; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - /** - * @brief Extend the JavaScript code of Orthanc Explorer. - * - * Add JavaScript code to customize the default behavior of Orthanc - * Explorer. This can for instance be used to add new buttons. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param javascript The custom JavaScript code. - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer( - OrthancPluginContext* context, - const char* javascript) - { - _OrthancPluginSetPluginProperty params; - params.plugin = OrthancPluginGetName(); - params.property = _OrthancPluginProperty_OrthancExplorer; - params.value = javascript; - - context->InvokeService(context, _OrthancPluginService_SetPluginProperty, ¶ms); - } - - - typedef struct - { - char** result; - int32_t property; - const char* value; - } _OrthancPluginGlobalProperty; - - - /** - * @brief Get the value of a global property. - * - * Get the value of a global property that is stored in the Orthanc database. Global - * properties whose index is below 1024 are reserved by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param property The global property of interest. - * @param defaultValue The value to return, if the global property is unset. - * @return The value of the global property, or NULL in the case of an error. This - * string must be freed by OrthancPluginFreeString(). - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty( - OrthancPluginContext* context, - int32_t property, - const char* defaultValue) - { - char* result; - - _OrthancPluginGlobalProperty params; - params.result = &result; - params.property = property; - params.value = defaultValue; - - if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Set the value of a global property. - * - * Set the value of a global property into the Orthanc - * database. Setting a global property can be used by plugins to - * save their internal parameters. Plugins are only allowed to set - * properties whose index are above or equal to 1024 (properties - * below 1024 are read-only and reserved by Orthanc). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param property The global property of interest. - * @param value The value to be set in the global property. - * @return 0 if success, or the error code if failure. - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty( - OrthancPluginContext* context, - int32_t property, - const char* value) - { - _OrthancPluginGlobalProperty params; - params.result = NULL; - params.property = property; - params.value = value; - - return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, ¶ms); - } - - - - typedef struct - { - int32_t *resultInt32; - uint32_t *resultUint32; - int64_t *resultInt64; - uint64_t *resultUint64; - } _OrthancPluginReturnSingleValue; - - /** - * @brief Get the number of command-line arguments. - * - * Retrieve the number of command-line arguments that were used to launch Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The number of arguments. - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - /** - * @brief Get the value of a command-line argument. - * - * Get the value of one of the command-line arguments that were used - * to launch Orthanc. The number of available arguments can be - * retrieved by OrthancPluginGetCommandLineArgumentsCount(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param argument The index of the argument. - * @return The value of the argument, or NULL in the case of an error. This - * string must be freed by OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument( - OrthancPluginContext* context, - uint32_t argument) - { - char* result; - - _OrthancPluginGlobalProperty params; - params.result = &result; - params.property = (int32_t) argument; - params.value = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Get the expected version of the database schema. - * - * Retrieve the expected version of the database schema. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The version. - * @ingroup Callbacks - * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase() - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - /** - * @brief Return the content of the configuration file(s). - * - * This function returns the content of the configuration that is - * used by Orthanc, formatted as a JSON string. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return NULL in the case of an error, or a newly allocated string - * containing the configuration. This string must be freed by - * OrthancPluginFreeString(). - **/ - ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context) - { - char* result; - - _OrthancPluginRetrieveDynamicString params; - params.result = &result; - params.argument = NULL; - - if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* subType; - const char* contentType; - } _OrthancPluginStartMultipartAnswer; - - /** - * @brief Start an HTTP multipart answer. - * - * Initiates a HTTP multipart answer, as the result of a REST request. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param subType The sub-type of the multipart answer ("mixed" or "related"). - * @param contentType The MIME type of the items in the multipart answer. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* subType, - const char* contentType) - { - _OrthancPluginStartMultipartAnswer params; - params.output = output; - params.subType = subType; - params.contentType = contentType; - return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, ¶ms); - } - - - /** - * @brief Send an item as a part of some HTTP multipart answer. - * - * This function sends an item as a part of some HTTP multipart - * answer that was initiated by OrthancPluginStartMultipartAnswer(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the item. - * @param answerSize Number of bytes of the item. - * @return 0 if success, or the error code if failure (this notably happens - * if the connection is closed by the client). - * @see OrthancPluginSendMultipartItem2() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize) - { - _OrthancPluginAnswerBuffer params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.mimeType = NULL; - return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const void* source; - uint32_t size; - OrthancPluginCompressionType compression; - uint8_t uncompress; - } _OrthancPluginBufferCompression; - - - /** - * @brief Compress or decompress a buffer. - * - * This function compresses or decompresses a buffer, using the - * version of the zlib library that is used by the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param source The source buffer. - * @param size The size in bytes of the source buffer. - * @param compression The compression algorithm. - * @param uncompress If set to "0", the buffer must be compressed. - * If set to "1", the buffer must be uncompressed. - * @return 0 if success, or the error code if failure. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const void* source, - uint32_t size, - OrthancPluginCompressionType compression, - uint8_t uncompress) - { - _OrthancPluginBufferCompression params; - params.target = target; - params.source = source; - params.size = size; - params.compression = compression; - params.uncompress = uncompress; - - return context->InvokeService(context, _OrthancPluginService_BufferCompression, ¶ms); - } - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* path; - } _OrthancPluginReadFile; - - /** - * @brief Read a file. - * - * Read the content of a file on the filesystem, and returns it into - * a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param path The path of the file to be read. - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReadFile( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* path) - { - _OrthancPluginReadFile params; - params.target = target; - params.path = path; - return context->InvokeService(context, _OrthancPluginService_ReadFile, ¶ms); - } - - - - typedef struct - { - const char* path; - const void* data; - uint32_t size; - } _OrthancPluginWriteFile; - - /** - * @brief Write a file. - * - * Write the content of a memory buffer to the filesystem. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param path The path of the file to be written. - * @param data The content of the memory buffer. - * @param size The size of the memory buffer. - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWriteFile( - OrthancPluginContext* context, - const char* path, - const void* data, - uint32_t size) - { - _OrthancPluginWriteFile params; - params.path = path; - params.data = data; - params.size = size; - return context->InvokeService(context, _OrthancPluginService_WriteFile, ¶ms); - } - - - - typedef struct - { - const char** target; - OrthancPluginErrorCode error; - } _OrthancPluginGetErrorDescription; - - /** - * @brief Get the description of a given error code. - * - * This function returns the description of a given error code. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param error The error code of interest. - * @return The error description. This is a statically-allocated - * string, do not free it. - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription( - OrthancPluginContext* context, - OrthancPluginErrorCode error) - { - const char* result = NULL; - - _OrthancPluginGetErrorDescription params; - params.target = &result; - params.error = error; - - if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, ¶ms) != OrthancPluginErrorCode_Success || - result == NULL) - { - return "Unknown error code"; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - uint16_t status; - const char* body; - uint32_t bodySize; - } _OrthancPluginSendHttpStatus; - - /** - * @brief Send a HTTP status, with a custom body. - * - * This function answers to a HTTP request by sending a HTTP status - * code (such as "400 - Bad Request"), together with a body - * describing the error. The body will only be returned if the - * configuration option "HttpDescribeErrors" of Orthanc is set to "true". - * - * Note that: - * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer(). - * - Redirections (status 301) must use ::OrthancPluginRedirect(). - * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized(). - * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param status The HTTP status code to be sent. - * @param body The body of the answer. - * @param bodySize The size of the body. - * @see OrthancPluginSendHttpStatusCode() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - uint16_t status, - const char* body, - uint32_t bodySize) - { - _OrthancPluginSendHttpStatus params; - params.output = output; - params.status = status; - params.body = body; - params.bodySize = bodySize; - context->InvokeService(context, _OrthancPluginService_SendHttpStatus, ¶ms); - } - - - - typedef struct - { - const OrthancPluginImage* image; - uint32_t* resultUint32; - OrthancPluginPixelFormat* resultPixelFormat; - void** resultBuffer; - } _OrthancPluginGetImageInfo; - - - /** - * @brief Return the pixel format of an image. - * - * This function returns the type of memory layout for the pixels of the given image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The pixel format. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat OrthancPluginGetImagePixelFormat( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - OrthancPluginPixelFormat target; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultPixelFormat = ⌖ - - if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, ¶ms) != OrthancPluginErrorCode_Success) - { - return OrthancPluginPixelFormat_Unknown; - } - else - { - return (OrthancPluginPixelFormat) target; - } - } - - - - /** - * @brief Return the width of an image. - * - * This function returns the width of the given image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The width. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageWidth( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - uint32_t width; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultUint32 = &width; - - if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return width; - } - } - - - - /** - * @brief Return the height of an image. - * - * This function returns the height of the given image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The height. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImageHeight( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - uint32_t height; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultUint32 = &height; - - if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return height; - } - } - - - - /** - * @brief Return the pitch of an image. - * - * This function returns the pitch of the given image. The pitch is - * defined as the number of bytes between 2 successive lines of the - * image in the memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The pitch. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetImagePitch( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - uint32_t pitch; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.resultUint32 = &pitch; - - if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return pitch; - } - } - - - - /** - * @brief Return a pointer to the content of an image. - * - * This function returns a pointer to the memory buffer that - * contains the pixels of the image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image of interest. - * @return The pointer. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE void* OrthancPluginGetImageBuffer( - OrthancPluginContext* context, - const OrthancPluginImage* image) - { - void* target = NULL; - - _OrthancPluginGetImageInfo params; - memset(¶ms, 0, sizeof(params)); - params.resultBuffer = ⌖ - params.image = image; - - if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - typedef struct - { - OrthancPluginImage** target; - const void* data; - uint32_t size; - OrthancPluginImageFormat format; - } _OrthancPluginUncompressImage; - - - /** - * @brief Decode a compressed image. - * - * This function decodes a compressed image from a memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param data Pointer to a memory buffer containing the compressed image. - * @param size Size of the memory buffer containing the compressed image. - * @param format The file format of the compressed image. - * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage( - OrthancPluginContext* context, - const void* data, - uint32_t size, - OrthancPluginImageFormat format) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginUncompressImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.data = data; - params.size = size; - params.format = format; - - if (context->InvokeService(context, _OrthancPluginService_UncompressImage, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - - typedef struct - { - OrthancPluginImage* image; - } _OrthancPluginFreeImage; - - /** - * @brief Free an image. - * - * This function frees an image that was decoded with OrthancPluginUncompressImage(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginFreeImage( - OrthancPluginContext* context, - OrthancPluginImage* image) - { - _OrthancPluginFreeImage params; - params.image = image; - - context->InvokeService(context, _OrthancPluginService_FreeImage, ¶ms); - } - - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - OrthancPluginImageFormat imageFormat; - OrthancPluginPixelFormat pixelFormat; - uint32_t width; - uint32_t height; - uint32_t pitch; - const void* buffer; - uint8_t quality; - } _OrthancPluginCompressImage; - - - /** - * @brief Encode a PNG image. - * - * This function compresses the given memory buffer containing an - * image using the PNG specification, and stores the result of the - * compression into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginCompressAndAnswerPngImage() - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer) - { - _OrthancPluginCompressImage params; - memset(¶ms, 0, sizeof(params)); - params.target = target; - params.imageFormat = OrthancPluginImageFormat_Png; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = 0; /* Unused for PNG */ - - return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); - } - - - /** - * @brief Encode a JPEG image. - * - * This function compresses the given memory buffer containing an - * image using the JPEG specification, and stores the result of the - * compression into a newly allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @param quality The quality of the JPEG encoding, between 1 (worst - * quality, best compression) and 100 (best quality, worst - * compression). - * @return 0 if success, or the error code if failure. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer, - uint8_t quality) - { - _OrthancPluginCompressImage params; - memset(¶ms, 0, sizeof(params)); - params.target = target; - params.imageFormat = OrthancPluginImageFormat_Jpeg; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = quality; - - return context->InvokeService(context, _OrthancPluginService_CompressImage, ¶ms); - } - - - - /** - * @brief Answer to a REST request with a JPEG image. - * - * This function answers to a REST request with a JPEG image. The - * parameters of this function describe a memory buffer that - * contains an uncompressed image. The image will be automatically compressed - * as a JPEG image by the core system of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param format The memory layout of the uncompressed image. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer containing the uncompressed image. - * @param quality The quality of the JPEG encoding, between 1 (worst - * quality, best compression) and 100 (best quality, worst - * compression). - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - const void* buffer, - uint8_t quality) - { - _OrthancPluginCompressAndAnswerImage params; - params.output = output; - params.imageFormat = OrthancPluginImageFormat_Jpeg; - params.pixelFormat = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - params.quality = quality; - context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, ¶ms); - } - - - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - OrthancPluginHttpMethod method; - const char* url; - const char* username; - const char* password; - const char* body; - uint32_t bodySize; - } _OrthancPluginCallHttpClient; - - - /** - * @brief Issue a HTTP GET call. - * - * Make a HTTP GET call to the given URL. The result to the query is - * stored into a newly allocated memory buffer. Favor - * OrthancPluginRestApiGet() if calling the built-in REST API of the - * Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param url The URL of interest. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpGet( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* url, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.target = target; - params.method = OrthancPluginHttpMethod_Get; - params.url = url; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - /** - * @brief Issue a HTTP POST call. - * - * Make a HTTP POST call to the given URL. The result to the query - * is stored into a newly allocated memory buffer. Favor - * OrthancPluginRestApiPost() if calling the built-in REST API of - * the Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param url The URL of interest. - * @param body The content of the body of the request. - * @param bodySize The size of the body of the request. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPost( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* url, - const char* body, - uint32_t bodySize, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.target = target; - params.method = OrthancPluginHttpMethod_Post; - params.url = url; - params.body = body; - params.bodySize = bodySize; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - /** - * @brief Issue a HTTP PUT call. - * - * Make a HTTP PUT call to the given URL. The result to the query is - * stored into a newly allocated memory buffer. Favor - * OrthancPluginRestApiPut() if calling the built-in REST API of the - * Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param url The URL of interest. - * @param body The content of the body of the request. - * @param bodySize The size of the body of the request. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpPut( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* url, - const char* body, - uint32_t bodySize, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.target = target; - params.method = OrthancPluginHttpMethod_Put; - params.url = url; - params.body = body; - params.bodySize = bodySize; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - /** - * @brief Issue a HTTP DELETE call. - * - * Make a HTTP DELETE call to the given URL. Favor - * OrthancPluginRestApiDelete() if calling the built-in REST API of - * the Orthanc instance that hosts this plugin. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param url The URL of interest. - * @param username The username (can be NULL if no password protection). - * @param password The password (can be NULL if no password protection). - * @return 0 if success, or the error code if failure. - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpDelete( - OrthancPluginContext* context, - const char* url, - const char* username, - const char* password) - { - _OrthancPluginCallHttpClient params; - memset(¶ms, 0, sizeof(params)); - - params.method = OrthancPluginHttpMethod_Delete; - params.url = url; - params.username = username; - params.password = password; - - return context->InvokeService(context, _OrthancPluginService_CallHttpClient, ¶ms); - } - - - - typedef struct - { - OrthancPluginImage** target; - const OrthancPluginImage* source; - OrthancPluginPixelFormat targetFormat; - } _OrthancPluginConvertPixelFormat; - - - /** - * @brief Change the pixel format of an image. - * - * This function creates a new image, changing the memory layout of the pixels. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param source The source image. - * @param targetFormat The target pixel format. - * @return The resulting image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat( - OrthancPluginContext* context, - const OrthancPluginImage* source, - OrthancPluginPixelFormat targetFormat) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginConvertPixelFormat params; - params.target = ⌖ - params.source = source; - params.targetFormat = targetFormat; - - if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - /** - * @brief Return the number of available fonts. - * - * This function returns the number of fonts that are built in the - * Orthanc core. These fonts can be used to draw texts on images - * through OrthancPluginDrawText(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @return The number of fonts. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount( - OrthancPluginContext* context) - { - uint32_t count = 0; - - _OrthancPluginReturnSingleValue params; - memset(¶ms, 0, sizeof(params)); - params.resultUint32 = &count; - - if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return 0; - } - else - { - return count; - } - } - - - - - typedef struct - { - uint32_t fontIndex; /* in */ - const char** name; /* out */ - uint32_t* size; /* out */ - } _OrthancPluginGetFontInfo; - - /** - * @brief Return the name of a font. - * - * This function returns the name of a font that is built in the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). - * @return The font name. This is a statically-allocated string, do not free it. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName( - OrthancPluginContext* context, - uint32_t fontIndex) - { - const char* result = NULL; - - _OrthancPluginGetFontInfo params; - memset(¶ms, 0, sizeof(params)); - params.name = &result; - params.fontIndex = fontIndex; - - if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Return the size of a font. - * - * This function returns the size of a font that is built in the Orthanc core. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). - * @return The font size. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize( - OrthancPluginContext* context, - uint32_t fontIndex) - { - uint32_t result; - - _OrthancPluginGetFontInfo params; - memset(¶ms, 0, sizeof(params)); - params.size = &result; - params.fontIndex = fontIndex; - - if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, ¶ms) != OrthancPluginErrorCode_Success) - { - return 0; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginImage* image; - uint32_t fontIndex; - const char* utf8Text; - int32_t x; - int32_t y; - uint8_t r; - uint8_t g; - uint8_t b; - } _OrthancPluginDrawText; - - - /** - * @brief Draw text on an image. - * - * This function draws some text on some image. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param image The image upon which to draw the text. - * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount(). - * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string. - * @param x The X position of the text over the image. - * @param y The Y position of the text over the image. - * @param r The value of the red color channel of the text. - * @param g The value of the green color channel of the text. - * @param b The value of the blue color channel of the text. - * @return 0 if success, other value if error. - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDrawText( - OrthancPluginContext* context, - OrthancPluginImage* image, - uint32_t fontIndex, - const char* utf8Text, - int32_t x, - int32_t y, - uint8_t r, - uint8_t g, - uint8_t b) - { - _OrthancPluginDrawText params; - memset(¶ms, 0, sizeof(params)); - params.image = image; - params.fontIndex = fontIndex; - params.utf8Text = utf8Text; - params.x = x; - params.y = y; - params.r = r; - params.g = g; - params.b = b; - - return context->InvokeService(context, _OrthancPluginService_DrawText, ¶ms); - } - - - - typedef struct - { - OrthancPluginStorageArea* storageArea; - const char* uuid; - const void* content; - uint64_t size; - OrthancPluginContentType type; - } _OrthancPluginStorageAreaCreate; - - - /** - * @brief Create a file inside the storage area. - * - * This function creates a new file inside the storage area that is - * currently used by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param storageArea The storage area. - * @param uuid The identifier of the file to be created. - * @param content The content to store in the newly created file. - * @param size The size of the content. - * @param type The type of the file content. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaCreate( - OrthancPluginContext* context, - OrthancPluginStorageArea* storageArea, - const char* uuid, - const void* content, - uint64_t size, - OrthancPluginContentType type) - { - _OrthancPluginStorageAreaCreate params; - params.storageArea = storageArea; - params.uuid = uuid; - params.content = content; - params.size = size; - params.type = type; - - return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, ¶ms); - } - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - OrthancPluginStorageArea* storageArea; - const char* uuid; - OrthancPluginContentType type; - } _OrthancPluginStorageAreaRead; - - - /** - * @brief Read a file from the storage area. - * - * This function reads the content of a given file from the storage - * area that is currently used by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param storageArea The storage area. - * @param uuid The identifier of the file to be read. - * @param type The type of the file content. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRead( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - OrthancPluginStorageArea* storageArea, - const char* uuid, - OrthancPluginContentType type) - { - _OrthancPluginStorageAreaRead params; - params.target = target; - params.storageArea = storageArea; - params.uuid = uuid; - params.type = type; - - return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, ¶ms); - } - - - typedef struct - { - OrthancPluginStorageArea* storageArea; - const char* uuid; - OrthancPluginContentType type; - } _OrthancPluginStorageAreaRemove; - - /** - * @brief Remove a file from the storage area. - * - * This function removes a given file from the storage area that is - * currently used by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param storageArea The storage area. - * @param uuid The identifier of the file to be removed. - * @param type The type of the file content. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStorageAreaRemove( - OrthancPluginContext* context, - OrthancPluginStorageArea* storageArea, - const char* uuid, - OrthancPluginContentType type) - { - _OrthancPluginStorageAreaRemove params; - params.storageArea = storageArea; - params.uuid = uuid; - params.type = type; - - return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, ¶ms); - } - - - - typedef struct - { - OrthancPluginErrorCode* target; - int32_t code; - uint16_t httpStatus; - const char* message; - } _OrthancPluginRegisterErrorCode; - - /** - * @brief Declare a custom error code for this plugin. - * - * This function declares a custom error code that can be generated - * by this plugin. This declaration is used to enrich the body of - * the HTTP answer in the case of an error, and to set the proper - * HTTP status code. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param code The error code that is internal to this plugin. - * @param httpStatus The HTTP status corresponding to this error. - * @param message The description of the error. - * @return The error code that has been assigned inside the Orthanc core. - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterErrorCode( - OrthancPluginContext* context, - int32_t code, - uint16_t httpStatus, - const char* message) - { - OrthancPluginErrorCode target; - - _OrthancPluginRegisterErrorCode params; - params.target = ⌖ - params.code = code; - params.httpStatus = httpStatus; - params.message = message; - - if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, ¶ms) == OrthancPluginErrorCode_Success) - { - return target; - } - else - { - /* There was an error while assigned the error. Use a generic code. */ - return OrthancPluginErrorCode_Plugin; - } - } - - - - typedef struct - { - uint16_t group; - uint16_t element; - OrthancPluginValueRepresentation vr; - const char* name; - uint32_t minMultiplicity; - uint32_t maxMultiplicity; - } _OrthancPluginRegisterDictionaryTag; - - /** - * @brief Register a new tag into the DICOM dictionary. - * - * This function declares a new tag in the dictionary of DICOM tags - * that are known to Orthanc. This function should be used in the - * OrthancPluginInitialize() callback. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param group The group of the tag. - * @param element The element of the tag. - * @param vr The value representation of the tag. - * @param name The nickname of the tag. - * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). - * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means - * an arbitrary multiplicity ("n"). - * @return 0 if success, other value if error. - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( - OrthancPluginContext* context, - uint16_t group, - uint16_t element, - OrthancPluginValueRepresentation vr, - const char* name, - uint32_t minMultiplicity, - uint32_t maxMultiplicity) - { - _OrthancPluginRegisterDictionaryTag params; - params.group = group; - params.element = element; - params.vr = vr; - params.name = name; - params.minMultiplicity = minMultiplicity; - params.maxMultiplicity = maxMultiplicity; - - return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, ¶ms); - } - - - - - typedef struct - { - OrthancPluginStorageArea* storageArea; - OrthancPluginResourceType level; - } _OrthancPluginReconstructMainDicomTags; - - /** - * @brief Reconstruct the main DICOM tags. - * - * This function requests the Orthanc core to reconstruct the main - * DICOM tags of all the resources of the given type. This function - * can only be used as a part of the upgrade of a custom database - * back-end - * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A - * database transaction will be automatically setup. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param storageArea The storage area. - * @param level The type of the resources of interest. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginReconstructMainDicomTags( - OrthancPluginContext* context, - OrthancPluginStorageArea* storageArea, - OrthancPluginResourceType level) - { - _OrthancPluginReconstructMainDicomTags params; - params.level = level; - params.storageArea = storageArea; - - return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, ¶ms); - } - - - 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 Flags governing the output. - * @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 Flags governing the output. - * @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; - } - } - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* uri; - uint32_t headersCount; - const char* const* headersKeys; - const char* const* headersValues; - int32_t afterPlugins; - } _OrthancPluginRestApiGet2; - - /** - * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers. - * - * Make a GET call to the Orthanc REST API with extended - * parameters. The result to the query is stored into a newly - * allocated memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param uri The URI in the built-in Orthanc API. - * @param headersCount The number of HTTP headers. - * @param headersKeys Array containing the keys of the HTTP headers. - * @param headersValues Array containing the values of the HTTP headers. - * @param afterPlugins If 0, the built-in API of Orthanc is used. - * If 1, the API is tainted by the plugins. - * @return 0 if success, or the error code if failure. - * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins - * @ingroup Orthanc - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRestApiGet2( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* uri, - uint32_t headersCount, - const char* const* headersKeys, - const char* const* headersValues, - int32_t afterPlugins) - { - _OrthancPluginRestApiGet2 params; - params.target = target; - params.uri = uri; - params.headersCount = headersCount; - params.headersKeys = headersKeys; - params.headersValues = headersValues; - params.afterPlugins = afterPlugins; - - return context->InvokeService(context, _OrthancPluginService_RestApiGet2, ¶ms); - } - - - - typedef struct - { - OrthancPluginWorklistCallback callback; - } _OrthancPluginWorklistCallback; - - /** - * @brief Register a callback to handle modality worklists requests. - * - * This function registers a callback to handle C-Find SCP requests - * on modality worklists. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback( - OrthancPluginContext* context, - OrthancPluginWorklistCallback callback) - { - _OrthancPluginWorklistCallback params; - params.callback = callback; - - return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginWorklistAnswers* answers; - const OrthancPluginWorklistQuery* query; - const void* dicom; - uint32_t size; - } _OrthancPluginWorklistAnswersOperation; - - /** - * @brief Add one answer to some modality worklist request. - * - * This function adds one worklist (encoded as a DICOM file) to the - * set of answers corresponding to some C-Find SCP request against - * modality worklists. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param answers The set of answers. - * @param query The worklist query, as received by the callback. - * @param dicom The worklist to answer, encoded as a DICOM file. - * @param size The size of the DICOM file. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistAddAnswer( - OrthancPluginContext* context, - OrthancPluginWorklistAnswers* answers, - const OrthancPluginWorklistQuery* query, - const void* dicom, - uint32_t size) - { - _OrthancPluginWorklistAnswersOperation params; - params.answers = answers; - params.query = query; - params.dicom = dicom; - params.size = size; - - return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, ¶ms); - } - - - /** - * @brief Mark the set of worklist answers as incomplete. - * - * This function marks as incomplete the set of answers - * corresponding to some C-Find SCP request against modality - * worklists. This must be used if canceling the handling of a - * request when too many answers are to be returned. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param answers The set of answers. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistMarkIncomplete( - OrthancPluginContext* context, - OrthancPluginWorklistAnswers* answers) - { - _OrthancPluginWorklistAnswersOperation params; - params.answers = answers; - params.query = NULL; - params.dicom = NULL; - params.size = 0; - - return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, ¶ms); - } - - - typedef struct - { - const OrthancPluginWorklistQuery* query; - const void* dicom; - uint32_t size; - int32_t* isMatch; - OrthancPluginMemoryBuffer* target; - } _OrthancPluginWorklistQueryOperation; - - /** - * @brief Test whether a worklist matches the query. - * - * This function checks whether one worklist (encoded as a DICOM - * file) matches the C-Find SCP query against modality - * worklists. This function must be called before adding the - * worklist as an answer through OrthancPluginWorklistAddAnswer(). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param query The worklist query, as received by the callback. - * @param dicom The worklist to answer, encoded as a DICOM file. - * @param size The size of the DICOM file. - * @return 1 if the worklist matches the query, 0 otherwise. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE int32_t OrthancPluginWorklistIsMatch( - OrthancPluginContext* context, - const OrthancPluginWorklistQuery* query, - const void* dicom, - uint32_t size) - { - int32_t isMatch = 0; - - _OrthancPluginWorklistQueryOperation params; - params.query = query; - params.dicom = dicom; - params.size = size; - params.isMatch = &isMatch; - params.target = NULL; - - if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, ¶ms) == OrthancPluginErrorCode_Success) - { - return isMatch; - } - else - { - /* Error: Assume non-match */ - return 0; - } - } - - - /** - * @brief Retrieve the worklist query as a DICOM file. - * - * This function retrieves the DICOM file that underlies a C-Find - * SCP query against modality worklists. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param query The worklist query, as received by the callback. - * @return 0 if success, other value if error. - * @ingroup Worklists - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginWorklistGetDicomQuery( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const OrthancPluginWorklistQuery* query) - { - _OrthancPluginWorklistQueryOperation params; - params.query = query; - params.dicom = NULL; - params.size = 0; - params.isMatch = NULL; - params.target = target; - - return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, ¶ms); - } - - - /** - * @brief Get the origin of a DICOM file. - * - * This function returns the origin of a DICOM instance that has been received by Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param instance The instance of interest. - * @return The origin of the instance. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin( - OrthancPluginContext* context, - OrthancPluginDicomInstance* instance) - { - OrthancPluginInstanceOrigin origin; - - _OrthancPluginAccessDicomInstance params; - memset(¶ms, 0, sizeof(params)); - params.resultOrigin = &origin; - params.instance = instance; - - if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return OrthancPluginInstanceOrigin_Unknown; - } - else - { - return origin; - } - } - - - typedef struct - { - OrthancPluginMemoryBuffer* target; - const char* json; - const OrthancPluginImage* pixelData; - OrthancPluginCreateDicomFlags flags; - } _OrthancPluginCreateDicom; - - /** - * @brief Create a DICOM instance from a JSON string and an image. - * - * This function takes as input a string containing a JSON file - * describing the content of a DICOM instance. As an output, it - * writes the corresponding DICOM instance to a newly allocated - * memory buffer. Additionally, an image to be encoded within the - * DICOM instance can also be provided. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer(). - * @param json The input JSON file. - * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme. - * @param flags Flags governing the output. - * @return 0 if success, other value if error. - * @ingroup Toolbox - * @see OrthancPluginDicomBufferToJson - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* target, - const char* json, - const OrthancPluginImage* pixelData, - OrthancPluginCreateDicomFlags flags) - { - _OrthancPluginCreateDicom params; - params.target = target; - params.json = json; - params.pixelData = pixelData; - params.flags = flags; - - return context->InvokeService(context, _OrthancPluginService_CreateDicom, ¶ms); - } - - - typedef struct - { - OrthancPluginDecodeImageCallback callback; - } _OrthancPluginDecodeImageCallback; - - /** - * @brief Register a callback to handle the decoding of DICOM images. - * - * This function registers a custom callback to the decoding of - * DICOM images, replacing the built-in decoder of Orthanc. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param callback The callback. - * @return 0 if success, other value if error. - * @ingroup Callbacks - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback( - OrthancPluginContext* context, - OrthancPluginDecodeImageCallback callback) - { - _OrthancPluginDecodeImageCallback params; - params.callback = callback; - - return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, ¶ms); - } - - - - typedef struct - { - OrthancPluginImage** target; - OrthancPluginPixelFormat format; - uint32_t width; - uint32_t height; - uint32_t pitch; - void* buffer; - const void* constBuffer; - uint32_t bufferSize; - uint32_t frameIndex; - } _OrthancPluginCreateImage; - - - /** - * @brief Create an image. - * - * This function creates an image of given size and format. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param format The format of the pixels. - * @param width The width of the image. - * @param height The height of the image. - * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage( - OrthancPluginContext* context, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginCreateImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.format = format; - params.width = width; - params.height = height; - - if (context->InvokeService(context, _OrthancPluginService_CreateImage, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - /** - * @brief Create an image pointing to a memory buffer. - * - * This function creates an image whose content points to a memory - * buffer managed by the plugin. Note that the buffer is directly - * accessed, no memory is allocated and no data is copied. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param format The format of the pixels. - * @param width The width of the image. - * @param height The height of the image. - * @param pitch The pitch of the image (i.e. the number of bytes - * between 2 successive lines of the image in the memory buffer). - * @param buffer The memory buffer. - * @return The newly allocated image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor( - OrthancPluginContext* context, - OrthancPluginPixelFormat format, - uint32_t width, - uint32_t height, - uint32_t pitch, - void* buffer) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginCreateImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.format = format; - params.width = width; - params.height = height; - params.pitch = pitch; - params.buffer = buffer; - - if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - /** - * @brief Decode one frame from a DICOM instance. - * - * This function decodes one frame of a DICOM image that is stored - * in a memory buffer. This function will give the same result as - * OrthancPluginUncompressImage() for single-frame DICOM images. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer Pointer to a memory buffer containing the DICOM image. - * @param bufferSize Size of the memory buffer containing the DICOM image. - * @param frameIndex The index of the frame of interest in a multi-frame image. - * @return The uncompressed image. It must be freed with OrthancPluginFreeImage(). - * @ingroup Images - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage( - OrthancPluginContext* context, - const void* buffer, - uint32_t bufferSize, - uint32_t frameIndex) - { - OrthancPluginImage* target = NULL; - - _OrthancPluginCreateImage params; - memset(¶ms, 0, sizeof(params)); - params.target = ⌖ - params.constBuffer = buffer; - params.bufferSize = bufferSize; - params.frameIndex = frameIndex; - - if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, ¶ms) != OrthancPluginErrorCode_Success) - { - return NULL; - } - else - { - return target; - } - } - - - - typedef struct - { - char** result; - const void* buffer; - uint32_t size; - } _OrthancPluginComputeHash; - - /** - * @brief Compute an MD5 hash. - * - * This functions computes the MD5 cryptographic hash of the given memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The source memory buffer. - * @param size The size in bytes of the source buffer. - * @return The NULL value in case of error, or a string containing the cryptographic hash. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5( - OrthancPluginContext* context, - const void* buffer, - uint32_t size) - { - char* result; - - _OrthancPluginComputeHash params; - params.result = &result; - params.buffer = buffer; - params.size = size; - - if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - /** - * @brief Compute a SHA-1 hash. - * - * This functions computes the SHA-1 cryptographic hash of the given memory buffer. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param buffer The source memory buffer. - * @param size The size in bytes of the source buffer. - * @return The NULL value in case of error, or a string containing the cryptographic hash. - * This string must be freed by OrthancPluginFreeString(). - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1( - OrthancPluginContext* context, - const void* buffer, - uint32_t size) - { - char* result; - - _OrthancPluginComputeHash params; - params.result = &result; - params.buffer = buffer; - params.size = size; - - if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, ¶ms) != OrthancPluginErrorCode_Success) - { - /* Error */ - return NULL; - } - else - { - return result; - } - } - - - - typedef struct - { - OrthancPluginDictionaryEntry* target; - const char* name; - } _OrthancPluginLookupDictionary; - - /** - * @brief Get information about the given DICOM tag. - * - * This functions makes a lookup in the dictionary of DICOM tags - * that are known to Orthanc, and returns information about this - * tag. The tag can be specified using its human-readable name - * (e.g. "PatientName") or a set of two hexadecimal numbers - * (e.g. "0010-0020"). - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param target Where to store the information about the tag. - * @param name The name of the DICOM tag. - * @return 0 if success, other value if error. - * @ingroup Toolbox - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( - OrthancPluginContext* context, - OrthancPluginDictionaryEntry* target, - const char* name) - { - _OrthancPluginLookupDictionary params; - params.target = target; - params.name = name; - return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); - } - - - - typedef struct - { - OrthancPluginRestOutput* output; - const char* answer; - uint32_t answerSize; - uint32_t headersCount; - const char* const* headersKeys; - const char* const* headersValues; - } _OrthancPluginSendMultipartItem2; - - /** - * @brief Send an item as a part of some HTTP multipart answer, with custom headers. - * - * This function sends an item as a part of some HTTP multipart - * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to - * OrthancPluginSendMultipartItem(), this function will set HTTP header associated - * with the item. - * - * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). - * @param output The HTTP connection to the client application. - * @param answer Pointer to the memory buffer containing the item. - * @param answerSize Number of bytes of the item. - * @param headersCount The number of HTTP headers. - * @param headersKeys Array containing the keys of the HTTP headers. - * @param headersValues Array containing the values of the HTTP headers. - * @return 0 if success, or the error code if failure (this notably happens - * if the connection is closed by the client). - * @see OrthancPluginSendMultipartItem() - * @ingroup REST - **/ - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2( - OrthancPluginContext* context, - OrthancPluginRestOutput* output, - const char* answer, - uint32_t answerSize, - uint32_t headersCount, - const char* const* headersKeys, - const char* const* headersValues) - { - _OrthancPluginSendMultipartItem2 params; - params.output = output; - params.answer = answer; - params.answerSize = answerSize; - params.headersCount = headersCount; - params.headersKeys = headersKeys; - params.headersValues = headersValues; - - return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, ¶ms); - } - - -#ifdef __cplusplus -} -#endif - - -/** @} */ - diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/Resources/Styles/_button.scss --- a/StoneWebViewer/Resources/Styles/_button.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -// clean icon buttons -.wvButton { - // Remove default styles. Take care - this class may either be used - // with or - - - - -
-
- -
-
- - -
-
- -
-
-
- - -
- -
-
- -
-
- -
- -

For patients, teachers and researchers.

-
-
- -
-
-
-
- - - - - {{ study.tags['0008,1030'] }} -
- {{ study.tags['0008,0020'] }} -
- -
-
    -
  • -
    -
    - -
    - - - - - - -
    - - - - -
    {{ series[seriesIndex].length }}
    -
    -
    - -
    -

    - [{{ series[seriesIndex].tags['0008,0060'] }}] - {{ series[seriesIndex].tags['0008,103e'] }} -

    -
    -
  • -
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
- -
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- - - -
- -
- -
- -
- -
- -
- -
- -
-
- - -
-
- - - - -
-
- -
- - - - - - - - - - - - - - - - - - - - diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/WebAssembly/CMakeLists.txt --- a/StoneWebViewer/WebAssembly/CMakeLists.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(OrthancStone) - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- -set(USE_WASM ON CACHE BOOL "") - -set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND ON CACHE BOOL "") - -set(WASM_FLAGS "-s WASM=1 -s FETCH=1") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - -# Stone of Orthanc configuration -# --------------------------------------------------------------- -set(ALLOW_DOWNLOADS ON) - -include(${CMAKE_SOURCE_DIR}/../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) - -SET(ENABLE_DCMTK ON) -SET(ENABLE_DCMTK_NETWORKING OFF) -SET(ENABLE_DCMTK_TRANSCODING OFF) -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_WASM ON) -SET(ORTHANC_SANDBOXED ON) - -# this will set up the build system for Stone of Orthanc and will -# populate the ORTHANC_STONE_SOURCES CMake variable -include(${ORTHANC_STONE_ROOT}/Resources/CMake/OrthancStoneConfiguration.cmake) - -if (CMAKE_BUILD_TYPE MATCHES Debug) - # specific flags go here -elseif (CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) - # specific flags go here -elseif (CMAKE_BUILD_TYPE MATCHES Release) - # specific flags go here -else() - message(FATAL_ERROR "CMAKE_BUILD_TYPE must match either Debug, RelWithDebInfo or Release" ) -endif() - -################################################################################ - -project(StoneWebViewer) - - -# Create the wrapper to call C++ from JavaScript -# --------------------------------------------------------------- - -set(LIBCLANG "libclang-4.0.so.1" CACHE PATH "Version of clang to generate the code model") -set(STONE_WRAPPER ${CMAKE_CURRENT_BINARY_DIR}/stone.js) - -add_custom_command( - COMMAND - ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py --libclang=${LIBCLANG} ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp > ${STONE_WRAPPER} - DEPENDS - ${CMAKE_SOURCE_DIR}/StoneWebViewer.cpp - ${CMAKE_SOURCE_DIR}/ParseWebAssemblyExports.py - OUTPUT - ${STONE_WRAPPER} - ) - -add_custom_target(StoneWrapper - DEPENDS - ${STONE_WRAPPER} - ) - - -# Define the WASM module -# --------------------------------------------------------------- - -add_executable(StoneWebViewer - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - StoneWebViewer.cpp - ) - -# Make sure to have the wrapper generated -add_dependencies(StoneWebViewer StoneWrapper) - - -# Declare installation files for the module -# --------------------------------------------------------------- - -install( - TARGETS StoneWebViewer - RUNTIME DESTINATION . - ) - - -# Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.js -# (the generated JS loader for the WASM module) is handled by the `install` -# section above: it is considered to be the binary output of -# the linker. -# --------------------------------------------------------------- -install( - FILES - ${CMAKE_CURRENT_BINARY_DIR}/StoneWebViewer.wasm - ${CMAKE_SOURCE_DIR}/../WebApplication/app.css - ${CMAKE_SOURCE_DIR}/../WebApplication/app.js - ${CMAKE_SOURCE_DIR}/../WebApplication/index.html - ${STONE_WRAPPER} - DESTINATION . - ) - -install( - FILES - ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x1.png - ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid1x2.png - ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x1.png - ${CMAKE_SOURCE_DIR}/../WebApplication/img/grid2x2.png - ${CMAKE_SOURCE_DIR}/../WebApplication/img/loading.gif - DESTINATION img - ) diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/WebAssembly/NOTES.txt --- a/StoneWebViewer/WebAssembly/NOTES.txt Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ - -Building WebAssembly samples using Docker -========================================= - -The script "./docker-build.sh" can be used to quickly build the -WebAssembly samples on any GNU/Linux distribution equipped with -Docker. This avoids newcomers to install Emscripten and learn the -CMake options. Just type: - -$ ./docker-build.sh Release - -After successful build, the binaries will be installed in the -following folder (i.e. in the folder "wasm-binaries" at the root of -the source distribution): - -$ ls -l ../../wasm-binaries - - -NB: The source code of the Docker build environment can be found at -the following location: -https://github.com/jodogne/OrthancDocker/tree/master/wasm-builder - - -Native compilation (without Docker) -=================================== - -Install Emscripten: -https://emscripten.org/docs/getting_started/downloads.html - -Then, if the installation path is "~/Downloads/emsdk/": - -# source ~/Downloads/emsdk/emsdk_env.sh -# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py --- a/StoneWebViewer/WebAssembly/ParseWebAssemblyExports.py Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ -#!/usr/bin/env python - -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2020 Osimis S.A., 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 . - - -# Ubuntu 20.04: -# sudo apt-get install python-clang-6.0 -# ./ParseWebAssemblyExports.py --libclang=libclang-6.0.so.1 ./Test.cpp - -# Ubuntu 18.04: -# sudo apt-get install python-clang-4.0 -# ./ParseWebAssemblyExports.py --libclang=libclang-4.0.so.1 ./Test.cpp - -# Ubuntu 14.04: -# ./ParseWebAssemblyExports.py --libclang=libclang-3.6.so.1 ./Test.cpp - - -import sys -import clang.cindex -import pystache -import argparse - -## -## Parse the command-line arguments -## - -parser = argparse.ArgumentParser(description = 'Parse WebAssembly C++ source file, and create a basic JavaScript wrapper.') -parser.add_argument('--libclang', - default = '', - help = 'manually provides the path to the libclang shared library') -parser.add_argument('source', - help = 'Input C++ file') - -args = parser.parse_args() - - - -if len(args.libclang) != 0: - clang.cindex.Config.set_library_file(args.libclang) - -index = clang.cindex.Index.create() - -# PARSE_SKIP_FUNCTION_BODIES prevents clang from failing because of -# undefined types, which prevents compilation of functions -tu = index.parse(args.source, - [ '-DEMSCRIPTEN_KEEPALIVE=__attribute__((annotate("WebAssembly")))' ], - options = clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) - - - -TEMPLATE = ''' -const Stone = function() { - {{#enumerations}} - this.{{name}} = { - {{#values}} - {{name}}: {{value}}{{separator}} - {{/values}} - }; - - {{/enumerations}} - {{#functions}} - this._{{name}} = undefined; - {{/functions}} -}; - -Stone.prototype.Setup = function(Module) { - {{#functions}} - this._{{name}} = Module.cwrap('{{name}}', {{{returnType}}}, [ {{#args}}{{{type}}}{{^last}}, {{/last}}{{/args}} ]); - {{/functions}} -}; - -{{#functions}} -Stone.prototype.{{name}} = function({{#args}}{{name}}{{^last}}, {{/last}}{{/args}}) { - {{#hasReturn}}return {{/hasReturn}}this._{{name}}({{#args}}{{name}}{{^last}}, {{/last}}{{/args}}); -}; - -{{/functions}} -var stone = new Stone(); -''' - - - - -# WARNING: Undefined types are mapped as "int" - -functions = [] -enumerations = [] - -def ToUpperCase(source): - target = source[0] - for c in source[1:]: - if c.isupper(): - target += '_' - target += c.upper() - return target - - - -def IsExported(node): - for child in node.get_children(): - if (child.kind == clang.cindex.CursorKind.ANNOTATE_ATTR and - child.displayname == 'WebAssembly'): - return True - - return False - - -def Explore(node): - if node.kind == clang.cindex.CursorKind.ENUM_DECL: - if IsExported(node): - name = node.spelling - values = [] - for value in node.get_children(): - if (value.spelling.startswith(name + '_')): - s = value.spelling[len(name) + 1:] - if len(values) > 0: - values[-1]['separator'] = ',' - values.append({ - 'name' : ToUpperCase(s), - 'value' : value.enum_value - }) - - enumerations.append({ - 'name' : name, - 'values' : values - }) - - if node.kind == clang.cindex.CursorKind.FUNCTION_DECL: - if IsExported(node): - f = { - 'name' : node.spelling, - 'args' : [], - } - - returnType = node.result_type.spelling - if returnType == 'void': - f['hasReturn'] = False - f['returnType'] = "null" - elif returnType == 'const char *': - f['hasReturn'] = True - f['returnType'] = "'string'" - elif returnType in [ 'int', 'unsigned int' ]: - f['hasReturn'] = True - f['returnType'] = "'int'" - else: - raise Exception('Unknown return type in function "%s()": %s' % (node.spelling, returnType)) - - for child in node.get_children(): - if child.kind == clang.cindex.CursorKind.PARM_DECL: - arg = { - 'name' : child.displayname, - } - - argType = child.type.spelling - if argType == 'int': - arg['type'] = "'int'" - elif argType == 'const char *': - arg['type'] = "'string'" - else: - raise Exception('Unknown type for argument "%s" in function "%s()": %s' % - (child.displayname, node.spelling, argType)) - - f['args'].append(arg) - - if len(f['args']) != 0: - f['args'][-1]['last'] = True - - functions.append(f) - - for child in node.get_children(): - Explore(child) - -Explore(tu.cursor) - - - -print(pystache.render(TEMPLATE, { - 'functions' : functions, - 'enumerations' : enumerations -})) diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2266 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., 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 . - **/ - - -#include - - -#define DISPATCH_JAVASCRIPT_EVENT(name) \ - EM_ASM( \ - const customEvent = document.createEvent("CustomEvent"); \ - customEvent.initCustomEvent(name, false, false, undefined); \ - window.dispatchEvent(customEvent); \ - ); - - -#define EXTERN_CATCH_EXCEPTIONS \ - catch (Orthanc::OrthancException& e) \ - { \ - LOG(ERROR) << "OrthancException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (OrthancStone::StoneException& e) \ - { \ - LOG(ERROR) << "StoneException: " << e.What(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (std::exception& e) \ - { \ - LOG(ERROR) << "Runtime error: " << e.what(); \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } \ - catch (...) \ - { \ - LOG(ERROR) << "Native exception"; \ - DISPATCH_JAVASCRIPT_EVENT("StoneException"); \ - } - - -#include -#include -#include -#include -#include -#include -#include - -#include "../../OrthancStone/Sources/Loaders/DicomResourcesLoader.h" -#include "../../OrthancStone/Sources/Loaders/SeriesMetadataLoader.h" -#include "../../OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h" -#include "../../OrthancStone/Sources/Loaders/WebAssemblyLoadersContext.h" -#include "../../OrthancStone/Sources/Messages/ObserverBase.h" -#include "../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h" -#include "../../OrthancStone/Sources/Scene2D/ColorTextureSceneLayer.h" -#include "../../OrthancStone/Sources/Scene2D/FloatTextureSceneLayer.h" -#include "../../OrthancStone/Sources/Scene2D/PolylineSceneLayer.h" -#include "../../OrthancStone/Sources/StoneException.h" -#include "../../OrthancStone/Sources/Toolbox/DicomInstanceParameters.h" -#include "../../OrthancStone/Sources/Toolbox/GeometryToolbox.h" -#include "../../OrthancStone/Sources/Toolbox/SortedFrames.h" -#include "../../OrthancStone/Sources/Viewport/WebGLViewport.h" - -#include -#include - - -enum EMSCRIPTEN_KEEPALIVE ThumbnailType -{ - ThumbnailType_Image, - ThumbnailType_NoPreview, - ThumbnailType_Pdf, - ThumbnailType_Video, - ThumbnailType_Loading, - ThumbnailType_Unknown -}; - - -enum EMSCRIPTEN_KEEPALIVE DisplayedFrameQuality -{ -DisplayedFrameQuality_None, - DisplayedFrameQuality_Low, - DisplayedFrameQuality_High - }; - - - -static const int PRIORITY_HIGH = -100; -static const int PRIORITY_LOW = 100; -static const int PRIORITY_NORMAL = 0; - -static const unsigned int QUALITY_JPEG = 0; -static const unsigned int QUALITY_FULL = 1; - -class ResourcesLoader : public OrthancStone::ObserverBase -{ -public: - class IObserver : public boost::noncopyable - { - public: - virtual ~IObserver() - { - } - - virtual void SignalResourcesLoaded() = 0; - - virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) = 0; - - virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) = 0; - }; - -private: - std::unique_ptr observer_; - OrthancStone::DicomSource source_; - size_t pending_; - boost::shared_ptr studies_; - boost::shared_ptr series_; - boost::shared_ptr resourcesLoader_; - boost::shared_ptr thumbnailsLoader_; - boost::shared_ptr metadataLoader_; - - ResourcesLoader(const OrthancStone::DicomSource& source) : - source_(source), - pending_(0), - studies_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID)), - series_(new OrthancStone::LoadedDicomResources(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID)) - { - } - - void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) - { - const Orthanc::SingleValueObject& payload = - dynamic_cast&>(message.GetUserPayload()); - - OrthancStone::LoadedDicomResources& dicom = *message.GetResources(); - - LOG(INFO) << "resources loaded: " << dicom.GetSize() - << ", " << Orthanc::EnumerationToString(payload.GetValue()); - - if (payload.GetValue() == Orthanc::ResourceType_Series) - { - for (size_t i = 0; i < dicom.GetSize(); i++) - { - std::string studyInstanceUid, seriesInstanceUid; - if (dicom.GetResource(i).LookupStringValue( - studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && - dicom.GetResource(i).LookupStringValue( - seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) - { - thumbnailsLoader_->ScheduleLoadThumbnail(source_, "", studyInstanceUid, seriesInstanceUid); - metadataLoader_->ScheduleLoadSeries(PRIORITY_LOW + 1, source_, studyInstanceUid, seriesInstanceUid); - } - } - } - - if (pending_ == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - pending_ --; - if (pending_ == 0 && - observer_.get() != NULL) - { - observer_->SignalResourcesLoaded(); - } - } - } - - void Handle(const OrthancStone::SeriesThumbnailsLoader::SuccessMessage& message) - { - if (observer_.get() != NULL) - { - observer_->SignalSeriesThumbnailLoaded( - message.GetStudyInstanceUid(), message.GetSeriesInstanceUid()); - } - } - - void Handle(const OrthancStone::SeriesMetadataLoader::SuccessMessage& message) - { - if (observer_.get() != NULL) - { - observer_->SignalSeriesMetadataLoaded( - message.GetStudyInstanceUid(), message.GetSeriesInstanceUid()); - } - } - - void FetchInternal(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) - { - // Firstly, load the study - Orthanc::DicomMap filter; - filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false); - - std::set tags; - tags.insert(Orthanc::DICOM_TAG_STUDY_DESCRIPTION); // Necessary for Orthanc DICOMweb plugin - - resourcesLoader_->ScheduleQido( - studies_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Study, filter, tags, - new Orthanc::SingleValueObject(Orthanc::ResourceType_Study)); - - // Secondly, load the series - if (!seriesInstanceUid.empty()) - { - filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false); - } - - tags.insert(Orthanc::DICOM_TAG_SERIES_NUMBER); // Necessary for Google Cloud Platform - - resourcesLoader_->ScheduleQido( - series_, PRIORITY_HIGH, source_, Orthanc::ResourceType_Series, filter, tags, - new Orthanc::SingleValueObject(Orthanc::ResourceType_Series)); - - pending_ += 2; - } - -public: - static boost::shared_ptr Create(OrthancStone::ILoadersContext::ILock& lock, - const OrthancStone::DicomSource& source) - { - boost::shared_ptr loader(new ResourcesLoader(source)); - - loader->resourcesLoader_ = OrthancStone::DicomResourcesLoader::Create(lock); - loader->thumbnailsLoader_ = OrthancStone::SeriesThumbnailsLoader::Create(lock, PRIORITY_LOW); - loader->metadataLoader_ = OrthancStone::SeriesMetadataLoader::Create(lock); - - loader->Register( - *loader->resourcesLoader_, &ResourcesLoader::Handle); - - loader->Register( - *loader->thumbnailsLoader_, &ResourcesLoader::Handle); - - loader->Register( - *loader->metadataLoader_, &ResourcesLoader::Handle); - - return loader; - } - - void FetchAllStudies() - { - FetchInternal("", ""); - } - - void FetchStudy(const std::string& studyInstanceUid) - { - FetchInternal(studyInstanceUid, ""); - } - - void FetchSeries(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) - { - FetchInternal(studyInstanceUid, seriesInstanceUid); - } - - size_t GetStudiesCount() const - { - return studies_->GetSize(); - } - - size_t GetSeriesCount() const - { - return series_->GetSize(); - } - - void GetStudy(Orthanc::DicomMap& target, - size_t i) - { - target.Assign(studies_->GetResource(i)); - } - - void GetSeries(Orthanc::DicomMap& target, - size_t i) - { - target.Assign(series_->GetResource(i)); - - // Complement with the study-level tags - std::string studyInstanceUid; - if (target.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) && - studies_->HasResource(studyInstanceUid)) - { - studies_->MergeResource(target, studyInstanceUid); - } - } - - OrthancStone::SeriesThumbnailType GetSeriesThumbnail(std::string& image, - std::string& mime, - const std::string& seriesInstanceUid) - { - return thumbnailsLoader_->GetSeriesThumbnail(image, mime, seriesInstanceUid); - } - - void FetchSeriesMetadata(int priority, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) - { - metadataLoader_->ScheduleLoadSeries(priority, source_, studyInstanceUid, seriesInstanceUid); - } - - bool IsSeriesComplete(const std::string& seriesInstanceUid) - { - OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); - return accessor.IsComplete(); - } - - bool SortSeriesFrames(OrthancStone::SortedFrames& target, - const std::string& seriesInstanceUid) - { - OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); - - if (accessor.IsComplete()) - { - target.Clear(); - - for (size_t i = 0; i < accessor.GetInstancesCount(); i++) - { - target.AddInstance(accessor.GetInstance(i)); - } - - target.Sort(); - - return true; - } - else - { - return false; - } - } - - void AcquireObserver(IObserver* observer) - { - observer_.reset(observer); - } -}; - - - -class FramesCache : public boost::noncopyable -{ -private: - class CachedImage : public Orthanc::ICacheable - { - private: - std::unique_ptr image_; - unsigned int quality_; - - public: - CachedImage(Orthanc::ImageAccessor* image, - unsigned int quality) : - image_(image), - quality_(quality) - { - assert(image != NULL); - } - - virtual size_t GetMemoryUsage() const - { - assert(image_.get() != NULL); - return (image_->GetBytesPerPixel() * image_->GetPitch() * image_->GetHeight()); - } - - const Orthanc::ImageAccessor& GetImage() const - { - assert(image_.get() != NULL); - return *image_; - } - - unsigned int GetQuality() const - { - return quality_; - } - }; - - - static std::string GetKey(const std::string& sopInstanceUid, - size_t frameIndex) - { - return sopInstanceUid + "|" + boost::lexical_cast(frameIndex); - } - - - Orthanc::MemoryObjectCache cache_; - -public: - FramesCache() - { - SetMaximumSize(100 * 1024 * 1024); // 100 MB - } - - size_t GetMaximumSize() - { - return cache_.GetMaximumSize(); - } - - void SetMaximumSize(size_t size) - { - cache_.SetMaximumSize(size); - } - - /** - * Returns "true" iff the provided image has better quality than the - * previously cached one, or if no cache was previously available. - **/ - bool Acquire(const std::string& sopInstanceUid, - size_t frameIndex, - Orthanc::ImageAccessor* image /* transfer ownership */, - unsigned int quality) - { - std::unique_ptr protection(image); - - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else if (image->GetFormat() != Orthanc::PixelFormat_Float32 && - image->GetFormat() != Orthanc::PixelFormat_RGB24) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - const std::string& key = GetKey(sopInstanceUid, frameIndex); - - bool invalidate = false; - - { - /** - * Access the previous cached entry, with side effect of tagging - * it as the most recently accessed frame (update of LRU recycling) - **/ - Orthanc::MemoryObjectCache::Accessor accessor(cache_, key, false /* unique lock */); - - if (accessor.IsValid()) - { - const CachedImage& previous = dynamic_cast(accessor.GetValue()); - - // There is already a cached image for this frame - if (previous.GetQuality() < quality) - { - // The previously stored image has poorer quality - invalidate = true; - } - else - { - // No update in the quality, don't change the cache - return false; - } - } - else - { - invalidate = false; - } - } - - if (invalidate) - { - cache_.Invalidate(key); - } - - cache_.Acquire(key, new CachedImage(protection.release(), quality)); - return true; - } - - class Accessor : public boost::noncopyable - { - private: - Orthanc::MemoryObjectCache::Accessor accessor_; - - const CachedImage& GetCachedImage() const - { - if (IsValid()) - { - return dynamic_cast(accessor_.GetValue()); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - public: - Accessor(FramesCache& that, - const std::string& sopInstanceUid, - size_t frameIndex) : - accessor_(that.cache_, GetKey(sopInstanceUid, frameIndex), false /* shared lock */) - { - } - - bool IsValid() const - { - return accessor_.IsValid(); - } - - const Orthanc::ImageAccessor& GetImage() const - { - return GetCachedImage().GetImage(); - } - - unsigned int GetQuality() const - { - return GetCachedImage().GetQuality(); - } - }; -}; - - - -class SeriesCursor : public boost::noncopyable -{ -public: - enum Action - { - Action_FastPlus, - Action_Plus, - Action_None, - Action_Minus, - Action_FastMinus - }; - -private: - std::vector prefetch_; - int framesCount_; - int currentFrame_; - bool isCircular_; - int fastDelta_; - Action lastAction_; - - int ComputeNextFrame(int currentFrame, - Action action) const - { - if (framesCount_ == 0) - { - assert(currentFrame == 0); - return 0; - } - - int nextFrame = currentFrame; - - switch (action) - { - case Action_FastPlus: - nextFrame += fastDelta_; - break; - - case Action_Plus: - nextFrame += 1; - break; - - case Action_None: - break; - - case Action_Minus: - nextFrame -= 1; - break; - - case Action_FastMinus: - nextFrame -= fastDelta_; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (isCircular_) - { - while (nextFrame < 0) - { - nextFrame += framesCount_; - } - - while (nextFrame >= framesCount_) - { - nextFrame -= framesCount_; - } - } - else - { - if (nextFrame < 0) - { - nextFrame = 0; - } - else if (nextFrame >= framesCount_) - { - nextFrame = framesCount_ - 1; - } - } - - return nextFrame; - } - - void UpdatePrefetch() - { - /** - * This method will order the frames of the series according to - * the number of "actions" (i.e. mouse wheels) that are necessary - * to reach them, starting from the current frame. It is assumed - * that once one action is done, it is more likely that the user - * will do the same action just afterwards. - **/ - - prefetch_.clear(); - - if (framesCount_ == 0) - { - return; - } - - prefetch_.reserve(framesCount_); - - // Breadth-first search using a FIFO. The queue associates a frame - // and the action that is the most likely in this frame - typedef std::list< std::pair > Queue; - - Queue queue; - std::set visited; // Frames that have already been visited - - queue.push_back(std::make_pair(currentFrame_, lastAction_)); - - while (!queue.empty()) - { - int frame = queue.front().first; - Action previousAction = queue.front().second; - queue.pop_front(); - - if (visited.find(frame) == visited.end()) - { - visited.insert(frame); - prefetch_.push_back(frame); - - switch (previousAction) - { - case Action_None: - case Action_Plus: - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); - break; - - case Action_Minus: - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); - break; - - case Action_FastPlus: - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); - break; - - case Action_FastMinus: - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); - queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - } - - assert(prefetch_.size() == framesCount_); - } - - bool CheckFrameIndex(int frame) const - { - return ((framesCount_ == 0 && frame == 0) || - (framesCount_ > 0 && frame >= 0 && frame < framesCount_)); - } - -public: - SeriesCursor(size_t framesCount) : - framesCount_(framesCount), - currentFrame_(framesCount / 2), // Start at the middle frame - isCircular_(false), - lastAction_(Action_None) - { - SetFastDelta(framesCount / 20); - UpdatePrefetch(); - } - - void SetCircular(bool isCircular) - { - isCircular_ = isCircular; - UpdatePrefetch(); - } - - void SetFastDelta(int delta) - { - fastDelta_ = (delta < 0 ? -delta : delta); - - if (fastDelta_ <= 0) - { - fastDelta_ = 1; - } - } - - void SetCurrentIndex(size_t frame) - { - if (frame >= framesCount_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - currentFrame_ = frame; - lastAction_ = Action_None; - UpdatePrefetch(); - } - } - - size_t GetCurrentIndex() const - { - assert(CheckFrameIndex(currentFrame_)); - return static_cast(currentFrame_); - } - - void Apply(Action action) - { - currentFrame_ = ComputeNextFrame(currentFrame_, action); - lastAction_ = action; - UpdatePrefetch(); - } - - size_t GetPrefetchSize() const - { - assert(prefetch_.size() == framesCount_); - return prefetch_.size(); - } - - size_t GetPrefetchFrameIndex(size_t i) const - { - if (i >= prefetch_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - assert(CheckFrameIndex(prefetch_[i])); - return static_cast(prefetch_[i]); - } - } -}; - - - - -class FrameGeometry -{ -private: - bool isValid_; - std::string frameOfReferenceUid_; - OrthancStone::CoordinateSystem3D coordinates_; - double pixelSpacingX_; - double pixelSpacingY_; - OrthancStone::Extent2D extent_; - -public: - FrameGeometry() : - isValid_(false) - { - } - - FrameGeometry(const Orthanc::DicomMap& tags) : - isValid_(false), - coordinates_(tags) - { - if (!tags.LookupStringValue( - frameOfReferenceUid_, Orthanc::DICOM_TAG_FRAME_OF_REFERENCE_UID, false)) - { - frameOfReferenceUid_.clear(); - } - - OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, tags); - - unsigned int rows, columns; - if (tags.HasTag(Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT) && - tags.HasTag(Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT) && - tags.ParseUnsignedInteger32(rows, Orthanc::DICOM_TAG_ROWS) && - tags.ParseUnsignedInteger32(columns, Orthanc::DICOM_TAG_COLUMNS)) - { - double ox = -pixelSpacingX_ / 2.0; - double oy = -pixelSpacingY_ / 2.0; - extent_.AddPoint(ox, oy); - extent_.AddPoint(ox + pixelSpacingX_ * static_cast(columns), - oy + pixelSpacingY_ * static_cast(rows)); - - isValid_ = true; - } - } - - bool IsValid() const - { - return isValid_; - } - - const std::string& GetFrameOfReferenceUid() const - { - if (isValid_) - { - return frameOfReferenceUid_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - const OrthancStone::CoordinateSystem3D& GetCoordinates() const - { - if (isValid_) - { - return coordinates_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - double GetPixelSpacingX() const - { - if (isValid_) - { - return pixelSpacingX_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - double GetPixelSpacingY() const - { - if (isValid_) - { - return pixelSpacingY_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - bool Intersect(double& x1, // Coordinates of the clipped line (out) - double& y1, - double& x2, - double& y2, - const FrameGeometry& other) const - { - if (this == &other) - { - return false; - } - - OrthancStone::Vector direction, origin; - - if (IsValid() && - other.IsValid() && - !extent_.IsEmpty() && - frameOfReferenceUid_ == other.frameOfReferenceUid_ && - OrthancStone::GeometryToolbox::IntersectTwoPlanes( - origin, direction, - coordinates_.GetOrigin(), coordinates_.GetNormal(), - other.coordinates_.GetOrigin(), other.coordinates_.GetNormal())) - { - double ax, ay, bx, by; - coordinates_.ProjectPoint(ax, ay, origin); - coordinates_.ProjectPoint(bx, by, origin + 100.0 * direction); - - return OrthancStone::GeometryToolbox::ClipLineToRectangle( - x1, y1, x2, y2, - ax, ay, bx, by, - extent_.GetX1(), extent_.GetY1(), extent_.GetX2(), extent_.GetY2()); - } - else - { - return false; - } - } -}; - - - -class ViewerViewport : public OrthancStone::ObserverBase -{ -public: - class IObserver : public boost::noncopyable - { - public: - virtual ~IObserver() - { - } - - virtual void SignalFrameUpdated(const ViewerViewport& viewport, - size_t currentFrame, - size_t countFrames, - DisplayedFrameQuality quality) = 0; - }; - -private: - static const int LAYER_TEXTURE = 0; - static const int LAYER_REFERENCE_LINES = 1; - - - class ICommand : public Orthanc::IDynamicObject - { - private: - boost::shared_ptr viewport_; - - public: - ICommand(boost::shared_ptr viewport) : - viewport_(viewport) - { - if (viewport == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - virtual ~ICommand() - { - } - - ViewerViewport& GetViewport() const - { - assert(viewport_ != NULL); - return *viewport_; - } - - virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - }; - - class SetDefaultWindowingCommand : public ICommand - { - public: - SetDefaultWindowingCommand(boost::shared_ptr viewport) : - ICommand(viewport) - { - } - - virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE - { - if (message.GetResources()->GetSize() != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - const Orthanc::DicomMap& dicom = message.GetResources()->GetResource(0); - - { - OrthancStone::DicomInstanceParameters params(dicom); - - if (params.HasDefaultWindowing()) - { - GetViewport().defaultWindowingCenter_ = params.GetDefaultWindowingCenter(); - GetViewport().defaultWindowingWidth_ = params.GetDefaultWindowingWidth(); - LOG(INFO) << "Default windowing: " << params.GetDefaultWindowingCenter() - << "," << params.GetDefaultWindowingWidth(); - - GetViewport().windowingCenter_ = params.GetDefaultWindowingCenter(); - GetViewport().windowingWidth_ = params.GetDefaultWindowingWidth(); - } - else - { - LOG(INFO) << "No default windowing"; - GetViewport().ResetDefaultWindowing(); - } - } - - GetViewport().DisplayCurrentFrame(); - } - }; - - class SetLowQualityFrame : public ICommand - { - private: - std::string sopInstanceUid_; - unsigned int frameIndex_; - float windowCenter_; - float windowWidth_; - bool isMonochrome1_; - bool isPrefetch_; - - public: - SetLowQualityFrame(boost::shared_ptr viewport, - const std::string& sopInstanceUid, - unsigned int frameIndex, - float windowCenter, - float windowWidth, - bool isMonochrome1, - bool isPrefetch) : - ICommand(viewport), - sopInstanceUid_(sopInstanceUid), - frameIndex_(frameIndex), - windowCenter_(windowCenter), - windowWidth_(windowWidth), - isMonochrome1_(isMonochrome1), - isPrefetch_(isPrefetch) - { - } - - virtual void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) const ORTHANC_OVERRIDE - { - std::unique_ptr jpeg(new Orthanc::JpegReader); - jpeg->ReadFromMemory(message.GetAnswer()); - - bool updatedCache; - - switch (jpeg->GetFormat()) - { - case Orthanc::PixelFormat_RGB24: - updatedCache = GetViewport().cache_->Acquire( - sopInstanceUid_, frameIndex_, jpeg.release(), QUALITY_JPEG); - break; - - case Orthanc::PixelFormat_Grayscale8: - { - if (isMonochrome1_) - { - Orthanc::ImageProcessing::Invert(*jpeg); - } - - std::unique_ptr converted( - new Orthanc::Image(Orthanc::PixelFormat_Float32, jpeg->GetWidth(), - jpeg->GetHeight(), false)); - - Orthanc::ImageProcessing::Convert(*converted, *jpeg); - - /** - - Orthanc::ImageProcessing::ShiftScale() computes "(x + offset) * scaling". - The system to solve is thus: - - (0 + offset) * scaling = windowingCenter - windowingWidth / 2 [a] - (255 + offset) * scaling = windowingCenter + windowingWidth / 2 [b] - - Resolution: - - [b - a] => 255 * scaling = windowingWidth - [a] => offset = (windowingCenter - windowingWidth / 2) / scaling - - **/ - - const float scaling = windowWidth_ / 255.0f; - const float offset = (OrthancStone::LinearAlgebra::IsCloseToZero(scaling) ? 0 : - (windowCenter_ - windowWidth_ / 2.0f) / scaling); - - Orthanc::ImageProcessing::ShiftScale(*converted, offset, scaling, false); - updatedCache = GetViewport().cache_->Acquire( - sopInstanceUid_, frameIndex_, converted.release(), QUALITY_JPEG); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - if (updatedCache) - { - GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_); - } - - if (isPrefetch_) - { - GetViewport().ScheduleNextPrefetch(); - } - } - }; - - - class SetFullDicomFrame : public ICommand - { - private: - std::string sopInstanceUid_; - unsigned int frameIndex_; - bool isPrefetch_; - - public: - SetFullDicomFrame(boost::shared_ptr viewport, - const std::string& sopInstanceUid, - unsigned int frameIndex, - bool isPrefetch) : - ICommand(viewport), - sopInstanceUid_(sopInstanceUid), - frameIndex_(frameIndex), - isPrefetch_(isPrefetch) - { - } - - virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const ORTHANC_OVERRIDE - { - Orthanc::DicomMap tags; - message.GetDicom().ExtractDicomSummary(tags, ORTHANC_STONE_MAX_TAG_LENGTH); - - std::string s; - if (!tags.LookupStringValue(s, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) - { - // Safety check - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - std::unique_ptr frame( - Orthanc::DicomImageDecoder::Decode(message.GetDicom(), frameIndex_)); - - if (frame.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - bool updatedCache; - - if (frame->GetFormat() == Orthanc::PixelFormat_RGB24) - { - updatedCache = GetViewport().cache_->Acquire( - sopInstanceUid_, frameIndex_, frame.release(), QUALITY_FULL); - } - else - { - double a = 1; - double b = 0; - - double doseScaling; - if (tags.ParseDouble(doseScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) - { - a = doseScaling; - } - - double rescaleIntercept, rescaleSlope; - if (tags.ParseDouble(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && - tags.ParseDouble(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE)) - { - a *= rescaleSlope; - b = rescaleIntercept; - } - - std::unique_ptr converted( - new Orthanc::Image(Orthanc::PixelFormat_Float32, frame->GetWidth(), frame->GetHeight(), false)); - Orthanc::ImageProcessing::Convert(*converted, *frame); - Orthanc::ImageProcessing::ShiftScale2(*converted, b, a, false); - - updatedCache = GetViewport().cache_->Acquire( - sopInstanceUid_, frameIndex_, converted.release(), QUALITY_FULL); - } - - if (updatedCache) - { - GetViewport().SignalUpdatedFrame(sopInstanceUid_, frameIndex_); - } - - if (isPrefetch_) - { - GetViewport().ScheduleNextPrefetch(); - } - } - }; - - - class PrefetchItem - { - private: - size_t frameIndex_; - bool isFull_; - - public: - PrefetchItem(size_t frameIndex, - bool isFull) : - frameIndex_(frameIndex), - isFull_(isFull) - { - } - - size_t GetFrameIndex() const - { - return frameIndex_; - } - - bool IsFull() const - { - return isFull_; - } - }; - - - std::unique_ptr observer_; - OrthancStone::ILoadersContext& context_; - boost::shared_ptr viewport_; - boost::shared_ptr loader_; - OrthancStone::DicomSource source_; - boost::shared_ptr cache_; - std::unique_ptr frames_; - std::unique_ptr cursor_; - float windowingCenter_; - float windowingWidth_; - float defaultWindowingCenter_; - float defaultWindowingWidth_; - bool inverted_; - bool fitNextContent_; - bool isCtrlDown_; - FrameGeometry currentFrameGeometry_; - std::list prefetchQueue_; - - void ScheduleNextPrefetch() - { - while (!prefetchQueue_.empty()) - { - size_t index = prefetchQueue_.front().GetFrameIndex(); - bool isFull = prefetchQueue_.front().IsFull(); - prefetchQueue_.pop_front(); - - const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); - unsigned int frame = frames_->GetFrameIndex(index); - - { - FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame); - if (!accessor.IsValid() || - (isFull && accessor.GetQuality() == 0)) - { - if (isFull) - { - ScheduleLoadFullDicomFrame(index, PRIORITY_NORMAL, true); - } - else - { - ScheduleLoadRenderedFrame(index, PRIORITY_NORMAL, true); - } - return; - } - } - } - } - - - void ResetDefaultWindowing() - { - defaultWindowingCenter_ = 128; - defaultWindowingWidth_ = 256; - - windowingCenter_ = defaultWindowingCenter_; - windowingWidth_ = defaultWindowingWidth_; - - inverted_ = false; - } - - void SignalUpdatedFrame(const std::string& sopInstanceUid, - unsigned int frameIndex) - { - if (cursor_.get() != NULL && - frames_.get() != NULL) - { - size_t index = cursor_->GetCurrentIndex(); - - if (frames_->GetFrameSopInstanceUid(index) == sopInstanceUid && - frames_->GetFrameIndex(index) == frameIndex) - { - DisplayCurrentFrame(); - } - } - } - - void DisplayCurrentFrame() - { - DisplayedFrameQuality quality = DisplayedFrameQuality_None; - - if (cursor_.get() != NULL && - frames_.get() != NULL) - { - const size_t index = cursor_->GetCurrentIndex(); - - unsigned int cachedQuality; - if (!DisplayFrame(cachedQuality, index)) - { - // This frame is not cached yet: Load it - if (source_.HasDicomWebRendered()) - { - ScheduleLoadRenderedFrame(index, PRIORITY_HIGH, false /* not a prefetch */); - } - else - { - ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */); - } - } - else if (cachedQuality < QUALITY_FULL) - { - // This frame is only available in low-res: Download the full DICOM - ScheduleLoadFullDicomFrame(index, PRIORITY_HIGH, false /* not a prefetch */); - quality = DisplayedFrameQuality_Low; - } - else - { - quality = DisplayedFrameQuality_High; - } - - currentFrameGeometry_ = FrameGeometry(frames_->GetFrameTags(index)); - - { - // Prepare prefetching - prefetchQueue_.clear(); - for (size_t i = 0; i < cursor_->GetPrefetchSize() && i < 16; i++) - { - size_t a = cursor_->GetPrefetchFrameIndex(i); - if (a != index) - { - prefetchQueue_.push_back(PrefetchItem(a, i < 2)); - } - } - - ScheduleNextPrefetch(); - } - - if (observer_.get() != NULL) - { - observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(), - frames_->GetFramesCount(), quality); - } - } - else - { - currentFrameGeometry_ = FrameGeometry(); - } - } - - void ClearViewport() - { - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetController().GetScene().DeleteLayer(LAYER_TEXTURE); - //lock->GetCompositor().Refresh(lock->GetController().GetScene()); - lock->Invalidate(); - } - } - - bool DisplayFrame(unsigned int& quality, - size_t index) - { - if (frames_.get() == NULL) - { - return false; - } - - const std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); - unsigned int frame = frames_->GetFrameIndex(index); - - FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame); - if (accessor.IsValid()) - { - { - std::unique_ptr lock(viewport_->Lock()); - - OrthancStone::Scene2D& scene = lock->GetController().GetScene(); - - // Save the current windowing (that could have been altered by - // GrayscaleWindowingSceneTracker), so that it can be reused - // by the next frames - if (scene.HasLayer(LAYER_TEXTURE) && - scene.GetLayer(LAYER_TEXTURE).GetType() == OrthancStone::ISceneLayer::Type_FloatTexture) - { - OrthancStone::FloatTextureSceneLayer& layer = - dynamic_cast(scene.GetLayer(LAYER_TEXTURE)); - layer.GetWindowing(windowingCenter_, windowingWidth_); - } - } - - quality = accessor.GetQuality(); - - std::unique_ptr layer; - - switch (accessor.GetImage().GetFormat()) - { - case Orthanc::PixelFormat_RGB24: - layer.reset(new OrthancStone::ColorTextureSceneLayer(accessor.GetImage())); - break; - - case Orthanc::PixelFormat_Float32: - { - std::unique_ptr tmp( - new OrthancStone::FloatTextureSceneLayer(accessor.GetImage())); - tmp->SetCustomWindowing(windowingCenter_, windowingWidth_); - tmp->SetInverted(inverted_ ^ frames_->IsFrameMonochrome1(index)); - layer.reset(tmp.release()); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - layer->SetLinearInterpolation(true); - - double pixelSpacingX, pixelSpacingY; - OrthancStone::GeometryToolbox::GetPixelSpacing( - pixelSpacingX, pixelSpacingY, frames_->GetFrameTags(index)); - layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY); - - if (layer.get() == NULL) - { - return false; - } - else - { - std::unique_ptr lock(viewport_->Lock()); - - OrthancStone::Scene2D& scene = lock->GetController().GetScene(); - - scene.SetLayer(LAYER_TEXTURE, layer.release()); - - if (fitNextContent_) - { - lock->GetCompositor().RefreshCanvasSize(); - lock->GetCompositor().FitContent(scene); - fitNextContent_ = false; - } - - //lock->GetCompositor().Refresh(scene); - lock->Invalidate(); - return true; - } - } - else - { - return false; - } - } - - void ScheduleLoadFullDicomFrame(size_t index, - int priority, - bool isPrefetch) - { - if (frames_.get() != NULL) - { - std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); - unsigned int frame = frames_->GetFrameIndex(index); - - { - std::unique_ptr lock(context_.Lock()); - lock->Schedule( - GetSharedObserver(), priority, OrthancStone::ParseDicomFromWadoCommand::Create( - source_, frames_->GetStudyInstanceUid(), frames_->GetSeriesInstanceUid(), - sopInstanceUid, false /* transcoding (TODO) */, - Orthanc::DicomTransferSyntax_LittleEndianExplicit /* TODO */, - new SetFullDicomFrame(GetSharedObserver(), sopInstanceUid, frame, isPrefetch))); - } - } - } - - void ScheduleLoadRenderedFrame(size_t index, - int priority, - bool isPrefetch) - { - if (!source_.HasDicomWebRendered()) - { - ScheduleLoadFullDicomFrame(index, priority, isPrefetch); - } - else if (frames_.get() != NULL) - { - std::string sopInstanceUid = frames_->GetFrameSopInstanceUid(index); - unsigned int frame = frames_->GetFrameIndex(index); - bool isMonochrome1 = frames_->IsFrameMonochrome1(index); - - const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() + - "/series/" + frames_->GetSeriesInstanceUid() + - "/instances/" + sopInstanceUid + - "/frames/" + boost::lexical_cast(frame + 1) + "/rendered"); - - std::map headers, arguments; - arguments["window"] = ( - boost::lexical_cast(windowingCenter_) + "," + - boost::lexical_cast(windowingWidth_) + ",linear"); - - std::unique_ptr command( - source_.CreateDicomWebCommand( - uri, arguments, headers, new SetLowQualityFrame( - GetSharedObserver(), sopInstanceUid, frame, - windowingCenter_, windowingWidth_, isMonochrome1, isPrefetch))); - - { - std::unique_ptr lock(context_.Lock()); - lock->Schedule(GetSharedObserver(), priority, command.release()); - } - } - } - - ViewerViewport(OrthancStone::ILoadersContext& context, - const OrthancStone::DicomSource& source, - const std::string& canvas, - boost::shared_ptr cache) : - context_(context), - source_(source), - viewport_(OrthancStone::WebGLViewport::Create(canvas)), - cache_(cache), - fitNextContent_(true), - isCtrlDown_(false) - { - if (!cache_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - emscripten_set_wheel_callback(viewport_->GetCanvasCssSelector().c_str(), this, true, OnWheel); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnKey); - - ResetDefaultWindowing(); - } - - static EM_BOOL OnKey(int eventType, - const EmscriptenKeyboardEvent *event, - void *userData) - { - /** - * WARNING: There is a problem with Firefox 71 that seems to mess - * the "ctrlKey" value. - **/ - - ViewerViewport& that = *reinterpret_cast(userData); - that.isCtrlDown_ = event->ctrlKey; - return false; - } - - - static EM_BOOL OnWheel(int eventType, - const EmscriptenWheelEvent *wheelEvent, - void *userData) - { - ViewerViewport& that = *reinterpret_cast(userData); - - if (that.cursor_.get() != NULL) - { - if (wheelEvent->deltaY < 0) - { - that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastMinus : SeriesCursor::Action_Minus); - } - else if (wheelEvent->deltaY > 0) - { - that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastPlus : SeriesCursor::Action_Plus); - } - } - - return true; - } - - void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) - { - dynamic_cast(message.GetUserPayload()).Handle(message); - } - - void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) - { - dynamic_cast(message.GetOrigin().GetPayload()).Handle(message); - } - - void Handle(const OrthancStone::ParseDicomSuccessMessage& message) - { - dynamic_cast(message.GetOrigin().GetPayload()).Handle(message); - } - -public: - static boost::shared_ptr Create(OrthancStone::ILoadersContext::ILock& lock, - const OrthancStone::DicomSource& source, - const std::string& canvas, - boost::shared_ptr cache) - { - boost::shared_ptr viewport( - new ViewerViewport(lock.GetContext(), source, canvas, cache)); - - viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(lock); - viewport->Register( - *viewport->loader_, &ViewerViewport::Handle); - - viewport->Register( - lock.GetOracleObservable(), &ViewerViewport::Handle); - - viewport->Register( - lock.GetOracleObservable(), &ViewerViewport::Handle); - - return viewport; - } - - void SetFrames(OrthancStone::SortedFrames* frames) - { - if (frames == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - fitNextContent_ = true; - - frames_.reset(frames); - cursor_.reset(new SeriesCursor(frames_->GetFramesCount())); - - LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount(); - - ResetDefaultWindowing(); - ClearViewport(); - prefetchQueue_.clear(); - currentFrameGeometry_ = FrameGeometry(); - - if (observer_.get() != NULL) - { - observer_->SignalFrameUpdated(*this, cursor_->GetCurrentIndex(), - frames_->GetFramesCount(), DisplayedFrameQuality_None); - } - - if (frames_->GetFramesCount() != 0) - { - const std::string& sopInstanceUid = frames_->GetFrameSopInstanceUid(cursor_->GetCurrentIndex()); - - { - // Fetch the default windowing for the central instance - const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() + - "/series/" + frames_->GetSeriesInstanceUid() + - "/instances/" + sopInstanceUid + "/metadata"); - - loader_->ScheduleGetDicomWeb( - boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), - 0, source_, uri, new SetDefaultWindowingCommand(GetSharedObserver())); - } - } - } - - // This method is used when the layout of the HTML page changes, - // which does not trigger the "emscripten_set_resize_callback()" - void UpdateSize(bool fitContent) - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetCompositor().RefreshCanvasSize(); - - if (fitContent) - { - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - } - - lock->Invalidate(); - } - - void AcquireObserver(IObserver* observer) - { - observer_.reset(observer); - } - - const std::string& GetCanvasId() const - { - assert(viewport_); - return viewport_->GetCanvasId(); - } - - void ChangeFrame(SeriesCursor::Action action) - { - if (cursor_.get() != NULL) - { - size_t previous = cursor_->GetCurrentIndex(); - - cursor_->Apply(action); - - size_t current = cursor_->GetCurrentIndex(); - if (previous != current) - { - DisplayCurrentFrame(); - } - } - } - - const FrameGeometry& GetCurrentFrameGeometry() const - { - return currentFrameGeometry_; - } - - void UpdateReferenceLines(const std::list& planes) - { - std::unique_ptr layer(new OrthancStone::PolylineSceneLayer); - - if (GetCurrentFrameGeometry().IsValid()) - { - for (std::list::const_iterator - it = planes.begin(); it != planes.end(); ++it) - { - assert(*it != NULL); - - double x1, y1, x2, y2; - if (GetCurrentFrameGeometry().Intersect(x1, y1, x2, y2, **it)) - { - OrthancStone::PolylineSceneLayer::Chain chain; - chain.push_back(OrthancStone::ScenePoint2D(x1, y1)); - chain.push_back(OrthancStone::ScenePoint2D(x2, y2)); - layer->AddChain(chain, false, 0, 255, 0); - } - } - } - - { - std::unique_ptr lock(viewport_->Lock()); - - if (layer->GetChainsCount() == 0) - { - lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES); - } - else - { - lock->GetController().GetScene().SetLayer(LAYER_REFERENCE_LINES, layer.release()); - } - - //lock->GetCompositor().Refresh(lock->GetController().GetScene()); - lock->Invalidate(); - } - } - - - void ClearReferenceLines() - { - { - std::unique_ptr lock(viewport_->Lock()); - lock->GetController().GetScene().DeleteLayer(LAYER_REFERENCE_LINES); - lock->Invalidate(); - } - } - - - void SetDefaultWindowing() - { - SetWindowing(defaultWindowingCenter_, defaultWindowingWidth_); - } - - void SetWindowing(float windowingCenter, - float windowingWidth) - { - windowingCenter_ = windowingCenter; - windowingWidth_ = windowingWidth; - - { - std::unique_ptr lock(viewport_->Lock()); - - if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) && - lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() == - OrthancStone::ISceneLayer::Type_FloatTexture) - { - dynamic_cast( - lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)). - SetCustomWindowing(windowingCenter_, windowingWidth_); - lock->Invalidate(); - } - } - } - - void Invert() - { - inverted_ = !inverted_; - - { - std::unique_ptr lock(viewport_->Lock()); - - if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) && - lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() == - OrthancStone::ISceneLayer::Type_FloatTexture) - { - OrthancStone::FloatTextureSceneLayer& layer = - dynamic_cast( - lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)); - - // NB: Using "IsInverted()" instead of "inverted_" is for - // compatibility with MONOCHROME1 images - layer.SetInverted(!layer.IsInverted()); - lock->Invalidate(); - } - } - } -}; - - - - - -typedef std::map > Viewports; -static Viewports allViewports_; -static bool showReferenceLines_ = true; - - -static void UpdateReferenceLines() -{ - if (showReferenceLines_) - { - std::list planes; - - for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) - { - assert(it->second != NULL); - planes.push_back(&it->second->GetCurrentFrameGeometry()); - } - - for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) - { - assert(it->second != NULL); - it->second->UpdateReferenceLines(planes); - } - } - else - { - for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) - { - assert(it->second != NULL); - it->second->ClearReferenceLines(); - } - } -} - - -class WebAssemblyObserver : public ResourcesLoader::IObserver, - public ViewerViewport::IObserver -{ -public: - virtual void SignalResourcesLoaded() ORTHANC_OVERRIDE - { - DISPATCH_JAVASCRIPT_EVENT("ResourcesLoaded"); - } - - virtual void SignalSeriesThumbnailLoaded(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) ORTHANC_OVERRIDE - { - EM_ASM({ - const customEvent = document.createEvent("CustomEvent"); - customEvent.initCustomEvent("ThumbnailLoaded", false, false, - { "studyInstanceUid" : UTF8ToString($0), - "seriesInstanceUid" : UTF8ToString($1) }); - window.dispatchEvent(customEvent); - }, - studyInstanceUid.c_str(), - seriesInstanceUid.c_str()); - } - - virtual void SignalSeriesMetadataLoaded(const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) ORTHANC_OVERRIDE - { - EM_ASM({ - const customEvent = document.createEvent("CustomEvent"); - customEvent.initCustomEvent("MetadataLoaded", false, false, - { "studyInstanceUid" : UTF8ToString($0), - "seriesInstanceUid" : UTF8ToString($1) }); - window.dispatchEvent(customEvent); - }, - studyInstanceUid.c_str(), - seriesInstanceUid.c_str()); - } - - virtual void SignalFrameUpdated(const ViewerViewport& viewport, - size_t currentFrame, - size_t countFrames, - DisplayedFrameQuality quality) ORTHANC_OVERRIDE - { - EM_ASM({ - const customEvent = document.createEvent("CustomEvent"); - customEvent.initCustomEvent("FrameUpdated", false, false, - { "canvasId" : UTF8ToString($0), - "currentFrame" : $1, - "framesCount" : $2, - "quality" : $3 }); - window.dispatchEvent(customEvent); - }, - viewport.GetCanvasId().c_str(), - static_cast(currentFrame), - static_cast(countFrames), - quality); - - - UpdateReferenceLines(); - }; -}; - - - -static OrthancStone::DicomSource source_; -static boost::shared_ptr cache_; -static boost::shared_ptr context_; -static std::string stringBuffer_; - - - -static void FormatTags(std::string& target, - const Orthanc::DicomMap& tags) -{ - Orthanc::DicomArray arr(tags); - Json::Value v = Json::objectValue; - - for (size_t i = 0; i < arr.GetSize(); i++) - { - const Orthanc::DicomElement& element = arr.GetElement(i); - if (!element.GetValue().IsBinary() && - !element.GetValue().IsNull()) - { - v[element.GetTag().Format()] = element.GetValue().GetContent(); - } - } - - target = v.toStyledString(); -} - - -static ResourcesLoader& GetResourcesLoader() -{ - static boost::shared_ptr resourcesLoader_; - - if (!resourcesLoader_) - { - std::unique_ptr lock(context_->Lock()); - resourcesLoader_ = ResourcesLoader::Create(*lock, source_); - resourcesLoader_->AcquireObserver(new WebAssemblyObserver); - } - - return *resourcesLoader_; -} - - -static boost::shared_ptr GetViewport(const std::string& canvas) -{ - Viewports::iterator found = allViewports_.find(canvas); - if (found == allViewports_.end()) - { - std::unique_ptr lock(context_->Lock()); - boost::shared_ptr viewport(ViewerViewport::Create(*lock, source_, canvas, cache_)); - viewport->AcquireObserver(new WebAssemblyObserver); - allViewports_[canvas] = viewport; - return viewport; - } - else - { - return found->second; - } -} - - -extern "C" -{ - int main(int argc, char const *argv[]) - { - printf("OK\n"); - Orthanc::InitializeFramework("", true); - Orthanc::Logging::EnableInfoLevel(true); - //Orthanc::Logging::EnableTraceLevel(true); - - context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); - cache_.reset(new FramesCache); - - DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); - } - - - EMSCRIPTEN_KEEPALIVE - void SetOrthancRoot(const char* uri, - int useRendered) - { - try - { - context_->SetLocalOrthanc(uri); // For "source_.SetDicomWebThroughOrthancSource()" - source_.SetDicomWebSource(std::string(uri) + "/dicom-web"); - source_.SetDicomWebRendered(useRendered != 0); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void SetDicomWebServer(const char* serverName, - int hasRendered) - { - try - { - source_.SetDicomWebThroughOrthancSource(serverName); - source_.SetDicomWebRendered(hasRendered != 0); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void FetchAllStudies() - { - try - { - GetResourcesLoader().FetchAllStudies(); - } - EXTERN_CATCH_EXCEPTIONS; - } - - EMSCRIPTEN_KEEPALIVE - void FetchStudy(const char* studyInstanceUid) - { - try - { - GetResourcesLoader().FetchStudy(studyInstanceUid); - } - EXTERN_CATCH_EXCEPTIONS; - } - - EMSCRIPTEN_KEEPALIVE - void FetchSeries(const char* studyInstanceUid, - const char* seriesInstanceUid) - { - try - { - GetResourcesLoader().FetchSeries(studyInstanceUid, seriesInstanceUid); - } - EXTERN_CATCH_EXCEPTIONS; - } - - EMSCRIPTEN_KEEPALIVE - int GetStudiesCount() - { - try - { - return GetResourcesLoader().GetStudiesCount(); - } - EXTERN_CATCH_EXCEPTIONS; - return 0; // on exception - } - - EMSCRIPTEN_KEEPALIVE - int GetSeriesCount() - { - try - { - return GetResourcesLoader().GetSeriesCount(); - } - EXTERN_CATCH_EXCEPTIONS; - return 0; // on exception - } - - - EMSCRIPTEN_KEEPALIVE - const char* GetStringBuffer() - { - return stringBuffer_.c_str(); - } - - - EMSCRIPTEN_KEEPALIVE - void LoadStudyTags(int i) - { - try - { - if (i < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - Orthanc::DicomMap dicom; - GetResourcesLoader().GetStudy(dicom, i); - FormatTags(stringBuffer_, dicom); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void LoadSeriesTags(int i) - { - try - { - if (i < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - Orthanc::DicomMap dicom; - GetResourcesLoader().GetSeries(dicom, i); - FormatTags(stringBuffer_, dicom); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - int LoadSeriesThumbnail(const char* seriesInstanceUid) - { - try - { - std::string image, mime; - switch (GetResourcesLoader().GetSeriesThumbnail(image, mime, seriesInstanceUid)) - { - case OrthancStone::SeriesThumbnailType_Image: - Orthanc::Toolbox::EncodeDataUriScheme(stringBuffer_, mime, image); - return ThumbnailType_Image; - - case OrthancStone::SeriesThumbnailType_Pdf: - return ThumbnailType_Pdf; - - case OrthancStone::SeriesThumbnailType_Video: - return ThumbnailType_Video; - - case OrthancStone::SeriesThumbnailType_NotLoaded: - return ThumbnailType_Loading; - - case OrthancStone::SeriesThumbnailType_Unsupported: - return ThumbnailType_NoPreview; - - default: - return ThumbnailType_Unknown; - } - } - EXTERN_CATCH_EXCEPTIONS; - return ThumbnailType_Unknown; - } - - - EMSCRIPTEN_KEEPALIVE - void SpeedUpFetchSeriesMetadata(const char* studyInstanceUid, - const char* seriesInstanceUid) - { - try - { - GetResourcesLoader().FetchSeriesMetadata(PRIORITY_HIGH, studyInstanceUid, seriesInstanceUid); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - int IsSeriesComplete(const char* seriesInstanceUid) - { - try - { - return GetResourcesLoader().IsSeriesComplete(seriesInstanceUid) ? 1 : 0; - } - EXTERN_CATCH_EXCEPTIONS; - return 0; - } - - EMSCRIPTEN_KEEPALIVE - int LoadSeriesInViewport(const char* canvas, - const char* seriesInstanceUid) - { - try - { - std::unique_ptr frames(new OrthancStone::SortedFrames); - - if (GetResourcesLoader().SortSeriesFrames(*frames, seriesInstanceUid)) - { - GetViewport(canvas)->SetFrames(frames.release()); - return 1; - } - else - { - return 0; - } - } - EXTERN_CATCH_EXCEPTIONS; - return 0; - } - - - EMSCRIPTEN_KEEPALIVE - void AllViewportsUpdateSize(int fitContent) - { - try - { - for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) - { - assert(it->second != NULL); - it->second->UpdateSize(fitContent != 0); - } - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void DecrementFrame(const char* canvas, - int fitContent) - { - try - { - GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Minus); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void IncrementFrame(const char* canvas, - int fitContent) - { - try - { - GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Plus); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void ShowReferenceLines(int show) - { - try - { - showReferenceLines_ = (show != 0); - UpdateReferenceLines(); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void SetDefaultWindowing(const char* canvas) - { - try - { - GetViewport(canvas)->SetDefaultWindowing(); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void SetWindowing(const char* canvas, - int center, - int width) - { - try - { - GetViewport(canvas)->SetWindowing(center, width); - } - EXTERN_CATCH_EXCEPTIONS; - } - - - EMSCRIPTEN_KEEPALIVE - void InvertContrast(const char* canvas) - { - try - { - GetViewport(canvas)->Invert(); - } - EXTERN_CATCH_EXCEPTIONS; - } -} diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/WebAssembly/docker-build.sh --- a/StoneWebViewer/WebAssembly/docker-build.sh Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -#!/bin/bash - -set -ex - -IMAGE=jodogne/wasm-builder:1.39.17-upstream -#IMAGE=wasm-builder - -if [ "$1" != "Debug" -a "$1" != "Release" ]; then - echo "Please provide build type: Debug or Release" - exit -1 -fi - -if [ -t 1 ]; then - # TTY is available => use interactive mode - DOCKER_FLAGS='-i' -fi - -ROOT_DIR=`dirname $(readlink -f $0)`/../.. - -mkdir -p ${ROOT_DIR}/wasm-binaries - -docker run -t ${DOCKER_FLAGS} --rm \ - --user $(id -u):$(id -g) \ - -v ${ROOT_DIR}:/source:ro \ - -v ${ROOT_DIR}/wasm-binaries:/target:rw ${IMAGE} \ - bash /source/StoneWebViewer/WebAssembly/docker-internal.sh $1 - -ls -lR ${ROOT_DIR}/wasm-binaries/StoneWebViewer/ diff -r de8cf5859e84 -r d1806b4e4839 StoneWebViewer/WebAssembly/docker-internal.sh --- a/StoneWebViewer/WebAssembly/docker-internal.sh Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -#!/bin/bash -set -ex - -source /opt/emsdk/emsdk_env.sh - -# Use a folder that is writeable by non-root users for the Emscripten cache -export EM_CACHE=/tmp/emscripten-cache - -# Get the Orthanc framework -cd /tmp/ -hg clone https://hg.orthanc-server.com/orthanc/ - -# Make a copy of the read-only folder containing the source code into -# a writeable folder, because of "DownloadPackage.cmake" that writes -# to the "ThirdPartyDownloads" folder next to the "CMakeLists.txt" -cd /source -hg clone /source /tmp/source-writeable - -mkdir /tmp/build -cd /tmp/build - -cmake /tmp/source-writeable/StoneWebViewer/WebAssembly \ - -DCMAKE_BUILD_TYPE=$1 \ - -DCMAKE_INSTALL_PREFIX=/target/StoneWebViewer \ - -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ - -DORTHANC_FRAMEWORK_ROOT=/tmp/orthanc/OrthancFramework/Sources \ - -DSTATIC_BUILD=ON \ - -G Ninja - -ninja -j2 install