Mercurial > hg > orthanc-stone
changeset 1538:d1806b4e4839
moving OrthancStone/Samples/ as Applications/Samples/
line wrap: on
line diff
--- /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
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancPluginCppWrapper.h" + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/move/unique_ptr.hpp> +#include <boost/thread.hpp> +#include <json/reader.h> +#include <json/writer.h> + + +#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<uint32_t>(size); + if (static_cast<size_t>(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<const char*>(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<const char*>(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<std::string, std::string>& httpHeaders, + bool applyPlugins) + { + Clear(); + + std::vector<const char*> headersKeys; + std::vector<const char*> headersValues; + + for (std::map<std::string, std::string>::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<const char*>(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<const char*>(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<unsigned int>(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<float>(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast<float>(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<std::string>& 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<std::string>& target, + const std::string& key, + bool allowSingleString) const + { + std::list<std::string> lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list<std::string>::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<std::string, std::string>& 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<std::string, std::string>& 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<std::string>(major) + "." + + boost::lexical_cast<std::string>(minor) + "." + + boost::lexical_cast<std::string>(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<unsigned int>(aa); + unsigned int b = static_cast<unsigned int>(bb); + unsigned int c = static_cast<unsigned int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<OrthancJob*>(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(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<OrthancJob*>(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<OrthancJob*>(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast<OrthancJob*>(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<Orthanc::ErrorCode>(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<OrthancJob> 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<float>(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<RequestBodyWrapper*>(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<uint32_t>(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<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<const char*> headersKeys_; + std::vector<const char*> 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<std::string*> 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<const char*>(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<IChunkedRequestReader*>(reader)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<IChunkedRequestReader*>(reader)->Execute(output); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader) + { + if (reader != NULL) + { + delete reinterpret_cast<IChunkedRequestReader*>(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<IChunkedRequestReader> 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<IChunkedRequestReader> 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<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(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<OrthancPluginDicomInstance*>(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<DicomInstance> result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#endif +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OrthancPluginException.h" + +#include <orthanc/OrthancCPlugin.h> +#include <boost/noncopyable.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <json/value.h> +#include <vector> +#include <list> +#include <set> +#include <map> + + + +/** + * 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<const char*>(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<std::string, std::string>& 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<std::string>& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set<std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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 <RestCallback Callback> + 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<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template <RestCallback Callback> + void RegisterRestCallback(const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock + (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); + } + else + { + OrthancPluginRegisterRestCallback + (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map<std::string, uint32_t> 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<std::string, std::string> 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 <ChunkedRestCallback Callback> + static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request)); + if (*reader == NULL) + { + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<GetHandler>, + PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>, + DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect<DeleteHandler>, + PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PutHandler>, + Internals::ChunkedRequestReaderAddChunk, + Internals::ChunkedRequestReaderExecute, + Internals::ChunkedRequestReaderFinalize); +#else + OrthancPluginRegisterRestCallbackNoLock( + GetGlobalContext(), uri.c_str(), + Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>); +#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<size_t>(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 + }; +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include <OrthancException.h> +# 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 <orthanc/OrthancCPlugin.h> +# 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<ORTHANC_PLUGINS_ERROR_ENUMERATION>(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 +}
--- /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 <http://www.gnu.org/licenses/>. + + +# 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()
--- /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: + *; +};
--- /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.
--- /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: + * + * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: + * 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(). + * -# <tt>void OrthancPluginFinalize()</tt>: + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# <tt>const char* OrthancPluginGetName()</tt>: + * The plugin must return a short string to identify itself. + * -# <tt>const char* OrthancPluginGetVersion()</tt>: + * 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 <tt>extern "C"</tt> 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 <http://www.gnu.org/licenses/>. + **/ + + + +#pragma once + + +#include <stdio.h> +#include <string.h> + +#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 <stdint.h> + +#include <stdlib.h> + + + +/******************************************************************** + ** 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 ("<tt>n</tt>"). + * @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 + + +/** @} */ +
--- /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)
--- /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 <http://www.gnu.org/licenses/>. + **/ + +// 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 <Logging.h> +#include <OrthancException.h> + +// System +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/make_shared.hpp> + +#include <stdio.h> + + +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<DicomVolumeImage>(); + doseVolume_ = boost::make_shared<DicomVolumeImage>(); + } + + boost::shared_ptr<RtViewerApp> RtViewerApp::Create() + { + boost::shared_ptr<RtViewerApp> 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<RtViewerView> + 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<DicomVolumeImage::GeometryReadyMessage> + (*ctLoader_, &RtViewerApp::HandleGeometryReady); + + Register<OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality> + (*ctLoader_, &RtViewerApp::HandleCTLoaded); + + Register<DicomVolumeImage::ContentUpdatedMessage> + (*ctLoader_, &RtViewerApp::HandleCTContentUpdated); + + Register<DicomVolumeImage::ContentUpdatedMessage> + (*doseLoader_, &RtViewerApp::HandleDoseLoaded); + + Register<DicomStructureSetLoader::StructuresReady> + (*rtstructLoader_, &RtViewerApp::HandleStructuresReady); + + Register<DicomStructureSetLoader::StructuresUpdated> + (*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 : <unspecified>"; + } + else + { + LOG(INFO) << " CT : " << GetArgument("ctseries"); + ctLoader_->LoadSeries(GetArgument("ctseries")); + } + + if (GetArgument("rtdose") == "") + { + LOG(INFO) << " RTDOSE : <unspecified>"; + } + else + { + LOG(INFO) << " RTDOSE : " << GetArgument("rtdose"); + doseLoader_->LoadInstance(GetArgument("rtdose")); + } + + if (GetArgument("rtstruct") == "") + { + LOG(INFO) << " RTSTRUCT : : <unspecified>"; + } + 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<std::string, std::string>::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()); + } +} +
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <boost/enable_shared_from_this.hpp> +#include <boost/thread.hpp> +#include <boost/noncopyable.hpp> + +#if ORTHANC_ENABLE_SDL +#include <SDL.h> +#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<RtViewerApp> + { + public: + + void PrepareScene(); + +#if ORTHANC_ENABLE_SDL + public: + void RunSdl(int argc, char* argv[]); + void SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& 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<RtViewerApp> 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<std::string, std::string> 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<TrackerCommand> 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<DicomVolumeImage> ctVolume_; + boost::shared_ptr<DicomVolumeImage> doseVolume_; + + std::vector<boost::shared_ptr<RtViewerView> > views_; + + boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader_; + boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader_; + boost::shared_ptr<DicomStructureSetLoader> rtstructLoader_; + + /** encapsulates resources shared by loaders */ + boost::shared_ptr<ILoadersContext> 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<OrthancStone::IGeometryProvider> geometryProvider_; + + + boost::shared_ptr<IFlexiblePointerTracker> activeTracker_; + + boost::shared_ptr<UndoStack> undoStack_; + }; + +} + + +
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +// Sample app +#include "RtViewerView.h" +#include "RtViewerApp.h" +#include "SampleHelpers.h" + +#include <EmbeddedResources.h> + +// 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 <Compatibility.h> // For std::unique_ptr<> +#include <Logging.h> +#include <OrthancException.h> + +// System +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/make_shared.hpp> + +#include <stdio.h> + + +namespace OrthancStone +{ + boost::shared_ptr<RtViewerApp> RtViewerView::GetApp() + { + return app_.lock(); + } + + void RtViewerView::DisplayInfoText() + { + std::unique_ptr<IViewport::ILock> 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<std::string, std::string>::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<TextSceneLayer&>( + scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); + layerP = &layer; + } + else + { + std::unique_ptr<TextSceneLayer> 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<IViewport::ILock> 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<TextSceneLayer&>(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); + layer.SetText(buf); + layer.SetPosition(p.GetX(), p.GetY()); + } + else + { + std::unique_ptr<TextSceneLayer> 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<IViewport::ILock> 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<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + + void RtViewerView::FitContent() + { + std::unique_ptr<OrthancStone::IViewport::ILock> 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<int>(currentPlane_) + delta; + + if (temp < 0) + { + next = 0; + } + else if (temp >= static_cast<int>(planes_.size())) + { + next = static_cast<unsigned int>(planes_.size()) - 1; + } + else + { + next = static_cast<size_t>(temp); + } + LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next; + + if (next != static_cast<int>(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<IViewport::ILock> 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<IViewport::ILock> 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<IViewport::ILock> lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Register<ViewportController::SceneTransformChanged>(controller, &RtViewerView::OnSceneTransformChanged); + } + + void RtViewerView::CreateLayers(boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader, + boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader, + boost::shared_ptr<DicomVolumeImage> doseVolume, + boost::shared_ptr<DicomStructureSetLoader> rtstructLoader) + { + /** + Configure the CT + */ + std::unique_ptr<GrayscaleStyleConfigurator> 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<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator); + config->SetLookupTable(lut); + + boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(doseVolume)); + this->SetDoseVolumeSlicer(tmp, config.release()); + } + + this->SetStructureSet(rtstructLoader); + } + + void RtViewerView::SetCtVolumeSlicer(const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + std::unique_ptr<IViewport::ILock> 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<OrthancStone::IVolumeSlicer>& volume, + OrthancStone::ILayerStyleConfigurator* style) + { + std::unique_ptr<IViewport::ILock> 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<OrthancStone::DicomStructureSetLoader>& volume) + { + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); + ViewportController& controller = lock->GetController(); + Scene2D& scene = controller.GetScene(); + int depth = scene.GetMaxDepth() + 1; + + structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); + } +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <boost/enable_shared_from_this.hpp> +#include <boost/thread.hpp> +#include <boost/noncopyable.hpp> + +namespace OrthancStone +{ + class RtViewerApp; + + class RtViewerView : public ObserverBase<RtViewerView> + { + public: + RtViewerView(boost::weak_ptr<RtViewerApp> 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<OrthancSeriesVolumeProgressiveLoader> ctLoader, + boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader, + boost::shared_ptr<DicomVolumeImage> doseVolume, + boost::shared_ptr<DicomStructureSetLoader> rtstructLoader); + + boost::shared_ptr<IViewport> GetViewport() + { + return viewport_; + } + + int GetCtLayerIndex() const + { + return ctLayer_; + } + + private: + void SetInfoDisplayMessage(std::string key, std::string value); + boost::shared_ptr<RtViewerApp> GetApp(); + boost::shared_ptr<IViewport> CreateViewport(const std::string& canvasId); + void DisplayInfoText(); + void HideInfoText(); + void DisplayFloatingCtrlInfoText(const PointerEvent& e); + + void SetCtVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume, + ILayerStyleConfigurator* style); + + void SetDoseVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume, + ILayerStyleConfigurator* style); + + void SetStructureSet(const boost::shared_ptr<DicomStructureSetLoader>& volume); + + private: + boost::weak_ptr<RtViewerApp> app_; + boost::shared_ptr<VolumeSceneLayerSource> ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_; + + // collection of cutting planes for this particular view + std::vector<OrthancStone::CoordinateSystem3D> planes_; + size_t currentPlane_; + + VolumeProjection projection_; + + std::map<std::string, std::string> infoTextMap_; + + int FLOATING_INFOTEXT_LAYER_ZINDEX; + int FIXED_INFOTEXT_LAYER_ZINDEX; + boost::shared_ptr<IViewport> viewport_; + + int ctLayer_; + }; +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <Logging.h> + +#include <boost/algorithm/string.hpp> + +#include <string> +#include <iostream> + +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); + } + } +}
--- /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 `<REPO-ROOT>/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 `<REPO-ROOT>/out/install-stone-wasm-RtViewer-RelWithDebInfo` to `<REPO-ROOT>/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. +
--- /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 + )
--- /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 = $('<a>') + .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); + }); + } + }); + } + }); + } + }); +}); +
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#include <EmbeddedResources.h> + +#include <SystemToolbox.h> +#include <Toolbox.h> + +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<OrthancPluginErrorCode>(e.GetErrorCode()); + } + + return OrthancPluginErrorCode_Success; +} + +template <enum Orthanc::EmbeddedResources::DirectoryResourceId folder> +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 <enum Orthanc::EmbeddedResources::FileResourceId file> +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 + <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_JS> > + ("/stone-rtviewer/RtViewerWasm.js", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM> > + ("/stone-rtviewer/RtViewerWasm.wasm", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_APP_JS> > + ("/stone-rtviewer/RtViewerWasmApp.js", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_INDEX_HTML> > + ("/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; + } +}
--- /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
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancPluginCppWrapper.h" + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/move/unique_ptr.hpp> +#include <boost/thread.hpp> +#include <json/reader.h> +#include <json/writer.h> + + +#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<uint32_t>(size); + if (static_cast<size_t>(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<const char*>(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<const char*>(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<std::string, std::string>& httpHeaders, + bool applyPlugins) + { + Clear(); + + std::vector<const char*> headersKeys; + std::vector<const char*> headersValues; + + for (std::map<std::string, std::string>::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<const char*>(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<const char*>(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<unsigned int>(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<float>(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast<float>(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<std::string>& 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<std::string>& target, + const std::string& key, + bool allowSingleString) const + { + std::list<std::string> lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list<std::string>::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<std::string, std::string>& 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<std::string, std::string>& 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<std::string>(major) + "." + + boost::lexical_cast<std::string>(minor) + "." + + boost::lexical_cast<std::string>(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<unsigned int>(aa); + unsigned int b = static_cast<unsigned int>(bb); + unsigned int c = static_cast<unsigned int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<OrthancJob*>(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(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<OrthancJob*>(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<OrthancJob*>(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast<OrthancJob*>(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<Orthanc::ErrorCode>(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<OrthancJob> 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<float>(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<RequestBodyWrapper*>(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<uint32_t>(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<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<const char*> headersKeys_; + std::vector<const char*> 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<std::string*> 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<const char*>(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<IChunkedRequestReader*>(reader)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<IChunkedRequestReader*>(reader)->Execute(output); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader) + { + if (reader != NULL) + { + delete reinterpret_cast<IChunkedRequestReader*>(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<IChunkedRequestReader> 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<IChunkedRequestReader> 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<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(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<OrthancPluginDicomInstance*>(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<DicomInstance> result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#endif +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OrthancPluginException.h" + +#include <orthanc/OrthancCPlugin.h> +#include <boost/noncopyable.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <json/value.h> +#include <vector> +#include <list> +#include <set> +#include <map> + + + +/** + * 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<const char*>(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<std::string, std::string>& 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<std::string>& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set<std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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 <RestCallback Callback> + 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<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template <RestCallback Callback> + void RegisterRestCallback(const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock + (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); + } + else + { + OrthancPluginRegisterRestCallback + (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map<std::string, uint32_t> 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<std::string, std::string> 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 <ChunkedRestCallback Callback> + static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request)); + if (*reader == NULL) + { + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<GetHandler>, + PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>, + DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect<DeleteHandler>, + PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PutHandler>, + Internals::ChunkedRequestReaderAddChunk, + Internals::ChunkedRequestReaderExecute, + Internals::ChunkedRequestReaderFinalize); +#else + OrthancPluginRegisterRestCallbackNoLock( + GetGlobalContext(), uri.c_str(), + Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>); +#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<size_t>(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 + }; +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include <OrthancException.h> +# 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 <orthanc/OrthancCPlugin.h> +# 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<ORTHANC_PLUGINS_ERROR_ENUMERATION>(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 +}
--- /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 <http://www.gnu.org/licenses/>. + + +# 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()
--- /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: + *; +};
--- /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: + * + * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: + * 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(). + * -# <tt>void OrthancPluginFinalize()</tt>: + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# <tt>const char* OrthancPluginGetName()</tt>: + * The plugin must return a short string to identify itself. + * -# <tt>const char* OrthancPluginGetVersion()</tt>: + * 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 <tt>extern "C"</tt> 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 <http://www.gnu.org/licenses/>. + **/ + + + +#pragma once + + +#include <stdio.h> +#include <string.h> + +#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 <stdint.h> + +#include <stdlib.h> + + + +/******************************************************************** + ** 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 ("<tt>n</tt>"). + * @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 + + +/** @} */ +
--- /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 <http://www.gnu.org/licenses/>. + + +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()
--- /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" + "$<TARGET_FILE_DIR:UnitTests>/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" +) + +target_link_libraries(UnitTests OrthancStone ${DCMTK_LIBRARIES})
--- /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})
--- /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
--- /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 <http://www.gnu.org/licenses/>. + **/ + +#include "../../Common/RtViewerApp.h" +#include "../../Common/RtViewerView.h" +#include "../SdlHelpers.h" + +#include <EmbeddedResources.h> + +// 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 <Compatibility.h> // For std::unique_ptr<> +#include <Images/Image.h> +#include <Images/ImageProcessing.h> +#include <Images/PngWriter.h> + + +#include <boost/program_options.hpp> +#include <boost/shared_ptr.hpp> + +// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions + +#include <SDL.h> + +#include <string> + + +#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<IViewport> 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<std::string>()->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<std::string>()->default_value("http://localhost:8042"), + "Base URL of the Orthanc instance") + + ("ctseries", po::value<std::string>()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"), + "Orthanc ID of the CT series to load. This must be supplied.") + + ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"), + "Orthanc ID of the RTDOSE instance to load. This may be an empty string.") + + ("rtstruct", po::value<std::string>()->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<std::string>(); + 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<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(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<IViewport::ILock> 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<OrthancStone::RtViewerView> GetViewFromWindowId( + const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views, + Uint32 windowID) + { + using namespace OrthancStone; + for (size_t i = 0; i < views.size(); ++i) + { + boost::shared_ptr<OrthancStone::RtViewerView> view = views[i]; + boost::shared_ptr<IViewport> viewport = view->GetViewport(); + boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport); + Uint32 curWindowID = sdlViewport->GetSdlWindowId(); + if (windowID == curWindowID) + return view; + } + return boost::shared_ptr<OrthancStone::RtViewerView>(); + } + + void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views, + OrthancStone::DefaultViewportInteractor& interactor) + { + using namespace OrthancStone; + + // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views + std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports; + for (size_t i = 0; i < views.size(); ++i) + { + boost::shared_ptr<RtViewerView> view = views[i]; + boost::shared_ptr<IViewport> viewport = view->GetViewport(); + boost::shared_ptr<SdlViewport> sdlViewport = + boost::dynamic_pointer_cast<SdlViewport>(viewport); + viewports.push_back(sdlViewport); + } + + { + int scancodeCount = 0; + const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); + + bool stop = false; + while (!stop) + { + std::vector<SDL_Event> sdlEvents; + std::map<Uint32,SDL_Event> 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<Uint32,SDL_Event>::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it) + sdlEvents.push_back(it->second); + + // now process the events + for (std::vector<SDL_Event>::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<RtViewerView> view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + boost::shared_ptr<SdlViewport> sdlViewport = + boost::dynamic_pointer_cast<SdlViewport>(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<RtViewerView> view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + boost::shared_ptr<SdlViewport> sdlViewport = + boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); + sdlViewport->Paint(); + } + else if (sdlEvent.type == SDL_KEYDOWN && + sdlEvent.key.repeat == 0 /* Ignore key bounce */) + { + boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + switch (sdlEvent.key.keysym.sym) + { + case SDLK_f: + { + boost::shared_ptr<SdlViewport> sdlViewport = + boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); + sdlViewport->ToggleMaximize(); + } + break; + + case SDLK_s: + { + std::unique_ptr<OrthancStone::IViewport::ILock> 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<RtViewerView> view = GetViewFromWindowId( + views, sdlEvent.window.windowID); + + std::unique_ptr<OrthancStone::IViewport::ILock> 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<RtViewerView> 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> sdlViewport = + boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport()); + if (sdlViewport->IsRefreshEvent(sdlEvent)) + { + sdlViewport->Paint(); + } + } + } + } + // Small delay to avoid using 100% of CPU + SDL_Delay(1); + } + } + } +} + +boost::weak_ptr<OrthancStone::RtViewerApp> 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<RtViewerApp> app = RtViewerApp::Create(); + g_app = app; + app->RunSdl(argc,argv); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + StoneFinalize(); + + return 0; +} +
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <boost/shared_ptr.hpp> + +#include <SDL.h> + +#include <map> +#include <string> + +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<KeyboardModifiers>(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<OrthancStone::SdlViewport> GetSdlViewportFromWindowId( + const std::vector<boost::shared_ptr<OrthancStone::SdlViewport> >& viewports, + Uint32 windowID) + { + using namespace OrthancStone; + for (size_t i = 0; i < viewports.size(); ++i) + { + boost::shared_ptr<IViewport> viewport = viewports[i]; + boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport); + Uint32 curWindowID = sdlViewport->GetSdlWindowId(); + if (windowID == curWindowID) + return sdlViewport; + } + + return boost::shared_ptr<OrthancStone::SdlViewport>(); + } +} + +
--- /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}) +
--- /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
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <Compatibility.h> // For std::unique_ptr<> +#include <OrthancException.h> + +#include <boost/program_options.hpp> +#include <SDL.h> + +#include <string> + + +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<std::string>()->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<std::string>()->default_value("http://localhost:8042"), + "Base URL of the Orthanc instance") + + ("instance", po::value<std::string>()->default_value("285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"), + "Orthanc ID of the instance to display") + + ("frame_index", po::value<int>()->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<std::string>(); + OrthancStoneHelpers::SetLogLevel(logLevel); + } + + if (vm.count("orthanc") > 0) + { + // maybe check URL validity here + orthancUrl = vm["orthanc"].as<std::string>(); + } + + if (vm.count("instance") > 0) + { + instanceId = vm["instance"].as<std::string>(); + } + + if (vm.count("frame_index") > 0) + { + frameIndex = vm["frame_index"].as<int>(); + } + +} + +/** + * 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<OrthancStone::SdlViewport> viewport = + OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); +#else + boost::shared_ptr<OrthancStone::SdlViewport> 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<SdlSimpleViewerApplication> 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<OrthancStone::IViewport::ILock> 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; + } +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <Compatibility.h> // For std::unique_ptr<> + +#include <boost/make_shared.hpp> + + +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<SdlSimpleViewerApplication> +{ + +public: + static boost::shared_ptr<SdlSimpleViewerApplication> Create(ILoadersContext& context, boost::shared_ptr<IViewport> viewport) + { + boost::shared_ptr<SdlSimpleViewerApplication> application(new SdlSimpleViewerApplication(context, viewport)); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context.Lock()); + application->dicomLoader_ = DicomResourcesLoader::Create(*lock); + } + + application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame) + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject<unsigned int>(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<ILoadersContext::ILock> lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject<unsigned int>(frame)); + } +#endif + + void FitContent() + { + std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); + lock->GetCompositor().FitContent(lock->GetController().GetScene()); + lock->Invalidate(); + } + +private: + ILoadersContext& context_; + boost::shared_ptr<IViewport> viewport_; + boost::shared_ptr<DicomResourcesLoader> dicomLoader_; + boost::shared_ptr<SeriesFramesLoader> framesLoader_; + + SdlSimpleViewerApplication(ILoadersContext& context, + boost::shared_ptr<IViewport> 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<TextureBaseSceneLayer> layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::unique_ptr<IViewport::ILock> 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<ILoadersContext::ILock> lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>( + f.Create(*lock)); + + Register<SeriesFramesLoader::FrameLoadedMessage>( + *framesLoader_, &SdlSimpleViewerApplication::Handle); + + assert(message.HasUserPayload()); + + const Orthanc::SingleValueObject<unsigned int>& payload = + dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>( + 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); + } + } + +}; +
--- /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: <<OK>>") +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: <<OK>>") +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() +
--- /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/ + )
--- /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 +
--- /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} + )
--- /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 <http://www.gnu.org/licenses/>. + **/ + +#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 <OrthancException.h> + +#include <emscripten/html5.h> +#include <emscripten.h> + +#include <boost/make_shared.hpp> + + +class ViewportManager; + +static const unsigned int FONT_SIZE = 32; + +boost::shared_ptr<OrthancStone::DicomVolumeImage> ct_(new OrthancStone::DicomVolumeImage); +boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_; +std::unique_ptr<ViewportManager> widget1_; +std::unique_ptr<ViewportManager> widget2_; +std::unique_ptr<ViewportManager> widget3_; +//OrthancStone::MessageBroker broker_; +//OrthancStone::WebAssemblyOracle oracle_(broker_); +std::unique_ptr<OrthancStone::IFlexiblePointerTracker> tracker_; +static std::map<std::string, std::string> 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<OrthancStone::PointerEvent> 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<OrthancStone::WebGLViewport>& viewport = + *reinterpret_cast<boost::shared_ptr<OrthancStone::WebGLViewport>*>(userData); + + std::unique_ptr<OrthancStone::IViewport::ILock> 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<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer); + layer->SetText(buf); + scene.SetLayer(100, layer.release()); + lock->Invalidate(); + break; + } + + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; + + { + std::unique_ptr<OrthancStone::PointerEvent> 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<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, controller->GetViewport())); + tracker_->PointerMove(*event); + controller->GetViewport().Refresh(); + } + break; + + case EMSCRIPTEN_EVENT_MOUSEUP: + if (tracker_.get() != NULL) + { + std::unique_ptr<OrthancStone::PointerEvent> 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<OrthancStone::WebGLViewport>& 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<ViewportManager> + { + private: + OrthancStone::WebAssemblyViewport viewport_; + std::unique_ptr<VolumeSceneLayerSource> source_; + VolumeProjection projection_; + std::vector<CoordinateSystem3D> 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<IVolumeSlicer>& 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<ViewportManager, DicomVolumeImage::GeometryReadyMessage> + (*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<int>(currentPlane_) + delta; + unsigned int next; + + if (tmp < 0) + { + next = 0; + } + else if (tmp >= static_cast<int>(planes_.size())) + { + next = planes_.size() - 1; + } + else + { + next = static_cast<size_t>(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<OrthancStone::ViewportManager*>(userData); + + if (ctrlDown_) + { + delta *= static_cast<int>(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<TestSleep, SleepOracleCommand::TimeoutMessage> + (*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<std::string, std::string>::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<OrthancStone::GrayscaleStyleConfigurator> 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<OrthancStone::GrayscaleStyleConfigurator> 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<OrthancStone::GrayscaleStyleConfigurator> 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(); + } + } +} +
--- /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 <http://www.gnu.org/licenses/>. + **/ + +#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 <Toolbox.h> + +#include <boost/program_options.hpp> +#include <boost/shared_ptr.hpp> +// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions + +#include <emscripten.h> +#include <emscripten/html5.h> + + +#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<RtViewerView*>(userData); + + int delta = 0; + if (wheelEvent->deltaY < 0) + delta = -1; + if (wheelEvent->deltaY > 0) + delta = 1; + + that->Scroll(delta); + + return 1; + } + + boost::shared_ptr<IViewport> RtViewerView::CreateViewport( + const std::string& canvasId) + { + boost::shared_ptr<IViewport> viewport = WebGLViewport::Create(canvasId); + + void* userData = reinterpret_cast<void*>(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<WebAssemblyLoadersContext> loadersContext = + boost::dynamic_pointer_cast<WebAssemblyLoadersContext>(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<OrthancStone::RtViewerApp> 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); + } + +}
--- /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 +
--- /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(); +
--- /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 @@ +<!doctype html> +<html lang="en-us"> + <head> + <title>Stone of Orthanc Single Frame Viewer </title> + <meta charset="utf-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> + <link rel="icon" href="data:;base64,iVBORw0KGgo="> + + <!-- Disable pinch zoom on mobile devices --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="HandheldFriendly" content="true" /> + + <style> + html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + } + + #RtViewer_Axial { + position: absolute; + left: 0%; + top: 0%; + background-color: red; + width: 50%; + height: 100%; + } + + #RtViewer_Coronal { + position: absolute; + left: 50%; + top: 0%; + background-color: green; + width: 50%; + height: 50%; + } + + #RtViewer_Sagittal { + position: absolute; + left: 50%; + top: 50%; + background-color: blue; + width: 50%; + height: 50%; + } + </style> + </head> + <body> + <canvas id="RtViewer_Axial" oncontextmenu="return false;"></canvas> + <canvas id="RtViewer_Coronal" oncontextmenu="return false;"></canvas> + <canvas id="RtViewer_Sagittal" oncontextmenu="return false;"></canvas> + + <script src="https://code.jquery.com/jquery-3.4.1.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> + + <script src="RtViewerWasmApp.js"></script> + </body> +</html>
--- /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} + )
--- /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
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "SingleFrameViewerApplication.h" + +#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h" +#include "../../../Sources/StoneException.h" +#include "../../../Sources/StoneInitialization.h" + +#include <Compatibility.h> // For std::unique_ptr<> +#include <Toolbox.h> + +#include <emscripten.h> +#include <emscripten/html5.h> + + +#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<OrthancStone::WebAssemblyLoadersContext> context_; +static boost::shared_ptr<OrthancStone::Application> 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<OrthancStone::WebGLViewport> 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; + } +}
--- /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()); +});
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <Compatibility.h> // For std::unique_ptr<> + +#include <boost/make_shared.hpp> + + +namespace OrthancStone +{ + class Application : public ObserverBase<Application> + { + private: + ILoadersContext& context_; + boost::shared_ptr<IViewport> viewport_; + boost::shared_ptr<DicomResourcesLoader> dicomLoader_; + boost::shared_ptr<SeriesFramesLoader> framesLoader_; + + Application(ILoadersContext& context, + boost::shared_ptr<IViewport> 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<TextureBaseSceneLayer> layer( + message.GetInstanceParameters().CreateTexture(message.GetImage())); + layer->SetLinearInterpolation(true); + + { + std::unique_ptr<IViewport::ILock> 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<ILoadersContext::ILock> lock(context_.Lock()); + SeriesFramesLoader::Factory f(*message.GetResources()); + + framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock)); + Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle); + + assert(message.HasUserPayload()); + const Orthanc::SingleValueObject<unsigned int>& payload = + dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(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<Application> Create(ILoadersContext& context, + boost::shared_ptr<IViewport> viewport) + { + boost::shared_ptr<Application> application(new Application(context, viewport)); + + { + std::unique_ptr<ILoadersContext::ILock> lock(context.Lock()); + application->dicomLoader_ = DicomResourcesLoader::Create(*lock); + } + + application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle); + + return application; + } + + void LoadOrthancFrame(const DicomSource& source, + const std::string& instanceId, + unsigned int frame) + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + dicomLoader_->ScheduleLoadOrthancResource( + boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), + 0, source, Orthanc::ResourceType_Instance, instanceId, + new Orthanc::SingleValueObject<unsigned int>(frame)); + } + + void LoadDicomWebFrame(const DicomSource& source, + const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid, + unsigned int frame) + { + std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); + + // We first must load the "/metadata" to know the number of frames + dicomLoader_->ScheduleGetDicomWeb( + boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, + "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", + new Orthanc::SingleValueObject<unsigned int>(frame)); + } + + void FitContent() + { + std::unique_ptr<IViewport::ILock> 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<WebViewerLoaders> + { + 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<IWebViewerLoadersObserver> observer_; + bool loadThumbnails_; + DicomSource source_; + std::set<std::string> scheduledSeries_; + std::set<std::string> scheduledThumbnails_; + std::set<std::string> scheduledStudies_; + boost::shared_ptr<LoadedDicomResources> loadedSeries_; + boost::shared_ptr<LoadedDicomResources> loadedStudies_; + boost::shared_ptr<DicomResourcesLoader> resourcesLoader_; + boost::shared_ptr<SeriesThumbnailsLoader> thumbnailsLoader_; + + WebViewerLoaders(ILoadersContext& context, + IWebViewerLoadersObserver* observer) : + context_(context), + observer_(observer), + loadThumbnails_(false) + { + loadedSeries_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + loadedStudies_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); + } + + static Orthanc::IDynamicObject* CreatePayload(Type type) + { + return new Orthanc::SingleValueObject<Type>(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<const Orthanc::SingleValueObject<Type>&>(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<WebViewerLoaders> Create(ILoadersContext& context, + const DicomSource& source, + bool loadThumbnails, + IWebViewerLoadersObserver* observer) + { + boost::shared_ptr<WebViewerLoaders> application(new WebViewerLoaders(context, observer)); + application->source_ = source; + application->loadThumbnails_ = loadThumbnails; + + { + std::unique_ptr<ILoadersContext::ILock> lock(context.Lock()); + + application->resourcesLoader_ = DicomResourcesLoader::Create(*lock); + + { + SeriesThumbnailsLoader::Factory f; + f.SetPriority(PRIORITY_THUMBNAILS); + application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock)); + } + + application->Register<OrthancRestApiCommand::SuccessMessage>( + lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); + + application->Register<DicomResourcesLoader::SuccessMessage>( + *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); + + application->Register<SeriesThumbnailsLoader::SuccessMessage>( + *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); + + lock->AddLoader(application); + } + + return application; + } + + void AddDicomAllSeries() + { + std::unique_ptr<ILoadersContext::ILock> 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<OrthancRestApiCommand> 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<Orthanc::DicomTag> tags; + + { + std::unique_ptr<ILoadersContext::ILock> 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<OrthancRestApiCommand> 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<ILoadersContext::ILock> 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<Orthanc::DicomTag> tags; + + std::unique_ptr<ILoadersContext::ILock> 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<OrthancRestApiCommand> 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<ILoadersContext::ILock> 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<ILoadersContext::ILock> 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"); + } + } + }; +}
--- /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 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Stone of Orthanc Single Frame Viewer </title> + <meta charset="utf-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> + <link rel="icon" href="data:;base64,iVBORw0KGgo="> + + <style> + canvas { + background-color: green; + width : 100%; + height : 512px; + } + </style> + </head> + <body> + <h1>Viewport</h1> + + <canvas id="viewport" > + </canvas> + + <h1>Load from Orthanc</h1> + <p> + Orthanc instance: <input type="text" id="orthancInstance" size="80" + value="5eb2dd5f-3fca21a8-fa7565fd-63e112ae-344830a4"> + </p> + <p> + Frame number: <input type="text" id="orthancFrame" value="0"> + </p> + <p> + <button id="orthancLoad">Load</button> + </p> + + <script src="https://code.jquery.com/jquery-3.4.1.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> + + <script src="SingleFrameViewerApp.js"></script> + </body> +</html>
--- /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/
--- /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
--- /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
--- /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. <http://fsf.org/> + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +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 +<http://www.gnu.org/licenses/>.
--- /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 + )
--- /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 = $('<a>') + .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 = $('<a>') + .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); + } + }); + } + }); + } + }); +});
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#include <EmbeddedResources.h> + +#include <SystemToolbox.h> +#include <Toolbox.h> + +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<std::string> 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<int>(tokens[0]); + minor = boost::lexical_cast<int>(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<OrthancPluginErrorCode>(e.GetErrorCode()); + } + + return OrthancPluginErrorCode_Success; +} + + +template <enum Orthanc::EmbeddedResources::DirectoryResourceId folder> +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 <enum Orthanc::EmbeddedResources::FileResourceId file> +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 + <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_WASM> > + ("/stone-webviewer/StoneWebViewer.wasm", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_JS> > + ("/stone-webviewer/StoneWebViewer.js", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WRAPPER> > + ("/stone-webviewer/stone.js", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFolder<Orthanc::EmbeddedResources::IMAGES> > + ("/stone-webviewer/img/(.*)", true); + + OrthancPlugins::RegisterRestCallback + <ServeEmbeddedFolder<Orthanc::EmbeddedResources::WEB_APPLICATION> > + ("/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; + } +}
--- /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 <http://www.gnu.org/licenses/>. + + +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')
--- /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
--- /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
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "OrthancPluginCppWrapper.h" + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/move/unique_ptr.hpp> +#include <boost/thread.hpp> +#include <json/reader.h> +#include <json/writer.h> + + +#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<uint32_t>(size); + if (static_cast<size_t>(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<const char*>(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<const char*>(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<std::string, std::string>& httpHeaders, + bool applyPlugins) + { + Clear(); + + std::vector<const char*> headersKeys; + std::vector<const char*> headersValues; + + for (std::map<std::string, std::string>::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<const char*>(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<const char*>(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<unsigned int>(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<float>(configuration_[key].asInt()); + return true; + + case Json::uintValue: + target = static_cast<float>(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<std::string>& 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<std::string>& target, + const std::string& key, + bool allowSingleString) const + { + std::list<std::string> lst; + + if (LookupListOfStrings(lst, key, allowSingleString)) + { + target.clear(); + + for (std::list<std::string>::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<std::string, std::string>& 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<std::string, std::string>& 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<std::string>(major) + "." + + boost::lexical_cast<std::string>(minor) + "." + + boost::lexical_cast<std::string>(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<unsigned int>(aa); + unsigned int b = static_cast<unsigned int>(bb); + unsigned int c = static_cast<unsigned int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<OrthancJob*>(job); + } + } + + + float OrthancJob::CallbackGetProgress(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->progress_; + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetContent(void* job) + { + assert(job != NULL); + + try + { + return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); + } + catch (...) + { + return 0; + } + } + + + const char* OrthancJob::CallbackGetSerialized(void* job) + { + assert(job != NULL); + + try + { + const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(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<OrthancJob*>(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<OrthancJob*>(job)->Stop(reason); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) + { + assert(job != NULL); + + try + { + reinterpret_cast<OrthancJob*>(job)->Reset(); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<Orthanc::ErrorCode>(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<OrthancJob> 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<float>(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<RequestBodyWrapper*>(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<uint32_t>(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<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<const char*> headersKeys_; + std::vector<const char*> 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<std::string*> 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<const char*>(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<IChunkedRequestReader*>(reader)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<IChunkedRequestReader*>(reader)->Execute(output); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + + + void ChunkedRequestReaderFinalize( + OrthancPluginServerChunkedRequestReader* reader) + { + if (reader != NULL) + { + delete reinterpret_cast<IChunkedRequestReader*>(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<IChunkedRequestReader> 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<IChunkedRequestReader> 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<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(rawHandler); + *target = handler.Lookup(sopClassUid, sopInstanceUid); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(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<OrthancPluginDicomInstance*>(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<DicomInstance> result(new DicomInstance(instance)); + result->toFree_ = true; + return result.release(); + } + } +#endif +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OrthancPluginException.h" + +#include <orthanc/OrthancCPlugin.h> +#include <boost/noncopyable.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <json/value.h> +#include <vector> +#include <list> +#include <set> +#include <map> + + + +/** + * 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<const char*>(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<std::string, std::string>& 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<std::string>& target, + const std::string& key, + bool allowSingleString) const; + + bool LookupSetOfStrings(std::set<std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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 <RestCallback Callback> + 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<OrthancPluginErrorCode>(e.GetErrorCode()); + } + catch (boost::bad_lexical_cast&) + { + return OrthancPluginErrorCode_BadFileFormat; + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } + } + + + template <RestCallback Callback> + void RegisterRestCallback(const std::string& uri, + bool isThreadSafe) + { + if (isThreadSafe) + { + OrthancPluginRegisterRestCallbackNoLock + (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); + } + else + { + OrthancPluginRegisterRestCallback + (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); + } + } + + +#if HAS_ORTHANC_PLUGIN_PEERS == 1 + class OrthancPeers : public boost::noncopyable + { + private: + typedef std::map<std::string, uint32_t> 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<std::string, std::string> 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 <ChunkedRestCallback Callback> + static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, + const char* url, + const OrthancPluginHttpRequest* request) + { + try + { + if (reader == NULL) + { + return OrthancPluginErrorCode_InternalError; + } + else + { + *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request)); + if (*reader == NULL) + { + return OrthancPluginErrorCode_Plugin; + } + else + { + return OrthancPluginErrorCode_Success; + } + } + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast<OrthancPluginErrorCode>(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<GetHandler>, + PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>, + DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect<DeleteHandler>, + PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PutHandler>, + Internals::ChunkedRequestReaderAddChunk, + Internals::ChunkedRequestReaderExecute, + Internals::ChunkedRequestReaderFinalize); +#else + OrthancPluginRegisterRestCallbackNoLock( + GetGlobalContext(), uri.c_str(), + Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>); +#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<size_t>(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 + }; +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if !defined(HAS_ORTHANC_EXCEPTION) +# error The macro HAS_ORTHANC_EXCEPTION must be defined +#endif + + +#if HAS_ORTHANC_EXCEPTION == 1 +# include <OrthancException.h> +# 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 <orthanc/OrthancCPlugin.h> +# 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<ORTHANC_PLUGINS_ERROR_ENUMERATION>(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 +}
--- /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 <http://www.gnu.org/licenses/>. + + +# 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()
--- /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: + *; +};
--- /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: + * + * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: + * 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(). + * -# <tt>void OrthancPluginFinalize()</tt>: + * This function is invoked by Orthanc during its shutdown. The plugin + * must free all its memory. + * -# <tt>const char* OrthancPluginGetName()</tt>: + * The plugin must return a short string to identify itself. + * -# <tt>const char* OrthancPluginGetVersion()</tt>: + * 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 <tt>extern "C"</tt> 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 <http://www.gnu.org/licenses/>. + **/ + + + +#pragma once + + +#include <stdio.h> +#include <string.h> + +#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 <stdint.h> + +#include <stdlib.h> + + + +/******************************************************************** + ** 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). + * @param password The password (can be <tt>NULL</tt> 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 ("<tt>n</tt>"). + * @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 + + +/** @} */ +
--- /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 <a> default styles. Take care - this class may either be used + // with <a> or <button>. + &:hover { + text-decoration: none; + color: white; + } + + // Remove <button> default styles. + outline: none; + background-color: transparent; + border: none; + border-radius: 0; + + // Set relative to position button absolutely + position: relative; + + // Style button + display: inline-block; + cursor: pointer; + font-variant: small-caps; + text-transform: lowercase; + text-align: center; + font-size: 1.3rem; + font-weight: 400; + color: hsl(0, 0%, 85%); + transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; + + // Position button + margin: 0; + min-width: 3rem; + padding: 0 10px; + line-height: 3.6rem; + &.wvLargeButton{ + font-size: 2rem; + line-height: 6.2rem; + padding: 0 20px; + } +} + +.wvButton--rotate { + @extend .wvButton; + // Rotate only the icon + &:before, &:after{ + transform: rotate(90deg); + display: inline-block; + } + +} + +.wvButton--vflip { + @extend .wvButton; + // flip only the icon + &:before, &:after{ + transform: scaleX(-1); + display: inline-block; + } + +} + +// button w/ blue underline +.wvButton--underline, .fa.wvButton--underline { + @extend .wvButton; + + position: relative; + + background-color:inherit; + text-decoration: none; + text-align:left; + font-size: 1.2rem; + &.wvLargeButton{ + font-size: 2rem; + width: 6.4rem; + } + * { + pointer-events: none; + } + &:hover, &:active, &:focus{ + outline:0; + } + + width: 3.2rem; + vertical-align: middle; + color:white; + opacity: 0.75; + border:none; + border-bottom: 2px solid rgba(255,255,255,0.1); + &:hover, &:focus{ + border-color: rgba(255,255,255,1); + opacity:1; + .wvButton__bottomTriangle{ + border-left-color: rgba(255,255,255,1); + &.toggled{ + // border-color: rgba(255, 255, 255, 1); + } + } + } + &.active{ + opacity: 1; + border-color: $primary; + } + + // Make sure the 2px border is not hidden by viewports and other parts of + // the layout (.glyphicon class sets top to 1px) + top: 0px; + + // Compensate glyphicon whitespace + &::before { + position: relative; + top: -1px; + } + + // Adapt font-awesome icon to glyphicon styles + &.fa { + top: 0px; + font-weight: 800; + } +} + +.wvButton__bottomTriangle{ + transition: 0.3s border ease, 0.3s opacity ease; + + display:block; + position: absolute; + bottom:0; + left:0; + + width: 0; + height: 0; + border-style: solid; + border-width: 10px 0 0 10px; + border-color: transparent transparent transparent rgba(255,255,255,0.1); + &.active{ + border-color: transparent transparent transparent $primary !important; + &.toggled{ + border-left-color: $primary !important; + } + } + &.toggled{ + // border-width: 15px 0 0 15px; + } +} + +// button w/ border +.wvButton--border { + @extend .wvButton; + + // Prevent multi line buttons. + max-height: calc(2.8rem - 3px); + max-width: 100%; + overflow: hidden; + + // Set margin + margin: 0.6rem; + margin-left: 0rem; + margin-right: 0rem; + & + & { + margin-left: 0.7rem; + } + + // Set button size + line-height: 2rem; + + // Align text + padding-top: 0.1rem; + padding-bottom: 0.5rem; + + // Style button + font-size: 1.4rem; + border: 1px solid hsl(0, 0%, 27%); + + // Set best looking font with small-caps. + font-family: Arial; + + // Change background on hover + background-color: hsl(0, 0%, 0%); + &:hover { + background-color: hsl(0, 0%, 10%); + } + + & > .glyphicon { // used with the same element as glyphicons + // Position button + position: relative; + display: inline-block; + top: 3px; + margin-right: 4px; + } +} + +// button w/ border + white modifier to use when the background is white. +.wvButton--borderAndWhite { + @extend .wvButton--border; + + // Text color + color: hsl(0, 0%, 10%); + border: 1px solid hsl(0, 0%, 73%); + + // Change background on hover + background-color: hsl(0, 0%, 100%); + &:hover { + color: hsl(0, 0%, 10%); + background-color: hsl(0, 0%, 90%); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_exitButton.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,28 @@ +.wvExitButton { + margin-left: 1rem; + margin-top: .25rem; + font-size: 1.25em; + color: white; + opacity: .66; + + transition: .3s opacity ease; + background-color: inherit; + border: none; + text-decoration: none; + text-align: left; + padding: 0; + cursor: pointer; + font-family: inherit; + line-height: inherit; + + &:hover, &:focus { + opacity: 1; + outline: 0; + } +} + +.wvExitButton__text { + // Align text with glyph-icon. + position: relative; + top: -1px; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_helpers.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,1115 @@ +/* + * Source code taken from private Osimis' frontend toolbox 3.2.1. + */ + +/** + _overlay.scss + */ +.overlay__transparent{ + position:fixed; + top:0; + left:0; + width:100%; + height:100%; + z-index: 1; +} + +/** _transition.scss **/ +.transition, +%transition{ + &{ + transition: 0.3s all ease; + } +} + +.transition--long, +%transition--long{ + &{ + transition: 0.6s all ease; + } +} + + +/** _list.scss **/ +// This is used in the study information panel. +dd+dt{ + clear:both; +} +.listDefinition{ + width:100%; + line-height: 1.3; +} +.listDefinition__term{ + clear:both; + float:left; + text-align: right; + padding-right:10px; + width:50%; + @extend .font__light; + +} +.listDefinition__data{ + text-align: left; + padding-left:10px; + float:right; + width:50%; + @extend .font__normal; + +} + + +/** _animation.scss **/ +@keyframes blink__primary { + 0% { color: $primary; } + 100% { color: $text-color; } +} + +.blink__primary{ + animation: blink__primary 0.8s linear infinite; +} + +[translate-cloak]{ + transition: 0.3s all ease; + opacity: 1; + &.translate-cloak{ + opacity: 0; + } +} + +/** _button.scss **/ +.button__unstyled, +%button__unstyled{ + background-color:inherit; + border:none; + text-decoration: none; + text-align:left; + padding:0; + cursor: pointer; + *{ + pointer-events: none; + } + &:hover, &:active, &:focus{ + outline:0; + } +} +.button__base, +%button__base{ + @extend .button__unstyled; + transition: 0.3s all ease; + //& *,& *:before,& *:after{ + // @extend .transition; + //} +} +.button__state--active{ + @extend .button__base; + opacity:1; + &:hover{ + opacity:0.9; + color:$primary; + } +} +.button__state--inactive{ + @extend .button__base; + opacity:0.333; + &:hover{ + opacity:0.4333; + color:$primary; + } +} +.button__iconed{ + @extend .button__base; + opacity: 1; + &:hover, &:focus, &.active{ + opacity: 0.75; + color:$primary; + } +} +.button__switch--base, +%button__switch--base{ + @extend .button__base; + padding:5px 0px; + display:inline-block; + text-align: center; + border-top: 1px solid; + border-bottom: 1px solid; + border-color: $primary; + background-color: white; + overflow: hidden; + &:hover, &:focus{ + background-color: lighten($primary, 10); + color:white; + } + &.active{ + background-color:$primary; + color:white; + } +} +.button__switch{ + @extend .button__switch--base; +} +.button__switch--first{ + @extend .button__switch--base; + border-left: 1px solid $primary; + border-radius: 5px 0 0 5px; +} +.button__switch--last{ + @extend .button__switch--base; + border-right: 1px solid $primary; + border-radius: 0 5px 5px 0; +} +.button__lightgrey--hover{ + @extend .button__base; + &:hover, &:focus, &.active{ + background-color: $lightGrey; + } +} +.button__text-danger--hover{ + @extend .button__base; + &:hover, &:focus, &.active{ + color: $dangerColor; + } +} +.button__text-primary--hover{ + @extend .button__base; + &:hover, &:focus, &.active{ + color: $primary; + } +} +.button__danger--hover{ + @extend .button__base; + &:hover, &:focus, &.active{ + background-color: $dangerColor; + color:white; + } +} +.button__text{ + @extend .button__base; + opacity:0.66; + &:hover, &.active, &:focus{ + opacity:1; + } +} +.button__text--underlined{ + @extend .button__base; + text-decoration: underline; + &:hover, &.active, &:focus{ + text-decoration:none; + } +} +.button__bordered{ + @extend .button__base; + border-bottom: 2px solid $text-color; + &:hover, &:focus, &.active{ + border-color: $primary; + } +} +.button__bordered--inverted{ + @extend .button__base; + border-bottom: 2px solid white; +} +.button__close{ + @extend .button__base; + position:absolute; + top:0; + right:0; + opacity:0.6; + z-index:10; + &:hover, &:focus{ + opacity:1; + } +} + + +/** _block.scss **/ +.block{ + display: block !important; +} + + +/** _boxsizing.scss **/ +.boxsizing__borderbox{ + box-sizing:border-box; +} +.boxsizing__contentbox{ + box-sizing:content-box; +} + + +/** _scrollable.scss **/ +.scrollable{ + overflow-y:auto; +} +.scrollable--x{ + overflow-x:auto; +} +.no-scroll{ + overflow:hidden; +} + + +/** _float.scss **/ +.float__right, +%float__right{ + float:right; +} +.float__left, +%float__left{ + float:left; +} + + +/** _fonts.scss **/ +.font__bold, +%font__bold{ + font-weight:600; +} +.font__normal, +%font__normal{ + font-weight: 400; +} +.font__light, +%font__light{ + font-weight: 200; +} +.fontColor__primary{ + color:$primary; +} +.fontColor__lightGrey{ + color:$lightGrey; +} +.fontColor__normal{ + color:$text-color; +} +.fontColor__darker{ + color:$darkGrey; +} +.fontColor__white{ + color:white; +} + + +/** _forms.scss **/ +.textarea__unstyled{ + border:none; + outline:none; + background-color:transparent; + color:inherit; + resize:none; + padding:0; + margin:0; + width:100%; +} + + +/** _position.scss **/ +.position__relative{ + position: relative; +} + + +/** _margin.scss **/ +.margin__auto{ + margin:auto; +} + + +/** _helpers.scss **/ +$steps: ( + 0,5,8,10, + 11,12,13,14,15,16,17,18,19,20, + 21,22,23,24,25,26,27,28,29,30, + 31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50, + 55,60,64,65,70,72,75,80,85,90,95,96,100, + 110,120,130,140,150,160,170,180,190,200, + 210,220,230,240,250,260,270,280,290,300, + 310,320,330,340,350,360,370,380,390,400, + 410,420,430,440,450,460,470,480,490,500 +); + + +/*************HELPERS**************/ +/**********************************/ + +/*** identical width and height ***/ +@each $step in $steps { + .wh__#{$step} { + width: $step + px !important; + height: $step + px !important; + } + .lh__#{$step} { + line-height: $step + px !important; + } +} +.lh__1{ + line-height: 1 !important; +} +.no-wrap{ + white-space: nowrap; +} + +.ov-h{ + overflow: hidden; +} +.va-m{ + vertical-align: middle; +} +.bg-inherit{ + background-color:inherit; +} +.bg-black{ + background-color: black; +} +.v-center{ + &:before { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + margin-top: -0.25em; /* Adjusts for spacing */ + } +} +.fluid-height{ + height:100%; +} +.visibility__hidden{ + visibility: hidden; +} + +.pointerEvents__none{ + pointer-events: none; +} + +/* Padding Helpers */ +.pn { + padding: 0 !important; +} +.p1 { + padding: 1px !important; +} +.p2 { + padding: 2px !important; +} +.p3 { + padding: 3px !important; +} +.p4 { + padding: 4px !important; +} +.p5 { + padding: 5px !important; +} +.p6 { + padding: 6px !important; +} +.p7 { + padding: 7px !important; +} +.p8 { + padding: 8px !important; +} +.p10 { + padding: 10px !important; +} +.p12 { + padding: 12px !important; +} +.p15 { + padding: 15px !important; +} +.p20 { + padding: 20px !important; +} +.p25 { + padding: 25px !important; +} +.p30 { + padding: 30px !important; +} +.p35 { + padding: 35px !important; +} +.p40 { + padding: 40px !important; +} +.p50 { + padding: 50px !important; +} +.ptn { + padding-top: 0 !important; +} +.pt5 { + padding-top: 5px !important; +} +.pt10 { + padding-top: 10px !important; +} +.pt15 { + padding-top: 15px !important; +} +.pt20 { + padding-top: 20px !important; +} +.pt25 { + padding-top: 25px !important; +} +.pt30 { + padding-top: 30px !important; +} +.pt35 { + padding-top: 35px !important; +} +.pt40 { + padding-top: 40px !important; +} +.pt50 { + padding-top: 50px !important; +} +.prn { + padding-right: 0 !important; +} +.pr5 { + padding-right: 5px !important; +} +.pr10 { + padding-right: 10px !important; +} +.pr15 { + padding-right: 15px !important; +} +.pr20 { + padding-right: 20px !important; +} +.pr25 { + padding-right: 25px !important; +} +.pr30 { + padding-right: 30px !important; +} +.pr35 { + padding-right: 35px !important; +} +.pr40 { + padding-right: 40px !important; +} +.pr50 { + padding-right: 50px !important; +} +.pbn { + padding-bottom: 0 !important; +} +.pb5 { + padding-bottom: 5px !important; +} +.pb10 { + padding-bottom: 10px !important; +} +.pb15 { + padding-bottom: 15px !important; +} +.pb20 { + padding-bottom: 20px !important; +} +.pb25 { + padding-bottom: 25px !important; +} +.pb30 { + padding-bottom: 30px !important; +} +.pb35 { + padding-bottom: 35px !important; +} +.pb40 { + padding-bottom: 40px !important; +} +.pb50 { + padding-bottom: 50px !important; +} +.pln { + padding-left: 0 !important; +} +.pl5 { + padding-left: 5px !important; +} +.pl10 { + padding-left: 10px !important; +} +.pl15 { + padding-left: 15px !important; +} +.pl20 { + padding-left: 20px !important; +} +.pl25 { + padding-left: 25px !important; +} +.pl30 { + padding-left: 30px !important; +} +.pl35 { + padding-left: 35px !important; +} +.pl40 { + padding-left: 40px !important; +} +.pl50 { + padding-left: 50px !important; +} + +/* Axis Padding (both top/bottom or left/right) */ +.pv5 { + padding-top: 5px !important; + padding-bottom: 5px !important; +} +.pv8 { + padding-top: 8px !important; + padding-bottom: 8px !important; +} +.pv10 { + padding-top: 10px !important; + padding-bottom: 10px !important; +} +.pv15 { + padding-top: 15px !important; + padding-bottom: 15px !important; +} +.pv20 { + padding-top: 20px !important; + padding-bottom: 20px !important; +} +.pv25 { + padding-top: 25px !important; + padding-bottom: 25px !important; +} +.pv30 { + padding-top: 30px !important; + padding-bottom: 30px !important; +} +.pv40 { + padding-top: 40px !important; + padding-bottom: 40px !important; +} +.pv50 { + padding-top: 50px !important; + padding-bottom: 50px !important; +} +.ph5 { + padding-left: 5px !important; + padding-right: 5px !important; +} +.ph8 { + padding-left: 8px !important; + padding-right: 8px !important; +} +.ph10 { + padding-left: 10px !important; + padding-right: 10px !important; +} +.ph15 { + padding-left: 15px !important; + padding-right: 15px !important; +} +.ph20 { + padding-left: 20px !important; + padding-right: 20px !important; +} +.ph25 { + padding-left: 25px !important; + padding-right: 25px !important; +} +.ph30 { + padding-left: 30px !important; + padding-right: 30px !important; +} +.ph40 { + padding-left: 40px !important; + padding-right: 40px !important; +} +.ph50 { + padding-left: 50px !important; + padding-right: 50px !important; +} + +/* margin center helper */ +.mauto { + margin-left: auto; + margin-right: auto; +} +.mn { + margin: 0 !important; +} +.m1 { + margin: 1px !important; +} +.m2 { + margin: 2px !important; +} +.m3 { + margin: 3px !important; +} +.m4 { + margin: 4px !important; +} +.m5 { + margin: 5px !important; +} +.m8 { + margin: 8px !important; +} +.m10 { + margin: 10px !important; +} +.m15 { + margin: 15px !important; +} +.m20 { + margin: 20px !important; +} +.m25 { + margin: 25px !important; +} +.m30 { + margin: 30px !important; +} +.m35 { + margin: 35px !important; +} +.m40 { + margin: 40px !important; +} +.m50 { + margin: 50px !important; +} +.mtn { + margin-top: 0 !important; +} +.mt5 { + margin-top: 5px !important; +} +.mt10 { + margin-top: 10px !important; +} +.mt15 { + margin-top: 15px !important; +} +.mt20 { + margin-top: 20px !important; +} +.mt25 { + margin-top: 25px !important; +} +.mt30 { + margin-top: 30px !important; +} +.mt35 { + margin-top: 35px !important; +} +.mt40 { + margin-top: 40px !important; +} +.mt50 { + margin-top: 50px !important; +} +.mt70 { + margin-top: 70px !important; +} +.mrn { + margin-right: 0 !important; +} +.mr5 { + margin-right: 5px !important; +} +.mr10 { + margin-right: 10px !important; +} +.mr15 { + margin-right: 15px !important; +} +.mr20 { + margin-right: 20px !important; +} +.mr25 { + margin-right: 25px !important; +} +.mr30 { + margin-right: 30px !important; +} +.mr35 { + margin-right: 35px !important; +} +.mr40 { + margin-right: 40px !important; +} +.mr50 { + margin-right: 50px !important; +} +.mbn { + margin-bottom: 0 !important; +} +.mb5 { + margin-bottom: 5px !important; +} +.mb10 { + margin-bottom: 10px !important; +} +.mb15 { + margin-bottom: 15px !important; +} +.mb20 { + margin-bottom: 20px !important; +} +.mb25 { + margin-bottom: 25px !important; +} +.mb30 { + margin-bottom: 30px !important; +} +.mb35 { + margin-bottom: 35px !important; +} +.mb40 { + margin-bottom: 40px !important; +} +.mb50 { + margin-bottom: 50px !important; +} +.mb70 { + margin-bottom: 70px !important; +} +.mln { + margin-left: 0 !important; +} +.ml5 { + margin-left: 5px !important; +} +.ml10 { + margin-left: 10px !important; +} +.ml15 { + margin-left: 15px !important; +} +.ml20 { + margin-left: 20px !important; +} +.ml25 { + margin-left: 25px !important; +} +.ml30 { + margin-left: 30px !important; +} +.ml35 { + margin-left: 35px !important; +} +.ml40 { + margin-left: 40px !important; +} +.ml50 { + margin-left: 50px !important; +} + +/* Axis Margins (both top/bottom or left/right) */ +.mv5 { + margin-top: 5px !important; + margin-bottom: 5px !important; +} +.mv10 { + margin-top: 10px !important; + margin-bottom: 10px !important; +} +.mv15 { + margin-top: 15px !important; + margin-bottom: 15px !important; +} +.mv20 { + margin-top: 20px !important; + margin-bottom: 20px !important; +} +.mv25 { + margin-top: 25px !important; + margin-bottom: 25px !important; +} +.mv30 { + margin-top: 30px !important; + margin-bottom: 30px !important; +} +.mv40 { + margin-top: 40px !important; + margin-bottom: 40px !important; +} +.mv50 { + margin-top: 50px !important; + margin-bottom: 50px !important; +} +.mv70 { + margin-top: 70px !important; + margin-bottom: 70px !important; +} +.mh5 { + margin-left: 5px !important; + margin-right: 5px !important; +} +.mh10 { + margin-left: 10px !important; + margin-right: 10px !important; +} +.mh15 { + margin-left: 15px !important; + margin-right: 15px !important; +} +.mh20 { + margin-left: 20px !important; + margin-right: 20px !important; +} +.mh25 { + margin-left: 25px !important; + margin-right: 25px !important; +} +.mh30 { + margin-left: 30px !important; + margin-right: 30px !important; +} +.mh40 { + margin-left: 40px !important; + margin-right: 40px !important; +} +.mh50 { + margin-left: 50px !important; + margin-right: 50px !important; +} +.mh70 { + margin-left: 70px !important; + margin-right: 70px !important; +} + +/* Negative Margin Helpers */ +.mtn5 { + margin-top: -5px !important; +} +.mtn10 { + margin-top: -10px !important; +} +.mtn15 { + margin-top: -15px !important; +} +.mtn20 { + margin-top: -20px !important; +} +.mtn30 { + margin-top: -30px !important; +} +.mrn5 { + margin-right: -5px !important; +} +.mrn10 { + margin-right: -10px !important; +} +.mrn15 { + margin-right: -15px !important; +} +.mrn20 { + margin-right: -20px !important; +} +.mrn30 { + margin-right: -30px !important; +} +.mbn5 { + margin-bottom: -5px !important; +} +.mbn10 { + margin-bottom: -10px !important; +} +.mbn15 { + margin-bottom: -15px !important; +} +.mbn20 { + margin-bottom: -20px !important; +} +.mbn30 { + margin-bottom: -30px !important; +} +.mln5 { + margin-left: -5px !important; +} +.mln10 { + margin-left: -10px !important; +} +.mln15 { + margin-left: -15px !important; +} +.mln20 { + margin-left: -20px !important; +} +.mln30 { + margin-left: -30px !important; +} + +/* Vertical Negative Margin "mv" + "n" + "x" */ +.mvn5 { + margin-top: -5px !important; + margin-bottom: -5px !important; +} +.mvn10 { + margin-top: -10px !important; + margin-bottom: -10px !important; +} +.mvn15 { + margin-top: -15px !important; + margin-bottom: -15px !important; +} +.mvn20 { + margin-top: -20px !important; + margin-bottom: -20px !important; +} +.mvn30 { + margin-top: -30px !important; + margin-bottom: -30px !important; +} + +/* Horizontal Negative Margin "mh" + "n" + "x" */ +.mhn5 { + margin-left: -5px !important; + margin-right: -5px !important; +} +.mhn10 { + margin-left: -10px !important; + margin-right: -10px !important; +} +.mhn15 { + margin-left: -15px !important; + margin-right: -15px !important; +} +.mhn20 { + margin-left: -20px !important; + margin-right: -20px !important; +} +.mhn30 { + margin-left: -30px !important; + margin-right: -30px !important; +} + +/* Vertical Align Helpers */ +.va-t { + vertical-align: top !important; +} +.va-m { + vertical-align: middle !important; +} +.va-b { + vertical-align: bottom !important; +} +.va-s { + vertical-align: super !important; +} + +/* Text Helpers */ +.text-left { + text-align: left !important; +} +.text-right { + text-align: right !important; +} +.text-center { + text-align: center !important; +} +.text-justify { + text-align: justify !important; +} +.text-nowrap { + white-space: nowrap !important; +} + +/* Inline Block Helper */ +.ib, +.inline-object { + display: inline-block !important; +} + +.clear { + clear: both; +} + +// warning popup + +.wvWarning { + position: relative; + width: 320px; + min-height: 130px; + z-index: 999; + left: calc(50% - 160px); + border: #000 solid 1px; + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + border-radius: 7px; + color: #FF5722; + box-shadow: 0px 3px 23px #ff980078; + -webkit-animation-name: example; /* Safari 4.0 - 8.0 */ + -webkit-animation-duration: 3s; /* Safari 4.0 - 8.0 */ + -webkit-animation-fill-mode: both; /* Safari 4.0 - 8.0 */ + animation-name: example; + animation-duration: 2s; + animation-fill-mode: both; + animation-timing-function: ease-out; +} + +@-webkit-keyframes example { + from {top: 0vh;opacity: 0;background: #868686} + to {top: 10vh;opacity: 1;background: #ffffff} +} + +@keyframes example { + from {top: 0vh;opacity: 0;background: #868686} + to {top: 10vh;opacity: 1;background: #ffffff} +} + +.wvWarning-content{ + position: relative; + width: 190px; + min-height: 88px; + max-height: 80vh; + margin: auto; +} +.wvWarning-icon{ + font-size: 32px; +} +.wvWarning-text{ + position: relative; +} +.wvWarning-button{ + background-color: #f1ededcc; + color: #607D8B; + width: 50px; + font-weight: 600; + margin-top: 2px; + margin-right: 30px; +} + +.wvScreenToSmallWarning { + position: fixed; + display: block; + top: 0; + left: 0; + background-color: white; + color: #333; + width: 100%; + height: 100%; + z-index: 1000; +} + +.wvScreenToSmallWarning-content { + padding: 10px; + text-align: center; +} + +/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ +@media only screen and (min-width: 550px) and (min-height: 280px) { + .wvScreenToSmallWarning { + display: none; + } +} + +/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ +@media only screen and (min-width: 280px) and (min-height: 550px) { + .wvScreenToSmallWarning { + display: none; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_layout.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,498 @@ +$topHeight: 42px; +$bottomHeightSmall: 7rem; // On small width, we provide two-lines bottom zone to compensate the smaller width +$bottomHeightLarge: 5rem; // On large width, we provide one-line bottom zone + +$asideWidth: 32rem; +$asideMinifyWidth: 12rem; + +$asideRightMinifyWidth: 85px; // eq. 7.5rem * 12px - ( $asideRightPadding / 2 ) +$asideRightPadding: 10px; + +/* layout: left section */ + +// Adapt left aside based on state (opened/closed). +.wvLayoutLeft { + // Set general properties. + position:absolute; + z-index:2; + background-color:black; + width: $asideWidth; + + // Position the left side below the top zone if it is shown + &.wvLayoutLeft--toppadding { + top: $topHeight; + } + &:not(.wvLayoutLeft--toppadding) { + top: 0; + } + + // Position the left section over the bottom one if the latter is shown + &.wvLayoutLeft--bottompadding { + @media screen and (max-device-width: 374px) { + bottom: $bottomHeightSmall; + } + @media screen and (min-device-width: 375px) { + bottom: $bottomHeightLarge; + } + } + &:not(.wvLayoutLeft--bottompadding) { + bottom: 0; + } + + // Position the left side on the left + left: 0; + + // When layout left is shown, nothing special happens (default state) + &:not(.wvLayoutLeft--closed) { + } + + // When layout left is closed, move it aside + &.wvLayoutLeft--closed { + transform: translateX(- $asideWidth); // Move all out of the screen + &.wvLayoutLeft--small { + transform: translateX(-$asideMinifyWidth); + } + } + &.wvLayoutLeft--small{ + width: $asideMinifyWidth; + & .wvLayoutLeft__contentTop, & .wvLayoutLeft__contentMiddle, & .wvLayoutLeft__contentBottom{ + width: 100%; + } + } +} + +// Layout-Left Flexbox containers for the content. +.wvLayoutLeft__content { + border-right: 1px solid #AAA; + + // Display flex mode so optional actions can be positionned at the bottom + // side. + flex: 1; + display: flex; + flex-direction: column; + + // Make it scrollable. + overflow-y: auto; + height: 100%; +} + +.wvLayoutLeft__contentTop { + // We have to set a static height since we use floating to make white space + // disapear in nested content. + // note: could be deactivate with the clearfix so we can have a dynamic height + // max-height: 6rem; + padding: 0rem 1rem 0rem 1rem; + + // Enforce width even if there is a scrollbar on win/IE11 (-1 px for + // border). + width: $asideWidth - 0.1rem; + + &:after{ + content: ""; + display:block; + height:0; + width:0; + clear:both; + } +} + +.wvLayoutLeft__contentMiddle { + // Let the middle zone take as much space as required. + flex: 1 0 auto; + + // Enforce width even if there is a scrollbar on win/IE11 (-1 px for + // border). + width: $asideWidth - 0.1rem; + +} + +.wvLayoutLeft__contentBottom { + // Enforce width even if there is a scrollbar on win/IE11 (-1 px for + // border). + width: $asideWidth - 0.1rem; +} +.wvLayout__leftBottom.wvLayout__leftBottom--enabled { + border-top: 1px solid hsla(0, 0%, 100%, 0.2); + margin-top: 1rem; + padding: 1rem; + + // Prevent from taking more space than intended. + // flex-grow: 0; +} + +.wvLayoutLeft__actions, +%wvLayoutLeft__actions{ + display:block; + position:absolute; + right:1px; // border + top: 50%; + transform: translateY(-50%); + width:25px; +} +.wvLayoutLeft__actions--outside{ + @extend.wvLayoutLeft__actions; + right:-25px; // width + border +} +.wvLayoutLeft__action{ + background-color:$primary; + opacity: 0.5; + color:white; + transition: none; + &:hover, &:focus{ + opacity: 1; + } +} + + +/* layout: right section */ + +// Adapt right aside based on state (opened/closed). +.wvLayout__right { + display:block; + position:absolute; + z-index:2; + background-color:black; + width: $asideRightMinifyWidth; + + // Position the left side below the top zone if it is shown + &.wvLayout__right--toppadding { + top: $topHeight; + } + &:not(.wvLayout__right--toppadding) { + top: 0; + } + + // Position the right section over the bottom one if the latter is shown + &.wvLayout__right--bottompadding { + @media screen and (max-device-width: 374px) { + bottom: $bottomHeightSmall; + } + @media screen and (min-device-width: 375px) { + bottom: $bottomHeightLarge; + } + } + &:not(.wvLayout__right--bottompadding) { + bottom: 0; + } + + // Position the right side on the right + right: 0; + + // When layout right is shown, nothing special happens (default state) + &:not(.wvLayout__right--closed) { + } + + // When layout right is closed, move it aside + &.wvLayout__right--closed { + transform: translateX(+ $asideRightMinifyWidth); // Move all out of the screen + } + + // Set childrens to full height (so border-left appears at 100% height) + & > wv-layout-right, + & > wv-layout-right > .wvViewer__asideRight + { + display: block; + height: 100%; + width: 100%; + } +} + +.wvAsideRight__content { + height: 100%; + float: left; + + border-left: 1px solid #AAA; + + padding: 0 $asideRightPadding/2; + width: $asideWidth; +} + +.wvAsideRight__actions, +%wvAsideRight__actions{ + display:block; + position:absolute; + left:1px; // border + top: 50%; + transform: translateY(-50%); + width:25px; + + // Compensate aside z-index to let user click on button when another button + // is behind the actions. + z-index: 3; +} +.wvAsideRight__actions--outside{ + @extend.wvAsideRight__actions; + left:-25px; // width + border +} +.wvAsideRight__action{ + background-color:$primary; + opacity: 0.5; + color:white; + transition: none; + &:hover, &:focus{ + opacity: 1; + } +} +.wvAsideRight__fixOpenFullyTooltip + .tooltip { // Fix the "open fully" bad tooltip placement of the asideRight + left: -6.633em !important; + top: 1px !important; +} + + +/* layout: bottom section */ + +// Set bottom section size & position +.wvLayout__bottom { + position: absolute; + + // Display the bottom bar in the bottom side + @media screen and (max-device-width: 374px) { + height: $bottomHeightSmall; + } + @media screen and (min-device-width: 375px) { + height: $bottomHeightLarge; + } + + left: 0; + bottom: 0; + right: 0; + + // Set grey background color (as it is only used to display notices) + background-color: hsl(0, 0%, 10%); +} + + +/* layout: main section */ + +// Set main section size & position +.wvLayout__main { + position: absolute; + + // Align content (such as toolbar) + text-align: center; + + // Position splitpane considering the toolbar when toolbar is present. + & .wvLayout__splitpane--toolbarAtTop { + display: block; + height: calc(100% - #{$toolbarHeight}); + width: 100%; + + position: relative; + top: $toolbarHeight; + } + & .wvLayout__splitpane--toolbarAtRight { + display: block; + height: 100%; + width: calc(100% - #{$toolbarHeight}); + } + + & .wvLayout__splitpane--bigToolbarAtTop { + display: block; + height: calc(100% - 68px); + width: 100%; + + position: relative; + top: 68px; + } + & .wvLayout__splitpane--bigToolbarAtRight { + display: block; + height: 100%; + width: calc(100% - 68px); + } + + // Position the main section below the top zone if the latter is shown + &.wvLayout__main--toppadding { + top: $topHeight; + } + &:not(.wvLayout__main--toppadding) { + top: 0; + } + + // Position the main section over the bottom one if the latter is shown + &.wvLayout__main--bottompadding { + bottom:440px; + @media screen and (max-device-width: 374px) { + bottom: $bottomHeightSmall; + } + @media screen and (min-device-width: 375px) { + bottom: $bottomHeightLarge; + } + } + &:not(.wvLayout__main--bottompadding) { + bottom: 0; + } + + // Make the main content fill the screen by default + // Depending on the browser, the left and right attributes are more + // optimized than padding's ones. The reason is that they based upon + // absolute positioning. The require no contextual positioning calculation + // and are less performance intensive, especially concidering transition + // animation. + right: 0; + left: 0; + + // transition: 0.6s left ease, 0.6s right ease; + + // Adapt main section's size based on aside left's state (opened/closed) + // 1. When aside left is not hidden , move the main section 300 pixel to + // the right + &.wvLayout__main--leftpadding { + left: $asideWidth; + } + // 2. When aside left is hidden, let the main take 100% of the place + &:not(.wvLayout__main--leftpadding, .wvLayout__main--smallleftpadding) { + left: 0px; + } + &.wvLayout__main--smallleftpadding { + left: $asideMinifyWidth; + } + + // Adapt main section's size based on aside right's state (opened/closed) + // 1. When aside right is not hidden , move the main section 84 pixel to + // the left + &.wvLayout__main--rightpadding { + right: $asideRightMinifyWidth; + } + // 2. When aside right is hidden, let the main take 100% of the place + &:not(.wvLayout__main--rightpadding) { + right: 0px; + } +} + +/* global */ +.popover { + // Set back black as default popover text color + color: black; +} + +.wvViewer__editor--full{ + position:absolute; + top:0; + right:0; + z-index:10; + opacity:0; + transform: translateX(100%); + width:100%; + height:100%; + background-color:white; + color:$text-color; + &.opened{ + opacity:1; + transform: translateX(0); + } +} + +.wvViewer__topBar{ + width:100%; + // margin-top: 0.5rem; + + // Allow user to scroll through the toolbar if screen is too small. Note we + // can't use z-index properly to show buttons on top of the viewer, as any + // popover will appear behind them (even with higher z-index) due to an + // overflow property hidden somewhere. + overflow-y: auto; + white-space: nowrap; + max-width: 100%; +} +.wvViewer__buttonGroup{ + display:inline-block; +} +.wvViewer__buttonGroup--asideWidth{ + width: $asideWidth; + padding-right: 1rem; +} +.wvViewer__buttonGroup--contentWidth{ + width: calc(100% - #{$asideWidth}); + padding-left: 1rem; + max-height: 4.2rem; // Make sure mobile keeps the menubar below a certain size +} +.wvViewer__iframe{ + position:absolute; + left:0; + top:0; +} + +/* bottom bar */ +.wvViewer__bottomBar, +%wvViewer__bottomBar{ + position:absolute; + left:0; + bottom:0; + width:100%; + background-color:#111111; +} + +.wvViewer__bottomBar--expanded{ + @extend .wvViewer__bottomBar; + height: 80px; //total height of the last serieList cell (64 + 10(margin bottom previous item) + item padding bottom +1 border-width (top item) + //border-top: 1px solid rgba(255,255,255,0.1); + color:white; + + .wvViewer__timeline{ + width: calc(100% - 80px); + } + .wvTimeline__hotspots{ + bottom: -40px; + } +} + +.wvViewer__bottomBar--minimized{ + @extend .wvViewer__bottomBar; + color: white; + + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 2.5rem; + + .wvTimeline__hotspot{ + top: -40px; + opacity:0; + visibility:hidden; + z-index:-1; + // transition: all 0.3s ease 0.6s; //adding a delay when mouse leave + // transition-property: opacity, visibility, z-index; + } + &:hover .wvTimeline__hotspot{ + opacity:1; + visibility: visible; + z-index:5; + transition-delay: 0s; + } +} + +.wvViewer__timeline{ + height:24px; + //background-color:rgba(1,1,1,0.2); + line-height: 24px; + vertical-align: middle; + width:100%; +} + +.wvViewer__trademark{ + display:inline-block; + float:right; + width:80px; + height:80px; + float:right; + line-height: 80px; + vertical-align: middle; + text-align: center; +} +.wvTimeline__action--text{ + +} +.wvTimeline__input{ + border-radius: 3px; + &:focus{ + outline:none; + } + margin-top:2px; + border: 1px solid $border-color; +} + +.wvTimeline__actions{ + display:inline-block; + border-right: 1px solid $border-color; +} +.wvTimeline__wrapper{ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_notice.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,55 @@ +wv-notice { + display: block; + height: 100%; + width: 100%; +} + +.wvNotice { + // Set padding + padding: 0.5rem 0.25rem; + + // Fill parent element so text can be centered + height: 100%; +} + +.wvNotice__text { + // Center text + position: relative; + top: 50%; + transform: translateY(-50%); + text-align: center; + margin-left: 1rem; + + // Text style + font-weight: 400; + color: hsl(0, 0%, 70%); + + // Keep space for button + float: left; + width: calc(100% - 7rem); // 3.5 rem + 3 rem margin (incl. button margin + text margin) +} + +.wvNotice__closeButton { + // Position button on right + float: right; + margin-right: 0.5em; + + // Center button vertically + position: relative; + top: 50%; + transform: translateY(-50%); + + // Set button size + width: 3.5rem; + height: 2.5rem; // half the bottom zone height + + // Configure button icon + text-align: center; + font-size: 1em; + font-weight: 100; + line-height: 2.2rem; + + // Set button style + cursor: pointer; + border: 1px solid hsl(0, 0%, 27%); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_print.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,56 @@ +.wvPrintExclude{ + display:none; +} + +.wvPrintFullPage{ + width: 100% !important; + height: 100% !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; + display:block !important; +} + +.wvLayout__main{ + top: 0 !important; + right: 0 !important; + left: 0 !important; + bottom: 0 !important; +} + +.wvPrintViewer{ + width: 100%; + height:100%; + display: flex; + align-items: center; + justify-content: center; +} + +.wvPrintViewer canvas{ + max-width: 100% !important; + max-height: 100% !important; + margin:auto; +} + +.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft{ + &, & * { + background-color: black !important; + -webkit-print-color-adjust: exact !important; + color-adjust: exact !important; + color: orange !important; + } +} +.tooltip{ + display: none !important; +} +body{ + margin: 0; + padding: 0; + position: relative; + &, *{ + background-color: black !important; + -webkit-print-color-adjust: exact !important; + } + width: 8.5in; + height: 11in; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_selectionActionlist.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,9 @@ +.wvSelectionActionlist { + display: block; + + text-align: center; +} + +.wvSelectionActionlist__bottom { + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_serieslist.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,182 @@ +$gray: gray; +$blue: hsla(204, 70%, 53%, 0.7); +$red: rgba(206, 0, 0, 0.7); +$green: rgba(0, 160, 27, .7); +$yellow: rgba(220, 200 , 0, .9); +$violet: rgba(255, 31, 255, .7); + +$borderColor: rgba(255, 255, 255, 0.8); +$borderColorActive: rgba(255, 255, 255, 0.6); +$borderColorHighlighted: rgba(255, 255, 255, 1); +$pictureSize: 6.5rem; + +.wvSerieslist { + margin: 0; + padding: 0; + list-style: none; +} + +.wvSerieslist__seriesItem--selectable { + // Pointer cursor (for `ng-click`) + cursor: pointer !important; + + // Lighten up the icon on hover + &:hover { + color: white; + } +} + +.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa { // Make sure it has precedence over .fa class { + position: absolute; + + // Fill the li element + width: 100%; + height: 100%; + + // Fill the li element with the fontawesome icon + font-size: $pictureSize/2; + line-height: $pictureSize; + text-align: center; +} + +.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa { // Make sure it has precedence over .fa class + // Grey out (since no report is available) + color: #c3c3c3; + + // Diagonal line crossing report icon (to tell none are available) + // position: relative; + + &::after { // use after to not conflicts with font-awesome :before + position: absolute; + + left: 0; + top: 50%; + right: 0; + + transform: rotate(-45deg) scaleX(0.9); + + border-top: 5px solid; + border-color: inherit; + + content: ""; + } +} + +.wvSerieslist__picture{ + display: inline-block; + font-size: 14px; + width: $pictureSize; + height: $pictureSize; + position: relative; + + // Move picture behind the `toggle layout@ left` button. + z-index: -1; +} +.wvSerieslist__badge { + position: absolute; + bottom:5px; + right:5px; + font-size:10px; + line-height:15px; + width:15px; + height:15px; + border-radius: 100%; + background-color: $gray; + vertical-align: middle; + text-align: center; + font-weight: bold; +} +.wvSerieslist__information{ + font-size: 14px; + float: right; + padding-left: 1rem; + width: calc(100% - #{$pictureSize}); + height: $pictureSize; +} +.wvSerieslist__label{ + white-space: nowrap; + width:calc(100% - 10px); + overflow:hidden; + height:$pictureSize/2; + line-height:$pictureSize/2; + vertical-align: middle; +} +.wvSerieslist__timeline{ + //border-top: 0.1rem solid rgba(255,255,255,0.2); + height:$pictureSize/2; + line-height:$pictureSize/2; + vertical-align: middle; +} + +.wvSerieslist__seriesItem { + // anchor + position: relative; + + // unstyle list + padding-left: 0; + list-style: none; + font-size: 0; + + // mimic on hover border for draggable + border-right: 0.2rem solid transparent; + border-left: 0.2rem solid transparent; + border-top: 0.2rem solid transparent; + border-bottom: 0.2rem solid transparent; + border-corner-shape: notch; + + line-height: 0px; + margin: 0.1rem; + + &.active{ + border-color: $borderColorActive; + border-style: solid; + } + + &.highlighted{ + border-color: $borderColorHighlighted; + border-style: solid; + } + + &:hover, &:focus, &.focused{ + border-style: dashed; + border-color: $borderColor; + } +} + +.wvSerieslist__seriesItem--list { + display: block; +} +.wvSerieslist__seriesItem--grid { + display: inline-block; +} +.wvSerieslist__seriesItem--oneCol{ + text-align: center; +} + +.wvSerieslist__seriesItem--activated, +.wvSerieslist__videoItem--activated, +.wvSerieslist__pdfItem--activated { + border: 0.2rem solid hsla(204, 70%, 53%, 1) !important; +} + +// Color related modifiers +.wvSerieslist__badge--blue { + @extend .wvSerieslist__badge; + background-color: $blue; +} +.wvSerieslist__badge--red { + @extend .wvSerieslist__badge; + background-color: $red; +} +.wvSerieslist__badge--green { + @extend .wvSerieslist__badge; + background-color: $green; +} +.wvSerieslist__badge--yellow { + @extend .wvSerieslist__badge; + background-color: $yellow; +} +.wvSerieslist__badge--violet { + @extend .wvSerieslist__badge; + background-color: $violet; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_studyInformationBreadcrumb.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,39 @@ +.wvStudyInformationBreadcrumb { +} + +.wvStudyInformationBreadcrumb__patient { + display: inline-block; + background-color: rgba(255, 255, 255, 0.15); + padding: 0.2rem 1rem 0.3rem 1rem; + text-align: center; + font-size: 1em; + margin: 0.6rem; + font-weight: 400; + line-height: 2.3rem; + + // Prevent doubled margin with the next item + margin-right: 0; +} +.wvStudyInformationBreadcrumb__patientName { + +} +.wvStudyInformationBreadcrumb__patientBirthDate { + +} + +.wvStudyInformationBreadcrumb__study { + display: inline-block; + background-color: rgba(255, 255, 255, 0.15); + padding: 0.2rem 1rem 0.3rem 1rem; + text-align: center; + font-size: 1em; + margin: 0.6rem; + font-weight: 400; + line-height: 2.3rem; +} +.wvStudyInformationBreadcrumb__studyDescription { + +} +.wvStudyInformationBreadcrumb__studyDate { + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_studyIsland.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,90 @@ +$gray: gray; +$blue: hsla(204, 70%, 53%, 0.7); +$red: rgba(206, 0, 0, 0.7); +$green: rgba(0, 160, 27, .7); +$yellow: rgba(220, 200 , 0, .9); +$violet: rgba(255, 31, 255, .7); + + +%wvStudyIsland { + margin: 1rem 1rem 1rem 1rem; + border: 0.3rem solid $gray; +} + +%wvStudyIsland__header { + background-color: $gray; + padding: 0.5rem 0.5rem 0.8rem 0.5rem; + line-height: 1.35rem; + width: 100%; +} +.wvStudyIsland__actions { + float: right; + + // Compensate header padding (since the inner download study button + // actually has margin). + margin-top: -0.8rem; + margin-right: -0.8rem; +} + +.wvStudyIsland__actions--oneCol { + float: none; + text-align: center; +} + +.wvStudyIsland__main { + padding: 0.4rem; // 0.7rem - 0.3rem (2px transparent border + 1px margin) + color: hsl(0, 0%, 100%); + width: 100%; // let some space for the 4-columns based items +} + + +// Color modifiers +.wvStudyIsland--blue { + @extend %wvStudyIsland; + border-color: $blue; +} + +.wvStudyIsland__header--blue { + @extend %wvStudyIsland__header; + background-color: $blue; +} + +.wvStudyIsland--red { + @extend %wvStudyIsland; + border-color: $red; +} + +.wvStudyIsland__header--red { + @extend %wvStudyIsland__header; + background-color: $red; +} + +.wvStudyIsland--green { + @extend %wvStudyIsland; + border-color: $green; +} + +.wvStudyIsland__header--green { + @extend %wvStudyIsland__header; + background-color: $green; +} + +.wvStudyIsland--yellow { + @extend %wvStudyIsland; + border-color: $yellow; +} + +.wvStudyIsland__header--yellow { + @extend %wvStudyIsland__header; + background-color: $yellow; +} + +.wvStudyIsland--violet { + @extend %wvStudyIsland; + border-color: $violet; +} + +.wvStudyIsland__header--violet { + @extend %wvStudyIsland__header; + background-color: $violet; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_toolbar.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,178 @@ +.wvToolbar { + position: absolute; +} + +.wvToolbar--top { + top: 0; + height: $toolbarHeight; + + // Position the toolbar to the right (even if it's positioned + // horizontally). + right: 0; + text-align: right; + + // Allow user to scroll through the toolbar if screen is too small. Note we + // can't use z-index properly to show buttons on top of the viewer, as any + // popover will appear behind them (even with higher z-index) due to an + // overflow property hidden somewhere. + // overflow-y: auto; + white-space: nowrap; + max-width: 100%; +} + +.wvToolbar--right { + right: 0; + width: 42px; // != $toolbarHeight since we're in the reverse order. + + // Allow user to scroll through the toolbar if screen is too small. + // overflow-x: auto; + height: 100%; + z-index: 2; + &.wvToolbar--big{ + width: 68px; + } +} + +/* Splitpane Grid Configuration */ + +.wvToolbar__splitpaneConfigPopover { + // Prevent white space between buttons. + font-size: 0; +} + +.wvToolbar__splitpaneConfigNotice { + font-size: 1.25rem; + font-style: italic; + text-align: center; + + color: #333; +} + +input[type="radio"].wvToolbar__splitpaneConfigButtonInput { + // Hide the radio input, but make it fit the label, so we can rely on its + // html caracteristics without having to suffer from its design. + position: absolute; + width: 0; + height: 0; + left: 0; + top: 0; + bottom: 2px; + right: 0; + opacity: 0; +} + +/* Windowing Preset */ + +.wvToolbar__windowingPresetConfigPopover { + +} +.wvToolbar__windowingPresetConfigNotice { + font-size: 1.25rem; + font-style: italic; + text-align: center; + + color: #333; +} + +.wvToolbar__windowingPresetList { + list-style: none; + margin: 0; + padding: 0; + + font-size: 1.5rem; +} +.wvToolbar__windowingPresetListItem { + // Remove <a> default styles. Take care - this class may either be used + // with <a> or <button>. + &:hover { + text-decoration: none; + color: white; + } + + // Remove <button> default styles. + outline: none; + background-color: transparent; + border: none; + + // Set relative to position button absolutely + position: relative; + + // Style button + display: inline-block; + cursor: pointer; + font-variant: small-caps; + text-transform: lowercase; + text-align: center; + font-size: 1.3rem; + font-weight: 400; + line-height: 2.2rem; + color: hsl(0, 0%, 85%); + transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; + + // Position button + margin: 0; + min-width: 3rem; + padding: 0 10px; + line-height: 3.6rem; + + + + // Prevent multi line buttons. + max-height: 2.8rem; + max-width: 100%; + overflow: hidden; + + // Set margin + margin: 0.6rem; + margin-left: 0rem; + margin-right: 0rem; + & + & { + margin-left: 0.7rem; + } + + // Set button size + line-height: 2rem; + + // Align text + padding-top: 0.1rem; + padding-bottom: 0.5rem; + + // Style button + font-size: 1.4rem; + border: 1px solid hsl(0, 0%, 27%); + + // Set best looking font with small-caps. + font-family: Arial; + + // Change background on hover + background-color: hsl(0, 0%, 0%); + &:hover { + background-color: hsl(0, 0%, 10%); + } + + & > .glyphicon { // used with the same element as glyphicons + // Position button + position: relative; + display: inline-block; + top: 3px; + margin-right: 4px; + } + + // Text color + color: hsl(0, 0%, 10%); + border: 1px solid hsl(0, 0%, 73%); + + // Change background on hover + background-color: hsl(0, 0%, 100%); + &:hover { + color: hsl(0, 0%, 10%); + background-color: hsl(0, 0%, 90%); + } + + + width: 100%; + margin: 0; + margin-left: 0 !important; + border-top: none; + border-bottom: none; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_variable.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,14 @@ +$primary-lighten: #57aae1; +$primary: #3498db; +$dangerColor: #E63F24; + +$panel-header: #fafafa; +$border-color: #e7e7e7; +$lightGrey: #cccccc; +$text-color: #666666; + +$darkGrey: #333333; +$darkBlue: #203A6F; +$blueGrey: #303E4D; + +$toolbarHeight: 42px; \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/_video.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,9 @@ +.wvVideo { + // Align component vertically & horizontally + position: absolute; + top:50%; + left:0; + width: 100%; + height: auto; + transform: translateY(-50%); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/styles.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,32 @@ +// bower:scss +// endbower + +@import "webviewer.main.scss"; +@import "webviewer.components.scss"; + + +@media print { + @import "print"; +} + +.closePrintButton{ + display:none; +} + +body.print{ + @import "print"; + + @media screen { + .closePrintButton{ + display:block; + position: fixed; + top: 0; + right: 0; + padding: 10px; + font-size: 24px; + background-color: black; + color: white; + border: none; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/tb-group.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,36 @@ +.tbGroup{ + position:relative; +} +.tbGroup__buttons--base, +%tbGroup__buttons--base{ + z-index: 5; + background-color: black; + position: absolute; +} + +.tbGroup__buttons--bottom{ + @extend .tbGroup__buttons--base; + right:0; // let the element at it's initial position but align him in right (natural position is below the toggl button element) + display:block; +} +.tbGroup__buttons--left{ + @extend .tbGroup__buttons--base; + right:100%; + top:0; + display:block; +} +.tbGroup__icon{ + display:block; + position: absolute; + bottom:0; + left:0; + + width: 0; + height: 0; + border-style: solid; + border-width: 10px 0 0 10px; + border-color: transparent transparent transparent rgba(255,255,255,0.1); + &.active{ + border-color: transparent transparent transparent $primary; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/webviewer.components.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,59 @@ +/* wvp-ui stuffs */ +wv-webviewer { + display: block; + height: 100%; + overflow: hidden; +} + +@import "variable"; +@import "button"; +@import "exitButton"; +@import "studyIsland"; +@import "helpers"; +@import "notice"; +@import "layout"; +@import "serieslist"; +@import "toolbar"; +@import "video"; +@import "studyInformationBreadcrumb"; +@import "selectionActionlist"; + +/* wvb-ui stuffs */ +@import "wv-overlay.scss"; +@import "wv-pdf-viewer.scss"; +@import "wv-splitpane.scss"; +@import "wv-timeline.scss"; +@import "wv-timeline-controls.scss"; +@import "wv-loadingbar.scss"; +@import "wv-disclaimer"; +@import "tb-group"; + +wv-viewport { // make sure the element is sized when using with drag & drop + display: inline-block; + width: 100%; + height: 100%; + + > div { + position: relative; + width: 100%; + height: 100%; + } + + // We don't set 100% width/height to the canvas element, as it would stretch + // the pixels. Instead, we center it for more fluid transition when pane's + // width changes (at least the content is kept centered even if the js hasn't + // yet reacted to layout reflow). + > div > .wv-cornerstone-enabled-image { + width: 100%; + height: 100%; + text-align: center; + } +} + +.wv-draggable-clone { + width: 150px; + height: 150px; + background-color: rgba(255,255,255,0.25); + + // No need to set z-index (already done by jquery ui lib). +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/webviewer.main.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,90 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +.wv-html, .wv-body { + height: 100%; + width: 100%; + + margin: 0; + padding: 0; + + overflow: hidden; +} +.wv-body { + background-color: black; + color: white; + position: relative; + overflow: hidden; + + font-family: "Open Sans", Helvetica, Arial, sans-serif; + -webkit-tap-highlight-color: hsla(0, 0%, 0%, 0); + font-size: 13px; + font-weight: 400; + line-height: 1.49; + font-size-adjust: 100%; + + // Smooth text + -moz-osx-font-smoothing: grayscale !important; + font-smoothing: antialiased !important; + -webkit-font-smoothing: antialiased !important; +} + +.wvLoadingScreen { + width: 100%; + height: 100%; + background-color: black; + position: fixed; + top: 0; + left: 0; + z-index: 9999; + + display: flex; + align-items: center; + justify-content: center; +} + +.wvLoadingSpinner { + margin: 100px auto 0; + width: 70px; + text-align: center; +} + +.wvLoadingSpinner > div { + width: 18px; + height: 18px; + background-color: #FFF; + + border-radius: 100%; + display: inline-block; + -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; + animation: sk-bouncedelay 1.4s infinite ease-in-out both; +} + +.wvLoadingSpinner .bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} + +.wvLoadingSpinner .bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} + +@-webkit-keyframes sk-bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0) } + 40% { -webkit-transform: scale(1.0) } +} + +@keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); + } 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-disclaimer.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,7 @@ +.disclaimer{ + color: $dangerColor; + background-color: #303030; + padding:5px; + text-align: center; + font-weight: bold; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-loadingbar.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,33 @@ +.wv-loadingbar-image-bar { + cursor: pointer; +} +.wv-loadingbar-not-loaded { + fill: rgba(255, 255, 255, 0.1); +} +.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality { + transition: none; +} +.wv-loadingbar-not-loaded:hover { + fill: rgba(255, 255, 255, 0.2); +} +.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality { + fill:rgba(0, 255, 0, 0.7); +} +.wv-loadingbar-LOSSLESS-quality:hover, +.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active, +.wv-loadingbar-PIXELDATA-quality:hover, +.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active { + fill:rgba(0, 255, 0, 1); +} +.wv-loadingbar-LOW-quality { + fill:rgba(255, 0, 0, 0.7); +} +.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active { + fill:rgba(255, 0, 0, 1); +} +.wv-loadingbar-MEDIUM-quality { + fill:rgba(255, 95, 0, 0.7); +} +.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active { + fill:rgba(255, 95, 0, 1); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-overlay.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,135 @@ +$gray: gray; +$blue: hsla(204, 70%, 53%, 0.7); +$red: rgba(206, 0, 0, 0.7); +$green: rgba(0, 160, 27, .7); +$yellow: rgba(220, 200 , 0, .9); +$violet: rgba(255, 31, 255, .7); + +.wv-overlay { + // width&height is 0x0 to avoid capturing viewport events + color: orange; +} + +.wv-overlay-icon { + width: 64px; +} + +.wvOverlay__studyBadge { + position: absolute; + top: 0; + left: 0; + width: 1.5rem; + height: 1.5rem; + background-color: $gray; + z-index: 1; +} + +.wv-overlay-topleft { + position: absolute; + top: 0rem; + left: 0rem; + text-align: left; +} + +.wv-overlay-topright { + position: absolute; + top: 0rem; + right: 0rem; + text-align: right; +} + +.wv-overlay-bottomright { + position: absolute; + bottom: 2em; // save 2em for the timeline + right: 0rem; + text-align: right; +} + +.wv-overlay-bottomleft { + position: absolute; + bottom: 2em; // save 2em for the timeline + left: 0rem; + text-align: left; +} + +.wv-overlay-timeline-wrapper { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 1; // Make sure the representation of the selected image on the timeline appear on top of other overlay panels +} + +.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft { + padding: 2rem; + transition: color 500ms, background-color 500ms; + background-color: rgba(0, 0, 0, 0.66); +} + +.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover { + background-color: rgba(0, 0, 0, 0.9); +} + +.wvPaneOverlay { + position: absolute; + top: 50%; + width: 100%; + transform: translateY(-50%); + + font-weight: 100; + text-align: center; + color: white; + font-size: 2rem; +} + +.wv-overlay-scrollbar-loaded { + position: absolute; + bottom:0; + left:0; + height: 5px; + background-color: red; + will-change: right; + transform-origin: 0% 50%; +} + +.wv-overlay-scrollbar-loading { + position: absolute; + bottom:0; + left:0; + height: 5px; + background-color: #660000; + will-change: right; + transform-origin: 0% 50%; +} + +.wv-overlay-scrollbar-text { + position: absolute; + bottom: calc(1em + 5px); + left: 5px; + height: 1em; + color: red; + font-size: 0.8em; + font-family: helvetica; +} + +// Color related modifiers +.wvOverlay__studyBadge--blue { + @extend .wvOverlay__studyBadge; + background-color: $blue; +} +.wvOverlay__studyBadge--red { + @extend .wvOverlay__studyBadge; + background-color: $red; +} +.wvOverlay__studyBadge--green { + @extend .wvOverlay__studyBadge; + background-color: $green; +} +.wvOverlay__studyBadge--yellow { + @extend .wvOverlay__studyBadge; + background-color: $yellow; +} +.wvOverlay__studyBadge--violet { + @extend .wvOverlay__studyBadge; + background-color: $violet; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-pdf-viewer.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,1761 @@ +wv-pdf-viewer { + display: block; + width: 100%; + height: 100%; +} + +#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton { // We need high priority, !important keywords don't work + background-color: inherit; + color: hsl(0, 0%, 100%); + border: none; + + padding: 2px; + margin-left: 4px; + margin-right: 2px; + + &:hover { + color: black; + } +} + +.fa.fa-window-close.wv-pdf-viewer-closebuttonicon { // We need high priority + font-size: 2rem; + line-height: 28px; // pdf.js toolbar size (- closebutton margin) +} + +// The following code has been generated via: +// +// ```bash +// cd bower_components/pdf.js-viewer/ +// lessc --global-var='pdfjsImagePath="../images/pdf.js-viewer"' viewer.less viewer.css +// ``` + +.pdfjs .textLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + opacity: 0.2; +} +.pdfjs .textLayer > div { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -o-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; +} +.pdfjs .textLayer .highlight { + margin: -1px; + padding: 1px; + background-color: #b400aa; + border-radius: 4px; +} +.pdfjs .textLayer .highlight.begin { + border-radius: 4px 0 0 4px; +} +.pdfjs .textLayer .highlight.end { + border-radius: 0 4px 4px 0; +} +.pdfjs .textLayer .highlight.middle { + border-radius: 0; +} +.pdfjs .textLayer .highlight.selected { + background-color: #006400; +} +.pdfjs .textLayer ::selection { + background: #00f; +} +.pdfjs .textLayer ::-moz-selection { + background: #00f; +} +.pdfjs .pdfViewer .canvasWrapper { + overflow: hidden; +} +.pdfjs .pdfViewer .page { + direction: ltr; + width: 816px; + height: 1056px; + margin: 1px auto -8px; + position: relative; + overflow: visible; + border: 9px solid transparent; + background-clip: content-box; + border-image: url('../images/pdf.js-viewer/shadow.png') 9 9 repeat; + background-color: #fff; +} +body { + height: 100%; +} +.pdfjs .pdfViewer.removePageBorders .page { + margin: 0 auto 10px; + border: none; +} +.pdfjs .pdfViewer .page canvas { + margin: 0; + display: block; +} +.pdfjs .pdfViewer .page .loadingIcon { + position: absolute; + display: block; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: url('../images/pdf.js-viewer/loading-icon.gif') center no-repeat; +} +.pdfjs .pdfViewer .page .annotLink > a:hover { + opacity: .2; + background: #ff0; + box-shadow: 0 2px 10px #ff0; +} +.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page { + margin-bottom: 100%; + border: 0; +} +.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page { + margin-bottom: 100%; + border: 0; +} +.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page { + margin-bottom: 100%!important; + border: 0; +} +.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page { + margin-bottom: 100%; + border: 0; +} +.pdfjs .pdfViewer .page .annotText > img { + position: absolute; + cursor: pointer; +} +.pdfjs .pdfViewer .page .annotTextContentWrapper { + position: absolute; + width: 20em; +} +.pdfjs .pdfViewer .page .annotTextContent { + z-index: 200; + float: left; + max-width: 20em; + background-color: #FF9; + box-shadow: 0 2px 5px #333; + border-radius: 2px; + padding: .6em; + cursor: pointer; +} +.pdfjs .pdfViewer .page .annotTextContent > h1 { + font-size: 1em; + border-bottom: 1px solid #000; + padding-bottom: 0.2em; +} +.pdfjs .pdfViewer .page .annotTextContent > p { + padding-top: 0.2em; +} +.pdfjs .pdfViewer .page .annotLink > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.pdfjs .pdfViewer .page .annotLink > a { + background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA\ LAAAAAABAAEAAAIBRAA7") 0 0 repeat; +} +.pdfjs * { + padding: 0; + margin: 0; +} +html { + height: 100%; + font-size: 10px; +} +.pdfjs input, +.pdfjs button, +.pdfjs select { + font: message-box; + outline: none; +} +.pdfjs .hidden { + display: none !important; +} +.pdfjs [hidden] { + display: none !important; +} +.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen { + top: 0; + border-top: 2px solid transparent; + background-color: #000; + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + -webkit-user-select: none; +} +.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen { + top: 0; + border-top: 2px solid transparent; + background-color: #000; + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + -moz-user-select: none; +} +.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen { + top: 0!important; + border-top: 2px solid transparent; + width: 100%; + height: 100%; + overflow: hidden!important; + cursor: none; + -ms-user-select: none; +} +.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop { + background-color: #000; +} +.pdfjs #viewerContainer.pdfPresentationMode:fullscreen { + top: 0; + border-top: 2px solid transparent; + background-color: #000; + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} +.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) { + display: none; +} +.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) { + display: none; +} +.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) { + display: none !important; +} +.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) { + display: none; +} +.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div { + cursor: none; +} +.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div { + cursor: none; +} +.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div { + cursor: none; +} +.pdfjs .pdfPresentationMode:fullscreen .textLayer > div { + cursor: none; +} +.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *, +.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div { + cursor: default; +} +.pdfjs .outerCenter { + pointer-events: none; + position: relative; +} +html[dir='ltr'] .pdfjs .outerCenter { + float: right; + right: 50%; +} +html[dir='rtl'] .pdfjs .outerCenter { + float: left; + left: 50%; +} +.pdfjs .innerCenter { + pointer-events: auto; + position: relative; +} +html[dir='ltr'] .pdfjs .innerCenter { + float: right; + right: -50%; +} +html[dir='rtl'] .pdfjs .innerCenter { + float: left; + left: -50%; +} +.pdfjs #outerContainer { + width: 100%; + height: 100%; + position: relative; + background-color: #404040; + background-image: url('../images/pdf.js-viewer/texture.png'); +} +.pdfjs #sidebarContainer { + position: absolute; + top: 0; + bottom: 0; + width: 200px; + visibility: hidden; + -webkit-transition-duration: 200ms; + -webkit-transition-timing-function: ease; + transition-duration: 200ms; + transition-timing-function: ease; +} +html[dir='ltr'] .pdfjs #sidebarContainer { + -webkit-transition-property: left; + transition-property: left; + left: -200px; +} +html[dir='rtl'] .pdfjs #sidebarContainer { + -webkit-transition-property: right; + transition-property: right; + right: -200px; +} +.pdfjs #outerContainer.sidebarMoving > #sidebarContainer, +.pdfjs #outerContainer.sidebarOpen > #sidebarContainer { + visibility: visible; +} +html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { + left: 0; +} +html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { + right: 0; +} +.pdfjs #mainContainer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + min-width: 320px; + -webkit-transition-duration: 200ms; + -webkit-transition-timing-function: ease; + transition-duration: 200ms; + transition-timing-function: ease; +} +html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + -webkit-transition-property: left; + transition-property: left; + left: 200px; +} +html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + -webkit-transition-property: right; + transition-property: right; + right: 200px; +} +.pdfjs #sidebarContent { + top: 32px; + bottom: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + width: 200px; + background-color: rgba(0, 0, 0, 0.1); +} +html[dir='ltr'] .pdfjs #sidebarContent { + left: 0; + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25); +} +html[dir='rtl'] .pdfjs #sidebarContent { + right: 0; + box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25); +} +.pdfjs #viewerContainer { + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + top: 32px; + right: 0; + bottom: 0; + left: 0; + outline: none; +} +html[dir='ltr'] .pdfjs #viewerContainer { + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05); +} +html[dir='rtl'] .pdfjs #viewerContainer { + box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05); +} +.pdfjs .toolbar { + position: relative; + left: 0; + right: 0; + // z-index: 9999; + cursor: default; +} +.pdfjs #toolbarContainer { + width: 100%; +} +.pdfjs #toolbarSidebar { + width: 200px; + height: 32px; + background-color: #424242; + background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95)); +} +html[dir='ltr'] .pdfjs #toolbarSidebar { + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); +} +html[dir='rtl'] .pdfjs #toolbarSidebar { + box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); +} +.pdfjs #toolbarContainer, +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + position: relative; + height: 32px; + background-color: #474747; + background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); +} +html[dir='ltr'] .pdfjs #toolbarContainer, +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); +} +html[dir='rtl'] .pdfjs #toolbarContainer, +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); +} +.pdfjs #toolbarViewer { + height: 32px; +} +.pdfjs #loadingBar { + position: relative; + width: 100%; + height: 4px; + background-color: #333; + border-bottom: 1px solid #333; +} +.pdfjs #loadingBar .progress { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 100%; + background-color: #ddd; + overflow: hidden; + -webkit-transition: width 200ms; + transition: width 200ms; +} +@-webkit-keyframes progressIndeterminate { + 0% { + left: 0; + } + 50% { + left: 100%; + } + 100% { + left: 100%; + } +} +@keyframes progressIndeterminate { + 0% { + left: 0; + } + 50% { + left: 100%; + } + 100% { + left: 100%; + } +} +.pdfjs #loadingBar .progress.indeterminate { + background-color: #999; + -webkit-transition: none; + transition: none; +} +.pdfjs #loadingBar .indeterminate .glimmer { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 50px; + background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%); + background-size: 100% 100%; + background-repeat: no-repeat; + -webkit-animation: progressIndeterminate 2s linear infinite; + animation: progressIndeterminate 2s linear infinite; +} +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + top: 32px; + position: absolute; + z-index: 10000; + height: 32px; + min-width: 16px; + padding: 0 6px; + margin: 4px 2px; + color: #d9d9d9; + font-size: 12px; + line-height: 14px; + text-align: left; + cursor: default; +} +html[dir='ltr'] .pdfjs .findbar { + left: 68px; +} +html[dir='rtl'] .pdfjs .findbar { + right: 68px; +} +.pdfjs .findbar label { + -webkit-user-select: none; + -moz-user-select: none; +} +.pdfjs #findInput[data-status="pending"] { + background-image: url('../images/pdf.js-viewer/loading-small.png'); + background-repeat: no-repeat; + background-position: right; +} +html[dir='rtl'] .pdfjs #findInput[data-status="pending"] { + background-position: left; +} +.pdfjs .secondaryToolbar { + padding: 6px; + height: auto; + z-index: 30000; +} +html[dir='ltr'] .pdfjs .secondaryToolbar { + right: 4px; +} +html[dir='rtl'] .pdfjs .secondaryToolbar { + left: 4px; +} +.pdfjs #secondaryToolbarButtonContainer { + max-width: 200px; + max-height: 400px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + margin-bottom: -4px; +} +.pdfjs .doorHanger, +.pdfjs .doorHangerRight { + border: 1px solid rgba(0, 0, 0, 0.5); + border-radius: 2px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); +} +.pdfjs .doorHanger:after, +.pdfjs .doorHanger:before, +.pdfjs .doorHangerRight:after, +.pdfjs .doorHangerRight:before { + bottom: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} +.pdfjs .doorHanger:after, +.pdfjs .doorHangerRight:after { + border-bottom-color: rgba(82, 82, 82, 0.99); + border-width: 8px; +} +.pdfjs .doorHanger:before, +.pdfjs .doorHangerRight:before { + border-bottom-color: rgba(0, 0, 0, 0.5); + border-width: 9px; +} +html[dir='ltr'] .pdfjs .doorHanger:after, +html[dir='rtl'] .pdfjs .doorHangerRight:after { + left: 13px; + margin-left: -8px; +} +html[dir='ltr'] .pdfjs .doorHanger:before, +html[dir='rtl'] .pdfjs .doorHangerRight:before { + left: 13px; + margin-left: -9px; +} +html[dir='rtl'] .pdfjs .doorHanger:after, +html[dir='ltr'] .pdfjs .doorHangerRight:after { + right: 13px; + margin-right: -8px; +} +html[dir='rtl'] .pdfjs .doorHanger:before, +html[dir='ltr'] .pdfjs .doorHangerRight:before { + right: 13px; + margin-right: -9px; +} +.pdfjs #findMsg { + font-style: italic; + color: #A6B7D0; +} +.pdfjs #findInput.notFound { + background-color: #f66; +} +html[dir='ltr'] .pdfjs #toolbarViewerLeft { + margin-left: -1px; +} +html[dir='rtl'] .pdfjs #toolbarViewerRight { + margin-right: -1px; +} +html[dir='ltr'] .pdfjs #toolbarViewerLeft, +html[dir='rtl'] .pdfjs #toolbarViewerRight { + position: absolute; + top: 0; + left: 0; +} +html[dir='ltr'] .pdfjs #toolbarViewerRight, +html[dir='rtl'] .pdfjs #toolbarViewerLeft { + position: absolute; + top: 0; + right: 0; +} +html[dir='ltr'] .pdfjs #toolbarViewerLeft > *, +html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *, +html[dir='ltr'] .pdfjs #toolbarViewerRight > *, +html[dir='ltr'] .pdfjs .findbar > * { + position: relative; + float: left; +} +html[dir='rtl'] .pdfjs #toolbarViewerLeft > *, +html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *, +html[dir='rtl'] .pdfjs #toolbarViewerRight > *, +html[dir='rtl'] .pdfjs .findbar > * { + position: relative; + float: right; +} +html[dir='ltr'] .pdfjs .splitToolbarButton { + margin: 3px 2px 4px 0; + display: inline-block; +} +html[dir='rtl'] .pdfjs .splitToolbarButton { + margin: 3px 0 4px 2px; + display: inline-block; +} +html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton { + border-radius: 0; + float: left; +} +html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton { + border-radius: 0; + float: right; +} +.pdfjs .toolbarButton, +.pdfjs .secondaryToolbarButton, +.pdfjs .overlayButton { + border: 0 none; + background: none; + width: 32px; + height: 25px; +} +.pdfjs .toolbarButton > span { + display: inline-block; + width: 0; + height: 0; + overflow: hidden; +} +.pdfjs .toolbarButton[disabled], +.pdfjs .secondaryToolbarButton[disabled], +.pdfjs .overlayButton[disabled] { + opacity: 0.5; +} +.pdfjs .toolbarButton.group { + margin-right: 0; +} +.pdfjs .splitToolbarButton.toggled .toolbarButton { + margin: 0; +} +.pdfjs .splitToolbarButton:hover > .toolbarButton, +.pdfjs .splitToolbarButton:focus > .toolbarButton, +.pdfjs .splitToolbarButton.toggled > .toolbarButton, +.pdfjs .toolbarButton.textButton { + background-color: rgba(0, 0, 0, 0.12); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.35); + border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 150ms; + -webkit-transition-timing-function: ease; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} +.pdfjs .splitToolbarButton > .toolbarButton:hover, +.pdfjs .splitToolbarButton > .toolbarButton:focus, +.pdfjs .dropdownToolbarButton:hover, +.pdfjs .overlayButton:hover, +.pdfjs .toolbarButton.textButton:hover, +.pdfjs .toolbarButton.textButton:focus { + background-color: rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05); + z-index: 199; +} +.pdfjs .splitToolbarButton > .toolbarButton { + position: relative; +} +html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child, +html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child { + position: relative; + margin: 0; + margin-right: -1px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + border-right-color: transparent; +} +html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child, +html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child { + position: relative; + margin: 0; + margin-left: -1px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-left-color: transparent; +} +.pdfjs .splitToolbarButtonSeparator { + padding: 8px 0; + width: 1px; + background-color: rgba(0, 0, 0, 0.5); + z-index: 99; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); + display: inline-block; + margin: 5px 0; +} +html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator { + float: left; +} +html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator { + float: right; +} +.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator, +.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator { + padding: 12px 0; + margin: 1px 0; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03); + -webkit-transition-property: padding; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: ease; + transition-property: padding; + transition-duration: 10ms; + transition-timing-function: ease; +} +.pdfjs .toolbarButton, +.pdfjs .dropdownToolbarButton, +.pdfjs .secondaryToolbarButton, +.pdfjs .overlayButton { + min-width: 16px; + padding: 2px 6px 0; + border: 1px solid transparent; + border-radius: 2px; + color: rgba(255, 255, 255, 0.8); + font-size: 12px; + line-height: 14px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + cursor: default; + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 150ms; + -webkit-transition-timing-function: ease; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} +html[dir='ltr'] .pdfjs .toolbarButton, +html[dir='ltr'] .pdfjs .overlayButton, +html[dir='ltr'] .pdfjs .dropdownToolbarButton { + margin: 3px 2px 4px 0; +} +html[dir='rtl'] .pdfjs .toolbarButton, +html[dir='rtl'] .pdfjs .overlayButton, +html[dir='rtl'] .pdfjs .dropdownToolbarButton { + margin: 3px 0 4px 2px; +} +.pdfjs .toolbarButton:hover, +.pdfjs .toolbarButton:focus, +.pdfjs .dropdownToolbarButton, +.pdfjs .overlayButton, +.pdfjs .secondaryToolbarButton:hover, +.pdfjs .secondaryToolbarButton:focus { + background-color: rgba(0, 0, 0, 0.12); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.35); + border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); +} +.pdfjs .toolbarButton:hover:active, +.pdfjs .overlayButton:hover:active, +.pdfjs .dropdownToolbarButton:hover:active, +.pdfjs .secondaryToolbarButton:hover:active { + background-color: rgba(0, 0, 0, 0.2); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: linear; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} +.pdfjs .toolbarButton.toggled, +.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled, +.pdfjs .secondaryToolbarButton.toggled { + background-color: rgba(0, 0, 0, 0.3); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: linear; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} +.pdfjs .toolbarButton.toggled:hover:active, +.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active, +.pdfjs .secondaryToolbarButton.toggled:hover:active { + background-color: rgba(0, 0, 0, 0.4); + border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05); +} +.pdfjs .dropdownToolbarButton { + width: 120px; + max-width: 120px; + padding: 0; + overflow: hidden; + background: url('../images/pdf.js-viewer/toolbarButton-menuArrows.png') no-repeat; +} +html[dir='ltr'] .pdfjs .dropdownToolbarButton { + background-position: 95%; +} +html[dir='rtl'] .pdfjs .dropdownToolbarButton { + background-position: 5%; +} +.pdfjs .dropdownToolbarButton > select { + min-width: 140px; + font-size: 12px; + color: #f2f2f2; + margin: 0; + padding: 3px 2px 2px; + border: none; + background: rgba(0, 0, 0, 0); +} +.pdfjs .dropdownToolbarButton > select > option { + background: #3d3d3d; +} +.pdfjs #customScaleOption { + display: none; +} +.pdfjs #pageWidthOption { + border-bottom: 1px rgba(255, 255, 255, 0.5) solid; +} +html[dir='ltr'] .pdfjs .splitToolbarButton:first-child, +html[dir='ltr'] .pdfjs .toolbarButton:first-child, +html[dir='rtl'] .pdfjs .splitToolbarButton:last-child, +html[dir='rtl'] .pdfjs .toolbarButton:last-child { + margin-left: 4px; +} +html[dir='ltr'] .pdfjs .splitToolbarButton:last-child, +html[dir='ltr'] .pdfjs .toolbarButton:last-child, +html[dir='rtl'] .pdfjs .splitToolbarButton:first-child, +html[dir='rtl'] .pdfjs .toolbarButton:first-child { + margin-right: 4px; +} +.pdfjs .toolbarButtonSpacer { + width: 30px; + display: inline-block; + height: 1px; +} +.pdfjs .toolbarButtonFlexibleSpacer { + -webkit-box-flex: 1; + -moz-box-flex: 1; + min-width: 30px; +} +html[dir='ltr'] .pdfjs #findPrevious { + margin-left: 3px; +} +html[dir='ltr'] .pdfjs #findNext { + margin-right: 3px; +} +html[dir='rtl'] .pdfjs #findPrevious { + margin-right: 3px; +} +html[dir='rtl'] .pdfjs #findNext { + margin-left: 3px; +} +.pdfjs .toolbarButton::before, +.pdfjs .secondaryToolbarButton::before { + position: absolute; + display: inline-block; + top: 4px; + left: 7px; +} +html[dir="ltr"] .pdfjs .secondaryToolbarButton::before { + left: 4px; +} +html[dir="rtl"] .pdfjs .secondaryToolbarButton::before { + right: 4px; +} +html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle.png'); +} +html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png'); +} +html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png'); +} +html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png'); +} +html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { + content: url('../images/pdf.js-viewer/findbarButton-previous.png'); +} +html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { + content: url('../images/pdf.js-viewer/findbarButton-previous-rtl.png'); +} +html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { + content: url('../images/pdf.js-viewer/findbarButton-next.png'); +} +html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { + content: url('../images/pdf.js-viewer/findbarButton-next-rtl.png'); +} +html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageUp.png'); +} +html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png'); +} +html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageDown.png'); +} +html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png'); +} +.pdfjs .toolbarButton.zoomOut::before { + content: url('../images/pdf.js-viewer/toolbarButton-zoomOut.png'); +} +.pdfjs .toolbarButton.zoomIn::before { + content: url('../images/pdf.js-viewer/toolbarButton-zoomIn.png'); +} +.pdfjs .toolbarButton.presentationMode::before, +.pdfjs .secondaryToolbarButton.presentationMode::before { + content: url('../images/pdf.js-viewer/toolbarButton-presentationMode.png'); +} +.pdfjs .toolbarButton.print::before, +.pdfjs .secondaryToolbarButton.print::before { + content: url('../images/pdf.js-viewer/toolbarButton-print.png'); +} +.pdfjs .toolbarButton.openFile::before, +.pdfjs .secondaryToolbarButton.openFile::before { + content: url('../images/pdf.js-viewer/toolbarButton-openFile.png'); +} +.pdfjs .toolbarButton.download::before, +.pdfjs .secondaryToolbarButton.download::before { + content: url('../images/pdf.js-viewer/toolbarButton-download.png'); +} +.pdfjs .toolbarButton.bookmark, +.pdfjs .secondaryToolbarButton.bookmark { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + outline: none; + padding-top: 4px; + text-decoration: none; +} +.pdfjs .secondaryToolbarButton.bookmark { + padding-top: 5px; +} +.pdfjs .bookmark[href='#'] { + opacity: .5; + pointer-events: none; +} +.pdfjs .toolbarButton.bookmark::before, +.pdfjs .secondaryToolbarButton.bookmark::before { + content: url('../images/pdf.js-viewer/toolbarButton-bookmark.png'); +} +.pdfjs #viewThumbnail.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail.png'); +} +html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewOutline.png'); +} +html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png'); +} +.pdfjs #viewAttachments.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments.png'); +} +.pdfjs #viewFind.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-search.png'); +} +.pdfjs .secondaryToolbarButton { + position: relative; + margin: 0 0 4px; + padding: 3px 0 1px; + height: auto; + min-height: 25px; + width: auto; + min-width: 100%; + white-space: normal; +} +html[dir="ltr"] .pdfjs .secondaryToolbarButton { + padding-left: 24px; + text-align: left; +} +html[dir="rtl"] .pdfjs .secondaryToolbarButton { + padding-right: 24px; + text-align: right; +} +html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark { + padding-left: 27px; +} +html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark { + padding-right: 27px; +} +html[dir="ltr"] .pdfjs .secondaryToolbarButton > span { + padding-right: 4px; +} +html[dir="rtl"] .pdfjs .secondaryToolbarButton > span { + padding-left: 4px; +} +.pdfjs .secondaryToolbarButton.firstPage::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png'); +} +.pdfjs .secondaryToolbarButton.lastPage::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png'); +} +.pdfjs .secondaryToolbarButton.rotateCcw::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png'); +} +.pdfjs .secondaryToolbarButton.rotateCw::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png'); +} +.pdfjs .secondaryToolbarButton.handTool::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool.png'); +} +.pdfjs .secondaryToolbarButton.documentProperties::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png'); +} +.pdfjs .verticalToolbarSeparator { + display: block; + padding: 8px 0; + margin: 8px 4px; + width: 1px; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); +} +html[dir='ltr'] .pdfjs .verticalToolbarSeparator { + margin-left: 2px; +} +html[dir='rtl'] .pdfjs .verticalToolbarSeparator { + margin-right: 2px; +} +.pdfjs .horizontalToolbarSeparator { + display: block; + margin: 0 0 4px; + height: 1px; + width: 100%; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); +} +.pdfjs .toolbarField { + padding: 3px 6px; + margin: 4px 0; + border: 1px solid transparent; + border-radius: 2px; + background-color: rgba(255, 255, 255, 0.09); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.35); + border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + color: #f2f2f2; + font-size: 12px; + line-height: 14px; + outline-style: none; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} +.pdfjs .toolbarField[type=checkbox] { + display: inline-block; + margin: 8px 0; +} +.pdfjs .toolbarField.pageNumber { + -moz-appearance: textfield; + min-width: 16px; + text-align: right; + width: 40px; +} +.pdfjs .toolbarField.pageNumber.visiblePageIsLoading { + background-image: url('../images/pdf.js-viewer/loading-small.png'); + background-repeat: no-repeat; + background-position: 1px; +} +.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button, +.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} +.pdfjs .toolbarField:hover { + background-color: rgba(255, 255, 255, 0.11); + border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45); +} +.pdfjs .toolbarField:focus { + background-color: rgba(255, 255, 255, 0.15); + border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9); +} +.pdfjs .toolbarLabel { + min-width: 16px; + padding: 3px 6px 3px 2px; + margin: 4px 2px 4px 0; + border: 1px solid transparent; + border-radius: 2px; + color: #d9d9d9; + font-size: 12px; + line-height: 14px; + text-align: left; + -webkit-user-select: none; + -moz-user-select: none; + cursor: default; +} +.pdfjs #thumbnailView { + position: absolute; + width: 120px; + top: 0; + bottom: 0; + padding: 10px 40px 0; + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.pdfjs .thumbnail { + float: left; + margin-bottom: 5px; +} +.pdfjs #thumbnailView > a:last-of-type > .thumbnail { + margin-bottom: 10px; +} +.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) { + margin-bottom: 9px; +} +.pdfjs .thumbnail:not([data-loaded]) { + border: 1px dashed rgba(255, 255, 255, 0.5); + margin: -1px -1px 4px; +} +.pdfjs .thumbnailImage { + border: 1px solid transparent; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); + opacity: .8; + z-index: 99; + background-color: #fff; + background-clip: content-box; +} +.pdfjs .thumbnailSelectionRing { + border-radius: 2px; + padding: 7px; +} +.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage, +.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage { + opacity: 0.9; +} +.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing, +.pdfjs .thumbnail:hover > .thumbnailSelectionRing { + background-color: rgba(255, 255, 255, 0.15); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: rgba(255, 255, 255, 0.9); +} +.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5); + opacity: 1; +} +.pdfjs .thumbnail.selected > .thumbnailSelectionRing { + background-color: rgba(255, 255, 255, 0.3); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: #ffffff; +} +.pdfjs #outlineView, +.pdfjs #attachmentsView { + position: absolute; + width: 192px; + top: 0; + bottom: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; + -webkit-user-select: none; + -moz-user-select: none; +} +.pdfjs #outlineView { + padding: 4px 4px 0; +} +.pdfjs #attachmentsView { + padding: 3px 4px 0; +} +html[dir='ltr'] .pdfjs .outlineItem > .outlineItems { + margin-left: 20px; +} +html[dir='rtl'] .pdfjs .outlineItem > .outlineItems { + margin-right: 20px; +} +.pdfjs .outlineItem > a, +.pdfjs .attachmentsItem > button { + text-decoration: none; + display: inline-block; + min-width: 95%; + height: auto; + margin-bottom: 1px; + border-radius: 2px; + color: rgba(255, 255, 255, 0.8); + font-size: 13px; + line-height: 15px; + -moz-user-select: none; + white-space: normal; +} +.pdfjs .attachmentsItem > button { + border: 0 none; + background: none; + cursor: pointer; + width: 100%; +} +html[dir='ltr'] .pdfjs .outlineItem > a { + padding: 2px 0 5px 10px; +} +html[dir='ltr'] .pdfjs .attachmentsItem > button { + padding: 2px 0 3px 7px; + text-align: left; +} +html[dir='rtl'] .pdfjs .outlineItem > a { + padding: 2px 10px 5px 0; +} +html[dir='rtl'] .pdfjs .attachmentsItem > button { + padding: 2px 7px 3px 0; + text-align: right; +} +.pdfjs .outlineItem > a:hover, +.pdfjs .attachmentsItem > button:hover { + background-color: rgba(255, 255, 255, 0.02); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: rgba(255, 255, 255, 0.9); +} +.pdfjs .outlineItem.selected { + background-color: rgba(255, 255, 255, 0.08); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: #ffffff; +} +.pdfjs .noResults { + font-size: 12px; + color: rgba(255, 255, 255, 0.8); + font-style: italic; + cursor: default; +} +.pdfjs ::selection { + background: rgba(0, 0, 255, 0.3); +} +.pdfjs ::-moz-selection { + background: rgba(0, 0, 255, 0.3); +} +.pdfjs #errorWrapper { + background: none repeat scroll 0 0 #F55; + color: #fff; + left: 0; + position: absolute; + right: 0; + z-index: 1000; + padding: 3px; + font-size: 0.8em; +} +.pdfjs .loadingInProgress #errorWrapper { + top: 37px; +} +.pdfjs #errorMessageLeft { + float: left; +} +.pdfjs #errorMessageRight { + float: right; +} +.pdfjs #errorMoreInfo { + background-color: #FFF; + color: #000; + padding: 3px; + margin: 3px; + width: 98%; +} +.pdfjs .overlayButton { + width: auto; + margin: 3px 4px 2px!important; + padding: 2px 6px 3px; +} +.pdfjs #overlayContainer { + display: table; + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.2); + z-index: 40000; +} +.pdfjs #overlayContainer > * { + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.pdfjs #overlayContainer > .container { + display: table-cell; + vertical-align: middle; + text-align: center; +} +.pdfjs #overlayContainer > .container > .dialog { + display: inline-block; + padding: 15px; + border-spacing: 4px; + color: #d9d9d9; + font-size: 12px; + line-height: 14px; + background-color: #474747; + background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(0, 0, 0, 0.5); + border-radius: 4px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); +} +.pdfjs .dialog > .row { + display: table-row; +} +.pdfjs .dialog > .row > * { + display: table-cell; +} +.pdfjs .dialog .toolbarField { + margin: 5px 0; +} +.pdfjs .dialog .separator { + display: block; + margin: 4px 0; + height: 1px; + width: 100%; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); +} +.pdfjs .dialog .buttonRow { + text-align: center; + vertical-align: middle; +} +.pdfjs #passwordOverlay > .dialog { + text-align: center; +} +.pdfjs #passwordOverlay .toolbarField { + width: 200px; +} +.pdfjs #documentPropertiesOverlay > .dialog { + text-align: left; +} +.pdfjs #documentPropertiesOverlay .row > * { + min-width: 100px; +} +html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * { + text-align: left; +} +html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * { + text-align: right; +} +.pdfjs #documentPropertiesOverlay .row > span { + width: 125px; + word-wrap: break-word; +} +.pdfjs #documentPropertiesOverlay .row > p { + max-width: 225px; + word-wrap: break-word; +} +.pdfjs #documentPropertiesOverlay .buttonRow { + margin-top: 10px; +} +.pdfjs .clearBoth { + clear: both; +} +.pdfjs .fileInput { + background: #fff; + color: #000; + margin-top: 5px; + visibility: hidden; + position: fixed; + right: 0; + top: 0; +} +.pdfjs #PDFBug { + background: none repeat scroll 0 0 #fff; + border: 1px solid #666; + position: fixed; + top: 32px; + right: 0; + bottom: 0; + font-size: 10px; + padding: 0; + width: 300px; +} +.pdfjs #PDFBug .controls { + background: #EEE; + border-bottom: 1px solid #666; + padding: 3px; +} +.pdfjs #PDFBug .panels { + bottom: 0; + left: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + right: 0; + top: 27px; +} +.pdfjs #PDFBug button.active { + font-weight: 700; +} +.pdfjs .debuggerShowText { + background: none repeat scroll 0 0 #ff0; + color: blue; +} +.pdfjs .debuggerHideText:hover { + background: none repeat scroll 0 0 #ff0; +} +.pdfjs #PDFBug .stats { + font-family: courier; + font-size: 10px; + white-space: pre; +} +.pdfjs #PDFBug .stats .title { + font-weight: 700; +} +.pdfjs #PDFBug table { + font-size: 10px; +} +.pdfjs #viewer.textLayer-visible .textLayer > div, +.pdfjs #viewer.textLayer-hover .textLayer > div:hover { + background-color: #fff; + color: #000; +} +.pdfjs #viewer.textLayer-shadow .textLayer > div { + background-color: rgba(255, 255, 255, 0.6); + color: #000; +} +.pdfjs .grab-to-pan-grab { + cursor: url('../images/pdf.js-viewer/grab.cur'), move !important; + cursor: -webkit-grab !important; + cursor: -moz-grab !important; + cursor: grab !important; +} +.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) { + cursor: inherit !important; +} +.pdfjs .grab-to-pan-grab:active, +.pdfjs .grab-to-pan-grabbing { + cursor: url('../images/pdf.js-viewer/grabbing.cur'), move !important; + cursor: -webkit-grabbing !important; + cursor: -moz-grabbing !important; + cursor: grabbing!important; + position: fixed; + background: transparent; + display: block; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: 50000; +} +@page { + margin: 0; +} +.pdfjs #printContainer { + display: none; +} +@media screen and (min-resolution: 2dppx) { + .pdfjs .toolbarButton::before { + -webkit-transform: scale(0.5); + transform: scale(0.5); + top: -5px; + } + .pdfjs .secondaryToolbarButton::before { + -webkit-transform: scale(0.5); + transform: scale(0.5); + top: -4px; + } + html[dir='ltr'] .pdfjs .toolbarButton::before, + html[dir='rtl'] .pdfjs .toolbarButton::before { + left: -1px; + } + html[dir='ltr'] .pdfjs .secondaryToolbarButton::before { + left: -2px; + } + html[dir='rtl'] .pdfjs .secondaryToolbarButton::before { + left: 186px; + } + .pdfjs .toolbarField.pageNumber.visiblePageIsLoading, + .pdfjs #findInput[data-status="pending"] { + background-image: url('../images/pdf.js-viewer/loading-small@2x.png'); + background-size: 16px 17px; + } + .pdfjs .dropdownToolbarButton { + background: url('../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png') no-repeat; + background-size: 7px 16px; + } + html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png'); + } + html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png'); + } + html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png'); + } + html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png'); + } + html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { + content: url('../images/pdf.js-viewer/findbarButton-previous@2x.png'); + } + html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { + content: url('../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png'); + } + html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { + content: url('../images/pdf.js-viewer/findbarButton-next@2x.png'); + } + html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { + content: url('../images/pdf.js-viewer/findbarButton-next-rtl@2x.png'); + } + html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageUp@2x.png'); + } + html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png'); + } + html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageDown@2x.png'); + } + html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { + content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png'); + } + .pdfjs .toolbarButton.zoomIn::before { + content: url('../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png'); + } + .pdfjs .toolbarButton.zoomOut::before { + content: url('../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png'); + } + .pdfjs .toolbarButton.presentationMode::before, + .pdfjs .secondaryToolbarButton.presentationMode::before { + content: url('../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png'); + } + .pdfjs .toolbarButton.print::before, + .pdfjs .secondaryToolbarButton.print::before { + content: url('../images/pdf.js-viewer/toolbarButton-print@2x.png'); + } + .pdfjs .toolbarButton.openFile::before, + .pdfjs .secondaryToolbarButton.openFile::before { + content: url('../images/pdf.js-viewer/toolbarButton-openFile@2x.png'); + } + .pdfjs .toolbarButton.download::before, + .pdfjs .secondaryToolbarButton.download::before { + content: url('../images/pdf.js-viewer/toolbarButton-download@2x.png'); + } + .pdfjs .toolbarButton.bookmark::before, + .pdfjs .secondaryToolbarButton.bookmark::before { + content: url('../images/pdf.js-viewer/toolbarButton-bookmark@2x.png'); + } + .pdfjs #viewThumbnail.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png'); + } + html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png'); + } + html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png'); + } + .pdfjs #viewAttachments.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png'); + } + .pdfjs #viewFind.toolbarButton::before { + content: url('../images/pdf.js-viewer/toolbarButton-search@2x.png'); + } + .pdfjs .secondaryToolbarButton.firstPage::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png'); + } + .pdfjs .secondaryToolbarButton.lastPage::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png'); + } + .pdfjs .secondaryToolbarButton.rotateCcw::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png'); + } + .pdfjs .secondaryToolbarButton.rotateCw::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png'); + } + .pdfjs .secondaryToolbarButton.handTool::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png'); + } + .pdfjs .secondaryToolbarButton.documentProperties::before { + content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png'); + } +} +@media print { + body { + background: transparent none; + } + .pdfjs #sidebarContainer, + .pdfjs #secondaryToolbar, + .pdfjs .toolbar, + .pdfjs #loadingBox, + .pdfjs #errorWrapper, + .pdfjs .textLayer { + display: none; + } + .pdfjs #viewerContainer { + overflow: visible; + } + .pdfjs #mainContainer, + .pdfjs #viewerContainer, + .pdfjs .page, + .pdfjs .page canvas { + position: static; + padding: 0; + margin: 0; + } + .pdfjs .page { + float: left; + display: none; + border: none; + box-shadow: none; + background-clip: content-box; + background-color: #fff; + } + .pdfjs .page[data-loaded] { + display: block; + } + .pdfjs .fileInput { + display: none; + } + body[data-mozPrintCallback] .pdfjs #outerContainer { + display: none; + } + body[data-mozPrintCallback] .pdfjs #printContainer { + display: block; + } + .pdfjs #printContainer > div { + position: relative; + top: 0; + left: 0; + overflow: hidden; + } + .pdfjs #printContainer canvas { + display: block; + } +} +.pdfjs .visibleLargeView, +.pdfjs .visibleMediumView, +.pdfjs .visibleSmallView { + display: none; +} +@media all and (max-width: 960px) { + html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter { + float: left; + left: 205px; + } + html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter { + float: right; + right: 205px; + } +} +@media all and (max-width: 900px) { + .pdfjs .sidebarOpen .hiddenLargeView { + display: none; + } + .pdfjs .sidebarOpen .visibleLargeView { + display: inherit; + } +} +@media all and (max-width: 860px) { + .pdfjs .sidebarOpen .hiddenMediumView { + display: none; + } + .pdfjs .sidebarOpen .visibleMediumView { + display: inherit; + } +} +@media all and (max-width: 770px) { + .pdfjs #sidebarContainer { + top: 32px; + z-index: 100; + } + .pdfjs .loadingInProgress #sidebarContainer { + top: 37px; + } + .pdfjs #sidebarContent { + top: 32px; + background-color: rgba(0, 0, 0, 0.7); + } + html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + left: 0; + } + html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + right: 0; + } + html[dir='ltr'] .pdfjs .outerCenter { + float: left; + left: 205px; + } + html[dir='rtl'] .pdfjs .outerCenter { + float: right; + right: 205px; + } + .pdfjs #outerContainer .hiddenLargeView, + .pdfjs #outerContainer .hiddenMediumView { + display: inherit; + } + .pdfjs #outerContainer .visibleLargeView, + .pdfjs #outerContainer .visibleMediumView { + display: none; + } +} +@media all and (max-width: 700px) { + .pdfjs #outerContainer .hiddenLargeView { + display: none; + } + .pdfjs #outerContainer .visibleLargeView { + display: inherit; + } +} +@media all and (max-width: 660px) { + .pdfjs #outerContainer .hiddenMediumView { + display: none; + } + .pdfjs #outerContainer .visibleMediumView { + display: inherit; + } +} +@media all and (max-width: 600px) { + .pdfjs .hiddenSmallView { + display: none; + } + .pdfjs .visibleSmallView { + display: inherit; + } + html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter, + html[dir='ltr'] .pdfjs .outerCenter { + left: 156px; + } + html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter, + html[dir='rtl'] .pdfjs .outerCenter { + right: 156px; + } + .pdfjs .toolbarButtonSpacer { + width: 0; + } +} +@media all and (max-width: 510px) { + .pdfjs #scaleSelectContainer, + .pdfjs #pageNumberLabel { + display: none; + } +} +/* should be hidden differently */ +#fileInput.fileInput { + display: none; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-splitpane.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,80 @@ +$gray: gray; +$blue: hsla(204, 70%, 53%, 0.7); +$red: rgba(206, 0, 0, 0.7); +$green: rgba(0, 160, 27, .7); +$yellow: rgba(220, 200 , 0, .9); +$violet: rgba(255, 31, 255, .7); + +.wvSplitpane { + height: 100%; + padding: 7px 2px 2px 2px; + + // Anchor + position: relative; +} +.wvSplitpane__cell { + display: inline-block; + float: left; + height: 100%; + width: 100%; + + // Anchor + position: relative; +} +.wvSplitpane__cellBorder, +%wvSplitpane__cellBorder { + display: inline-block; + float: left; + height: calc(100% - 2px); + width: calc(100% - 2px); + + border: 2px dashed transparent; + + padding: 2px; + margin: 1px; +} +.wvSplitpane__cellBorder--selected { + @extend .wvSplitpane__cellBorder; + + // Add border + border: 2px solid $blue; +} + +// Color modifiers +.wvSplitpane__cellBorder--blue { + @extend .wvSplitpane__cellBorder; + border-color: $blue; +} + +.wvSplitpane__cellBorder--red { + @extend .wvSplitpane__cellBorder; + border-color: $red; +} + +.wvSplitpane__cellBorder--green { + @extend .wvSplitpane__cellBorder; + border-color: $green; +} + +.wvSplitpane__cellBorder--yellow { + @extend .wvSplitpane__cellBorder; + border-color: $yellow; +} + +.wvSplitpane__cellBorder--violet { + @extend .wvSplitpane__cellBorder; + border-color: $violet; +} + +// Make sure the pane keeps its size +wv-pane-policy { + display: block; + width: 100%; + height: 100%; + + > div[ng-transclude] { + display: block; + width: 100%; + height: 100%; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-timeline-controls.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,194 @@ +/* wv-timeline-controls directive */ +.wv-timeline-controls { + padding: 0.5em 0.5em 0.5em 0.5em; + line-height: 1em; + background-color: rgba(0, 0, 0, 0.66); + + text-align: center; + + transition: color 500ms, background-color 500ms; +} + +.wv-timeline-controls:hover { + background-color: rgba(0, 0, 0, 0.9); +} + +// Used to make sure buttons doesn't break the style +.wv-timeline-controls-vertical-sizing { + display: inline-block; + line-height: 1em; + font-size: 1em; +} + +.wv-timeline-controls-vflip { + // flip only the icon + &:before, &:after{ + transform: scaleX(-1); + display: inline-block; + } +} + +.wv-timeline-controls-button { + display: inline-block; + height: 1em; + width: 1em; + line-height: 1em; + font-size: 1em; + margin: 0; + + user-select: none; + cursor: pointer; +} + +.wv-timeline-controls-input { + height: 1em; + width: 3em; + padding: 0; + padding-bottom: 1px; + box-sizing: content-box; + + border: none; + border-bottom: 1px solid hsla(35, 100%, 75%, 0.24); + background-color: transparent; + + text-align: right; +} + +// Display play button on the right side +.wv-timeline-controls-play-button-wrapper { + float: right; +} + +/* wv-play-button directive */ +.wv-play-button { + display: inline-block; + position: relative; + line-height: 1em; + + // This is for the boxing box + height: 3em; + width: 6em; + padding-bottom: 1em; + padding-left: 0.25em; + padding-right: 0.25em; +} + +.wv-play-button:hover .wv-play-button-config-position-handler { + visibility: visible; +} + +// This is a 0x0 div to set the position +.wv-play-button-config-position-handler { + visibility: hidden; + position: absolute; + bottom: 3em; + left: 1em; + right: 0.5em; + // z-index: 2; +} + +// The layout of play configuration +.wv-play-button-config { + position: absolute; + bottom: 0; + left: -6em; + width: 12em; + padding: 1em; + background-color: hsla(0,1,0, 0.5); +} + +/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */ + +.wv-play-button-config-framerate-wrapper { + display: inline-block; + margin: 0.25em 0 0.5em 0; +} +input[type="range"].wv-play-button-config-framerate { + /*removes default webkit styles*/ + -webkit-appearance: none; + + /*fix for FF unable to apply focus style bug */ + border: 1px solid white; + + /*required for proper track sizing in FF*/ + width: 10em; +} +input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track { + width: 10em; + height: 5px; + background: #ddd; + border: none; + border-radius: 3px; +} +input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb { + -webkit-appearance: none; + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: goldenrod; + margin-top: -4px; +} +input[type="range"].wv-play-button-config-framerate:focus { + outline: none; +} +input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track { + background: #ccc; +} + +input[type="range"].wv-play-button-config-framerate::-moz-range-track { + width: 10em; + height: 5px; + background: #ddd; + border: none; + border-radius: 3px; +} +input[type="range"].wv-play-button-config-framerate::-moz-range-thumb { + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: goldenrod; +} + +/*hide the outline behind the border*/ +input[type="range"].wv-play-button-config-framerate:-moz-focusring{ + outline: 1px solid white; + outline-offset: -1px; +} + +input[type="range"].wv-play-button-config-framerate::-ms-track { + width: 10em; + height: 5px; + + /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */ + background: transparent; + + /*leave room for the larger thumb to overflow with a transparent border */ + border-color: transparent; + border-width: 6px 0; + + /*remove default tick marks*/ + color: transparent; +} +input[type="range"].wv-play-button-config-framerate::-ms-fill-lower { + background: #777; + border-radius: 10px; +} +input[type="range"].wv-play-button-config-framerate::-ms-fill-upper { + background: #ddd; + border-radius: 10px; +} +input[type="range"].wv-play-button-config-framerate::-ms-thumb { + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: goldenrod; +} +input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower { + background: #888; +} +input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper { + background: #ccc; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/Resources/Styles/wv-timeline.scss Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,34 @@ +.wv-timeline { + position: relative; + height: 2em; // save 2px to display the "upper part of the currently selected image on the timeline" + &.reduced{ + height: 5px; + .wv-timeline-loading-bar-wrapper{ + width: 100%; + height: 100%; + } + } +} + +.wv-timeline-controls-wrapper { + position: absolute; + left: 0; + bottom: 0; + width: 16em; + height: 100%; + color: white; +} + +.wv-timeline-loading-bar-wrapper { + position: absolute; + right: 0; + bottom: 0; + width: calc(100% - 16em); + height: calc(100% + 2px); + + svg{ + position:absolute; + left:0; + top:0; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebApplication/app.css Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,4752 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; } + +.wv-html, .wv-body { + height: 100%; + width: 100%; + margin: 0; + padding: 0; + overflow: hidden; } + +.wv-body { + background-color: black; + color: white; + position: relative; + overflow: hidden; + font-family: "Open Sans", Helvetica, Arial, sans-serif; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + font-size: 13px; + font-weight: 400; + line-height: 1.49; + font-size-adjust: 100%; + -moz-osx-font-smoothing: grayscale !important; + font-smoothing: antialiased !important; + -webkit-font-smoothing: antialiased !important; } + +.wvLoadingScreen { + width: 100%; + height: 100%; + background-color: black; + position: fixed; + top: 0; + left: 0; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; } + +.wvLoadingSpinner { + margin: 100px auto 0; + width: 70px; + text-align: center; } + +.wvLoadingSpinner > div { + width: 18px; + height: 18px; + background-color: #FFF; + border-radius: 100%; + display: inline-block; + -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; + animation: sk-bouncedelay 1.4s infinite ease-in-out both; } + +.wvLoadingSpinner .bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; } + +.wvLoadingSpinner .bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; } + +@-webkit-keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); } + 40% { + -webkit-transform: scale(1); } } + +@keyframes sk-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); } + 40% { + -webkit-transform: scale(1); + transform: scale(1); } } + +/* wvp-ui stuffs */ +wv-webviewer { + display: block; + height: 100%; + overflow: hidden; } + +.wvButton, .wvButton--rotate, .wvButton--vflip, .wvButton--underline, .fa.wvButton--underline, .wvButton--border, .wvButton--borderAndWhite { + outline: none; + background-color: transparent; + border: none; + border-radius: 0; + position: relative; + display: inline-block; + cursor: pointer; + font-variant: small-caps; + text-transform: lowercase; + text-align: center; + font-size: 1.3rem; + font-weight: 400; + color: #d9d9d9; + transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; + margin: 0; + min-width: 3rem; + padding: 0 10px; + line-height: 3.6rem; } + .wvButton:hover, .wvButton--rotate:hover, .wvButton--vflip:hover, .wvButton--underline:hover, .wvButton--border:hover, .wvButton--borderAndWhite:hover { + text-decoration: none; + color: white; } + .wvButton.wvLargeButton, .wvLargeButton.wvButton--rotate, .wvLargeButton.wvButton--vflip, .wvLargeButton.wvButton--underline, .wvLargeButton.wvButton--border, .wvLargeButton.wvButton--borderAndWhite { + font-size: 2rem; + line-height: 6.2rem; + padding: 0 20px; } + +.wvButton--rotate:before, .wvButton--rotate:after { + transform: rotate(90deg); + display: inline-block; } + +.wvButton--vflip:before, .wvButton--vflip:after { + transform: scaleX(-1); + display: inline-block; } + +.wvButton--underline, .fa.wvButton--underline { + position: relative; + background-color: inherit; + text-decoration: none; + text-align: left; + font-size: 1.2rem; + width: 3.2rem; + vertical-align: middle; + color: white; + opacity: 0.75; + border: none; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); + top: 0px; } + .wvButton--underline.wvLargeButton, .fa.wvButton--underline.wvLargeButton { + font-size: 2rem; + width: 6.4rem; } + .wvButton--underline *, .fa.wvButton--underline * { + pointer-events: none; } + .wvButton--underline:hover, .wvButton--underline:active, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:active, .fa.wvButton--underline:focus { + outline: 0; } + .wvButton--underline:hover, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:focus { + border-color: white; + opacity: 1; } + .wvButton--underline:hover .wvButton__bottomTriangle, .wvButton--underline:focus .wvButton__bottomTriangle, .fa.wvButton--underline:hover .wvButton__bottomTriangle, .fa.wvButton--underline:focus .wvButton__bottomTriangle { + border-left-color: white; } + .wvButton--underline.active, .fa.wvButton--underline.active { + opacity: 1; + border-color: #3498db; } + .wvButton--underline::before, .fa.wvButton--underline::before { + position: relative; + top: -1px; } + .wvButton--underline.fa, .fa.wvButton--underline.fa { + top: 0px; + font-weight: 800; } + +.wvButton__bottomTriangle { + transition: 0.3s border ease, 0.3s opacity ease; + display: block; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 0; + border-style: solid; + border-width: 10px 0 0 10px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); } + .wvButton__bottomTriangle.active { + border-color: transparent transparent transparent #3498db !important; } + .wvButton__bottomTriangle.active.toggled { + border-left-color: #3498db !important; } + +.wvButton--border, .wvButton--borderAndWhite { + max-height: calc(2.8rem - 3px); + max-width: 100%; + overflow: hidden; + margin: 0.6rem; + margin-left: 0rem; + margin-right: 0rem; + line-height: 2rem; + padding-top: 0.1rem; + padding-bottom: 0.5rem; + font-size: 1.4rem; + border: 1px solid #454545; + font-family: Arial; + background-color: black; } + .wvButton--border + .wvButton--border, .wvButton--borderAndWhite + .wvButton--border, .wvButton--border + .wvButton--borderAndWhite, .wvButton--borderAndWhite + .wvButton--borderAndWhite { + margin-left: 0.7rem; } + .wvButton--border:hover, .wvButton--borderAndWhite:hover { + background-color: #1a1a1a; } + .wvButton--border > .glyphicon, .wvButton--borderAndWhite > .glyphicon { + position: relative; + display: inline-block; + top: 3px; + margin-right: 4px; } + +.wvButton--borderAndWhite { + color: #1a1a1a; + border: 1px solid #bababa; + background-color: white; } + .wvButton--borderAndWhite:hover { + color: #1a1a1a; + background-color: #e6e6e6; } + +.wvExitButton { + margin-left: 1rem; + margin-top: .25rem; + font-size: 1.25em; + color: white; + opacity: .66; + transition: .3s opacity ease; + background-color: inherit; + border: none; + text-decoration: none; + text-align: left; + padding: 0; + cursor: pointer; + font-family: inherit; + line-height: inherit; } + .wvExitButton:hover, .wvExitButton:focus { + opacity: 1; + outline: 0; } + +.wvExitButton__text { + position: relative; + top: -1px; } + +.wvStudyIsland--blue, .wvStudyIsland--red, .wvStudyIsland--green, .wvStudyIsland--yellow, .wvStudyIsland--violet { + margin: 1rem 1rem 1rem 1rem; + border: 0.3rem solid gray; } + +.wvStudyIsland__header--blue, .wvStudyIsland__header--red, .wvStudyIsland__header--green, .wvStudyIsland__header--yellow, .wvStudyIsland__header--violet { + background-color: gray; + padding: 0.5rem 0.5rem 0.8rem 0.5rem; + line-height: 1.35rem; + width: 100%; } + +.wvStudyIsland__actions { + float: right; + margin-top: -0.8rem; + margin-right: -0.8rem; } + +.wvStudyIsland__actions--oneCol { + float: none; + text-align: center; } + +.wvStudyIsland__main { + padding: 0.4rem; + color: white; + width: 100%; } + +.wvStudyIsland--blue { + border-color: rgba(51, 152, 219, 0.7); } + +.wvStudyIsland__header--blue { + background-color: rgba(51, 152, 219, 0.7); } + +.wvStudyIsland--red { + border-color: rgba(206, 0, 0, 0.7); } + +.wvStudyIsland__header--red { + background-color: rgba(206, 0, 0, 0.7); } + +.wvStudyIsland--green { + border-color: rgba(0, 160, 27, 0.7); } + +.wvStudyIsland__header--green { + background-color: rgba(0, 160, 27, 0.7); } + +.wvStudyIsland--yellow { + border-color: rgba(220, 200, 0, 0.9); } + +.wvStudyIsland__header--yellow { + background-color: rgba(220, 200, 0, 0.9); } + +.wvStudyIsland--violet { + border-color: rgba(255, 31, 255, 0.7); } + +.wvStudyIsland__header--violet { + background-color: rgba(255, 31, 255, 0.7); } + +/* + * Source code taken from private Osimis' frontend toolbox 3.2.1. + */ +/** + _overlay.scss + */ +.overlay__transparent { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; } + +/** _transition.scss **/ +.transition { + transition: 0.3s all ease; } + +.transition--long { + transition: 0.6s all ease; } + +/** _list.scss **/ +dd + dt { + clear: both; } + +.listDefinition { + width: 100%; + line-height: 1.3; } + +.listDefinition__term { + clear: both; + float: left; + text-align: right; + padding-right: 10px; + width: 50%; } + +.listDefinition__data { + text-align: left; + padding-left: 10px; + float: right; + width: 50%; } + +/** _animation.scss **/ +@keyframes blink__primary { + 0% { + color: #3498db; } + 100% { + color: #666666; } } + +.blink__primary { + animation: blink__primary 0.8s linear infinite; } + +[translate-cloak] { + transition: 0.3s all ease; + opacity: 1; } + [translate-cloak].translate-cloak { + opacity: 0; } + +/** _button.scss **/ +.button__unstyled, .button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close { + background-color: inherit; + border: none; + text-decoration: none; + text-align: left; + padding: 0; + cursor: pointer; } + .button__unstyled *, .button__base *, .button__state--active *, .button__state--inactive *, .button__iconed *, .button__switch--base *, .button__switch *, .button__switch--first *, .button__switch--last *, .button__lightgrey--hover *, .button__text-danger--hover *, .button__text-primary--hover *, .button__danger--hover *, .button__text *, .button__text--underlined *, .button__bordered *, .button__bordered--inverted *, .button__close * { + pointer-events: none; } + .button__unstyled:hover, .button__base:hover, .button__state--active:hover, .button__state--inactive:hover, .button__iconed:hover, .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__lightgrey--hover:hover, .button__text-danger--hover:hover, .button__text-primary--hover:hover, .button__danger--hover:hover, .button__text:hover, .button__text--underlined:hover, .button__bordered:hover, .button__bordered--inverted:hover, .button__close:hover, .button__unstyled:active, .button__base:active, .button__state--active:active, .button__state--inactive:active, .button__iconed:active, .button__switch--base:active, .button__switch:active, .button__switch--first:active, .button__switch--last:active, .button__lightgrey--hover:active, .button__text-danger--hover:active, .button__text-primary--hover:active, .button__danger--hover:active, .button__text:active, .button__text--underlined:active, .button__bordered:active, .button__bordered--inverted:active, .button__close:active, .button__unstyled:focus, .button__base:focus, .button__state--active:focus, .button__state--inactive:focus, .button__iconed:focus, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus, .button__lightgrey--hover:focus, .button__text-danger--hover:focus, .button__text-primary--hover:focus, .button__danger--hover:focus, .button__text:focus, .button__text--underlined:focus, .button__bordered:focus, .button__bordered--inverted:focus, .button__close:focus { + outline: 0; } + +.button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close { + transition: 0.3s all ease; } + +.button__state--active { + opacity: 1; } + .button__state--active:hover { + opacity: 0.9; + color: #3498db; } + +.button__state--inactive { + opacity: 0.333; } + .button__state--inactive:hover { + opacity: 0.4333; + color: #3498db; } + +.button__iconed { + opacity: 1; } + .button__iconed:hover, .button__iconed:focus, .button__iconed.active { + opacity: 0.75; + color: #3498db; } + +.button__switch--base, .button__switch, .button__switch--first, .button__switch--last { + padding: 5px 0px; + display: inline-block; + text-align: center; + border-top: 1px solid; + border-bottom: 1px solid; + border-color: #3498db; + background-color: white; + overflow: hidden; } + .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus { + background-color: #5faee3; + color: white; } + .button__switch--base.active, .active.button__switch, .active.button__switch--first, .active.button__switch--last { + background-color: #3498db; + color: white; } + +.button__switch--first { + border-left: 1px solid #3498db; + border-radius: 5px 0 0 5px; } + +.button__switch--last { + border-right: 1px solid #3498db; + border-radius: 0 5px 5px 0; } + +.button__lightgrey--hover:hover, .button__lightgrey--hover:focus, .button__lightgrey--hover.active { + background-color: #cccccc; } + +.button__text-danger--hover:hover, .button__text-danger--hover:focus, .button__text-danger--hover.active { + color: #E63F24; } + +.button__text-primary--hover:hover, .button__text-primary--hover:focus, .button__text-primary--hover.active { + color: #3498db; } + +.button__danger--hover:hover, .button__danger--hover:focus, .button__danger--hover.active { + background-color: #E63F24; + color: white; } + +.button__text { + opacity: 0.66; } + .button__text:hover, .button__text.active, .button__text:focus { + opacity: 1; } + +.button__text--underlined { + text-decoration: underline; } + .button__text--underlined:hover, .button__text--underlined.active, .button__text--underlined:focus { + text-decoration: none; } + +.button__bordered { + border-bottom: 2px solid #666666; } + .button__bordered:hover, .button__bordered:focus, .button__bordered.active { + border-color: #3498db; } + +.button__bordered--inverted { + border-bottom: 2px solid white; } + +.button__close { + position: absolute; + top: 0; + right: 0; + opacity: 0.6; + z-index: 10; } + .button__close:hover, .button__close:focus { + opacity: 1; } + +/** _block.scss **/ +.block { + display: block !important; } + +/** _boxsizing.scss **/ +.boxsizing__borderbox { + box-sizing: border-box; } + +.boxsizing__contentbox { + box-sizing: content-box; } + +/** _scrollable.scss **/ +.scrollable { + overflow-y: auto; } + +.scrollable--x { + overflow-x: auto; } + +.no-scroll { + overflow: hidden; } + +/** _float.scss **/ +.float__right { + float: right; } + +.float__left { + float: left; } + +/** _fonts.scss **/ +.font__bold { + font-weight: 600; } + +.font__normal, .listDefinition__data { + font-weight: 400; } + +.font__light, .listDefinition__term { + font-weight: 200; } + +.fontColor__primary { + color: #3498db; } + +.fontColor__lightGrey { + color: #cccccc; } + +.fontColor__normal { + color: #666666; } + +.fontColor__darker { + color: #333333; } + +.fontColor__white { + color: white; } + +/** _forms.scss **/ +.textarea__unstyled { + border: none; + outline: none; + background-color: transparent; + color: inherit; + resize: none; + padding: 0; + margin: 0; + width: 100%; } + +/** _position.scss **/ +.position__relative { + position: relative; } + +/** _margin.scss **/ +.margin__auto { + margin: auto; } + +/** _helpers.scss **/ +/*************HELPERS**************/ +/**********************************/ +/*** identical width and height ***/ +.wh__0 { + width: 0px !important; + height: 0px !important; } + +.lh__0 { + line-height: 0px !important; } + +.wh__5 { + width: 5px !important; + height: 5px !important; } + +.lh__5 { + line-height: 5px !important; } + +.wh__8 { + width: 8px !important; + height: 8px !important; } + +.lh__8 { + line-height: 8px !important; } + +.wh__10 { + width: 10px !important; + height: 10px !important; } + +.lh__10 { + line-height: 10px !important; } + +.wh__11 { + width: 11px !important; + height: 11px !important; } + +.lh__11 { + line-height: 11px !important; } + +.wh__12 { + width: 12px !important; + height: 12px !important; } + +.lh__12 { + line-height: 12px !important; } + +.wh__13 { + width: 13px !important; + height: 13px !important; } + +.lh__13 { + line-height: 13px !important; } + +.wh__14 { + width: 14px !important; + height: 14px !important; } + +.lh__14 { + line-height: 14px !important; } + +.wh__15 { + width: 15px !important; + height: 15px !important; } + +.lh__15 { + line-height: 15px !important; } + +.wh__16 { + width: 16px !important; + height: 16px !important; } + +.lh__16 { + line-height: 16px !important; } + +.wh__17 { + width: 17px !important; + height: 17px !important; } + +.lh__17 { + line-height: 17px !important; } + +.wh__18 { + width: 18px !important; + height: 18px !important; } + +.lh__18 { + line-height: 18px !important; } + +.wh__19 { + width: 19px !important; + height: 19px !important; } + +.lh__19 { + line-height: 19px !important; } + +.wh__20 { + width: 20px !important; + height: 20px !important; } + +.lh__20 { + line-height: 20px !important; } + +.wh__21 { + width: 21px !important; + height: 21px !important; } + +.lh__21 { + line-height: 21px !important; } + +.wh__22 { + width: 22px !important; + height: 22px !important; } + +.lh__22 { + line-height: 22px !important; } + +.wh__23 { + width: 23px !important; + height: 23px !important; } + +.lh__23 { + line-height: 23px !important; } + +.wh__24 { + width: 24px !important; + height: 24px !important; } + +.lh__24 { + line-height: 24px !important; } + +.wh__25 { + width: 25px !important; + height: 25px !important; } + +.lh__25 { + line-height: 25px !important; } + +.wh__26 { + width: 26px !important; + height: 26px !important; } + +.lh__26 { + line-height: 26px !important; } + +.wh__27 { + width: 27px !important; + height: 27px !important; } + +.lh__27 { + line-height: 27px !important; } + +.wh__28 { + width: 28px !important; + height: 28px !important; } + +.lh__28 { + line-height: 28px !important; } + +.wh__29 { + width: 29px !important; + height: 29px !important; } + +.lh__29 { + line-height: 29px !important; } + +.wh__30 { + width: 30px !important; + height: 30px !important; } + +.lh__30 { + line-height: 30px !important; } + +.wh__31 { + width: 31px !important; + height: 31px !important; } + +.lh__31 { + line-height: 31px !important; } + +.wh__32 { + width: 32px !important; + height: 32px !important; } + +.lh__32 { + line-height: 32px !important; } + +.wh__33 { + width: 33px !important; + height: 33px !important; } + +.lh__33 { + line-height: 33px !important; } + +.wh__34 { + width: 34px !important; + height: 34px !important; } + +.lh__34 { + line-height: 34px !important; } + +.wh__35 { + width: 35px !important; + height: 35px !important; } + +.lh__35 { + line-height: 35px !important; } + +.wh__36 { + width: 36px !important; + height: 36px !important; } + +.lh__36 { + line-height: 36px !important; } + +.wh__37 { + width: 37px !important; + height: 37px !important; } + +.lh__37 { + line-height: 37px !important; } + +.wh__38 { + width: 38px !important; + height: 38px !important; } + +.lh__38 { + line-height: 38px !important; } + +.wh__39 { + width: 39px !important; + height: 39px !important; } + +.lh__39 { + line-height: 39px !important; } + +.wh__40 { + width: 40px !important; + height: 40px !important; } + +.lh__40 { + line-height: 40px !important; } + +.wh__41 { + width: 41px !important; + height: 41px !important; } + +.lh__41 { + line-height: 41px !important; } + +.wh__42 { + width: 42px !important; + height: 42px !important; } + +.lh__42 { + line-height: 42px !important; } + +.wh__43 { + width: 43px !important; + height: 43px !important; } + +.lh__43 { + line-height: 43px !important; } + +.wh__44 { + width: 44px !important; + height: 44px !important; } + +.lh__44 { + line-height: 44px !important; } + +.wh__45 { + width: 45px !important; + height: 45px !important; } + +.lh__45 { + line-height: 45px !important; } + +.wh__46 { + width: 46px !important; + height: 46px !important; } + +.lh__46 { + line-height: 46px !important; } + +.wh__47 { + width: 47px !important; + height: 47px !important; } + +.lh__47 { + line-height: 47px !important; } + +.wh__48 { + width: 48px !important; + height: 48px !important; } + +.lh__48 { + line-height: 48px !important; } + +.wh__49 { + width: 49px !important; + height: 49px !important; } + +.lh__49 { + line-height: 49px !important; } + +.wh__50 { + width: 50px !important; + height: 50px !important; } + +.lh__50 { + line-height: 50px !important; } + +.wh__55 { + width: 55px !important; + height: 55px !important; } + +.lh__55 { + line-height: 55px !important; } + +.wh__60 { + width: 60px !important; + height: 60px !important; } + +.lh__60 { + line-height: 60px !important; } + +.wh__64 { + width: 64px !important; + height: 64px !important; } + +.lh__64 { + line-height: 64px !important; } + +.wh__65 { + width: 65px !important; + height: 65px !important; } + +.lh__65 { + line-height: 65px !important; } + +.wh__70 { + width: 70px !important; + height: 70px !important; } + +.lh__70 { + line-height: 70px !important; } + +.wh__72 { + width: 72px !important; + height: 72px !important; } + +.lh__72 { + line-height: 72px !important; } + +.wh__75 { + width: 75px !important; + height: 75px !important; } + +.lh__75 { + line-height: 75px !important; } + +.wh__80 { + width: 80px !important; + height: 80px !important; } + +.lh__80 { + line-height: 80px !important; } + +.wh__85 { + width: 85px !important; + height: 85px !important; } + +.lh__85 { + line-height: 85px !important; } + +.wh__90 { + width: 90px !important; + height: 90px !important; } + +.lh__90 { + line-height: 90px !important; } + +.wh__95 { + width: 95px !important; + height: 95px !important; } + +.lh__95 { + line-height: 95px !important; } + +.wh__96 { + width: 96px !important; + height: 96px !important; } + +.lh__96 { + line-height: 96px !important; } + +.wh__100 { + width: 100px !important; + height: 100px !important; } + +.lh__100 { + line-height: 100px !important; } + +.wh__110 { + width: 110px !important; + height: 110px !important; } + +.lh__110 { + line-height: 110px !important; } + +.wh__120 { + width: 120px !important; + height: 120px !important; } + +.lh__120 { + line-height: 120px !important; } + +.wh__130 { + width: 130px !important; + height: 130px !important; } + +.lh__130 { + line-height: 130px !important; } + +.wh__140 { + width: 140px !important; + height: 140px !important; } + +.lh__140 { + line-height: 140px !important; } + +.wh__150 { + width: 150px !important; + height: 150px !important; } + +.lh__150 { + line-height: 150px !important; } + +.wh__160 { + width: 160px !important; + height: 160px !important; } + +.lh__160 { + line-height: 160px !important; } + +.wh__170 { + width: 170px !important; + height: 170px !important; } + +.lh__170 { + line-height: 170px !important; } + +.wh__180 { + width: 180px !important; + height: 180px !important; } + +.lh__180 { + line-height: 180px !important; } + +.wh__190 { + width: 190px !important; + height: 190px !important; } + +.lh__190 { + line-height: 190px !important; } + +.wh__200 { + width: 200px !important; + height: 200px !important; } + +.lh__200 { + line-height: 200px !important; } + +.wh__210 { + width: 210px !important; + height: 210px !important; } + +.lh__210 { + line-height: 210px !important; } + +.wh__220 { + width: 220px !important; + height: 220px !important; } + +.lh__220 { + line-height: 220px !important; } + +.wh__230 { + width: 230px !important; + height: 230px !important; } + +.lh__230 { + line-height: 230px !important; } + +.wh__240 { + width: 240px !important; + height: 240px !important; } + +.lh__240 { + line-height: 240px !important; } + +.wh__250 { + width: 250px !important; + height: 250px !important; } + +.lh__250 { + line-height: 250px !important; } + +.wh__260 { + width: 260px !important; + height: 260px !important; } + +.lh__260 { + line-height: 260px !important; } + +.wh__270 { + width: 270px !important; + height: 270px !important; } + +.lh__270 { + line-height: 270px !important; } + +.wh__280 { + width: 280px !important; + height: 280px !important; } + +.lh__280 { + line-height: 280px !important; } + +.wh__290 { + width: 290px !important; + height: 290px !important; } + +.lh__290 { + line-height: 290px !important; } + +.wh__300 { + width: 300px !important; + height: 300px !important; } + +.lh__300 { + line-height: 300px !important; } + +.wh__310 { + width: 310px !important; + height: 310px !important; } + +.lh__310 { + line-height: 310px !important; } + +.wh__320 { + width: 320px !important; + height: 320px !important; } + +.lh__320 { + line-height: 320px !important; } + +.wh__330 { + width: 330px !important; + height: 330px !important; } + +.lh__330 { + line-height: 330px !important; } + +.wh__340 { + width: 340px !important; + height: 340px !important; } + +.lh__340 { + line-height: 340px !important; } + +.wh__350 { + width: 350px !important; + height: 350px !important; } + +.lh__350 { + line-height: 350px !important; } + +.wh__360 { + width: 360px !important; + height: 360px !important; } + +.lh__360 { + line-height: 360px !important; } + +.wh__370 { + width: 370px !important; + height: 370px !important; } + +.lh__370 { + line-height: 370px !important; } + +.wh__380 { + width: 380px !important; + height: 380px !important; } + +.lh__380 { + line-height: 380px !important; } + +.wh__390 { + width: 390px !important; + height: 390px !important; } + +.lh__390 { + line-height: 390px !important; } + +.wh__400 { + width: 400px !important; + height: 400px !important; } + +.lh__400 { + line-height: 400px !important; } + +.wh__410 { + width: 410px !important; + height: 410px !important; } + +.lh__410 { + line-height: 410px !important; } + +.wh__420 { + width: 420px !important; + height: 420px !important; } + +.lh__420 { + line-height: 420px !important; } + +.wh__430 { + width: 430px !important; + height: 430px !important; } + +.lh__430 { + line-height: 430px !important; } + +.wh__440 { + width: 440px !important; + height: 440px !important; } + +.lh__440 { + line-height: 440px !important; } + +.wh__450 { + width: 450px !important; + height: 450px !important; } + +.lh__450 { + line-height: 450px !important; } + +.wh__460 { + width: 460px !important; + height: 460px !important; } + +.lh__460 { + line-height: 460px !important; } + +.wh__470 { + width: 470px !important; + height: 470px !important; } + +.lh__470 { + line-height: 470px !important; } + +.wh__480 { + width: 480px !important; + height: 480px !important; } + +.lh__480 { + line-height: 480px !important; } + +.wh__490 { + width: 490px !important; + height: 490px !important; } + +.lh__490 { + line-height: 490px !important; } + +.wh__500 { + width: 500px !important; + height: 500px !important; } + +.lh__500 { + line-height: 500px !important; } + +.lh__1 { + line-height: 1 !important; } + +.no-wrap { + white-space: nowrap; } + +.ov-h { + overflow: hidden; } + +.va-m { + vertical-align: middle; } + +.bg-inherit { + background-color: inherit; } + +.bg-black { + background-color: black; } + +.v-center:before { + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + margin-top: -0.25em; + /* Adjusts for spacing */ } + +.fluid-height { + height: 100%; } + +.visibility__hidden { + visibility: hidden; } + +.pointerEvents__none { + pointer-events: none; } + +/* Padding Helpers */ +.pn { + padding: 0 !important; } + +.p1 { + padding: 1px !important; } + +.p2 { + padding: 2px !important; } + +.p3 { + padding: 3px !important; } + +.p4 { + padding: 4px !important; } + +.p5 { + padding: 5px !important; } + +.p6 { + padding: 6px !important; } + +.p7 { + padding: 7px !important; } + +.p8 { + padding: 8px !important; } + +.p10 { + padding: 10px !important; } + +.p12 { + padding: 12px !important; } + +.p15 { + padding: 15px !important; } + +.p20 { + padding: 20px !important; } + +.p25 { + padding: 25px !important; } + +.p30 { + padding: 30px !important; } + +.p35 { + padding: 35px !important; } + +.p40 { + padding: 40px !important; } + +.p50 { + padding: 50px !important; } + +.ptn { + padding-top: 0 !important; } + +.pt5 { + padding-top: 5px !important; } + +.pt10 { + padding-top: 10px !important; } + +.pt15 { + padding-top: 15px !important; } + +.pt20 { + padding-top: 20px !important; } + +.pt25 { + padding-top: 25px !important; } + +.pt30 { + padding-top: 30px !important; } + +.pt35 { + padding-top: 35px !important; } + +.pt40 { + padding-top: 40px !important; } + +.pt50 { + padding-top: 50px !important; } + +.prn { + padding-right: 0 !important; } + +.pr5 { + padding-right: 5px !important; } + +.pr10 { + padding-right: 10px !important; } + +.pr15 { + padding-right: 15px !important; } + +.pr20 { + padding-right: 20px !important; } + +.pr25 { + padding-right: 25px !important; } + +.pr30 { + padding-right: 30px !important; } + +.pr35 { + padding-right: 35px !important; } + +.pr40 { + padding-right: 40px !important; } + +.pr50 { + padding-right: 50px !important; } + +.pbn { + padding-bottom: 0 !important; } + +.pb5 { + padding-bottom: 5px !important; } + +.pb10 { + padding-bottom: 10px !important; } + +.pb15 { + padding-bottom: 15px !important; } + +.pb20 { + padding-bottom: 20px !important; } + +.pb25 { + padding-bottom: 25px !important; } + +.pb30 { + padding-bottom: 30px !important; } + +.pb35 { + padding-bottom: 35px !important; } + +.pb40 { + padding-bottom: 40px !important; } + +.pb50 { + padding-bottom: 50px !important; } + +.pln { + padding-left: 0 !important; } + +.pl5 { + padding-left: 5px !important; } + +.pl10 { + padding-left: 10px !important; } + +.pl15 { + padding-left: 15px !important; } + +.pl20 { + padding-left: 20px !important; } + +.pl25 { + padding-left: 25px !important; } + +.pl30 { + padding-left: 30px !important; } + +.pl35 { + padding-left: 35px !important; } + +.pl40 { + padding-left: 40px !important; } + +.pl50 { + padding-left: 50px !important; } + +/* Axis Padding (both top/bottom or left/right) */ +.pv5 { + padding-top: 5px !important; + padding-bottom: 5px !important; } + +.pv8 { + padding-top: 8px !important; + padding-bottom: 8px !important; } + +.pv10 { + padding-top: 10px !important; + padding-bottom: 10px !important; } + +.pv15 { + padding-top: 15px !important; + padding-bottom: 15px !important; } + +.pv20 { + padding-top: 20px !important; + padding-bottom: 20px !important; } + +.pv25 { + padding-top: 25px !important; + padding-bottom: 25px !important; } + +.pv30 { + padding-top: 30px !important; + padding-bottom: 30px !important; } + +.pv40 { + padding-top: 40px !important; + padding-bottom: 40px !important; } + +.pv50 { + padding-top: 50px !important; + padding-bottom: 50px !important; } + +.ph5 { + padding-left: 5px !important; + padding-right: 5px !important; } + +.ph8 { + padding-left: 8px !important; + padding-right: 8px !important; } + +.ph10 { + padding-left: 10px !important; + padding-right: 10px !important; } + +.ph15 { + padding-left: 15px !important; + padding-right: 15px !important; } + +.ph20 { + padding-left: 20px !important; + padding-right: 20px !important; } + +.ph25 { + padding-left: 25px !important; + padding-right: 25px !important; } + +.ph30 { + padding-left: 30px !important; + padding-right: 30px !important; } + +.ph40 { + padding-left: 40px !important; + padding-right: 40px !important; } + +.ph50 { + padding-left: 50px !important; + padding-right: 50px !important; } + +/* margin center helper */ +.mauto { + margin-left: auto; + margin-right: auto; } + +.mn { + margin: 0 !important; } + +.m1 { + margin: 1px !important; } + +.m2 { + margin: 2px !important; } + +.m3 { + margin: 3px !important; } + +.m4 { + margin: 4px !important; } + +.m5 { + margin: 5px !important; } + +.m8 { + margin: 8px !important; } + +.m10 { + margin: 10px !important; } + +.m15 { + margin: 15px !important; } + +.m20 { + margin: 20px !important; } + +.m25 { + margin: 25px !important; } + +.m30 { + margin: 30px !important; } + +.m35 { + margin: 35px !important; } + +.m40 { + margin: 40px !important; } + +.m50 { + margin: 50px !important; } + +.mtn { + margin-top: 0 !important; } + +.mt5 { + margin-top: 5px !important; } + +.mt10 { + margin-top: 10px !important; } + +.mt15 { + margin-top: 15px !important; } + +.mt20 { + margin-top: 20px !important; } + +.mt25 { + margin-top: 25px !important; } + +.mt30 { + margin-top: 30px !important; } + +.mt35 { + margin-top: 35px !important; } + +.mt40 { + margin-top: 40px !important; } + +.mt50 { + margin-top: 50px !important; } + +.mt70 { + margin-top: 70px !important; } + +.mrn { + margin-right: 0 !important; } + +.mr5 { + margin-right: 5px !important; } + +.mr10 { + margin-right: 10px !important; } + +.mr15 { + margin-right: 15px !important; } + +.mr20 { + margin-right: 20px !important; } + +.mr25 { + margin-right: 25px !important; } + +.mr30 { + margin-right: 30px !important; } + +.mr35 { + margin-right: 35px !important; } + +.mr40 { + margin-right: 40px !important; } + +.mr50 { + margin-right: 50px !important; } + +.mbn { + margin-bottom: 0 !important; } + +.mb5 { + margin-bottom: 5px !important; } + +.mb10 { + margin-bottom: 10px !important; } + +.mb15 { + margin-bottom: 15px !important; } + +.mb20 { + margin-bottom: 20px !important; } + +.mb25 { + margin-bottom: 25px !important; } + +.mb30 { + margin-bottom: 30px !important; } + +.mb35 { + margin-bottom: 35px !important; } + +.mb40 { + margin-bottom: 40px !important; } + +.mb50 { + margin-bottom: 50px !important; } + +.mb70 { + margin-bottom: 70px !important; } + +.mln { + margin-left: 0 !important; } + +.ml5 { + margin-left: 5px !important; } + +.ml10 { + margin-left: 10px !important; } + +.ml15 { + margin-left: 15px !important; } + +.ml20 { + margin-left: 20px !important; } + +.ml25 { + margin-left: 25px !important; } + +.ml30 { + margin-left: 30px !important; } + +.ml35 { + margin-left: 35px !important; } + +.ml40 { + margin-left: 40px !important; } + +.ml50 { + margin-left: 50px !important; } + +/* Axis Margins (both top/bottom or left/right) */ +.mv5 { + margin-top: 5px !important; + margin-bottom: 5px !important; } + +.mv10 { + margin-top: 10px !important; + margin-bottom: 10px !important; } + +.mv15 { + margin-top: 15px !important; + margin-bottom: 15px !important; } + +.mv20 { + margin-top: 20px !important; + margin-bottom: 20px !important; } + +.mv25 { + margin-top: 25px !important; + margin-bottom: 25px !important; } + +.mv30 { + margin-top: 30px !important; + margin-bottom: 30px !important; } + +.mv40 { + margin-top: 40px !important; + margin-bottom: 40px !important; } + +.mv50 { + margin-top: 50px !important; + margin-bottom: 50px !important; } + +.mv70 { + margin-top: 70px !important; + margin-bottom: 70px !important; } + +.mh5 { + margin-left: 5px !important; + margin-right: 5px !important; } + +.mh10 { + margin-left: 10px !important; + margin-right: 10px !important; } + +.mh15 { + margin-left: 15px !important; + margin-right: 15px !important; } + +.mh20 { + margin-left: 20px !important; + margin-right: 20px !important; } + +.mh25 { + margin-left: 25px !important; + margin-right: 25px !important; } + +.mh30 { + margin-left: 30px !important; + margin-right: 30px !important; } + +.mh40 { + margin-left: 40px !important; + margin-right: 40px !important; } + +.mh50 { + margin-left: 50px !important; + margin-right: 50px !important; } + +.mh70 { + margin-left: 70px !important; + margin-right: 70px !important; } + +/* Negative Margin Helpers */ +.mtn5 { + margin-top: -5px !important; } + +.mtn10 { + margin-top: -10px !important; } + +.mtn15 { + margin-top: -15px !important; } + +.mtn20 { + margin-top: -20px !important; } + +.mtn30 { + margin-top: -30px !important; } + +.mrn5 { + margin-right: -5px !important; } + +.mrn10 { + margin-right: -10px !important; } + +.mrn15 { + margin-right: -15px !important; } + +.mrn20 { + margin-right: -20px !important; } + +.mrn30 { + margin-right: -30px !important; } + +.mbn5 { + margin-bottom: -5px !important; } + +.mbn10 { + margin-bottom: -10px !important; } + +.mbn15 { + margin-bottom: -15px !important; } + +.mbn20 { + margin-bottom: -20px !important; } + +.mbn30 { + margin-bottom: -30px !important; } + +.mln5 { + margin-left: -5px !important; } + +.mln10 { + margin-left: -10px !important; } + +.mln15 { + margin-left: -15px !important; } + +.mln20 { + margin-left: -20px !important; } + +.mln30 { + margin-left: -30px !important; } + +/* Vertical Negative Margin "mv" + "n" + "x" */ +.mvn5 { + margin-top: -5px !important; + margin-bottom: -5px !important; } + +.mvn10 { + margin-top: -10px !important; + margin-bottom: -10px !important; } + +.mvn15 { + margin-top: -15px !important; + margin-bottom: -15px !important; } + +.mvn20 { + margin-top: -20px !important; + margin-bottom: -20px !important; } + +.mvn30 { + margin-top: -30px !important; + margin-bottom: -30px !important; } + +/* Horizontal Negative Margin "mh" + "n" + "x" */ +.mhn5 { + margin-left: -5px !important; + margin-right: -5px !important; } + +.mhn10 { + margin-left: -10px !important; + margin-right: -10px !important; } + +.mhn15 { + margin-left: -15px !important; + margin-right: -15px !important; } + +.mhn20 { + margin-left: -20px !important; + margin-right: -20px !important; } + +.mhn30 { + margin-left: -30px !important; + margin-right: -30px !important; } + +/* Vertical Align Helpers */ +.va-t { + vertical-align: top !important; } + +.va-m { + vertical-align: middle !important; } + +.va-b { + vertical-align: bottom !important; } + +.va-s { + vertical-align: super !important; } + +/* Text Helpers */ +.text-left { + text-align: left !important; } + +.text-right { + text-align: right !important; } + +.text-center { + text-align: center !important; } + +.text-justify { + text-align: justify !important; } + +.text-nowrap { + white-space: nowrap !important; } + +/* Inline Block Helper */ +.ib, +.inline-object { + display: inline-block !important; } + +.clear { + clear: both; } + +.wvWarning { + position: relative; + width: 320px; + min-height: 130px; + z-index: 999; + left: calc(50% - 160px); + border: #000 solid 1px; + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + border-radius: 7px; + color: #FF5722; + box-shadow: 0px 3px 23px #ff980078; + -webkit-animation-name: example; + /* Safari 4.0 - 8.0 */ + -webkit-animation-duration: 3s; + /* Safari 4.0 - 8.0 */ + -webkit-animation-fill-mode: both; + /* Safari 4.0 - 8.0 */ + animation-name: example; + animation-duration: 2s; + animation-fill-mode: both; + animation-timing-function: ease-out; } + +@-webkit-keyframes example { + from { + top: 0vh; + opacity: 0; + background: #868686; } + to { + top: 10vh; + opacity: 1; + background: #ffffff; } } + +@keyframes example { + from { + top: 0vh; + opacity: 0; + background: #868686; } + to { + top: 10vh; + opacity: 1; + background: #ffffff; } } + +.wvWarning-content { + position: relative; + width: 190px; + min-height: 88px; + max-height: 80vh; + margin: auto; } + +.wvWarning-icon { + font-size: 32px; } + +.wvWarning-text { + position: relative; } + +.wvWarning-button { + background-color: #f1ededcc; + color: #607D8B; + width: 50px; + font-weight: 600; + margin-top: 2px; + margin-right: 30px; } + +.wvScreenToSmallWarning { + position: fixed; + display: block; + top: 0; + left: 0; + background-color: white; + color: #333; + width: 100%; + height: 100%; + z-index: 1000; } + +.wvScreenToSmallWarning-content { + padding: 10px; + text-align: center; } + +/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ +@media only screen and (min-width: 550px) and (min-height: 280px) { + .wvScreenToSmallWarning { + display: none; } } + +/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ +@media only screen and (min-width: 280px) and (min-height: 550px) { + .wvScreenToSmallWarning { + display: none; } } + +wv-notice { + display: block; + height: 100%; + width: 100%; } + +.wvNotice { + padding: 0.5rem 0.25rem; + height: 100%; } + +.wvNotice__text { + position: relative; + top: 50%; + transform: translateY(-50%); + text-align: center; + margin-left: 1rem; + font-weight: 400; + color: #b3b3b3; + float: left; + width: calc(100% - 7rem); } + +.wvNotice__closeButton { + float: right; + margin-right: 0.5em; + position: relative; + top: 50%; + transform: translateY(-50%); + width: 3.5rem; + height: 2.5rem; + text-align: center; + font-size: 1em; + font-weight: 100; + line-height: 2.2rem; + cursor: pointer; + border: 1px solid #454545; } + +/* layout: left section */ +.wvLayoutLeft { + position: absolute; + z-index: 2; + background-color: black; + width: 32rem; + left: 0; } + .wvLayoutLeft.wvLayoutLeft--toppadding { + top: 42px; } + .wvLayoutLeft:not(.wvLayoutLeft--toppadding) { + top: 0; } + @media screen and (max-device-width: 374px) { + .wvLayoutLeft.wvLayoutLeft--bottompadding { + bottom: 7rem; } } + @media screen and (min-device-width: 375px) { + .wvLayoutLeft.wvLayoutLeft--bottompadding { + bottom: 5rem; } } + .wvLayoutLeft:not(.wvLayoutLeft--bottompadding) { + bottom: 0; } + .wvLayoutLeft.wvLayoutLeft--closed { + transform: translateX(-32rem); } + .wvLayoutLeft.wvLayoutLeft--closed.wvLayoutLeft--small { + transform: translateX(-12rem); } + .wvLayoutLeft.wvLayoutLeft--small { + width: 12rem; } + .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentTop, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentMiddle, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentBottom { + width: 100%; } + +.wvLayoutLeft__content { + border-right: 1px solid #AAA; + flex: 1; + display: flex; + flex-direction: column; + overflow-y: auto; + height: 100%; } + +.wvLayoutLeft__contentTop { + padding: 0rem 1rem 0rem 1rem; + width: 31.9rem; } + .wvLayoutLeft__contentTop:after { + content: ""; + display: block; + height: 0; + width: 0; + clear: both; } + +.wvLayoutLeft__contentMiddle { + flex: 1 0 auto; + width: 31.9rem; } + +.wvLayoutLeft__contentBottom { + width: 31.9rem; } + +.wvLayout__leftBottom.wvLayout__leftBottom--enabled { + border-top: 1px solid rgba(255, 255, 255, 0.2); + margin-top: 1rem; + padding: 1rem; } + +.wvLayoutLeft__actions, .wvLayoutLeft__actions--outside { + display: block; + position: absolute; + right: 1px; + top: 50%; + transform: translateY(-50%); + width: 25px; } + +.wvLayoutLeft__actions--outside { + right: -25px; } + +.wvLayoutLeft__action { + background-color: #3498db; + opacity: 0.5; + color: white; + transition: none; } + .wvLayoutLeft__action:hover, .wvLayoutLeft__action:focus { + opacity: 1; } + +/* layout: right section */ +.wvLayout__right { + display: block; + position: absolute; + z-index: 2; + background-color: black; + width: 85px; + right: 0; } + .wvLayout__right.wvLayout__right--toppadding { + top: 42px; } + .wvLayout__right:not(.wvLayout__right--toppadding) { + top: 0; } + @media screen and (max-device-width: 374px) { + .wvLayout__right.wvLayout__right--bottompadding { + bottom: 7rem; } } + @media screen and (min-device-width: 375px) { + .wvLayout__right.wvLayout__right--bottompadding { + bottom: 5rem; } } + .wvLayout__right:not(.wvLayout__right--bottompadding) { + bottom: 0; } + .wvLayout__right.wvLayout__right--closed { + transform: translateX(85px); } + .wvLayout__right > wv-layout-right, + .wvLayout__right > wv-layout-right > .wvViewer__asideRight { + display: block; + height: 100%; + width: 100%; } + +.wvAsideRight__content { + height: 100%; + float: left; + border-left: 1px solid #AAA; + padding: 0 5px; + width: 32rem; } + +.wvAsideRight__actions, .wvAsideRight__actions--outside { + display: block; + position: absolute; + left: 1px; + top: 50%; + transform: translateY(-50%); + width: 25px; + z-index: 3; } + +.wvAsideRight__actions--outside { + left: -25px; } + +.wvAsideRight__action { + background-color: #3498db; + opacity: 0.5; + color: white; + transition: none; } + .wvAsideRight__action:hover, .wvAsideRight__action:focus { + opacity: 1; } + +.wvAsideRight__fixOpenFullyTooltip + .tooltip { + left: -6.633em !important; + top: 1px !important; } + +/* layout: bottom section */ +.wvLayout__bottom { + position: absolute; + left: 0; + bottom: 0; + right: 0; + background-color: #1a1a1a; } + @media screen and (max-device-width: 374px) { + .wvLayout__bottom { + height: 7rem; } } + @media screen and (min-device-width: 375px) { + .wvLayout__bottom { + height: 5rem; } } + +/* layout: main section */ +.wvLayout__main { + position: absolute; + text-align: center; + right: 0; + left: 0; } + .wvLayout__main .wvLayout__splitpane--toolbarAtTop { + display: block; + height: calc(100% - 42px); + width: 100%; + position: relative; + top: 42px; } + .wvLayout__main .wvLayout__splitpane--toolbarAtRight { + display: block; + height: 100%; + width: calc(100% - 42px); } + .wvLayout__main .wvLayout__splitpane--bigToolbarAtTop { + display: block; + height: calc(100% - 68px); + width: 100%; + position: relative; + top: 68px; } + .wvLayout__main .wvLayout__splitpane--bigToolbarAtRight { + display: block; + height: 100%; + width: calc(100% - 68px); } + .wvLayout__main.wvLayout__main--toppadding { + top: 42px; } + .wvLayout__main:not(.wvLayout__main--toppadding) { + top: 0; } + .wvLayout__main.wvLayout__main--bottompadding { + bottom: 440px; } + @media screen and (max-device-width: 374px) { + .wvLayout__main.wvLayout__main--bottompadding { + bottom: 7rem; } } + @media screen and (min-device-width: 375px) { + .wvLayout__main.wvLayout__main--bottompadding { + bottom: 5rem; } } + .wvLayout__main:not(.wvLayout__main--bottompadding) { + bottom: 0; } + .wvLayout__main.wvLayout__main--leftpadding { + left: 32rem; } + .wvLayout__main { + left: 0px; } + .wvLayout__main.wvLayout__main--smallleftpadding { + left: 12rem; } + .wvLayout__main.wvLayout__main--rightpadding { + right: 85px; } + .wvLayout__main:not(.wvLayout__main--rightpadding) { + right: 0px; } + +/* global */ +.popover { + color: black; } + +.wvViewer__editor--full { + position: absolute; + top: 0; + right: 0; + z-index: 10; + opacity: 0; + transform: translateX(100%); + width: 100%; + height: 100%; + background-color: white; + color: #666666; } + .wvViewer__editor--full.opened { + opacity: 1; + transform: translateX(0); } + +.wvViewer__topBar { + width: 100%; + overflow-y: auto; + white-space: nowrap; + max-width: 100%; } + +.wvViewer__buttonGroup { + display: inline-block; } + +.wvViewer__buttonGroup--asideWidth { + width: 32rem; + padding-right: 1rem; } + +.wvViewer__buttonGroup--contentWidth { + width: calc(100% - 32rem); + padding-left: 1rem; + max-height: 4.2rem; } + +.wvViewer__iframe { + position: absolute; + left: 0; + top: 0; } + +/* bottom bar */ +.wvViewer__bottomBar, .wvViewer__bottomBar--expanded, .wvViewer__bottomBar--minimized { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + background-color: #111111; } + +.wvViewer__bottomBar--expanded { + height: 80px; + color: white; } + .wvViewer__bottomBar--expanded .wvViewer__timeline { + width: calc(100% - 80px); } + .wvViewer__bottomBar--expanded .wvTimeline__hotspots { + bottom: -40px; } + +.wvViewer__bottomBar--minimized { + color: white; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 2.5rem; } + .wvViewer__bottomBar--minimized .wvTimeline__hotspot { + top: -40px; + opacity: 0; + visibility: hidden; + z-index: -1; } + .wvViewer__bottomBar--minimized:hover .wvTimeline__hotspot { + opacity: 1; + visibility: visible; + z-index: 5; + transition-delay: 0s; } + +.wvViewer__timeline { + height: 24px; + line-height: 24px; + vertical-align: middle; + width: 100%; } + +.wvViewer__trademark { + display: inline-block; + float: right; + width: 80px; + height: 80px; + float: right; + line-height: 80px; + vertical-align: middle; + text-align: center; } + +.wvTimeline__input { + border-radius: 3px; + margin-top: 2px; + border: 1px solid #e7e7e7; } + .wvTimeline__input:focus { + outline: none; } + +.wvTimeline__actions { + display: inline-block; + border-right: 1px solid #e7e7e7; } + +.wvSerieslist { + margin: 0; + padding: 0; + list-style: none; } + +.wvSerieslist__seriesItem--selectable { + cursor: pointer !important; } + .wvSerieslist__seriesItem--selectable:hover { + color: white; } + +.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa { + position: absolute; + width: 100%; + height: 100%; + font-size: 3.25rem; + line-height: 6.5rem; + text-align: center; } + +.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa { + color: #c3c3c3; } + .wvSerieslist__placeholderIcon--strikeout::after, .wvSerieslist__placeholderIcon--strikeout.fa::after { + position: absolute; + left: 0; + top: 50%; + right: 0; + transform: rotate(-45deg) scaleX(0.9); + border-top: 5px solid; + border-color: inherit; + content: ""; } + +.wvSerieslist__picture { + display: inline-block; + font-size: 14px; + width: 6.5rem; + height: 6.5rem; + position: relative; + z-index: -1; } + +.wvSerieslist__badge, .wvSerieslist__badge--blue, .wvSerieslist__badge--red, .wvSerieslist__badge--green, .wvSerieslist__badge--yellow, .wvSerieslist__badge--violet { + position: absolute; + bottom: 5px; + right: 5px; + font-size: 10px; + line-height: 15px; + width: 15px; + height: 15px; + border-radius: 100%; + background-color: gray; + vertical-align: middle; + text-align: center; + font-weight: bold; } + +.wvSerieslist__information { + font-size: 14px; + float: right; + padding-left: 1rem; + width: calc(100% - 6.5rem); + height: 6.5rem; } + +.wvSerieslist__label { + white-space: nowrap; + width: calc(100% - 10px); + overflow: hidden; + height: 3.25rem; + line-height: 3.25rem; + vertical-align: middle; } + +.wvSerieslist__timeline { + height: 3.25rem; + line-height: 3.25rem; + vertical-align: middle; } + +.wvSerieslist__seriesItem { + position: relative; + padding-left: 0; + list-style: none; + font-size: 0; + border-right: 0.2rem solid transparent; + border-left: 0.2rem solid transparent; + border-top: 0.2rem solid transparent; + border-bottom: 0.2rem solid transparent; + border-corner-shape: notch; + line-height: 0px; + margin: 0.1rem; } + .wvSerieslist__seriesItem.active { + border-color: rgba(255, 255, 255, 0.6); + border-style: solid; } + .wvSerieslist__seriesItem.highlighted { + border-color: white; + border-style: solid; } + .wvSerieslist__seriesItem:hover, .wvSerieslist__seriesItem:focus, .wvSerieslist__seriesItem.focused { + border-style: dashed; + border-color: rgba(255, 255, 255, 0.8); } + +.wvSerieslist__seriesItem--list { + display: block; } + +.wvSerieslist__seriesItem--grid { + display: inline-block; } + +.wvSerieslist__seriesItem--oneCol { + text-align: center; } + +.wvSerieslist__seriesItem--activated, +.wvSerieslist__videoItem--activated, +.wvSerieslist__pdfItem--activated { + border: 0.2rem solid #3398db !important; } + +.wvSerieslist__badge--blue { + background-color: rgba(51, 152, 219, 0.7); } + +.wvSerieslist__badge--red { + background-color: rgba(206, 0, 0, 0.7); } + +.wvSerieslist__badge--green { + background-color: rgba(0, 160, 27, 0.7); } + +.wvSerieslist__badge--yellow { + background-color: rgba(220, 200, 0, 0.9); } + +.wvSerieslist__badge--violet { + background-color: rgba(255, 31, 255, 0.7); } + +.wvToolbar { + position: absolute; } + +.wvToolbar--top { + top: 0; + height: 42px; + right: 0; + text-align: right; + white-space: nowrap; + max-width: 100%; } + +.wvToolbar--right { + right: 0; + width: 42px; + height: 100%; + z-index: 2; } + .wvToolbar--right.wvToolbar--big { + width: 68px; } + +/* Splitpane Grid Configuration */ +.wvToolbar__splitpaneConfigPopover { + font-size: 0; } + +.wvToolbar__splitpaneConfigNotice { + font-size: 1.25rem; + font-style: italic; + text-align: center; + color: #333; } + +input[type="radio"].wvToolbar__splitpaneConfigButtonInput { + position: absolute; + width: 0; + height: 0; + left: 0; + top: 0; + bottom: 2px; + right: 0; + opacity: 0; } + +/* Windowing Preset */ +.wvToolbar__windowingPresetConfigNotice { + font-size: 1.25rem; + font-style: italic; + text-align: center; + color: #333; } + +.wvToolbar__windowingPresetList { + list-style: none; + margin: 0; + padding: 0; + font-size: 1.5rem; } + +.wvToolbar__windowingPresetListItem { + outline: none; + background-color: transparent; + border: none; + position: relative; + display: inline-block; + cursor: pointer; + font-variant: small-caps; + text-transform: lowercase; + text-align: center; + font-size: 1.3rem; + font-weight: 400; + line-height: 2.2rem; + color: #d9d9d9; + transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; + margin: 0; + min-width: 3rem; + padding: 0 10px; + line-height: 3.6rem; + max-height: 2.8rem; + max-width: 100%; + overflow: hidden; + margin: 0.6rem; + margin-left: 0rem; + margin-right: 0rem; + line-height: 2rem; + padding-top: 0.1rem; + padding-bottom: 0.5rem; + font-size: 1.4rem; + border: 1px solid #454545; + font-family: Arial; + background-color: black; + color: #1a1a1a; + border: 1px solid #bababa; + background-color: white; + width: 100%; + margin: 0; + margin-left: 0 !important; + border-top: none; + border-bottom: none; } + .wvToolbar__windowingPresetListItem:hover { + text-decoration: none; + color: white; } + .wvToolbar__windowingPresetListItem + .wvToolbar__windowingPresetListItem { + margin-left: 0.7rem; } + .wvToolbar__windowingPresetListItem:hover { + background-color: #1a1a1a; } + .wvToolbar__windowingPresetListItem > .glyphicon { + position: relative; + display: inline-block; + top: 3px; + margin-right: 4px; } + .wvToolbar__windowingPresetListItem:hover { + color: #1a1a1a; + background-color: #e6e6e6; } + +.wvVideo { + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: auto; + transform: translateY(-50%); } + +.wvStudyInformationBreadcrumb__patient { + display: inline-block; + background-color: rgba(255, 255, 255, 0.15); + padding: 0.2rem 1rem 0.3rem 1rem; + text-align: center; + font-size: 1em; + margin: 0.6rem; + font-weight: 400; + line-height: 2.3rem; + margin-right: 0; } + +.wvStudyInformationBreadcrumb__study { + display: inline-block; + background-color: rgba(255, 255, 255, 0.15); + padding: 0.2rem 1rem 0.3rem 1rem; + text-align: center; + font-size: 1em; + margin: 0.6rem; + font-weight: 400; + line-height: 2.3rem; } + +.wvSelectionActionlist { + display: block; + text-align: center; } + +/* wvb-ui stuffs */ +.wv-overlay { + color: orange; } + +.wv-overlay-icon { + width: 64px; } + +.wvOverlay__studyBadge, .wvOverlay__studyBadge--blue, .wvOverlay__studyBadge--red, .wvOverlay__studyBadge--green, .wvOverlay__studyBadge--yellow, .wvOverlay__studyBadge--violet { + position: absolute; + top: 0; + left: 0; + width: 1.5rem; + height: 1.5rem; + background-color: gray; + z-index: 1; } + +.wv-overlay-topleft { + position: absolute; + top: 0rem; + left: 0rem; + text-align: left; } + +.wv-overlay-topright { + position: absolute; + top: 0rem; + right: 0rem; + text-align: right; } + +.wv-overlay-bottomright { + position: absolute; + bottom: 2em; + right: 0rem; + text-align: right; } + +.wv-overlay-bottomleft { + position: absolute; + bottom: 2em; + left: 0rem; + text-align: left; } + +.wv-overlay-timeline-wrapper { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 1; } + +.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft { + padding: 2rem; + transition: color 500ms, background-color 500ms; + background-color: rgba(0, 0, 0, 0.66); } + +.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover { + background-color: rgba(0, 0, 0, 0.9); } + +.wvPaneOverlay { + position: absolute; + top: 50%; + width: 100%; + transform: translateY(-50%); + font-weight: 100; + text-align: center; + color: white; + font-size: 2rem; } + +.wv-overlay-scrollbar-loaded { + position: absolute; + bottom: 0; + left: 0; + height: 5px; + background-color: red; + will-change: right; + transform-origin: 0% 50%; } + +.wv-overlay-scrollbar-loading { + position: absolute; + bottom: 0; + left: 0; + height: 5px; + background-color: #660000; + will-change: right; + transform-origin: 0% 50%; } + +.wv-overlay-scrollbar-text { + position: absolute; + bottom: calc(1em + 5px); + left: 5px; + height: 1em; + color: red; + font-size: 0.8em; + font-family: helvetica; } + +.wvOverlay__studyBadge--blue { + background-color: rgba(51, 152, 219, 0.7); } + +.wvOverlay__studyBadge--red { + background-color: rgba(206, 0, 0, 0.7); } + +.wvOverlay__studyBadge--green { + background-color: rgba(0, 160, 27, 0.7); } + +.wvOverlay__studyBadge--yellow { + background-color: rgba(220, 200, 0, 0.9); } + +.wvOverlay__studyBadge--violet { + background-color: rgba(255, 31, 255, 0.7); } + +wv-pdf-viewer { + display: block; + width: 100%; + height: 100%; } + +#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton { + background-color: inherit; + color: white; + border: none; + padding: 2px; + margin-left: 4px; + margin-right: 2px; } + #toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton:hover { + color: black; } + +.fa.fa-window-close.wv-pdf-viewer-closebuttonicon { + font-size: 2rem; + line-height: 28px; } + +.pdfjs .textLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + opacity: 0.2; } + +.pdfjs .textLayer > div { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -o-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; } + +.pdfjs .textLayer .highlight { + margin: -1px; + padding: 1px; + background-color: #b400aa; + border-radius: 4px; } + +.pdfjs .textLayer .highlight.begin { + border-radius: 4px 0 0 4px; } + +.pdfjs .textLayer .highlight.end { + border-radius: 0 4px 4px 0; } + +.pdfjs .textLayer .highlight.middle { + border-radius: 0; } + +.pdfjs .textLayer .highlight.selected { + background-color: #006400; } + +.pdfjs .textLayer ::selection { + background: #00f; } + +.pdfjs .textLayer ::-moz-selection { + background: #00f; } + +.pdfjs .pdfViewer .canvasWrapper { + overflow: hidden; } + +.pdfjs .pdfViewer .page { + direction: ltr; + width: 816px; + height: 1056px; + margin: 1px auto -8px; + position: relative; + overflow: visible; + border: 9px solid transparent; + background-clip: content-box; + border-image: url("../images/pdf.js-viewer/shadow.png") 9 9 repeat; + background-color: #fff; } + +body { + height: 100%; } + +.pdfjs .pdfViewer.removePageBorders .page { + margin: 0 auto 10px; + border: none; } + +.pdfjs .pdfViewer .page canvas { + margin: 0; + display: block; } + +.pdfjs .pdfViewer .page .loadingIcon { + position: absolute; + display: block; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: url("../images/pdf.js-viewer/loading-icon.gif") center no-repeat; } + +.pdfjs .pdfViewer .page .annotLink > a:hover { + opacity: .2; + background: #ff0; + box-shadow: 0 2px 10px #ff0; } + +.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page { + margin-bottom: 100%; + border: 0; } + +.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page { + margin-bottom: 100%; + border: 0; } + +.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page { + margin-bottom: 100% !important; + border: 0; } + +.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page { + margin-bottom: 100%; + border: 0; } + +.pdfjs .pdfViewer .page .annotText > img { + position: absolute; + cursor: pointer; } + +.pdfjs .pdfViewer .page .annotTextContentWrapper { + position: absolute; + width: 20em; } + +.pdfjs .pdfViewer .page .annotTextContent { + z-index: 200; + float: left; + max-width: 20em; + background-color: #FF9; + box-shadow: 0 2px 5px #333; + border-radius: 2px; + padding: .6em; + cursor: pointer; } + +.pdfjs .pdfViewer .page .annotTextContent > h1 { + font-size: 1em; + border-bottom: 1px solid #000; + padding-bottom: 0.2em; } + +.pdfjs .pdfViewer .page .annotTextContent > p { + padding-top: 0.2em; } + +.pdfjs .pdfViewer .page .annotLink > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; } + +.pdfjs .pdfViewer .page .annotLink > a { + background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA LAAAAAABAAEAAAIBRAA7") 0 0 repeat; } + +.pdfjs * { + padding: 0; + margin: 0; } + +html { + height: 100%; + font-size: 10px; } + +.pdfjs input, +.pdfjs button, +.pdfjs select { + font: message-box; + outline: none; } + +.pdfjs .hidden { + display: none !important; } + +.pdfjs [hidden] { + display: none !important; } + +.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen { + top: 0; + border-top: 2px solid transparent; + background-color: #000; + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + -webkit-user-select: none; } + +.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen { + top: 0; + border-top: 2px solid transparent; + background-color: #000; + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + -moz-user-select: none; } + +.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen { + top: 0 !important; + border-top: 2px solid transparent; + width: 100%; + height: 100%; + overflow: hidden !important; + cursor: none; + -ms-user-select: none; } + +.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop { + background-color: #000; } + +.pdfjs #viewerContainer.pdfPresentationMode:fullscreen { + top: 0; + border-top: 2px solid transparent; + background-color: #000; + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; } + +.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) { + display: none; } + +.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) { + display: none; } + +.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) { + display: none !important; } + +.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) { + display: none; } + +.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div { + cursor: none; } + +.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div { + cursor: none; } + +.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div { + cursor: none; } + +.pdfjs .pdfPresentationMode:fullscreen .textLayer > div { + cursor: none; } + +.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *, +.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div { + cursor: default; } + +.pdfjs .outerCenter { + pointer-events: none; + position: relative; } + +html[dir='ltr'] .pdfjs .outerCenter { + float: right; + right: 50%; } + +html[dir='rtl'] .pdfjs .outerCenter { + float: left; + left: 50%; } + +.pdfjs .innerCenter { + pointer-events: auto; + position: relative; } + +html[dir='ltr'] .pdfjs .innerCenter { + float: right; + right: -50%; } + +html[dir='rtl'] .pdfjs .innerCenter { + float: left; + left: -50%; } + +.pdfjs #outerContainer { + width: 100%; + height: 100%; + position: relative; + background-color: #404040; + background-image: url("../images/pdf.js-viewer/texture.png"); } + +.pdfjs #sidebarContainer { + position: absolute; + top: 0; + bottom: 0; + width: 200px; + visibility: hidden; + -webkit-transition-duration: 200ms; + -webkit-transition-timing-function: ease; + transition-duration: 200ms; + transition-timing-function: ease; } + +html[dir='ltr'] .pdfjs #sidebarContainer { + -webkit-transition-property: left; + transition-property: left; + left: -200px; } + +html[dir='rtl'] .pdfjs #sidebarContainer { + -webkit-transition-property: right; + transition-property: right; + right: -200px; } + +.pdfjs #outerContainer.sidebarMoving > #sidebarContainer, +.pdfjs #outerContainer.sidebarOpen > #sidebarContainer { + visibility: visible; } + +html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { + left: 0; } + +html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { + right: 0; } + +.pdfjs #mainContainer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + min-width: 320px; + -webkit-transition-duration: 200ms; + -webkit-transition-timing-function: ease; + transition-duration: 200ms; + transition-timing-function: ease; } + +html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + -webkit-transition-property: left; + transition-property: left; + left: 200px; } + +html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + -webkit-transition-property: right; + transition-property: right; + right: 200px; } + +.pdfjs #sidebarContent { + top: 32px; + bottom: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + width: 200px; + background-color: rgba(0, 0, 0, 0.1); } + +html[dir='ltr'] .pdfjs #sidebarContent { + left: 0; + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25); } + +html[dir='rtl'] .pdfjs #sidebarContent { + right: 0; + box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25); } + +.pdfjs #viewerContainer { + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + top: 32px; + right: 0; + bottom: 0; + left: 0; + outline: none; } + +html[dir='ltr'] .pdfjs #viewerContainer { + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05); } + +html[dir='rtl'] .pdfjs #viewerContainer { + box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05); } + +.pdfjs .toolbar { + position: relative; + left: 0; + right: 0; + cursor: default; } + +.pdfjs #toolbarContainer { + width: 100%; } + +.pdfjs #toolbarSidebar { + width: 200px; + height: 32px; + background-color: #424242; + background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95)); } + +html[dir='ltr'] .pdfjs #toolbarSidebar { + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); } + +html[dir='rtl'] .pdfjs #toolbarSidebar { + box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); } + +.pdfjs #toolbarContainer, +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + position: relative; + height: 32px; + background-color: #474747; + background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); } + +html[dir='ltr'] .pdfjs #toolbarContainer, +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); } + +html[dir='rtl'] .pdfjs #toolbarContainer, +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); } + +.pdfjs #toolbarViewer { + height: 32px; } + +.pdfjs #loadingBar { + position: relative; + width: 100%; + height: 4px; + background-color: #333; + border-bottom: 1px solid #333; } + +.pdfjs #loadingBar .progress { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 100%; + background-color: #ddd; + overflow: hidden; + -webkit-transition: width 200ms; + transition: width 200ms; } + +@-webkit-keyframes progressIndeterminate { + 0% { + left: 0; } + 50% { + left: 100%; } + 100% { + left: 100%; } } + +@keyframes progressIndeterminate { + 0% { + left: 0; } + 50% { + left: 100%; } + 100% { + left: 100%; } } + +.pdfjs #loadingBar .progress.indeterminate { + background-color: #999; + -webkit-transition: none; + transition: none; } + +.pdfjs #loadingBar .indeterminate .glimmer { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 50px; + background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%); + background-size: 100% 100%; + background-repeat: no-repeat; + -webkit-animation: progressIndeterminate 2s linear infinite; + animation: progressIndeterminate 2s linear infinite; } + +.pdfjs .findbar, +.pdfjs .secondaryToolbar { + top: 32px; + position: absolute; + z-index: 10000; + height: 32px; + min-width: 16px; + padding: 0 6px; + margin: 4px 2px; + color: #d9d9d9; + font-size: 12px; + line-height: 14px; + text-align: left; + cursor: default; } + +html[dir='ltr'] .pdfjs .findbar { + left: 68px; } + +html[dir='rtl'] .pdfjs .findbar { + right: 68px; } + +.pdfjs .findbar label { + -webkit-user-select: none; + -moz-user-select: none; } + +.pdfjs #findInput[data-status="pending"] { + background-image: url("../images/pdf.js-viewer/loading-small.png"); + background-repeat: no-repeat; + background-position: right; } + +html[dir='rtl'] .pdfjs #findInput[data-status="pending"] { + background-position: left; } + +.pdfjs .secondaryToolbar { + padding: 6px; + height: auto; + z-index: 30000; } + +html[dir='ltr'] .pdfjs .secondaryToolbar { + right: 4px; } + +html[dir='rtl'] .pdfjs .secondaryToolbar { + left: 4px; } + +.pdfjs #secondaryToolbarButtonContainer { + max-width: 200px; + max-height: 400px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + margin-bottom: -4px; } + +.pdfjs .doorHanger, +.pdfjs .doorHangerRight { + border: 1px solid rgba(0, 0, 0, 0.5); + border-radius: 2px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } + +.pdfjs .doorHanger:after, +.pdfjs .doorHanger:before, +.pdfjs .doorHangerRight:after, +.pdfjs .doorHangerRight:before { + bottom: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; } + +.pdfjs .doorHanger:after, +.pdfjs .doorHangerRight:after { + border-bottom-color: rgba(82, 82, 82, 0.99); + border-width: 8px; } + +.pdfjs .doorHanger:before, +.pdfjs .doorHangerRight:before { + border-bottom-color: rgba(0, 0, 0, 0.5); + border-width: 9px; } + +html[dir='ltr'] .pdfjs .doorHanger:after, +html[dir='rtl'] .pdfjs .doorHangerRight:after { + left: 13px; + margin-left: -8px; } + +html[dir='ltr'] .pdfjs .doorHanger:before, +html[dir='rtl'] .pdfjs .doorHangerRight:before { + left: 13px; + margin-left: -9px; } + +html[dir='rtl'] .pdfjs .doorHanger:after, +html[dir='ltr'] .pdfjs .doorHangerRight:after { + right: 13px; + margin-right: -8px; } + +html[dir='rtl'] .pdfjs .doorHanger:before, +html[dir='ltr'] .pdfjs .doorHangerRight:before { + right: 13px; + margin-right: -9px; } + +.pdfjs #findMsg { + font-style: italic; + color: #A6B7D0; } + +.pdfjs #findInput.notFound { + background-color: #f66; } + +html[dir='ltr'] .pdfjs #toolbarViewerLeft { + margin-left: -1px; } + +html[dir='rtl'] .pdfjs #toolbarViewerRight { + margin-right: -1px; } + +html[dir='ltr'] .pdfjs #toolbarViewerLeft, +html[dir='rtl'] .pdfjs #toolbarViewerRight { + position: absolute; + top: 0; + left: 0; } + +html[dir='ltr'] .pdfjs #toolbarViewerRight, +html[dir='rtl'] .pdfjs #toolbarViewerLeft { + position: absolute; + top: 0; + right: 0; } + +html[dir='ltr'] .pdfjs #toolbarViewerLeft > *, +html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *, +html[dir='ltr'] .pdfjs #toolbarViewerRight > *, +html[dir='ltr'] .pdfjs .findbar > * { + position: relative; + float: left; } + +html[dir='rtl'] .pdfjs #toolbarViewerLeft > *, +html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *, +html[dir='rtl'] .pdfjs #toolbarViewerRight > *, +html[dir='rtl'] .pdfjs .findbar > * { + position: relative; + float: right; } + +html[dir='ltr'] .pdfjs .splitToolbarButton { + margin: 3px 2px 4px 0; + display: inline-block; } + +html[dir='rtl'] .pdfjs .splitToolbarButton { + margin: 3px 0 4px 2px; + display: inline-block; } + +html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton { + border-radius: 0; + float: left; } + +html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton { + border-radius: 0; + float: right; } + +.pdfjs .toolbarButton, +.pdfjs .secondaryToolbarButton, +.pdfjs .overlayButton { + border: 0 none; + background: none; + width: 32px; + height: 25px; } + +.pdfjs .toolbarButton > span { + display: inline-block; + width: 0; + height: 0; + overflow: hidden; } + +.pdfjs .toolbarButton[disabled], +.pdfjs .secondaryToolbarButton[disabled], +.pdfjs .overlayButton[disabled] { + opacity: 0.5; } + +.pdfjs .toolbarButton.group { + margin-right: 0; } + +.pdfjs .splitToolbarButton.toggled .toolbarButton { + margin: 0; } + +.pdfjs .splitToolbarButton:hover > .toolbarButton, +.pdfjs .splitToolbarButton:focus > .toolbarButton, +.pdfjs .splitToolbarButton.toggled > .toolbarButton, +.pdfjs .toolbarButton.textButton { + background-color: rgba(0, 0, 0, 0.12); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.35); + border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 150ms; + -webkit-transition-timing-function: ease; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; } + +.pdfjs .splitToolbarButton > .toolbarButton:hover, +.pdfjs .splitToolbarButton > .toolbarButton:focus, +.pdfjs .dropdownToolbarButton:hover, +.pdfjs .overlayButton:hover, +.pdfjs .toolbarButton.textButton:hover, +.pdfjs .toolbarButton.textButton:focus { + background-color: rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05); + z-index: 199; } + +.pdfjs .splitToolbarButton > .toolbarButton { + position: relative; } + +html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child, +html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child { + position: relative; + margin: 0; + margin-right: -1px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + border-right-color: transparent; } + +html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child, +html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child { + position: relative; + margin: 0; + margin-left: -1px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-left-color: transparent; } + +.pdfjs .splitToolbarButtonSeparator { + padding: 8px 0; + width: 1px; + background-color: rgba(0, 0, 0, 0.5); + z-index: 99; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); + display: inline-block; + margin: 5px 0; } + +html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator { + float: left; } + +html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator { + float: right; } + +.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator, +.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator { + padding: 12px 0; + margin: 1px 0; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03); + -webkit-transition-property: padding; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: ease; + transition-property: padding; + transition-duration: 10ms; + transition-timing-function: ease; } + +.pdfjs .toolbarButton, +.pdfjs .dropdownToolbarButton, +.pdfjs .secondaryToolbarButton, +.pdfjs .overlayButton { + min-width: 16px; + padding: 2px 6px 0; + border: 1px solid transparent; + border-radius: 2px; + color: rgba(255, 255, 255, 0.8); + font-size: 12px; + line-height: 14px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + cursor: default; + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 150ms; + -webkit-transition-timing-function: ease; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; } + +html[dir='ltr'] .pdfjs .toolbarButton, +html[dir='ltr'] .pdfjs .overlayButton, +html[dir='ltr'] .pdfjs .dropdownToolbarButton { + margin: 3px 2px 4px 0; } + +html[dir='rtl'] .pdfjs .toolbarButton, +html[dir='rtl'] .pdfjs .overlayButton, +html[dir='rtl'] .pdfjs .dropdownToolbarButton { + margin: 3px 0 4px 2px; } + +.pdfjs .toolbarButton:hover, +.pdfjs .toolbarButton:focus, +.pdfjs .dropdownToolbarButton, +.pdfjs .overlayButton, +.pdfjs .secondaryToolbarButton:hover, +.pdfjs .secondaryToolbarButton:focus { + background-color: rgba(0, 0, 0, 0.12); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.35); + border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); } + +.pdfjs .toolbarButton:hover:active, +.pdfjs .overlayButton:hover:active, +.pdfjs .dropdownToolbarButton:hover:active, +.pdfjs .secondaryToolbarButton:hover:active { + background-color: rgba(0, 0, 0, 0.2); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: linear; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; } + +.pdfjs .toolbarButton.toggled, +.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled, +.pdfjs .secondaryToolbarButton.toggled { + background-color: rgba(0, 0, 0, 0.3); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: linear; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; } + +.pdfjs .toolbarButton.toggled:hover:active, +.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active, +.pdfjs .secondaryToolbarButton.toggled:hover:active { + background-color: rgba(0, 0, 0, 0.4); + border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05); } + +.pdfjs .dropdownToolbarButton { + width: 120px; + max-width: 120px; + padding: 0; + overflow: hidden; + background: url("../images/pdf.js-viewer/toolbarButton-menuArrows.png") no-repeat; } + +html[dir='ltr'] .pdfjs .dropdownToolbarButton { + background-position: 95%; } + +html[dir='rtl'] .pdfjs .dropdownToolbarButton { + background-position: 5%; } + +.pdfjs .dropdownToolbarButton > select { + min-width: 140px; + font-size: 12px; + color: #f2f2f2; + margin: 0; + padding: 3px 2px 2px; + border: none; + background: rgba(0, 0, 0, 0); } + +.pdfjs .dropdownToolbarButton > select > option { + background: #3d3d3d; } + +.pdfjs #customScaleOption { + display: none; } + +.pdfjs #pageWidthOption { + border-bottom: 1px rgba(255, 255, 255, 0.5) solid; } + +html[dir='ltr'] .pdfjs .splitToolbarButton:first-child, +html[dir='ltr'] .pdfjs .toolbarButton:first-child, +html[dir='rtl'] .pdfjs .splitToolbarButton:last-child, +html[dir='rtl'] .pdfjs .toolbarButton:last-child { + margin-left: 4px; } + +html[dir='ltr'] .pdfjs .splitToolbarButton:last-child, +html[dir='ltr'] .pdfjs .toolbarButton:last-child, +html[dir='rtl'] .pdfjs .splitToolbarButton:first-child, +html[dir='rtl'] .pdfjs .toolbarButton:first-child { + margin-right: 4px; } + +.pdfjs .toolbarButtonSpacer { + width: 30px; + display: inline-block; + height: 1px; } + +.pdfjs .toolbarButtonFlexibleSpacer { + -webkit-box-flex: 1; + -moz-box-flex: 1; + min-width: 30px; } + +html[dir='ltr'] .pdfjs #findPrevious { + margin-left: 3px; } + +html[dir='ltr'] .pdfjs #findNext { + margin-right: 3px; } + +html[dir='rtl'] .pdfjs #findPrevious { + margin-right: 3px; } + +html[dir='rtl'] .pdfjs #findNext { + margin-left: 3px; } + +.pdfjs .toolbarButton::before, +.pdfjs .secondaryToolbarButton::before { + position: absolute; + display: inline-block; + top: 4px; + left: 7px; } + +html[dir="ltr"] .pdfjs .secondaryToolbarButton::before { + left: 4px; } + +html[dir="rtl"] .pdfjs .secondaryToolbarButton::before { + right: 4px; } + +html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle.png"); } + +html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png"); } + +html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png"); } + +html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png"); } + +html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { + content: url("../images/pdf.js-viewer/findbarButton-previous.png"); } + +html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { + content: url("../images/pdf.js-viewer/findbarButton-previous-rtl.png"); } + +html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { + content: url("../images/pdf.js-viewer/findbarButton-next.png"); } + +html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { + content: url("../images/pdf.js-viewer/findbarButton-next-rtl.png"); } + +html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageUp.png"); } + +html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png"); } + +html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageDown.png"); } + +html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png"); } + +.pdfjs .toolbarButton.zoomOut::before { + content: url("../images/pdf.js-viewer/toolbarButton-zoomOut.png"); } + +.pdfjs .toolbarButton.zoomIn::before { + content: url("../images/pdf.js-viewer/toolbarButton-zoomIn.png"); } + +.pdfjs .toolbarButton.presentationMode::before, +.pdfjs .secondaryToolbarButton.presentationMode::before { + content: url("../images/pdf.js-viewer/toolbarButton-presentationMode.png"); } + +.pdfjs .toolbarButton.print::before, +.pdfjs .secondaryToolbarButton.print::before { + content: url("../images/pdf.js-viewer/toolbarButton-print.png"); } + +.pdfjs .toolbarButton.openFile::before, +.pdfjs .secondaryToolbarButton.openFile::before { + content: url("../images/pdf.js-viewer/toolbarButton-openFile.png"); } + +.pdfjs .toolbarButton.download::before, +.pdfjs .secondaryToolbarButton.download::before { + content: url("../images/pdf.js-viewer/toolbarButton-download.png"); } + +.pdfjs .toolbarButton.bookmark, +.pdfjs .secondaryToolbarButton.bookmark { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + outline: none; + padding-top: 4px; + text-decoration: none; } + +.pdfjs .secondaryToolbarButton.bookmark { + padding-top: 5px; } + +.pdfjs .bookmark[href='#'] { + opacity: .5; + pointer-events: none; } + +.pdfjs .toolbarButton.bookmark::before, +.pdfjs .secondaryToolbarButton.bookmark::before { + content: url("../images/pdf.js-viewer/toolbarButton-bookmark.png"); } + +.pdfjs #viewThumbnail.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail.png"); } + +html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewOutline.png"); } + +html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png"); } + +.pdfjs #viewAttachments.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments.png"); } + +.pdfjs #viewFind.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-search.png"); } + +.pdfjs .secondaryToolbarButton { + position: relative; + margin: 0 0 4px; + padding: 3px 0 1px; + height: auto; + min-height: 25px; + width: auto; + min-width: 100%; + white-space: normal; } + +html[dir="ltr"] .pdfjs .secondaryToolbarButton { + padding-left: 24px; + text-align: left; } + +html[dir="rtl"] .pdfjs .secondaryToolbarButton { + padding-right: 24px; + text-align: right; } + +html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark { + padding-left: 27px; } + +html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark { + padding-right: 27px; } + +html[dir="ltr"] .pdfjs .secondaryToolbarButton > span { + padding-right: 4px; } + +html[dir="rtl"] .pdfjs .secondaryToolbarButton > span { + padding-left: 4px; } + +.pdfjs .secondaryToolbarButton.firstPage::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png"); } + +.pdfjs .secondaryToolbarButton.lastPage::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png"); } + +.pdfjs .secondaryToolbarButton.rotateCcw::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png"); } + +.pdfjs .secondaryToolbarButton.rotateCw::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png"); } + +.pdfjs .secondaryToolbarButton.handTool::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool.png"); } + +.pdfjs .secondaryToolbarButton.documentProperties::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png"); } + +.pdfjs .verticalToolbarSeparator { + display: block; + padding: 8px 0; + margin: 8px 4px; + width: 1px; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); } + +html[dir='ltr'] .pdfjs .verticalToolbarSeparator { + margin-left: 2px; } + +html[dir='rtl'] .pdfjs .verticalToolbarSeparator { + margin-right: 2px; } + +.pdfjs .horizontalToolbarSeparator { + display: block; + margin: 0 0 4px; + height: 1px; + width: 100%; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); } + +.pdfjs .toolbarField { + padding: 3px 6px; + margin: 4px 0; + border: 1px solid transparent; + border-radius: 2px; + background-color: rgba(255, 255, 255, 0.09); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.35); + border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05); + color: #f2f2f2; + font-size: 12px; + line-height: 14px; + outline-style: none; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; } + +.pdfjs .toolbarField[type=checkbox] { + display: inline-block; + margin: 8px 0; } + +.pdfjs .toolbarField.pageNumber { + -moz-appearance: textfield; + min-width: 16px; + text-align: right; + width: 40px; } + +.pdfjs .toolbarField.pageNumber.visiblePageIsLoading { + background-image: url("../images/pdf.js-viewer/loading-small.png"); + background-repeat: no-repeat; + background-position: 1px; } + +.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button, +.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; } + +.pdfjs .toolbarField:hover { + background-color: rgba(255, 255, 255, 0.11); + border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45); } + +.pdfjs .toolbarField:focus { + background-color: rgba(255, 255, 255, 0.15); + border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9); } + +.pdfjs .toolbarLabel { + min-width: 16px; + padding: 3px 6px 3px 2px; + margin: 4px 2px 4px 0; + border: 1px solid transparent; + border-radius: 2px; + color: #d9d9d9; + font-size: 12px; + line-height: 14px; + text-align: left; + -webkit-user-select: none; + -moz-user-select: none; + cursor: default; } + +.pdfjs #thumbnailView { + position: absolute; + width: 120px; + top: 0; + bottom: 0; + padding: 10px 40px 0; + overflow: auto; + -webkit-overflow-scrolling: touch; } + +.pdfjs .thumbnail { + float: left; + margin-bottom: 5px; } + +.pdfjs #thumbnailView > a:last-of-type > .thumbnail { + margin-bottom: 10px; } + +.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) { + margin-bottom: 9px; } + +.pdfjs .thumbnail:not([data-loaded]) { + border: 1px dashed rgba(255, 255, 255, 0.5); + margin: -1px -1px 4px; } + +.pdfjs .thumbnailImage { + border: 1px solid transparent; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); + opacity: .8; + z-index: 99; + background-color: #fff; + background-clip: content-box; } + +.pdfjs .thumbnailSelectionRing { + border-radius: 2px; + padding: 7px; } + +.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage, +.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage { + opacity: 0.9; } + +.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing, +.pdfjs .thumbnail:hover > .thumbnailSelectionRing { + background-color: rgba(255, 255, 255, 0.15); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: rgba(255, 255, 255, 0.9); } + +.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage { + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5); + opacity: 1; } + +.pdfjs .thumbnail.selected > .thumbnailSelectionRing { + background-color: rgba(255, 255, 255, 0.3); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: #ffffff; } + +.pdfjs #outlineView, +.pdfjs #attachmentsView { + position: absolute; + width: 192px; + top: 0; + bottom: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; + -webkit-user-select: none; + -moz-user-select: none; } + +.pdfjs #outlineView { + padding: 4px 4px 0; } + +.pdfjs #attachmentsView { + padding: 3px 4px 0; } + +html[dir='ltr'] .pdfjs .outlineItem > .outlineItems { + margin-left: 20px; } + +html[dir='rtl'] .pdfjs .outlineItem > .outlineItems { + margin-right: 20px; } + +.pdfjs .outlineItem > a, +.pdfjs .attachmentsItem > button { + text-decoration: none; + display: inline-block; + min-width: 95%; + height: auto; + margin-bottom: 1px; + border-radius: 2px; + color: rgba(255, 255, 255, 0.8); + font-size: 13px; + line-height: 15px; + -moz-user-select: none; + white-space: normal; } + +.pdfjs .attachmentsItem > button { + border: 0 none; + background: none; + cursor: pointer; + width: 100%; } + +html[dir='ltr'] .pdfjs .outlineItem > a { + padding: 2px 0 5px 10px; } + +html[dir='ltr'] .pdfjs .attachmentsItem > button { + padding: 2px 0 3px 7px; + text-align: left; } + +html[dir='rtl'] .pdfjs .outlineItem > a { + padding: 2px 10px 5px 0; } + +html[dir='rtl'] .pdfjs .attachmentsItem > button { + padding: 2px 7px 3px 0; + text-align: right; } + +.pdfjs .outlineItem > a:hover, +.pdfjs .attachmentsItem > button:hover { + background-color: rgba(255, 255, 255, 0.02); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: rgba(255, 255, 255, 0.9); } + +.pdfjs .outlineItem.selected { + background-color: rgba(255, 255, 255, 0.08); + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); + background-clip: padding-box; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); + color: #ffffff; } + +.pdfjs .noResults { + font-size: 12px; + color: rgba(255, 255, 255, 0.8); + font-style: italic; + cursor: default; } + +.pdfjs ::selection { + background: rgba(0, 0, 255, 0.3); } + +.pdfjs ::-moz-selection { + background: rgba(0, 0, 255, 0.3); } + +.pdfjs #errorWrapper { + background: none repeat scroll 0 0 #F55; + color: #fff; + left: 0; + position: absolute; + right: 0; + z-index: 1000; + padding: 3px; + font-size: 0.8em; } + +.pdfjs .loadingInProgress #errorWrapper { + top: 37px; } + +.pdfjs #errorMessageLeft { + float: left; } + +.pdfjs #errorMessageRight { + float: right; } + +.pdfjs #errorMoreInfo { + background-color: #FFF; + color: #000; + padding: 3px; + margin: 3px; + width: 98%; } + +.pdfjs .overlayButton { + width: auto; + margin: 3px 4px 2px !important; + padding: 2px 6px 3px; } + +.pdfjs #overlayContainer { + display: table; + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.2); + z-index: 40000; } + +.pdfjs #overlayContainer > * { + overflow: auto; + -webkit-overflow-scrolling: touch; } + +.pdfjs #overlayContainer > .container { + display: table-cell; + vertical-align: middle; + text-align: center; } + +.pdfjs #overlayContainer > .container > .dialog { + display: inline-block; + padding: 15px; + border-spacing: 4px; + color: #d9d9d9; + font-size: 12px; + line-height: 14px; + background-color: #474747; + background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(0, 0, 0, 0.5); + border-radius: 4px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } + +.pdfjs .dialog > .row { + display: table-row; } + +.pdfjs .dialog > .row > * { + display: table-cell; } + +.pdfjs .dialog .toolbarField { + margin: 5px 0; } + +.pdfjs .dialog .separator { + display: block; + margin: 4px 0; + height: 1px; + width: 100%; + background-color: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); } + +.pdfjs .dialog .buttonRow { + text-align: center; + vertical-align: middle; } + +.pdfjs #passwordOverlay > .dialog { + text-align: center; } + +.pdfjs #passwordOverlay .toolbarField { + width: 200px; } + +.pdfjs #documentPropertiesOverlay > .dialog { + text-align: left; } + +.pdfjs #documentPropertiesOverlay .row > * { + min-width: 100px; } + +html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * { + text-align: left; } + +html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * { + text-align: right; } + +.pdfjs #documentPropertiesOverlay .row > span { + width: 125px; + word-wrap: break-word; } + +.pdfjs #documentPropertiesOverlay .row > p { + max-width: 225px; + word-wrap: break-word; } + +.pdfjs #documentPropertiesOverlay .buttonRow { + margin-top: 10px; } + +.pdfjs .clearBoth { + clear: both; } + +.pdfjs .fileInput { + background: #fff; + color: #000; + margin-top: 5px; + visibility: hidden; + position: fixed; + right: 0; + top: 0; } + +.pdfjs #PDFBug { + background: none repeat scroll 0 0 #fff; + border: 1px solid #666; + position: fixed; + top: 32px; + right: 0; + bottom: 0; + font-size: 10px; + padding: 0; + width: 300px; } + +.pdfjs #PDFBug .controls { + background: #EEE; + border-bottom: 1px solid #666; + padding: 3px; } + +.pdfjs #PDFBug .panels { + bottom: 0; + left: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + right: 0; + top: 27px; } + +.pdfjs #PDFBug button.active { + font-weight: 700; } + +.pdfjs .debuggerShowText { + background: none repeat scroll 0 0 #ff0; + color: blue; } + +.pdfjs .debuggerHideText:hover { + background: none repeat scroll 0 0 #ff0; } + +.pdfjs #PDFBug .stats { + font-family: courier; + font-size: 10px; + white-space: pre; } + +.pdfjs #PDFBug .stats .title { + font-weight: 700; } + +.pdfjs #PDFBug table { + font-size: 10px; } + +.pdfjs #viewer.textLayer-visible .textLayer > div, +.pdfjs #viewer.textLayer-hover .textLayer > div:hover { + background-color: #fff; + color: #000; } + +.pdfjs #viewer.textLayer-shadow .textLayer > div { + background-color: rgba(255, 255, 255, 0.6); + color: #000; } + +.pdfjs .grab-to-pan-grab { + cursor: url("../images/pdf.js-viewer/grab.cur"), move !important; + cursor: -webkit-grab !important; + cursor: -moz-grab !important; + cursor: grab !important; } + +.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) { + cursor: inherit !important; } + +.pdfjs .grab-to-pan-grab:active, +.pdfjs .grab-to-pan-grabbing { + cursor: url("../images/pdf.js-viewer/grabbing.cur"), move !important; + cursor: -webkit-grabbing !important; + cursor: -moz-grabbing !important; + cursor: grabbing !important; + position: fixed; + background: transparent; + display: block; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: 50000; } + +@page { + margin: 0; } + +.pdfjs #printContainer { + display: none; } + +@media screen and (min-resolution: 2dppx) { + .pdfjs .toolbarButton::before { + -webkit-transform: scale(0.5); + transform: scale(0.5); + top: -5px; } + .pdfjs .secondaryToolbarButton::before { + -webkit-transform: scale(0.5); + transform: scale(0.5); + top: -4px; } + html[dir='ltr'] .pdfjs .toolbarButton::before, + html[dir='rtl'] .pdfjs .toolbarButton::before { + left: -1px; } + html[dir="ltr"] .pdfjs .secondaryToolbarButton::before { + left: -2px; } + html[dir="rtl"] .pdfjs .secondaryToolbarButton::before { + left: 186px; } + .pdfjs .toolbarField.pageNumber.visiblePageIsLoading, + .pdfjs #findInput[data-status="pending"] { + background-image: url("../images/pdf.js-viewer/loading-small@2x.png"); + background-size: 16px 17px; } + .pdfjs .dropdownToolbarButton { + background: url("../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png") no-repeat; + background-size: 7px 16px; } + html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png"); } + html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png"); } + html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png"); } + html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { + content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png"); } + html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { + content: url("../images/pdf.js-viewer/findbarButton-previous@2x.png"); } + html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { + content: url("../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png"); } + html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { + content: url("../images/pdf.js-viewer/findbarButton-next@2x.png"); } + html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { + content: url("../images/pdf.js-viewer/findbarButton-next-rtl@2x.png"); } + html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageUp@2x.png"); } + html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png"); } + html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageDown@2x.png"); } + html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { + content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png"); } + .pdfjs .toolbarButton.zoomIn::before { + content: url("../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png"); } + .pdfjs .toolbarButton.zoomOut::before { + content: url("../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png"); } + .pdfjs .toolbarButton.presentationMode::before, + .pdfjs .secondaryToolbarButton.presentationMode::before { + content: url("../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png"); } + .pdfjs .toolbarButton.print::before, + .pdfjs .secondaryToolbarButton.print::before { + content: url("../images/pdf.js-viewer/toolbarButton-print@2x.png"); } + .pdfjs .toolbarButton.openFile::before, + .pdfjs .secondaryToolbarButton.openFile::before { + content: url("../images/pdf.js-viewer/toolbarButton-openFile@2x.png"); } + .pdfjs .toolbarButton.download::before, + .pdfjs .secondaryToolbarButton.download::before { + content: url("../images/pdf.js-viewer/toolbarButton-download@2x.png"); } + .pdfjs .toolbarButton.bookmark::before, + .pdfjs .secondaryToolbarButton.bookmark::before { + content: url("../images/pdf.js-viewer/toolbarButton-bookmark@2x.png"); } + .pdfjs #viewThumbnail.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png"); } + html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png"); } + html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png"); } + .pdfjs #viewAttachments.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png"); } + .pdfjs #viewFind.toolbarButton::before { + content: url("../images/pdf.js-viewer/toolbarButton-search@2x.png"); } + .pdfjs .secondaryToolbarButton.firstPage::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png"); } + .pdfjs .secondaryToolbarButton.lastPage::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png"); } + .pdfjs .secondaryToolbarButton.rotateCcw::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png"); } + .pdfjs .secondaryToolbarButton.rotateCw::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png"); } + .pdfjs .secondaryToolbarButton.handTool::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png"); } + .pdfjs .secondaryToolbarButton.documentProperties::before { + content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png"); } } + +@media print { + body { + background: transparent none; } + .pdfjs #sidebarContainer, + .pdfjs #secondaryToolbar, + .pdfjs .toolbar, + .pdfjs #loadingBox, + .pdfjs #errorWrapper, + .pdfjs .textLayer { + display: none; } + .pdfjs #viewerContainer { + overflow: visible; } + .pdfjs #mainContainer, + .pdfjs #viewerContainer, + .pdfjs .page, + .pdfjs .page canvas { + position: static; + padding: 0; + margin: 0; } + .pdfjs .page { + float: left; + display: none; + border: none; + box-shadow: none; + background-clip: content-box; + background-color: #fff; } + .pdfjs .page[data-loaded] { + display: block; } + .pdfjs .fileInput { + display: none; } + body[data-mozPrintCallback] .pdfjs #outerContainer { + display: none; } + body[data-mozPrintCallback] .pdfjs #printContainer { + display: block; } + .pdfjs #printContainer > div { + position: relative; + top: 0; + left: 0; + overflow: hidden; } + .pdfjs #printContainer canvas { + display: block; } } + +.pdfjs .visibleLargeView, +.pdfjs .visibleMediumView, +.pdfjs .visibleSmallView { + display: none; } + +@media all and (max-width: 960px) { + html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter { + float: left; + left: 205px; } + html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter { + float: right; + right: 205px; } } + +@media all and (max-width: 900px) { + .pdfjs .sidebarOpen .hiddenLargeView { + display: none; } + .pdfjs .sidebarOpen .visibleLargeView { + display: inherit; } } + +@media all and (max-width: 860px) { + .pdfjs .sidebarOpen .hiddenMediumView { + display: none; } + .pdfjs .sidebarOpen .visibleMediumView { + display: inherit; } } + +@media all and (max-width: 770px) { + .pdfjs #sidebarContainer { + top: 32px; + z-index: 100; } + .pdfjs .loadingInProgress #sidebarContainer { + top: 37px; } + .pdfjs #sidebarContent { + top: 32px; + background-color: rgba(0, 0, 0, 0.7); } + html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + left: 0; } + html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { + right: 0; } + html[dir='ltr'] .pdfjs .outerCenter { + float: left; + left: 205px; } + html[dir='rtl'] .pdfjs .outerCenter { + float: right; + right: 205px; } + .pdfjs #outerContainer .hiddenLargeView, + .pdfjs #outerContainer .hiddenMediumView { + display: inherit; } + .pdfjs #outerContainer .visibleLargeView, + .pdfjs #outerContainer .visibleMediumView { + display: none; } } + +@media all and (max-width: 700px) { + .pdfjs #outerContainer .hiddenLargeView { + display: none; } + .pdfjs #outerContainer .visibleLargeView { + display: inherit; } } + +@media all and (max-width: 660px) { + .pdfjs #outerContainer .hiddenMediumView { + display: none; } + .pdfjs #outerContainer .visibleMediumView { + display: inherit; } } + +@media all and (max-width: 600px) { + .pdfjs .hiddenSmallView { + display: none; } + .pdfjs .visibleSmallView { + display: inherit; } + html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter, + html[dir='ltr'] .pdfjs .outerCenter { + left: 156px; } + html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, + html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter, + html[dir='rtl'] .pdfjs .outerCenter { + right: 156px; } + .pdfjs .toolbarButtonSpacer { + width: 0; } } + +@media all and (max-width: 510px) { + .pdfjs #scaleSelectContainer, + .pdfjs #pageNumberLabel { + display: none; } } + +/* should be hidden differently */ +#fileInput.fileInput { + display: none; } + +.wvSplitpane { + height: 100%; + padding: 7px 2px 2px 2px; + position: relative; } + +.wvSplitpane__cell { + display: inline-block; + float: left; + height: 100%; + width: 100%; + position: relative; } + +.wvSplitpane__cellBorder, .wvSplitpane__cellBorder--selected, .wvSplitpane__cellBorder--blue, .wvSplitpane__cellBorder--red, .wvSplitpane__cellBorder--green, .wvSplitpane__cellBorder--yellow, .wvSplitpane__cellBorder--violet { + display: inline-block; + float: left; + height: calc(100% - 2px); + width: calc(100% - 2px); + border: 2px dashed transparent; + padding: 2px; + margin: 1px; } + +.wvSplitpane__cellBorder--selected { + border: 2px solid rgba(51, 152, 219, 0.7); } + +.wvSplitpane__cellBorder--blue { + border-color: rgba(51, 152, 219, 0.7); } + +.wvSplitpane__cellBorder--red { + border-color: rgba(206, 0, 0, 0.7); } + +.wvSplitpane__cellBorder--green { + border-color: rgba(0, 160, 27, 0.7); } + +.wvSplitpane__cellBorder--yellow { + border-color: rgba(220, 200, 0, 0.9); } + +.wvSplitpane__cellBorder--violet { + border-color: rgba(255, 31, 255, 0.7); } + +wv-pane-policy { + display: block; + width: 100%; + height: 100%; } + wv-pane-policy > div[ng-transclude] { + display: block; + width: 100%; + height: 100%; } + +.wv-timeline { + position: relative; + height: 2em; } + .wv-timeline.reduced { + height: 5px; } + .wv-timeline.reduced .wv-timeline-loading-bar-wrapper { + width: 100%; + height: 100%; } + +.wv-timeline-controls-wrapper { + position: absolute; + left: 0; + bottom: 0; + width: 16em; + height: 100%; + color: white; } + +.wv-timeline-loading-bar-wrapper { + position: absolute; + right: 0; + bottom: 0; + width: calc(100% - 16em); + height: calc(100% + 2px); } + .wv-timeline-loading-bar-wrapper svg { + position: absolute; + left: 0; + top: 0; } + +/* wv-timeline-controls directive */ +.wv-timeline-controls { + padding: 0.5em 0.5em 0.5em 0.5em; + line-height: 1em; + background-color: rgba(0, 0, 0, 0.66); + text-align: center; + transition: color 500ms, background-color 500ms; } + +.wv-timeline-controls:hover { + background-color: rgba(0, 0, 0, 0.9); } + +.wv-timeline-controls-vertical-sizing { + display: inline-block; + line-height: 1em; + font-size: 1em; } + +.wv-timeline-controls-vflip:before, .wv-timeline-controls-vflip:after { + transform: scaleX(-1); + display: inline-block; } + +.wv-timeline-controls-button { + display: inline-block; + height: 1em; + width: 1em; + line-height: 1em; + font-size: 1em; + margin: 0; + user-select: none; + cursor: pointer; } + +.wv-timeline-controls-input { + height: 1em; + width: 3em; + padding: 0; + padding-bottom: 1px; + box-sizing: content-box; + border: none; + border-bottom: 1px solid rgba(255, 202, 128, 0.24); + background-color: transparent; + text-align: right; } + +.wv-timeline-controls-play-button-wrapper { + float: right; } + +/* wv-play-button directive */ +.wv-play-button { + display: inline-block; + position: relative; + line-height: 1em; + height: 3em; + width: 6em; + padding-bottom: 1em; + padding-left: 0.25em; + padding-right: 0.25em; } + +.wv-play-button:hover .wv-play-button-config-position-handler { + visibility: visible; } + +.wv-play-button-config-position-handler { + visibility: hidden; + position: absolute; + bottom: 3em; + left: 1em; + right: 0.5em; } + +.wv-play-button-config { + position: absolute; + bottom: 0; + left: -6em; + width: 12em; + padding: 1em; + background-color: rgba(0, 0, 0, 0.5); } + +/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */ +.wv-play-button-config-framerate-wrapper { + display: inline-block; + margin: 0.25em 0 0.5em 0; } + +input[type="range"].wv-play-button-config-framerate { + /*removes default webkit styles*/ + -webkit-appearance: none; + /*fix for FF unable to apply focus style bug */ + border: 1px solid white; + /*required for proper track sizing in FF*/ + width: 10em; } + +input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track { + width: 10em; + height: 5px; + background: #ddd; + border: none; + border-radius: 3px; } + +input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb { + -webkit-appearance: none; + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: goldenrod; + margin-top: -4px; } + +input[type="range"].wv-play-button-config-framerate:focus { + outline: none; } + +input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track { + background: #ccc; } + +input[type="range"].wv-play-button-config-framerate::-moz-range-track { + width: 10em; + height: 5px; + background: #ddd; + border: none; + border-radius: 3px; } + +input[type="range"].wv-play-button-config-framerate::-moz-range-thumb { + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: goldenrod; } + +/*hide the outline behind the border*/ +input[type="range"].wv-play-button-config-framerate:-moz-focusring { + outline: 1px solid white; + outline-offset: -1px; } + +input[type="range"].wv-play-button-config-framerate::-ms-track { + width: 10em; + height: 5px; + /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */ + background: transparent; + /*leave room for the larger thumb to overflow with a transparent border */ + border-color: transparent; + border-width: 6px 0; + /*remove default tick marks*/ + color: transparent; } + +input[type="range"].wv-play-button-config-framerate::-ms-fill-lower { + background: #777; + border-radius: 10px; } + +input[type="range"].wv-play-button-config-framerate::-ms-fill-upper { + background: #ddd; + border-radius: 10px; } + +input[type="range"].wv-play-button-config-framerate::-ms-thumb { + border: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: goldenrod; } + +input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower { + background: #888; } + +input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper { + background: #ccc; } + +.wv-loadingbar-image-bar { + cursor: pointer; } + +.wv-loadingbar-not-loaded { + fill: rgba(255, 255, 255, 0.1); } + +.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality { + transition: none; } + +.wv-loadingbar-not-loaded:hover { + fill: rgba(255, 255, 255, 0.2); } + +.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality { + fill: rgba(0, 255, 0, 0.7); } + +.wv-loadingbar-LOSSLESS-quality:hover, +.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active, +.wv-loadingbar-PIXELDATA-quality:hover, +.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active { + fill: lime; } + +.wv-loadingbar-LOW-quality { + fill: rgba(255, 0, 0, 0.7); } + +.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active { + fill: red; } + +.wv-loadingbar-MEDIUM-quality { + fill: rgba(255, 95, 0, 0.7); } + +.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active { + fill: #ff5f00; } + +.disclaimer { + color: #E63F24; + background-color: #303030; + padding: 5px; + text-align: center; + font-weight: bold; } + +.tbGroup { + position: relative; } + +.tbGroup__buttons--base, .tbGroup__buttons--bottom, .tbGroup__buttons--left { + z-index: 5; + background-color: black; + position: absolute; } + +.tbGroup__buttons--bottom { + right: 0; + display: block; } + +.tbGroup__buttons--left { + right: 100%; + top: 0; + display: block; } + +.tbGroup__icon { + display: block; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 0; + border-style: solid; + border-width: 10px 0 0 10px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); } + .tbGroup__icon.active { + border-color: transparent transparent transparent #3498db; } + +wv-viewport { + display: inline-block; + width: 100%; + height: 100%; } + wv-viewport > div { + position: relative; + width: 100%; + height: 100%; } + wv-viewport > div > .wv-cornerstone-enabled-image { + width: 100%; + height: 100%; + text-align: center; } + +.wv-draggable-clone { + width: 150px; + height: 150px; + background-color: rgba(255, 255, 255, 0.25); } + +@media print { + .wvPrintExclude { + display: none; } + .wvPrintFullPage { + width: 100% !important; + height: 100% !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; + display: block !important; } + .wvLayout__main { + top: 0 !important; + right: 0 !important; + left: 0 !important; + bottom: 0 !important; } + .wvPrintViewer { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; } + .wvPrintViewer canvas { + max-width: 100% !important; + max-height: 100% !important; + margin: auto; } + .wv-overlay-topleft, .wv-overlay-topleft *, .wv-overlay-topright, .wv-overlay-topright *, .wv-overlay-bottomright, .wv-overlay-bottomright *, .wv-overlay-bottomleft, .wv-overlay-bottomleft * { + background-color: black !important; + -webkit-print-color-adjust: exact !important; + color-adjust: exact !important; + color: orange !important; } + .tooltip { + display: none !important; } + body { + margin: 0; + padding: 0; + position: relative; + width: 8.5in; + height: 11in; } + body, body * { + background-color: black !important; + -webkit-print-color-adjust: exact !important; } } + +.closePrintButton { + display: none; } + +body.print .wvPrintExclude { + display: none; } + +body.print .wvPrintFullPage { + width: 100% !important; + height: 100% !important; + position: absolute !important; + top: 0 !important; + left: 0 !important; + display: block !important; } + +body.print .wvLayout__main { + top: 0 !important; + right: 0 !important; + left: 0 !important; + bottom: 0 !important; } + +body.print .wvPrintViewer { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; } + +body.print .wvPrintViewer canvas { + max-width: 100% !important; + max-height: 100% !important; + margin: auto; } + +body.print .wv-overlay-topleft, body.print .wv-overlay-topleft *, body.print .wv-overlay-topright, body.print .wv-overlay-topright *, body.print .wv-overlay-bottomright, body.print .wv-overlay-bottomright *, body.print .wv-overlay-bottomleft, body.print .wv-overlay-bottomleft * { + background-color: black !important; + -webkit-print-color-adjust: exact !important; + color-adjust: exact !important; + color: orange !important; } + +body.print .tooltip { + display: none !important; } + +body.print body { + margin: 0; + padding: 0; + position: relative; + width: 8.5in; + height: 11in; } + body.print body, body.print body * { + background-color: black !important; + -webkit-print-color-adjust: exact !important; } + +@media screen { + body.print .closePrintButton { + display: block; + position: fixed; + top: 0; + right: 0; + padding: 10px; + font-size: 24px; + background-color: black; + color: white; + border: none; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebApplication/app.js Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,614 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + **/ + + +var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ]; +var SERIES_INSTANCE_UID = '0020,000e'; +var STUDY_INSTANCE_UID = '0020,000d'; +var STUDY_DESCRIPTION = '0008,1030'; +var STUDY_DATE = '0008,0020'; + + +function getParameterFromUrl(key) { + var url = window.location.search.substring(1); + var args = url.split('&'); + for (var i = 0; i < args.length; i++) { + var arg = args[i].split('='); + if (arg[0] == key) { + return arg[1]; + } + } +} + + +Vue.component('viewport', { + props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex', + 'quality', 'framesCount', 'currentFrame', 'showInfo' ], + template: '#viewport-template', + data: function () { + return { + stone: stone, // To access global object "stone" from "index.html" + status: 'waiting' + } + }, + watch: { + series: function(newVal, oldVal) { + this.status = 'loading'; + + var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID]; + var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID]; + stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid); + + if ((newVal.type == stone.ThumbnailType.IMAGE || + newVal.type == stone.ThumbnailType.NO_PREVIEW) && + newVal.complete) { + this.status = 'ready'; + + var that = this; + Vue.nextTick(function() { + stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid); + }); + } + else if (newVal.type == stone.ThumbnailType.PDF || + newVal.type == stone.ThumbnailType.VIDEO) { + // TODO + } + } + }, + methods: { + SeriesDragAccept: function(event) { + event.preventDefault(); + }, + SeriesDragDrop: function(event) { + event.preventDefault(); + this.$emit('updated-series', event.dataTransfer.getData('seriesIndex')); + }, + MakeActive: function() { + this.$emit('selected-viewport'); + }, + DecrementFrame: function() { + stone.DecrementFrame(this.canvasId); + }, + IncrementFrame: function() { + stone.IncrementFrame(this.canvasId); + } + } +}) + + +var app = new Vue({ + el: '#wv', + data: function() { + return { + stone: stone, // To access global object "stone" from "index.html" + ready: false, + leftMode: 'grid', // Can be 'small', 'grid' or 'full' + leftVisible: true, + showWarning: false, + viewportLayoutButtonsVisible: false, + activeViewport: 0, + showInfo: true, + showReferenceLines: true, + + viewport1Width: '100%', + viewport1Height: '100%', + viewport1Left: '0%', + viewport1Top: '0%', + viewport1Visible: true, + viewport1Series: {}, + viewport1Quality: '', + viewport1FramesCount: 0, + viewport1CurrentFrame: 0, + + viewport2Width: '100%', + viewport2Height: '100%', + viewport2Left: '0%', + viewport2Top: '0%', + viewport2Visible: false, + viewport2Series: {}, + viewport2Quality: '', + viewport2FramesCount: 0, + viewport2CurrentFrame: 0, + + viewport3Width: '100%', + viewport3Height: '100%', + viewport3Left: '0%', + viewport3Top: '0%', + viewport3Visible: false, + viewport3Series: {}, + viewport3Quality: '', + viewport3FramesCount: 0, + viewport3CurrentFrame: 0, + + viewport4Width: '100%', + viewport4Height: '100%', + viewport4Left: '0%', + viewport4Top: '0%', + viewport4Visible: false, + viewport4Series: {}, + viewport4Quality: '', + viewport4FramesCount: 0, + viewport4CurrentFrame: 0, + + series: [], + studies: [], + seriesIndex: {} // Maps "SeriesInstanceUID" to "index in this.series" + } + }, + computed: { + getSelectedStudies() { + var s = ''; + for (var i = 0; i < this.studies.length; i++) { + if (this.studies[i].selected) { + if (s.length > 0) + s += ', '; + s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' + + this.studies[i].tags[STUDY_DATE] + ']'); + } + } + if (s.length == 0) + return '...'; + else + return s; + } + }, + watch: { + leftVisible: function(newVal, oldVal) { + this.FitContent(); + }, + showReferenceLines: function(newVal, oldVal) { + stone.ShowReferenceLines(newVal ? 1 : 0); + } + }, + methods: { + FitContent() { + // This function can be used even if WebAssembly is not initialized yet + if (typeof stone._AllViewportsUpdateSize !== 'undefined') { + this.$nextTick(function () { + stone.AllViewportsUpdateSize(true /* fit content */); + }); + } + }, + + GetActiveSeries() { + var s = []; + + if ('tags' in this.viewport1Series) + s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]); + + if ('tags' in this.viewport2Series) + s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]); + + if ('tags' in this.viewport3Series) + s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]); + + if ('tags' in this.viewport4Series) + s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]); + + return s; + }, + + GetActiveCanvas() { + if (this.activeViewport == 1) { + return 'canvas1'; + } + else if (this.activeViewport == 2) { + return 'canvas2'; + } + else if (this.activeViewport == 3) { + return 'canvas3'; + } + else if (this.activeViewport == 4) { + return 'canvas4'; + } + else { + return 'canvas1'; + } + }, + + SetResources: function(sourceStudies, sourceSeries) { + var indexStudies = {}; + + var studies = []; + var posColor = 0; + + for (var i = 0; i < sourceStudies.length; i++) { + var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID]; + if (studyInstanceUid !== undefined) { + if (studyInstanceUid in indexStudies) { + console.error('Twice the same study: ' + studyInstanceUid); + } else { + indexStudies[studyInstanceUid] = studies.length; + + studies.push({ + 'studyInstanceUid' : studyInstanceUid, + 'series' : [ ], + 'color' : COLORS[posColor], + 'selected' : true, + 'tags' : sourceStudies[i] + }); + + posColor = (posColor + 1) % COLORS.length; + } + } + } + + var series = []; + var seriesIndex = {}; + + for (var i = 0; i < sourceSeries.length; i++) { + var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID]; + var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID]; + if (studyInstanceUid !== undefined && + seriesInstanceUid !== undefined) { + if (studyInstanceUid in indexStudies) { + seriesIndex[seriesInstanceUid] = series.length; + var study = studies[indexStudies[studyInstanceUid]]; + study.series.push(i); + series.push({ + //'length' : 4, + 'complete' : false, + 'type' : stone.ThumbnailType.LOADING, + 'color': study.color, + 'tags': sourceSeries[i] + }); + } + } + } + + this.studies = studies; + this.series = series; + this.seriesIndex = seriesIndex; + this.ready = true; + }, + + SeriesDragStart: function(event, seriesIndex) { + event.dataTransfer.setData('seriesIndex', seriesIndex); + }, + + SetViewportSeries: function(viewportIndex, seriesIndex) { + var series = this.series[seriesIndex]; + + if (viewportIndex == 1) { + this.viewport1Series = series; + } + else if (viewportIndex == 2) { + this.viewport2Series = series; + } + else if (viewportIndex == 3) { + this.viewport3Series = series; + } + else if (viewportIndex == 4) { + this.viewport4Series = series; + } + }, + + ClickSeries: function(seriesIndex) { + this.SetViewportSeries(this.activeViewport, seriesIndex); + }, + + HideViewport: function(index) { + if (index == 1) { + this.viewport1Visible = false; + } + else if (index == 2) { + this.viewport2Visible = false; + } + else if (index == 3) { + this.viewport3Visible = false; + } + else if (index == 4) { + this.viewport4Visible = false; + } + }, + + ShowViewport: function(index, left, top, width, height) { + if (index == 1) { + this.viewport1Visible = true; + this.viewport1Left = left; + this.viewport1Top = top; + this.viewport1Width = width; + this.viewport1Height = height; + } + else if (index == 2) { + this.viewport2Visible = true; + this.viewport2Left = left; + this.viewport2Top = top; + this.viewport2Width = width; + this.viewport2Height = height; + } + else if (index == 3) { + this.viewport3Visible = true; + this.viewport3Left = left; + this.viewport3Top = top; + this.viewport3Width = width; + this.viewport3Height = height; + } + else if (index == 4) { + this.viewport4Visible = true; + this.viewport4Left = left; + this.viewport4Top = top; + this.viewport4Width = width; + this.viewport4Height = height; + } + }, + + SetViewportLayout: function(layout) { + this.viewportLayoutButtonsVisible = false; + if (layout == '1x1') { + this.ShowViewport(1, '0%', '0%', '100%', '100%'); + this.HideViewport(2); + this.HideViewport(3); + this.HideViewport(4); + } + else if (layout == '2x2') { + this.ShowViewport(1, '0%', '0%', '50%', '50%'); + this.ShowViewport(2, '50%', '0%', '50%', '50%'); + this.ShowViewport(3, '0%', '50%', '50%', '50%'); + this.ShowViewport(4, '50%', '50%', '50%', '50%'); + } + else if (layout == '2x1') { + this.ShowViewport(1, '0%', '0%', '50%', '100%'); + this.ShowViewport(2, '50%', '0%', '50%', '100%'); + this.HideViewport(3); + this.HideViewport(4); + } + else if (layout == '1x2') { + this.ShowViewport(1, '0%', '0%', '100%', '50%'); + this.ShowViewport(2, '0%', '50%', '100%', '50%'); + this.HideViewport(3); + this.HideViewport(4); + } + + this.FitContent(); + }, + + UpdateSeriesThumbnail: function(seriesInstanceUid) { + if (seriesInstanceUid in this.seriesIndex) { + var index = this.seriesIndex[seriesInstanceUid]; + var series = this.series[index]; + + var type = stone.LoadSeriesThumbnail(seriesInstanceUid); + series.type = type; + + if (type == stone.ThumbnailType.IMAGE) { + series.thumbnail = stone.GetStringBuffer(); + } + + // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating + this.$set(this.series, index, series); + } + }, + + UpdateIsSeriesComplete: function(seriesInstanceUid) { + if (seriesInstanceUid in this.seriesIndex) { + var index = this.seriesIndex[seriesInstanceUid]; + var series = this.series[index]; + + series.complete = stone.IsSeriesComplete(seriesInstanceUid); + + // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating + this.$set(this.series, index, series); + + if ('tags' in this.viewport1Series && + this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport1Series, series); + } + + if ('tags' in this.viewport2Series && + this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport2Series, series); + } + + if ('tags' in this.viewport3Series && + this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport3Series, series); + } + + if ('tags' in this.viewport4Series && + this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { + this.$set(this.viewport4Series, series); + } + } + }, + + SetWindowing(center, width) { + var canvas = this.GetActiveCanvas(); + if (canvas != '') { + stone.SetWindowing(canvas, center, width); + } + }, + + SetDefaultWindowing() { + var canvas = this.GetActiveCanvas(); + if (canvas != '') { + stone.SetDefaultWindowing(canvas); + } + }, + + InvertContrast() { + var canvas = this.GetActiveCanvas(); + if (canvas != '') { + stone.InvertContrast(canvas); + } + } + }, + + mounted: function() { + this.SetViewportLayout('1x1'); + } +}); + + + +window.addEventListener('StoneInitialized', function() { + stone.Setup(Module); + stone.SetOrthancRoot('..', true); + console.warn('Native Stone properly intialized'); + + var study = getParameterFromUrl('study'); + var series = getParameterFromUrl('series'); + + if (study === undefined) { + alert('No study was provided in the URL!'); + } else { + if (series === undefined) { + console.warn('Loading study: ' + study); + stone.FetchStudy(study); + } else { + console.warn('Loading series: ' + series + ' from study ' + study); + stone.FetchSeries(study, series); + app.leftMode = 'full'; + } + } +}); + + +window.addEventListener('ResourcesLoaded', function() { + console.log('resources loaded'); + + var studies = []; + for (var i = 0; i < stone.GetStudiesCount(); i++) { + stone.LoadStudyTags(i); + studies.push(JSON.parse(stone.GetStringBuffer())); + } + + var series = []; + for (var i = 0; i < stone.GetSeriesCount(); i++) { + stone.LoadSeriesTags(i); + series.push(JSON.parse(stone.GetStringBuffer())); + } + + app.SetResources(studies, series); + + for (var i = 0; i < app.series.length; i++) { + var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID]; + app.UpdateSeriesThumbnail(seriesInstanceUid); + app.UpdateIsSeriesComplete(seriesInstanceUid); + } +}); + + +window.addEventListener('ThumbnailLoaded', function(args) { + //var studyInstanceUid = args.detail.studyInstanceUid; + var seriesInstanceUid = args.detail.seriesInstanceUid; + app.UpdateSeriesThumbnail(seriesInstanceUid); +}); + + +window.addEventListener('MetadataLoaded', function(args) { + //var studyInstanceUid = args.detail.studyInstanceUid; + var seriesInstanceUid = args.detail.seriesInstanceUid; + app.UpdateIsSeriesComplete(seriesInstanceUid); +}); + + +window.addEventListener('FrameUpdated', function(args) { + var canvasId = args.detail.canvasId; + var framesCount = args.detail.framesCount; + var currentFrame = (args.detail.currentFrame + 1); + var quality = args.detail.quality; + + if (canvasId == 'canvas1') { + app.viewport1CurrentFrame = currentFrame; + app.viewport1FramesCount = framesCount; + app.viewport1Quality = quality; + } + else if (canvasId == 'canvas2') { + app.viewport2CurrentFrame = currentFrame; + app.viewport2FramesCount = framesCount; + app.viewport2Quality = quality; + } + else if (canvasId == 'canvas3') { + app.viewport3CurrentFrame = currentFrame; + app.viewport3FramesCount = framesCount; + app.viewport3Quality = quality; + } + else if (canvasId == 'canvas4') { + app.viewport4CurrentFrame = currentFrame; + app.viewport4FramesCount = framesCount; + app.viewport4Quality = quality; + } +}); + + +window.addEventListener('StoneException', function() { + console.error('Exception catched in Stone'); +}); + + + + + + +$(document).ready(function() { + // Enable support for tooltips in Bootstrap + //$('[data-toggle="tooltip"]').tooltip(); + + //app.showWarning = true; + + + $('#windowing-popover').popover({ + container: 'body', + content: $('#windowing-content').html(), + template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>', + placement: 'auto', + html: true, + sanitize: false, + trigger: 'focus' // Close on click + }); + + + var wasmSource = 'StoneWebViewer.js'; + + // Option 1: Loading script using plain HTML + + /* + var script = document.createElement('script'); + script.src = wasmSource; + script.type = 'text/javascript'; + document.body.appendChild(script); + */ + + // Option 2: Loading script using AJAX (gives the opportunity to + // report explicit errors) + + axios.get(wasmSource) + .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'); + }); +}); + + +// "Prevent Bootstrap dropdown from closing on clicks" for the list of +// studies: https://stackoverflow.com/questions/26639346 +$('.dropdown-menu').click(function(e) { + e.stopPropagation(); +}); + + +// Disable the selection of text using the mouse +document.onselectstart = new Function ('return false');
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Applications/StoneWebViewer/WebApplication/index.html Tue Aug 11 13:24:38 2020 +0200 @@ -0,0 +1,488 @@ +<!doctype html> +<html class="wv-html"> + <head> + <title>Stone Web Viewer</title> + <meta charset="utf-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> + <link rel="icon" href="data:;base64,iVBORw0KGgo="> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css"> + <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> + <link rel="stylesheet" href="app.css"> + + <!-- https://stackoverflow.com/a/16863182/881731 --> + <style> + .tooltip { + position: fixed; + } + </style> + + <!-- Fix if Bootstrap CSS is not used --> + <!--style> + *, + *::before, + *::after { + box-sizing: border-box; + } + </style--> + </head> + <body class="wv-body"> + <div id="wv"> + <div class="wvLoadingScreen" v-show="!ready"> + <span class="wvLoadingSpinner"> + <div class="bounce1"></div> + <div class="bounce2"></div> + <div class="bounce3"></div> + </span> + </div> + + <div class="fluid-height fluid-width" v-show="ready"> + + <div class="wvWarning wvPrintExclude" v-show="showWarning"> + <div class="wvWarning-content clearfix"> + <span class="wvWarning-text"> + <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2> + <p class="mn mb10" style="color:#000"> + You browser is not supported. You might expect + inconsistent behaviours and must not use the viewer to + produce a diagnostic. + </p> + </span> + </div> + <div class="text-right mb10 mr10"> + <button class="btn btn-primary" @click="showWarning=false">OK</button> + </div> + </div> + + + <div class="wvLayoutLeft wvLayoutLeft--closed" v-show="!leftVisible"> + <div class="wvLayoutLeft__actions--outside" style="z-index:10"> + <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center" + @click="leftVisible = true"> + <i class="fa fa-angle-double-right"></i> + </button> + </div> + </div> + + + <div class="wvLayoutLeft" v-show="leftVisible" + v-bind:class="{ 'wvLayoutLeft--small': leftMode == 'small' }" + > + <div class="wvLayoutLeft__actions" style="z-index:10"> + <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center" + @click="leftVisible = false"> + <i class="fa fa-angle-double-left"></i> + </button> + </div> + <div class="wvLayoutLeft__content"> + <div class="wvLayoutLeft__contentTop"> + <div class="float__left dropdown" style="max-width: calc(100% - 4.5rem); height:4.5rem !important" v-show="leftMode != 'small'"> + <button type="button" class="wvButton--border" data-toggle="dropdown"> + {{ getSelectedStudies }} + <span class="caret"></span> + </button> + <ul class="dropdown-menu checkbox-menu allow-focus"> + <li v-for="study in studies" + v-bind:class="{ active: study.selected }" + @click="study.selected = !study.selected"> + <a> + {{ study.tags['0008,1030'] }} + <span v-if="study.selected"> <i class="fa fa-check"></i></span> + </a> + </li> + </ul> + </div> + + <div class="float__right wvButton" v-if="leftMode == 'grid'" @click="leftMode = 'full'"> + <i class="fa fa-th-list"></i> + </div> + <div class="float__right wvButton" v-if="leftMode == 'full'" @click="leftMode = 'small'"> + <i class="fa fa-ellipsis-v"></i> + </div> + <div class="float__right wvButton" v-if="leftMode == 'small'" @click="leftMode = 'grid'"> + <i class="fa fa-th"></i> + </div> + + <p class="clear disclaimer mbn">For patients, teachers and researchers.</p> + </div> + <div class="wvLayoutLeft__contentMiddle"> + + <div v-for="study in studies"> + <div v-if="study.selected"> + <div v-bind:class="'wvStudyIsland--' + study.color"> + <div v-bind:class="'wvStudyIsland__header--' + study.color"> + <!-- Actions --> + <div class="wvStudyIsland__actions" + v-bind:class="{ 'wvStudyIsland__actions--oneCol': leftMode == 'small' }"> + <a class="wvButton"> + <!-- download --> + <i class="fa fa-download"></i> + </a> + </div> + + <!-- Title --> + {{ study.tags['0008,1030'] }} + <br/> + <small>{{ study.tags['0008,0020'] }}</small> + </div> + + <div class="wvStudyIsland__main"> + <ul class="wvSerieslist"> + <li class="wvSerieslist__seriesItem" + v-bind:class="{ highlighted : GetActiveSeries().includes(series[seriesIndex].tags['0020,000e']), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }" + v-on:dragstart="SeriesDragStart($event, seriesIndex)" + v-on:click="ClickSeries(seriesIndex)" + v-for="seriesIndex in study.series"> + <div class="wvSerieslist__picture" style="z-index:0" + draggable="true" + v-if="series[seriesIndex].type != stone.ThumbnailType.UNKNOWN" + > + <div v-if="series[seriesIndex].type == stone.ThumbnailType.LOADING"> + <img src="img/loading.gif" + style="vertical-align:baseline" + width="65px" height="65px" + /> + </div> + + <i v-if="series[seriesIndex].type == stone.ThumbnailType.PDF" + class="wvSerieslist__placeholderIcon fa fa-file-text"></i> + + <i v-if="series[seriesIndex].type == stone.ThumbnailType.VIDEO" + class="wvSerieslist__placeholderIcon fa fa-video-camera"></i> + + + <div v-if="[stone.ThumbnailType.IMAGE, stone.ThumbnailType.NO_PREVIEW].includes(series[seriesIndex].type)" + class="wvSerieslist__placeholderIcon" + v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']"> + <i v-if="series[seriesIndex].type == stone.ThumbnailType.NO_PREVIEW" + class="fa fa-eye-slash"></i> + + <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE" + v-bind:src="series[seriesIndex].thumbnail" + style="vertical-align:baseline" + width="65px" height="65px" + v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']" + /> + + <div v-bind:class="'wvSerieslist__badge--' + study.color" + v-if="'length' in series[seriesIndex]">{{ series[seriesIndex].length }}</div> + </div> + </div> + + <div v-if="leftMode == 'full'" class="wvSerieslist__information" + draggable="true" + v-on:dragstart="SeriesDragStart($event, seriesIndex)" + v-on:click="ClickSeries(seriesIndex)"> + <p class="wvSerieslist__label"> + [{{ series[seriesIndex].tags['0008,0060'] }}] + {{ series[seriesIndex].tags['0008,103e'] }} + </p> + </div> + </li> + </ul> + </div> + </div> + </div> + </div> + + </div> + <div class="wvLayoutLeft__contentBottom"> + </div> + </div> + </div> + <div class="wvLayout__main" + v-bind:class="{ 'wvLayout__main--smallleftpadding': leftVisible && leftMode == 'small', 'wvLayout__main--leftpadding': leftVisible && leftMode != 'small' }" + > + + <div class="wvToolbar wvToolbar--top"> + <div class="ng-scope inline-object"> + <div class="tbGroup"> + <div class="tbGroup__toggl"> + <button class="wvButton" + v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible }" + @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible"> + <i class="fa fa-th"></i> + </button> + </div> + + <div class="tbGroup__buttons--bottom" v-show="viewportLayoutButtonsVisible"> + <div class="inline-object"> + <button class="wvButton" @click="SetViewportLayout('1x1')"> + <img src="img/grid1x1.png" style="width:1em;height:1em" /> + </button> + </div> + <div class="inline-object"> + <button class="wvButton" @click="SetViewportLayout('2x1')"> + <img src="img/grid2x1.png" style="width:1em;height:1em" /> + </button> + </div> + <div class="inline-object"> + <button class="wvButton" @click="SetViewportLayout('1x2')"> + <img src="img/grid1x2.png" style="width:1em;height:1em" /> + </button> + </div> + <div class="inline-object"> + <button class="wvButton" @click="SetViewportLayout('2x2')"> + <img src="img/grid2x2.png" style="width:1em;height:1em" /> + </button> + </div> + </div> + </div> + </div> + + <!--div class="ng-scope inline-object"> + <button class="wvButton--underline text-center active"> + <i class="fa fa-hand-pointer-o"></i> + </button> + </div> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center"> + <i class="fa fa-search"></i> + </button> + </div> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center"> + <i class="fa fa-arrows"></i> + </button> + </div--> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center" + v-on:click="InvertContrast()"> + <i class="fa fa-adjust"></i> + </button> + </div> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center" id="windowing-popover"> + <i class="fa fa-sun-o"></i> + </button> + </div> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center" + v-bind:class="{ 'active' : showInfo }" + v-on:click="showInfo = !showInfo"> + <i class="fa fa-info-circle"></i> + </button> + </div> + + <div class="ng-scope inline-object"> + <button class="wvButton--underline text-center" + v-bind:class="{ 'active' : showReferenceLines }" + v-on:click="showReferenceLines = !showReferenceLines"> + <i class="fa fa-bars"></i> + </button> + </div> + </div> + + + <div class="wvLayout__splitpane--toolbarAtTop"> + <div id="viewport" class="wvSplitpane"> + <viewport v-on:updated-series="SetViewportSeries(1, $event)" + v-on:selected-viewport="activeViewport=1" + v-show="viewport1Visible" + canvas-id="canvas1" + v-bind:series="viewport1Series" + v-bind:left="viewport1Left" + v-bind:top="viewport1Top" + v-bind:width="viewport1Width" + v-bind:height="viewport1Height" + v-bind:quality="viewport1Quality" + v-bind:current-frame="viewport1CurrentFrame" + v-bind:frames-count="viewport1FramesCount" + v-bind:show-info="showInfo" + v-bind:active="activeViewport==1"></viewport> + <viewport v-on:updated-series="SetViewportSeries(2, $event)" + v-on:selected-viewport="activeViewport=2" + v-show="viewport2Visible" + canvas-id="canvas2" + v-bind:series="viewport2Series" + v-bind:left="viewport2Left" + v-bind:top="viewport2Top" + v-bind:width="viewport2Width" + v-bind:height="viewport2Height" + v-bind:quality="viewport2Quality" + v-bind:current-frame="viewport2CurrentFrame" + v-bind:frames-count="viewport2FramesCount" + v-bind:show-info="showInfo" + v-bind:active="activeViewport==2"></viewport> + <viewport v-on:updated-series="SetViewportSeries(3, $event)" + v-on:selected-viewport="activeViewport=3" + v-show="viewport3Visible" + canvas-id="canvas3" + v-bind:series="viewport3Series" + v-bind:left="viewport3Left" + v-bind:top="viewport3Top" + v-bind:width="viewport3Width" + v-bind:height="viewport3Height" + v-bind:quality="viewport3Quality" + v-bind:current-frame="viewport3CurrentFrame" + v-bind:frames-count="viewport3FramesCount" + v-bind:show-info="showInfo" + v-bind:active="activeViewport==3"></viewport> + <viewport v-on:updated-series="SetViewportSeries(4, $event)" + v-on:selected-viewport="activeViewport=4" + v-show="viewport4Visible" + canvas-id="canvas4" + v-bind:series="viewport4Series" + v-bind:left="viewport4Left" + v-bind:top="viewport4Top" + v-bind:width="viewport4Width" + v-bind:height="viewport4Height" + v-bind:quality="viewport4Quality" + v-bind:current-frame="viewport4CurrentFrame" + v-bind:frames-count="viewport4FramesCount" + v-bind:show-info="showInfo" + v-bind:active="activeViewport==4"></viewport> + </div> + </div> + + </div> + </div> + </div> + + + + <script type="text/x-template" id="windowing-content"> + <p class="wvToolbar__windowingPresetConfigNotice"> + Click on the button to toggle the windowing tool or apply a preset to the selected viewport. + </p> + + <ul class="wvToolbar__windowingPresetList"> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetDefaultWindowing()"> + Default + </a> + </li> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetWindowing(-400, 1600)"> + CT Lung <small>(L -400, W 1,600)</small> + </a> + </li> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetWindowing(300, 1500)"> + CT Abdomen <small>(L 300, W 1,500)</small> + </a> + </li> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetWindowing(40, 80)"> + CT Bone <small>(L 40, W 80)</small> + </a> + </li> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetWindowing(40, 400)"> + CT Brain <small>(L 40, W 400)</small> + </a> + </li> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetWindowing(-400, 1600)"> + CT Chest <small>(L -400, W 1,600)</small> + </a> + </li> + <li class="wvToolbar__windowingPresetListItem"> + <a href="#" onclick="app.SetWindowing(300, 600)"> + CT Angio <small>(L 300, W 600)</small> + </a> + </li> + </ul> + </script> + + + <script type="text/x-template" id="viewport-template"> + <div v-bind:style="{ padding:'2px', + position:'absolute', + left: left, + top: top, + width: width, + height: height }"> + <div v-bind:class="{ 'wvSplitpane__cellBorder--selected' : active, + 'wvSplitpane__cellBorder' : series.color == '', + 'wvSplitpane__cellBorder--blue' : series.color == 'blue', + 'wvSplitpane__cellBorder--red' : series.color == 'red', + 'wvSplitpane__cellBorder--green' : series.color == 'green', + 'wvSplitpane__cellBorder--yellow' : series.color == 'yellow', + 'wvSplitpane__cellBorder--violet' : series.color == 'violet' + }" + v-on:dragover="SeriesDragAccept($event)" + v-on:drop="SeriesDragDrop($event)" + style="width:100%;height:100%"> + <div class="wvSplitpane__cell" + v-on:click="MakeActive()"> + <div v-show="status == 'ready'" + style="position:absolute; left:0; top:0; width:100%; height:100%"> + <!--div style="width: 100%; height: 100%; background-color: red"></div--> + <canvas v-bind:id="canvasId" + style="position:absolute; left:0; top:0; width:100%; height:100%" + oncontextmenu="return false"></canvas> + + <div v-if="'tags' in series" v-show="showInfo"> + <div class="wv-overlay"> + <div class="wv-overlay-topleft"> + {{ series.tags['0010,0010'] }}<br/> + {{ series.tags['0010,0020'] }} + </div> + <div class="wv-overlay-topright"> + {{ series.tags['0008,1030'] }}<br/> + {{ series.tags['0008,0020'] }}<br/> + {{ series.tags['0020,0011'] }} | {{ series.tags['0008,103e'] }} + </div> + <div class="wv-overlay-bottomleft" + v-show="framesCount != 0"> + <button class="btn btn-primary" @click="DecrementFrame()"> + <i class="fa fa-chevron-circle-left"></i> + </button> + {{ currentFrame }} / {{ framesCount }} + <button class="btn btn-primary" @click="IncrementFrame()"> + <i class="fa fa-chevron-circle-right"></i> + </button> + </div> + <div class="wv-overlay-bottomright"> + <div v-show="quality == stone.DisplayedFrameQuality.NONE" + style="display:block;background-color:red;width:1em;height:1em" /> + <div v-show="quality == stone.DisplayedFrameQuality.LOW" + style="display:block;background-color:orange;width:1em;height:1em" /> + <div v-show="quality == stone.DisplayedFrameQuality.HIGH" + style="display:block;background-color:green;width:1em;height:1em" /> + </div> + </div> + </div> + </div> + + <div v-if="status == 'waiting'" class="wvPaneOverlay"> + [ drop a series here ] + </div> + + <!--div v-if="status == 'video'" class="wvPaneOverlay"> + <video class="wvVideo" autoplay="" loop="" controls="" preload="auto" type="video/mp4" + src="http://viewer-pro.osimis.io/instances/e465dd27-83c96343-96848735-7035a133-1facf1a0/frames/0/raw"> + </video> + </div--> + + <div v-if="status == 'loading'" class="wvPaneOverlay"> + <span class="wvLoadingSpinner"> + <div class="bounce1"></div> + <div class="bounce2"></div> + <div class="bounce3"></div> + </span> + </div> + </div> + </div> + </div> + </script> + + + <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> + + <script src="stone.js"></script> + <script src="app.js"></script> + </body> +</html>
--- /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 + )
--- /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
--- /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 <http://www.gnu.org/licenses/>. + + +# 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 +}))
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include <emscripten.h> + + +#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 <Cache/MemoryObjectCache.h> +#include <DicomFormat/DicomArray.h> +#include <DicomParsing/Internals/DicomImageDecoder.h> +#include <Images/Image.h> +#include <Images/ImageProcessing.h> +#include <Images/JpegReader.h> +#include <Logging.h> + +#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 <boost/make_shared.hpp> +#include <stdio.h> + + +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<ResourcesLoader> +{ +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<IObserver> observer_; + OrthancStone::DicomSource source_; + size_t pending_; + boost::shared_ptr<OrthancStone::LoadedDicomResources> studies_; + boost::shared_ptr<OrthancStone::LoadedDicomResources> series_; + boost::shared_ptr<OrthancStone::DicomResourcesLoader> resourcesLoader_; + boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader> thumbnailsLoader_; + boost::shared_ptr<OrthancStone::SeriesMetadataLoader> 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<Orthanc::ResourceType>& payload = + dynamic_cast<const Orthanc::SingleValueObject<Orthanc::ResourceType>&>(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<Orthanc::DicomTag> 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>(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>(Orthanc::ResourceType_Series)); + + pending_ += 2; + } + +public: + static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::ILoadersContext::ILock& lock, + const OrthancStone::DicomSource& source) + { + boost::shared_ptr<ResourcesLoader> 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<OrthancStone::DicomResourcesLoader::SuccessMessage>( + *loader->resourcesLoader_, &ResourcesLoader::Handle); + + loader->Register<OrthancStone::SeriesThumbnailsLoader::SuccessMessage>( + *loader->thumbnailsLoader_, &ResourcesLoader::Handle); + + loader->Register<OrthancStone::SeriesMetadataLoader::SuccessMessage>( + *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<Orthanc::ImageAccessor> 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<std::string>(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<Orthanc::ImageAccessor> 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<const CachedImage&>(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<CachedImage&>(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<size_t> 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<int, Action> > Queue; + + Queue queue; + std::set<int> 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<size_t>(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<size_t>(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<double>(columns), + oy + pixelSpacingY_ * static_cast<double>(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<ViewerViewport> +{ +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<ViewerViewport> viewport_; + + public: + ICommand(boost::shared_ptr<ViewerViewport> 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<ViewerViewport> 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<ViewerViewport> 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<Orthanc::JpegReader> 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<Orthanc::Image> 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<ViewerViewport> 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<Orthanc::ImageAccessor> 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<Orthanc::ImageAccessor> 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<IObserver> observer_; + OrthancStone::ILoadersContext& context_; + boost::shared_ptr<OrthancStone::WebGLViewport> viewport_; + boost::shared_ptr<OrthancStone::DicomResourcesLoader> loader_; + OrthancStone::DicomSource source_; + boost::shared_ptr<FramesCache> cache_; + std::unique_ptr<OrthancStone::SortedFrames> frames_; + std::unique_ptr<SeriesCursor> cursor_; + float windowingCenter_; + float windowingWidth_; + float defaultWindowingCenter_; + float defaultWindowingWidth_; + bool inverted_; + bool fitNextContent_; + bool isCtrlDown_; + FrameGeometry currentFrameGeometry_; + std::list<PrefetchItem> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::FloatTextureSceneLayer&>(scene.GetLayer(LAYER_TEXTURE)); + layer.GetWindowing(windowingCenter_, windowingWidth_); + } + } + + quality = accessor.GetQuality(); + + std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer; + + switch (accessor.GetImage().GetFormat()) + { + case Orthanc::PixelFormat_RGB24: + layer.reset(new OrthancStone::ColorTextureSceneLayer(accessor.GetImage())); + break; + + case Orthanc::PixelFormat_Float32: + { + std::unique_ptr<OrthancStone::FloatTextureSceneLayer> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::ILoadersContext::ILock> 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<std::string>(frame + 1) + "/rendered"); + + std::map<std::string, std::string> headers, arguments; + arguments["window"] = ( + boost::lexical_cast<std::string>(windowingCenter_) + "," + + boost::lexical_cast<std::string>(windowingWidth_) + ",linear"); + + std::unique_ptr<OrthancStone::IOracleCommand> command( + source_.CreateDicomWebCommand( + uri, arguments, headers, new SetLowQualityFrame( + GetSharedObserver(), sopInstanceUid, frame, + windowingCenter_, windowingWidth_, isMonochrome1, isPrefetch))); + + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock()); + lock->Schedule(GetSharedObserver(), priority, command.release()); + } + } + } + + ViewerViewport(OrthancStone::ILoadersContext& context, + const OrthancStone::DicomSource& source, + const std::string& canvas, + boost::shared_ptr<FramesCache> 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<ViewerViewport*>(userData); + that.isCtrlDown_ = event->ctrlKey; + return false; + } + + + static EM_BOOL OnWheel(int eventType, + const EmscriptenWheelEvent *wheelEvent, + void *userData) + { + ViewerViewport& that = *reinterpret_cast<ViewerViewport*>(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<const ICommand&>(message.GetUserPayload()).Handle(message); + } + + void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) + { + dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message); + } + + void Handle(const OrthancStone::ParseDicomSuccessMessage& message) + { + dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message); + } + +public: + static boost::shared_ptr<ViewerViewport> Create(OrthancStone::ILoadersContext::ILock& lock, + const OrthancStone::DicomSource& source, + const std::string& canvas, + boost::shared_ptr<FramesCache> cache) + { + boost::shared_ptr<ViewerViewport> viewport( + new ViewerViewport(lock.GetContext(), source, canvas, cache)); + + viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(lock); + viewport->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>( + *viewport->loader_, &ViewerViewport::Handle); + + viewport->Register<OrthancStone::HttpCommand::SuccessMessage>( + lock.GetOracleObservable(), &ViewerViewport::Handle); + + viewport->Register<OrthancStone::ParseDicomSuccessMessage>( + 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<OrthancStone::LoadedDicomResources>(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<OrthancStone::IViewport::ILock> 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<const FrameGeometry*>& planes) + { + std::unique_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer); + + if (GetCurrentFrameGeometry().IsValid()) + { + for (std::list<const FrameGeometry*>::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<OrthancStone::IViewport::ILock> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); + + if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) && + lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() == + OrthancStone::ISceneLayer::Type_FloatTexture) + { + dynamic_cast<OrthancStone::FloatTextureSceneLayer&>( + lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)). + SetCustomWindowing(windowingCenter_, windowingWidth_); + lock->Invalidate(); + } + } + } + + void Invert() + { + inverted_ = !inverted_; + + { + std::unique_ptr<OrthancStone::IViewport::ILock> 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<OrthancStone::FloatTextureSceneLayer&>( + 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<std::string, boost::shared_ptr<ViewerViewport> > Viewports; +static Viewports allViewports_; +static bool showReferenceLines_ = true; + + +static void UpdateReferenceLines() +{ + if (showReferenceLines_) + { + std::list<const FrameGeometry*> 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<int>(currentFrame), + static_cast<int>(countFrames), + quality); + + + UpdateReferenceLines(); + }; +}; + + + +static OrthancStone::DicomSource source_; +static boost::shared_ptr<FramesCache> cache_; +static boost::shared_ptr<OrthancStone::WebAssemblyLoadersContext> 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> resourcesLoader_; + + if (!resourcesLoader_) + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock()); + resourcesLoader_ = ResourcesLoader::Create(*lock, source_); + resourcesLoader_->AcquireObserver(new WebAssemblyObserver); + } + + return *resourcesLoader_; +} + + +static boost::shared_ptr<ViewerViewport> GetViewport(const std::string& canvas) +{ + Viewports::iterator found = allViewports_.find(canvas); + if (found == allViewports_.end()) + { + std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock()); + boost::shared_ptr<ViewerViewport> 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<OrthancStone::SortedFrames> 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; + } +}
--- /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/
--- /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
--- /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<>".
--- 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<>".
--- 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 <http://www.gnu.org/licenses/>. - **/ - -// 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 <Logging.h> -#include <OrthancException.h> - -// System -#include <boost/shared_ptr.hpp> -#include <boost/weak_ptr.hpp> -#include <boost/make_shared.hpp> - -#include <stdio.h> - - -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<DicomVolumeImage>(); - doseVolume_ = boost::make_shared<DicomVolumeImage>(); - } - - boost::shared_ptr<RtViewerApp> RtViewerApp::Create() - { - boost::shared_ptr<RtViewerApp> 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<RtViewerView> - 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<DicomVolumeImage::GeometryReadyMessage> - (*ctLoader_, &RtViewerApp::HandleGeometryReady); - - Register<OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality> - (*ctLoader_, &RtViewerApp::HandleCTLoaded); - - Register<DicomVolumeImage::ContentUpdatedMessage> - (*ctLoader_, &RtViewerApp::HandleCTContentUpdated); - - Register<DicomVolumeImage::ContentUpdatedMessage> - (*doseLoader_, &RtViewerApp::HandleDoseLoaded); - - Register<DicomStructureSetLoader::StructuresReady> - (*rtstructLoader_, &RtViewerApp::HandleStructuresReady); - - Register<DicomStructureSetLoader::StructuresUpdated> - (*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 : <unspecified>"; - } - else - { - LOG(INFO) << " CT : " << GetArgument("ctseries"); - ctLoader_->LoadSeries(GetArgument("ctseries")); - } - - if (GetArgument("rtdose") == "") - { - LOG(INFO) << " RTDOSE : <unspecified>"; - } - else - { - LOG(INFO) << " RTDOSE : " << GetArgument("rtdose"); - doseLoader_->LoadInstance(GetArgument("rtdose")); - } - - if (GetArgument("rtstruct") == "") - { - LOG(INFO) << " RTSTRUCT : : <unspecified>"; - } - 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<std::string, std::string>::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()); - } -} -
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <boost/enable_shared_from_this.hpp> -#include <boost/thread.hpp> -#include <boost/noncopyable.hpp> - -#if ORTHANC_ENABLE_SDL -#include <SDL.h> -#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<RtViewerApp> - { - public: - - void PrepareScene(); - -#if ORTHANC_ENABLE_SDL - public: - void RunSdl(int argc, char* argv[]); - void SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& 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<RtViewerApp> 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<std::string, std::string> 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<TrackerCommand> 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<DicomVolumeImage> ctVolume_; - boost::shared_ptr<DicomVolumeImage> doseVolume_; - - std::vector<boost::shared_ptr<RtViewerView> > views_; - - boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader_; - boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader_; - boost::shared_ptr<DicomStructureSetLoader> rtstructLoader_; - - /** encapsulates resources shared by loaders */ - boost::shared_ptr<ILoadersContext> 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<OrthancStone::IGeometryProvider> geometryProvider_; - - - boost::shared_ptr<IFlexiblePointerTracker> activeTracker_; - - boost::shared_ptr<UndoStack> undoStack_; - }; - -} - - -
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -// Sample app -#include "RtViewerView.h" -#include "RtViewerApp.h" -#include "SampleHelpers.h" - -#include <EmbeddedResources.h> - -// 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 <Compatibility.h> // For std::unique_ptr<> -#include <Logging.h> -#include <OrthancException.h> - -// System -#include <boost/shared_ptr.hpp> -#include <boost/weak_ptr.hpp> -#include <boost/make_shared.hpp> - -#include <stdio.h> - - -namespace OrthancStone -{ - boost::shared_ptr<RtViewerApp> RtViewerView::GetApp() - { - return app_.lock(); - } - - void RtViewerView::DisplayInfoText() - { - std::unique_ptr<IViewport::ILock> 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<std::string, std::string>::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<TextSceneLayer&>( - scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX)); - layerP = &layer; - } - else - { - std::unique_ptr<TextSceneLayer> 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<IViewport::ILock> 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<TextSceneLayer&>(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX)); - layer.SetText(buf); - layer.SetPosition(p.GetX(), p.GetY()); - } - else - { - std::unique_ptr<TextSceneLayer> 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<IViewport::ILock> 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<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - - void RtViewerView::FitContent() - { - std::unique_ptr<OrthancStone::IViewport::ILock> 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<int>(currentPlane_) + delta; - - if (temp < 0) - { - next = 0; - } - else if (temp >= static_cast<int>(planes_.size())) - { - next = static_cast<unsigned int>(planes_.size()) - 1; - } - else - { - next = static_cast<size_t>(temp); - } - LOG(INFO) << "RtViewerView::Scroll(" << delta << ") --> slice is now = " << next; - - if (next != static_cast<int>(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<IViewport::ILock> 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<IViewport::ILock> 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<IViewport::ILock> lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Register<ViewportController::SceneTransformChanged>(controller, &RtViewerView::OnSceneTransformChanged); - } - - void RtViewerView::CreateLayers(boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader, - boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader, - boost::shared_ptr<DicomVolumeImage> doseVolume, - boost::shared_ptr<DicomStructureSetLoader> rtstructLoader) - { - /** - Configure the CT - */ - std::unique_ptr<GrayscaleStyleConfigurator> 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<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator); - config->SetLookupTable(lut); - - boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(doseVolume)); - this->SetDoseVolumeSlicer(tmp, config.release()); - } - - this->SetStructureSet(rtstructLoader); - } - - void RtViewerView::SetCtVolumeSlicer(const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, - OrthancStone::ILayerStyleConfigurator* style) - { - std::unique_ptr<IViewport::ILock> 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<OrthancStone::IVolumeSlicer>& volume, - OrthancStone::ILayerStyleConfigurator* style) - { - std::unique_ptr<IViewport::ILock> 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<OrthancStone::DicomStructureSetLoader>& volume) - { - std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); - ViewportController& controller = lock->GetController(); - Scene2D& scene = controller.GetScene(); - int depth = scene.GetMaxDepth() + 1; - - structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(viewport_, depth, volume)); - } -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <boost/enable_shared_from_this.hpp> -#include <boost/thread.hpp> -#include <boost/noncopyable.hpp> - -namespace OrthancStone -{ - class RtViewerApp; - - class RtViewerView : public ObserverBase<RtViewerView> - { - public: - RtViewerView(boost::weak_ptr<RtViewerApp> 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<OrthancSeriesVolumeProgressiveLoader> ctLoader, - boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader, - boost::shared_ptr<DicomVolumeImage> doseVolume, - boost::shared_ptr<DicomStructureSetLoader> rtstructLoader); - - boost::shared_ptr<IViewport> GetViewport() - { - return viewport_; - } - - int GetCtLayerIndex() const - { - return ctLayer_; - } - - private: - void SetInfoDisplayMessage(std::string key, std::string value); - boost::shared_ptr<RtViewerApp> GetApp(); - boost::shared_ptr<IViewport> CreateViewport(const std::string& canvasId); - void DisplayInfoText(); - void HideInfoText(); - void DisplayFloatingCtrlInfoText(const PointerEvent& e); - - void SetCtVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume, - ILayerStyleConfigurator* style); - - void SetDoseVolumeSlicer(const boost::shared_ptr<IVolumeSlicer>& volume, - ILayerStyleConfigurator* style); - - void SetStructureSet(const boost::shared_ptr<DicomStructureSetLoader>& volume); - - private: - boost::weak_ptr<RtViewerApp> app_; - boost::shared_ptr<VolumeSceneLayerSource> ctVolumeLayerSource_, doseVolumeLayerSource_, structLayerSource_; - - // collection of cutting planes for this particular view - std::vector<OrthancStone::CoordinateSystem3D> planes_; - size_t currentPlane_; - - VolumeProjection projection_; - - std::map<std::string, std::string> infoTextMap_; - - int FLOATING_INFOTEXT_LAYER_ZINDEX; - int FIXED_INFOTEXT_LAYER_ZINDEX; - boost::shared_ptr<IViewport> viewport_; - - int ctLayer_; - }; -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include <Logging.h> - -#include <boost/algorithm/string.hpp> - -#include <string> -#include <iostream> - -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); - } - } -}
--- 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 `<REPO-ROOT>/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 `<REPO-ROOT>/out/install-stone-wasm-RtViewer-RelWithDebInfo` to `<REPO-ROOT>/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. -
--- 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 - )
--- 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 = $('<a>') - .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); - }); - } - }); - } - }); - } - }); -}); -
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" - -#include <EmbeddedResources.h> - -#include <SystemToolbox.h> -#include <Toolbox.h> - -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<OrthancPluginErrorCode>(e.GetErrorCode()); - } - - return OrthancPluginErrorCode_Success; -} - -template <enum Orthanc::EmbeddedResources::DirectoryResourceId folder> -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 <enum Orthanc::EmbeddedResources::FileResourceId file> -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 - <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_JS> > - ("/stone-rtviewer/RtViewerWasm.js", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM> > - ("/stone-rtviewer/RtViewerWasm.wasm", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_WASM_APP_JS> > - ("/stone-rtviewer/RtViewerWasmApp.js", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFile<Orthanc::EmbeddedResources::RT_VIEWER_INDEX_HTML> > - ("/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; - } -}
--- 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
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "OrthancPluginCppWrapper.h" - -#include <boost/algorithm/string/predicate.hpp> -#include <boost/move/unique_ptr.hpp> -#include <boost/thread.hpp> -#include <json/reader.h> -#include <json/writer.h> - - -#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<uint32_t>(size); - if (static_cast<size_t>(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<const char*>(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<const char*>(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<std::string, std::string>& httpHeaders, - bool applyPlugins) - { - Clear(); - - std::vector<const char*> headersKeys; - std::vector<const char*> headersValues; - - for (std::map<std::string, std::string>::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<const char*>(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<const char*>(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<unsigned int>(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<float>(configuration_[key].asInt()); - return true; - - case Json::uintValue: - target = static_cast<float>(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<std::string>& 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<std::string>& target, - const std::string& key, - bool allowSingleString) const - { - std::list<std::string> lst; - - if (LookupListOfStrings(lst, key, allowSingleString)) - { - target.clear(); - - for (std::list<std::string>::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<std::string, std::string>& 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<std::string, std::string>& 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<std::string>(major) + "." + - boost::lexical_cast<std::string>(minor) + "." + - boost::lexical_cast<std::string>(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<unsigned int>(aa); - unsigned int b = static_cast<unsigned int>(bb); - unsigned int c = static_cast<unsigned int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<OrthancJob*>(job); - } - } - - - float OrthancJob::CallbackGetProgress(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast<OrthancJob*>(job)->progress_; - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetContent(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetSerialized(void* job) - { - assert(job != NULL); - - try - { - const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(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<OrthancJob*>(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<OrthancJob*>(job)->Stop(reason); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) - { - assert(job != NULL); - - try - { - reinterpret_cast<OrthancJob*>(job)->Reset(); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<Orthanc::ErrorCode>(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<OrthancJob> 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<float>(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<RequestBodyWrapper*>(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<uint32_t>(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<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddHeader(key, value); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<const char*> headersKeys_; - std::vector<const char*> 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<std::string*> 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<const char*>(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<IChunkedRequestReader*>(reader)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<IChunkedRequestReader*>(reader)->Execute(output); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - void ChunkedRequestReaderFinalize( - OrthancPluginServerChunkedRequestReader* reader) - { - if (reader != NULL) - { - delete reinterpret_cast<IChunkedRequestReader*>(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<IChunkedRequestReader> 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<IChunkedRequestReader> 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<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(rawHandler); - *target = handler.Lookup(sopClassUid, sopInstanceUid); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(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<OrthancPluginDicomInstance*>(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<DicomInstance> result(new DicomInstance(instance)); - result->toFree_ = true; - return result.release(); - } - } -#endif -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "OrthancPluginException.h" - -#include <orthanc/OrthancCPlugin.h> -#include <boost/noncopyable.hpp> -#include <boost/lexical_cast.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> -#include <json/value.h> -#include <vector> -#include <list> -#include <set> -#include <map> - - - -/** - * 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<const char*>(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<std::string, std::string>& 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<std::string>& target, - const std::string& key, - bool allowSingleString) const; - - bool LookupSetOfStrings(std::set<std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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 <RestCallback Callback> - 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<OrthancPluginErrorCode>(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - } - - - template <RestCallback Callback> - void RegisterRestCallback(const std::string& uri, - bool isThreadSafe) - { - if (isThreadSafe) - { - OrthancPluginRegisterRestCallbackNoLock - (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); - } - else - { - OrthancPluginRegisterRestCallback - (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); - } - } - - -#if HAS_ORTHANC_PLUGIN_PEERS == 1 - class OrthancPeers : public boost::noncopyable - { - private: - typedef std::map<std::string, uint32_t> 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<std::string, std::string> 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 <ChunkedRestCallback Callback> - static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, - const char* url, - const OrthancPluginHttpRequest* request) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - else - { - *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request)); - if (*reader == NULL) - { - return OrthancPluginErrorCode_Plugin; - } - else - { - return OrthancPluginErrorCode_Success; - } - } - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<GetHandler>, - PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>, - DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect<DeleteHandler>, - PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PutHandler>, - Internals::ChunkedRequestReaderAddChunk, - Internals::ChunkedRequestReaderExecute, - Internals::ChunkedRequestReaderFinalize); -#else - OrthancPluginRegisterRestCallbackNoLock( - GetGlobalContext(), uri.c_str(), - Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>); -#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<size_t>(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 - }; -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if !defined(HAS_ORTHANC_EXCEPTION) -# error The macro HAS_ORTHANC_EXCEPTION must be defined -#endif - - -#if HAS_ORTHANC_EXCEPTION == 1 -# include <OrthancException.h> -# 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 <orthanc/OrthancCPlugin.h> -# 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<ORTHANC_PLUGINS_ERROR_ENUMERATION>(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 -}
--- 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 <http://www.gnu.org/licenses/>. - - -# 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()
--- 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: - *; -};
--- 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: - * - * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: - * 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(). - * -# <tt>void OrthancPluginFinalize()</tt>: - * This function is invoked by Orthanc during its shutdown. The plugin - * must free all its memory. - * -# <tt>const char* OrthancPluginGetName()</tt>: - * The plugin must return a short string to identify itself. - * -# <tt>const char* OrthancPluginGetVersion()</tt>: - * 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 <tt>extern "C"</tt> 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 <http://www.gnu.org/licenses/>. - **/ - - - -#pragma once - - -#include <stdio.h> -#include <string.h> - -#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 <stdint.h> - -#include <stdlib.h> - - - -/******************************************************************** - ** 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 ("<tt>n</tt>"). - * @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 - - -/** @} */ -
--- 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 <http://www.gnu.org/licenses/>. - - -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()
--- 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" - "$<TARGET_FILE_DIR:UnitTests>/72c773ac-5059f2c4-2e6a9120-4fd4bca1-45701661.json" -) - -target_link_libraries(UnitTests OrthancStone ${DCMTK_LIBRARIES})
--- 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})
--- 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
--- 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 <http://www.gnu.org/licenses/>. - **/ - -#include "../../Common/RtViewerApp.h" -#include "../../Common/RtViewerView.h" -#include "../SdlHelpers.h" - -#include <EmbeddedResources.h> - -// 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 <Compatibility.h> // For std::unique_ptr<> -#include <Images/Image.h> -#include <Images/ImageProcessing.h> -#include <Images/PngWriter.h> - - -#include <boost/program_options.hpp> -#include <boost/shared_ptr.hpp> - -// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions - -#include <SDL.h> - -#include <string> - - -#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<IViewport> 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<std::string>()->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<std::string>()->default_value("http://localhost:8042"), - "Base URL of the Orthanc instance") - - ("ctseries", po::value<std::string>()->default_value("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"), - "Orthanc ID of the CT series to load. This must be supplied.") - - ("rtdose", po::value<std::string>()->default_value("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"), - "Orthanc ID of the RTDOSE instance to load. This may be an empty string.") - - ("rtstruct", po::value<std::string>()->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<std::string>(); - 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<GenericLoadersContext> loadersContext = boost::dynamic_pointer_cast<GenericLoadersContext>(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<IViewport::ILock> 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<OrthancStone::RtViewerView> GetViewFromWindowId( - const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views, - Uint32 windowID) - { - using namespace OrthancStone; - for (size_t i = 0; i < views.size(); ++i) - { - boost::shared_ptr<OrthancStone::RtViewerView> view = views[i]; - boost::shared_ptr<IViewport> viewport = view->GetViewport(); - boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport); - Uint32 curWindowID = sdlViewport->GetSdlWindowId(); - if (windowID == curWindowID) - return view; - } - return boost::shared_ptr<OrthancStone::RtViewerView>(); - } - - void RtViewerApp::SdlRunLoop(const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views, - OrthancStone::DefaultViewportInteractor& interactor) - { - using namespace OrthancStone; - - // const std::vector<boost::shared_ptr<OrthancStone::RtViewerView> >& views - std::vector<boost::shared_ptr<OrthancStone::SdlViewport> > viewports; - for (size_t i = 0; i < views.size(); ++i) - { - boost::shared_ptr<RtViewerView> view = views[i]; - boost::shared_ptr<IViewport> viewport = view->GetViewport(); - boost::shared_ptr<SdlViewport> sdlViewport = - boost::dynamic_pointer_cast<SdlViewport>(viewport); - viewports.push_back(sdlViewport); - } - - { - int scancodeCount = 0; - const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount); - - bool stop = false; - while (!stop) - { - std::vector<SDL_Event> sdlEvents; - std::map<Uint32,SDL_Event> 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<Uint32,SDL_Event>::const_iterator it = userEventsMap.begin(); it != userEventsMap.end(); ++it) - sdlEvents.push_back(it->second); - - // now process the events - for (std::vector<SDL_Event>::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<RtViewerView> view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - boost::shared_ptr<SdlViewport> sdlViewport = - boost::dynamic_pointer_cast<SdlViewport>(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<RtViewerView> view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - boost::shared_ptr<SdlViewport> sdlViewport = - boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); - sdlViewport->Paint(); - } - else if (sdlEvent.type == SDL_KEYDOWN && - sdlEvent.key.repeat == 0 /* Ignore key bounce */) - { - boost::shared_ptr<RtViewerView> view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - switch (sdlEvent.key.keysym.sym) - { - case SDLK_f: - { - boost::shared_ptr<SdlViewport> sdlViewport = - boost::dynamic_pointer_cast<SdlViewport>(view->GetViewport()); - sdlViewport->ToggleMaximize(); - } - break; - - case SDLK_s: - { - std::unique_ptr<OrthancStone::IViewport::ILock> 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<RtViewerView> view = GetViewFromWindowId( - views, sdlEvent.window.windowID); - - std::unique_ptr<OrthancStone::IViewport::ILock> 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<RtViewerView> 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> sdlViewport = - boost::dynamic_pointer_cast<SdlViewport>(views[i]->GetViewport()); - if (sdlViewport->IsRefreshEvent(sdlEvent)) - { - sdlViewport->Paint(); - } - } - } - } - // Small delay to avoid using 100% of CPU - SDL_Delay(1); - } - } - } -} - -boost::weak_ptr<OrthancStone::RtViewerApp> 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<RtViewerApp> app = RtViewerApp::Create(); - g_app = app; - app->RunSdl(argc,argv); - } - catch (Orthanc::OrthancException& e) - { - LOG(ERROR) << "EXCEPTION: " << e.What(); - } - - StoneFinalize(); - - return 0; -} -
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <boost/shared_ptr.hpp> - -#include <SDL.h> - -#include <map> -#include <string> - -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<KeyboardModifiers>(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<OrthancStone::SdlViewport> GetSdlViewportFromWindowId( - const std::vector<boost::shared_ptr<OrthancStone::SdlViewport> >& viewports, - Uint32 windowID) - { - using namespace OrthancStone; - for (size_t i = 0; i < viewports.size(); ++i) - { - boost::shared_ptr<IViewport> viewport = viewports[i]; - boost::shared_ptr<SdlViewport> sdlViewport = boost::dynamic_pointer_cast<SdlViewport>(viewport); - Uint32 curWindowID = sdlViewport->GetSdlWindowId(); - if (windowID == curWindowID) - return sdlViewport; - } - - return boost::shared_ptr<OrthancStone::SdlViewport>(); - } -} - -
--- 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}) -
--- 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
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <Compatibility.h> // For std::unique_ptr<> -#include <OrthancException.h> - -#include <boost/program_options.hpp> -#include <SDL.h> - -#include <string> - - -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<std::string>()->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<std::string>()->default_value("http://localhost:8042"), - "Base URL of the Orthanc instance") - - ("instance", po::value<std::string>()->default_value("285dece8-e1956b38-cdc7d084-6ce3371e-536a9ffc"), - "Orthanc ID of the instance to display") - - ("frame_index", po::value<int>()->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<std::string>(); - OrthancStoneHelpers::SetLogLevel(logLevel); - } - - if (vm.count("orthanc") > 0) - { - // maybe check URL validity here - orthancUrl = vm["orthanc"].as<std::string>(); - } - - if (vm.count("instance") > 0) - { - instanceId = vm["instance"].as<std::string>(); - } - - if (vm.count("frame_index") > 0) - { - frameIndex = vm["frame_index"].as<int>(); - } - -} - -/** - * 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<OrthancStone::SdlViewport> viewport = - OrthancStone::SdlOpenGLViewport::Create("Stone of Orthanc", 800, 600); -#else - boost::shared_ptr<OrthancStone::SdlViewport> 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<SdlSimpleViewerApplication> 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<OrthancStone::IViewport::ILock> 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; - } -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <Compatibility.h> // For std::unique_ptr<> - -#include <boost/make_shared.hpp> - - -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<SdlSimpleViewerApplication> -{ - -public: - static boost::shared_ptr<SdlSimpleViewerApplication> Create(ILoadersContext& context, boost::shared_ptr<IViewport> viewport) - { - boost::shared_ptr<SdlSimpleViewerApplication> application(new SdlSimpleViewerApplication(context, viewport)); - - { - std::unique_ptr<ILoadersContext::ILock> lock(context.Lock()); - application->dicomLoader_ = DicomResourcesLoader::Create(*lock); - } - - application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &SdlSimpleViewerApplication::Handle); - - return application; - } - - void LoadOrthancFrame(const DicomSource& source, const std::string& instanceId, unsigned int frame) - { - std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); - - dicomLoader_->ScheduleLoadOrthancResource(boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), - 0, source, Orthanc::ResourceType_Instance, instanceId, - new Orthanc::SingleValueObject<unsigned int>(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<ILoadersContext::ILock> lock(context_.Lock()); - - // We first must load the "/metadata" to know the number of frames - dicomLoader_->ScheduleGetDicomWeb( - boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, - "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", - new Orthanc::SingleValueObject<unsigned int>(frame)); - } -#endif - - void FitContent() - { - std::unique_ptr<IViewport::ILock> lock(viewport_->Lock()); - lock->GetCompositor().FitContent(lock->GetController().GetScene()); - lock->Invalidate(); - } - -private: - ILoadersContext& context_; - boost::shared_ptr<IViewport> viewport_; - boost::shared_ptr<DicomResourcesLoader> dicomLoader_; - boost::shared_ptr<SeriesFramesLoader> framesLoader_; - - SdlSimpleViewerApplication(ILoadersContext& context, - boost::shared_ptr<IViewport> 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<TextureBaseSceneLayer> layer( - message.GetInstanceParameters().CreateTexture(message.GetImage())); - layer->SetLinearInterpolation(true); - - { - std::unique_ptr<IViewport::ILock> 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<ILoadersContext::ILock> lock(context_.Lock()); - SeriesFramesLoader::Factory f(*message.GetResources()); - - framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>( - f.Create(*lock)); - - Register<SeriesFramesLoader::FrameLoadedMessage>( - *framesLoader_, &SdlSimpleViewerApplication::Handle); - - assert(message.HasUserPayload()); - - const Orthanc::SingleValueObject<unsigned int>& payload = - dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>( - 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); - } - } - -}; -
--- 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: <<OK>>") -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: <<OK>>") -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() -
--- 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/ - )
--- 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 -
--- 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} - )
--- 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 <http://www.gnu.org/licenses/>. - **/ - -#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 <OrthancException.h> - -#include <emscripten/html5.h> -#include <emscripten.h> - -#include <boost/make_shared.hpp> - - -class ViewportManager; - -static const unsigned int FONT_SIZE = 32; - -boost::shared_ptr<OrthancStone::DicomVolumeImage> ct_(new OrthancStone::DicomVolumeImage); -boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_; -std::unique_ptr<ViewportManager> widget1_; -std::unique_ptr<ViewportManager> widget2_; -std::unique_ptr<ViewportManager> widget3_; -//OrthancStone::MessageBroker broker_; -//OrthancStone::WebAssemblyOracle oracle_(broker_); -std::unique_ptr<OrthancStone::IFlexiblePointerTracker> tracker_; -static std::map<std::string, std::string> 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<OrthancStone::PointerEvent> 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<OrthancStone::WebGLViewport>& viewport = - *reinterpret_cast<boost::shared_ptr<OrthancStone::WebGLViewport>*>(userData); - - std::unique_ptr<OrthancStone::IViewport::ILock> 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<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer); - layer->SetText(buf); - scene.SetLayer(100, layer.release()); - lock->Invalidate(); - break; - } - - case EMSCRIPTEN_EVENT_MOUSEDOWN: - { - boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; - - { - std::unique_ptr<OrthancStone::PointerEvent> 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<OrthancStone::PointerEvent> event( - ConvertMouseEvent(*mouseEvent, controller->GetViewport())); - tracker_->PointerMove(*event); - controller->GetViewport().Refresh(); - } - break; - - case EMSCRIPTEN_EVENT_MOUSEUP: - if (tracker_.get() != NULL) - { - std::unique_ptr<OrthancStone::PointerEvent> 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<OrthancStone::WebGLViewport>& 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<ViewportManager> - { - private: - OrthancStone::WebAssemblyViewport viewport_; - std::unique_ptr<VolumeSceneLayerSource> source_; - VolumeProjection projection_; - std::vector<CoordinateSystem3D> 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<IVolumeSlicer>& 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<ViewportManager, DicomVolumeImage::GeometryReadyMessage> - (*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<int>(currentPlane_) + delta; - unsigned int next; - - if (tmp < 0) - { - next = 0; - } - else if (tmp >= static_cast<int>(planes_.size())) - { - next = planes_.size() - 1; - } - else - { - next = static_cast<size_t>(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<OrthancStone::ViewportManager*>(userData); - - if (ctrlDown_) - { - delta *= static_cast<int>(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<TestSleep, SleepOracleCommand::TimeoutMessage> - (*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<std::string, std::string>::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<OrthancStone::GrayscaleStyleConfigurator> 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<OrthancStone::GrayscaleStyleConfigurator> 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<OrthancStone::GrayscaleStyleConfigurator> 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(); - } - } -} -
--- 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 <http://www.gnu.org/licenses/>. - **/ - -#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 <Toolbox.h> - -#include <boost/program_options.hpp> -#include <boost/shared_ptr.hpp> -// #include <boost/pointer_cast.hpp> this include might be necessary in more recent boost versions - -#include <emscripten.h> -#include <emscripten/html5.h> - - -#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<RtViewerView*>(userData); - - int delta = 0; - if (wheelEvent->deltaY < 0) - delta = -1; - if (wheelEvent->deltaY > 0) - delta = 1; - - that->Scroll(delta); - - return 1; - } - - boost::shared_ptr<IViewport> RtViewerView::CreateViewport( - const std::string& canvasId) - { - boost::shared_ptr<IViewport> viewport = WebGLViewport::Create(canvasId); - - void* userData = reinterpret_cast<void*>(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<WebAssemblyLoadersContext> loadersContext = - boost::dynamic_pointer_cast<WebAssemblyLoadersContext>(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<OrthancStone::RtViewerApp> 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); - } - -}
--- 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 -
--- 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(); -
--- 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 @@ -<!doctype html> -<html lang="en-us"> - <head> - <title>Stone of Orthanc Single Frame Viewer </title> - <meta charset="utf-8" /> - <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> - <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - <meta name="apple-mobile-web-app-capable" content="yes" /> - <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> - <link rel="icon" href="data:;base64,iVBORw0KGgo="> - - <!-- Disable pinch zoom on mobile devices --> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> - <meta name="HandheldFriendly" content="true" /> - - <style> - html, body { - width: 100%; - height: 100%; - margin: 0px; - border: 0; - overflow: hidden; /* Disable scrollbars */ - display: block; /* No floating content on sides */ - } - - #RtViewer_Axial { - position: absolute; - left: 0%; - top: 0%; - background-color: red; - width: 50%; - height: 100%; - } - - #RtViewer_Coronal { - position: absolute; - left: 50%; - top: 0%; - background-color: green; - width: 50%; - height: 50%; - } - - #RtViewer_Sagittal { - position: absolute; - left: 50%; - top: 50%; - background-color: blue; - width: 50%; - height: 50%; - } - </style> - </head> - <body> - <canvas id="RtViewer_Axial" oncontextmenu="return false;"></canvas> - <canvas id="RtViewer_Coronal" oncontextmenu="return false;"></canvas> - <canvas id="RtViewer_Sagittal" oncontextmenu="return false;"></canvas> - - <script src="https://code.jquery.com/jquery-3.4.1.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> - - <script src="RtViewerWasmApp.js"></script> - </body> -</html>
--- 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} - )
--- 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
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "SingleFrameViewerApplication.h" - -#include "../../../Sources/Loaders/WebAssemblyLoadersContext.h" -#include "../../../Sources/StoneException.h" -#include "../../../Sources/StoneInitialization.h" - -#include <Compatibility.h> // For std::unique_ptr<> -#include <Toolbox.h> - -#include <emscripten.h> -#include <emscripten/html5.h> - - -#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<OrthancStone::WebAssemblyLoadersContext> context_; -static boost::shared_ptr<OrthancStone::Application> 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<OrthancStone::WebGLViewport> 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; - } -}
--- 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()); -});
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <Compatibility.h> // For std::unique_ptr<> - -#include <boost/make_shared.hpp> - - -namespace OrthancStone -{ - class Application : public ObserverBase<Application> - { - private: - ILoadersContext& context_; - boost::shared_ptr<IViewport> viewport_; - boost::shared_ptr<DicomResourcesLoader> dicomLoader_; - boost::shared_ptr<SeriesFramesLoader> framesLoader_; - - Application(ILoadersContext& context, - boost::shared_ptr<IViewport> 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<TextureBaseSceneLayer> layer( - message.GetInstanceParameters().CreateTexture(message.GetImage())); - layer->SetLinearInterpolation(true); - - { - std::unique_ptr<IViewport::ILock> 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<ILoadersContext::ILock> lock(context_.Lock()); - SeriesFramesLoader::Factory f(*message.GetResources()); - - framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock)); - Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle); - - assert(message.HasUserPayload()); - const Orthanc::SingleValueObject<unsigned int>& payload = - dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(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<Application> Create(ILoadersContext& context, - boost::shared_ptr<IViewport> viewport) - { - boost::shared_ptr<Application> application(new Application(context, viewport)); - - { - std::unique_ptr<ILoadersContext::ILock> lock(context.Lock()); - application->dicomLoader_ = DicomResourcesLoader::Create(*lock); - } - - application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle); - - return application; - } - - void LoadOrthancFrame(const DicomSource& source, - const std::string& instanceId, - unsigned int frame) - { - std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); - - dicomLoader_->ScheduleLoadOrthancResource( - boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), - 0, source, Orthanc::ResourceType_Instance, instanceId, - new Orthanc::SingleValueObject<unsigned int>(frame)); - } - - void LoadDicomWebFrame(const DicomSource& source, - const std::string& studyInstanceUid, - const std::string& seriesInstanceUid, - const std::string& sopInstanceUid, - unsigned int frame) - { - std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock()); - - // We first must load the "/metadata" to know the number of frames - dicomLoader_->ScheduleGetDicomWeb( - boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source, - "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata", - new Orthanc::SingleValueObject<unsigned int>(frame)); - } - - void FitContent() - { - std::unique_ptr<IViewport::ILock> 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<WebViewerLoaders> - { - 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<IWebViewerLoadersObserver> observer_; - bool loadThumbnails_; - DicomSource source_; - std::set<std::string> scheduledSeries_; - std::set<std::string> scheduledThumbnails_; - std::set<std::string> scheduledStudies_; - boost::shared_ptr<LoadedDicomResources> loadedSeries_; - boost::shared_ptr<LoadedDicomResources> loadedStudies_; - boost::shared_ptr<DicomResourcesLoader> resourcesLoader_; - boost::shared_ptr<SeriesThumbnailsLoader> thumbnailsLoader_; - - WebViewerLoaders(ILoadersContext& context, - IWebViewerLoadersObserver* observer) : - context_(context), - observer_(observer), - loadThumbnails_(false) - { - loadedSeries_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); - loadedStudies_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID); - } - - static Orthanc::IDynamicObject* CreatePayload(Type type) - { - return new Orthanc::SingleValueObject<Type>(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<const Orthanc::SingleValueObject<Type>&>(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<WebViewerLoaders> Create(ILoadersContext& context, - const DicomSource& source, - bool loadThumbnails, - IWebViewerLoadersObserver* observer) - { - boost::shared_ptr<WebViewerLoaders> application(new WebViewerLoaders(context, observer)); - application->source_ = source; - application->loadThumbnails_ = loadThumbnails; - - { - std::unique_ptr<ILoadersContext::ILock> lock(context.Lock()); - - application->resourcesLoader_ = DicomResourcesLoader::Create(*lock); - - { - SeriesThumbnailsLoader::Factory f; - f.SetPriority(PRIORITY_THUMBNAILS); - application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock)); - } - - application->Register<OrthancRestApiCommand::SuccessMessage>( - lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi); - - application->Register<DicomResourcesLoader::SuccessMessage>( - *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources); - - application->Register<SeriesThumbnailsLoader::SuccessMessage>( - *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail); - - lock->AddLoader(application); - } - - return application; - } - - void AddDicomAllSeries() - { - std::unique_ptr<ILoadersContext::ILock> 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<OrthancRestApiCommand> 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<Orthanc::DicomTag> tags; - - { - std::unique_ptr<ILoadersContext::ILock> 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<OrthancRestApiCommand> 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<ILoadersContext::ILock> 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<Orthanc::DicomTag> tags; - - std::unique_ptr<ILoadersContext::ILock> 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<OrthancRestApiCommand> 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<ILoadersContext::ILock> 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<ILoadersContext::ILock> 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"); - } - } - }; -}
--- 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 @@ -<!doctype html> -<html lang="en"> - <head> - <title>Stone of Orthanc Single Frame Viewer </title> - <meta charset="utf-8" /> - <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> - <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - <meta name="apple-mobile-web-app-capable" content="yes" /> - <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> - <link rel="icon" href="data:;base64,iVBORw0KGgo="> - - <style> - canvas { - background-color: green; - width : 100%; - height : 512px; - } - </style> - </head> - <body> - <h1>Viewport</h1> - - <canvas id="viewport" > - </canvas> - - <h1>Load from Orthanc</h1> - <p> - Orthanc instance: <input type="text" id="orthancInstance" size="80" - value="5eb2dd5f-3fca21a8-fa7565fd-63e112ae-344830a4"> - </p> - <p> - Frame number: <input type="text" id="orthancFrame" value="0"> - </p> - <p> - <button id="orthancLoad">Load</button> - </p> - - <script src="https://code.jquery.com/jquery-3.4.1.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> - - <script src="SingleFrameViewerApp.js"></script> - </body> -</html>
--- 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/
--- 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
--- 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
--- 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. <http://fsf.org/> - 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. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - -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 -<http://www.gnu.org/licenses/>.
--- 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 - )
--- 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 = $('<a>') - .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 = $('<a>') - .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); - } - }); - } - }); - } - }); -});
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" - -#include <EmbeddedResources.h> - -#include <SystemToolbox.h> -#include <Toolbox.h> - -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<std::string> 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<int>(tokens[0]); - minor = boost::lexical_cast<int>(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<OrthancPluginErrorCode>(e.GetErrorCode()); - } - - return OrthancPluginErrorCode_Success; -} - - -template <enum Orthanc::EmbeddedResources::DirectoryResourceId folder> -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 <enum Orthanc::EmbeddedResources::FileResourceId file> -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 - <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_WASM> > - ("/stone-webviewer/StoneWebViewer.wasm", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WEB_VIEWER_JS> > - ("/stone-webviewer/StoneWebViewer.js", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFile<Orthanc::EmbeddedResources::STONE_WRAPPER> > - ("/stone-webviewer/stone.js", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFolder<Orthanc::EmbeddedResources::IMAGES> > - ("/stone-webviewer/img/(.*)", true); - - OrthancPlugins::RegisterRestCallback - <ServeEmbeddedFolder<Orthanc::EmbeddedResources::WEB_APPLICATION> > - ("/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; - } -}
--- 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 <http://www.gnu.org/licenses/>. - - -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')
--- 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
--- 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
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "OrthancPluginCppWrapper.h" - -#include <boost/algorithm/string/predicate.hpp> -#include <boost/move/unique_ptr.hpp> -#include <boost/thread.hpp> -#include <json/reader.h> -#include <json/writer.h> - - -#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<uint32_t>(size); - if (static_cast<size_t>(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<const char*>(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<const char*>(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<std::string, std::string>& httpHeaders, - bool applyPlugins) - { - Clear(); - - std::vector<const char*> headersKeys; - std::vector<const char*> headersValues; - - for (std::map<std::string, std::string>::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<const char*>(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<const char*>(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<unsigned int>(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<float>(configuration_[key].asInt()); - return true; - - case Json::uintValue: - target = static_cast<float>(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<std::string>& 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<std::string>& target, - const std::string& key, - bool allowSingleString) const - { - std::list<std::string> lst; - - if (LookupListOfStrings(lst, key, allowSingleString)) - { - target.clear(); - - for (std::list<std::string>::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<std::string, std::string>& 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<std::string, std::string>& 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<std::string>(major) + "." + - boost::lexical_cast<std::string>(minor) + "." + - boost::lexical_cast<std::string>(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<unsigned int>(aa); - unsigned int b = static_cast<unsigned int>(bb); - unsigned int c = static_cast<unsigned int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<OrthancJob*>(job); - } - } - - - float OrthancJob::CallbackGetProgress(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast<OrthancJob*>(job)->progress_; - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetContent(void* job) - { - assert(job != NULL); - - try - { - return reinterpret_cast<OrthancJob*>(job)->content_.c_str(); - } - catch (...) - { - return 0; - } - } - - - const char* OrthancJob::CallbackGetSerialized(void* job) - { - assert(job != NULL); - - try - { - const OrthancJob& tmp = *reinterpret_cast<OrthancJob*>(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<OrthancJob*>(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<OrthancJob*>(job)->Stop(reason); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - OrthancPluginErrorCode OrthancJob::CallbackReset(void* job) - { - assert(job != NULL); - - try - { - reinterpret_cast<OrthancJob*>(job)->Reset(); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<Orthanc::ErrorCode>(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<OrthancJob> 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<float>(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<RequestBodyWrapper*>(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<uint32_t>(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<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddHeader(key, value); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<HttpClient::IAnswer*>(answer)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<const char*> headersKeys_; - std::vector<const char*> 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<std::string*> 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<const char*>(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<IChunkedRequestReader*>(reader)->AddChunk(data, size); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<IChunkedRequestReader*>(reader)->Execute(output); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - - - void ChunkedRequestReaderFinalize( - OrthancPluginServerChunkedRequestReader* reader) - { - if (reader != NULL) - { - delete reinterpret_cast<IChunkedRequestReader*>(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<IChunkedRequestReader> 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<IChunkedRequestReader> 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<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(rawHandler); - *target = handler.Lookup(sopClassUid, sopInstanceUid); - return OrthancPluginErrorCode_Success; - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<IStorageCommitmentScpHandler*>(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<OrthancPluginDicomInstance*>(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<DicomInstance> result(new DicomInstance(instance)); - result->toFree_ = true; - return result.release(); - } - } -#endif -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "OrthancPluginException.h" - -#include <orthanc/OrthancCPlugin.h> -#include <boost/noncopyable.hpp> -#include <boost/lexical_cast.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> -#include <json/value.h> -#include <vector> -#include <list> -#include <set> -#include <map> - - - -/** - * 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<const char*>(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<std::string, std::string>& 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<std::string>& target, - const std::string& key, - bool allowSingleString) const; - - bool LookupSetOfStrings(std::set<std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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 <RestCallback Callback> - 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<OrthancPluginErrorCode>(e.GetErrorCode()); - } - catch (boost::bad_lexical_cast&) - { - return OrthancPluginErrorCode_BadFileFormat; - } - catch (...) - { - return OrthancPluginErrorCode_Plugin; - } - } - } - - - template <RestCallback Callback> - void RegisterRestCallback(const std::string& uri, - bool isThreadSafe) - { - if (isThreadSafe) - { - OrthancPluginRegisterRestCallbackNoLock - (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); - } - else - { - OrthancPluginRegisterRestCallback - (GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>); - } - } - - -#if HAS_ORTHANC_PLUGIN_PEERS == 1 - class OrthancPeers : public boost::noncopyable - { - private: - typedef std::map<std::string, uint32_t> 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<std::string, std::string> 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 <ChunkedRestCallback Callback> - static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader, - const char* url, - const OrthancPluginHttpRequest* request) - { - try - { - if (reader == NULL) - { - return OrthancPluginErrorCode_InternalError; - } - else - { - *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request)); - if (*reader == NULL) - { - return OrthancPluginErrorCode_Plugin; - } - else - { - return OrthancPluginErrorCode_Success; - } - } - } - catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) - { - return static_cast<OrthancPluginErrorCode>(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<GetHandler>, - PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>, - DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect<DeleteHandler>, - PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PutHandler>, - Internals::ChunkedRequestReaderAddChunk, - Internals::ChunkedRequestReaderExecute, - Internals::ChunkedRequestReaderFinalize); -#else - OrthancPluginRegisterRestCallbackNoLock( - GetGlobalContext(), uri.c_str(), - Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>); -#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<size_t>(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 - }; -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if !defined(HAS_ORTHANC_EXCEPTION) -# error The macro HAS_ORTHANC_EXCEPTION must be defined -#endif - - -#if HAS_ORTHANC_EXCEPTION == 1 -# include <OrthancException.h> -# 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 <orthanc/OrthancCPlugin.h> -# 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<ORTHANC_PLUGINS_ERROR_ENUMERATION>(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 -}
--- 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 <http://www.gnu.org/licenses/>. - - -# 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()
--- 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: - *; -};
--- 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: - * - * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>: - * 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(). - * -# <tt>void OrthancPluginFinalize()</tt>: - * This function is invoked by Orthanc during its shutdown. The plugin - * must free all its memory. - * -# <tt>const char* OrthancPluginGetName()</tt>: - * The plugin must return a short string to identify itself. - * -# <tt>const char* OrthancPluginGetVersion()</tt>: - * 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 <tt>extern "C"</tt> 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 <http://www.gnu.org/licenses/>. - **/ - - - -#pragma once - - -#include <stdio.h> -#include <string.h> - -#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 <stdint.h> - -#include <stdlib.h> - - - -/******************************************************************** - ** 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 <tt>NULL</tt> if no password protection). - * @param password The password (can be <tt>NULL</tt> 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 ("<tt>n</tt>"). - * @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 - - -/** @} */ -
--- 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 <a> default styles. Take care - this class may either be used - // with <a> or <button>. - &:hover { - text-decoration: none; - color: white; - } - - // Remove <button> default styles. - outline: none; - background-color: transparent; - border: none; - border-radius: 0; - - // Set relative to position button absolutely - position: relative; - - // Style button - display: inline-block; - cursor: pointer; - font-variant: small-caps; - text-transform: lowercase; - text-align: center; - font-size: 1.3rem; - font-weight: 400; - color: hsl(0, 0%, 85%); - transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; - - // Position button - margin: 0; - min-width: 3rem; - padding: 0 10px; - line-height: 3.6rem; - &.wvLargeButton{ - font-size: 2rem; - line-height: 6.2rem; - padding: 0 20px; - } -} - -.wvButton--rotate { - @extend .wvButton; - // Rotate only the icon - &:before, &:after{ - transform: rotate(90deg); - display: inline-block; - } - -} - -.wvButton--vflip { - @extend .wvButton; - // flip only the icon - &:before, &:after{ - transform: scaleX(-1); - display: inline-block; - } - -} - -// button w/ blue underline -.wvButton--underline, .fa.wvButton--underline { - @extend .wvButton; - - position: relative; - - background-color:inherit; - text-decoration: none; - text-align:left; - font-size: 1.2rem; - &.wvLargeButton{ - font-size: 2rem; - width: 6.4rem; - } - * { - pointer-events: none; - } - &:hover, &:active, &:focus{ - outline:0; - } - - width: 3.2rem; - vertical-align: middle; - color:white; - opacity: 0.75; - border:none; - border-bottom: 2px solid rgba(255,255,255,0.1); - &:hover, &:focus{ - border-color: rgba(255,255,255,1); - opacity:1; - .wvButton__bottomTriangle{ - border-left-color: rgba(255,255,255,1); - &.toggled{ - // border-color: rgba(255, 255, 255, 1); - } - } - } - &.active{ - opacity: 1; - border-color: $primary; - } - - // Make sure the 2px border is not hidden by viewports and other parts of - // the layout (.glyphicon class sets top to 1px) - top: 0px; - - // Compensate glyphicon whitespace - &::before { - position: relative; - top: -1px; - } - - // Adapt font-awesome icon to glyphicon styles - &.fa { - top: 0px; - font-weight: 800; - } -} - -.wvButton__bottomTriangle{ - transition: 0.3s border ease, 0.3s opacity ease; - - display:block; - position: absolute; - bottom:0; - left:0; - - width: 0; - height: 0; - border-style: solid; - border-width: 10px 0 0 10px; - border-color: transparent transparent transparent rgba(255,255,255,0.1); - &.active{ - border-color: transparent transparent transparent $primary !important; - &.toggled{ - border-left-color: $primary !important; - } - } - &.toggled{ - // border-width: 15px 0 0 15px; - } -} - -// button w/ border -.wvButton--border { - @extend .wvButton; - - // Prevent multi line buttons. - max-height: calc(2.8rem - 3px); - max-width: 100%; - overflow: hidden; - - // Set margin - margin: 0.6rem; - margin-left: 0rem; - margin-right: 0rem; - & + & { - margin-left: 0.7rem; - } - - // Set button size - line-height: 2rem; - - // Align text - padding-top: 0.1rem; - padding-bottom: 0.5rem; - - // Style button - font-size: 1.4rem; - border: 1px solid hsl(0, 0%, 27%); - - // Set best looking font with small-caps. - font-family: Arial; - - // Change background on hover - background-color: hsl(0, 0%, 0%); - &:hover { - background-color: hsl(0, 0%, 10%); - } - - & > .glyphicon { // used with the same element as glyphicons - // Position button - position: relative; - display: inline-block; - top: 3px; - margin-right: 4px; - } -} - -// button w/ border + white modifier to use when the background is white. -.wvButton--borderAndWhite { - @extend .wvButton--border; - - // Text color - color: hsl(0, 0%, 10%); - border: 1px solid hsl(0, 0%, 73%); - - // Change background on hover - background-color: hsl(0, 0%, 100%); - &:hover { - color: hsl(0, 0%, 10%); - background-color: hsl(0, 0%, 90%); - } -}
--- a/StoneWebViewer/Resources/Styles/_exitButton.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -.wvExitButton { - margin-left: 1rem; - margin-top: .25rem; - font-size: 1.25em; - color: white; - opacity: .66; - - transition: .3s opacity ease; - background-color: inherit; - border: none; - text-decoration: none; - text-align: left; - padding: 0; - cursor: pointer; - font-family: inherit; - line-height: inherit; - - &:hover, &:focus { - opacity: 1; - outline: 0; - } -} - -.wvExitButton__text { - // Align text with glyph-icon. - position: relative; - top: -1px; -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_helpers.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1115 +0,0 @@ -/* - * Source code taken from private Osimis' frontend toolbox 3.2.1. - */ - -/** - _overlay.scss - */ -.overlay__transparent{ - position:fixed; - top:0; - left:0; - width:100%; - height:100%; - z-index: 1; -} - -/** _transition.scss **/ -.transition, -%transition{ - &{ - transition: 0.3s all ease; - } -} - -.transition--long, -%transition--long{ - &{ - transition: 0.6s all ease; - } -} - - -/** _list.scss **/ -// This is used in the study information panel. -dd+dt{ - clear:both; -} -.listDefinition{ - width:100%; - line-height: 1.3; -} -.listDefinition__term{ - clear:both; - float:left; - text-align: right; - padding-right:10px; - width:50%; - @extend .font__light; - -} -.listDefinition__data{ - text-align: left; - padding-left:10px; - float:right; - width:50%; - @extend .font__normal; - -} - - -/** _animation.scss **/ -@keyframes blink__primary { - 0% { color: $primary; } - 100% { color: $text-color; } -} - -.blink__primary{ - animation: blink__primary 0.8s linear infinite; -} - -[translate-cloak]{ - transition: 0.3s all ease; - opacity: 1; - &.translate-cloak{ - opacity: 0; - } -} - -/** _button.scss **/ -.button__unstyled, -%button__unstyled{ - background-color:inherit; - border:none; - text-decoration: none; - text-align:left; - padding:0; - cursor: pointer; - *{ - pointer-events: none; - } - &:hover, &:active, &:focus{ - outline:0; - } -} -.button__base, -%button__base{ - @extend .button__unstyled; - transition: 0.3s all ease; - //& *,& *:before,& *:after{ - // @extend .transition; - //} -} -.button__state--active{ - @extend .button__base; - opacity:1; - &:hover{ - opacity:0.9; - color:$primary; - } -} -.button__state--inactive{ - @extend .button__base; - opacity:0.333; - &:hover{ - opacity:0.4333; - color:$primary; - } -} -.button__iconed{ - @extend .button__base; - opacity: 1; - &:hover, &:focus, &.active{ - opacity: 0.75; - color:$primary; - } -} -.button__switch--base, -%button__switch--base{ - @extend .button__base; - padding:5px 0px; - display:inline-block; - text-align: center; - border-top: 1px solid; - border-bottom: 1px solid; - border-color: $primary; - background-color: white; - overflow: hidden; - &:hover, &:focus{ - background-color: lighten($primary, 10); - color:white; - } - &.active{ - background-color:$primary; - color:white; - } -} -.button__switch{ - @extend .button__switch--base; -} -.button__switch--first{ - @extend .button__switch--base; - border-left: 1px solid $primary; - border-radius: 5px 0 0 5px; -} -.button__switch--last{ - @extend .button__switch--base; - border-right: 1px solid $primary; - border-radius: 0 5px 5px 0; -} -.button__lightgrey--hover{ - @extend .button__base; - &:hover, &:focus, &.active{ - background-color: $lightGrey; - } -} -.button__text-danger--hover{ - @extend .button__base; - &:hover, &:focus, &.active{ - color: $dangerColor; - } -} -.button__text-primary--hover{ - @extend .button__base; - &:hover, &:focus, &.active{ - color: $primary; - } -} -.button__danger--hover{ - @extend .button__base; - &:hover, &:focus, &.active{ - background-color: $dangerColor; - color:white; - } -} -.button__text{ - @extend .button__base; - opacity:0.66; - &:hover, &.active, &:focus{ - opacity:1; - } -} -.button__text--underlined{ - @extend .button__base; - text-decoration: underline; - &:hover, &.active, &:focus{ - text-decoration:none; - } -} -.button__bordered{ - @extend .button__base; - border-bottom: 2px solid $text-color; - &:hover, &:focus, &.active{ - border-color: $primary; - } -} -.button__bordered--inverted{ - @extend .button__base; - border-bottom: 2px solid white; -} -.button__close{ - @extend .button__base; - position:absolute; - top:0; - right:0; - opacity:0.6; - z-index:10; - &:hover, &:focus{ - opacity:1; - } -} - - -/** _block.scss **/ -.block{ - display: block !important; -} - - -/** _boxsizing.scss **/ -.boxsizing__borderbox{ - box-sizing:border-box; -} -.boxsizing__contentbox{ - box-sizing:content-box; -} - - -/** _scrollable.scss **/ -.scrollable{ - overflow-y:auto; -} -.scrollable--x{ - overflow-x:auto; -} -.no-scroll{ - overflow:hidden; -} - - -/** _float.scss **/ -.float__right, -%float__right{ - float:right; -} -.float__left, -%float__left{ - float:left; -} - - -/** _fonts.scss **/ -.font__bold, -%font__bold{ - font-weight:600; -} -.font__normal, -%font__normal{ - font-weight: 400; -} -.font__light, -%font__light{ - font-weight: 200; -} -.fontColor__primary{ - color:$primary; -} -.fontColor__lightGrey{ - color:$lightGrey; -} -.fontColor__normal{ - color:$text-color; -} -.fontColor__darker{ - color:$darkGrey; -} -.fontColor__white{ - color:white; -} - - -/** _forms.scss **/ -.textarea__unstyled{ - border:none; - outline:none; - background-color:transparent; - color:inherit; - resize:none; - padding:0; - margin:0; - width:100%; -} - - -/** _position.scss **/ -.position__relative{ - position: relative; -} - - -/** _margin.scss **/ -.margin__auto{ - margin:auto; -} - - -/** _helpers.scss **/ -$steps: ( - 0,5,8,10, - 11,12,13,14,15,16,17,18,19,20, - 21,22,23,24,25,26,27,28,29,30, - 31,32,33,34,35,36,37,38,39,40, - 41,42,43,44,45,46,47,48,49,50, - 55,60,64,65,70,72,75,80,85,90,95,96,100, - 110,120,130,140,150,160,170,180,190,200, - 210,220,230,240,250,260,270,280,290,300, - 310,320,330,340,350,360,370,380,390,400, - 410,420,430,440,450,460,470,480,490,500 -); - - -/*************HELPERS**************/ -/**********************************/ - -/*** identical width and height ***/ -@each $step in $steps { - .wh__#{$step} { - width: $step + px !important; - height: $step + px !important; - } - .lh__#{$step} { - line-height: $step + px !important; - } -} -.lh__1{ - line-height: 1 !important; -} -.no-wrap{ - white-space: nowrap; -} - -.ov-h{ - overflow: hidden; -} -.va-m{ - vertical-align: middle; -} -.bg-inherit{ - background-color:inherit; -} -.bg-black{ - background-color: black; -} -.v-center{ - &:before { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - margin-top: -0.25em; /* Adjusts for spacing */ - } -} -.fluid-height{ - height:100%; -} -.visibility__hidden{ - visibility: hidden; -} - -.pointerEvents__none{ - pointer-events: none; -} - -/* Padding Helpers */ -.pn { - padding: 0 !important; -} -.p1 { - padding: 1px !important; -} -.p2 { - padding: 2px !important; -} -.p3 { - padding: 3px !important; -} -.p4 { - padding: 4px !important; -} -.p5 { - padding: 5px !important; -} -.p6 { - padding: 6px !important; -} -.p7 { - padding: 7px !important; -} -.p8 { - padding: 8px !important; -} -.p10 { - padding: 10px !important; -} -.p12 { - padding: 12px !important; -} -.p15 { - padding: 15px !important; -} -.p20 { - padding: 20px !important; -} -.p25 { - padding: 25px !important; -} -.p30 { - padding: 30px !important; -} -.p35 { - padding: 35px !important; -} -.p40 { - padding: 40px !important; -} -.p50 { - padding: 50px !important; -} -.ptn { - padding-top: 0 !important; -} -.pt5 { - padding-top: 5px !important; -} -.pt10 { - padding-top: 10px !important; -} -.pt15 { - padding-top: 15px !important; -} -.pt20 { - padding-top: 20px !important; -} -.pt25 { - padding-top: 25px !important; -} -.pt30 { - padding-top: 30px !important; -} -.pt35 { - padding-top: 35px !important; -} -.pt40 { - padding-top: 40px !important; -} -.pt50 { - padding-top: 50px !important; -} -.prn { - padding-right: 0 !important; -} -.pr5 { - padding-right: 5px !important; -} -.pr10 { - padding-right: 10px !important; -} -.pr15 { - padding-right: 15px !important; -} -.pr20 { - padding-right: 20px !important; -} -.pr25 { - padding-right: 25px !important; -} -.pr30 { - padding-right: 30px !important; -} -.pr35 { - padding-right: 35px !important; -} -.pr40 { - padding-right: 40px !important; -} -.pr50 { - padding-right: 50px !important; -} -.pbn { - padding-bottom: 0 !important; -} -.pb5 { - padding-bottom: 5px !important; -} -.pb10 { - padding-bottom: 10px !important; -} -.pb15 { - padding-bottom: 15px !important; -} -.pb20 { - padding-bottom: 20px !important; -} -.pb25 { - padding-bottom: 25px !important; -} -.pb30 { - padding-bottom: 30px !important; -} -.pb35 { - padding-bottom: 35px !important; -} -.pb40 { - padding-bottom: 40px !important; -} -.pb50 { - padding-bottom: 50px !important; -} -.pln { - padding-left: 0 !important; -} -.pl5 { - padding-left: 5px !important; -} -.pl10 { - padding-left: 10px !important; -} -.pl15 { - padding-left: 15px !important; -} -.pl20 { - padding-left: 20px !important; -} -.pl25 { - padding-left: 25px !important; -} -.pl30 { - padding-left: 30px !important; -} -.pl35 { - padding-left: 35px !important; -} -.pl40 { - padding-left: 40px !important; -} -.pl50 { - padding-left: 50px !important; -} - -/* Axis Padding (both top/bottom or left/right) */ -.pv5 { - padding-top: 5px !important; - padding-bottom: 5px !important; -} -.pv8 { - padding-top: 8px !important; - padding-bottom: 8px !important; -} -.pv10 { - padding-top: 10px !important; - padding-bottom: 10px !important; -} -.pv15 { - padding-top: 15px !important; - padding-bottom: 15px !important; -} -.pv20 { - padding-top: 20px !important; - padding-bottom: 20px !important; -} -.pv25 { - padding-top: 25px !important; - padding-bottom: 25px !important; -} -.pv30 { - padding-top: 30px !important; - padding-bottom: 30px !important; -} -.pv40 { - padding-top: 40px !important; - padding-bottom: 40px !important; -} -.pv50 { - padding-top: 50px !important; - padding-bottom: 50px !important; -} -.ph5 { - padding-left: 5px !important; - padding-right: 5px !important; -} -.ph8 { - padding-left: 8px !important; - padding-right: 8px !important; -} -.ph10 { - padding-left: 10px !important; - padding-right: 10px !important; -} -.ph15 { - padding-left: 15px !important; - padding-right: 15px !important; -} -.ph20 { - padding-left: 20px !important; - padding-right: 20px !important; -} -.ph25 { - padding-left: 25px !important; - padding-right: 25px !important; -} -.ph30 { - padding-left: 30px !important; - padding-right: 30px !important; -} -.ph40 { - padding-left: 40px !important; - padding-right: 40px !important; -} -.ph50 { - padding-left: 50px !important; - padding-right: 50px !important; -} - -/* margin center helper */ -.mauto { - margin-left: auto; - margin-right: auto; -} -.mn { - margin: 0 !important; -} -.m1 { - margin: 1px !important; -} -.m2 { - margin: 2px !important; -} -.m3 { - margin: 3px !important; -} -.m4 { - margin: 4px !important; -} -.m5 { - margin: 5px !important; -} -.m8 { - margin: 8px !important; -} -.m10 { - margin: 10px !important; -} -.m15 { - margin: 15px !important; -} -.m20 { - margin: 20px !important; -} -.m25 { - margin: 25px !important; -} -.m30 { - margin: 30px !important; -} -.m35 { - margin: 35px !important; -} -.m40 { - margin: 40px !important; -} -.m50 { - margin: 50px !important; -} -.mtn { - margin-top: 0 !important; -} -.mt5 { - margin-top: 5px !important; -} -.mt10 { - margin-top: 10px !important; -} -.mt15 { - margin-top: 15px !important; -} -.mt20 { - margin-top: 20px !important; -} -.mt25 { - margin-top: 25px !important; -} -.mt30 { - margin-top: 30px !important; -} -.mt35 { - margin-top: 35px !important; -} -.mt40 { - margin-top: 40px !important; -} -.mt50 { - margin-top: 50px !important; -} -.mt70 { - margin-top: 70px !important; -} -.mrn { - margin-right: 0 !important; -} -.mr5 { - margin-right: 5px !important; -} -.mr10 { - margin-right: 10px !important; -} -.mr15 { - margin-right: 15px !important; -} -.mr20 { - margin-right: 20px !important; -} -.mr25 { - margin-right: 25px !important; -} -.mr30 { - margin-right: 30px !important; -} -.mr35 { - margin-right: 35px !important; -} -.mr40 { - margin-right: 40px !important; -} -.mr50 { - margin-right: 50px !important; -} -.mbn { - margin-bottom: 0 !important; -} -.mb5 { - margin-bottom: 5px !important; -} -.mb10 { - margin-bottom: 10px !important; -} -.mb15 { - margin-bottom: 15px !important; -} -.mb20 { - margin-bottom: 20px !important; -} -.mb25 { - margin-bottom: 25px !important; -} -.mb30 { - margin-bottom: 30px !important; -} -.mb35 { - margin-bottom: 35px !important; -} -.mb40 { - margin-bottom: 40px !important; -} -.mb50 { - margin-bottom: 50px !important; -} -.mb70 { - margin-bottom: 70px !important; -} -.mln { - margin-left: 0 !important; -} -.ml5 { - margin-left: 5px !important; -} -.ml10 { - margin-left: 10px !important; -} -.ml15 { - margin-left: 15px !important; -} -.ml20 { - margin-left: 20px !important; -} -.ml25 { - margin-left: 25px !important; -} -.ml30 { - margin-left: 30px !important; -} -.ml35 { - margin-left: 35px !important; -} -.ml40 { - margin-left: 40px !important; -} -.ml50 { - margin-left: 50px !important; -} - -/* Axis Margins (both top/bottom or left/right) */ -.mv5 { - margin-top: 5px !important; - margin-bottom: 5px !important; -} -.mv10 { - margin-top: 10px !important; - margin-bottom: 10px !important; -} -.mv15 { - margin-top: 15px !important; - margin-bottom: 15px !important; -} -.mv20 { - margin-top: 20px !important; - margin-bottom: 20px !important; -} -.mv25 { - margin-top: 25px !important; - margin-bottom: 25px !important; -} -.mv30 { - margin-top: 30px !important; - margin-bottom: 30px !important; -} -.mv40 { - margin-top: 40px !important; - margin-bottom: 40px !important; -} -.mv50 { - margin-top: 50px !important; - margin-bottom: 50px !important; -} -.mv70 { - margin-top: 70px !important; - margin-bottom: 70px !important; -} -.mh5 { - margin-left: 5px !important; - margin-right: 5px !important; -} -.mh10 { - margin-left: 10px !important; - margin-right: 10px !important; -} -.mh15 { - margin-left: 15px !important; - margin-right: 15px !important; -} -.mh20 { - margin-left: 20px !important; - margin-right: 20px !important; -} -.mh25 { - margin-left: 25px !important; - margin-right: 25px !important; -} -.mh30 { - margin-left: 30px !important; - margin-right: 30px !important; -} -.mh40 { - margin-left: 40px !important; - margin-right: 40px !important; -} -.mh50 { - margin-left: 50px !important; - margin-right: 50px !important; -} -.mh70 { - margin-left: 70px !important; - margin-right: 70px !important; -} - -/* Negative Margin Helpers */ -.mtn5 { - margin-top: -5px !important; -} -.mtn10 { - margin-top: -10px !important; -} -.mtn15 { - margin-top: -15px !important; -} -.mtn20 { - margin-top: -20px !important; -} -.mtn30 { - margin-top: -30px !important; -} -.mrn5 { - margin-right: -5px !important; -} -.mrn10 { - margin-right: -10px !important; -} -.mrn15 { - margin-right: -15px !important; -} -.mrn20 { - margin-right: -20px !important; -} -.mrn30 { - margin-right: -30px !important; -} -.mbn5 { - margin-bottom: -5px !important; -} -.mbn10 { - margin-bottom: -10px !important; -} -.mbn15 { - margin-bottom: -15px !important; -} -.mbn20 { - margin-bottom: -20px !important; -} -.mbn30 { - margin-bottom: -30px !important; -} -.mln5 { - margin-left: -5px !important; -} -.mln10 { - margin-left: -10px !important; -} -.mln15 { - margin-left: -15px !important; -} -.mln20 { - margin-left: -20px !important; -} -.mln30 { - margin-left: -30px !important; -} - -/* Vertical Negative Margin "mv" + "n" + "x" */ -.mvn5 { - margin-top: -5px !important; - margin-bottom: -5px !important; -} -.mvn10 { - margin-top: -10px !important; - margin-bottom: -10px !important; -} -.mvn15 { - margin-top: -15px !important; - margin-bottom: -15px !important; -} -.mvn20 { - margin-top: -20px !important; - margin-bottom: -20px !important; -} -.mvn30 { - margin-top: -30px !important; - margin-bottom: -30px !important; -} - -/* Horizontal Negative Margin "mh" + "n" + "x" */ -.mhn5 { - margin-left: -5px !important; - margin-right: -5px !important; -} -.mhn10 { - margin-left: -10px !important; - margin-right: -10px !important; -} -.mhn15 { - margin-left: -15px !important; - margin-right: -15px !important; -} -.mhn20 { - margin-left: -20px !important; - margin-right: -20px !important; -} -.mhn30 { - margin-left: -30px !important; - margin-right: -30px !important; -} - -/* Vertical Align Helpers */ -.va-t { - vertical-align: top !important; -} -.va-m { - vertical-align: middle !important; -} -.va-b { - vertical-align: bottom !important; -} -.va-s { - vertical-align: super !important; -} - -/* Text Helpers */ -.text-left { - text-align: left !important; -} -.text-right { - text-align: right !important; -} -.text-center { - text-align: center !important; -} -.text-justify { - text-align: justify !important; -} -.text-nowrap { - white-space: nowrap !important; -} - -/* Inline Block Helper */ -.ib, -.inline-object { - display: inline-block !important; -} - -.clear { - clear: both; -} - -// warning popup - -.wvWarning { - position: relative; - width: 320px; - min-height: 130px; - z-index: 999; - left: calc(50% - 160px); - border: #000 solid 1px; - -webkit-border-radius: 7px; - -moz-border-radius: 7px; - border-radius: 7px; - color: #FF5722; - box-shadow: 0px 3px 23px #ff980078; - -webkit-animation-name: example; /* Safari 4.0 - 8.0 */ - -webkit-animation-duration: 3s; /* Safari 4.0 - 8.0 */ - -webkit-animation-fill-mode: both; /* Safari 4.0 - 8.0 */ - animation-name: example; - animation-duration: 2s; - animation-fill-mode: both; - animation-timing-function: ease-out; -} - -@-webkit-keyframes example { - from {top: 0vh;opacity: 0;background: #868686} - to {top: 10vh;opacity: 1;background: #ffffff} -} - -@keyframes example { - from {top: 0vh;opacity: 0;background: #868686} - to {top: 10vh;opacity: 1;background: #ffffff} -} - -.wvWarning-content{ - position: relative; - width: 190px; - min-height: 88px; - max-height: 80vh; - margin: auto; -} -.wvWarning-icon{ - font-size: 32px; -} -.wvWarning-text{ - position: relative; -} -.wvWarning-button{ - background-color: #f1ededcc; - color: #607D8B; - width: 50px; - font-weight: 600; - margin-top: 2px; - margin-right: 30px; -} - -.wvScreenToSmallWarning { - position: fixed; - display: block; - top: 0; - left: 0; - background-color: white; - color: #333; - width: 100%; - height: 100%; - z-index: 1000; -} - -.wvScreenToSmallWarning-content { - padding: 10px; - text-align: center; -} - -/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ -@media only screen and (min-width: 550px) and (min-height: 280px) { - .wvScreenToSmallWarning { - display: none; - } -} - -/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ -@media only screen and (min-width: 280px) and (min-height: 550px) { - .wvScreenToSmallWarning { - display: none; - } -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_layout.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,498 +0,0 @@ -$topHeight: 42px; -$bottomHeightSmall: 7rem; // On small width, we provide two-lines bottom zone to compensate the smaller width -$bottomHeightLarge: 5rem; // On large width, we provide one-line bottom zone - -$asideWidth: 32rem; -$asideMinifyWidth: 12rem; - -$asideRightMinifyWidth: 85px; // eq. 7.5rem * 12px - ( $asideRightPadding / 2 ) -$asideRightPadding: 10px; - -/* layout: left section */ - -// Adapt left aside based on state (opened/closed). -.wvLayoutLeft { - // Set general properties. - position:absolute; - z-index:2; - background-color:black; - width: $asideWidth; - - // Position the left side below the top zone if it is shown - &.wvLayoutLeft--toppadding { - top: $topHeight; - } - &:not(.wvLayoutLeft--toppadding) { - top: 0; - } - - // Position the left section over the bottom one if the latter is shown - &.wvLayoutLeft--bottompadding { - @media screen and (max-device-width: 374px) { - bottom: $bottomHeightSmall; - } - @media screen and (min-device-width: 375px) { - bottom: $bottomHeightLarge; - } - } - &:not(.wvLayoutLeft--bottompadding) { - bottom: 0; - } - - // Position the left side on the left - left: 0; - - // When layout left is shown, nothing special happens (default state) - &:not(.wvLayoutLeft--closed) { - } - - // When layout left is closed, move it aside - &.wvLayoutLeft--closed { - transform: translateX(- $asideWidth); // Move all out of the screen - &.wvLayoutLeft--small { - transform: translateX(-$asideMinifyWidth); - } - } - &.wvLayoutLeft--small{ - width: $asideMinifyWidth; - & .wvLayoutLeft__contentTop, & .wvLayoutLeft__contentMiddle, & .wvLayoutLeft__contentBottom{ - width: 100%; - } - } -} - -// Layout-Left Flexbox containers for the content. -.wvLayoutLeft__content { - border-right: 1px solid #AAA; - - // Display flex mode so optional actions can be positionned at the bottom - // side. - flex: 1; - display: flex; - flex-direction: column; - - // Make it scrollable. - overflow-y: auto; - height: 100%; -} - -.wvLayoutLeft__contentTop { - // We have to set a static height since we use floating to make white space - // disapear in nested content. - // note: could be deactivate with the clearfix so we can have a dynamic height - // max-height: 6rem; - padding: 0rem 1rem 0rem 1rem; - - // Enforce width even if there is a scrollbar on win/IE11 (-1 px for - // border). - width: $asideWidth - 0.1rem; - - &:after{ - content: ""; - display:block; - height:0; - width:0; - clear:both; - } -} - -.wvLayoutLeft__contentMiddle { - // Let the middle zone take as much space as required. - flex: 1 0 auto; - - // Enforce width even if there is a scrollbar on win/IE11 (-1 px for - // border). - width: $asideWidth - 0.1rem; - -} - -.wvLayoutLeft__contentBottom { - // Enforce width even if there is a scrollbar on win/IE11 (-1 px for - // border). - width: $asideWidth - 0.1rem; -} -.wvLayout__leftBottom.wvLayout__leftBottom--enabled { - border-top: 1px solid hsla(0, 0%, 100%, 0.2); - margin-top: 1rem; - padding: 1rem; - - // Prevent from taking more space than intended. - // flex-grow: 0; -} - -.wvLayoutLeft__actions, -%wvLayoutLeft__actions{ - display:block; - position:absolute; - right:1px; // border - top: 50%; - transform: translateY(-50%); - width:25px; -} -.wvLayoutLeft__actions--outside{ - @extend.wvLayoutLeft__actions; - right:-25px; // width + border -} -.wvLayoutLeft__action{ - background-color:$primary; - opacity: 0.5; - color:white; - transition: none; - &:hover, &:focus{ - opacity: 1; - } -} - - -/* layout: right section */ - -// Adapt right aside based on state (opened/closed). -.wvLayout__right { - display:block; - position:absolute; - z-index:2; - background-color:black; - width: $asideRightMinifyWidth; - - // Position the left side below the top zone if it is shown - &.wvLayout__right--toppadding { - top: $topHeight; - } - &:not(.wvLayout__right--toppadding) { - top: 0; - } - - // Position the right section over the bottom one if the latter is shown - &.wvLayout__right--bottompadding { - @media screen and (max-device-width: 374px) { - bottom: $bottomHeightSmall; - } - @media screen and (min-device-width: 375px) { - bottom: $bottomHeightLarge; - } - } - &:not(.wvLayout__right--bottompadding) { - bottom: 0; - } - - // Position the right side on the right - right: 0; - - // When layout right is shown, nothing special happens (default state) - &:not(.wvLayout__right--closed) { - } - - // When layout right is closed, move it aside - &.wvLayout__right--closed { - transform: translateX(+ $asideRightMinifyWidth); // Move all out of the screen - } - - // Set childrens to full height (so border-left appears at 100% height) - & > wv-layout-right, - & > wv-layout-right > .wvViewer__asideRight - { - display: block; - height: 100%; - width: 100%; - } -} - -.wvAsideRight__content { - height: 100%; - float: left; - - border-left: 1px solid #AAA; - - padding: 0 $asideRightPadding/2; - width: $asideWidth; -} - -.wvAsideRight__actions, -%wvAsideRight__actions{ - display:block; - position:absolute; - left:1px; // border - top: 50%; - transform: translateY(-50%); - width:25px; - - // Compensate aside z-index to let user click on button when another button - // is behind the actions. - z-index: 3; -} -.wvAsideRight__actions--outside{ - @extend.wvAsideRight__actions; - left:-25px; // width + border -} -.wvAsideRight__action{ - background-color:$primary; - opacity: 0.5; - color:white; - transition: none; - &:hover, &:focus{ - opacity: 1; - } -} -.wvAsideRight__fixOpenFullyTooltip + .tooltip { // Fix the "open fully" bad tooltip placement of the asideRight - left: -6.633em !important; - top: 1px !important; -} - - -/* layout: bottom section */ - -// Set bottom section size & position -.wvLayout__bottom { - position: absolute; - - // Display the bottom bar in the bottom side - @media screen and (max-device-width: 374px) { - height: $bottomHeightSmall; - } - @media screen and (min-device-width: 375px) { - height: $bottomHeightLarge; - } - - left: 0; - bottom: 0; - right: 0; - - // Set grey background color (as it is only used to display notices) - background-color: hsl(0, 0%, 10%); -} - - -/* layout: main section */ - -// Set main section size & position -.wvLayout__main { - position: absolute; - - // Align content (such as toolbar) - text-align: center; - - // Position splitpane considering the toolbar when toolbar is present. - & .wvLayout__splitpane--toolbarAtTop { - display: block; - height: calc(100% - #{$toolbarHeight}); - width: 100%; - - position: relative; - top: $toolbarHeight; - } - & .wvLayout__splitpane--toolbarAtRight { - display: block; - height: 100%; - width: calc(100% - #{$toolbarHeight}); - } - - & .wvLayout__splitpane--bigToolbarAtTop { - display: block; - height: calc(100% - 68px); - width: 100%; - - position: relative; - top: 68px; - } - & .wvLayout__splitpane--bigToolbarAtRight { - display: block; - height: 100%; - width: calc(100% - 68px); - } - - // Position the main section below the top zone if the latter is shown - &.wvLayout__main--toppadding { - top: $topHeight; - } - &:not(.wvLayout__main--toppadding) { - top: 0; - } - - // Position the main section over the bottom one if the latter is shown - &.wvLayout__main--bottompadding { - bottom:440px; - @media screen and (max-device-width: 374px) { - bottom: $bottomHeightSmall; - } - @media screen and (min-device-width: 375px) { - bottom: $bottomHeightLarge; - } - } - &:not(.wvLayout__main--bottompadding) { - bottom: 0; - } - - // Make the main content fill the screen by default - // Depending on the browser, the left and right attributes are more - // optimized than padding's ones. The reason is that they based upon - // absolute positioning. The require no contextual positioning calculation - // and are less performance intensive, especially concidering transition - // animation. - right: 0; - left: 0; - - // transition: 0.6s left ease, 0.6s right ease; - - // Adapt main section's size based on aside left's state (opened/closed) - // 1. When aside left is not hidden , move the main section 300 pixel to - // the right - &.wvLayout__main--leftpadding { - left: $asideWidth; - } - // 2. When aside left is hidden, let the main take 100% of the place - &:not(.wvLayout__main--leftpadding, .wvLayout__main--smallleftpadding) { - left: 0px; - } - &.wvLayout__main--smallleftpadding { - left: $asideMinifyWidth; - } - - // Adapt main section's size based on aside right's state (opened/closed) - // 1. When aside right is not hidden , move the main section 84 pixel to - // the left - &.wvLayout__main--rightpadding { - right: $asideRightMinifyWidth; - } - // 2. When aside right is hidden, let the main take 100% of the place - &:not(.wvLayout__main--rightpadding) { - right: 0px; - } -} - -/* global */ -.popover { - // Set back black as default popover text color - color: black; -} - -.wvViewer__editor--full{ - position:absolute; - top:0; - right:0; - z-index:10; - opacity:0; - transform: translateX(100%); - width:100%; - height:100%; - background-color:white; - color:$text-color; - &.opened{ - opacity:1; - transform: translateX(0); - } -} - -.wvViewer__topBar{ - width:100%; - // margin-top: 0.5rem; - - // Allow user to scroll through the toolbar if screen is too small. Note we - // can't use z-index properly to show buttons on top of the viewer, as any - // popover will appear behind them (even with higher z-index) due to an - // overflow property hidden somewhere. - overflow-y: auto; - white-space: nowrap; - max-width: 100%; -} -.wvViewer__buttonGroup{ - display:inline-block; -} -.wvViewer__buttonGroup--asideWidth{ - width: $asideWidth; - padding-right: 1rem; -} -.wvViewer__buttonGroup--contentWidth{ - width: calc(100% - #{$asideWidth}); - padding-left: 1rem; - max-height: 4.2rem; // Make sure mobile keeps the menubar below a certain size -} -.wvViewer__iframe{ - position:absolute; - left:0; - top:0; -} - -/* bottom bar */ -.wvViewer__bottomBar, -%wvViewer__bottomBar{ - position:absolute; - left:0; - bottom:0; - width:100%; - background-color:#111111; -} - -.wvViewer__bottomBar--expanded{ - @extend .wvViewer__bottomBar; - height: 80px; //total height of the last serieList cell (64 + 10(margin bottom previous item) + item padding bottom +1 border-width (top item) - //border-top: 1px solid rgba(255,255,255,0.1); - color:white; - - .wvViewer__timeline{ - width: calc(100% - 80px); - } - .wvTimeline__hotspots{ - bottom: -40px; - } -} - -.wvViewer__bottomBar--minimized{ - @extend .wvViewer__bottomBar; - color: white; - - padding-top: 0.5rem; - padding-bottom: 0.5rem; - padding-left: 2.5rem; - - .wvTimeline__hotspot{ - top: -40px; - opacity:0; - visibility:hidden; - z-index:-1; - // transition: all 0.3s ease 0.6s; //adding a delay when mouse leave - // transition-property: opacity, visibility, z-index; - } - &:hover .wvTimeline__hotspot{ - opacity:1; - visibility: visible; - z-index:5; - transition-delay: 0s; - } -} - -.wvViewer__timeline{ - height:24px; - //background-color:rgba(1,1,1,0.2); - line-height: 24px; - vertical-align: middle; - width:100%; -} - -.wvViewer__trademark{ - display:inline-block; - float:right; - width:80px; - height:80px; - float:right; - line-height: 80px; - vertical-align: middle; - text-align: center; -} -.wvTimeline__action--text{ - -} -.wvTimeline__input{ - border-radius: 3px; - &:focus{ - outline:none; - } - margin-top:2px; - border: 1px solid $border-color; -} - -.wvTimeline__actions{ - display:inline-block; - border-right: 1px solid $border-color; -} -.wvTimeline__wrapper{ -}
--- a/StoneWebViewer/Resources/Styles/_notice.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -wv-notice { - display: block; - height: 100%; - width: 100%; -} - -.wvNotice { - // Set padding - padding: 0.5rem 0.25rem; - - // Fill parent element so text can be centered - height: 100%; -} - -.wvNotice__text { - // Center text - position: relative; - top: 50%; - transform: translateY(-50%); - text-align: center; - margin-left: 1rem; - - // Text style - font-weight: 400; - color: hsl(0, 0%, 70%); - - // Keep space for button - float: left; - width: calc(100% - 7rem); // 3.5 rem + 3 rem margin (incl. button margin + text margin) -} - -.wvNotice__closeButton { - // Position button on right - float: right; - margin-right: 0.5em; - - // Center button vertically - position: relative; - top: 50%; - transform: translateY(-50%); - - // Set button size - width: 3.5rem; - height: 2.5rem; // half the bottom zone height - - // Configure button icon - text-align: center; - font-size: 1em; - font-weight: 100; - line-height: 2.2rem; - - // Set button style - cursor: pointer; - border: 1px solid hsl(0, 0%, 27%); -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_print.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -.wvPrintExclude{ - display:none; -} - -.wvPrintFullPage{ - width: 100% !important; - height: 100% !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - display:block !important; -} - -.wvLayout__main{ - top: 0 !important; - right: 0 !important; - left: 0 !important; - bottom: 0 !important; -} - -.wvPrintViewer{ - width: 100%; - height:100%; - display: flex; - align-items: center; - justify-content: center; -} - -.wvPrintViewer canvas{ - max-width: 100% !important; - max-height: 100% !important; - margin:auto; -} - -.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft{ - &, & * { - background-color: black !important; - -webkit-print-color-adjust: exact !important; - color-adjust: exact !important; - color: orange !important; - } -} -.tooltip{ - display: none !important; -} -body{ - margin: 0; - padding: 0; - position: relative; - &, *{ - background-color: black !important; - -webkit-print-color-adjust: exact !important; - } - width: 8.5in; - height: 11in; -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_selectionActionlist.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.wvSelectionActionlist { - display: block; - - text-align: center; -} - -.wvSelectionActionlist__bottom { - -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_serieslist.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -$gray: gray; -$blue: hsla(204, 70%, 53%, 0.7); -$red: rgba(206, 0, 0, 0.7); -$green: rgba(0, 160, 27, .7); -$yellow: rgba(220, 200 , 0, .9); -$violet: rgba(255, 31, 255, .7); - -$borderColor: rgba(255, 255, 255, 0.8); -$borderColorActive: rgba(255, 255, 255, 0.6); -$borderColorHighlighted: rgba(255, 255, 255, 1); -$pictureSize: 6.5rem; - -.wvSerieslist { - margin: 0; - padding: 0; - list-style: none; -} - -.wvSerieslist__seriesItem--selectable { - // Pointer cursor (for `ng-click`) - cursor: pointer !important; - - // Lighten up the icon on hover - &:hover { - color: white; - } -} - -.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa { // Make sure it has precedence over .fa class { - position: absolute; - - // Fill the li element - width: 100%; - height: 100%; - - // Fill the li element with the fontawesome icon - font-size: $pictureSize/2; - line-height: $pictureSize; - text-align: center; -} - -.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa { // Make sure it has precedence over .fa class - // Grey out (since no report is available) - color: #c3c3c3; - - // Diagonal line crossing report icon (to tell none are available) - // position: relative; - - &::after { // use after to not conflicts with font-awesome :before - position: absolute; - - left: 0; - top: 50%; - right: 0; - - transform: rotate(-45deg) scaleX(0.9); - - border-top: 5px solid; - border-color: inherit; - - content: ""; - } -} - -.wvSerieslist__picture{ - display: inline-block; - font-size: 14px; - width: $pictureSize; - height: $pictureSize; - position: relative; - - // Move picture behind the `toggle layout@ left` button. - z-index: -1; -} -.wvSerieslist__badge { - position: absolute; - bottom:5px; - right:5px; - font-size:10px; - line-height:15px; - width:15px; - height:15px; - border-radius: 100%; - background-color: $gray; - vertical-align: middle; - text-align: center; - font-weight: bold; -} -.wvSerieslist__information{ - font-size: 14px; - float: right; - padding-left: 1rem; - width: calc(100% - #{$pictureSize}); - height: $pictureSize; -} -.wvSerieslist__label{ - white-space: nowrap; - width:calc(100% - 10px); - overflow:hidden; - height:$pictureSize/2; - line-height:$pictureSize/2; - vertical-align: middle; -} -.wvSerieslist__timeline{ - //border-top: 0.1rem solid rgba(255,255,255,0.2); - height:$pictureSize/2; - line-height:$pictureSize/2; - vertical-align: middle; -} - -.wvSerieslist__seriesItem { - // anchor - position: relative; - - // unstyle list - padding-left: 0; - list-style: none; - font-size: 0; - - // mimic on hover border for draggable - border-right: 0.2rem solid transparent; - border-left: 0.2rem solid transparent; - border-top: 0.2rem solid transparent; - border-bottom: 0.2rem solid transparent; - border-corner-shape: notch; - - line-height: 0px; - margin: 0.1rem; - - &.active{ - border-color: $borderColorActive; - border-style: solid; - } - - &.highlighted{ - border-color: $borderColorHighlighted; - border-style: solid; - } - - &:hover, &:focus, &.focused{ - border-style: dashed; - border-color: $borderColor; - } -} - -.wvSerieslist__seriesItem--list { - display: block; -} -.wvSerieslist__seriesItem--grid { - display: inline-block; -} -.wvSerieslist__seriesItem--oneCol{ - text-align: center; -} - -.wvSerieslist__seriesItem--activated, -.wvSerieslist__videoItem--activated, -.wvSerieslist__pdfItem--activated { - border: 0.2rem solid hsla(204, 70%, 53%, 1) !important; -} - -// Color related modifiers -.wvSerieslist__badge--blue { - @extend .wvSerieslist__badge; - background-color: $blue; -} -.wvSerieslist__badge--red { - @extend .wvSerieslist__badge; - background-color: $red; -} -.wvSerieslist__badge--green { - @extend .wvSerieslist__badge; - background-color: $green; -} -.wvSerieslist__badge--yellow { - @extend .wvSerieslist__badge; - background-color: $yellow; -} -.wvSerieslist__badge--violet { - @extend .wvSerieslist__badge; - background-color: $violet; -}
--- a/StoneWebViewer/Resources/Styles/_studyInformationBreadcrumb.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -.wvStudyInformationBreadcrumb { -} - -.wvStudyInformationBreadcrumb__patient { - display: inline-block; - background-color: rgba(255, 255, 255, 0.15); - padding: 0.2rem 1rem 0.3rem 1rem; - text-align: center; - font-size: 1em; - margin: 0.6rem; - font-weight: 400; - line-height: 2.3rem; - - // Prevent doubled margin with the next item - margin-right: 0; -} -.wvStudyInformationBreadcrumb__patientName { - -} -.wvStudyInformationBreadcrumb__patientBirthDate { - -} - -.wvStudyInformationBreadcrumb__study { - display: inline-block; - background-color: rgba(255, 255, 255, 0.15); - padding: 0.2rem 1rem 0.3rem 1rem; - text-align: center; - font-size: 1em; - margin: 0.6rem; - font-weight: 400; - line-height: 2.3rem; -} -.wvStudyInformationBreadcrumb__studyDescription { - -} -.wvStudyInformationBreadcrumb__studyDate { - -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_studyIsland.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -$gray: gray; -$blue: hsla(204, 70%, 53%, 0.7); -$red: rgba(206, 0, 0, 0.7); -$green: rgba(0, 160, 27, .7); -$yellow: rgba(220, 200 , 0, .9); -$violet: rgba(255, 31, 255, .7); - - -%wvStudyIsland { - margin: 1rem 1rem 1rem 1rem; - border: 0.3rem solid $gray; -} - -%wvStudyIsland__header { - background-color: $gray; - padding: 0.5rem 0.5rem 0.8rem 0.5rem; - line-height: 1.35rem; - width: 100%; -} -.wvStudyIsland__actions { - float: right; - - // Compensate header padding (since the inner download study button - // actually has margin). - margin-top: -0.8rem; - margin-right: -0.8rem; -} - -.wvStudyIsland__actions--oneCol { - float: none; - text-align: center; -} - -.wvStudyIsland__main { - padding: 0.4rem; // 0.7rem - 0.3rem (2px transparent border + 1px margin) - color: hsl(0, 0%, 100%); - width: 100%; // let some space for the 4-columns based items -} - - -// Color modifiers -.wvStudyIsland--blue { - @extend %wvStudyIsland; - border-color: $blue; -} - -.wvStudyIsland__header--blue { - @extend %wvStudyIsland__header; - background-color: $blue; -} - -.wvStudyIsland--red { - @extend %wvStudyIsland; - border-color: $red; -} - -.wvStudyIsland__header--red { - @extend %wvStudyIsland__header; - background-color: $red; -} - -.wvStudyIsland--green { - @extend %wvStudyIsland; - border-color: $green; -} - -.wvStudyIsland__header--green { - @extend %wvStudyIsland__header; - background-color: $green; -} - -.wvStudyIsland--yellow { - @extend %wvStudyIsland; - border-color: $yellow; -} - -.wvStudyIsland__header--yellow { - @extend %wvStudyIsland__header; - background-color: $yellow; -} - -.wvStudyIsland--violet { - @extend %wvStudyIsland; - border-color: $violet; -} - -.wvStudyIsland__header--violet { - @extend %wvStudyIsland__header; - background-color: $violet; -}
--- a/StoneWebViewer/Resources/Styles/_toolbar.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -.wvToolbar { - position: absolute; -} - -.wvToolbar--top { - top: 0; - height: $toolbarHeight; - - // Position the toolbar to the right (even if it's positioned - // horizontally). - right: 0; - text-align: right; - - // Allow user to scroll through the toolbar if screen is too small. Note we - // can't use z-index properly to show buttons on top of the viewer, as any - // popover will appear behind them (even with higher z-index) due to an - // overflow property hidden somewhere. - // overflow-y: auto; - white-space: nowrap; - max-width: 100%; -} - -.wvToolbar--right { - right: 0; - width: 42px; // != $toolbarHeight since we're in the reverse order. - - // Allow user to scroll through the toolbar if screen is too small. - // overflow-x: auto; - height: 100%; - z-index: 2; - &.wvToolbar--big{ - width: 68px; - } -} - -/* Splitpane Grid Configuration */ - -.wvToolbar__splitpaneConfigPopover { - // Prevent white space between buttons. - font-size: 0; -} - -.wvToolbar__splitpaneConfigNotice { - font-size: 1.25rem; - font-style: italic; - text-align: center; - - color: #333; -} - -input[type="radio"].wvToolbar__splitpaneConfigButtonInput { - // Hide the radio input, but make it fit the label, so we can rely on its - // html caracteristics without having to suffer from its design. - position: absolute; - width: 0; - height: 0; - left: 0; - top: 0; - bottom: 2px; - right: 0; - opacity: 0; -} - -/* Windowing Preset */ - -.wvToolbar__windowingPresetConfigPopover { - -} -.wvToolbar__windowingPresetConfigNotice { - font-size: 1.25rem; - font-style: italic; - text-align: center; - - color: #333; -} - -.wvToolbar__windowingPresetList { - list-style: none; - margin: 0; - padding: 0; - - font-size: 1.5rem; -} -.wvToolbar__windowingPresetListItem { - // Remove <a> default styles. Take care - this class may either be used - // with <a> or <button>. - &:hover { - text-decoration: none; - color: white; - } - - // Remove <button> default styles. - outline: none; - background-color: transparent; - border: none; - - // Set relative to position button absolutely - position: relative; - - // Style button - display: inline-block; - cursor: pointer; - font-variant: small-caps; - text-transform: lowercase; - text-align: center; - font-size: 1.3rem; - font-weight: 400; - line-height: 2.2rem; - color: hsl(0, 0%, 85%); - transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; - - // Position button - margin: 0; - min-width: 3rem; - padding: 0 10px; - line-height: 3.6rem; - - - - // Prevent multi line buttons. - max-height: 2.8rem; - max-width: 100%; - overflow: hidden; - - // Set margin - margin: 0.6rem; - margin-left: 0rem; - margin-right: 0rem; - & + & { - margin-left: 0.7rem; - } - - // Set button size - line-height: 2rem; - - // Align text - padding-top: 0.1rem; - padding-bottom: 0.5rem; - - // Style button - font-size: 1.4rem; - border: 1px solid hsl(0, 0%, 27%); - - // Set best looking font with small-caps. - font-family: Arial; - - // Change background on hover - background-color: hsl(0, 0%, 0%); - &:hover { - background-color: hsl(0, 0%, 10%); - } - - & > .glyphicon { // used with the same element as glyphicons - // Position button - position: relative; - display: inline-block; - top: 3px; - margin-right: 4px; - } - - // Text color - color: hsl(0, 0%, 10%); - border: 1px solid hsl(0, 0%, 73%); - - // Change background on hover - background-color: hsl(0, 0%, 100%); - &:hover { - color: hsl(0, 0%, 10%); - background-color: hsl(0, 0%, 90%); - } - - - width: 100%; - margin: 0; - margin-left: 0 !important; - border-top: none; - border-bottom: none; -}
--- a/StoneWebViewer/Resources/Styles/_variable.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -$primary-lighten: #57aae1; -$primary: #3498db; -$dangerColor: #E63F24; - -$panel-header: #fafafa; -$border-color: #e7e7e7; -$lightGrey: #cccccc; -$text-color: #666666; - -$darkGrey: #333333; -$darkBlue: #203A6F; -$blueGrey: #303E4D; - -$toolbarHeight: 42px; \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/_video.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -.wvVideo { - // Align component vertically & horizontally - position: absolute; - top:50%; - left:0; - width: 100%; - height: auto; - transform: translateY(-50%); -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/styles.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,32 +0,0 @@ -// bower:scss -// endbower - -@import "webviewer.main.scss"; -@import "webviewer.components.scss"; - - -@media print { - @import "print"; -} - -.closePrintButton{ - display:none; -} - -body.print{ - @import "print"; - - @media screen { - .closePrintButton{ - display:block; - position: fixed; - top: 0; - right: 0; - padding: 10px; - font-size: 24px; - background-color: black; - color: white; - border: none; - } - } -}
--- a/StoneWebViewer/Resources/Styles/tb-group.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -.tbGroup{ - position:relative; -} -.tbGroup__buttons--base, -%tbGroup__buttons--base{ - z-index: 5; - background-color: black; - position: absolute; -} - -.tbGroup__buttons--bottom{ - @extend .tbGroup__buttons--base; - right:0; // let the element at it's initial position but align him in right (natural position is below the toggl button element) - display:block; -} -.tbGroup__buttons--left{ - @extend .tbGroup__buttons--base; - right:100%; - top:0; - display:block; -} -.tbGroup__icon{ - display:block; - position: absolute; - bottom:0; - left:0; - - width: 0; - height: 0; - border-style: solid; - border-width: 10px 0 0 10px; - border-color: transparent transparent transparent rgba(255,255,255,0.1); - &.active{ - border-color: transparent transparent transparent $primary; - } -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/webviewer.components.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -/* wvp-ui stuffs */ -wv-webviewer { - display: block; - height: 100%; - overflow: hidden; -} - -@import "variable"; -@import "button"; -@import "exitButton"; -@import "studyIsland"; -@import "helpers"; -@import "notice"; -@import "layout"; -@import "serieslist"; -@import "toolbar"; -@import "video"; -@import "studyInformationBreadcrumb"; -@import "selectionActionlist"; - -/* wvb-ui stuffs */ -@import "wv-overlay.scss"; -@import "wv-pdf-viewer.scss"; -@import "wv-splitpane.scss"; -@import "wv-timeline.scss"; -@import "wv-timeline-controls.scss"; -@import "wv-loadingbar.scss"; -@import "wv-disclaimer"; -@import "tb-group"; - -wv-viewport { // make sure the element is sized when using with drag & drop - display: inline-block; - width: 100%; - height: 100%; - - > div { - position: relative; - width: 100%; - height: 100%; - } - - // We don't set 100% width/height to the canvas element, as it would stretch - // the pixels. Instead, we center it for more fluid transition when pane's - // width changes (at least the content is kept centered even if the js hasn't - // yet reacted to layout reflow). - > div > .wv-cornerstone-enabled-image { - width: 100%; - height: 100%; - text-align: center; - } -} - -.wv-draggable-clone { - width: 150px; - height: 150px; - background-color: rgba(255,255,255,0.25); - - // No need to set z-index (already done by jquery ui lib). -}
--- a/StoneWebViewer/Resources/Styles/webviewer.main.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -.browsehappy { - margin: 0.2em 0; - background: #ccc; - color: #000; - padding: 0.2em 0; -} - -.wv-html, .wv-body { - height: 100%; - width: 100%; - - margin: 0; - padding: 0; - - overflow: hidden; -} -.wv-body { - background-color: black; - color: white; - position: relative; - overflow: hidden; - - font-family: "Open Sans", Helvetica, Arial, sans-serif; - -webkit-tap-highlight-color: hsla(0, 0%, 0%, 0); - font-size: 13px; - font-weight: 400; - line-height: 1.49; - font-size-adjust: 100%; - - // Smooth text - -moz-osx-font-smoothing: grayscale !important; - font-smoothing: antialiased !important; - -webkit-font-smoothing: antialiased !important; -} - -.wvLoadingScreen { - width: 100%; - height: 100%; - background-color: black; - position: fixed; - top: 0; - left: 0; - z-index: 9999; - - display: flex; - align-items: center; - justify-content: center; -} - -.wvLoadingSpinner { - margin: 100px auto 0; - width: 70px; - text-align: center; -} - -.wvLoadingSpinner > div { - width: 18px; - height: 18px; - background-color: #FFF; - - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; -} - -.wvLoadingSpinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; -} - -.wvLoadingSpinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; -} - -@-webkit-keyframes sk-bouncedelay { - 0%, 80%, 100% { -webkit-transform: scale(0) } - 40% { -webkit-transform: scale(1.0) } -} - -@keyframes sk-bouncedelay { - 0%, 80%, 100% { - -webkit-transform: scale(0); - transform: scale(0); - } 40% { - -webkit-transform: scale(1.0); - transform: scale(1.0); - } -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-disclaimer.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -.disclaimer{ - color: $dangerColor; - background-color: #303030; - padding:5px; - text-align: center; - font-weight: bold; -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-loadingbar.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -.wv-loadingbar-image-bar { - cursor: pointer; -} -.wv-loadingbar-not-loaded { - fill: rgba(255, 255, 255, 0.1); -} -.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality { - transition: none; -} -.wv-loadingbar-not-loaded:hover { - fill: rgba(255, 255, 255, 0.2); -} -.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality { - fill:rgba(0, 255, 0, 0.7); -} -.wv-loadingbar-LOSSLESS-quality:hover, -.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active, -.wv-loadingbar-PIXELDATA-quality:hover, -.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active { - fill:rgba(0, 255, 0, 1); -} -.wv-loadingbar-LOW-quality { - fill:rgba(255, 0, 0, 0.7); -} -.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active { - fill:rgba(255, 0, 0, 1); -} -.wv-loadingbar-MEDIUM-quality { - fill:rgba(255, 95, 0, 0.7); -} -.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active { - fill:rgba(255, 95, 0, 1); -}
--- a/StoneWebViewer/Resources/Styles/wv-overlay.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -$gray: gray; -$blue: hsla(204, 70%, 53%, 0.7); -$red: rgba(206, 0, 0, 0.7); -$green: rgba(0, 160, 27, .7); -$yellow: rgba(220, 200 , 0, .9); -$violet: rgba(255, 31, 255, .7); - -.wv-overlay { - // width&height is 0x0 to avoid capturing viewport events - color: orange; -} - -.wv-overlay-icon { - width: 64px; -} - -.wvOverlay__studyBadge { - position: absolute; - top: 0; - left: 0; - width: 1.5rem; - height: 1.5rem; - background-color: $gray; - z-index: 1; -} - -.wv-overlay-topleft { - position: absolute; - top: 0rem; - left: 0rem; - text-align: left; -} - -.wv-overlay-topright { - position: absolute; - top: 0rem; - right: 0rem; - text-align: right; -} - -.wv-overlay-bottomright { - position: absolute; - bottom: 2em; // save 2em for the timeline - right: 0rem; - text-align: right; -} - -.wv-overlay-bottomleft { - position: absolute; - bottom: 2em; // save 2em for the timeline - left: 0rem; - text-align: left; -} - -.wv-overlay-timeline-wrapper { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 1; // Make sure the representation of the selected image on the timeline appear on top of other overlay panels -} - -.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft { - padding: 2rem; - transition: color 500ms, background-color 500ms; - background-color: rgba(0, 0, 0, 0.66); -} - -.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover { - background-color: rgba(0, 0, 0, 0.9); -} - -.wvPaneOverlay { - position: absolute; - top: 50%; - width: 100%; - transform: translateY(-50%); - - font-weight: 100; - text-align: center; - color: white; - font-size: 2rem; -} - -.wv-overlay-scrollbar-loaded { - position: absolute; - bottom:0; - left:0; - height: 5px; - background-color: red; - will-change: right; - transform-origin: 0% 50%; -} - -.wv-overlay-scrollbar-loading { - position: absolute; - bottom:0; - left:0; - height: 5px; - background-color: #660000; - will-change: right; - transform-origin: 0% 50%; -} - -.wv-overlay-scrollbar-text { - position: absolute; - bottom: calc(1em + 5px); - left: 5px; - height: 1em; - color: red; - font-size: 0.8em; - font-family: helvetica; -} - -// Color related modifiers -.wvOverlay__studyBadge--blue { - @extend .wvOverlay__studyBadge; - background-color: $blue; -} -.wvOverlay__studyBadge--red { - @extend .wvOverlay__studyBadge; - background-color: $red; -} -.wvOverlay__studyBadge--green { - @extend .wvOverlay__studyBadge; - background-color: $green; -} -.wvOverlay__studyBadge--yellow { - @extend .wvOverlay__studyBadge; - background-color: $yellow; -} -.wvOverlay__studyBadge--violet { - @extend .wvOverlay__studyBadge; - background-color: $violet; -}
--- a/StoneWebViewer/Resources/Styles/wv-pdf-viewer.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1761 +0,0 @@ -wv-pdf-viewer { - display: block; - width: 100%; - height: 100%; -} - -#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton { // We need high priority, !important keywords don't work - background-color: inherit; - color: hsl(0, 0%, 100%); - border: none; - - padding: 2px; - margin-left: 4px; - margin-right: 2px; - - &:hover { - color: black; - } -} - -.fa.fa-window-close.wv-pdf-viewer-closebuttonicon { // We need high priority - font-size: 2rem; - line-height: 28px; // pdf.js toolbar size (- closebutton margin) -} - -// The following code has been generated via: -// -// ```bash -// cd bower_components/pdf.js-viewer/ -// lessc --global-var='pdfjsImagePath="../images/pdf.js-viewer"' viewer.less viewer.css -// ``` - -.pdfjs .textLayer { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - overflow: hidden; - opacity: 0.2; -} -.pdfjs .textLayer > div { - color: transparent; - position: absolute; - white-space: pre; - cursor: text; - -webkit-transform-origin: 0 0; - -moz-transform-origin: 0 0; - -o-transform-origin: 0 0; - -ms-transform-origin: 0 0; - transform-origin: 0 0; -} -.pdfjs .textLayer .highlight { - margin: -1px; - padding: 1px; - background-color: #b400aa; - border-radius: 4px; -} -.pdfjs .textLayer .highlight.begin { - border-radius: 4px 0 0 4px; -} -.pdfjs .textLayer .highlight.end { - border-radius: 0 4px 4px 0; -} -.pdfjs .textLayer .highlight.middle { - border-radius: 0; -} -.pdfjs .textLayer .highlight.selected { - background-color: #006400; -} -.pdfjs .textLayer ::selection { - background: #00f; -} -.pdfjs .textLayer ::-moz-selection { - background: #00f; -} -.pdfjs .pdfViewer .canvasWrapper { - overflow: hidden; -} -.pdfjs .pdfViewer .page { - direction: ltr; - width: 816px; - height: 1056px; - margin: 1px auto -8px; - position: relative; - overflow: visible; - border: 9px solid transparent; - background-clip: content-box; - border-image: url('../images/pdf.js-viewer/shadow.png') 9 9 repeat; - background-color: #fff; -} -body { - height: 100%; -} -.pdfjs .pdfViewer.removePageBorders .page { - margin: 0 auto 10px; - border: none; -} -.pdfjs .pdfViewer .page canvas { - margin: 0; - display: block; -} -.pdfjs .pdfViewer .page .loadingIcon { - position: absolute; - display: block; - left: 0; - top: 0; - right: 0; - bottom: 0; - background: url('../images/pdf.js-viewer/loading-icon.gif') center no-repeat; -} -.pdfjs .pdfViewer .page .annotLink > a:hover { - opacity: .2; - background: #ff0; - box-shadow: 0 2px 10px #ff0; -} -.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page { - margin-bottom: 100%; - border: 0; -} -.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page { - margin-bottom: 100%; - border: 0; -} -.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page { - margin-bottom: 100%!important; - border: 0; -} -.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page { - margin-bottom: 100%; - border: 0; -} -.pdfjs .pdfViewer .page .annotText > img { - position: absolute; - cursor: pointer; -} -.pdfjs .pdfViewer .page .annotTextContentWrapper { - position: absolute; - width: 20em; -} -.pdfjs .pdfViewer .page .annotTextContent { - z-index: 200; - float: left; - max-width: 20em; - background-color: #FF9; - box-shadow: 0 2px 5px #333; - border-radius: 2px; - padding: .6em; - cursor: pointer; -} -.pdfjs .pdfViewer .page .annotTextContent > h1 { - font-size: 1em; - border-bottom: 1px solid #000; - padding-bottom: 0.2em; -} -.pdfjs .pdfViewer .page .annotTextContent > p { - padding-top: 0.2em; -} -.pdfjs .pdfViewer .page .annotLink > a { - position: absolute; - font-size: 1em; - top: 0; - left: 0; - width: 100%; - height: 100%; -} -.pdfjs .pdfViewer .page .annotLink > a { - background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA\ LAAAAAABAAEAAAIBRAA7") 0 0 repeat; -} -.pdfjs * { - padding: 0; - margin: 0; -} -html { - height: 100%; - font-size: 10px; -} -.pdfjs input, -.pdfjs button, -.pdfjs select { - font: message-box; - outline: none; -} -.pdfjs .hidden { - display: none !important; -} -.pdfjs [hidden] { - display: none !important; -} -.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen { - top: 0; - border-top: 2px solid transparent; - background-color: #000; - width: 100%; - height: 100%; - overflow: hidden; - cursor: none; - -webkit-user-select: none; -} -.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen { - top: 0; - border-top: 2px solid transparent; - background-color: #000; - width: 100%; - height: 100%; - overflow: hidden; - cursor: none; - -moz-user-select: none; -} -.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen { - top: 0!important; - border-top: 2px solid transparent; - width: 100%; - height: 100%; - overflow: hidden!important; - cursor: none; - -ms-user-select: none; -} -.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop { - background-color: #000; -} -.pdfjs #viewerContainer.pdfPresentationMode:fullscreen { - top: 0; - border-top: 2px solid transparent; - background-color: #000; - width: 100%; - height: 100%; - overflow: hidden; - cursor: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; -} -.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) { - display: none; -} -.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) { - display: none; -} -.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) { - display: none !important; -} -.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) { - display: none; -} -.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div { - cursor: none; -} -.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div { - cursor: none; -} -.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div { - cursor: none; -} -.pdfjs .pdfPresentationMode:fullscreen .textLayer > div { - cursor: none; -} -.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *, -.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div { - cursor: default; -} -.pdfjs .outerCenter { - pointer-events: none; - position: relative; -} -html[dir='ltr'] .pdfjs .outerCenter { - float: right; - right: 50%; -} -html[dir='rtl'] .pdfjs .outerCenter { - float: left; - left: 50%; -} -.pdfjs .innerCenter { - pointer-events: auto; - position: relative; -} -html[dir='ltr'] .pdfjs .innerCenter { - float: right; - right: -50%; -} -html[dir='rtl'] .pdfjs .innerCenter { - float: left; - left: -50%; -} -.pdfjs #outerContainer { - width: 100%; - height: 100%; - position: relative; - background-color: #404040; - background-image: url('../images/pdf.js-viewer/texture.png'); -} -.pdfjs #sidebarContainer { - position: absolute; - top: 0; - bottom: 0; - width: 200px; - visibility: hidden; - -webkit-transition-duration: 200ms; - -webkit-transition-timing-function: ease; - transition-duration: 200ms; - transition-timing-function: ease; -} -html[dir='ltr'] .pdfjs #sidebarContainer { - -webkit-transition-property: left; - transition-property: left; - left: -200px; -} -html[dir='rtl'] .pdfjs #sidebarContainer { - -webkit-transition-property: right; - transition-property: right; - right: -200px; -} -.pdfjs #outerContainer.sidebarMoving > #sidebarContainer, -.pdfjs #outerContainer.sidebarOpen > #sidebarContainer { - visibility: visible; -} -html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { - left: 0; -} -html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { - right: 0; -} -.pdfjs #mainContainer { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - min-width: 320px; - -webkit-transition-duration: 200ms; - -webkit-transition-timing-function: ease; - transition-duration: 200ms; - transition-timing-function: ease; -} -html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - -webkit-transition-property: left; - transition-property: left; - left: 200px; -} -html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - -webkit-transition-property: right; - transition-property: right; - right: 200px; -} -.pdfjs #sidebarContent { - top: 32px; - bottom: 0; - overflow: auto; - -webkit-overflow-scrolling: touch; - position: absolute; - width: 200px; - background-color: rgba(0, 0, 0, 0.1); -} -html[dir='ltr'] .pdfjs #sidebarContent { - left: 0; - box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25); -} -html[dir='rtl'] .pdfjs #sidebarContent { - right: 0; - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25); -} -.pdfjs #viewerContainer { - overflow: auto; - -webkit-overflow-scrolling: touch; - position: absolute; - top: 32px; - right: 0; - bottom: 0; - left: 0; - outline: none; -} -html[dir='ltr'] .pdfjs #viewerContainer { - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05); -} -html[dir='rtl'] .pdfjs #viewerContainer { - box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05); -} -.pdfjs .toolbar { - position: relative; - left: 0; - right: 0; - // z-index: 9999; - cursor: default; -} -.pdfjs #toolbarContainer { - width: 100%; -} -.pdfjs #toolbarSidebar { - width: 200px; - height: 32px; - background-color: #424242; - background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95)); -} -html[dir='ltr'] .pdfjs #toolbarSidebar { - box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); -} -html[dir='rtl'] .pdfjs #toolbarSidebar { - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); -} -.pdfjs #toolbarContainer, -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - position: relative; - height: 32px; - background-color: #474747; - background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); -} -html[dir='ltr'] .pdfjs #toolbarContainer, -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); -} -html[dir='rtl'] .pdfjs #toolbarContainer, -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); -} -.pdfjs #toolbarViewer { - height: 32px; -} -.pdfjs #loadingBar { - position: relative; - width: 100%; - height: 4px; - background-color: #333; - border-bottom: 1px solid #333; -} -.pdfjs #loadingBar .progress { - position: absolute; - top: 0; - left: 0; - width: 0; - height: 100%; - background-color: #ddd; - overflow: hidden; - -webkit-transition: width 200ms; - transition: width 200ms; -} -@-webkit-keyframes progressIndeterminate { - 0% { - left: 0; - } - 50% { - left: 100%; - } - 100% { - left: 100%; - } -} -@keyframes progressIndeterminate { - 0% { - left: 0; - } - 50% { - left: 100%; - } - 100% { - left: 100%; - } -} -.pdfjs #loadingBar .progress.indeterminate { - background-color: #999; - -webkit-transition: none; - transition: none; -} -.pdfjs #loadingBar .indeterminate .glimmer { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 50px; - background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%); - background-size: 100% 100%; - background-repeat: no-repeat; - -webkit-animation: progressIndeterminate 2s linear infinite; - animation: progressIndeterminate 2s linear infinite; -} -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - top: 32px; - position: absolute; - z-index: 10000; - height: 32px; - min-width: 16px; - padding: 0 6px; - margin: 4px 2px; - color: #d9d9d9; - font-size: 12px; - line-height: 14px; - text-align: left; - cursor: default; -} -html[dir='ltr'] .pdfjs .findbar { - left: 68px; -} -html[dir='rtl'] .pdfjs .findbar { - right: 68px; -} -.pdfjs .findbar label { - -webkit-user-select: none; - -moz-user-select: none; -} -.pdfjs #findInput[data-status="pending"] { - background-image: url('../images/pdf.js-viewer/loading-small.png'); - background-repeat: no-repeat; - background-position: right; -} -html[dir='rtl'] .pdfjs #findInput[data-status="pending"] { - background-position: left; -} -.pdfjs .secondaryToolbar { - padding: 6px; - height: auto; - z-index: 30000; -} -html[dir='ltr'] .pdfjs .secondaryToolbar { - right: 4px; -} -html[dir='rtl'] .pdfjs .secondaryToolbar { - left: 4px; -} -.pdfjs #secondaryToolbarButtonContainer { - max-width: 200px; - max-height: 400px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - margin-bottom: -4px; -} -.pdfjs .doorHanger, -.pdfjs .doorHangerRight { - border: 1px solid rgba(0, 0, 0, 0.5); - border-radius: 2px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -} -.pdfjs .doorHanger:after, -.pdfjs .doorHanger:before, -.pdfjs .doorHangerRight:after, -.pdfjs .doorHangerRight:before { - bottom: 100%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} -.pdfjs .doorHanger:after, -.pdfjs .doorHangerRight:after { - border-bottom-color: rgba(82, 82, 82, 0.99); - border-width: 8px; -} -.pdfjs .doorHanger:before, -.pdfjs .doorHangerRight:before { - border-bottom-color: rgba(0, 0, 0, 0.5); - border-width: 9px; -} -html[dir='ltr'] .pdfjs .doorHanger:after, -html[dir='rtl'] .pdfjs .doorHangerRight:after { - left: 13px; - margin-left: -8px; -} -html[dir='ltr'] .pdfjs .doorHanger:before, -html[dir='rtl'] .pdfjs .doorHangerRight:before { - left: 13px; - margin-left: -9px; -} -html[dir='rtl'] .pdfjs .doorHanger:after, -html[dir='ltr'] .pdfjs .doorHangerRight:after { - right: 13px; - margin-right: -8px; -} -html[dir='rtl'] .pdfjs .doorHanger:before, -html[dir='ltr'] .pdfjs .doorHangerRight:before { - right: 13px; - margin-right: -9px; -} -.pdfjs #findMsg { - font-style: italic; - color: #A6B7D0; -} -.pdfjs #findInput.notFound { - background-color: #f66; -} -html[dir='ltr'] .pdfjs #toolbarViewerLeft { - margin-left: -1px; -} -html[dir='rtl'] .pdfjs #toolbarViewerRight { - margin-right: -1px; -} -html[dir='ltr'] .pdfjs #toolbarViewerLeft, -html[dir='rtl'] .pdfjs #toolbarViewerRight { - position: absolute; - top: 0; - left: 0; -} -html[dir='ltr'] .pdfjs #toolbarViewerRight, -html[dir='rtl'] .pdfjs #toolbarViewerLeft { - position: absolute; - top: 0; - right: 0; -} -html[dir='ltr'] .pdfjs #toolbarViewerLeft > *, -html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *, -html[dir='ltr'] .pdfjs #toolbarViewerRight > *, -html[dir='ltr'] .pdfjs .findbar > * { - position: relative; - float: left; -} -html[dir='rtl'] .pdfjs #toolbarViewerLeft > *, -html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *, -html[dir='rtl'] .pdfjs #toolbarViewerRight > *, -html[dir='rtl'] .pdfjs .findbar > * { - position: relative; - float: right; -} -html[dir='ltr'] .pdfjs .splitToolbarButton { - margin: 3px 2px 4px 0; - display: inline-block; -} -html[dir='rtl'] .pdfjs .splitToolbarButton { - margin: 3px 0 4px 2px; - display: inline-block; -} -html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton { - border-radius: 0; - float: left; -} -html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton { - border-radius: 0; - float: right; -} -.pdfjs .toolbarButton, -.pdfjs .secondaryToolbarButton, -.pdfjs .overlayButton { - border: 0 none; - background: none; - width: 32px; - height: 25px; -} -.pdfjs .toolbarButton > span { - display: inline-block; - width: 0; - height: 0; - overflow: hidden; -} -.pdfjs .toolbarButton[disabled], -.pdfjs .secondaryToolbarButton[disabled], -.pdfjs .overlayButton[disabled] { - opacity: 0.5; -} -.pdfjs .toolbarButton.group { - margin-right: 0; -} -.pdfjs .splitToolbarButton.toggled .toolbarButton { - margin: 0; -} -.pdfjs .splitToolbarButton:hover > .toolbarButton, -.pdfjs .splitToolbarButton:focus > .toolbarButton, -.pdfjs .splitToolbarButton.toggled > .toolbarButton, -.pdfjs .toolbarButton.textButton { - background-color: rgba(0, 0, 0, 0.12); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.35); - border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 150ms; - -webkit-transition-timing-function: ease; - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; -} -.pdfjs .splitToolbarButton > .toolbarButton:hover, -.pdfjs .splitToolbarButton > .toolbarButton:focus, -.pdfjs .dropdownToolbarButton:hover, -.pdfjs .overlayButton:hover, -.pdfjs .toolbarButton.textButton:hover, -.pdfjs .toolbarButton.textButton:focus { - background-color: rgba(0, 0, 0, 0.2); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05); - z-index: 199; -} -.pdfjs .splitToolbarButton > .toolbarButton { - position: relative; -} -html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child, -html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child { - position: relative; - margin: 0; - margin-right: -1px; - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - border-right-color: transparent; -} -html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child, -html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child { - position: relative; - margin: 0; - margin-left: -1px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - border-left-color: transparent; -} -.pdfjs .splitToolbarButtonSeparator { - padding: 8px 0; - width: 1px; - background-color: rgba(0, 0, 0, 0.5); - z-index: 99; - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); - display: inline-block; - margin: 5px 0; -} -html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator { - float: left; -} -html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator { - float: right; -} -.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator, -.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator { - padding: 12px 0; - margin: 1px 0; - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03); - -webkit-transition-property: padding; - -webkit-transition-duration: 10ms; - -webkit-transition-timing-function: ease; - transition-property: padding; - transition-duration: 10ms; - transition-timing-function: ease; -} -.pdfjs .toolbarButton, -.pdfjs .dropdownToolbarButton, -.pdfjs .secondaryToolbarButton, -.pdfjs .overlayButton { - min-width: 16px; - padding: 2px 6px 0; - border: 1px solid transparent; - border-radius: 2px; - color: rgba(255, 255, 255, 0.8); - font-size: 12px; - line-height: 14px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - cursor: default; - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 150ms; - -webkit-transition-timing-function: ease; - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; -} -html[dir='ltr'] .pdfjs .toolbarButton, -html[dir='ltr'] .pdfjs .overlayButton, -html[dir='ltr'] .pdfjs .dropdownToolbarButton { - margin: 3px 2px 4px 0; -} -html[dir='rtl'] .pdfjs .toolbarButton, -html[dir='rtl'] .pdfjs .overlayButton, -html[dir='rtl'] .pdfjs .dropdownToolbarButton { - margin: 3px 0 4px 2px; -} -.pdfjs .toolbarButton:hover, -.pdfjs .toolbarButton:focus, -.pdfjs .dropdownToolbarButton, -.pdfjs .overlayButton, -.pdfjs .secondaryToolbarButton:hover, -.pdfjs .secondaryToolbarButton:focus { - background-color: rgba(0, 0, 0, 0.12); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.35); - border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); -} -.pdfjs .toolbarButton:hover:active, -.pdfjs .overlayButton:hover:active, -.pdfjs .dropdownToolbarButton:hover:active, -.pdfjs .secondaryToolbarButton:hover:active { - background-color: rgba(0, 0, 0, 0.2); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 10ms; - -webkit-transition-timing-function: linear; - transition-property: background-color, border-color, box-shadow; - transition-duration: 10ms; - transition-timing-function: linear; -} -.pdfjs .toolbarButton.toggled, -.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled, -.pdfjs .secondaryToolbarButton.toggled { - background-color: rgba(0, 0, 0, 0.3); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 10ms; - -webkit-transition-timing-function: linear; - transition-property: background-color, border-color, box-shadow; - transition-duration: 10ms; - transition-timing-function: linear; -} -.pdfjs .toolbarButton.toggled:hover:active, -.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active, -.pdfjs .secondaryToolbarButton.toggled:hover:active { - background-color: rgba(0, 0, 0, 0.4); - border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05); -} -.pdfjs .dropdownToolbarButton { - width: 120px; - max-width: 120px; - padding: 0; - overflow: hidden; - background: url('../images/pdf.js-viewer/toolbarButton-menuArrows.png') no-repeat; -} -html[dir='ltr'] .pdfjs .dropdownToolbarButton { - background-position: 95%; -} -html[dir='rtl'] .pdfjs .dropdownToolbarButton { - background-position: 5%; -} -.pdfjs .dropdownToolbarButton > select { - min-width: 140px; - font-size: 12px; - color: #f2f2f2; - margin: 0; - padding: 3px 2px 2px; - border: none; - background: rgba(0, 0, 0, 0); -} -.pdfjs .dropdownToolbarButton > select > option { - background: #3d3d3d; -} -.pdfjs #customScaleOption { - display: none; -} -.pdfjs #pageWidthOption { - border-bottom: 1px rgba(255, 255, 255, 0.5) solid; -} -html[dir='ltr'] .pdfjs .splitToolbarButton:first-child, -html[dir='ltr'] .pdfjs .toolbarButton:first-child, -html[dir='rtl'] .pdfjs .splitToolbarButton:last-child, -html[dir='rtl'] .pdfjs .toolbarButton:last-child { - margin-left: 4px; -} -html[dir='ltr'] .pdfjs .splitToolbarButton:last-child, -html[dir='ltr'] .pdfjs .toolbarButton:last-child, -html[dir='rtl'] .pdfjs .splitToolbarButton:first-child, -html[dir='rtl'] .pdfjs .toolbarButton:first-child { - margin-right: 4px; -} -.pdfjs .toolbarButtonSpacer { - width: 30px; - display: inline-block; - height: 1px; -} -.pdfjs .toolbarButtonFlexibleSpacer { - -webkit-box-flex: 1; - -moz-box-flex: 1; - min-width: 30px; -} -html[dir='ltr'] .pdfjs #findPrevious { - margin-left: 3px; -} -html[dir='ltr'] .pdfjs #findNext { - margin-right: 3px; -} -html[dir='rtl'] .pdfjs #findPrevious { - margin-right: 3px; -} -html[dir='rtl'] .pdfjs #findNext { - margin-left: 3px; -} -.pdfjs .toolbarButton::before, -.pdfjs .secondaryToolbarButton::before { - position: absolute; - display: inline-block; - top: 4px; - left: 7px; -} -html[dir="ltr"] .pdfjs .secondaryToolbarButton::before { - left: 4px; -} -html[dir="rtl"] .pdfjs .secondaryToolbarButton::before { - right: 4px; -} -html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle.png'); -} -html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png'); -} -html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png'); -} -html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png'); -} -html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { - content: url('../images/pdf.js-viewer/findbarButton-previous.png'); -} -html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { - content: url('../images/pdf.js-viewer/findbarButton-previous-rtl.png'); -} -html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { - content: url('../images/pdf.js-viewer/findbarButton-next.png'); -} -html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { - content: url('../images/pdf.js-viewer/findbarButton-next-rtl.png'); -} -html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageUp.png'); -} -html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png'); -} -html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageDown.png'); -} -html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png'); -} -.pdfjs .toolbarButton.zoomOut::before { - content: url('../images/pdf.js-viewer/toolbarButton-zoomOut.png'); -} -.pdfjs .toolbarButton.zoomIn::before { - content: url('../images/pdf.js-viewer/toolbarButton-zoomIn.png'); -} -.pdfjs .toolbarButton.presentationMode::before, -.pdfjs .secondaryToolbarButton.presentationMode::before { - content: url('../images/pdf.js-viewer/toolbarButton-presentationMode.png'); -} -.pdfjs .toolbarButton.print::before, -.pdfjs .secondaryToolbarButton.print::before { - content: url('../images/pdf.js-viewer/toolbarButton-print.png'); -} -.pdfjs .toolbarButton.openFile::before, -.pdfjs .secondaryToolbarButton.openFile::before { - content: url('../images/pdf.js-viewer/toolbarButton-openFile.png'); -} -.pdfjs .toolbarButton.download::before, -.pdfjs .secondaryToolbarButton.download::before { - content: url('../images/pdf.js-viewer/toolbarButton-download.png'); -} -.pdfjs .toolbarButton.bookmark, -.pdfjs .secondaryToolbarButton.bookmark { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - outline: none; - padding-top: 4px; - text-decoration: none; -} -.pdfjs .secondaryToolbarButton.bookmark { - padding-top: 5px; -} -.pdfjs .bookmark[href='#'] { - opacity: .5; - pointer-events: none; -} -.pdfjs .toolbarButton.bookmark::before, -.pdfjs .secondaryToolbarButton.bookmark::before { - content: url('../images/pdf.js-viewer/toolbarButton-bookmark.png'); -} -.pdfjs #viewThumbnail.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail.png'); -} -html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewOutline.png'); -} -html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png'); -} -.pdfjs #viewAttachments.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments.png'); -} -.pdfjs #viewFind.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-search.png'); -} -.pdfjs .secondaryToolbarButton { - position: relative; - margin: 0 0 4px; - padding: 3px 0 1px; - height: auto; - min-height: 25px; - width: auto; - min-width: 100%; - white-space: normal; -} -html[dir="ltr"] .pdfjs .secondaryToolbarButton { - padding-left: 24px; - text-align: left; -} -html[dir="rtl"] .pdfjs .secondaryToolbarButton { - padding-right: 24px; - text-align: right; -} -html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark { - padding-left: 27px; -} -html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark { - padding-right: 27px; -} -html[dir="ltr"] .pdfjs .secondaryToolbarButton > span { - padding-right: 4px; -} -html[dir="rtl"] .pdfjs .secondaryToolbarButton > span { - padding-left: 4px; -} -.pdfjs .secondaryToolbarButton.firstPage::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png'); -} -.pdfjs .secondaryToolbarButton.lastPage::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png'); -} -.pdfjs .secondaryToolbarButton.rotateCcw::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png'); -} -.pdfjs .secondaryToolbarButton.rotateCw::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png'); -} -.pdfjs .secondaryToolbarButton.handTool::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool.png'); -} -.pdfjs .secondaryToolbarButton.documentProperties::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png'); -} -.pdfjs .verticalToolbarSeparator { - display: block; - padding: 8px 0; - margin: 8px 4px; - width: 1px; - background-color: rgba(0, 0, 0, 0.5); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); -} -html[dir='ltr'] .pdfjs .verticalToolbarSeparator { - margin-left: 2px; -} -html[dir='rtl'] .pdfjs .verticalToolbarSeparator { - margin-right: 2px; -} -.pdfjs .horizontalToolbarSeparator { - display: block; - margin: 0 0 4px; - height: 1px; - width: 100%; - background-color: rgba(0, 0, 0, 0.5); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); -} -.pdfjs .toolbarField { - padding: 3px 6px; - margin: 4px 0; - border: 1px solid transparent; - border-radius: 2px; - background-color: rgba(255, 255, 255, 0.09); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.35); - border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - color: #f2f2f2; - font-size: 12px; - line-height: 14px; - outline-style: none; - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; -} -.pdfjs .toolbarField[type=checkbox] { - display: inline-block; - margin: 8px 0; -} -.pdfjs .toolbarField.pageNumber { - -moz-appearance: textfield; - min-width: 16px; - text-align: right; - width: 40px; -} -.pdfjs .toolbarField.pageNumber.visiblePageIsLoading { - background-image: url('../images/pdf.js-viewer/loading-small.png'); - background-repeat: no-repeat; - background-position: 1px; -} -.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button, -.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} -.pdfjs .toolbarField:hover { - background-color: rgba(255, 255, 255, 0.11); - border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45); -} -.pdfjs .toolbarField:focus { - background-color: rgba(255, 255, 255, 0.15); - border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9); -} -.pdfjs .toolbarLabel { - min-width: 16px; - padding: 3px 6px 3px 2px; - margin: 4px 2px 4px 0; - border: 1px solid transparent; - border-radius: 2px; - color: #d9d9d9; - font-size: 12px; - line-height: 14px; - text-align: left; - -webkit-user-select: none; - -moz-user-select: none; - cursor: default; -} -.pdfjs #thumbnailView { - position: absolute; - width: 120px; - top: 0; - bottom: 0; - padding: 10px 40px 0; - overflow: auto; - -webkit-overflow-scrolling: touch; -} -.pdfjs .thumbnail { - float: left; - margin-bottom: 5px; -} -.pdfjs #thumbnailView > a:last-of-type > .thumbnail { - margin-bottom: 10px; -} -.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) { - margin-bottom: 9px; -} -.pdfjs .thumbnail:not([data-loaded]) { - border: 1px dashed rgba(255, 255, 255, 0.5); - margin: -1px -1px 4px; -} -.pdfjs .thumbnailImage { - border: 1px solid transparent; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); - opacity: .8; - z-index: 99; - background-color: #fff; - background-clip: content-box; -} -.pdfjs .thumbnailSelectionRing { - border-radius: 2px; - padding: 7px; -} -.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage, -.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage { - opacity: 0.9; -} -.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing, -.pdfjs .thumbnail:hover > .thumbnailSelectionRing { - background-color: rgba(255, 255, 255, 0.15); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: rgba(255, 255, 255, 0.9); -} -.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage { - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5); - opacity: 1; -} -.pdfjs .thumbnail.selected > .thumbnailSelectionRing { - background-color: rgba(255, 255, 255, 0.3); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: #ffffff; -} -.pdfjs #outlineView, -.pdfjs #attachmentsView { - position: absolute; - width: 192px; - top: 0; - bottom: 0; - overflow: auto; - -webkit-overflow-scrolling: touch; - -webkit-user-select: none; - -moz-user-select: none; -} -.pdfjs #outlineView { - padding: 4px 4px 0; -} -.pdfjs #attachmentsView { - padding: 3px 4px 0; -} -html[dir='ltr'] .pdfjs .outlineItem > .outlineItems { - margin-left: 20px; -} -html[dir='rtl'] .pdfjs .outlineItem > .outlineItems { - margin-right: 20px; -} -.pdfjs .outlineItem > a, -.pdfjs .attachmentsItem > button { - text-decoration: none; - display: inline-block; - min-width: 95%; - height: auto; - margin-bottom: 1px; - border-radius: 2px; - color: rgba(255, 255, 255, 0.8); - font-size: 13px; - line-height: 15px; - -moz-user-select: none; - white-space: normal; -} -.pdfjs .attachmentsItem > button { - border: 0 none; - background: none; - cursor: pointer; - width: 100%; -} -html[dir='ltr'] .pdfjs .outlineItem > a { - padding: 2px 0 5px 10px; -} -html[dir='ltr'] .pdfjs .attachmentsItem > button { - padding: 2px 0 3px 7px; - text-align: left; -} -html[dir='rtl'] .pdfjs .outlineItem > a { - padding: 2px 10px 5px 0; -} -html[dir='rtl'] .pdfjs .attachmentsItem > button { - padding: 2px 7px 3px 0; - text-align: right; -} -.pdfjs .outlineItem > a:hover, -.pdfjs .attachmentsItem > button:hover { - background-color: rgba(255, 255, 255, 0.02); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: rgba(255, 255, 255, 0.9); -} -.pdfjs .outlineItem.selected { - background-color: rgba(255, 255, 255, 0.08); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: #ffffff; -} -.pdfjs .noResults { - font-size: 12px; - color: rgba(255, 255, 255, 0.8); - font-style: italic; - cursor: default; -} -.pdfjs ::selection { - background: rgba(0, 0, 255, 0.3); -} -.pdfjs ::-moz-selection { - background: rgba(0, 0, 255, 0.3); -} -.pdfjs #errorWrapper { - background: none repeat scroll 0 0 #F55; - color: #fff; - left: 0; - position: absolute; - right: 0; - z-index: 1000; - padding: 3px; - font-size: 0.8em; -} -.pdfjs .loadingInProgress #errorWrapper { - top: 37px; -} -.pdfjs #errorMessageLeft { - float: left; -} -.pdfjs #errorMessageRight { - float: right; -} -.pdfjs #errorMoreInfo { - background-color: #FFF; - color: #000; - padding: 3px; - margin: 3px; - width: 98%; -} -.pdfjs .overlayButton { - width: auto; - margin: 3px 4px 2px!important; - padding: 2px 6px 3px; -} -.pdfjs #overlayContainer { - display: table; - position: absolute; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.2); - z-index: 40000; -} -.pdfjs #overlayContainer > * { - overflow: auto; - -webkit-overflow-scrolling: touch; -} -.pdfjs #overlayContainer > .container { - display: table-cell; - vertical-align: middle; - text-align: center; -} -.pdfjs #overlayContainer > .container > .dialog { - display: inline-block; - padding: 15px; - border-spacing: 4px; - color: #d9d9d9; - font-size: 12px; - line-height: 14px; - background-color: #474747; - background-image: url('../images/pdf.js-viewer/texture.png'), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); - border: 1px solid rgba(0, 0, 0, 0.5); - border-radius: 4px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -} -.pdfjs .dialog > .row { - display: table-row; -} -.pdfjs .dialog > .row > * { - display: table-cell; -} -.pdfjs .dialog .toolbarField { - margin: 5px 0; -} -.pdfjs .dialog .separator { - display: block; - margin: 4px 0; - height: 1px; - width: 100%; - background-color: rgba(0, 0, 0, 0.5); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); -} -.pdfjs .dialog .buttonRow { - text-align: center; - vertical-align: middle; -} -.pdfjs #passwordOverlay > .dialog { - text-align: center; -} -.pdfjs #passwordOverlay .toolbarField { - width: 200px; -} -.pdfjs #documentPropertiesOverlay > .dialog { - text-align: left; -} -.pdfjs #documentPropertiesOverlay .row > * { - min-width: 100px; -} -html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * { - text-align: left; -} -html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * { - text-align: right; -} -.pdfjs #documentPropertiesOverlay .row > span { - width: 125px; - word-wrap: break-word; -} -.pdfjs #documentPropertiesOverlay .row > p { - max-width: 225px; - word-wrap: break-word; -} -.pdfjs #documentPropertiesOverlay .buttonRow { - margin-top: 10px; -} -.pdfjs .clearBoth { - clear: both; -} -.pdfjs .fileInput { - background: #fff; - color: #000; - margin-top: 5px; - visibility: hidden; - position: fixed; - right: 0; - top: 0; -} -.pdfjs #PDFBug { - background: none repeat scroll 0 0 #fff; - border: 1px solid #666; - position: fixed; - top: 32px; - right: 0; - bottom: 0; - font-size: 10px; - padding: 0; - width: 300px; -} -.pdfjs #PDFBug .controls { - background: #EEE; - border-bottom: 1px solid #666; - padding: 3px; -} -.pdfjs #PDFBug .panels { - bottom: 0; - left: 0; - overflow: auto; - -webkit-overflow-scrolling: touch; - position: absolute; - right: 0; - top: 27px; -} -.pdfjs #PDFBug button.active { - font-weight: 700; -} -.pdfjs .debuggerShowText { - background: none repeat scroll 0 0 #ff0; - color: blue; -} -.pdfjs .debuggerHideText:hover { - background: none repeat scroll 0 0 #ff0; -} -.pdfjs #PDFBug .stats { - font-family: courier; - font-size: 10px; - white-space: pre; -} -.pdfjs #PDFBug .stats .title { - font-weight: 700; -} -.pdfjs #PDFBug table { - font-size: 10px; -} -.pdfjs #viewer.textLayer-visible .textLayer > div, -.pdfjs #viewer.textLayer-hover .textLayer > div:hover { - background-color: #fff; - color: #000; -} -.pdfjs #viewer.textLayer-shadow .textLayer > div { - background-color: rgba(255, 255, 255, 0.6); - color: #000; -} -.pdfjs .grab-to-pan-grab { - cursor: url('../images/pdf.js-viewer/grab.cur'), move !important; - cursor: -webkit-grab !important; - cursor: -moz-grab !important; - cursor: grab !important; -} -.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) { - cursor: inherit !important; -} -.pdfjs .grab-to-pan-grab:active, -.pdfjs .grab-to-pan-grabbing { - cursor: url('../images/pdf.js-viewer/grabbing.cur'), move !important; - cursor: -webkit-grabbing !important; - cursor: -moz-grabbing !important; - cursor: grabbing!important; - position: fixed; - background: transparent; - display: block; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; - z-index: 50000; -} -@page { - margin: 0; -} -.pdfjs #printContainer { - display: none; -} -@media screen and (min-resolution: 2dppx) { - .pdfjs .toolbarButton::before { - -webkit-transform: scale(0.5); - transform: scale(0.5); - top: -5px; - } - .pdfjs .secondaryToolbarButton::before { - -webkit-transform: scale(0.5); - transform: scale(0.5); - top: -4px; - } - html[dir='ltr'] .pdfjs .toolbarButton::before, - html[dir='rtl'] .pdfjs .toolbarButton::before { - left: -1px; - } - html[dir='ltr'] .pdfjs .secondaryToolbarButton::before { - left: -2px; - } - html[dir='rtl'] .pdfjs .secondaryToolbarButton::before { - left: 186px; - } - .pdfjs .toolbarField.pageNumber.visiblePageIsLoading, - .pdfjs #findInput[data-status="pending"] { - background-image: url('../images/pdf.js-viewer/loading-small@2x.png'); - background-size: 16px 17px; - } - .pdfjs .dropdownToolbarButton { - background: url('../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png') no-repeat; - background-size: 7px 16px; - } - html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png'); - } - html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png'); - } - html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png'); - } - html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url('../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png'); - } - html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { - content: url('../images/pdf.js-viewer/findbarButton-previous@2x.png'); - } - html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { - content: url('../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png'); - } - html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { - content: url('../images/pdf.js-viewer/findbarButton-next@2x.png'); - } - html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { - content: url('../images/pdf.js-viewer/findbarButton-next-rtl@2x.png'); - } - html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageUp@2x.png'); - } - html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png'); - } - html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageDown@2x.png'); - } - html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { - content: url('../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png'); - } - .pdfjs .toolbarButton.zoomIn::before { - content: url('../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png'); - } - .pdfjs .toolbarButton.zoomOut::before { - content: url('../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png'); - } - .pdfjs .toolbarButton.presentationMode::before, - .pdfjs .secondaryToolbarButton.presentationMode::before { - content: url('../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png'); - } - .pdfjs .toolbarButton.print::before, - .pdfjs .secondaryToolbarButton.print::before { - content: url('../images/pdf.js-viewer/toolbarButton-print@2x.png'); - } - .pdfjs .toolbarButton.openFile::before, - .pdfjs .secondaryToolbarButton.openFile::before { - content: url('../images/pdf.js-viewer/toolbarButton-openFile@2x.png'); - } - .pdfjs .toolbarButton.download::before, - .pdfjs .secondaryToolbarButton.download::before { - content: url('../images/pdf.js-viewer/toolbarButton-download@2x.png'); - } - .pdfjs .toolbarButton.bookmark::before, - .pdfjs .secondaryToolbarButton.bookmark::before { - content: url('../images/pdf.js-viewer/toolbarButton-bookmark@2x.png'); - } - .pdfjs #viewThumbnail.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png'); - } - html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png'); - } - html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png'); - } - .pdfjs #viewAttachments.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png'); - } - .pdfjs #viewFind.toolbarButton::before { - content: url('../images/pdf.js-viewer/toolbarButton-search@2x.png'); - } - .pdfjs .secondaryToolbarButton.firstPage::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png'); - } - .pdfjs .secondaryToolbarButton.lastPage::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png'); - } - .pdfjs .secondaryToolbarButton.rotateCcw::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png'); - } - .pdfjs .secondaryToolbarButton.rotateCw::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png'); - } - .pdfjs .secondaryToolbarButton.handTool::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png'); - } - .pdfjs .secondaryToolbarButton.documentProperties::before { - content: url('../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png'); - } -} -@media print { - body { - background: transparent none; - } - .pdfjs #sidebarContainer, - .pdfjs #secondaryToolbar, - .pdfjs .toolbar, - .pdfjs #loadingBox, - .pdfjs #errorWrapper, - .pdfjs .textLayer { - display: none; - } - .pdfjs #viewerContainer { - overflow: visible; - } - .pdfjs #mainContainer, - .pdfjs #viewerContainer, - .pdfjs .page, - .pdfjs .page canvas { - position: static; - padding: 0; - margin: 0; - } - .pdfjs .page { - float: left; - display: none; - border: none; - box-shadow: none; - background-clip: content-box; - background-color: #fff; - } - .pdfjs .page[data-loaded] { - display: block; - } - .pdfjs .fileInput { - display: none; - } - body[data-mozPrintCallback] .pdfjs #outerContainer { - display: none; - } - body[data-mozPrintCallback] .pdfjs #printContainer { - display: block; - } - .pdfjs #printContainer > div { - position: relative; - top: 0; - left: 0; - overflow: hidden; - } - .pdfjs #printContainer canvas { - display: block; - } -} -.pdfjs .visibleLargeView, -.pdfjs .visibleMediumView, -.pdfjs .visibleSmallView { - display: none; -} -@media all and (max-width: 960px) { - html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter { - float: left; - left: 205px; - } - html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter { - float: right; - right: 205px; - } -} -@media all and (max-width: 900px) { - .pdfjs .sidebarOpen .hiddenLargeView { - display: none; - } - .pdfjs .sidebarOpen .visibleLargeView { - display: inherit; - } -} -@media all and (max-width: 860px) { - .pdfjs .sidebarOpen .hiddenMediumView { - display: none; - } - .pdfjs .sidebarOpen .visibleMediumView { - display: inherit; - } -} -@media all and (max-width: 770px) { - .pdfjs #sidebarContainer { - top: 32px; - z-index: 100; - } - .pdfjs .loadingInProgress #sidebarContainer { - top: 37px; - } - .pdfjs #sidebarContent { - top: 32px; - background-color: rgba(0, 0, 0, 0.7); - } - html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - left: 0; - } - html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - right: 0; - } - html[dir='ltr'] .pdfjs .outerCenter { - float: left; - left: 205px; - } - html[dir='rtl'] .pdfjs .outerCenter { - float: right; - right: 205px; - } - .pdfjs #outerContainer .hiddenLargeView, - .pdfjs #outerContainer .hiddenMediumView { - display: inherit; - } - .pdfjs #outerContainer .visibleLargeView, - .pdfjs #outerContainer .visibleMediumView { - display: none; - } -} -@media all and (max-width: 700px) { - .pdfjs #outerContainer .hiddenLargeView { - display: none; - } - .pdfjs #outerContainer .visibleLargeView { - display: inherit; - } -} -@media all and (max-width: 660px) { - .pdfjs #outerContainer .hiddenMediumView { - display: none; - } - .pdfjs #outerContainer .visibleMediumView { - display: inherit; - } -} -@media all and (max-width: 600px) { - .pdfjs .hiddenSmallView { - display: none; - } - .pdfjs .visibleSmallView { - display: inherit; - } - html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter, - html[dir='ltr'] .pdfjs .outerCenter { - left: 156px; - } - html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter, - html[dir='rtl'] .pdfjs .outerCenter { - right: 156px; - } - .pdfjs .toolbarButtonSpacer { - width: 0; - } -} -@media all and (max-width: 510px) { - .pdfjs #scaleSelectContainer, - .pdfjs #pageNumberLabel { - display: none; - } -} -/* should be hidden differently */ -#fileInput.fileInput { - display: none; -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-splitpane.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -$gray: gray; -$blue: hsla(204, 70%, 53%, 0.7); -$red: rgba(206, 0, 0, 0.7); -$green: rgba(0, 160, 27, .7); -$yellow: rgba(220, 200 , 0, .9); -$violet: rgba(255, 31, 255, .7); - -.wvSplitpane { - height: 100%; - padding: 7px 2px 2px 2px; - - // Anchor - position: relative; -} -.wvSplitpane__cell { - display: inline-block; - float: left; - height: 100%; - width: 100%; - - // Anchor - position: relative; -} -.wvSplitpane__cellBorder, -%wvSplitpane__cellBorder { - display: inline-block; - float: left; - height: calc(100% - 2px); - width: calc(100% - 2px); - - border: 2px dashed transparent; - - padding: 2px; - margin: 1px; -} -.wvSplitpane__cellBorder--selected { - @extend .wvSplitpane__cellBorder; - - // Add border - border: 2px solid $blue; -} - -// Color modifiers -.wvSplitpane__cellBorder--blue { - @extend .wvSplitpane__cellBorder; - border-color: $blue; -} - -.wvSplitpane__cellBorder--red { - @extend .wvSplitpane__cellBorder; - border-color: $red; -} - -.wvSplitpane__cellBorder--green { - @extend .wvSplitpane__cellBorder; - border-color: $green; -} - -.wvSplitpane__cellBorder--yellow { - @extend .wvSplitpane__cellBorder; - border-color: $yellow; -} - -.wvSplitpane__cellBorder--violet { - @extend .wvSplitpane__cellBorder; - border-color: $violet; -} - -// Make sure the pane keeps its size -wv-pane-policy { - display: block; - width: 100%; - height: 100%; - - > div[ng-transclude] { - display: block; - width: 100%; - height: 100%; - } -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-timeline-controls.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* wv-timeline-controls directive */ -.wv-timeline-controls { - padding: 0.5em 0.5em 0.5em 0.5em; - line-height: 1em; - background-color: rgba(0, 0, 0, 0.66); - - text-align: center; - - transition: color 500ms, background-color 500ms; -} - -.wv-timeline-controls:hover { - background-color: rgba(0, 0, 0, 0.9); -} - -// Used to make sure buttons doesn't break the style -.wv-timeline-controls-vertical-sizing { - display: inline-block; - line-height: 1em; - font-size: 1em; -} - -.wv-timeline-controls-vflip { - // flip only the icon - &:before, &:after{ - transform: scaleX(-1); - display: inline-block; - } -} - -.wv-timeline-controls-button { - display: inline-block; - height: 1em; - width: 1em; - line-height: 1em; - font-size: 1em; - margin: 0; - - user-select: none; - cursor: pointer; -} - -.wv-timeline-controls-input { - height: 1em; - width: 3em; - padding: 0; - padding-bottom: 1px; - box-sizing: content-box; - - border: none; - border-bottom: 1px solid hsla(35, 100%, 75%, 0.24); - background-color: transparent; - - text-align: right; -} - -// Display play button on the right side -.wv-timeline-controls-play-button-wrapper { - float: right; -} - -/* wv-play-button directive */ -.wv-play-button { - display: inline-block; - position: relative; - line-height: 1em; - - // This is for the boxing box - height: 3em; - width: 6em; - padding-bottom: 1em; - padding-left: 0.25em; - padding-right: 0.25em; -} - -.wv-play-button:hover .wv-play-button-config-position-handler { - visibility: visible; -} - -// This is a 0x0 div to set the position -.wv-play-button-config-position-handler { - visibility: hidden; - position: absolute; - bottom: 3em; - left: 1em; - right: 0.5em; - // z-index: 2; -} - -// The layout of play configuration -.wv-play-button-config { - position: absolute; - bottom: 0; - left: -6em; - width: 12em; - padding: 1em; - background-color: hsla(0,1,0, 0.5); -} - -/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */ - -.wv-play-button-config-framerate-wrapper { - display: inline-block; - margin: 0.25em 0 0.5em 0; -} -input[type="range"].wv-play-button-config-framerate { - /*removes default webkit styles*/ - -webkit-appearance: none; - - /*fix for FF unable to apply focus style bug */ - border: 1px solid white; - - /*required for proper track sizing in FF*/ - width: 10em; -} -input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track { - width: 10em; - height: 5px; - background: #ddd; - border: none; - border-radius: 3px; -} -input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb { - -webkit-appearance: none; - border: none; - height: 16px; - width: 16px; - border-radius: 50%; - background: goldenrod; - margin-top: -4px; -} -input[type="range"].wv-play-button-config-framerate:focus { - outline: none; -} -input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track { - background: #ccc; -} - -input[type="range"].wv-play-button-config-framerate::-moz-range-track { - width: 10em; - height: 5px; - background: #ddd; - border: none; - border-radius: 3px; -} -input[type="range"].wv-play-button-config-framerate::-moz-range-thumb { - border: none; - height: 16px; - width: 16px; - border-radius: 50%; - background: goldenrod; -} - -/*hide the outline behind the border*/ -input[type="range"].wv-play-button-config-framerate:-moz-focusring{ - outline: 1px solid white; - outline-offset: -1px; -} - -input[type="range"].wv-play-button-config-framerate::-ms-track { - width: 10em; - height: 5px; - - /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */ - background: transparent; - - /*leave room for the larger thumb to overflow with a transparent border */ - border-color: transparent; - border-width: 6px 0; - - /*remove default tick marks*/ - color: transparent; -} -input[type="range"].wv-play-button-config-framerate::-ms-fill-lower { - background: #777; - border-radius: 10px; -} -input[type="range"].wv-play-button-config-framerate::-ms-fill-upper { - background: #ddd; - border-radius: 10px; -} -input[type="range"].wv-play-button-config-framerate::-ms-thumb { - border: none; - height: 16px; - width: 16px; - border-radius: 50%; - background: goldenrod; -} -input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower { - background: #888; -} -input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper { - background: #ccc; -} \ No newline at end of file
--- a/StoneWebViewer/Resources/Styles/wv-timeline.scss Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -.wv-timeline { - position: relative; - height: 2em; // save 2px to display the "upper part of the currently selected image on the timeline" - &.reduced{ - height: 5px; - .wv-timeline-loading-bar-wrapper{ - width: 100%; - height: 100%; - } - } -} - -.wv-timeline-controls-wrapper { - position: absolute; - left: 0; - bottom: 0; - width: 16em; - height: 100%; - color: white; -} - -.wv-timeline-loading-bar-wrapper { - position: absolute; - right: 0; - bottom: 0; - width: calc(100% - 16em); - height: calc(100% + 2px); - - svg{ - position:absolute; - left:0; - top:0; - } -} \ No newline at end of file
--- a/StoneWebViewer/WebApplication/app.css Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4752 +0,0 @@ -.browsehappy { - margin: 0.2em 0; - background: #ccc; - color: #000; - padding: 0.2em 0; } - -.wv-html, .wv-body { - height: 100%; - width: 100%; - margin: 0; - padding: 0; - overflow: hidden; } - -.wv-body { - background-color: black; - color: white; - position: relative; - overflow: hidden; - font-family: "Open Sans", Helvetica, Arial, sans-serif; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - font-size: 13px; - font-weight: 400; - line-height: 1.49; - font-size-adjust: 100%; - -moz-osx-font-smoothing: grayscale !important; - font-smoothing: antialiased !important; - -webkit-font-smoothing: antialiased !important; } - -.wvLoadingScreen { - width: 100%; - height: 100%; - background-color: black; - position: fixed; - top: 0; - left: 0; - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; } - -.wvLoadingSpinner { - margin: 100px auto 0; - width: 70px; - text-align: center; } - -.wvLoadingSpinner > div { - width: 18px; - height: 18px; - background-color: #FFF; - border-radius: 100%; - display: inline-block; - -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both; - animation: sk-bouncedelay 1.4s infinite ease-in-out both; } - -.wvLoadingSpinner .bounce1 { - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; } - -.wvLoadingSpinner .bounce2 { - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; } - -@-webkit-keyframes sk-bouncedelay { - 0%, 80%, 100% { - -webkit-transform: scale(0); } - 40% { - -webkit-transform: scale(1); } } - -@keyframes sk-bouncedelay { - 0%, 80%, 100% { - -webkit-transform: scale(0); - transform: scale(0); } - 40% { - -webkit-transform: scale(1); - transform: scale(1); } } - -/* wvp-ui stuffs */ -wv-webviewer { - display: block; - height: 100%; - overflow: hidden; } - -.wvButton, .wvButton--rotate, .wvButton--vflip, .wvButton--underline, .fa.wvButton--underline, .wvButton--border, .wvButton--borderAndWhite { - outline: none; - background-color: transparent; - border: none; - border-radius: 0; - position: relative; - display: inline-block; - cursor: pointer; - font-variant: small-caps; - text-transform: lowercase; - text-align: center; - font-size: 1.3rem; - font-weight: 400; - color: #d9d9d9; - transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; - margin: 0; - min-width: 3rem; - padding: 0 10px; - line-height: 3.6rem; } - .wvButton:hover, .wvButton--rotate:hover, .wvButton--vflip:hover, .wvButton--underline:hover, .wvButton--border:hover, .wvButton--borderAndWhite:hover { - text-decoration: none; - color: white; } - .wvButton.wvLargeButton, .wvLargeButton.wvButton--rotate, .wvLargeButton.wvButton--vflip, .wvLargeButton.wvButton--underline, .wvLargeButton.wvButton--border, .wvLargeButton.wvButton--borderAndWhite { - font-size: 2rem; - line-height: 6.2rem; - padding: 0 20px; } - -.wvButton--rotate:before, .wvButton--rotate:after { - transform: rotate(90deg); - display: inline-block; } - -.wvButton--vflip:before, .wvButton--vflip:after { - transform: scaleX(-1); - display: inline-block; } - -.wvButton--underline, .fa.wvButton--underline { - position: relative; - background-color: inherit; - text-decoration: none; - text-align: left; - font-size: 1.2rem; - width: 3.2rem; - vertical-align: middle; - color: white; - opacity: 0.75; - border: none; - border-bottom: 2px solid rgba(255, 255, 255, 0.1); - top: 0px; } - .wvButton--underline.wvLargeButton, .fa.wvButton--underline.wvLargeButton { - font-size: 2rem; - width: 6.4rem; } - .wvButton--underline *, .fa.wvButton--underline * { - pointer-events: none; } - .wvButton--underline:hover, .wvButton--underline:active, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:active, .fa.wvButton--underline:focus { - outline: 0; } - .wvButton--underline:hover, .wvButton--underline:focus, .fa.wvButton--underline:hover, .fa.wvButton--underline:focus { - border-color: white; - opacity: 1; } - .wvButton--underline:hover .wvButton__bottomTriangle, .wvButton--underline:focus .wvButton__bottomTriangle, .fa.wvButton--underline:hover .wvButton__bottomTriangle, .fa.wvButton--underline:focus .wvButton__bottomTriangle { - border-left-color: white; } - .wvButton--underline.active, .fa.wvButton--underline.active { - opacity: 1; - border-color: #3498db; } - .wvButton--underline::before, .fa.wvButton--underline::before { - position: relative; - top: -1px; } - .wvButton--underline.fa, .fa.wvButton--underline.fa { - top: 0px; - font-weight: 800; } - -.wvButton__bottomTriangle { - transition: 0.3s border ease, 0.3s opacity ease; - display: block; - position: absolute; - bottom: 0; - left: 0; - width: 0; - height: 0; - border-style: solid; - border-width: 10px 0 0 10px; - border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); } - .wvButton__bottomTriangle.active { - border-color: transparent transparent transparent #3498db !important; } - .wvButton__bottomTriangle.active.toggled { - border-left-color: #3498db !important; } - -.wvButton--border, .wvButton--borderAndWhite { - max-height: calc(2.8rem - 3px); - max-width: 100%; - overflow: hidden; - margin: 0.6rem; - margin-left: 0rem; - margin-right: 0rem; - line-height: 2rem; - padding-top: 0.1rem; - padding-bottom: 0.5rem; - font-size: 1.4rem; - border: 1px solid #454545; - font-family: Arial; - background-color: black; } - .wvButton--border + .wvButton--border, .wvButton--borderAndWhite + .wvButton--border, .wvButton--border + .wvButton--borderAndWhite, .wvButton--borderAndWhite + .wvButton--borderAndWhite { - margin-left: 0.7rem; } - .wvButton--border:hover, .wvButton--borderAndWhite:hover { - background-color: #1a1a1a; } - .wvButton--border > .glyphicon, .wvButton--borderAndWhite > .glyphicon { - position: relative; - display: inline-block; - top: 3px; - margin-right: 4px; } - -.wvButton--borderAndWhite { - color: #1a1a1a; - border: 1px solid #bababa; - background-color: white; } - .wvButton--borderAndWhite:hover { - color: #1a1a1a; - background-color: #e6e6e6; } - -.wvExitButton { - margin-left: 1rem; - margin-top: .25rem; - font-size: 1.25em; - color: white; - opacity: .66; - transition: .3s opacity ease; - background-color: inherit; - border: none; - text-decoration: none; - text-align: left; - padding: 0; - cursor: pointer; - font-family: inherit; - line-height: inherit; } - .wvExitButton:hover, .wvExitButton:focus { - opacity: 1; - outline: 0; } - -.wvExitButton__text { - position: relative; - top: -1px; } - -.wvStudyIsland--blue, .wvStudyIsland--red, .wvStudyIsland--green, .wvStudyIsland--yellow, .wvStudyIsland--violet { - margin: 1rem 1rem 1rem 1rem; - border: 0.3rem solid gray; } - -.wvStudyIsland__header--blue, .wvStudyIsland__header--red, .wvStudyIsland__header--green, .wvStudyIsland__header--yellow, .wvStudyIsland__header--violet { - background-color: gray; - padding: 0.5rem 0.5rem 0.8rem 0.5rem; - line-height: 1.35rem; - width: 100%; } - -.wvStudyIsland__actions { - float: right; - margin-top: -0.8rem; - margin-right: -0.8rem; } - -.wvStudyIsland__actions--oneCol { - float: none; - text-align: center; } - -.wvStudyIsland__main { - padding: 0.4rem; - color: white; - width: 100%; } - -.wvStudyIsland--blue { - border-color: rgba(51, 152, 219, 0.7); } - -.wvStudyIsland__header--blue { - background-color: rgba(51, 152, 219, 0.7); } - -.wvStudyIsland--red { - border-color: rgba(206, 0, 0, 0.7); } - -.wvStudyIsland__header--red { - background-color: rgba(206, 0, 0, 0.7); } - -.wvStudyIsland--green { - border-color: rgba(0, 160, 27, 0.7); } - -.wvStudyIsland__header--green { - background-color: rgba(0, 160, 27, 0.7); } - -.wvStudyIsland--yellow { - border-color: rgba(220, 200, 0, 0.9); } - -.wvStudyIsland__header--yellow { - background-color: rgba(220, 200, 0, 0.9); } - -.wvStudyIsland--violet { - border-color: rgba(255, 31, 255, 0.7); } - -.wvStudyIsland__header--violet { - background-color: rgba(255, 31, 255, 0.7); } - -/* - * Source code taken from private Osimis' frontend toolbox 3.2.1. - */ -/** - _overlay.scss - */ -.overlay__transparent { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1; } - -/** _transition.scss **/ -.transition { - transition: 0.3s all ease; } - -.transition--long { - transition: 0.6s all ease; } - -/** _list.scss **/ -dd + dt { - clear: both; } - -.listDefinition { - width: 100%; - line-height: 1.3; } - -.listDefinition__term { - clear: both; - float: left; - text-align: right; - padding-right: 10px; - width: 50%; } - -.listDefinition__data { - text-align: left; - padding-left: 10px; - float: right; - width: 50%; } - -/** _animation.scss **/ -@keyframes blink__primary { - 0% { - color: #3498db; } - 100% { - color: #666666; } } - -.blink__primary { - animation: blink__primary 0.8s linear infinite; } - -[translate-cloak] { - transition: 0.3s all ease; - opacity: 1; } - [translate-cloak].translate-cloak { - opacity: 0; } - -/** _button.scss **/ -.button__unstyled, .button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close { - background-color: inherit; - border: none; - text-decoration: none; - text-align: left; - padding: 0; - cursor: pointer; } - .button__unstyled *, .button__base *, .button__state--active *, .button__state--inactive *, .button__iconed *, .button__switch--base *, .button__switch *, .button__switch--first *, .button__switch--last *, .button__lightgrey--hover *, .button__text-danger--hover *, .button__text-primary--hover *, .button__danger--hover *, .button__text *, .button__text--underlined *, .button__bordered *, .button__bordered--inverted *, .button__close * { - pointer-events: none; } - .button__unstyled:hover, .button__base:hover, .button__state--active:hover, .button__state--inactive:hover, .button__iconed:hover, .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__lightgrey--hover:hover, .button__text-danger--hover:hover, .button__text-primary--hover:hover, .button__danger--hover:hover, .button__text:hover, .button__text--underlined:hover, .button__bordered:hover, .button__bordered--inverted:hover, .button__close:hover, .button__unstyled:active, .button__base:active, .button__state--active:active, .button__state--inactive:active, .button__iconed:active, .button__switch--base:active, .button__switch:active, .button__switch--first:active, .button__switch--last:active, .button__lightgrey--hover:active, .button__text-danger--hover:active, .button__text-primary--hover:active, .button__danger--hover:active, .button__text:active, .button__text--underlined:active, .button__bordered:active, .button__bordered--inverted:active, .button__close:active, .button__unstyled:focus, .button__base:focus, .button__state--active:focus, .button__state--inactive:focus, .button__iconed:focus, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus, .button__lightgrey--hover:focus, .button__text-danger--hover:focus, .button__text-primary--hover:focus, .button__danger--hover:focus, .button__text:focus, .button__text--underlined:focus, .button__bordered:focus, .button__bordered--inverted:focus, .button__close:focus { - outline: 0; } - -.button__base, .button__state--active, .button__state--inactive, .button__iconed, .button__switch--base, .button__switch, .button__switch--first, .button__switch--last, .button__lightgrey--hover, .button__text-danger--hover, .button__text-primary--hover, .button__danger--hover, .button__text, .button__text--underlined, .button__bordered, .button__bordered--inverted, .button__close { - transition: 0.3s all ease; } - -.button__state--active { - opacity: 1; } - .button__state--active:hover { - opacity: 0.9; - color: #3498db; } - -.button__state--inactive { - opacity: 0.333; } - .button__state--inactive:hover { - opacity: 0.4333; - color: #3498db; } - -.button__iconed { - opacity: 1; } - .button__iconed:hover, .button__iconed:focus, .button__iconed.active { - opacity: 0.75; - color: #3498db; } - -.button__switch--base, .button__switch, .button__switch--first, .button__switch--last { - padding: 5px 0px; - display: inline-block; - text-align: center; - border-top: 1px solid; - border-bottom: 1px solid; - border-color: #3498db; - background-color: white; - overflow: hidden; } - .button__switch--base:hover, .button__switch:hover, .button__switch--first:hover, .button__switch--last:hover, .button__switch--base:focus, .button__switch:focus, .button__switch--first:focus, .button__switch--last:focus { - background-color: #5faee3; - color: white; } - .button__switch--base.active, .active.button__switch, .active.button__switch--first, .active.button__switch--last { - background-color: #3498db; - color: white; } - -.button__switch--first { - border-left: 1px solid #3498db; - border-radius: 5px 0 0 5px; } - -.button__switch--last { - border-right: 1px solid #3498db; - border-radius: 0 5px 5px 0; } - -.button__lightgrey--hover:hover, .button__lightgrey--hover:focus, .button__lightgrey--hover.active { - background-color: #cccccc; } - -.button__text-danger--hover:hover, .button__text-danger--hover:focus, .button__text-danger--hover.active { - color: #E63F24; } - -.button__text-primary--hover:hover, .button__text-primary--hover:focus, .button__text-primary--hover.active { - color: #3498db; } - -.button__danger--hover:hover, .button__danger--hover:focus, .button__danger--hover.active { - background-color: #E63F24; - color: white; } - -.button__text { - opacity: 0.66; } - .button__text:hover, .button__text.active, .button__text:focus { - opacity: 1; } - -.button__text--underlined { - text-decoration: underline; } - .button__text--underlined:hover, .button__text--underlined.active, .button__text--underlined:focus { - text-decoration: none; } - -.button__bordered { - border-bottom: 2px solid #666666; } - .button__bordered:hover, .button__bordered:focus, .button__bordered.active { - border-color: #3498db; } - -.button__bordered--inverted { - border-bottom: 2px solid white; } - -.button__close { - position: absolute; - top: 0; - right: 0; - opacity: 0.6; - z-index: 10; } - .button__close:hover, .button__close:focus { - opacity: 1; } - -/** _block.scss **/ -.block { - display: block !important; } - -/** _boxsizing.scss **/ -.boxsizing__borderbox { - box-sizing: border-box; } - -.boxsizing__contentbox { - box-sizing: content-box; } - -/** _scrollable.scss **/ -.scrollable { - overflow-y: auto; } - -.scrollable--x { - overflow-x: auto; } - -.no-scroll { - overflow: hidden; } - -/** _float.scss **/ -.float__right { - float: right; } - -.float__left { - float: left; } - -/** _fonts.scss **/ -.font__bold { - font-weight: 600; } - -.font__normal, .listDefinition__data { - font-weight: 400; } - -.font__light, .listDefinition__term { - font-weight: 200; } - -.fontColor__primary { - color: #3498db; } - -.fontColor__lightGrey { - color: #cccccc; } - -.fontColor__normal { - color: #666666; } - -.fontColor__darker { - color: #333333; } - -.fontColor__white { - color: white; } - -/** _forms.scss **/ -.textarea__unstyled { - border: none; - outline: none; - background-color: transparent; - color: inherit; - resize: none; - padding: 0; - margin: 0; - width: 100%; } - -/** _position.scss **/ -.position__relative { - position: relative; } - -/** _margin.scss **/ -.margin__auto { - margin: auto; } - -/** _helpers.scss **/ -/*************HELPERS**************/ -/**********************************/ -/*** identical width and height ***/ -.wh__0 { - width: 0px !important; - height: 0px !important; } - -.lh__0 { - line-height: 0px !important; } - -.wh__5 { - width: 5px !important; - height: 5px !important; } - -.lh__5 { - line-height: 5px !important; } - -.wh__8 { - width: 8px !important; - height: 8px !important; } - -.lh__8 { - line-height: 8px !important; } - -.wh__10 { - width: 10px !important; - height: 10px !important; } - -.lh__10 { - line-height: 10px !important; } - -.wh__11 { - width: 11px !important; - height: 11px !important; } - -.lh__11 { - line-height: 11px !important; } - -.wh__12 { - width: 12px !important; - height: 12px !important; } - -.lh__12 { - line-height: 12px !important; } - -.wh__13 { - width: 13px !important; - height: 13px !important; } - -.lh__13 { - line-height: 13px !important; } - -.wh__14 { - width: 14px !important; - height: 14px !important; } - -.lh__14 { - line-height: 14px !important; } - -.wh__15 { - width: 15px !important; - height: 15px !important; } - -.lh__15 { - line-height: 15px !important; } - -.wh__16 { - width: 16px !important; - height: 16px !important; } - -.lh__16 { - line-height: 16px !important; } - -.wh__17 { - width: 17px !important; - height: 17px !important; } - -.lh__17 { - line-height: 17px !important; } - -.wh__18 { - width: 18px !important; - height: 18px !important; } - -.lh__18 { - line-height: 18px !important; } - -.wh__19 { - width: 19px !important; - height: 19px !important; } - -.lh__19 { - line-height: 19px !important; } - -.wh__20 { - width: 20px !important; - height: 20px !important; } - -.lh__20 { - line-height: 20px !important; } - -.wh__21 { - width: 21px !important; - height: 21px !important; } - -.lh__21 { - line-height: 21px !important; } - -.wh__22 { - width: 22px !important; - height: 22px !important; } - -.lh__22 { - line-height: 22px !important; } - -.wh__23 { - width: 23px !important; - height: 23px !important; } - -.lh__23 { - line-height: 23px !important; } - -.wh__24 { - width: 24px !important; - height: 24px !important; } - -.lh__24 { - line-height: 24px !important; } - -.wh__25 { - width: 25px !important; - height: 25px !important; } - -.lh__25 { - line-height: 25px !important; } - -.wh__26 { - width: 26px !important; - height: 26px !important; } - -.lh__26 { - line-height: 26px !important; } - -.wh__27 { - width: 27px !important; - height: 27px !important; } - -.lh__27 { - line-height: 27px !important; } - -.wh__28 { - width: 28px !important; - height: 28px !important; } - -.lh__28 { - line-height: 28px !important; } - -.wh__29 { - width: 29px !important; - height: 29px !important; } - -.lh__29 { - line-height: 29px !important; } - -.wh__30 { - width: 30px !important; - height: 30px !important; } - -.lh__30 { - line-height: 30px !important; } - -.wh__31 { - width: 31px !important; - height: 31px !important; } - -.lh__31 { - line-height: 31px !important; } - -.wh__32 { - width: 32px !important; - height: 32px !important; } - -.lh__32 { - line-height: 32px !important; } - -.wh__33 { - width: 33px !important; - height: 33px !important; } - -.lh__33 { - line-height: 33px !important; } - -.wh__34 { - width: 34px !important; - height: 34px !important; } - -.lh__34 { - line-height: 34px !important; } - -.wh__35 { - width: 35px !important; - height: 35px !important; } - -.lh__35 { - line-height: 35px !important; } - -.wh__36 { - width: 36px !important; - height: 36px !important; } - -.lh__36 { - line-height: 36px !important; } - -.wh__37 { - width: 37px !important; - height: 37px !important; } - -.lh__37 { - line-height: 37px !important; } - -.wh__38 { - width: 38px !important; - height: 38px !important; } - -.lh__38 { - line-height: 38px !important; } - -.wh__39 { - width: 39px !important; - height: 39px !important; } - -.lh__39 { - line-height: 39px !important; } - -.wh__40 { - width: 40px !important; - height: 40px !important; } - -.lh__40 { - line-height: 40px !important; } - -.wh__41 { - width: 41px !important; - height: 41px !important; } - -.lh__41 { - line-height: 41px !important; } - -.wh__42 { - width: 42px !important; - height: 42px !important; } - -.lh__42 { - line-height: 42px !important; } - -.wh__43 { - width: 43px !important; - height: 43px !important; } - -.lh__43 { - line-height: 43px !important; } - -.wh__44 { - width: 44px !important; - height: 44px !important; } - -.lh__44 { - line-height: 44px !important; } - -.wh__45 { - width: 45px !important; - height: 45px !important; } - -.lh__45 { - line-height: 45px !important; } - -.wh__46 { - width: 46px !important; - height: 46px !important; } - -.lh__46 { - line-height: 46px !important; } - -.wh__47 { - width: 47px !important; - height: 47px !important; } - -.lh__47 { - line-height: 47px !important; } - -.wh__48 { - width: 48px !important; - height: 48px !important; } - -.lh__48 { - line-height: 48px !important; } - -.wh__49 { - width: 49px !important; - height: 49px !important; } - -.lh__49 { - line-height: 49px !important; } - -.wh__50 { - width: 50px !important; - height: 50px !important; } - -.lh__50 { - line-height: 50px !important; } - -.wh__55 { - width: 55px !important; - height: 55px !important; } - -.lh__55 { - line-height: 55px !important; } - -.wh__60 { - width: 60px !important; - height: 60px !important; } - -.lh__60 { - line-height: 60px !important; } - -.wh__64 { - width: 64px !important; - height: 64px !important; } - -.lh__64 { - line-height: 64px !important; } - -.wh__65 { - width: 65px !important; - height: 65px !important; } - -.lh__65 { - line-height: 65px !important; } - -.wh__70 { - width: 70px !important; - height: 70px !important; } - -.lh__70 { - line-height: 70px !important; } - -.wh__72 { - width: 72px !important; - height: 72px !important; } - -.lh__72 { - line-height: 72px !important; } - -.wh__75 { - width: 75px !important; - height: 75px !important; } - -.lh__75 { - line-height: 75px !important; } - -.wh__80 { - width: 80px !important; - height: 80px !important; } - -.lh__80 { - line-height: 80px !important; } - -.wh__85 { - width: 85px !important; - height: 85px !important; } - -.lh__85 { - line-height: 85px !important; } - -.wh__90 { - width: 90px !important; - height: 90px !important; } - -.lh__90 { - line-height: 90px !important; } - -.wh__95 { - width: 95px !important; - height: 95px !important; } - -.lh__95 { - line-height: 95px !important; } - -.wh__96 { - width: 96px !important; - height: 96px !important; } - -.lh__96 { - line-height: 96px !important; } - -.wh__100 { - width: 100px !important; - height: 100px !important; } - -.lh__100 { - line-height: 100px !important; } - -.wh__110 { - width: 110px !important; - height: 110px !important; } - -.lh__110 { - line-height: 110px !important; } - -.wh__120 { - width: 120px !important; - height: 120px !important; } - -.lh__120 { - line-height: 120px !important; } - -.wh__130 { - width: 130px !important; - height: 130px !important; } - -.lh__130 { - line-height: 130px !important; } - -.wh__140 { - width: 140px !important; - height: 140px !important; } - -.lh__140 { - line-height: 140px !important; } - -.wh__150 { - width: 150px !important; - height: 150px !important; } - -.lh__150 { - line-height: 150px !important; } - -.wh__160 { - width: 160px !important; - height: 160px !important; } - -.lh__160 { - line-height: 160px !important; } - -.wh__170 { - width: 170px !important; - height: 170px !important; } - -.lh__170 { - line-height: 170px !important; } - -.wh__180 { - width: 180px !important; - height: 180px !important; } - -.lh__180 { - line-height: 180px !important; } - -.wh__190 { - width: 190px !important; - height: 190px !important; } - -.lh__190 { - line-height: 190px !important; } - -.wh__200 { - width: 200px !important; - height: 200px !important; } - -.lh__200 { - line-height: 200px !important; } - -.wh__210 { - width: 210px !important; - height: 210px !important; } - -.lh__210 { - line-height: 210px !important; } - -.wh__220 { - width: 220px !important; - height: 220px !important; } - -.lh__220 { - line-height: 220px !important; } - -.wh__230 { - width: 230px !important; - height: 230px !important; } - -.lh__230 { - line-height: 230px !important; } - -.wh__240 { - width: 240px !important; - height: 240px !important; } - -.lh__240 { - line-height: 240px !important; } - -.wh__250 { - width: 250px !important; - height: 250px !important; } - -.lh__250 { - line-height: 250px !important; } - -.wh__260 { - width: 260px !important; - height: 260px !important; } - -.lh__260 { - line-height: 260px !important; } - -.wh__270 { - width: 270px !important; - height: 270px !important; } - -.lh__270 { - line-height: 270px !important; } - -.wh__280 { - width: 280px !important; - height: 280px !important; } - -.lh__280 { - line-height: 280px !important; } - -.wh__290 { - width: 290px !important; - height: 290px !important; } - -.lh__290 { - line-height: 290px !important; } - -.wh__300 { - width: 300px !important; - height: 300px !important; } - -.lh__300 { - line-height: 300px !important; } - -.wh__310 { - width: 310px !important; - height: 310px !important; } - -.lh__310 { - line-height: 310px !important; } - -.wh__320 { - width: 320px !important; - height: 320px !important; } - -.lh__320 { - line-height: 320px !important; } - -.wh__330 { - width: 330px !important; - height: 330px !important; } - -.lh__330 { - line-height: 330px !important; } - -.wh__340 { - width: 340px !important; - height: 340px !important; } - -.lh__340 { - line-height: 340px !important; } - -.wh__350 { - width: 350px !important; - height: 350px !important; } - -.lh__350 { - line-height: 350px !important; } - -.wh__360 { - width: 360px !important; - height: 360px !important; } - -.lh__360 { - line-height: 360px !important; } - -.wh__370 { - width: 370px !important; - height: 370px !important; } - -.lh__370 { - line-height: 370px !important; } - -.wh__380 { - width: 380px !important; - height: 380px !important; } - -.lh__380 { - line-height: 380px !important; } - -.wh__390 { - width: 390px !important; - height: 390px !important; } - -.lh__390 { - line-height: 390px !important; } - -.wh__400 { - width: 400px !important; - height: 400px !important; } - -.lh__400 { - line-height: 400px !important; } - -.wh__410 { - width: 410px !important; - height: 410px !important; } - -.lh__410 { - line-height: 410px !important; } - -.wh__420 { - width: 420px !important; - height: 420px !important; } - -.lh__420 { - line-height: 420px !important; } - -.wh__430 { - width: 430px !important; - height: 430px !important; } - -.lh__430 { - line-height: 430px !important; } - -.wh__440 { - width: 440px !important; - height: 440px !important; } - -.lh__440 { - line-height: 440px !important; } - -.wh__450 { - width: 450px !important; - height: 450px !important; } - -.lh__450 { - line-height: 450px !important; } - -.wh__460 { - width: 460px !important; - height: 460px !important; } - -.lh__460 { - line-height: 460px !important; } - -.wh__470 { - width: 470px !important; - height: 470px !important; } - -.lh__470 { - line-height: 470px !important; } - -.wh__480 { - width: 480px !important; - height: 480px !important; } - -.lh__480 { - line-height: 480px !important; } - -.wh__490 { - width: 490px !important; - height: 490px !important; } - -.lh__490 { - line-height: 490px !important; } - -.wh__500 { - width: 500px !important; - height: 500px !important; } - -.lh__500 { - line-height: 500px !important; } - -.lh__1 { - line-height: 1 !important; } - -.no-wrap { - white-space: nowrap; } - -.ov-h { - overflow: hidden; } - -.va-m { - vertical-align: middle; } - -.bg-inherit { - background-color: inherit; } - -.bg-black { - background-color: black; } - -.v-center:before { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - margin-top: -0.25em; - /* Adjusts for spacing */ } - -.fluid-height { - height: 100%; } - -.visibility__hidden { - visibility: hidden; } - -.pointerEvents__none { - pointer-events: none; } - -/* Padding Helpers */ -.pn { - padding: 0 !important; } - -.p1 { - padding: 1px !important; } - -.p2 { - padding: 2px !important; } - -.p3 { - padding: 3px !important; } - -.p4 { - padding: 4px !important; } - -.p5 { - padding: 5px !important; } - -.p6 { - padding: 6px !important; } - -.p7 { - padding: 7px !important; } - -.p8 { - padding: 8px !important; } - -.p10 { - padding: 10px !important; } - -.p12 { - padding: 12px !important; } - -.p15 { - padding: 15px !important; } - -.p20 { - padding: 20px !important; } - -.p25 { - padding: 25px !important; } - -.p30 { - padding: 30px !important; } - -.p35 { - padding: 35px !important; } - -.p40 { - padding: 40px !important; } - -.p50 { - padding: 50px !important; } - -.ptn { - padding-top: 0 !important; } - -.pt5 { - padding-top: 5px !important; } - -.pt10 { - padding-top: 10px !important; } - -.pt15 { - padding-top: 15px !important; } - -.pt20 { - padding-top: 20px !important; } - -.pt25 { - padding-top: 25px !important; } - -.pt30 { - padding-top: 30px !important; } - -.pt35 { - padding-top: 35px !important; } - -.pt40 { - padding-top: 40px !important; } - -.pt50 { - padding-top: 50px !important; } - -.prn { - padding-right: 0 !important; } - -.pr5 { - padding-right: 5px !important; } - -.pr10 { - padding-right: 10px !important; } - -.pr15 { - padding-right: 15px !important; } - -.pr20 { - padding-right: 20px !important; } - -.pr25 { - padding-right: 25px !important; } - -.pr30 { - padding-right: 30px !important; } - -.pr35 { - padding-right: 35px !important; } - -.pr40 { - padding-right: 40px !important; } - -.pr50 { - padding-right: 50px !important; } - -.pbn { - padding-bottom: 0 !important; } - -.pb5 { - padding-bottom: 5px !important; } - -.pb10 { - padding-bottom: 10px !important; } - -.pb15 { - padding-bottom: 15px !important; } - -.pb20 { - padding-bottom: 20px !important; } - -.pb25 { - padding-bottom: 25px !important; } - -.pb30 { - padding-bottom: 30px !important; } - -.pb35 { - padding-bottom: 35px !important; } - -.pb40 { - padding-bottom: 40px !important; } - -.pb50 { - padding-bottom: 50px !important; } - -.pln { - padding-left: 0 !important; } - -.pl5 { - padding-left: 5px !important; } - -.pl10 { - padding-left: 10px !important; } - -.pl15 { - padding-left: 15px !important; } - -.pl20 { - padding-left: 20px !important; } - -.pl25 { - padding-left: 25px !important; } - -.pl30 { - padding-left: 30px !important; } - -.pl35 { - padding-left: 35px !important; } - -.pl40 { - padding-left: 40px !important; } - -.pl50 { - padding-left: 50px !important; } - -/* Axis Padding (both top/bottom or left/right) */ -.pv5 { - padding-top: 5px !important; - padding-bottom: 5px !important; } - -.pv8 { - padding-top: 8px !important; - padding-bottom: 8px !important; } - -.pv10 { - padding-top: 10px !important; - padding-bottom: 10px !important; } - -.pv15 { - padding-top: 15px !important; - padding-bottom: 15px !important; } - -.pv20 { - padding-top: 20px !important; - padding-bottom: 20px !important; } - -.pv25 { - padding-top: 25px !important; - padding-bottom: 25px !important; } - -.pv30 { - padding-top: 30px !important; - padding-bottom: 30px !important; } - -.pv40 { - padding-top: 40px !important; - padding-bottom: 40px !important; } - -.pv50 { - padding-top: 50px !important; - padding-bottom: 50px !important; } - -.ph5 { - padding-left: 5px !important; - padding-right: 5px !important; } - -.ph8 { - padding-left: 8px !important; - padding-right: 8px !important; } - -.ph10 { - padding-left: 10px !important; - padding-right: 10px !important; } - -.ph15 { - padding-left: 15px !important; - padding-right: 15px !important; } - -.ph20 { - padding-left: 20px !important; - padding-right: 20px !important; } - -.ph25 { - padding-left: 25px !important; - padding-right: 25px !important; } - -.ph30 { - padding-left: 30px !important; - padding-right: 30px !important; } - -.ph40 { - padding-left: 40px !important; - padding-right: 40px !important; } - -.ph50 { - padding-left: 50px !important; - padding-right: 50px !important; } - -/* margin center helper */ -.mauto { - margin-left: auto; - margin-right: auto; } - -.mn { - margin: 0 !important; } - -.m1 { - margin: 1px !important; } - -.m2 { - margin: 2px !important; } - -.m3 { - margin: 3px !important; } - -.m4 { - margin: 4px !important; } - -.m5 { - margin: 5px !important; } - -.m8 { - margin: 8px !important; } - -.m10 { - margin: 10px !important; } - -.m15 { - margin: 15px !important; } - -.m20 { - margin: 20px !important; } - -.m25 { - margin: 25px !important; } - -.m30 { - margin: 30px !important; } - -.m35 { - margin: 35px !important; } - -.m40 { - margin: 40px !important; } - -.m50 { - margin: 50px !important; } - -.mtn { - margin-top: 0 !important; } - -.mt5 { - margin-top: 5px !important; } - -.mt10 { - margin-top: 10px !important; } - -.mt15 { - margin-top: 15px !important; } - -.mt20 { - margin-top: 20px !important; } - -.mt25 { - margin-top: 25px !important; } - -.mt30 { - margin-top: 30px !important; } - -.mt35 { - margin-top: 35px !important; } - -.mt40 { - margin-top: 40px !important; } - -.mt50 { - margin-top: 50px !important; } - -.mt70 { - margin-top: 70px !important; } - -.mrn { - margin-right: 0 !important; } - -.mr5 { - margin-right: 5px !important; } - -.mr10 { - margin-right: 10px !important; } - -.mr15 { - margin-right: 15px !important; } - -.mr20 { - margin-right: 20px !important; } - -.mr25 { - margin-right: 25px !important; } - -.mr30 { - margin-right: 30px !important; } - -.mr35 { - margin-right: 35px !important; } - -.mr40 { - margin-right: 40px !important; } - -.mr50 { - margin-right: 50px !important; } - -.mbn { - margin-bottom: 0 !important; } - -.mb5 { - margin-bottom: 5px !important; } - -.mb10 { - margin-bottom: 10px !important; } - -.mb15 { - margin-bottom: 15px !important; } - -.mb20 { - margin-bottom: 20px !important; } - -.mb25 { - margin-bottom: 25px !important; } - -.mb30 { - margin-bottom: 30px !important; } - -.mb35 { - margin-bottom: 35px !important; } - -.mb40 { - margin-bottom: 40px !important; } - -.mb50 { - margin-bottom: 50px !important; } - -.mb70 { - margin-bottom: 70px !important; } - -.mln { - margin-left: 0 !important; } - -.ml5 { - margin-left: 5px !important; } - -.ml10 { - margin-left: 10px !important; } - -.ml15 { - margin-left: 15px !important; } - -.ml20 { - margin-left: 20px !important; } - -.ml25 { - margin-left: 25px !important; } - -.ml30 { - margin-left: 30px !important; } - -.ml35 { - margin-left: 35px !important; } - -.ml40 { - margin-left: 40px !important; } - -.ml50 { - margin-left: 50px !important; } - -/* Axis Margins (both top/bottom or left/right) */ -.mv5 { - margin-top: 5px !important; - margin-bottom: 5px !important; } - -.mv10 { - margin-top: 10px !important; - margin-bottom: 10px !important; } - -.mv15 { - margin-top: 15px !important; - margin-bottom: 15px !important; } - -.mv20 { - margin-top: 20px !important; - margin-bottom: 20px !important; } - -.mv25 { - margin-top: 25px !important; - margin-bottom: 25px !important; } - -.mv30 { - margin-top: 30px !important; - margin-bottom: 30px !important; } - -.mv40 { - margin-top: 40px !important; - margin-bottom: 40px !important; } - -.mv50 { - margin-top: 50px !important; - margin-bottom: 50px !important; } - -.mv70 { - margin-top: 70px !important; - margin-bottom: 70px !important; } - -.mh5 { - margin-left: 5px !important; - margin-right: 5px !important; } - -.mh10 { - margin-left: 10px !important; - margin-right: 10px !important; } - -.mh15 { - margin-left: 15px !important; - margin-right: 15px !important; } - -.mh20 { - margin-left: 20px !important; - margin-right: 20px !important; } - -.mh25 { - margin-left: 25px !important; - margin-right: 25px !important; } - -.mh30 { - margin-left: 30px !important; - margin-right: 30px !important; } - -.mh40 { - margin-left: 40px !important; - margin-right: 40px !important; } - -.mh50 { - margin-left: 50px !important; - margin-right: 50px !important; } - -.mh70 { - margin-left: 70px !important; - margin-right: 70px !important; } - -/* Negative Margin Helpers */ -.mtn5 { - margin-top: -5px !important; } - -.mtn10 { - margin-top: -10px !important; } - -.mtn15 { - margin-top: -15px !important; } - -.mtn20 { - margin-top: -20px !important; } - -.mtn30 { - margin-top: -30px !important; } - -.mrn5 { - margin-right: -5px !important; } - -.mrn10 { - margin-right: -10px !important; } - -.mrn15 { - margin-right: -15px !important; } - -.mrn20 { - margin-right: -20px !important; } - -.mrn30 { - margin-right: -30px !important; } - -.mbn5 { - margin-bottom: -5px !important; } - -.mbn10 { - margin-bottom: -10px !important; } - -.mbn15 { - margin-bottom: -15px !important; } - -.mbn20 { - margin-bottom: -20px !important; } - -.mbn30 { - margin-bottom: -30px !important; } - -.mln5 { - margin-left: -5px !important; } - -.mln10 { - margin-left: -10px !important; } - -.mln15 { - margin-left: -15px !important; } - -.mln20 { - margin-left: -20px !important; } - -.mln30 { - margin-left: -30px !important; } - -/* Vertical Negative Margin "mv" + "n" + "x" */ -.mvn5 { - margin-top: -5px !important; - margin-bottom: -5px !important; } - -.mvn10 { - margin-top: -10px !important; - margin-bottom: -10px !important; } - -.mvn15 { - margin-top: -15px !important; - margin-bottom: -15px !important; } - -.mvn20 { - margin-top: -20px !important; - margin-bottom: -20px !important; } - -.mvn30 { - margin-top: -30px !important; - margin-bottom: -30px !important; } - -/* Horizontal Negative Margin "mh" + "n" + "x" */ -.mhn5 { - margin-left: -5px !important; - margin-right: -5px !important; } - -.mhn10 { - margin-left: -10px !important; - margin-right: -10px !important; } - -.mhn15 { - margin-left: -15px !important; - margin-right: -15px !important; } - -.mhn20 { - margin-left: -20px !important; - margin-right: -20px !important; } - -.mhn30 { - margin-left: -30px !important; - margin-right: -30px !important; } - -/* Vertical Align Helpers */ -.va-t { - vertical-align: top !important; } - -.va-m { - vertical-align: middle !important; } - -.va-b { - vertical-align: bottom !important; } - -.va-s { - vertical-align: super !important; } - -/* Text Helpers */ -.text-left { - text-align: left !important; } - -.text-right { - text-align: right !important; } - -.text-center { - text-align: center !important; } - -.text-justify { - text-align: justify !important; } - -.text-nowrap { - white-space: nowrap !important; } - -/* Inline Block Helper */ -.ib, -.inline-object { - display: inline-block !important; } - -.clear { - clear: both; } - -.wvWarning { - position: relative; - width: 320px; - min-height: 130px; - z-index: 999; - left: calc(50% - 160px); - border: #000 solid 1px; - -webkit-border-radius: 7px; - -moz-border-radius: 7px; - border-radius: 7px; - color: #FF5722; - box-shadow: 0px 3px 23px #ff980078; - -webkit-animation-name: example; - /* Safari 4.0 - 8.0 */ - -webkit-animation-duration: 3s; - /* Safari 4.0 - 8.0 */ - -webkit-animation-fill-mode: both; - /* Safari 4.0 - 8.0 */ - animation-name: example; - animation-duration: 2s; - animation-fill-mode: both; - animation-timing-function: ease-out; } - -@-webkit-keyframes example { - from { - top: 0vh; - opacity: 0; - background: #868686; } - to { - top: 10vh; - opacity: 1; - background: #ffffff; } } - -@keyframes example { - from { - top: 0vh; - opacity: 0; - background: #868686; } - to { - top: 10vh; - opacity: 1; - background: #ffffff; } } - -.wvWarning-content { - position: relative; - width: 190px; - min-height: 88px; - max-height: 80vh; - margin: auto; } - -.wvWarning-icon { - font-size: 32px; } - -.wvWarning-text { - position: relative; } - -.wvWarning-button { - background-color: #f1ededcc; - color: #607D8B; - width: 50px; - font-weight: 600; - margin-top: 2px; - margin-right: 30px; } - -.wvScreenToSmallWarning { - position: fixed; - display: block; - top: 0; - left: 0; - background-color: white; - color: #333; - width: 100%; - height: 100%; - z-index: 1000; } - -.wvScreenToSmallWarning-content { - padding: 10px; - text-align: center; } - -/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ -@media only screen and (min-width: 550px) and (min-height: 280px) { - .wvScreenToSmallWarning { - display: none; } } - -/* on some mobile devices, the size returned for the "screen" is actually the viewport size so 360x640 is actually equal to 280x560 */ -@media only screen and (min-width: 280px) and (min-height: 550px) { - .wvScreenToSmallWarning { - display: none; } } - -wv-notice { - display: block; - height: 100%; - width: 100%; } - -.wvNotice { - padding: 0.5rem 0.25rem; - height: 100%; } - -.wvNotice__text { - position: relative; - top: 50%; - transform: translateY(-50%); - text-align: center; - margin-left: 1rem; - font-weight: 400; - color: #b3b3b3; - float: left; - width: calc(100% - 7rem); } - -.wvNotice__closeButton { - float: right; - margin-right: 0.5em; - position: relative; - top: 50%; - transform: translateY(-50%); - width: 3.5rem; - height: 2.5rem; - text-align: center; - font-size: 1em; - font-weight: 100; - line-height: 2.2rem; - cursor: pointer; - border: 1px solid #454545; } - -/* layout: left section */ -.wvLayoutLeft { - position: absolute; - z-index: 2; - background-color: black; - width: 32rem; - left: 0; } - .wvLayoutLeft.wvLayoutLeft--toppadding { - top: 42px; } - .wvLayoutLeft:not(.wvLayoutLeft--toppadding) { - top: 0; } - @media screen and (max-device-width: 374px) { - .wvLayoutLeft.wvLayoutLeft--bottompadding { - bottom: 7rem; } } - @media screen and (min-device-width: 375px) { - .wvLayoutLeft.wvLayoutLeft--bottompadding { - bottom: 5rem; } } - .wvLayoutLeft:not(.wvLayoutLeft--bottompadding) { - bottom: 0; } - .wvLayoutLeft.wvLayoutLeft--closed { - transform: translateX(-32rem); } - .wvLayoutLeft.wvLayoutLeft--closed.wvLayoutLeft--small { - transform: translateX(-12rem); } - .wvLayoutLeft.wvLayoutLeft--small { - width: 12rem; } - .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentTop, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentMiddle, .wvLayoutLeft.wvLayoutLeft--small .wvLayoutLeft__contentBottom { - width: 100%; } - -.wvLayoutLeft__content { - border-right: 1px solid #AAA; - flex: 1; - display: flex; - flex-direction: column; - overflow-y: auto; - height: 100%; } - -.wvLayoutLeft__contentTop { - padding: 0rem 1rem 0rem 1rem; - width: 31.9rem; } - .wvLayoutLeft__contentTop:after { - content: ""; - display: block; - height: 0; - width: 0; - clear: both; } - -.wvLayoutLeft__contentMiddle { - flex: 1 0 auto; - width: 31.9rem; } - -.wvLayoutLeft__contentBottom { - width: 31.9rem; } - -.wvLayout__leftBottom.wvLayout__leftBottom--enabled { - border-top: 1px solid rgba(255, 255, 255, 0.2); - margin-top: 1rem; - padding: 1rem; } - -.wvLayoutLeft__actions, .wvLayoutLeft__actions--outside { - display: block; - position: absolute; - right: 1px; - top: 50%; - transform: translateY(-50%); - width: 25px; } - -.wvLayoutLeft__actions--outside { - right: -25px; } - -.wvLayoutLeft__action { - background-color: #3498db; - opacity: 0.5; - color: white; - transition: none; } - .wvLayoutLeft__action:hover, .wvLayoutLeft__action:focus { - opacity: 1; } - -/* layout: right section */ -.wvLayout__right { - display: block; - position: absolute; - z-index: 2; - background-color: black; - width: 85px; - right: 0; } - .wvLayout__right.wvLayout__right--toppadding { - top: 42px; } - .wvLayout__right:not(.wvLayout__right--toppadding) { - top: 0; } - @media screen and (max-device-width: 374px) { - .wvLayout__right.wvLayout__right--bottompadding { - bottom: 7rem; } } - @media screen and (min-device-width: 375px) { - .wvLayout__right.wvLayout__right--bottompadding { - bottom: 5rem; } } - .wvLayout__right:not(.wvLayout__right--bottompadding) { - bottom: 0; } - .wvLayout__right.wvLayout__right--closed { - transform: translateX(85px); } - .wvLayout__right > wv-layout-right, - .wvLayout__right > wv-layout-right > .wvViewer__asideRight { - display: block; - height: 100%; - width: 100%; } - -.wvAsideRight__content { - height: 100%; - float: left; - border-left: 1px solid #AAA; - padding: 0 5px; - width: 32rem; } - -.wvAsideRight__actions, .wvAsideRight__actions--outside { - display: block; - position: absolute; - left: 1px; - top: 50%; - transform: translateY(-50%); - width: 25px; - z-index: 3; } - -.wvAsideRight__actions--outside { - left: -25px; } - -.wvAsideRight__action { - background-color: #3498db; - opacity: 0.5; - color: white; - transition: none; } - .wvAsideRight__action:hover, .wvAsideRight__action:focus { - opacity: 1; } - -.wvAsideRight__fixOpenFullyTooltip + .tooltip { - left: -6.633em !important; - top: 1px !important; } - -/* layout: bottom section */ -.wvLayout__bottom { - position: absolute; - left: 0; - bottom: 0; - right: 0; - background-color: #1a1a1a; } - @media screen and (max-device-width: 374px) { - .wvLayout__bottom { - height: 7rem; } } - @media screen and (min-device-width: 375px) { - .wvLayout__bottom { - height: 5rem; } } - -/* layout: main section */ -.wvLayout__main { - position: absolute; - text-align: center; - right: 0; - left: 0; } - .wvLayout__main .wvLayout__splitpane--toolbarAtTop { - display: block; - height: calc(100% - 42px); - width: 100%; - position: relative; - top: 42px; } - .wvLayout__main .wvLayout__splitpane--toolbarAtRight { - display: block; - height: 100%; - width: calc(100% - 42px); } - .wvLayout__main .wvLayout__splitpane--bigToolbarAtTop { - display: block; - height: calc(100% - 68px); - width: 100%; - position: relative; - top: 68px; } - .wvLayout__main .wvLayout__splitpane--bigToolbarAtRight { - display: block; - height: 100%; - width: calc(100% - 68px); } - .wvLayout__main.wvLayout__main--toppadding { - top: 42px; } - .wvLayout__main:not(.wvLayout__main--toppadding) { - top: 0; } - .wvLayout__main.wvLayout__main--bottompadding { - bottom: 440px; } - @media screen and (max-device-width: 374px) { - .wvLayout__main.wvLayout__main--bottompadding { - bottom: 7rem; } } - @media screen and (min-device-width: 375px) { - .wvLayout__main.wvLayout__main--bottompadding { - bottom: 5rem; } } - .wvLayout__main:not(.wvLayout__main--bottompadding) { - bottom: 0; } - .wvLayout__main.wvLayout__main--leftpadding { - left: 32rem; } - .wvLayout__main { - left: 0px; } - .wvLayout__main.wvLayout__main--smallleftpadding { - left: 12rem; } - .wvLayout__main.wvLayout__main--rightpadding { - right: 85px; } - .wvLayout__main:not(.wvLayout__main--rightpadding) { - right: 0px; } - -/* global */ -.popover { - color: black; } - -.wvViewer__editor--full { - position: absolute; - top: 0; - right: 0; - z-index: 10; - opacity: 0; - transform: translateX(100%); - width: 100%; - height: 100%; - background-color: white; - color: #666666; } - .wvViewer__editor--full.opened { - opacity: 1; - transform: translateX(0); } - -.wvViewer__topBar { - width: 100%; - overflow-y: auto; - white-space: nowrap; - max-width: 100%; } - -.wvViewer__buttonGroup { - display: inline-block; } - -.wvViewer__buttonGroup--asideWidth { - width: 32rem; - padding-right: 1rem; } - -.wvViewer__buttonGroup--contentWidth { - width: calc(100% - 32rem); - padding-left: 1rem; - max-height: 4.2rem; } - -.wvViewer__iframe { - position: absolute; - left: 0; - top: 0; } - -/* bottom bar */ -.wvViewer__bottomBar, .wvViewer__bottomBar--expanded, .wvViewer__bottomBar--minimized { - position: absolute; - left: 0; - bottom: 0; - width: 100%; - background-color: #111111; } - -.wvViewer__bottomBar--expanded { - height: 80px; - color: white; } - .wvViewer__bottomBar--expanded .wvViewer__timeline { - width: calc(100% - 80px); } - .wvViewer__bottomBar--expanded .wvTimeline__hotspots { - bottom: -40px; } - -.wvViewer__bottomBar--minimized { - color: white; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - padding-left: 2.5rem; } - .wvViewer__bottomBar--minimized .wvTimeline__hotspot { - top: -40px; - opacity: 0; - visibility: hidden; - z-index: -1; } - .wvViewer__bottomBar--minimized:hover .wvTimeline__hotspot { - opacity: 1; - visibility: visible; - z-index: 5; - transition-delay: 0s; } - -.wvViewer__timeline { - height: 24px; - line-height: 24px; - vertical-align: middle; - width: 100%; } - -.wvViewer__trademark { - display: inline-block; - float: right; - width: 80px; - height: 80px; - float: right; - line-height: 80px; - vertical-align: middle; - text-align: center; } - -.wvTimeline__input { - border-radius: 3px; - margin-top: 2px; - border: 1px solid #e7e7e7; } - .wvTimeline__input:focus { - outline: none; } - -.wvTimeline__actions { - display: inline-block; - border-right: 1px solid #e7e7e7; } - -.wvSerieslist { - margin: 0; - padding: 0; - list-style: none; } - -.wvSerieslist__seriesItem--selectable { - cursor: pointer !important; } - .wvSerieslist__seriesItem--selectable:hover { - color: white; } - -.wvSerieslist__placeholderIcon, .wvSerieslist__placeholderIcon.fa { - position: absolute; - width: 100%; - height: 100%; - font-size: 3.25rem; - line-height: 6.5rem; - text-align: center; } - -.wvSerieslist__placeholderIcon--strikeout, .wvSerieslist__placeholderIcon--strikeout.fa { - color: #c3c3c3; } - .wvSerieslist__placeholderIcon--strikeout::after, .wvSerieslist__placeholderIcon--strikeout.fa::after { - position: absolute; - left: 0; - top: 50%; - right: 0; - transform: rotate(-45deg) scaleX(0.9); - border-top: 5px solid; - border-color: inherit; - content: ""; } - -.wvSerieslist__picture { - display: inline-block; - font-size: 14px; - width: 6.5rem; - height: 6.5rem; - position: relative; - z-index: -1; } - -.wvSerieslist__badge, .wvSerieslist__badge--blue, .wvSerieslist__badge--red, .wvSerieslist__badge--green, .wvSerieslist__badge--yellow, .wvSerieslist__badge--violet { - position: absolute; - bottom: 5px; - right: 5px; - font-size: 10px; - line-height: 15px; - width: 15px; - height: 15px; - border-radius: 100%; - background-color: gray; - vertical-align: middle; - text-align: center; - font-weight: bold; } - -.wvSerieslist__information { - font-size: 14px; - float: right; - padding-left: 1rem; - width: calc(100% - 6.5rem); - height: 6.5rem; } - -.wvSerieslist__label { - white-space: nowrap; - width: calc(100% - 10px); - overflow: hidden; - height: 3.25rem; - line-height: 3.25rem; - vertical-align: middle; } - -.wvSerieslist__timeline { - height: 3.25rem; - line-height: 3.25rem; - vertical-align: middle; } - -.wvSerieslist__seriesItem { - position: relative; - padding-left: 0; - list-style: none; - font-size: 0; - border-right: 0.2rem solid transparent; - border-left: 0.2rem solid transparent; - border-top: 0.2rem solid transparent; - border-bottom: 0.2rem solid transparent; - border-corner-shape: notch; - line-height: 0px; - margin: 0.1rem; } - .wvSerieslist__seriesItem.active { - border-color: rgba(255, 255, 255, 0.6); - border-style: solid; } - .wvSerieslist__seriesItem.highlighted { - border-color: white; - border-style: solid; } - .wvSerieslist__seriesItem:hover, .wvSerieslist__seriesItem:focus, .wvSerieslist__seriesItem.focused { - border-style: dashed; - border-color: rgba(255, 255, 255, 0.8); } - -.wvSerieslist__seriesItem--list { - display: block; } - -.wvSerieslist__seriesItem--grid { - display: inline-block; } - -.wvSerieslist__seriesItem--oneCol { - text-align: center; } - -.wvSerieslist__seriesItem--activated, -.wvSerieslist__videoItem--activated, -.wvSerieslist__pdfItem--activated { - border: 0.2rem solid #3398db !important; } - -.wvSerieslist__badge--blue { - background-color: rgba(51, 152, 219, 0.7); } - -.wvSerieslist__badge--red { - background-color: rgba(206, 0, 0, 0.7); } - -.wvSerieslist__badge--green { - background-color: rgba(0, 160, 27, 0.7); } - -.wvSerieslist__badge--yellow { - background-color: rgba(220, 200, 0, 0.9); } - -.wvSerieslist__badge--violet { - background-color: rgba(255, 31, 255, 0.7); } - -.wvToolbar { - position: absolute; } - -.wvToolbar--top { - top: 0; - height: 42px; - right: 0; - text-align: right; - white-space: nowrap; - max-width: 100%; } - -.wvToolbar--right { - right: 0; - width: 42px; - height: 100%; - z-index: 2; } - .wvToolbar--right.wvToolbar--big { - width: 68px; } - -/* Splitpane Grid Configuration */ -.wvToolbar__splitpaneConfigPopover { - font-size: 0; } - -.wvToolbar__splitpaneConfigNotice { - font-size: 1.25rem; - font-style: italic; - text-align: center; - color: #333; } - -input[type="radio"].wvToolbar__splitpaneConfigButtonInput { - position: absolute; - width: 0; - height: 0; - left: 0; - top: 0; - bottom: 2px; - right: 0; - opacity: 0; } - -/* Windowing Preset */ -.wvToolbar__windowingPresetConfigNotice { - font-size: 1.25rem; - font-style: italic; - text-align: center; - color: #333; } - -.wvToolbar__windowingPresetList { - list-style: none; - margin: 0; - padding: 0; - font-size: 1.5rem; } - -.wvToolbar__windowingPresetListItem { - outline: none; - background-color: transparent; - border: none; - position: relative; - display: inline-block; - cursor: pointer; - font-variant: small-caps; - text-transform: lowercase; - text-align: center; - font-size: 1.3rem; - font-weight: 400; - line-height: 2.2rem; - color: #d9d9d9; - transition: 0.3s text-decoration ease, 0.3s border ease, 0.3s opacity ease; - margin: 0; - min-width: 3rem; - padding: 0 10px; - line-height: 3.6rem; - max-height: 2.8rem; - max-width: 100%; - overflow: hidden; - margin: 0.6rem; - margin-left: 0rem; - margin-right: 0rem; - line-height: 2rem; - padding-top: 0.1rem; - padding-bottom: 0.5rem; - font-size: 1.4rem; - border: 1px solid #454545; - font-family: Arial; - background-color: black; - color: #1a1a1a; - border: 1px solid #bababa; - background-color: white; - width: 100%; - margin: 0; - margin-left: 0 !important; - border-top: none; - border-bottom: none; } - .wvToolbar__windowingPresetListItem:hover { - text-decoration: none; - color: white; } - .wvToolbar__windowingPresetListItem + .wvToolbar__windowingPresetListItem { - margin-left: 0.7rem; } - .wvToolbar__windowingPresetListItem:hover { - background-color: #1a1a1a; } - .wvToolbar__windowingPresetListItem > .glyphicon { - position: relative; - display: inline-block; - top: 3px; - margin-right: 4px; } - .wvToolbar__windowingPresetListItem:hover { - color: #1a1a1a; - background-color: #e6e6e6; } - -.wvVideo { - position: absolute; - top: 50%; - left: 0; - width: 100%; - height: auto; - transform: translateY(-50%); } - -.wvStudyInformationBreadcrumb__patient { - display: inline-block; - background-color: rgba(255, 255, 255, 0.15); - padding: 0.2rem 1rem 0.3rem 1rem; - text-align: center; - font-size: 1em; - margin: 0.6rem; - font-weight: 400; - line-height: 2.3rem; - margin-right: 0; } - -.wvStudyInformationBreadcrumb__study { - display: inline-block; - background-color: rgba(255, 255, 255, 0.15); - padding: 0.2rem 1rem 0.3rem 1rem; - text-align: center; - font-size: 1em; - margin: 0.6rem; - font-weight: 400; - line-height: 2.3rem; } - -.wvSelectionActionlist { - display: block; - text-align: center; } - -/* wvb-ui stuffs */ -.wv-overlay { - color: orange; } - -.wv-overlay-icon { - width: 64px; } - -.wvOverlay__studyBadge, .wvOverlay__studyBadge--blue, .wvOverlay__studyBadge--red, .wvOverlay__studyBadge--green, .wvOverlay__studyBadge--yellow, .wvOverlay__studyBadge--violet { - position: absolute; - top: 0; - left: 0; - width: 1.5rem; - height: 1.5rem; - background-color: gray; - z-index: 1; } - -.wv-overlay-topleft { - position: absolute; - top: 0rem; - left: 0rem; - text-align: left; } - -.wv-overlay-topright { - position: absolute; - top: 0rem; - right: 0rem; - text-align: right; } - -.wv-overlay-bottomright { - position: absolute; - bottom: 2em; - right: 0rem; - text-align: right; } - -.wv-overlay-bottomleft { - position: absolute; - bottom: 2em; - left: 0rem; - text-align: left; } - -.wv-overlay-timeline-wrapper { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 1; } - -.wv-overlay-topleft, .wv-overlay-topright, .wv-overlay-bottomright, .wv-overlay-bottomleft { - padding: 2rem; - transition: color 500ms, background-color 500ms; - background-color: rgba(0, 0, 0, 0.66); } - -.wv-overlay-topleft:hover, .wv-overlay-topright:hover, .wv-overlay-bottomright:hover, .wv-overlay-bottomleft:hover { - background-color: rgba(0, 0, 0, 0.9); } - -.wvPaneOverlay { - position: absolute; - top: 50%; - width: 100%; - transform: translateY(-50%); - font-weight: 100; - text-align: center; - color: white; - font-size: 2rem; } - -.wv-overlay-scrollbar-loaded { - position: absolute; - bottom: 0; - left: 0; - height: 5px; - background-color: red; - will-change: right; - transform-origin: 0% 50%; } - -.wv-overlay-scrollbar-loading { - position: absolute; - bottom: 0; - left: 0; - height: 5px; - background-color: #660000; - will-change: right; - transform-origin: 0% 50%; } - -.wv-overlay-scrollbar-text { - position: absolute; - bottom: calc(1em + 5px); - left: 5px; - height: 1em; - color: red; - font-size: 0.8em; - font-family: helvetica; } - -.wvOverlay__studyBadge--blue { - background-color: rgba(51, 152, 219, 0.7); } - -.wvOverlay__studyBadge--red { - background-color: rgba(206, 0, 0, 0.7); } - -.wvOverlay__studyBadge--green { - background-color: rgba(0, 160, 27, 0.7); } - -.wvOverlay__studyBadge--yellow { - background-color: rgba(220, 200, 0, 0.9); } - -.wvOverlay__studyBadge--violet { - background-color: rgba(255, 31, 255, 0.7); } - -wv-pdf-viewer { - display: block; - width: 100%; - height: 100%; } - -#toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton { - background-color: inherit; - color: white; - border: none; - padding: 2px; - margin-left: 4px; - margin-right: 2px; } - #toolbarContainer > #toolbarViewer > #toolbarViewerLeft > .wv-pdf-viewer-closebutton:hover { - color: black; } - -.fa.fa-window-close.wv-pdf-viewer-closebuttonicon { - font-size: 2rem; - line-height: 28px; } - -.pdfjs .textLayer { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - overflow: hidden; - opacity: 0.2; } - -.pdfjs .textLayer > div { - color: transparent; - position: absolute; - white-space: pre; - cursor: text; - -webkit-transform-origin: 0 0; - -moz-transform-origin: 0 0; - -o-transform-origin: 0 0; - -ms-transform-origin: 0 0; - transform-origin: 0 0; } - -.pdfjs .textLayer .highlight { - margin: -1px; - padding: 1px; - background-color: #b400aa; - border-radius: 4px; } - -.pdfjs .textLayer .highlight.begin { - border-radius: 4px 0 0 4px; } - -.pdfjs .textLayer .highlight.end { - border-radius: 0 4px 4px 0; } - -.pdfjs .textLayer .highlight.middle { - border-radius: 0; } - -.pdfjs .textLayer .highlight.selected { - background-color: #006400; } - -.pdfjs .textLayer ::selection { - background: #00f; } - -.pdfjs .textLayer ::-moz-selection { - background: #00f; } - -.pdfjs .pdfViewer .canvasWrapper { - overflow: hidden; } - -.pdfjs .pdfViewer .page { - direction: ltr; - width: 816px; - height: 1056px; - margin: 1px auto -8px; - position: relative; - overflow: visible; - border: 9px solid transparent; - background-clip: content-box; - border-image: url("../images/pdf.js-viewer/shadow.png") 9 9 repeat; - background-color: #fff; } - -body { - height: 100%; } - -.pdfjs .pdfViewer.removePageBorders .page { - margin: 0 auto 10px; - border: none; } - -.pdfjs .pdfViewer .page canvas { - margin: 0; - display: block; } - -.pdfjs .pdfViewer .page .loadingIcon { - position: absolute; - display: block; - left: 0; - top: 0; - right: 0; - bottom: 0; - background: url("../images/pdf.js-viewer/loading-icon.gif") center no-repeat; } - -.pdfjs .pdfViewer .page .annotLink > a:hover { - opacity: .2; - background: #ff0; - box-shadow: 0 2px 10px #ff0; } - -.pdfjs .pdfPresentationMode:-webkit-full-screen .pdfViewer .page { - margin-bottom: 100%; - border: 0; } - -.pdfjs .pdfPresentationMode:-moz-full-screen .pdfViewer .page { - margin-bottom: 100%; - border: 0; } - -.pdfjs .pdfPresentationMode:-ms-fullscreen .pdfViewer .page { - margin-bottom: 100% !important; - border: 0; } - -.pdfjs .pdfPresentationMode:fullscreen .pdfViewer .page { - margin-bottom: 100%; - border: 0; } - -.pdfjs .pdfViewer .page .annotText > img { - position: absolute; - cursor: pointer; } - -.pdfjs .pdfViewer .page .annotTextContentWrapper { - position: absolute; - width: 20em; } - -.pdfjs .pdfViewer .page .annotTextContent { - z-index: 200; - float: left; - max-width: 20em; - background-color: #FF9; - box-shadow: 0 2px 5px #333; - border-radius: 2px; - padding: .6em; - cursor: pointer; } - -.pdfjs .pdfViewer .page .annotTextContent > h1 { - font-size: 1em; - border-bottom: 1px solid #000; - padding-bottom: 0.2em; } - -.pdfjs .pdfViewer .page .annotTextContent > p { - padding-top: 0.2em; } - -.pdfjs .pdfViewer .page .annotLink > a { - position: absolute; - font-size: 1em; - top: 0; - left: 0; - width: 100%; - height: 100%; } - -.pdfjs .pdfViewer .page .annotLink > a { - background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAA LAAAAAABAAEAAAIBRAA7") 0 0 repeat; } - -.pdfjs * { - padding: 0; - margin: 0; } - -html { - height: 100%; - font-size: 10px; } - -.pdfjs input, -.pdfjs button, -.pdfjs select { - font: message-box; - outline: none; } - -.pdfjs .hidden { - display: none !important; } - -.pdfjs [hidden] { - display: none !important; } - -.pdfjs #viewerContainer.pdfPresentationMode:-webkit-full-screen { - top: 0; - border-top: 2px solid transparent; - background-color: #000; - width: 100%; - height: 100%; - overflow: hidden; - cursor: none; - -webkit-user-select: none; } - -.pdfjs #viewerContainer.pdfPresentationMode:-moz-full-screen { - top: 0; - border-top: 2px solid transparent; - background-color: #000; - width: 100%; - height: 100%; - overflow: hidden; - cursor: none; - -moz-user-select: none; } - -.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen { - top: 0 !important; - border-top: 2px solid transparent; - width: 100%; - height: 100%; - overflow: hidden !important; - cursor: none; - -ms-user-select: none; } - -.pdfjs #viewerContainer.pdfPresentationMode:-ms-fullscreen::-ms-backdrop { - background-color: #000; } - -.pdfjs #viewerContainer.pdfPresentationMode:fullscreen { - top: 0; - border-top: 2px solid transparent; - background-color: #000; - width: 100%; - height: 100%; - overflow: hidden; - cursor: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; } - -.pdfjs .pdfPresentationMode:-webkit-full-screen a:not(.internalLink) { - display: none; } - -.pdfjs .pdfPresentationMode:-moz-full-screen a:not(.internalLink) { - display: none; } - -.pdfjs .pdfPresentationMode:-ms-fullscreen a:not(.internalLink) { - display: none !important; } - -.pdfjs .pdfPresentationMode:fullscreen a:not(.internalLink) { - display: none; } - -.pdfjs .pdfPresentationMode:-webkit-full-screen .textLayer > div { - cursor: none; } - -.pdfjs .pdfPresentationMode:-moz-full-screen .textLayer > div { - cursor: none; } - -.pdfjs .pdfPresentationMode:-ms-fullscreen .textLayer > div { - cursor: none; } - -.pdfjs .pdfPresentationMode:fullscreen .textLayer > div { - cursor: none; } - -.pdfjs .pdfPresentationMode.pdfPresentationModeControls > *, -.pdfjs .pdfPresentationMode.pdfPresentationModeControls .textLayer > div { - cursor: default; } - -.pdfjs .outerCenter { - pointer-events: none; - position: relative; } - -html[dir='ltr'] .pdfjs .outerCenter { - float: right; - right: 50%; } - -html[dir='rtl'] .pdfjs .outerCenter { - float: left; - left: 50%; } - -.pdfjs .innerCenter { - pointer-events: auto; - position: relative; } - -html[dir='ltr'] .pdfjs .innerCenter { - float: right; - right: -50%; } - -html[dir='rtl'] .pdfjs .innerCenter { - float: left; - left: -50%; } - -.pdfjs #outerContainer { - width: 100%; - height: 100%; - position: relative; - background-color: #404040; - background-image: url("../images/pdf.js-viewer/texture.png"); } - -.pdfjs #sidebarContainer { - position: absolute; - top: 0; - bottom: 0; - width: 200px; - visibility: hidden; - -webkit-transition-duration: 200ms; - -webkit-transition-timing-function: ease; - transition-duration: 200ms; - transition-timing-function: ease; } - -html[dir='ltr'] .pdfjs #sidebarContainer { - -webkit-transition-property: left; - transition-property: left; - left: -200px; } - -html[dir='rtl'] .pdfjs #sidebarContainer { - -webkit-transition-property: right; - transition-property: right; - right: -200px; } - -.pdfjs #outerContainer.sidebarMoving > #sidebarContainer, -.pdfjs #outerContainer.sidebarOpen > #sidebarContainer { - visibility: visible; } - -html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { - left: 0; } - -html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #sidebarContainer { - right: 0; } - -.pdfjs #mainContainer { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - min-width: 320px; - -webkit-transition-duration: 200ms; - -webkit-transition-timing-function: ease; - transition-duration: 200ms; - transition-timing-function: ease; } - -html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - -webkit-transition-property: left; - transition-property: left; - left: 200px; } - -html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - -webkit-transition-property: right; - transition-property: right; - right: 200px; } - -.pdfjs #sidebarContent { - top: 32px; - bottom: 0; - overflow: auto; - -webkit-overflow-scrolling: touch; - position: absolute; - width: 200px; - background-color: rgba(0, 0, 0, 0.1); } - -html[dir='ltr'] .pdfjs #sidebarContent { - left: 0; - box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25); } - -html[dir='rtl'] .pdfjs #sidebarContent { - right: 0; - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25); } - -.pdfjs #viewerContainer { - overflow: auto; - -webkit-overflow-scrolling: touch; - position: absolute; - top: 32px; - right: 0; - bottom: 0; - left: 0; - outline: none; } - -html[dir='ltr'] .pdfjs #viewerContainer { - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05); } - -html[dir='rtl'] .pdfjs #viewerContainer { - box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.05); } - -.pdfjs .toolbar { - position: relative; - left: 0; - right: 0; - cursor: default; } - -.pdfjs #toolbarContainer { - width: 100%; } - -.pdfjs #toolbarSidebar { - width: 200px; - height: 32px; - background-color: #424242; - background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(77, 77, 77, 0.99), rgba(64, 64, 64, 0.95)); } - -html[dir='ltr'] .pdfjs #toolbarSidebar { - box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); } - -html[dir='rtl'] .pdfjs #toolbarSidebar { - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1); } - -.pdfjs #toolbarContainer, -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - position: relative; - height: 32px; - background-color: #474747; - background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); } - -html[dir='ltr'] .pdfjs #toolbarContainer, -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); } - -html[dir='rtl'] .pdfjs #toolbarContainer, -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); } - -.pdfjs #toolbarViewer { - height: 32px; } - -.pdfjs #loadingBar { - position: relative; - width: 100%; - height: 4px; - background-color: #333; - border-bottom: 1px solid #333; } - -.pdfjs #loadingBar .progress { - position: absolute; - top: 0; - left: 0; - width: 0; - height: 100%; - background-color: #ddd; - overflow: hidden; - -webkit-transition: width 200ms; - transition: width 200ms; } - -@-webkit-keyframes progressIndeterminate { - 0% { - left: 0; } - 50% { - left: 100%; } - 100% { - left: 100%; } } - -@keyframes progressIndeterminate { - 0% { - left: 0; } - 50% { - left: 100%; } - 100% { - left: 100%; } } - -.pdfjs #loadingBar .progress.indeterminate { - background-color: #999; - -webkit-transition: none; - transition: none; } - -.pdfjs #loadingBar .indeterminate .glimmer { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 50px; - background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%); - background-size: 100% 100%; - background-repeat: no-repeat; - -webkit-animation: progressIndeterminate 2s linear infinite; - animation: progressIndeterminate 2s linear infinite; } - -.pdfjs .findbar, -.pdfjs .secondaryToolbar { - top: 32px; - position: absolute; - z-index: 10000; - height: 32px; - min-width: 16px; - padding: 0 6px; - margin: 4px 2px; - color: #d9d9d9; - font-size: 12px; - line-height: 14px; - text-align: left; - cursor: default; } - -html[dir='ltr'] .pdfjs .findbar { - left: 68px; } - -html[dir='rtl'] .pdfjs .findbar { - right: 68px; } - -.pdfjs .findbar label { - -webkit-user-select: none; - -moz-user-select: none; } - -.pdfjs #findInput[data-status="pending"] { - background-image: url("../images/pdf.js-viewer/loading-small.png"); - background-repeat: no-repeat; - background-position: right; } - -html[dir='rtl'] .pdfjs #findInput[data-status="pending"] { - background-position: left; } - -.pdfjs .secondaryToolbar { - padding: 6px; - height: auto; - z-index: 30000; } - -html[dir='ltr'] .pdfjs .secondaryToolbar { - right: 4px; } - -html[dir='rtl'] .pdfjs .secondaryToolbar { - left: 4px; } - -.pdfjs #secondaryToolbarButtonContainer { - max-width: 200px; - max-height: 400px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - margin-bottom: -4px; } - -.pdfjs .doorHanger, -.pdfjs .doorHangerRight { - border: 1px solid rgba(0, 0, 0, 0.5); - border-radius: 2px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } - -.pdfjs .doorHanger:after, -.pdfjs .doorHanger:before, -.pdfjs .doorHangerRight:after, -.pdfjs .doorHangerRight:before { - bottom: 100%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; } - -.pdfjs .doorHanger:after, -.pdfjs .doorHangerRight:after { - border-bottom-color: rgba(82, 82, 82, 0.99); - border-width: 8px; } - -.pdfjs .doorHanger:before, -.pdfjs .doorHangerRight:before { - border-bottom-color: rgba(0, 0, 0, 0.5); - border-width: 9px; } - -html[dir='ltr'] .pdfjs .doorHanger:after, -html[dir='rtl'] .pdfjs .doorHangerRight:after { - left: 13px; - margin-left: -8px; } - -html[dir='ltr'] .pdfjs .doorHanger:before, -html[dir='rtl'] .pdfjs .doorHangerRight:before { - left: 13px; - margin-left: -9px; } - -html[dir='rtl'] .pdfjs .doorHanger:after, -html[dir='ltr'] .pdfjs .doorHangerRight:after { - right: 13px; - margin-right: -8px; } - -html[dir='rtl'] .pdfjs .doorHanger:before, -html[dir='ltr'] .pdfjs .doorHangerRight:before { - right: 13px; - margin-right: -9px; } - -.pdfjs #findMsg { - font-style: italic; - color: #A6B7D0; } - -.pdfjs #findInput.notFound { - background-color: #f66; } - -html[dir='ltr'] .pdfjs #toolbarViewerLeft { - margin-left: -1px; } - -html[dir='rtl'] .pdfjs #toolbarViewerRight { - margin-right: -1px; } - -html[dir='ltr'] .pdfjs #toolbarViewerLeft, -html[dir='rtl'] .pdfjs #toolbarViewerRight { - position: absolute; - top: 0; - left: 0; } - -html[dir='ltr'] .pdfjs #toolbarViewerRight, -html[dir='rtl'] .pdfjs #toolbarViewerLeft { - position: absolute; - top: 0; - right: 0; } - -html[dir='ltr'] .pdfjs #toolbarViewerLeft > *, -html[dir='ltr'] .pdfjs #toolbarViewerMiddle > *, -html[dir='ltr'] .pdfjs #toolbarViewerRight > *, -html[dir='ltr'] .pdfjs .findbar > * { - position: relative; - float: left; } - -html[dir='rtl'] .pdfjs #toolbarViewerLeft > *, -html[dir='rtl'] .pdfjs #toolbarViewerMiddle > *, -html[dir='rtl'] .pdfjs #toolbarViewerRight > *, -html[dir='rtl'] .pdfjs .findbar > * { - position: relative; - float: right; } - -html[dir='ltr'] .pdfjs .splitToolbarButton { - margin: 3px 2px 4px 0; - display: inline-block; } - -html[dir='rtl'] .pdfjs .splitToolbarButton { - margin: 3px 0 4px 2px; - display: inline-block; } - -html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton { - border-radius: 0; - float: left; } - -html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton { - border-radius: 0; - float: right; } - -.pdfjs .toolbarButton, -.pdfjs .secondaryToolbarButton, -.pdfjs .overlayButton { - border: 0 none; - background: none; - width: 32px; - height: 25px; } - -.pdfjs .toolbarButton > span { - display: inline-block; - width: 0; - height: 0; - overflow: hidden; } - -.pdfjs .toolbarButton[disabled], -.pdfjs .secondaryToolbarButton[disabled], -.pdfjs .overlayButton[disabled] { - opacity: 0.5; } - -.pdfjs .toolbarButton.group { - margin-right: 0; } - -.pdfjs .splitToolbarButton.toggled .toolbarButton { - margin: 0; } - -.pdfjs .splitToolbarButton:hover > .toolbarButton, -.pdfjs .splitToolbarButton:focus > .toolbarButton, -.pdfjs .splitToolbarButton.toggled > .toolbarButton, -.pdfjs .toolbarButton.textButton { - background-color: rgba(0, 0, 0, 0.12); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.35); - border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 150ms; - -webkit-transition-timing-function: ease; - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; } - -.pdfjs .splitToolbarButton > .toolbarButton:hover, -.pdfjs .splitToolbarButton > .toolbarButton:focus, -.pdfjs .dropdownToolbarButton:hover, -.pdfjs .overlayButton:hover, -.pdfjs .toolbarButton.textButton:hover, -.pdfjs .toolbarButton.textButton:focus { - background-color: rgba(0, 0, 0, 0.2); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 0 1px rgba(0, 0, 0, 0.05); - z-index: 199; } - -.pdfjs .splitToolbarButton > .toolbarButton { - position: relative; } - -html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:first-child, -html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:last-child { - position: relative; - margin: 0; - margin-right: -1px; - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - border-right-color: transparent; } - -html[dir='ltr'] .pdfjs .splitToolbarButton > .toolbarButton:last-child, -html[dir='rtl'] .pdfjs .splitToolbarButton > .toolbarButton:first-child { - position: relative; - margin: 0; - margin-left: -1px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - border-left-color: transparent; } - -.pdfjs .splitToolbarButtonSeparator { - padding: 8px 0; - width: 1px; - background-color: rgba(0, 0, 0, 0.5); - z-index: 99; - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); - display: inline-block; - margin: 5px 0; } - -html[dir='ltr'] .pdfjs .splitToolbarButtonSeparator { - float: left; } - -html[dir='rtl'] .pdfjs .splitToolbarButtonSeparator { - float: right; } - -.pdfjs .splitToolbarButton:hover > .splitToolbarButtonSeparator, -.pdfjs .splitToolbarButton.toggled > .splitToolbarButtonSeparator { - padding: 12px 0; - margin: 1px 0; - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03); - -webkit-transition-property: padding; - -webkit-transition-duration: 10ms; - -webkit-transition-timing-function: ease; - transition-property: padding; - transition-duration: 10ms; - transition-timing-function: ease; } - -.pdfjs .toolbarButton, -.pdfjs .dropdownToolbarButton, -.pdfjs .secondaryToolbarButton, -.pdfjs .overlayButton { - min-width: 16px; - padding: 2px 6px 0; - border: 1px solid transparent; - border-radius: 2px; - color: rgba(255, 255, 255, 0.8); - font-size: 12px; - line-height: 14px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - cursor: default; - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 150ms; - -webkit-transition-timing-function: ease; - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; } - -html[dir='ltr'] .pdfjs .toolbarButton, -html[dir='ltr'] .pdfjs .overlayButton, -html[dir='ltr'] .pdfjs .dropdownToolbarButton { - margin: 3px 2px 4px 0; } - -html[dir='rtl'] .pdfjs .toolbarButton, -html[dir='rtl'] .pdfjs .overlayButton, -html[dir='rtl'] .pdfjs .dropdownToolbarButton { - margin: 3px 0 4px 2px; } - -.pdfjs .toolbarButton:hover, -.pdfjs .toolbarButton:focus, -.pdfjs .dropdownToolbarButton, -.pdfjs .overlayButton, -.pdfjs .secondaryToolbarButton:hover, -.pdfjs .secondaryToolbarButton:focus { - background-color: rgba(0, 0, 0, 0.12); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.35); - border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.15) inset, 0 1px 0 rgba(255, 255, 255, 0.05); } - -.pdfjs .toolbarButton:hover:active, -.pdfjs .overlayButton:hover:active, -.pdfjs .dropdownToolbarButton:hover:active, -.pdfjs .secondaryToolbarButton:hover:active { - background-color: rgba(0, 0, 0, 0.2); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - border-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 10ms; - -webkit-transition-timing-function: linear; - transition-property: background-color, border-color, box-shadow; - transition-duration: 10ms; - transition-timing-function: linear; } - -.pdfjs .toolbarButton.toggled, -.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled, -.pdfjs .secondaryToolbarButton.toggled { - background-color: rgba(0, 0, 0, 0.3); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.45) rgba(0, 0, 0, 0.5); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - -webkit-transition-property: background-color, border-color, box-shadow; - -webkit-transition-duration: 10ms; - -webkit-transition-timing-function: linear; - transition-property: background-color, border-color, box-shadow; - transition-duration: 10ms; - transition-timing-function: linear; } - -.pdfjs .toolbarButton.toggled:hover:active, -.pdfjs .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active, -.pdfjs .secondaryToolbarButton.toggled:hover:active { - background-color: rgba(0, 0, 0, 0.4); - border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.5) rgba(0, 0, 0, 0.55); - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.3) inset, 0 1px 0 rgba(255, 255, 255, 0.05); } - -.pdfjs .dropdownToolbarButton { - width: 120px; - max-width: 120px; - padding: 0; - overflow: hidden; - background: url("../images/pdf.js-viewer/toolbarButton-menuArrows.png") no-repeat; } - -html[dir='ltr'] .pdfjs .dropdownToolbarButton { - background-position: 95%; } - -html[dir='rtl'] .pdfjs .dropdownToolbarButton { - background-position: 5%; } - -.pdfjs .dropdownToolbarButton > select { - min-width: 140px; - font-size: 12px; - color: #f2f2f2; - margin: 0; - padding: 3px 2px 2px; - border: none; - background: rgba(0, 0, 0, 0); } - -.pdfjs .dropdownToolbarButton > select > option { - background: #3d3d3d; } - -.pdfjs #customScaleOption { - display: none; } - -.pdfjs #pageWidthOption { - border-bottom: 1px rgba(255, 255, 255, 0.5) solid; } - -html[dir='ltr'] .pdfjs .splitToolbarButton:first-child, -html[dir='ltr'] .pdfjs .toolbarButton:first-child, -html[dir='rtl'] .pdfjs .splitToolbarButton:last-child, -html[dir='rtl'] .pdfjs .toolbarButton:last-child { - margin-left: 4px; } - -html[dir='ltr'] .pdfjs .splitToolbarButton:last-child, -html[dir='ltr'] .pdfjs .toolbarButton:last-child, -html[dir='rtl'] .pdfjs .splitToolbarButton:first-child, -html[dir='rtl'] .pdfjs .toolbarButton:first-child { - margin-right: 4px; } - -.pdfjs .toolbarButtonSpacer { - width: 30px; - display: inline-block; - height: 1px; } - -.pdfjs .toolbarButtonFlexibleSpacer { - -webkit-box-flex: 1; - -moz-box-flex: 1; - min-width: 30px; } - -html[dir='ltr'] .pdfjs #findPrevious { - margin-left: 3px; } - -html[dir='ltr'] .pdfjs #findNext { - margin-right: 3px; } - -html[dir='rtl'] .pdfjs #findPrevious { - margin-right: 3px; } - -html[dir='rtl'] .pdfjs #findNext { - margin-left: 3px; } - -.pdfjs .toolbarButton::before, -.pdfjs .secondaryToolbarButton::before { - position: absolute; - display: inline-block; - top: 4px; - left: 7px; } - -html[dir="ltr"] .pdfjs .secondaryToolbarButton::before { - left: 4px; } - -html[dir="rtl"] .pdfjs .secondaryToolbarButton::before { - right: 4px; } - -html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle.png"); } - -html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl.png"); } - -html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle.png"); } - -html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl.png"); } - -html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { - content: url("../images/pdf.js-viewer/findbarButton-previous.png"); } - -html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { - content: url("../images/pdf.js-viewer/findbarButton-previous-rtl.png"); } - -html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { - content: url("../images/pdf.js-viewer/findbarButton-next.png"); } - -html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { - content: url("../images/pdf.js-viewer/findbarButton-next-rtl.png"); } - -html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageUp.png"); } - -html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl.png"); } - -html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageDown.png"); } - -html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl.png"); } - -.pdfjs .toolbarButton.zoomOut::before { - content: url("../images/pdf.js-viewer/toolbarButton-zoomOut.png"); } - -.pdfjs .toolbarButton.zoomIn::before { - content: url("../images/pdf.js-viewer/toolbarButton-zoomIn.png"); } - -.pdfjs .toolbarButton.presentationMode::before, -.pdfjs .secondaryToolbarButton.presentationMode::before { - content: url("../images/pdf.js-viewer/toolbarButton-presentationMode.png"); } - -.pdfjs .toolbarButton.print::before, -.pdfjs .secondaryToolbarButton.print::before { - content: url("../images/pdf.js-viewer/toolbarButton-print.png"); } - -.pdfjs .toolbarButton.openFile::before, -.pdfjs .secondaryToolbarButton.openFile::before { - content: url("../images/pdf.js-viewer/toolbarButton-openFile.png"); } - -.pdfjs .toolbarButton.download::before, -.pdfjs .secondaryToolbarButton.download::before { - content: url("../images/pdf.js-viewer/toolbarButton-download.png"); } - -.pdfjs .toolbarButton.bookmark, -.pdfjs .secondaryToolbarButton.bookmark { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - outline: none; - padding-top: 4px; - text-decoration: none; } - -.pdfjs .secondaryToolbarButton.bookmark { - padding-top: 5px; } - -.pdfjs .bookmark[href='#'] { - opacity: .5; - pointer-events: none; } - -.pdfjs .toolbarButton.bookmark::before, -.pdfjs .secondaryToolbarButton.bookmark::before { - content: url("../images/pdf.js-viewer/toolbarButton-bookmark.png"); } - -.pdfjs #viewThumbnail.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail.png"); } - -html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewOutline.png"); } - -html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl.png"); } - -.pdfjs #viewAttachments.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments.png"); } - -.pdfjs #viewFind.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-search.png"); } - -.pdfjs .secondaryToolbarButton { - position: relative; - margin: 0 0 4px; - padding: 3px 0 1px; - height: auto; - min-height: 25px; - width: auto; - min-width: 100%; - white-space: normal; } - -html[dir="ltr"] .pdfjs .secondaryToolbarButton { - padding-left: 24px; - text-align: left; } - -html[dir="rtl"] .pdfjs .secondaryToolbarButton { - padding-right: 24px; - text-align: right; } - -html[dir="ltr"] .pdfjs .secondaryToolbarButton.bookmark { - padding-left: 27px; } - -html[dir="rtl"] .pdfjs .secondaryToolbarButton.bookmark { - padding-right: 27px; } - -html[dir="ltr"] .pdfjs .secondaryToolbarButton > span { - padding-right: 4px; } - -html[dir="rtl"] .pdfjs .secondaryToolbarButton > span { - padding-left: 4px; } - -.pdfjs .secondaryToolbarButton.firstPage::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage.png"); } - -.pdfjs .secondaryToolbarButton.lastPage::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage.png"); } - -.pdfjs .secondaryToolbarButton.rotateCcw::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw.png"); } - -.pdfjs .secondaryToolbarButton.rotateCw::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw.png"); } - -.pdfjs .secondaryToolbarButton.handTool::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool.png"); } - -.pdfjs .secondaryToolbarButton.documentProperties::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties.png"); } - -.pdfjs .verticalToolbarSeparator { - display: block; - padding: 8px 0; - margin: 8px 4px; - width: 1px; - background-color: rgba(0, 0, 0, 0.5); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); } - -html[dir='ltr'] .pdfjs .verticalToolbarSeparator { - margin-left: 2px; } - -html[dir='rtl'] .pdfjs .verticalToolbarSeparator { - margin-right: 2px; } - -.pdfjs .horizontalToolbarSeparator { - display: block; - margin: 0 0 4px; - height: 1px; - width: 100%; - background-color: rgba(0, 0, 0, 0.5); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); } - -.pdfjs .toolbarField { - padding: 3px 6px; - margin: 4px 0; - border: 1px solid transparent; - border-radius: 2px; - background-color: rgba(255, 255, 255, 0.09); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.35); - border-color: rgba(0, 0, 0, 0.32) rgba(0, 0, 0, 0.38) rgba(0, 0, 0, 0.42); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.05); - color: #f2f2f2; - font-size: 12px; - line-height: 14px; - outline-style: none; - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; } - -.pdfjs .toolbarField[type=checkbox] { - display: inline-block; - margin: 8px 0; } - -.pdfjs .toolbarField.pageNumber { - -moz-appearance: textfield; - min-width: 16px; - text-align: right; - width: 40px; } - -.pdfjs .toolbarField.pageNumber.visiblePageIsLoading { - background-image: url("../images/pdf.js-viewer/loading-small.png"); - background-repeat: no-repeat; - background-position: 1px; } - -.pdfjs .toolbarField.pageNumber::-webkit-inner-spin-button, -.pdfjs .toolbarField.pageNumber::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; } - -.pdfjs .toolbarField:hover { - background-color: rgba(255, 255, 255, 0.11); - border-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.43) rgba(0, 0, 0, 0.45); } - -.pdfjs .toolbarField:focus { - background-color: rgba(255, 255, 255, 0.15); - border-color: rgba(77, 184, 255, 0.8) rgba(77, 184, 255, 0.85) rgba(77, 184, 255, 0.9); } - -.pdfjs .toolbarLabel { - min-width: 16px; - padding: 3px 6px 3px 2px; - margin: 4px 2px 4px 0; - border: 1px solid transparent; - border-radius: 2px; - color: #d9d9d9; - font-size: 12px; - line-height: 14px; - text-align: left; - -webkit-user-select: none; - -moz-user-select: none; - cursor: default; } - -.pdfjs #thumbnailView { - position: absolute; - width: 120px; - top: 0; - bottom: 0; - padding: 10px 40px 0; - overflow: auto; - -webkit-overflow-scrolling: touch; } - -.pdfjs .thumbnail { - float: left; - margin-bottom: 5px; } - -.pdfjs #thumbnailView > a:last-of-type > .thumbnail { - margin-bottom: 10px; } - -.pdfjs #thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) { - margin-bottom: 9px; } - -.pdfjs .thumbnail:not([data-loaded]) { - border: 1px dashed rgba(255, 255, 255, 0.5); - margin: -1px -1px 4px; } - -.pdfjs .thumbnailImage { - border: 1px solid transparent; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); - opacity: .8; - z-index: 99; - background-color: #fff; - background-clip: content-box; } - -.pdfjs .thumbnailSelectionRing { - border-radius: 2px; - padding: 7px; } - -.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage, -.pdfjs .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage { - opacity: 0.9; } - -.pdfjs a:focus > .thumbnail > .thumbnailSelectionRing, -.pdfjs .thumbnail:hover > .thumbnailSelectionRing { - background-color: rgba(255, 255, 255, 0.15); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: rgba(255, 255, 255, 0.9); } - -.pdfjs .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage { - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5); - opacity: 1; } - -.pdfjs .thumbnail.selected > .thumbnailSelectionRing { - background-color: rgba(255, 255, 255, 0.3); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: #ffffff; } - -.pdfjs #outlineView, -.pdfjs #attachmentsView { - position: absolute; - width: 192px; - top: 0; - bottom: 0; - overflow: auto; - -webkit-overflow-scrolling: touch; - -webkit-user-select: none; - -moz-user-select: none; } - -.pdfjs #outlineView { - padding: 4px 4px 0; } - -.pdfjs #attachmentsView { - padding: 3px 4px 0; } - -html[dir='ltr'] .pdfjs .outlineItem > .outlineItems { - margin-left: 20px; } - -html[dir='rtl'] .pdfjs .outlineItem > .outlineItems { - margin-right: 20px; } - -.pdfjs .outlineItem > a, -.pdfjs .attachmentsItem > button { - text-decoration: none; - display: inline-block; - min-width: 95%; - height: auto; - margin-bottom: 1px; - border-radius: 2px; - color: rgba(255, 255, 255, 0.8); - font-size: 13px; - line-height: 15px; - -moz-user-select: none; - white-space: normal; } - -.pdfjs .attachmentsItem > button { - border: 0 none; - background: none; - cursor: pointer; - width: 100%; } - -html[dir='ltr'] .pdfjs .outlineItem > a { - padding: 2px 0 5px 10px; } - -html[dir='ltr'] .pdfjs .attachmentsItem > button { - padding: 2px 0 3px 7px; - text-align: left; } - -html[dir='rtl'] .pdfjs .outlineItem > a { - padding: 2px 10px 5px 0; } - -html[dir='rtl'] .pdfjs .attachmentsItem > button { - padding: 2px 7px 3px 0; - text-align: right; } - -.pdfjs .outlineItem > a:hover, -.pdfjs .attachmentsItem > button:hover { - background-color: rgba(255, 255, 255, 0.02); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.2) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: rgba(255, 255, 255, 0.9); } - -.pdfjs .outlineItem.selected { - background-color: rgba(255, 255, 255, 0.08); - background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)); - background-clip: padding-box; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 0 1px rgba(0, 0, 0, 0.2); - color: #ffffff; } - -.pdfjs .noResults { - font-size: 12px; - color: rgba(255, 255, 255, 0.8); - font-style: italic; - cursor: default; } - -.pdfjs ::selection { - background: rgba(0, 0, 255, 0.3); } - -.pdfjs ::-moz-selection { - background: rgba(0, 0, 255, 0.3); } - -.pdfjs #errorWrapper { - background: none repeat scroll 0 0 #F55; - color: #fff; - left: 0; - position: absolute; - right: 0; - z-index: 1000; - padding: 3px; - font-size: 0.8em; } - -.pdfjs .loadingInProgress #errorWrapper { - top: 37px; } - -.pdfjs #errorMessageLeft { - float: left; } - -.pdfjs #errorMessageRight { - float: right; } - -.pdfjs #errorMoreInfo { - background-color: #FFF; - color: #000; - padding: 3px; - margin: 3px; - width: 98%; } - -.pdfjs .overlayButton { - width: auto; - margin: 3px 4px 2px !important; - padding: 2px 6px 3px; } - -.pdfjs #overlayContainer { - display: table; - position: absolute; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.2); - z-index: 40000; } - -.pdfjs #overlayContainer > * { - overflow: auto; - -webkit-overflow-scrolling: touch; } - -.pdfjs #overlayContainer > .container { - display: table-cell; - vertical-align: middle; - text-align: center; } - -.pdfjs #overlayContainer > .container > .dialog { - display: inline-block; - padding: 15px; - border-spacing: 4px; - color: #d9d9d9; - font-size: 12px; - line-height: 14px; - background-color: #474747; - background-image: url("../images/pdf.js-viewer/texture.png"), linear-gradient(rgba(82, 82, 82, 0.99), rgba(69, 69, 69, 0.95)); - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08), inset 0 1px 1px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(255, 255, 255, 0.05), 0 1px 0 rgba(0, 0, 0, 0.15), 0 1px 1px rgba(0, 0, 0, 0.1); - border: 1px solid rgba(0, 0, 0, 0.5); - border-radius: 4px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } - -.pdfjs .dialog > .row { - display: table-row; } - -.pdfjs .dialog > .row > * { - display: table-cell; } - -.pdfjs .dialog .toolbarField { - margin: 5px 0; } - -.pdfjs .dialog .separator { - display: block; - margin: 4px 0; - height: 1px; - width: 100%; - background-color: rgba(0, 0, 0, 0.5); - box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); } - -.pdfjs .dialog .buttonRow { - text-align: center; - vertical-align: middle; } - -.pdfjs #passwordOverlay > .dialog { - text-align: center; } - -.pdfjs #passwordOverlay .toolbarField { - width: 200px; } - -.pdfjs #documentPropertiesOverlay > .dialog { - text-align: left; } - -.pdfjs #documentPropertiesOverlay .row > * { - min-width: 100px; } - -html[dir='ltr'] .pdfjs #documentPropertiesOverlay .row > * { - text-align: left; } - -html[dir='rtl'] .pdfjs #documentPropertiesOverlay .row > * { - text-align: right; } - -.pdfjs #documentPropertiesOverlay .row > span { - width: 125px; - word-wrap: break-word; } - -.pdfjs #documentPropertiesOverlay .row > p { - max-width: 225px; - word-wrap: break-word; } - -.pdfjs #documentPropertiesOverlay .buttonRow { - margin-top: 10px; } - -.pdfjs .clearBoth { - clear: both; } - -.pdfjs .fileInput { - background: #fff; - color: #000; - margin-top: 5px; - visibility: hidden; - position: fixed; - right: 0; - top: 0; } - -.pdfjs #PDFBug { - background: none repeat scroll 0 0 #fff; - border: 1px solid #666; - position: fixed; - top: 32px; - right: 0; - bottom: 0; - font-size: 10px; - padding: 0; - width: 300px; } - -.pdfjs #PDFBug .controls { - background: #EEE; - border-bottom: 1px solid #666; - padding: 3px; } - -.pdfjs #PDFBug .panels { - bottom: 0; - left: 0; - overflow: auto; - -webkit-overflow-scrolling: touch; - position: absolute; - right: 0; - top: 27px; } - -.pdfjs #PDFBug button.active { - font-weight: 700; } - -.pdfjs .debuggerShowText { - background: none repeat scroll 0 0 #ff0; - color: blue; } - -.pdfjs .debuggerHideText:hover { - background: none repeat scroll 0 0 #ff0; } - -.pdfjs #PDFBug .stats { - font-family: courier; - font-size: 10px; - white-space: pre; } - -.pdfjs #PDFBug .stats .title { - font-weight: 700; } - -.pdfjs #PDFBug table { - font-size: 10px; } - -.pdfjs #viewer.textLayer-visible .textLayer > div, -.pdfjs #viewer.textLayer-hover .textLayer > div:hover { - background-color: #fff; - color: #000; } - -.pdfjs #viewer.textLayer-shadow .textLayer > div { - background-color: rgba(255, 255, 255, 0.6); - color: #000; } - -.pdfjs .grab-to-pan-grab { - cursor: url("../images/pdf.js-viewer/grab.cur"), move !important; - cursor: -webkit-grab !important; - cursor: -moz-grab !important; - cursor: grab !important; } - -.pdfjs .grab-to-pan-grab :not(input):not(textarea):not(button):not(select):not(:link) { - cursor: inherit !important; } - -.pdfjs .grab-to-pan-grab:active, -.pdfjs .grab-to-pan-grabbing { - cursor: url("../images/pdf.js-viewer/grabbing.cur"), move !important; - cursor: -webkit-grabbing !important; - cursor: -moz-grabbing !important; - cursor: grabbing !important; - position: fixed; - background: transparent; - display: block; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; - z-index: 50000; } - -@page { - margin: 0; } - -.pdfjs #printContainer { - display: none; } - -@media screen and (min-resolution: 2dppx) { - .pdfjs .toolbarButton::before { - -webkit-transform: scale(0.5); - transform: scale(0.5); - top: -5px; } - .pdfjs .secondaryToolbarButton::before { - -webkit-transform: scale(0.5); - transform: scale(0.5); - top: -4px; } - html[dir='ltr'] .pdfjs .toolbarButton::before, - html[dir='rtl'] .pdfjs .toolbarButton::before { - left: -1px; } - html[dir="ltr"] .pdfjs .secondaryToolbarButton::before { - left: -2px; } - html[dir="rtl"] .pdfjs .secondaryToolbarButton::before { - left: 186px; } - .pdfjs .toolbarField.pageNumber.visiblePageIsLoading, - .pdfjs #findInput[data-status="pending"] { - background-image: url("../images/pdf.js-viewer/loading-small@2x.png"); - background-size: 16px 17px; } - .pdfjs .dropdownToolbarButton { - background: url("../images/pdf.js-viewer/toolbarButton-menuArrows@2x.png") no-repeat; - background-size: 7px 16px; } - html[dir='ltr'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle@2x.png"); } - html[dir='rtl'] .pdfjs .toolbarButton#sidebarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-sidebarToggle-rtl@2x.png"); } - html[dir='ltr'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle@2x.png"); } - html[dir='rtl'] .pdfjs .toolbarButton#secondaryToolbarToggle::before { - content: url("../images/pdf.js-viewer/toolbarButton-secondaryToolbarToggle-rtl@2x.png"); } - html[dir='ltr'] .pdfjs .toolbarButton.findPrevious::before { - content: url("../images/pdf.js-viewer/findbarButton-previous@2x.png"); } - html[dir='rtl'] .pdfjs .toolbarButton.findPrevious::before { - content: url("../images/pdf.js-viewer/findbarButton-previous-rtl@2x.png"); } - html[dir='ltr'] .pdfjs .toolbarButton.findNext::before { - content: url("../images/pdf.js-viewer/findbarButton-next@2x.png"); } - html[dir='rtl'] .pdfjs .toolbarButton.findNext::before { - content: url("../images/pdf.js-viewer/findbarButton-next-rtl@2x.png"); } - html[dir='ltr'] .pdfjs .toolbarButton.pageUp::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageUp@2x.png"); } - html[dir='rtl'] .pdfjs .toolbarButton.pageUp::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageUp-rtl@2x.png"); } - html[dir='ltr'] .pdfjs .toolbarButton.pageDown::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageDown@2x.png"); } - html[dir='rtl'] .pdfjs .toolbarButton.pageDown::before { - content: url("../images/pdf.js-viewer/toolbarButton-pageDown-rtl@2x.png"); } - .pdfjs .toolbarButton.zoomIn::before { - content: url("../images/pdf.js-viewer/toolbarButton-zoomIn@2x.png"); } - .pdfjs .toolbarButton.zoomOut::before { - content: url("../images/pdf.js-viewer/toolbarButton-zoomOut@2x.png"); } - .pdfjs .toolbarButton.presentationMode::before, - .pdfjs .secondaryToolbarButton.presentationMode::before { - content: url("../images/pdf.js-viewer/toolbarButton-presentationMode@2x.png"); } - .pdfjs .toolbarButton.print::before, - .pdfjs .secondaryToolbarButton.print::before { - content: url("../images/pdf.js-viewer/toolbarButton-print@2x.png"); } - .pdfjs .toolbarButton.openFile::before, - .pdfjs .secondaryToolbarButton.openFile::before { - content: url("../images/pdf.js-viewer/toolbarButton-openFile@2x.png"); } - .pdfjs .toolbarButton.download::before, - .pdfjs .secondaryToolbarButton.download::before { - content: url("../images/pdf.js-viewer/toolbarButton-download@2x.png"); } - .pdfjs .toolbarButton.bookmark::before, - .pdfjs .secondaryToolbarButton.bookmark::before { - content: url("../images/pdf.js-viewer/toolbarButton-bookmark@2x.png"); } - .pdfjs #viewThumbnail.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewThumbnail@2x.png"); } - html[dir="ltr"] .pdfjs #viewOutline.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewOutline@2x.png"); } - html[dir="rtl"] .pdfjs #viewOutline.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewOutline-rtl@2x.png"); } - .pdfjs #viewAttachments.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-viewAttachments@2x.png"); } - .pdfjs #viewFind.toolbarButton::before { - content: url("../images/pdf.js-viewer/toolbarButton-search@2x.png"); } - .pdfjs .secondaryToolbarButton.firstPage::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-firstPage@2x.png"); } - .pdfjs .secondaryToolbarButton.lastPage::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-lastPage@2x.png"); } - .pdfjs .secondaryToolbarButton.rotateCcw::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCcw@2x.png"); } - .pdfjs .secondaryToolbarButton.rotateCw::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-rotateCw@2x.png"); } - .pdfjs .secondaryToolbarButton.handTool::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-handTool@2x.png"); } - .pdfjs .secondaryToolbarButton.documentProperties::before { - content: url("../images/pdf.js-viewer/secondaryToolbarButton-documentProperties@2x.png"); } } - -@media print { - body { - background: transparent none; } - .pdfjs #sidebarContainer, - .pdfjs #secondaryToolbar, - .pdfjs .toolbar, - .pdfjs #loadingBox, - .pdfjs #errorWrapper, - .pdfjs .textLayer { - display: none; } - .pdfjs #viewerContainer { - overflow: visible; } - .pdfjs #mainContainer, - .pdfjs #viewerContainer, - .pdfjs .page, - .pdfjs .page canvas { - position: static; - padding: 0; - margin: 0; } - .pdfjs .page { - float: left; - display: none; - border: none; - box-shadow: none; - background-clip: content-box; - background-color: #fff; } - .pdfjs .page[data-loaded] { - display: block; } - .pdfjs .fileInput { - display: none; } - body[data-mozPrintCallback] .pdfjs #outerContainer { - display: none; } - body[data-mozPrintCallback] .pdfjs #printContainer { - display: block; } - .pdfjs #printContainer > div { - position: relative; - top: 0; - left: 0; - overflow: hidden; } - .pdfjs #printContainer canvas { - display: block; } } - -.pdfjs .visibleLargeView, -.pdfjs .visibleMediumView, -.pdfjs .visibleSmallView { - display: none; } - -@media all and (max-width: 960px) { - html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter { - float: left; - left: 205px; } - html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter { - float: right; - right: 205px; } } - -@media all and (max-width: 900px) { - .pdfjs .sidebarOpen .hiddenLargeView { - display: none; } - .pdfjs .sidebarOpen .visibleLargeView { - display: inherit; } } - -@media all and (max-width: 860px) { - .pdfjs .sidebarOpen .hiddenMediumView { - display: none; } - .pdfjs .sidebarOpen .visibleMediumView { - display: inherit; } } - -@media all and (max-width: 770px) { - .pdfjs #sidebarContainer { - top: 32px; - z-index: 100; } - .pdfjs .loadingInProgress #sidebarContainer { - top: 37px; } - .pdfjs #sidebarContent { - top: 32px; - background-color: rgba(0, 0, 0, 0.7); } - html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - left: 0; } - html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen > #mainContainer { - right: 0; } - html[dir='ltr'] .pdfjs .outerCenter { - float: left; - left: 205px; } - html[dir='rtl'] .pdfjs .outerCenter { - float: right; - right: 205px; } - .pdfjs #outerContainer .hiddenLargeView, - .pdfjs #outerContainer .hiddenMediumView { - display: inherit; } - .pdfjs #outerContainer .visibleLargeView, - .pdfjs #outerContainer .visibleMediumView { - display: none; } } - -@media all and (max-width: 700px) { - .pdfjs #outerContainer .hiddenLargeView { - display: none; } - .pdfjs #outerContainer .visibleLargeView { - display: inherit; } } - -@media all and (max-width: 660px) { - .pdfjs #outerContainer .hiddenMediumView { - display: none; } - .pdfjs #outerContainer .visibleMediumView { - display: inherit; } } - -@media all and (max-width: 600px) { - .pdfjs .hiddenSmallView { - display: none; } - .pdfjs .visibleSmallView { - display: inherit; } - html[dir='ltr'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='ltr'] .pdfjs #outerContainer.sidebarOpen .outerCenter, - html[dir='ltr'] .pdfjs .outerCenter { - left: 156px; } - html[dir='rtl'] .pdfjs #outerContainer.sidebarMoving .outerCenter, - html[dir='rtl'] .pdfjs #outerContainer.sidebarOpen .outerCenter, - html[dir='rtl'] .pdfjs .outerCenter { - right: 156px; } - .pdfjs .toolbarButtonSpacer { - width: 0; } } - -@media all and (max-width: 510px) { - .pdfjs #scaleSelectContainer, - .pdfjs #pageNumberLabel { - display: none; } } - -/* should be hidden differently */ -#fileInput.fileInput { - display: none; } - -.wvSplitpane { - height: 100%; - padding: 7px 2px 2px 2px; - position: relative; } - -.wvSplitpane__cell { - display: inline-block; - float: left; - height: 100%; - width: 100%; - position: relative; } - -.wvSplitpane__cellBorder, .wvSplitpane__cellBorder--selected, .wvSplitpane__cellBorder--blue, .wvSplitpane__cellBorder--red, .wvSplitpane__cellBorder--green, .wvSplitpane__cellBorder--yellow, .wvSplitpane__cellBorder--violet { - display: inline-block; - float: left; - height: calc(100% - 2px); - width: calc(100% - 2px); - border: 2px dashed transparent; - padding: 2px; - margin: 1px; } - -.wvSplitpane__cellBorder--selected { - border: 2px solid rgba(51, 152, 219, 0.7); } - -.wvSplitpane__cellBorder--blue { - border-color: rgba(51, 152, 219, 0.7); } - -.wvSplitpane__cellBorder--red { - border-color: rgba(206, 0, 0, 0.7); } - -.wvSplitpane__cellBorder--green { - border-color: rgba(0, 160, 27, 0.7); } - -.wvSplitpane__cellBorder--yellow { - border-color: rgba(220, 200, 0, 0.9); } - -.wvSplitpane__cellBorder--violet { - border-color: rgba(255, 31, 255, 0.7); } - -wv-pane-policy { - display: block; - width: 100%; - height: 100%; } - wv-pane-policy > div[ng-transclude] { - display: block; - width: 100%; - height: 100%; } - -.wv-timeline { - position: relative; - height: 2em; } - .wv-timeline.reduced { - height: 5px; } - .wv-timeline.reduced .wv-timeline-loading-bar-wrapper { - width: 100%; - height: 100%; } - -.wv-timeline-controls-wrapper { - position: absolute; - left: 0; - bottom: 0; - width: 16em; - height: 100%; - color: white; } - -.wv-timeline-loading-bar-wrapper { - position: absolute; - right: 0; - bottom: 0; - width: calc(100% - 16em); - height: calc(100% + 2px); } - .wv-timeline-loading-bar-wrapper svg { - position: absolute; - left: 0; - top: 0; } - -/* wv-timeline-controls directive */ -.wv-timeline-controls { - padding: 0.5em 0.5em 0.5em 0.5em; - line-height: 1em; - background-color: rgba(0, 0, 0, 0.66); - text-align: center; - transition: color 500ms, background-color 500ms; } - -.wv-timeline-controls:hover { - background-color: rgba(0, 0, 0, 0.9); } - -.wv-timeline-controls-vertical-sizing { - display: inline-block; - line-height: 1em; - font-size: 1em; } - -.wv-timeline-controls-vflip:before, .wv-timeline-controls-vflip:after { - transform: scaleX(-1); - display: inline-block; } - -.wv-timeline-controls-button { - display: inline-block; - height: 1em; - width: 1em; - line-height: 1em; - font-size: 1em; - margin: 0; - user-select: none; - cursor: pointer; } - -.wv-timeline-controls-input { - height: 1em; - width: 3em; - padding: 0; - padding-bottom: 1px; - box-sizing: content-box; - border: none; - border-bottom: 1px solid rgba(255, 202, 128, 0.24); - background-color: transparent; - text-align: right; } - -.wv-timeline-controls-play-button-wrapper { - float: right; } - -/* wv-play-button directive */ -.wv-play-button { - display: inline-block; - position: relative; - line-height: 1em; - height: 3em; - width: 6em; - padding-bottom: 1em; - padding-left: 0.25em; - padding-right: 0.25em; } - -.wv-play-button:hover .wv-play-button-config-position-handler { - visibility: visible; } - -.wv-play-button-config-position-handler { - visibility: hidden; - position: absolute; - bottom: 3em; - left: 1em; - right: 0.5em; } - -.wv-play-button-config { - position: absolute; - bottom: 0; - left: -6em; - width: 12em; - padding: 1em; - background-color: rgba(0, 0, 0, 0.5); } - -/* Style range input (see http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html) */ -.wv-play-button-config-framerate-wrapper { - display: inline-block; - margin: 0.25em 0 0.5em 0; } - -input[type="range"].wv-play-button-config-framerate { - /*removes default webkit styles*/ - -webkit-appearance: none; - /*fix for FF unable to apply focus style bug */ - border: 1px solid white; - /*required for proper track sizing in FF*/ - width: 10em; } - -input[type="range"].wv-play-button-config-framerate::-webkit-slider-runnable-track { - width: 10em; - height: 5px; - background: #ddd; - border: none; - border-radius: 3px; } - -input[type="range"].wv-play-button-config-framerate::-webkit-slider-thumb { - -webkit-appearance: none; - border: none; - height: 16px; - width: 16px; - border-radius: 50%; - background: goldenrod; - margin-top: -4px; } - -input[type="range"].wv-play-button-config-framerate:focus { - outline: none; } - -input[type="range"].wv-play-button-config-framerate:focus::-webkit-slider-runnable-track { - background: #ccc; } - -input[type="range"].wv-play-button-config-framerate::-moz-range-track { - width: 10em; - height: 5px; - background: #ddd; - border: none; - border-radius: 3px; } - -input[type="range"].wv-play-button-config-framerate::-moz-range-thumb { - border: none; - height: 16px; - width: 16px; - border-radius: 50%; - background: goldenrod; } - -/*hide the outline behind the border*/ -input[type="range"].wv-play-button-config-framerate:-moz-focusring { - outline: 1px solid white; - outline-offset: -1px; } - -input[type="range"].wv-play-button-config-framerate::-ms-track { - width: 10em; - height: 5px; - /*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */ - background: transparent; - /*leave room for the larger thumb to overflow with a transparent border */ - border-color: transparent; - border-width: 6px 0; - /*remove default tick marks*/ - color: transparent; } - -input[type="range"].wv-play-button-config-framerate::-ms-fill-lower { - background: #777; - border-radius: 10px; } - -input[type="range"].wv-play-button-config-framerate::-ms-fill-upper { - background: #ddd; - border-radius: 10px; } - -input[type="range"].wv-play-button-config-framerate::-ms-thumb { - border: none; - height: 16px; - width: 16px; - border-radius: 50%; - background: goldenrod; } - -input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-lower { - background: #888; } - -input[type="range"].wv-play-button-config-framerate:focus::-ms-fill-upper { - background: #ccc; } - -.wv-loadingbar-image-bar { - cursor: pointer; } - -.wv-loadingbar-not-loaded { - fill: rgba(255, 255, 255, 0.1); } - -.wv-loadingbar-not-loaded, .wv-loadingbar-LOW-quality { - transition: none; } - -.wv-loadingbar-not-loaded:hover { - fill: rgba(255, 255, 255, 0.2); } - -.wv-loadingbar-LOSSLESS-quality, .wv-loadingbar-PIXELDATA-quality { - fill: rgba(0, 255, 0, 0.7); } - -.wv-loadingbar-LOSSLESS-quality:hover, -.wv-loadingbar-LOSSLESS-quality.wv-loadingbar-active, -.wv-loadingbar-PIXELDATA-quality:hover, -.wv-loadingbar-PIXELDATA-quality.wv-loadingbar-active { - fill: lime; } - -.wv-loadingbar-LOW-quality { - fill: rgba(255, 0, 0, 0.7); } - -.wv-loadingbar-LOW-quality:hover, .wv-loadingbar-LOW-quality.wv-loadingbar-active { - fill: red; } - -.wv-loadingbar-MEDIUM-quality { - fill: rgba(255, 95, 0, 0.7); } - -.wv-loadingbar-MEDIUM-quality:hover, .wv-loadingbar-MEDIUM-quality.wv-loadingbar-active { - fill: #ff5f00; } - -.disclaimer { - color: #E63F24; - background-color: #303030; - padding: 5px; - text-align: center; - font-weight: bold; } - -.tbGroup { - position: relative; } - -.tbGroup__buttons--base, .tbGroup__buttons--bottom, .tbGroup__buttons--left { - z-index: 5; - background-color: black; - position: absolute; } - -.tbGroup__buttons--bottom { - right: 0; - display: block; } - -.tbGroup__buttons--left { - right: 100%; - top: 0; - display: block; } - -.tbGroup__icon { - display: block; - position: absolute; - bottom: 0; - left: 0; - width: 0; - height: 0; - border-style: solid; - border-width: 10px 0 0 10px; - border-color: transparent transparent transparent rgba(255, 255, 255, 0.1); } - .tbGroup__icon.active { - border-color: transparent transparent transparent #3498db; } - -wv-viewport { - display: inline-block; - width: 100%; - height: 100%; } - wv-viewport > div { - position: relative; - width: 100%; - height: 100%; } - wv-viewport > div > .wv-cornerstone-enabled-image { - width: 100%; - height: 100%; - text-align: center; } - -.wv-draggable-clone { - width: 150px; - height: 150px; - background-color: rgba(255, 255, 255, 0.25); } - -@media print { - .wvPrintExclude { - display: none; } - .wvPrintFullPage { - width: 100% !important; - height: 100% !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - display: block !important; } - .wvLayout__main { - top: 0 !important; - right: 0 !important; - left: 0 !important; - bottom: 0 !important; } - .wvPrintViewer { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; } - .wvPrintViewer canvas { - max-width: 100% !important; - max-height: 100% !important; - margin: auto; } - .wv-overlay-topleft, .wv-overlay-topleft *, .wv-overlay-topright, .wv-overlay-topright *, .wv-overlay-bottomright, .wv-overlay-bottomright *, .wv-overlay-bottomleft, .wv-overlay-bottomleft * { - background-color: black !important; - -webkit-print-color-adjust: exact !important; - color-adjust: exact !important; - color: orange !important; } - .tooltip { - display: none !important; } - body { - margin: 0; - padding: 0; - position: relative; - width: 8.5in; - height: 11in; } - body, body * { - background-color: black !important; - -webkit-print-color-adjust: exact !important; } } - -.closePrintButton { - display: none; } - -body.print .wvPrintExclude { - display: none; } - -body.print .wvPrintFullPage { - width: 100% !important; - height: 100% !important; - position: absolute !important; - top: 0 !important; - left: 0 !important; - display: block !important; } - -body.print .wvLayout__main { - top: 0 !important; - right: 0 !important; - left: 0 !important; - bottom: 0 !important; } - -body.print .wvPrintViewer { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; } - -body.print .wvPrintViewer canvas { - max-width: 100% !important; - max-height: 100% !important; - margin: auto; } - -body.print .wv-overlay-topleft, body.print .wv-overlay-topleft *, body.print .wv-overlay-topright, body.print .wv-overlay-topright *, body.print .wv-overlay-bottomright, body.print .wv-overlay-bottomright *, body.print .wv-overlay-bottomleft, body.print .wv-overlay-bottomleft * { - background-color: black !important; - -webkit-print-color-adjust: exact !important; - color-adjust: exact !important; - color: orange !important; } - -body.print .tooltip { - display: none !important; } - -body.print body { - margin: 0; - padding: 0; - position: relative; - width: 8.5in; - height: 11in; } - body.print body, body.print body * { - background-color: black !important; - -webkit-print-color-adjust: exact !important; } - -@media screen { - body.print .closePrintButton { - display: block; - position: fixed; - top: 0; - right: 0; - padding: 10px; - font-size: 24px; - background-color: black; - color: white; - border: none; } }
--- a/StoneWebViewer/WebApplication/app.js Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,614 +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 <http://www.gnu.org/licenses/>. - **/ - - -var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ]; -var SERIES_INSTANCE_UID = '0020,000e'; -var STUDY_INSTANCE_UID = '0020,000d'; -var STUDY_DESCRIPTION = '0008,1030'; -var STUDY_DATE = '0008,0020'; - - -function getParameterFromUrl(key) { - var url = window.location.search.substring(1); - var args = url.split('&'); - for (var i = 0; i < args.length; i++) { - var arg = args[i].split('='); - if (arg[0] == key) { - return arg[1]; - } - } -} - - -Vue.component('viewport', { - props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex', - 'quality', 'framesCount', 'currentFrame', 'showInfo' ], - template: '#viewport-template', - data: function () { - return { - stone: stone, // To access global object "stone" from "index.html" - status: 'waiting' - } - }, - watch: { - series: function(newVal, oldVal) { - this.status = 'loading'; - - var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID]; - var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID]; - stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid); - - if ((newVal.type == stone.ThumbnailType.IMAGE || - newVal.type == stone.ThumbnailType.NO_PREVIEW) && - newVal.complete) { - this.status = 'ready'; - - var that = this; - Vue.nextTick(function() { - stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid); - }); - } - else if (newVal.type == stone.ThumbnailType.PDF || - newVal.type == stone.ThumbnailType.VIDEO) { - // TODO - } - } - }, - methods: { - SeriesDragAccept: function(event) { - event.preventDefault(); - }, - SeriesDragDrop: function(event) { - event.preventDefault(); - this.$emit('updated-series', event.dataTransfer.getData('seriesIndex')); - }, - MakeActive: function() { - this.$emit('selected-viewport'); - }, - DecrementFrame: function() { - stone.DecrementFrame(this.canvasId); - }, - IncrementFrame: function() { - stone.IncrementFrame(this.canvasId); - } - } -}) - - -var app = new Vue({ - el: '#wv', - data: function() { - return { - stone: stone, // To access global object "stone" from "index.html" - ready: false, - leftMode: 'grid', // Can be 'small', 'grid' or 'full' - leftVisible: true, - showWarning: false, - viewportLayoutButtonsVisible: false, - activeViewport: 0, - showInfo: true, - showReferenceLines: true, - - viewport1Width: '100%', - viewport1Height: '100%', - viewport1Left: '0%', - viewport1Top: '0%', - viewport1Visible: true, - viewport1Series: {}, - viewport1Quality: '', - viewport1FramesCount: 0, - viewport1CurrentFrame: 0, - - viewport2Width: '100%', - viewport2Height: '100%', - viewport2Left: '0%', - viewport2Top: '0%', - viewport2Visible: false, - viewport2Series: {}, - viewport2Quality: '', - viewport2FramesCount: 0, - viewport2CurrentFrame: 0, - - viewport3Width: '100%', - viewport3Height: '100%', - viewport3Left: '0%', - viewport3Top: '0%', - viewport3Visible: false, - viewport3Series: {}, - viewport3Quality: '', - viewport3FramesCount: 0, - viewport3CurrentFrame: 0, - - viewport4Width: '100%', - viewport4Height: '100%', - viewport4Left: '0%', - viewport4Top: '0%', - viewport4Visible: false, - viewport4Series: {}, - viewport4Quality: '', - viewport4FramesCount: 0, - viewport4CurrentFrame: 0, - - series: [], - studies: [], - seriesIndex: {} // Maps "SeriesInstanceUID" to "index in this.series" - } - }, - computed: { - getSelectedStudies() { - var s = ''; - for (var i = 0; i < this.studies.length; i++) { - if (this.studies[i].selected) { - if (s.length > 0) - s += ', '; - s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' + - this.studies[i].tags[STUDY_DATE] + ']'); - } - } - if (s.length == 0) - return '...'; - else - return s; - } - }, - watch: { - leftVisible: function(newVal, oldVal) { - this.FitContent(); - }, - showReferenceLines: function(newVal, oldVal) { - stone.ShowReferenceLines(newVal ? 1 : 0); - } - }, - methods: { - FitContent() { - // This function can be used even if WebAssembly is not initialized yet - if (typeof stone._AllViewportsUpdateSize !== 'undefined') { - this.$nextTick(function () { - stone.AllViewportsUpdateSize(true /* fit content */); - }); - } - }, - - GetActiveSeries() { - var s = []; - - if ('tags' in this.viewport1Series) - s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]); - - if ('tags' in this.viewport2Series) - s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]); - - if ('tags' in this.viewport3Series) - s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]); - - if ('tags' in this.viewport4Series) - s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]); - - return s; - }, - - GetActiveCanvas() { - if (this.activeViewport == 1) { - return 'canvas1'; - } - else if (this.activeViewport == 2) { - return 'canvas2'; - } - else if (this.activeViewport == 3) { - return 'canvas3'; - } - else if (this.activeViewport == 4) { - return 'canvas4'; - } - else { - return 'canvas1'; - } - }, - - SetResources: function(sourceStudies, sourceSeries) { - var indexStudies = {}; - - var studies = []; - var posColor = 0; - - for (var i = 0; i < sourceStudies.length; i++) { - var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID]; - if (studyInstanceUid !== undefined) { - if (studyInstanceUid in indexStudies) { - console.error('Twice the same study: ' + studyInstanceUid); - } else { - indexStudies[studyInstanceUid] = studies.length; - - studies.push({ - 'studyInstanceUid' : studyInstanceUid, - 'series' : [ ], - 'color' : COLORS[posColor], - 'selected' : true, - 'tags' : sourceStudies[i] - }); - - posColor = (posColor + 1) % COLORS.length; - } - } - } - - var series = []; - var seriesIndex = {}; - - for (var i = 0; i < sourceSeries.length; i++) { - var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID]; - var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID]; - if (studyInstanceUid !== undefined && - seriesInstanceUid !== undefined) { - if (studyInstanceUid in indexStudies) { - seriesIndex[seriesInstanceUid] = series.length; - var study = studies[indexStudies[studyInstanceUid]]; - study.series.push(i); - series.push({ - //'length' : 4, - 'complete' : false, - 'type' : stone.ThumbnailType.LOADING, - 'color': study.color, - 'tags': sourceSeries[i] - }); - } - } - } - - this.studies = studies; - this.series = series; - this.seriesIndex = seriesIndex; - this.ready = true; - }, - - SeriesDragStart: function(event, seriesIndex) { - event.dataTransfer.setData('seriesIndex', seriesIndex); - }, - - SetViewportSeries: function(viewportIndex, seriesIndex) { - var series = this.series[seriesIndex]; - - if (viewportIndex == 1) { - this.viewport1Series = series; - } - else if (viewportIndex == 2) { - this.viewport2Series = series; - } - else if (viewportIndex == 3) { - this.viewport3Series = series; - } - else if (viewportIndex == 4) { - this.viewport4Series = series; - } - }, - - ClickSeries: function(seriesIndex) { - this.SetViewportSeries(this.activeViewport, seriesIndex); - }, - - HideViewport: function(index) { - if (index == 1) { - this.viewport1Visible = false; - } - else if (index == 2) { - this.viewport2Visible = false; - } - else if (index == 3) { - this.viewport3Visible = false; - } - else if (index == 4) { - this.viewport4Visible = false; - } - }, - - ShowViewport: function(index, left, top, width, height) { - if (index == 1) { - this.viewport1Visible = true; - this.viewport1Left = left; - this.viewport1Top = top; - this.viewport1Width = width; - this.viewport1Height = height; - } - else if (index == 2) { - this.viewport2Visible = true; - this.viewport2Left = left; - this.viewport2Top = top; - this.viewport2Width = width; - this.viewport2Height = height; - } - else if (index == 3) { - this.viewport3Visible = true; - this.viewport3Left = left; - this.viewport3Top = top; - this.viewport3Width = width; - this.viewport3Height = height; - } - else if (index == 4) { - this.viewport4Visible = true; - this.viewport4Left = left; - this.viewport4Top = top; - this.viewport4Width = width; - this.viewport4Height = height; - } - }, - - SetViewportLayout: function(layout) { - this.viewportLayoutButtonsVisible = false; - if (layout == '1x1') { - this.ShowViewport(1, '0%', '0%', '100%', '100%'); - this.HideViewport(2); - this.HideViewport(3); - this.HideViewport(4); - } - else if (layout == '2x2') { - this.ShowViewport(1, '0%', '0%', '50%', '50%'); - this.ShowViewport(2, '50%', '0%', '50%', '50%'); - this.ShowViewport(3, '0%', '50%', '50%', '50%'); - this.ShowViewport(4, '50%', '50%', '50%', '50%'); - } - else if (layout == '2x1') { - this.ShowViewport(1, '0%', '0%', '50%', '100%'); - this.ShowViewport(2, '50%', '0%', '50%', '100%'); - this.HideViewport(3); - this.HideViewport(4); - } - else if (layout == '1x2') { - this.ShowViewport(1, '0%', '0%', '100%', '50%'); - this.ShowViewport(2, '0%', '50%', '100%', '50%'); - this.HideViewport(3); - this.HideViewport(4); - } - - this.FitContent(); - }, - - UpdateSeriesThumbnail: function(seriesInstanceUid) { - if (seriesInstanceUid in this.seriesIndex) { - var index = this.seriesIndex[seriesInstanceUid]; - var series = this.series[index]; - - var type = stone.LoadSeriesThumbnail(seriesInstanceUid); - series.type = type; - - if (type == stone.ThumbnailType.IMAGE) { - series.thumbnail = stone.GetStringBuffer(); - } - - // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating - this.$set(this.series, index, series); - } - }, - - UpdateIsSeriesComplete: function(seriesInstanceUid) { - if (seriesInstanceUid in this.seriesIndex) { - var index = this.seriesIndex[seriesInstanceUid]; - var series = this.series[index]; - - series.complete = stone.IsSeriesComplete(seriesInstanceUid); - - // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating - this.$set(this.series, index, series); - - if ('tags' in this.viewport1Series && - this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { - this.$set(this.viewport1Series, series); - } - - if ('tags' in this.viewport2Series && - this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { - this.$set(this.viewport2Series, series); - } - - if ('tags' in this.viewport3Series && - this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { - this.$set(this.viewport3Series, series); - } - - if ('tags' in this.viewport4Series && - this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { - this.$set(this.viewport4Series, series); - } - } - }, - - SetWindowing(center, width) { - var canvas = this.GetActiveCanvas(); - if (canvas != '') { - stone.SetWindowing(canvas, center, width); - } - }, - - SetDefaultWindowing() { - var canvas = this.GetActiveCanvas(); - if (canvas != '') { - stone.SetDefaultWindowing(canvas); - } - }, - - InvertContrast() { - var canvas = this.GetActiveCanvas(); - if (canvas != '') { - stone.InvertContrast(canvas); - } - } - }, - - mounted: function() { - this.SetViewportLayout('1x1'); - } -}); - - - -window.addEventListener('StoneInitialized', function() { - stone.Setup(Module); - stone.SetOrthancRoot('..', true); - console.warn('Native Stone properly intialized'); - - var study = getParameterFromUrl('study'); - var series = getParameterFromUrl('series'); - - if (study === undefined) { - alert('No study was provided in the URL!'); - } else { - if (series === undefined) { - console.warn('Loading study: ' + study); - stone.FetchStudy(study); - } else { - console.warn('Loading series: ' + series + ' from study ' + study); - stone.FetchSeries(study, series); - app.leftMode = 'full'; - } - } -}); - - -window.addEventListener('ResourcesLoaded', function() { - console.log('resources loaded'); - - var studies = []; - for (var i = 0; i < stone.GetStudiesCount(); i++) { - stone.LoadStudyTags(i); - studies.push(JSON.parse(stone.GetStringBuffer())); - } - - var series = []; - for (var i = 0; i < stone.GetSeriesCount(); i++) { - stone.LoadSeriesTags(i); - series.push(JSON.parse(stone.GetStringBuffer())); - } - - app.SetResources(studies, series); - - for (var i = 0; i < app.series.length; i++) { - var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID]; - app.UpdateSeriesThumbnail(seriesInstanceUid); - app.UpdateIsSeriesComplete(seriesInstanceUid); - } -}); - - -window.addEventListener('ThumbnailLoaded', function(args) { - //var studyInstanceUid = args.detail.studyInstanceUid; - var seriesInstanceUid = args.detail.seriesInstanceUid; - app.UpdateSeriesThumbnail(seriesInstanceUid); -}); - - -window.addEventListener('MetadataLoaded', function(args) { - //var studyInstanceUid = args.detail.studyInstanceUid; - var seriesInstanceUid = args.detail.seriesInstanceUid; - app.UpdateIsSeriesComplete(seriesInstanceUid); -}); - - -window.addEventListener('FrameUpdated', function(args) { - var canvasId = args.detail.canvasId; - var framesCount = args.detail.framesCount; - var currentFrame = (args.detail.currentFrame + 1); - var quality = args.detail.quality; - - if (canvasId == 'canvas1') { - app.viewport1CurrentFrame = currentFrame; - app.viewport1FramesCount = framesCount; - app.viewport1Quality = quality; - } - else if (canvasId == 'canvas2') { - app.viewport2CurrentFrame = currentFrame; - app.viewport2FramesCount = framesCount; - app.viewport2Quality = quality; - } - else if (canvasId == 'canvas3') { - app.viewport3CurrentFrame = currentFrame; - app.viewport3FramesCount = framesCount; - app.viewport3Quality = quality; - } - else if (canvasId == 'canvas4') { - app.viewport4CurrentFrame = currentFrame; - app.viewport4FramesCount = framesCount; - app.viewport4Quality = quality; - } -}); - - -window.addEventListener('StoneException', function() { - console.error('Exception catched in Stone'); -}); - - - - - - -$(document).ready(function() { - // Enable support for tooltips in Bootstrap - //$('[data-toggle="tooltip"]').tooltip(); - - //app.showWarning = true; - - - $('#windowing-popover').popover({ - container: 'body', - content: $('#windowing-content').html(), - template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>', - placement: 'auto', - html: true, - sanitize: false, - trigger: 'focus' // Close on click - }); - - - var wasmSource = 'StoneWebViewer.js'; - - // Option 1: Loading script using plain HTML - - /* - var script = document.createElement('script'); - script.src = wasmSource; - script.type = 'text/javascript'; - document.body.appendChild(script); - */ - - // Option 2: Loading script using AJAX (gives the opportunity to - // report explicit errors) - - axios.get(wasmSource) - .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'); - }); -}); - - -// "Prevent Bootstrap dropdown from closing on clicks" for the list of -// studies: https://stackoverflow.com/questions/26639346 -$('.dropdown-menu').click(function(e) { - e.stopPropagation(); -}); - - -// Disable the selection of text using the mouse -document.onselectstart = new Function ('return false');
--- a/StoneWebViewer/WebApplication/index.html Tue Aug 11 12:47:59 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,488 +0,0 @@ -<!doctype html> -<html class="wv-html"> - <head> - <title>Stone Web Viewer</title> - <meta charset="utf-8" /> - <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> - <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - <meta name="apple-mobile-web-app-capable" content="yes" /> - <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> - <link rel="icon" href="data:;base64,iVBORw0KGgo="> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css"> - <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> - <link rel="stylesheet" href="app.css"> - - <!-- https://stackoverflow.com/a/16863182/881731 --> - <style> - .tooltip { - position: fixed; - } - </style> - - <!-- Fix if Bootstrap CSS is not used --> - <!--style> - *, - *::before, - *::after { - box-sizing: border-box; - } - </style--> - </head> - <body class="wv-body"> - <div id="wv"> - <div class="wvLoadingScreen" v-show="!ready"> - <span class="wvLoadingSpinner"> - <div class="bounce1"></div> - <div class="bounce2"></div> - <div class="bounce3"></div> - </span> - </div> - - <div class="fluid-height fluid-width" v-show="ready"> - - <div class="wvWarning wvPrintExclude" v-show="showWarning"> - <div class="wvWarning-content clearfix"> - <span class="wvWarning-text"> - <h2 class="mb10"><i class="fa fa-exclamation-triangle wvWarning-icon mr5"></i>Warning!</h2> - <p class="mn mb10" style="color:#000"> - You browser is not supported. You might expect - inconsistent behaviours and must not use the viewer to - produce a diagnostic. - </p> - </span> - </div> - <div class="text-right mb10 mr10"> - <button class="btn btn-primary" @click="showWarning=false">OK</button> - </div> - </div> - - - <div class="wvLayoutLeft wvLayoutLeft--closed" v-show="!leftVisible"> - <div class="wvLayoutLeft__actions--outside" style="z-index:10"> - <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center" - @click="leftVisible = true"> - <i class="fa fa-angle-double-right"></i> - </button> - </div> - </div> - - - <div class="wvLayoutLeft" v-show="leftVisible" - v-bind:class="{ 'wvLayoutLeft--small': leftMode == 'small' }" - > - <div class="wvLayoutLeft__actions" style="z-index:10"> - <button class="wvLayoutLeft__action button__base wh__25 lh__25 text-center" - @click="leftVisible = false"> - <i class="fa fa-angle-double-left"></i> - </button> - </div> - <div class="wvLayoutLeft__content"> - <div class="wvLayoutLeft__contentTop"> - <div class="float__left dropdown" style="max-width: calc(100% - 4.5rem); height:4.5rem !important" v-show="leftMode != 'small'"> - <button type="button" class="wvButton--border" data-toggle="dropdown"> - {{ getSelectedStudies }} - <span class="caret"></span> - </button> - <ul class="dropdown-menu checkbox-menu allow-focus"> - <li v-for="study in studies" - v-bind:class="{ active: study.selected }" - @click="study.selected = !study.selected"> - <a> - {{ study.tags['0008,1030'] }} - <span v-if="study.selected"> <i class="fa fa-check"></i></span> - </a> - </li> - </ul> - </div> - - <div class="float__right wvButton" v-if="leftMode == 'grid'" @click="leftMode = 'full'"> - <i class="fa fa-th-list"></i> - </div> - <div class="float__right wvButton" v-if="leftMode == 'full'" @click="leftMode = 'small'"> - <i class="fa fa-ellipsis-v"></i> - </div> - <div class="float__right wvButton" v-if="leftMode == 'small'" @click="leftMode = 'grid'"> - <i class="fa fa-th"></i> - </div> - - <p class="clear disclaimer mbn">For patients, teachers and researchers.</p> - </div> - <div class="wvLayoutLeft__contentMiddle"> - - <div v-for="study in studies"> - <div v-if="study.selected"> - <div v-bind:class="'wvStudyIsland--' + study.color"> - <div v-bind:class="'wvStudyIsland__header--' + study.color"> - <!-- Actions --> - <div class="wvStudyIsland__actions" - v-bind:class="{ 'wvStudyIsland__actions--oneCol': leftMode == 'small' }"> - <a class="wvButton"> - <!-- download --> - <i class="fa fa-download"></i> - </a> - </div> - - <!-- Title --> - {{ study.tags['0008,1030'] }} - <br/> - <small>{{ study.tags['0008,0020'] }}</small> - </div> - - <div class="wvStudyIsland__main"> - <ul class="wvSerieslist"> - <li class="wvSerieslist__seriesItem" - v-bind:class="{ highlighted : GetActiveSeries().includes(series[seriesIndex].tags['0020,000e']), 'wvSerieslist__seriesItem--list' : leftMode != 'grid', 'wvSerieslist__seriesItem--grid' : leftMode == 'grid' }" - v-on:dragstart="SeriesDragStart($event, seriesIndex)" - v-on:click="ClickSeries(seriesIndex)" - v-for="seriesIndex in study.series"> - <div class="wvSerieslist__picture" style="z-index:0" - draggable="true" - v-if="series[seriesIndex].type != stone.ThumbnailType.UNKNOWN" - > - <div v-if="series[seriesIndex].type == stone.ThumbnailType.LOADING"> - <img src="img/loading.gif" - style="vertical-align:baseline" - width="65px" height="65px" - /> - </div> - - <i v-if="series[seriesIndex].type == stone.ThumbnailType.PDF" - class="wvSerieslist__placeholderIcon fa fa-file-text"></i> - - <i v-if="series[seriesIndex].type == stone.ThumbnailType.VIDEO" - class="wvSerieslist__placeholderIcon fa fa-video-camera"></i> - - - <div v-if="[stone.ThumbnailType.IMAGE, stone.ThumbnailType.NO_PREVIEW].includes(series[seriesIndex].type)" - class="wvSerieslist__placeholderIcon" - v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']"> - <i v-if="series[seriesIndex].type == stone.ThumbnailType.NO_PREVIEW" - class="fa fa-eye-slash"></i> - - <img v-if="series[seriesIndex].type == stone.ThumbnailType.IMAGE" - v-bind:src="series[seriesIndex].thumbnail" - style="vertical-align:baseline" - width="65px" height="65px" - v-bind:title="leftMode == 'full' ? null : '[' + series[seriesIndex].tags['0008,0060'] + '] ' + series[seriesIndex].tags['0008,103e']" - /> - - <div v-bind:class="'wvSerieslist__badge--' + study.color" - v-if="'length' in series[seriesIndex]">{{ series[seriesIndex].length }}</div> - </div> - </div> - - <div v-if="leftMode == 'full'" class="wvSerieslist__information" - draggable="true" - v-on:dragstart="SeriesDragStart($event, seriesIndex)" - v-on:click="ClickSeries(seriesIndex)"> - <p class="wvSerieslist__label"> - [{{ series[seriesIndex].tags['0008,0060'] }}] - {{ series[seriesIndex].tags['0008,103e'] }} - </p> - </div> - </li> - </ul> - </div> - </div> - </div> - </div> - - </div> - <div class="wvLayoutLeft__contentBottom"> - </div> - </div> - </div> - <div class="wvLayout__main" - v-bind:class="{ 'wvLayout__main--smallleftpadding': leftVisible && leftMode == 'small', 'wvLayout__main--leftpadding': leftVisible && leftMode != 'small' }" - > - - <div class="wvToolbar wvToolbar--top"> - <div class="ng-scope inline-object"> - <div class="tbGroup"> - <div class="tbGroup__toggl"> - <button class="wvButton" - v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible }" - @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible"> - <i class="fa fa-th"></i> - </button> - </div> - - <div class="tbGroup__buttons--bottom" v-show="viewportLayoutButtonsVisible"> - <div class="inline-object"> - <button class="wvButton" @click="SetViewportLayout('1x1')"> - <img src="img/grid1x1.png" style="width:1em;height:1em" /> - </button> - </div> - <div class="inline-object"> - <button class="wvButton" @click="SetViewportLayout('2x1')"> - <img src="img/grid2x1.png" style="width:1em;height:1em" /> - </button> - </div> - <div class="inline-object"> - <button class="wvButton" @click="SetViewportLayout('1x2')"> - <img src="img/grid1x2.png" style="width:1em;height:1em" /> - </button> - </div> - <div class="inline-object"> - <button class="wvButton" @click="SetViewportLayout('2x2')"> - <img src="img/grid2x2.png" style="width:1em;height:1em" /> - </button> - </div> - </div> - </div> - </div> - - <!--div class="ng-scope inline-object"> - <button class="wvButton--underline text-center active"> - <i class="fa fa-hand-pointer-o"></i> - </button> - </div> - - <div class="ng-scope inline-object"> - <button class="wvButton--underline text-center"> - <i class="fa fa-search"></i> - </button> - </div> - - <div class="ng-scope inline-object"> - <button class="wvButton--underline text-center"> - <i class="fa fa-arrows"></i> - </button> - </div--> - - <div class="ng-scope inline-object"> - <button class="wvButton--underline text-center" - v-on:click="InvertContrast()"> - <i class="fa fa-adjust"></i> - </button> - </div> - - <div class="ng-scope inline-object"> - <button class="wvButton--underline text-center" id="windowing-popover"> - <i class="fa fa-sun-o"></i> - </button> - </div> - - <div class="ng-scope inline-object"> - <button class="wvButton--underline text-center" - v-bind:class="{ 'active' : showInfo }" - v-on:click="showInfo = !showInfo"> - <i class="fa fa-info-circle"></i> - </button> - </div> - - <div class="ng-scope inline-object"> - <button class="wvButton--underline text-center" - v-bind:class="{ 'active' : showReferenceLines }" - v-on:click="showReferenceLines = !showReferenceLines"> - <i class="fa fa-bars"></i> - </button> - </div> - </div> - - - <div class="wvLayout__splitpane--toolbarAtTop"> - <div id="viewport" class="wvSplitpane"> - <viewport v-on:updated-series="SetViewportSeries(1, $event)" - v-on:selected-viewport="activeViewport=1" - v-show="viewport1Visible" - canvas-id="canvas1" - v-bind:series="viewport1Series" - v-bind:left="viewport1Left" - v-bind:top="viewport1Top" - v-bind:width="viewport1Width" - v-bind:height="viewport1Height" - v-bind:quality="viewport1Quality" - v-bind:current-frame="viewport1CurrentFrame" - v-bind:frames-count="viewport1FramesCount" - v-bind:show-info="showInfo" - v-bind:active="activeViewport==1"></viewport> - <viewport v-on:updated-series="SetViewportSeries(2, $event)" - v-on:selected-viewport="activeViewport=2" - v-show="viewport2Visible" - canvas-id="canvas2" - v-bind:series="viewport2Series" - v-bind:left="viewport2Left" - v-bind:top="viewport2Top" - v-bind:width="viewport2Width" - v-bind:height="viewport2Height" - v-bind:quality="viewport2Quality" - v-bind:current-frame="viewport2CurrentFrame" - v-bind:frames-count="viewport2FramesCount" - v-bind:show-info="showInfo" - v-bind:active="activeViewport==2"></viewport> - <viewport v-on:updated-series="SetViewportSeries(3, $event)" - v-on:selected-viewport="activeViewport=3" - v-show="viewport3Visible" - canvas-id="canvas3" - v-bind:series="viewport3Series" - v-bind:left="viewport3Left" - v-bind:top="viewport3Top" - v-bind:width="viewport3Width" - v-bind:height="viewport3Height" - v-bind:quality="viewport3Quality" - v-bind:current-frame="viewport3CurrentFrame" - v-bind:frames-count="viewport3FramesCount" - v-bind:show-info="showInfo" - v-bind:active="activeViewport==3"></viewport> - <viewport v-on:updated-series="SetViewportSeries(4, $event)" - v-on:selected-viewport="activeViewport=4" - v-show="viewport4Visible" - canvas-id="canvas4" - v-bind:series="viewport4Series" - v-bind:left="viewport4Left" - v-bind:top="viewport4Top" - v-bind:width="viewport4Width" - v-bind:height="viewport4Height" - v-bind:quality="viewport4Quality" - v-bind:current-frame="viewport4CurrentFrame" - v-bind:frames-count="viewport4FramesCount" - v-bind:show-info="showInfo" - v-bind:active="activeViewport==4"></viewport> - </div> - </div> - - </div> - </div> - </div> - - - - <script type="text/x-template" id="windowing-content"> - <p class="wvToolbar__windowingPresetConfigNotice"> - Click on the button to toggle the windowing tool or apply a preset to the selected viewport. - </p> - - <ul class="wvToolbar__windowingPresetList"> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetDefaultWindowing()"> - Default - </a> - </li> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetWindowing(-400, 1600)"> - CT Lung <small>(L -400, W 1,600)</small> - </a> - </li> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetWindowing(300, 1500)"> - CT Abdomen <small>(L 300, W 1,500)</small> - </a> - </li> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetWindowing(40, 80)"> - CT Bone <small>(L 40, W 80)</small> - </a> - </li> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetWindowing(40, 400)"> - CT Brain <small>(L 40, W 400)</small> - </a> - </li> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetWindowing(-400, 1600)"> - CT Chest <small>(L -400, W 1,600)</small> - </a> - </li> - <li class="wvToolbar__windowingPresetListItem"> - <a href="#" onclick="app.SetWindowing(300, 600)"> - CT Angio <small>(L 300, W 600)</small> - </a> - </li> - </ul> - </script> - - - <script type="text/x-template" id="viewport-template"> - <div v-bind:style="{ padding:'2px', - position:'absolute', - left: left, - top: top, - width: width, - height: height }"> - <div v-bind:class="{ 'wvSplitpane__cellBorder--selected' : active, - 'wvSplitpane__cellBorder' : series.color == '', - 'wvSplitpane__cellBorder--blue' : series.color == 'blue', - 'wvSplitpane__cellBorder--red' : series.color == 'red', - 'wvSplitpane__cellBorder--green' : series.color == 'green', - 'wvSplitpane__cellBorder--yellow' : series.color == 'yellow', - 'wvSplitpane__cellBorder--violet' : series.color == 'violet' - }" - v-on:dragover="SeriesDragAccept($event)" - v-on:drop="SeriesDragDrop($event)" - style="width:100%;height:100%"> - <div class="wvSplitpane__cell" - v-on:click="MakeActive()"> - <div v-show="status == 'ready'" - style="position:absolute; left:0; top:0; width:100%; height:100%"> - <!--div style="width: 100%; height: 100%; background-color: red"></div--> - <canvas v-bind:id="canvasId" - style="position:absolute; left:0; top:0; width:100%; height:100%" - oncontextmenu="return false"></canvas> - - <div v-if="'tags' in series" v-show="showInfo"> - <div class="wv-overlay"> - <div class="wv-overlay-topleft"> - {{ series.tags['0010,0010'] }}<br/> - {{ series.tags['0010,0020'] }} - </div> - <div class="wv-overlay-topright"> - {{ series.tags['0008,1030'] }}<br/> - {{ series.tags['0008,0020'] }}<br/> - {{ series.tags['0020,0011'] }} | {{ series.tags['0008,103e'] }} - </div> - <div class="wv-overlay-bottomleft" - v-show="framesCount != 0"> - <button class="btn btn-primary" @click="DecrementFrame()"> - <i class="fa fa-chevron-circle-left"></i> - </button> - {{ currentFrame }} / {{ framesCount }} - <button class="btn btn-primary" @click="IncrementFrame()"> - <i class="fa fa-chevron-circle-right"></i> - </button> - </div> - <div class="wv-overlay-bottomright"> - <div v-show="quality == stone.DisplayedFrameQuality.NONE" - style="display:block;background-color:red;width:1em;height:1em" /> - <div v-show="quality == stone.DisplayedFrameQuality.LOW" - style="display:block;background-color:orange;width:1em;height:1em" /> - <div v-show="quality == stone.DisplayedFrameQuality.HIGH" - style="display:block;background-color:green;width:1em;height:1em" /> - </div> - </div> - </div> - </div> - - <div v-if="status == 'waiting'" class="wvPaneOverlay"> - [ drop a series here ] - </div> - - <!--div v-if="status == 'video'" class="wvPaneOverlay"> - <video class="wvVideo" autoplay="" loop="" controls="" preload="auto" type="video/mp4" - src="http://viewer-pro.osimis.io/instances/e465dd27-83c96343-96848735-7035a133-1facf1a0/frames/0/raw"> - </video> - </div--> - - <div v-if="status == 'loading'" class="wvPaneOverlay"> - <span class="wvLoadingSpinner"> - <div class="bounce1"></div> - <div class="bounce2"></div> - <div class="bounce3"></div> - </span> - </div> - </div> - </div> - </div> - </script> - - - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.js"></script> - - <script src="stone.js"></script> - <script src="app.js"></script> - </body> -</html>
--- 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 - )
--- 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
--- 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 <http://www.gnu.org/licenses/>. - - -# 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 -}))
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include <emscripten.h> - - -#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 <Cache/MemoryObjectCache.h> -#include <DicomFormat/DicomArray.h> -#include <DicomParsing/Internals/DicomImageDecoder.h> -#include <Images/Image.h> -#include <Images/ImageProcessing.h> -#include <Images/JpegReader.h> -#include <Logging.h> - -#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 <boost/make_shared.hpp> -#include <stdio.h> - - -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<ResourcesLoader> -{ -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<IObserver> observer_; - OrthancStone::DicomSource source_; - size_t pending_; - boost::shared_ptr<OrthancStone::LoadedDicomResources> studies_; - boost::shared_ptr<OrthancStone::LoadedDicomResources> series_; - boost::shared_ptr<OrthancStone::DicomResourcesLoader> resourcesLoader_; - boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader> thumbnailsLoader_; - boost::shared_ptr<OrthancStone::SeriesMetadataLoader> 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<Orthanc::ResourceType>& payload = - dynamic_cast<const Orthanc::SingleValueObject<Orthanc::ResourceType>&>(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<Orthanc::DicomTag> 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>(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>(Orthanc::ResourceType_Series)); - - pending_ += 2; - } - -public: - static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::ILoadersContext::ILock& lock, - const OrthancStone::DicomSource& source) - { - boost::shared_ptr<ResourcesLoader> 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<OrthancStone::DicomResourcesLoader::SuccessMessage>( - *loader->resourcesLoader_, &ResourcesLoader::Handle); - - loader->Register<OrthancStone::SeriesThumbnailsLoader::SuccessMessage>( - *loader->thumbnailsLoader_, &ResourcesLoader::Handle); - - loader->Register<OrthancStone::SeriesMetadataLoader::SuccessMessage>( - *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<Orthanc::ImageAccessor> 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<std::string>(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<Orthanc::ImageAccessor> 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<const CachedImage&>(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<CachedImage&>(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<size_t> 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<int, Action> > Queue; - - Queue queue; - std::set<int> 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<size_t>(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<size_t>(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<double>(columns), - oy + pixelSpacingY_ * static_cast<double>(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<ViewerViewport> -{ -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<ViewerViewport> viewport_; - - public: - ICommand(boost::shared_ptr<ViewerViewport> 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<ViewerViewport> 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<ViewerViewport> 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<Orthanc::JpegReader> 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<Orthanc::Image> 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<ViewerViewport> 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<Orthanc::ImageAccessor> 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<Orthanc::ImageAccessor> 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<IObserver> observer_; - OrthancStone::ILoadersContext& context_; - boost::shared_ptr<OrthancStone::WebGLViewport> viewport_; - boost::shared_ptr<OrthancStone::DicomResourcesLoader> loader_; - OrthancStone::DicomSource source_; - boost::shared_ptr<FramesCache> cache_; - std::unique_ptr<OrthancStone::SortedFrames> frames_; - std::unique_ptr<SeriesCursor> cursor_; - float windowingCenter_; - float windowingWidth_; - float defaultWindowingCenter_; - float defaultWindowingWidth_; - bool inverted_; - bool fitNextContent_; - bool isCtrlDown_; - FrameGeometry currentFrameGeometry_; - std::list<PrefetchItem> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::FloatTextureSceneLayer&>(scene.GetLayer(LAYER_TEXTURE)); - layer.GetWindowing(windowingCenter_, windowingWidth_); - } - } - - quality = accessor.GetQuality(); - - std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer; - - switch (accessor.GetImage().GetFormat()) - { - case Orthanc::PixelFormat_RGB24: - layer.reset(new OrthancStone::ColorTextureSceneLayer(accessor.GetImage())); - break; - - case Orthanc::PixelFormat_Float32: - { - std::unique_ptr<OrthancStone::FloatTextureSceneLayer> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::ILoadersContext::ILock> 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<std::string>(frame + 1) + "/rendered"); - - std::map<std::string, std::string> headers, arguments; - arguments["window"] = ( - boost::lexical_cast<std::string>(windowingCenter_) + "," + - boost::lexical_cast<std::string>(windowingWidth_) + ",linear"); - - std::unique_ptr<OrthancStone::IOracleCommand> command( - source_.CreateDicomWebCommand( - uri, arguments, headers, new SetLowQualityFrame( - GetSharedObserver(), sopInstanceUid, frame, - windowingCenter_, windowingWidth_, isMonochrome1, isPrefetch))); - - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock()); - lock->Schedule(GetSharedObserver(), priority, command.release()); - } - } - } - - ViewerViewport(OrthancStone::ILoadersContext& context, - const OrthancStone::DicomSource& source, - const std::string& canvas, - boost::shared_ptr<FramesCache> 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<ViewerViewport*>(userData); - that.isCtrlDown_ = event->ctrlKey; - return false; - } - - - static EM_BOOL OnWheel(int eventType, - const EmscriptenWheelEvent *wheelEvent, - void *userData) - { - ViewerViewport& that = *reinterpret_cast<ViewerViewport*>(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<const ICommand&>(message.GetUserPayload()).Handle(message); - } - - void Handle(const OrthancStone::HttpCommand::SuccessMessage& message) - { - dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message); - } - - void Handle(const OrthancStone::ParseDicomSuccessMessage& message) - { - dynamic_cast<const ICommand&>(message.GetOrigin().GetPayload()).Handle(message); - } - -public: - static boost::shared_ptr<ViewerViewport> Create(OrthancStone::ILoadersContext::ILock& lock, - const OrthancStone::DicomSource& source, - const std::string& canvas, - boost::shared_ptr<FramesCache> cache) - { - boost::shared_ptr<ViewerViewport> viewport( - new ViewerViewport(lock.GetContext(), source, canvas, cache)); - - viewport->loader_ = OrthancStone::DicomResourcesLoader::Create(lock); - viewport->Register<OrthancStone::DicomResourcesLoader::SuccessMessage>( - *viewport->loader_, &ViewerViewport::Handle); - - viewport->Register<OrthancStone::HttpCommand::SuccessMessage>( - lock.GetOracleObservable(), &ViewerViewport::Handle); - - viewport->Register<OrthancStone::ParseDicomSuccessMessage>( - 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<OrthancStone::LoadedDicomResources>(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<OrthancStone::IViewport::ILock> 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<const FrameGeometry*>& planes) - { - std::unique_ptr<OrthancStone::PolylineSceneLayer> layer(new OrthancStone::PolylineSceneLayer); - - if (GetCurrentFrameGeometry().IsValid()) - { - for (std::list<const FrameGeometry*>::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<OrthancStone::IViewport::ILock> 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<OrthancStone::IViewport::ILock> 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<OrthancStone::IViewport::ILock> lock(viewport_->Lock()); - - if (lock->GetController().GetScene().HasLayer(LAYER_TEXTURE) && - lock->GetController().GetScene().GetLayer(LAYER_TEXTURE).GetType() == - OrthancStone::ISceneLayer::Type_FloatTexture) - { - dynamic_cast<OrthancStone::FloatTextureSceneLayer&>( - lock->GetController().GetScene().GetLayer(LAYER_TEXTURE)). - SetCustomWindowing(windowingCenter_, windowingWidth_); - lock->Invalidate(); - } - } - } - - void Invert() - { - inverted_ = !inverted_; - - { - std::unique_ptr<OrthancStone::IViewport::ILock> 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<OrthancStone::FloatTextureSceneLayer&>( - 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<std::string, boost::shared_ptr<ViewerViewport> > Viewports; -static Viewports allViewports_; -static bool showReferenceLines_ = true; - - -static void UpdateReferenceLines() -{ - if (showReferenceLines_) - { - std::list<const FrameGeometry*> 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<int>(currentFrame), - static_cast<int>(countFrames), - quality); - - - UpdateReferenceLines(); - }; -}; - - - -static OrthancStone::DicomSource source_; -static boost::shared_ptr<FramesCache> cache_; -static boost::shared_ptr<OrthancStone::WebAssemblyLoadersContext> 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> resourcesLoader_; - - if (!resourcesLoader_) - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock()); - resourcesLoader_ = ResourcesLoader::Create(*lock, source_); - resourcesLoader_->AcquireObserver(new WebAssemblyObserver); - } - - return *resourcesLoader_; -} - - -static boost::shared_ptr<ViewerViewport> GetViewport(const std::string& canvas) -{ - Viewports::iterator found = allViewports_.find(canvas); - if (found == allViewports_.end()) - { - std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock()); - boost::shared_ptr<ViewerViewport> 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<OrthancStone::SortedFrames> 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; - } -}
--- 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/
--- 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