Mercurial > hg > orthanc-stone
changeset 746:d716bfb3e07c
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 22 May 2019 12:48:57 +0200 |
parents | c44c1d2d3598 |
children | ab236bb5dbc7 |
files | Framework/Oracle/GetOrthancImageCommand.cpp Framework/Oracle/GetOrthancImageCommand.h Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Framework/Oracle/GetOrthancWebViewerJpegCommand.h Framework/Oracle/OrthancRestApiCommand.cpp Framework/Oracle/OrthancRestApiCommand.h Framework/Toolbox/DicomInstanceParameters.cpp Framework/Toolbox/DicomInstanceParameters.h Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/Loader.cpp |
diffstat | 10 files changed, 1424 insertions(+), 1090 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancImageCommand.cpp Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,153 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "GetOrthancImageCommand.h" + +#include <Core/Images/JpegReader.h> +#include <Core/Images/PamReader.h> +#include <Core/Images/PngReader.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +namespace OrthancStone +{ + GetOrthancImageCommand::SuccessMessage::SuccessMessage(const GetOrthancImageCommand& command, + Orthanc::ImageAccessor* image, // Takes ownership + Orthanc::MimeType mime) : + OriginMessage(command), + image_(image), + mime_(mime) + { + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + GetOrthancImageCommand::GetOrthancImageCommand() : + uri_("/"), + timeout_(10), + hasExpectedFormat_(false) + { + } + + + void GetOrthancImageCommand::SetExpectedPixelFormat(Orthanc::PixelFormat format) + { + hasExpectedFormat_ = true; + expectedFormat_ = format; + } + + + void GetOrthancImageCommand::SetInstanceUri(const std::string& instance, + Orthanc::PixelFormat pixelFormat) + { + uri_ = "/instances/" + instance; + + switch (pixelFormat) + { + case Orthanc::PixelFormat_RGB24: + uri_ += "/preview"; + break; + + case Orthanc::PixelFormat_Grayscale16: + uri_ += "/image-uint16"; + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + uri_ += "/image-int16"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + void GetOrthancImageCommand::ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer, + const HttpHeaders& answerHeaders) const + { + Orthanc::MimeType contentType = Orthanc::MimeType_Binary; + + for (HttpHeaders::const_iterator it = answerHeaders.begin(); + it != answerHeaders.end(); ++it) + { + std::string s; + Orthanc::Toolbox::ToLowerCase(s, it->first); + + if (s == "content-type") + { + contentType = Orthanc::StringToMimeType(it->second); + break; + } + } + + std::auto_ptr<Orthanc::ImageAccessor> image; + + switch (contentType) + { + case Orthanc::MimeType_Png: + { + image.reset(new Orthanc::PngReader); + dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer); + break; + } + + case Orthanc::MimeType_Pam: + { + image.reset(new Orthanc::PamReader); + dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer); + break; + } + + case Orthanc::MimeType_Jpeg: + { + image.reset(new Orthanc::JpegReader); + dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Unsupported HTTP Content-Type for an image: " + + std::string(Orthanc::EnumerationToString(contentType))); + } + + if (hasExpectedFormat_) + { + if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 && + image->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + + if (expectedFormat_ != image->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + } + + SuccessMessage message(*this, image.release(), contentType); + emitter.EmitMessage(receiver, message); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancImageCommand.h Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,119 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "../Messages/IMessageEmitter.h" +#include "OracleCommandWithPayload.h" + +#include <Core/Images/ImageAccessor.h> + +#include <map> + +namespace OrthancStone +{ + class GetOrthancImageCommand : public OracleCommandWithPayload + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class SuccessMessage : public OriginMessage<GetOrthancImageCommand> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + std::auto_ptr<Orthanc::ImageAccessor> image_; + Orthanc::MimeType mime_; + + public: + SuccessMessage(const GetOrthancImageCommand& command, + Orthanc::ImageAccessor* image, // Takes ownership + Orthanc::MimeType mime); + + const Orthanc::ImageAccessor& GetImage() const + { + return *image_; + } + + Orthanc::MimeType GetMimeType() const + { + return mime_; + } + }; + + + private: + std::string uri_; + HttpHeaders headers_; + unsigned int timeout_; + bool hasExpectedFormat_; + Orthanc::PixelFormat expectedFormat_; + + public: + GetOrthancImageCommand(); + + virtual Type GetType() const + { + return Type_GetOrthancImage; + } + + void SetExpectedPixelFormat(Orthanc::PixelFormat format); + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetInstanceUri(const std::string& instance, + Orthanc::PixelFormat pixelFormat); + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + const std::string& GetUri() const + { + return uri_; + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + void ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer, + const HttpHeaders& answerHeaders) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,217 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "GetOrthancWebViewerJpegCommand.h" + +#include "../Toolbox/LinearAlgebra.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/JpegReader.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <json/reader.h> +#include <json/value.h> + +namespace OrthancStone +{ + GetOrthancWebViewerJpegCommand::SuccessMessage::SuccessMessage(const GetOrthancWebViewerJpegCommand& command, + Orthanc::ImageAccessor* image) : // Takes ownership + OriginMessage(command), + image_(image) + { + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + + GetOrthancWebViewerJpegCommand::GetOrthancWebViewerJpegCommand() : + frame_(0), + quality_(95), + timeout_(10), + expectedFormat_(Orthanc::PixelFormat_Grayscale8) + { + } + + + void GetOrthancWebViewerJpegCommand::SetQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + quality_ = quality; + } + } + + + std::string GetOrthancWebViewerJpegCommand::GetUri() const + { + return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) + + "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_)); + } + + + void GetOrthancWebViewerJpegCommand::ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer) const + { + // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" + + Json::Value encoded; + + { + Json::Reader reader; + if (!reader.parse(answer, encoded)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + if (encoded.type() != Json::objectValue || + !encoded.isMember("Orthanc") || + encoded["Orthanc"].type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + const Json::Value& info = encoded["Orthanc"]; + if (!info.isMember("PixelData") || + !info.isMember("Stretched") || + !info.isMember("Compression") || + info["Compression"].type() != Json::stringValue || + info["PixelData"].type() != Json::stringValue || + info["Stretched"].type() != Json::booleanValue || + info["Compression"].asString() != "Jpeg") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + bool isSigned = false; + bool isStretched = info["Stretched"].asBool(); + + if (info.isMember("IsSigned")) + { + if (info["IsSigned"].type() != Json::booleanValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + isSigned = info["IsSigned"].asBool(); + } + } + + std::auto_ptr<Orthanc::ImageAccessor> reader; + + { + std::string jpeg; + Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); + + reader.reset(new Orthanc::JpegReader); + dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); + } + + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image + { + if (expectedFormat_ != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (isSigned || isStretched) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SuccessMessage message(*this, reader.release()); + emitter.EmitMessage(receiver, message); + return; + } + } + + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!isStretched) + { + if (expectedFormat_ != reader->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SuccessMessage message(*this, reader.release()); + emitter.EmitMessage(receiver, message); + return; + } + } + + int32_t stretchLow = 0; + int32_t stretchHigh = 0; + + if (!info.isMember("StretchLow") || + !info.isMember("StretchHigh") || + info["StretchLow"].type() != Json::intValue || + info["StretchHigh"].type() != Json::intValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + stretchLow = info["StretchLow"].asInt(); + stretchHigh = info["StretchHigh"].asInt(); + + if (stretchLow < -32768 || + stretchHigh > 65535 || + (stretchLow < 0 && stretchHigh > 32767)) + { + // This range cannot be represented with a uint16_t or an int16_t + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // Decode a grayscale JPEG 8bpp image coming from the Web viewer + std::auto_ptr<Orthanc::ImageAccessor> image + (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); + + Orthanc::ImageProcessing::Convert(*image, *reader); + reader.reset(); + + float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; + + if (!LinearAlgebra::IsCloseToZero(scaling)) + { + float offset = static_cast<float>(stretchLow) / scaling; + Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); + } + + SuccessMessage message(*this, image.release()); + emitter.EmitMessage(receiver, message); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/GetOrthancWebViewerJpegCommand.h Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,135 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "../Messages/IMessageEmitter.h" +#include "OracleCommandWithPayload.h" + +#include <Core/Images/ImageAccessor.h> + +#include <map> + +namespace OrthancStone +{ + class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + std::auto_ptr<Orthanc::ImageAccessor> image_; + + public: + SuccessMessage(const GetOrthancWebViewerJpegCommand& command, + Orthanc::ImageAccessor* image); // Takes ownership + + const Orthanc::ImageAccessor& GetImage() const + { + return *image_; + } + }; + + private: + std::string instanceId_; + unsigned int frame_; + unsigned int quality_; + HttpHeaders headers_; + unsigned int timeout_; + Orthanc::PixelFormat expectedFormat_; + + public: + GetOrthancWebViewerJpegCommand(); + + virtual Type GetType() const + { + return Type_GetOrthancWebViewerJpeg; + } + + void SetExpectedPixelFormat(Orthanc::PixelFormat format) + { + expectedFormat_ = format; + } + + void SetInstance(const std::string& instanceId) + { + instanceId_ = instanceId; + } + + void SetFrame(unsigned int frame) + { + frame_ = frame; + } + + void SetQuality(unsigned int quality); + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return expectedFormat_; + } + + const std::string& GetInstanceId() const + { + return instanceId_; + } + + unsigned int GetFrame() const + { + return frame_; + } + + unsigned int GetQuality() const + { + return quality_; + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + std::string GetUri() const; + + void ProcessHttpAnswer(IMessageEmitter& emitter, + const IObserver& receiver, + const std::string& answer) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OrthancRestApiCommand.cpp Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,78 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "OrthancRestApiCommand.h" + +#include <Core/OrthancException.h> + +#include <json/reader.h> +#include <json/writer.h> + +namespace OrthancStone +{ + OrthancRestApiCommand::SuccessMessage::SuccessMessage(const OrthancRestApiCommand& command, + const HttpHeaders& answerHeaders, + std::string& answer) : + OriginMessage(command), + headers_(answerHeaders), + answer_(answer) + { + } + + + void OrthancRestApiCommand::SuccessMessage::ParseJsonBody(Json::Value& target) const + { + Json::Reader reader; + if (!reader.parse(answer_, target)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + OrthancRestApiCommand::OrthancRestApiCommand() : + method_(Orthanc::HttpMethod_Get), + uri_("/"), + timeout_(10) + { + } + + + void OrthancRestApiCommand::SetBody(const Json::Value& json) + { + Json::FastWriter writer; + body_ = writer.write(json); + } + + + const std::string& OrthancRestApiCommand::GetBody() const + { + if (method_ == Orthanc::HttpMethod_Post || + method_ == Orthanc::HttpMethod_Put) + { + return body_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/OrthancRestApiCommand.h Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,131 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "../Messages/IMessage.h" +#include "OracleCommandWithPayload.h" + +#include <Core/Enumerations.h> + +#include <map> +#include <json/value.h> + +namespace OrthancStone +{ + class OrthancRestApiCommand : public OracleCommandWithPayload + { + public: + typedef std::map<std::string, std::string> HttpHeaders; + + class SuccessMessage : public OriginMessage<OrthancRestApiCommand> + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + HttpHeaders headers_; + std::string answer_; + + public: + SuccessMessage(const OrthancRestApiCommand& command, + const HttpHeaders& answerHeaders, + std::string& answer /* will be swapped to avoid a memcpy() */); + + const std::string& GetAnswer() const + { + return answer_; + } + + void ParseJsonBody(Json::Value& target) const; + + const HttpHeaders& GetAnswerHeaders() const + { + return headers_; + } + }; + + + private: + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + + public: + OrthancRestApiCommand(); + + virtual Type GetType() const + { + return Type_OrthancRestApi; + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetBody(const std::string& body) + { + body_ = body; + } + + void SetBody(const Json::Value& json); + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::HttpMethod GetMethod() const + { + return method_; + } + + const std::string& GetUri() const + { + return uri_; + } + + const std::string& GetBody() const; + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/DicomInstanceParameters.cpp Wed May 22 12:48:57 2019 +0200 @@ -0,0 +1,378 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU 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 "DicomInstanceParameters.h" + +#include "../Scene2D/ColorTextureSceneLayer.h" +#include "../Scene2D/FloatTextureSceneLayer.h" +#include "../Toolbox/GeometryToolbox.h" + +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Logging.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + + +namespace OrthancStone +{ + void DicomInstanceParameters::Data::ComputeDoseOffsets(const Orthanc::DicomMap& dicom) + { + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html + + { + std::string increment; + + if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) + { + Orthanc::Toolbox::ToUpperCase(increment); + if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag + { + LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; + return; + } + } + } + + if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || + frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) + { + LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; + frameOffsets_.clear(); + } + else + { + if (frameOffsets_.size() >= 2) + { + thickness_ = frameOffsets_[1] - frameOffsets_[0]; + + if (thickness_ < 0) + { + thickness_ = -thickness_; + } + } + } + } + + + DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) : + imageInformation_(dicom) + { + if (imageInformation_.GetNumberOfFrames() <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || + !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) || + !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + std::string s; + if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + sopClassUid_ = StringToSopClassUid(s); + } + + if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) + { + thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); + } + + GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); + + std::string position, orientation; + if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + geometry_ = CoordinateSystem3D(position, orientation); + } + + if (sopClassUid_ == SopClassUid_RTDose) + { + ComputeDoseOffsets(dicom); + } + + isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && + imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); + + double doseGridScaling; + + if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + { + hasRescale_ = true; + } + else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + hasRescale_ = true; + rescaleIntercept_ = 0; + rescaleSlope_ = doseGridScaling; + } + else + { + hasRescale_ = false; + } + + Vector c, w; + if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && + LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && + c.size() > 0 && + w.size() > 0) + { + hasDefaultWindowing_ = true; + defaultWindowingCenter_ = static_cast<float>(c[0]); + defaultWindowingWidth_ = static_cast<float>(w[0]); + } + else + { + hasDefaultWindowing_ = false; + } + + if (sopClassUid_ == SopClassUid_RTDose) + { + switch (imageInformation_.GetBitsStored()) + { + case 16: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + break; + + case 32: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + else if (isColor_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; + } + else if (imageInformation_.IsSigned()) + { + expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; + } + else + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } + } + + + CoordinateSystem3D DicomInstanceParameters::Data::GetFrameGeometry(unsigned int frame) const + { + if (frame == 0) + { + return geometry_; + } + else if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (sopClassUid_ == SopClassUid_RTDose) + { + if (frame >= frameOffsets_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return CoordinateSystem3D( + geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), + geometry_.GetAxisX(), + geometry_.GetAxisY()); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame, + const CoordinateSystem3D& plane) const + { + if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + CoordinateSystem3D tmp = geometry_; + + if (frame != 0) + { + tmp = GetFrameGeometry(frame); + } + + double distance; + + return (CoordinateSystem3D::GetDistance(distance, tmp, plane) && + distance <= thickness_ / 2.0); + } + + + void DicomInstanceParameters::Data::ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const + { + if (image.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (hasRescale_) + { + const unsigned int width = image.GetWidth(); + const unsigned int height = image.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + float* p = reinterpret_cast<float*>(image.GetRow(y)); + + if (useDouble) + { + // Slower, accurate implementation using double + for (unsigned int x = 0; x < width; x++, p++) + { + double value = static_cast<double>(*p); + *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); + } + } + else + { + // Fast, approximate implementation using float + for (unsigned int x = 0; x < width; x++, p++) + { + *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); + } + } + } + } + } + + double DicomInstanceParameters::GetRescaleIntercept() const + { + if (data_.hasRescale_) + { + return data_.rescaleIntercept_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + double DicomInstanceParameters::GetRescaleSlope() const + { + if (data_.hasRescale_) + { + return data_.rescaleSlope_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + float DicomInstanceParameters::GetDefaultWindowingCenter() const + { + if (data_.hasDefaultWindowing_) + { + return data_.defaultWindowingCenter_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + float DicomInstanceParameters::GetDefaultWindowingWidth() const + { + if (data_.hasDefaultWindowing_) + { + return data_.defaultWindowingWidth_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture(const Orthanc::ImageAccessor& pixelData) const + { + assert(sizeof(float) == 4); + + Orthanc::PixelFormat sourceFormat = pixelData.GetFormat(); + + if (sourceFormat != GetExpectedPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (sourceFormat == Orthanc::PixelFormat_RGB24) + { + // This is the case of a color image. No conversion has to be done. + return new ColorTextureSceneLayer(pixelData); + } + else + { + if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && + sourceFormat != Orthanc::PixelFormat_Grayscale32 && + sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + std::auto_ptr<FloatTextureSceneLayer> texture; + + { + // This is the case of a grayscale frame. Convert it to Float32. + std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, + pixelData.GetWidth(), + pixelData.GetHeight(), + false)); + Orthanc::ImageProcessing::Convert(*converted, pixelData); + + // Correct rescale slope/intercept if need be + data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); + + texture.reset(new FloatTextureSceneLayer(*converted)); + } + + if (data_.hasDefaultWindowing_) + { + texture->SetCustomWindowing(data_.defaultWindowingCenter_, + data_.defaultWindowingWidth_); + } + + return texture.release(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/DicomInstanceParameters.h Wed May 22 12:48:57 2019 +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-2019 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 "../StoneEnumerations.h" +#include "../Scene2D/TextureBaseSceneLayer.h" +#include "../Toolbox/CoordinateSystem3D.h" + +#include <Core/IDynamicObject.h> +#include <Core/DicomFormat/DicomImageInformation.h> + +namespace OrthancStone +{ + class DicomInstanceParameters : + public Orthanc::IDynamicObject /* to be used as a payload to SlicesSorter */ + { + // This class supersedes the deprecated "DicomFrameConverter" + + private: + struct Data // Struct to ease the copy constructor + { + std::string orthancInstanceId_; + std::string studyInstanceUid_; + std::string seriesInstanceUid_; + std::string sopInstanceUid_; + Orthanc::DicomImageInformation imageInformation_; + SopClassUid sopClassUid_; + double thickness_; + double pixelSpacingX_; + double pixelSpacingY_; + CoordinateSystem3D geometry_; + Vector frameOffsets_; + bool isColor_; + bool hasRescale_; + double rescaleIntercept_; + double rescaleSlope_; + bool hasDefaultWindowing_; + float defaultWindowingCenter_; + float defaultWindowingWidth_; + Orthanc::PixelFormat expectedPixelFormat_; + + void ComputeDoseOffsets(const Orthanc::DicomMap& dicom); + + Data(const Orthanc::DicomMap& dicom); + + CoordinateSystem3D GetFrameGeometry(unsigned int frame) const; + + bool IsPlaneWithinSlice(unsigned int frame, + const CoordinateSystem3D& plane) const; + + void ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const; + }; + + + Data data_; + + + public: + DicomInstanceParameters(const DicomInstanceParameters& other) : + data_(other.data_) + { + } + + DicomInstanceParameters(const Orthanc::DicomMap& dicom) : + data_(dicom) + { + } + + void SetOrthancInstanceIdentifier(const std::string& id) + { + data_.orthancInstanceId_ = id; + } + + const std::string& GetOrthancInstanceIdentifier() const + { + return data_.orthancInstanceId_; + } + + const Orthanc::DicomImageInformation& GetImageInformation() const + { + return data_.imageInformation_; + } + + const std::string& GetStudyInstanceUid() const + { + return data_.studyInstanceUid_; + } + + const std::string& GetSeriesInstanceUid() const + { + return data_.seriesInstanceUid_; + } + + const std::string& GetSopInstanceUid() const + { + return data_.sopInstanceUid_; + } + + SopClassUid GetSopClassUid() const + { + return data_.sopClassUid_; + } + + double GetThickness() const + { + return data_.thickness_; + } + + double GetPixelSpacingX() const + { + return data_.pixelSpacingX_; + } + + double GetPixelSpacingY() const + { + return data_.pixelSpacingY_; + } + + const CoordinateSystem3D& GetGeometry() const + { + return data_.geometry_; + } + + CoordinateSystem3D GetFrameGeometry(unsigned int frame) const + { + return data_.GetFrameGeometry(frame); + } + + bool IsPlaneWithinSlice(unsigned int frame, + const CoordinateSystem3D& plane) const + { + return data_.IsPlaneWithinSlice(frame, plane); + } + + bool IsColor() const + { + return data_.isColor_; + } + + bool HasRescale() const + { + return data_.hasRescale_; + } + + double GetRescaleIntercept() const; + + double GetRescaleSlope() const; + + bool HasDefaultWindowing() const + { + return data_.hasDefaultWindowing_; + } + + float GetDefaultWindowingCenter() const; + + float GetDefaultWindowingWidth() const; + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return data_.expectedPixelFormat_; + } + + TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& pixelData) const; + }; +}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake Wed May 22 12:08:15 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Wed May 22 12:48:57 2019 +0200 @@ -32,7 +32,6 @@ include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake) include_directories(${ORTHANC_ROOT}) -include_directories(${ORTHANC_ROOT}/Core/Images) # hack for the numerous #include "../Enumerations.h" in Orthanc to work ##################################################################### @@ -430,7 +429,10 @@ ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.cpp ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h + ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancImageCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/GetOrthancWebViewerJpegCommand.cpp ${ORTHANC_STONE_ROOT}/Framework/Oracle/OracleCommandWithPayload.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/OrthancRestApiCommand.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyAlphaLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyDicomLayer.cpp ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyLayer.cpp @@ -452,6 +454,7 @@ ${ORTHANC_STONE_ROOT}/Framework/StoneInitialization.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/AffineTransform2D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomInstanceParameters.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DynamicBitmap.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/Extent2D.cpp
--- a/Samples/Sdl/Loader.cpp Wed May 22 12:08:15 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Wed May 22 12:48:57 2019 +0200 @@ -19,6 +19,10 @@ **/ +#include "../../Framework/Toolbox/DicomInstanceParameters.h" +#include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../Framework/Oracle/GetOrthancImageCommand.h" +#include "../../Framework/Oracle/OrthancRestApiCommand.h" #include "../../Framework/Oracle/SleepOracleCommand.h" #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" #include "../../Framework/Messages/IMessageEmitter.h" @@ -28,12 +32,6 @@ // From Stone #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" #include "../../Framework/Loaders/BasicFetchingStrategy.h" -#include "../../Framework/Messages/ICallable.h" -#include "../../Framework/Messages/IMessage.h" -#include "../../Framework/Messages/IObservable.h" -#include "../../Framework/Messages/MessageBroker.h" -#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" -#include "../../Framework/Scene2D/FloatTextureSceneLayer.h" #include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/StoneInitialization.h" #include "../../Framework/Toolbox/GeometryToolbox.h" @@ -48,12 +46,7 @@ #include <Core/DicomFormat/DicomImageInformation.h> #include <Core/HttpClient.h> #include <Core/IDynamicObject.h> -#include <Core/Images/Image.h> #include <Core/Images/ImageProcessing.h> -#include <Core/Images/JpegReader.h> -#include <Core/Images/PamReader.h> -#include <Core/Images/PngReader.h> -#include <Core/Images/PngWriter.h> #include <Core/Logging.h> #include <Core/MultiThreading/SharedMessageQueue.h> #include <Core/OrthancException.h> @@ -71,1075 +64,15 @@ namespace OrthancStone { - typedef std::map<std::string, std::string> HttpHeaders; - - class OrthancRestApiCommand : public OracleCommandWithPayload - { - public: - class SuccessMessage : public OriginMessage<OrthancRestApiCommand> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - HttpHeaders headers_; - std::string answer_; - - public: - SuccessMessage(const OrthancRestApiCommand& command, - const HttpHeaders& answerHeaders, - std::string& answer /* will be swapped to avoid a memcpy() */) : - OriginMessage(command), - headers_(answerHeaders), - answer_(answer) - { - } - - const std::string& GetAnswer() const - { - return answer_; - } - - void ParseJsonBody(Json::Value& target) const - { - Json::Reader reader; - if (!reader.parse(answer_, target)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - const HttpHeaders& GetAnswerHeaders() const - { - return headers_; - } - }; - - - private: - Orthanc::HttpMethod method_; - std::string uri_; - std::string body_; - HttpHeaders headers_; - unsigned int timeout_; - - public: - OrthancRestApiCommand() : - method_(Orthanc::HttpMethod_Get), - uri_("/"), - timeout_(10) - { - } - - virtual Type GetType() const - { - return Type_OrthancRestApi; - } - - void SetMethod(Orthanc::HttpMethod method) - { - method_ = method; - } - - void SetUri(const std::string& uri) - { - uri_ = uri; - } - - void SetBody(const std::string& body) - { - body_ = body; - } - - void SetBody(const Json::Value& json) - { - Json::FastWriter writer; - body_ = writer.write(json); - } - - void SetHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - Orthanc::HttpMethod GetMethod() const - { - return method_; - } - - const std::string& GetUri() const - { - return uri_; - } - - const std::string& GetBody() const - { - if (method_ == Orthanc::HttpMethod_Post || - method_ == Orthanc::HttpMethod_Put) - { - return body_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int seconds) - { - timeout_ = seconds; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - }; - - - - - class GetOrthancImageCommand : public OracleCommandWithPayload - { - public: - class SuccessMessage : public OriginMessage<GetOrthancImageCommand> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - std::auto_ptr<Orthanc::ImageAccessor> image_; - Orthanc::MimeType mime_; - - public: - SuccessMessage(const GetOrthancImageCommand& command, - Orthanc::ImageAccessor* image, // Takes ownership - Orthanc::MimeType mime) : - OriginMessage(command), - image_(image), - mime_(mime) - { - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const Orthanc::ImageAccessor& GetImage() const - { - return *image_; - } - - Orthanc::MimeType GetMimeType() const - { - return mime_; - } - }; - - - private: - std::string uri_; - HttpHeaders headers_; - unsigned int timeout_; - bool hasExpectedFormat_; - Orthanc::PixelFormat expectedFormat_; - - public: - GetOrthancImageCommand() : - uri_("/"), - timeout_(10), - hasExpectedFormat_(false) - { - } - - virtual Type GetType() const - { - return Type_GetOrthancImage; - } - - void SetExpectedPixelFormat(Orthanc::PixelFormat format) - { - hasExpectedFormat_ = true; - expectedFormat_ = format; - } - - void SetUri(const std::string& uri) - { - uri_ = uri; - } - - void SetInstanceUri(const std::string& instance, - Orthanc::PixelFormat pixelFormat) - { - uri_ = "/instances/" + instance; - - switch (pixelFormat) - { - case Orthanc::PixelFormat_RGB24: - uri_ += "/preview"; - break; - - case Orthanc::PixelFormat_Grayscale16: - uri_ += "/image-uint16"; - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - uri_ += "/image-int16"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - void SetHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - const std::string& GetUri() const - { - return uri_; - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int seconds) - { - timeout_ = seconds; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - - void ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, - const std::string& answer, - const HttpHeaders& answerHeaders) const - { - Orthanc::MimeType contentType = Orthanc::MimeType_Binary; - - for (HttpHeaders::const_iterator it = answerHeaders.begin(); - it != answerHeaders.end(); ++it) - { - std::string s; - Orthanc::Toolbox::ToLowerCase(s, it->first); - - if (s == "content-type") - { - contentType = Orthanc::StringToMimeType(it->second); - break; - } - } - - std::auto_ptr<Orthanc::ImageAccessor> image; - - switch (contentType) - { - case Orthanc::MimeType_Png: - { - image.reset(new Orthanc::PngReader); - dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer); - break; - } - - case Orthanc::MimeType_Pam: - { - image.reset(new Orthanc::PamReader); - dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer); - break; - } - - case Orthanc::MimeType_Jpeg: - { - image.reset(new Orthanc::JpegReader); - dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, - "Unsupported HTTP Content-Type for an image: " + - std::string(Orthanc::EnumerationToString(contentType))); - } - - if (hasExpectedFormat_) - { - if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 && - image->GetFormat() == Orthanc::PixelFormat_Grayscale16) - { - image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - - if (expectedFormat_ != image->GetFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - } - - SuccessMessage message(*this, image.release(), contentType); - emitter.EmitMessage(receiver, message); - } - }; - - - - class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload - { - public: - class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand> - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - std::auto_ptr<Orthanc::ImageAccessor> image_; - - public: - SuccessMessage(const GetOrthancWebViewerJpegCommand& command, - Orthanc::ImageAccessor* image) : // Takes ownership - OriginMessage(command), - image_(image) - { - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const Orthanc::ImageAccessor& GetImage() const - { - return *image_; - } - }; - - private: - std::string instanceId_; - unsigned int frame_; - unsigned int quality_; - HttpHeaders headers_; - unsigned int timeout_; - Orthanc::PixelFormat expectedFormat_; - - public: - GetOrthancWebViewerJpegCommand() : - frame_(0), - quality_(95), - timeout_(10), - expectedFormat_(Orthanc::PixelFormat_Grayscale8) - { - } - - virtual Type GetType() const - { - return Type_GetOrthancWebViewerJpeg; - } - - void SetExpectedPixelFormat(Orthanc::PixelFormat format) - { - expectedFormat_ = format; - } - - void SetInstance(const std::string& instanceId) - { - instanceId_ = instanceId; - } - - void SetFrame(unsigned int frame) - { - frame_ = frame; - } - - void SetQuality(unsigned int quality) - { - if (quality <= 0 || - quality > 100) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - quality_ = quality; - } - } - - void SetHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - Orthanc::PixelFormat GetExpectedPixelFormat() const - { - return expectedFormat_; - } - - const std::string& GetInstanceId() const - { - return instanceId_; - } - - unsigned int GetFrame() const - { - return frame_; - } - - unsigned int GetQuality() const - { - return quality_; - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int seconds) - { - timeout_ = seconds; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - - std::string GetUri() const - { - return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) + - "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_)); - } - - void ProcessHttpAnswer(IMessageEmitter& emitter, - const IObserver& receiver, - const std::string& answer) const - { - // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" - - Json::Value encoded; - - { - Json::Reader reader; - if (!reader.parse(answer, encoded)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - if (encoded.type() != Json::objectValue || - !encoded.isMember("Orthanc") || - encoded["Orthanc"].type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - const Json::Value& info = encoded["Orthanc"]; - if (!info.isMember("PixelData") || - !info.isMember("Stretched") || - !info.isMember("Compression") || - info["Compression"].type() != Json::stringValue || - info["PixelData"].type() != Json::stringValue || - info["Stretched"].type() != Json::booleanValue || - info["Compression"].asString() != "Jpeg") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - bool isSigned = false; - bool isStretched = info["Stretched"].asBool(); - - if (info.isMember("IsSigned")) - { - if (info["IsSigned"].type() != Json::booleanValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - isSigned = info["IsSigned"].asBool(); - } - } - - std::auto_ptr<Orthanc::ImageAccessor> reader; - - { - std::string jpeg; - Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); - - reader.reset(new Orthanc::JpegReader); - dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); - } - - if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image - { - if (expectedFormat_ != Orthanc::PixelFormat_RGB24) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (isSigned || isStretched) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - SuccessMessage message(*this, reader.release()); - emitter.EmitMessage(receiver, message); - return; - } - } - - if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (!isStretched) - { - if (expectedFormat_ != reader->GetFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - SuccessMessage message(*this, reader.release()); - emitter.EmitMessage(receiver, message); - return; - } - } - - int32_t stretchLow = 0; - int32_t stretchHigh = 0; - - if (!info.isMember("StretchLow") || - !info.isMember("StretchHigh") || - info["StretchLow"].type() != Json::intValue || - info["StretchHigh"].type() != Json::intValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - stretchLow = info["StretchLow"].asInt(); - stretchHigh = info["StretchHigh"].asInt(); - - if (stretchLow < -32768 || - stretchHigh > 65535 || - (stretchLow < 0 && stretchHigh > 32767)) - { - // This range cannot be represented with a uint16_t or an int16_t - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - // Decode a grayscale JPEG 8bpp image coming from the Web viewer - std::auto_ptr<Orthanc::ImageAccessor> image - (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); - - Orthanc::ImageProcessing::Convert(*image, *reader); - reader.reset(); - - float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; - - if (!LinearAlgebra::IsCloseToZero(scaling)) - { - float offset = static_cast<float>(stretchLow) / scaling; - Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); - } - - SuccessMessage message(*this, image.release()); - emitter.EmitMessage(receiver, message); - } - }; - - - - - - class DicomInstanceParameters : - public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ - { - private: - struct Data // Struct to ease the copy constructor - { - std::string orthancInstanceId_; - std::string studyInstanceUid_; - std::string seriesInstanceUid_; - std::string sopInstanceUid_; - Orthanc::DicomImageInformation imageInformation_; - SopClassUid sopClassUid_; - double thickness_; - double pixelSpacingX_; - double pixelSpacingY_; - CoordinateSystem3D geometry_; - Vector frameOffsets_; - bool isColor_; - bool hasRescale_; - double rescaleIntercept_; - double rescaleSlope_; - bool hasDefaultWindowing_; - float defaultWindowingCenter_; - float defaultWindowingWidth_; - Orthanc::PixelFormat expectedPixelFormat_; - - void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) - { - // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html - - { - std::string increment; - - if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) - { - Orthanc::Toolbox::ToUpperCase(increment); - if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag - { - LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; - return; - } - } - } - - if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || - frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) - { - LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; - frameOffsets_.clear(); - } - else - { - if (frameOffsets_.size() >= 2) - { - thickness_ = frameOffsets_[1] - frameOffsets_[0]; - - if (thickness_ < 0) - { - thickness_ = -thickness_; - } - } - } - } - - Data(const Orthanc::DicomMap& dicom) : - imageInformation_(dicom) - { - if (imageInformation_.GetNumberOfFrames() <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || - !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) || - !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - std::string s; - if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - sopClassUid_ = StringToSopClassUid(s); - } - - if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) - { - thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); - } - - GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); - - std::string position, orientation; - if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && - dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) - { - geometry_ = CoordinateSystem3D(position, orientation); - } - - if (sopClassUid_ == SopClassUid_RTDose) - { - ComputeDoseOffsets(dicom); - } - - isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && - imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); - - double doseGridScaling; - - if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && - dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) - { - hasRescale_ = true; - } - else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) - { - hasRescale_ = true; - rescaleIntercept_ = 0; - rescaleSlope_ = doseGridScaling; - } - else - { - hasRescale_ = false; - } - - Vector c, w; - if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && - LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && - c.size() > 0 && - w.size() > 0) - { - hasDefaultWindowing_ = true; - defaultWindowingCenter_ = static_cast<float>(c[0]); - defaultWindowingWidth_ = static_cast<float>(w[0]); - } - else - { - hasDefaultWindowing_ = false; - } - - if (sopClassUid_ == SopClassUid_RTDose) - { - switch (imageInformation_.GetBitsStored()) - { - case 16: - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - break; - - case 32: - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - else if (isColor_) - { - expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; - } - else if (imageInformation_.IsSigned()) - { - expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; - } - else - { - expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; - } - } - - CoordinateSystem3D GetFrameGeometry(unsigned int frame) const - { - if (frame == 0) - { - return geometry_; - } - else if (frame >= imageInformation_.GetNumberOfFrames()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else if (sopClassUid_ == SopClassUid_RTDose) - { - if (frame >= frameOffsets_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - return CoordinateSystem3D( - geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), - geometry_.GetAxisX(), - geometry_.GetAxisY()); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - // TODO - Is this necessary? - bool FrameContainsPlane(unsigned int frame, - const CoordinateSystem3D& plane) const - { - if (frame >= imageInformation_.GetNumberOfFrames()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - CoordinateSystem3D tmp = geometry_; - - if (frame != 0) - { - tmp = GetFrameGeometry(frame); - } - - double distance; - - return (CoordinateSystem3D::GetDistance(distance, tmp, plane) && - distance <= thickness_ / 2.0); - } - - - void ApplyRescale(Orthanc::ImageAccessor& image, - bool useDouble) const - { - if (image.GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - if (hasRescale_) - { - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - float* p = reinterpret_cast<float*>(image.GetRow(y)); - - if (useDouble) - { - // Slower, accurate implementation using double - for (unsigned int x = 0; x < width; x++, p++) - { - double value = static_cast<double>(*p); - *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); - } - } - else - { - // Fast, approximate implementation using float - for (unsigned int x = 0; x < width; x++, p++) - { - *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); - } - } - } - } - } - }; - - - Data data_; - - - public: - DicomInstanceParameters(const DicomInstanceParameters& other) : - data_(other.data_) - { - } - - DicomInstanceParameters(const Orthanc::DicomMap& dicom) : - data_(dicom) - { - } - - void SetOrthancInstanceIdentifier(const std::string& id) - { - data_.orthancInstanceId_ = id; - } - - const std::string& GetOrthancInstanceIdentifier() const - { - return data_.orthancInstanceId_; - } - - const Orthanc::DicomImageInformation& GetImageInformation() const - { - return data_.imageInformation_; - } - - const std::string& GetStudyInstanceUid() const - { - return data_.studyInstanceUid_; - } - - const std::string& GetSeriesInstanceUid() const - { - return data_.seriesInstanceUid_; - } - - const std::string& GetSopInstanceUid() const - { - return data_.sopInstanceUid_; - } - - SopClassUid GetSopClassUid() const - { - return data_.sopClassUid_; - } - - double GetThickness() const - { - return data_.thickness_; - } - - double GetPixelSpacingX() const - { - return data_.pixelSpacingX_; - } - - double GetPixelSpacingY() const - { - return data_.pixelSpacingY_; - } - - const CoordinateSystem3D& GetGeometry() const - { - return data_.geometry_; - } - - CoordinateSystem3D GetFrameGeometry(unsigned int frame) const - { - return data_.GetFrameGeometry(frame); - } - - // TODO - Is this necessary? - bool FrameContainsPlane(unsigned int frame, - const CoordinateSystem3D& plane) const - { - return data_.FrameContainsPlane(frame, plane); - } - - bool IsColor() const - { - return data_.isColor_; - } - - bool HasRescale() const - { - return data_.hasRescale_; - } - - double GetRescaleIntercept() const - { - if (data_.hasRescale_) - { - return data_.rescaleIntercept_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - double GetRescaleSlope() const - { - if (data_.hasRescale_) - { - return data_.rescaleSlope_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - bool HasDefaultWindowing() const - { - return data_.hasDefaultWindowing_; - } - - float GetDefaultWindowingCenter() const - { - if (data_.hasDefaultWindowing_) - { - return data_.defaultWindowingCenter_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - float GetDefaultWindowingWidth() const - { - if (data_.hasDefaultWindowing_) - { - return data_.defaultWindowingWidth_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - Orthanc::PixelFormat GetExpectedPixelFormat() const - { - return data_.expectedPixelFormat_; - } - - - TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const - { - assert(sizeof(float) == 4); - - Orthanc::PixelFormat sourceFormat = source.GetFormat(); - - if (sourceFormat != GetExpectedPixelFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - if (sourceFormat == Orthanc::PixelFormat_RGB24) - { - // This is the case of a color image. No conversion has to be done. - return new ColorTextureSceneLayer(source); - } - else - { - if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && - sourceFormat != Orthanc::PixelFormat_Grayscale32 && - sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - std::auto_ptr<FloatTextureSceneLayer> texture; - - { - // This is the case of a grayscale frame. Convert it to Float32. - std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, - source.GetWidth(), - source.GetHeight(), - false)); - Orthanc::ImageProcessing::Convert(*converted, source); - - // Correct rescale slope/intercept if need be - data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); - - texture.reset(new FloatTextureSceneLayer(*converted)); - } - - if (data_.hasDefaultWindowing_) - { - texture->SetCustomWindowing(data_.defaultWindowingCenter_, - data_.defaultWindowingWidth_); - } - - return texture.release(); - } - } - }; - - class DicomVolumeImage : public boost::noncopyable { private: - std::auto_ptr<ImageBuffer3D> image_; - std::auto_ptr<VolumeImageGeometry> geometry_; - std::vector<DicomInstanceParameters*> slices_; - uint64_t revision_; - std::vector<uint64_t> slicesRevision_; - std::vector<unsigned int> slicesQuality_; + std::auto_ptr<ImageBuffer3D> image_; + std::auto_ptr<VolumeImageGeometry> geometry_; + std::vector<DicomInstanceParameters*> slices_; + uint64_t revision_; + std::vector<uint64_t> slicesRevision_; + std::vector<unsigned int> slicesQuality_; void CheckSlice(size_t index, const DicomInstanceParameters& reference) const @@ -1380,10 +313,10 @@ - class IDicomVolumeSource : public boost::noncopyable + class IDicomVolumeImageSource : public boost::noncopyable { public: - virtual ~IDicomVolumeSource() + virtual ~IDicomVolumeImageSource() { } @@ -1396,7 +329,7 @@ class VolumeSeriesOrthancLoader : public IObserver, - public IDicomVolumeSource + public IDicomVolumeImageSource { private: static const unsigned int LOW_QUALITY = 0; @@ -1687,19 +620,19 @@ class DicomVolumeMPRSlicer : public IVolumeSlicer { private: - bool linearInterpolation_; - Scene2D& scene_; - int layerDepth_; - IDicomVolumeSource& source_; - bool first_; - VolumeProjection lastProjection_; - unsigned int lastSliceIndex_; - uint64_t lastSliceRevision_; + bool linearInterpolation_; + Scene2D& scene_; + int layerDepth_; + IDicomVolumeImageSource& source_; + bool first_; + VolumeProjection lastProjection_; + unsigned int lastSliceIndex_; + uint64_t lastSliceRevision_; public: DicomVolumeMPRSlicer(Scene2D& scene, int layerDepth, - IDicomVolumeSource& source) : + IDicomVolumeImageSource& source) : linearInterpolation_(false), scene_(scene), layerDepth_(layerDepth), @@ -1814,6 +747,8 @@ class ThreadedOracle : public IOracle { private: + typedef std::map<std::string, std::string> HttpHeaders; + class Item : public Orthanc::IDynamicObject { private: