# HG changeset patch # User Benjamin Golinvaux # Date 1559045703 -7200 # Node ID 7efcbae4717ec24bc1c87445d2be47726a532d81 # Parent 98a89b116b6287820cfc04004b72c85ec3a00749# Parent 20262f5e5e88c635502e409cf5a13595460a7d86 Added HttpClient init to Stone init diff -r 20262f5e5e88 -r 7efcbae4717e Applications/Generic/NativeStoneApplicationRunner.cpp --- a/Applications/Generic/NativeStoneApplicationRunner.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Applications/Generic/NativeStoneApplicationRunner.cpp Tue May 28 14:15:03 2019 +0200 @@ -25,7 +25,7 @@ #include "NativeStoneApplicationRunner.h" -#include "../../Framework/Toolbox/MessagingToolbox.h" +#include "../../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include "../../Platforms/Generic/OracleWebService.h" #include "../../Platforms/Generic/OracleDelayedCallExecutor.h" #include "NativeStoneApplicationContext.h" @@ -180,7 +180,7 @@ { OrthancPlugins::OrthancHttpConnection orthanc(webServiceParameters); - if (!MessagingToolbox::CheckOrthancVersion(orthanc)) + if (!Deprecated::MessagingToolbox::CheckOrthancVersion(orthanc)) { LOG(ERROR) << "Your version of Orthanc is incompatible with Stone of " << "Orthanc, please upgrade"; diff -r 20262f5e5e88 -r 7efcbae4717e Applications/Sdl/SdlStoneApplicationRunner.cpp --- a/Applications/Sdl/SdlStoneApplicationRunner.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Applications/Sdl/SdlStoneApplicationRunner.cpp Tue May 28 14:15:03 2019 +0200 @@ -25,7 +25,6 @@ #include "SdlStoneApplicationRunner.h" -#include "../../Framework/Toolbox/MessagingToolbox.h" #include "../../Platforms/Generic/OracleWebService.h" #include "SdlEngine.h" diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp --- a/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Deprecated/Layers/DicomStructureSetSlicer.cpp Tue May 28 14:15:03 2019 +0200 @@ -80,7 +80,7 @@ const OrthancStone::CoordinateSystem3D& plane) : plane_(plane) { - for (size_t k = 0; k < structureSet.GetStructureCount(); k++) + for (size_t k = 0; k < structureSet.GetStructuresCount(); k++) { structures_.push_back(new Structure(structureSet, plane, k)); } diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Toolbox/MessagingToolbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/MessagingToolbox.cpp Tue May 28 14:15:03 2019 +0200 @@ -0,0 +1,456 @@ +/** + * 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 . + **/ + + +#include "MessagingToolbox.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Deprecated +{ + namespace MessagingToolbox + { + static bool ParseVersion(std::string& version, + unsigned int& major, + unsigned int& minor, + unsigned int& patch, + const Json::Value& info) + { + if (info.type() != Json::objectValue || + !info.isMember("Version") || + info["Version"].type() != Json::stringValue) + { + return false; + } + + version = info["Version"].asString(); + if (version == "mainline") + { + // Some arbitrary high values Orthanc versions will never reach ;) + major = 999; + minor = 999; + patch = 999; + return true; + } + + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, version, '.'); + + if (tokens.size() != 2 && + tokens.size() != 3) + { + return false; + } + + int a, b, c; + try + { + a = boost::lexical_cast(tokens[0]); + b = boost::lexical_cast(tokens[1]); + + if (tokens.size() == 3) + { + c = boost::lexical_cast(tokens[2]); + } + else + { + c = 0; + } + } + catch (boost::bad_lexical_cast&) + { + return false; + } + + if (a < 0 || + b < 0 || + c < 0) + { + return false; + } + else + { + major = static_cast(a); + minor = static_cast(b); + patch = static_cast(c); + return true; + } + } + + + bool ParseJson(Json::Value& target, + const void* content, + size_t size) + { + Json::Reader reader; + return reader.parse(reinterpret_cast(content), + reinterpret_cast(content) + size, + target); + } + + void JsonToString(std::string& target, + const Json::Value& source) + { + Json::FastWriter writer; + target = writer.write(source); + } + + static void ParseJsonException(Json::Value& target, + const std::string& source) + { + Json::Reader reader; + if (!reader.parse(source, target)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + + void RestApiGet(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri) + { + std::string tmp; + orthanc.RestApiGet(tmp, uri); + ParseJsonException(target, tmp); + } + + + void RestApiPost(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body) + { + std::string tmp; + orthanc.RestApiPost(tmp, uri, body); + ParseJsonException(target, tmp); + } + + + bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc) + { + try + { + Json::Value json; + RestApiGet(json, orthanc, "/plugins/web-viewer"); + return json.type() == Json::objectValue; + } + catch (Orthanc::OrthancException&) + { + return false; + } + } + + + bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc) + { + Json::Value json; + std::string version; + unsigned int major, minor, patch; + + try + { + RestApiGet(json, orthanc, "/system"); + } + catch (Orthanc::OrthancException&) + { + LOG(ERROR) << "Cannot connect to your Orthanc server"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + if (!ParseVersion(version, major, minor, patch, json)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version; + + // Stone is only compatible with Orthanc >= 1.3.1 + if (major < 1 || + (major == 1 && minor < 3) || + (major == 1 && minor == 3 && patch < 1)) + { + return false; + } + + try + { + RestApiGet(json, orthanc, "/plugins/web-viewer"); + } + catch (Orthanc::OrthancException&) + { + // The Web viewer is not installed, this is OK + LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled"; + return true; + } + + if (!ParseVersion(version, major, minor, patch, json)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version; + + return (major >= 3 || + (major == 2 && minor >= 2)); + } + + + Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + Orthanc::PixelFormat targetFormat) + { + std::string uri = ("instances/" + instance + "/frames/" + + boost::lexical_cast(frame)); + + std::string compressed; + + switch (targetFormat) + { + case Orthanc::PixelFormat_RGB24: + orthanc.RestApiGet(compressed, uri + "/preview"); + break; + + case Orthanc::PixelFormat_Grayscale16: + orthanc.RestApiGet(compressed, uri + "/image-uint16"); + break; + + case Orthanc::PixelFormat_SignedGrayscale16: + orthanc.RestApiGet(compressed, uri + "/image-int16"); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::auto_ptr result(new Orthanc::PngReader); + result->ReadFromMemory(compressed); + + if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16) + { + if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16) + { + result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + } + + return result.release(); + } + + + Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + unsigned int quality, + Orthanc::PixelFormat targetFormat) + { + if (quality <= 0 || + quality > 100) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + // This requires the official Web viewer plugin to be installed! + std::string uri = ("web-viewer/instances/jpeg" + + boost::lexical_cast(quality) + + "-" + instance + "_" + + boost::lexical_cast(frame)); + + Json::Value encoded; + RestApiGet(encoded, orthanc, uri); + + if (encoded.type() != Json::objectValue || + !encoded.isMember("Orthanc") || + encoded["Orthanc"].type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + 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_NetworkProtocol); + } + + bool isSigned = false; + bool isStretched = info["Stretched"].asBool(); + + if (info.isMember("IsSigned")) + { + if (info["IsSigned"].type() != Json::booleanValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + else + { + isSigned = info["IsSigned"].asBool(); + } + } + + std::string jpeg; + Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); + + std::auto_ptr reader(new Orthanc::JpegReader); + reader->ReadFromMemory(jpeg); + + if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image + { + if (targetFormat != Orthanc::PixelFormat_RGB24) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + if (isSigned || isStretched) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + else + { + return reader.release(); + } + } + + if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + if (!isStretched) + { + if (targetFormat != reader->GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + return reader.release(); + } + + 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_NetworkProtocol); + } + + 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_NotImplemented); + } + + // Decode a grayscale JPEG 8bpp image coming from the Web viewer + std::auto_ptr image + (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false)); + + float scaling = static_cast(stretchHigh - stretchLow) / 255.0f; + float offset = static_cast(stretchLow) / scaling; + + Orthanc::ImageProcessing::Convert(*image, *reader); + Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); + +#if 0 + /*info.removeMember("PixelData"); + std::cout << info.toStyledString();*/ + + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image); + std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl; +#endif + + return image.release(); + } + + + static void AddTag(Orthanc::DicomMap& target, + const OrthancPlugins::IDicomDataset& source, + const Orthanc::DicomTag& tag) + { + OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement()); + + std::string value; + if (source.GetStringValue(value, key)) + { + target.SetValue(tag, value, false); + } + } + + + void ConvertDataset(Orthanc::DicomMap& target, + const OrthancPlugins::IDicomDataset& source) + { + target.Clear(); + + AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED); + AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED); + AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS); + AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING); + AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER); + AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); + AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT); + AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT); + AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT); + AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES); + AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION); + AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION); + AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING); + AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION); + AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); + AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE); + AddTag(target, source, Orthanc::DICOM_TAG_ROWS); + AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL); + AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); + AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS); + AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID); + AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID); + AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER); + AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH); + } + } +} diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Toolbox/MessagingToolbox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Deprecated/Toolbox/MessagingToolbox.h Tue May 28 14:15:03 2019 +0200 @@ -0,0 +1,75 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "../../StoneEnumerations.h" + +#include +#include +#include +#include + +#include + +namespace Deprecated +{ + namespace MessagingToolbox + { + bool ParseJson(Json::Value& target, + const void* content, + size_t size); + + void JsonToString(std::string& target, + const Json::Value& source); + + + void RestApiGet(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri); + + void RestApiPost(Json::Value& target, + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& uri, + const std::string& body); + + bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc); + + bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc); + + // This downloads the image from Orthanc and keeps its pixel + // format unchanged (will be either Grayscale8, Grayscale16, + // SignedGrayscale16, or RGB24) + Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + Orthanc::PixelFormat targetFormat); + + Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instance, + unsigned int frame, + unsigned int quality, + Orthanc::PixelFormat targetFormat); + + void ConvertDataset(Orthanc::DicomMap& target, + const OrthancPlugins::IDicomDataset& source); + } +} diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Toolbox/OrthancApiClient.cpp --- a/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancApiClient.cpp Tue May 28 14:15:03 2019 +0200 @@ -20,7 +20,7 @@ #include "OrthancApiClient.h" -#include "../../Toolbox/MessagingToolbox.h" +#include "../Toolbox/MessagingToolbox.h" #include @@ -139,7 +139,7 @@ else if (jsonHandler_.get() != NULL) { Json::Value response; - if (OrthancStone::MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) + if (MessagingToolbox::ParseJson(response, message.GetAnswer(), message.GetAnswerSize())) { jsonHandler_->Apply(OrthancApiClient::JsonResponseReadyMessage (message.GetUri(), response, userPayload_.get())); @@ -270,7 +270,7 @@ Orthanc::IDynamicObject* payload) { std::string body; - OrthancStone::MessagingToolbox::JsonToString(body, data); + MessagingToolbox::JsonToString(body, data); return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload); } @@ -279,7 +279,7 @@ const Json::Value& data) { std::string body; - OrthancStone::MessagingToolbox::JsonToString(body, data); + MessagingToolbox::JsonToString(body, data); return PostBinaryAsync(uri, body); } @@ -291,7 +291,7 @@ Orthanc::IDynamicObject* payload /* takes ownership */) { std::string body; - OrthancStone::MessagingToolbox::JsonToString(body, data); + MessagingToolbox::JsonToString(body, data); return PostBinaryAsync(uri, body, successCallback, failureCallback, payload); } diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp Tue May 28 14:15:03 2019 +0200 @@ -21,7 +21,7 @@ #include "OrthancSlicesLoader.h" -#include "../../Toolbox/MessagingToolbox.h" +#include "../Toolbox/MessagingToolbox.h" #include #include @@ -231,7 +231,7 @@ OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); Orthanc::DicomMap dicom; - OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset); + MessagingToolbox::ConvertDataset(dicom, dataset); unsigned int frames; if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) @@ -265,7 +265,7 @@ OrthancPlugins::FullOrthancDataset dataset(tags); Orthanc::DicomMap dicom; - OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset); + MessagingToolbox::ConvertDataset(dicom, dataset); unsigned int frames; if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) @@ -306,7 +306,7 @@ state_ = State_GeometryReady; Orthanc::DicomMap dicom; - OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset); + MessagingToolbox::ConvertDataset(dicom, dataset); std::auto_ptr slice(new Slice); if (slice->ParseOrthancFrame(dicom, instanceId, frame)) diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Volumes/StructureSetLoader.cpp --- a/Framework/Deprecated/Volumes/StructureSetLoader.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.cpp Tue May 28 14:15:03 2019 +0200 @@ -21,7 +21,7 @@ #include "StructureSetLoader.h" -#include "../../Toolbox/MessagingToolbox.h" +#include "../Toolbox/MessagingToolbox.h" #include @@ -41,7 +41,7 @@ OrthancPlugins::FullOrthancDataset dataset(message.GetJson()); Orthanc::DicomMap slice; - OrthancStone::MessagingToolbox::ConvertDataset(slice, dataset); + MessagingToolbox::ConvertDataset(slice, dataset); structureSet_->AddReferencedSlice(slice); BroadcastMessage(ContentChangedMessage(*this)); @@ -113,4 +113,47 @@ return *structureSet_; } } + + + OrthancStone::DicomStructureSet* StructureSetLoader::SynchronousLoad( + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId) + { + const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; + OrthancPlugins::FullOrthancDataset dataset(orthanc, uri); + + std::auto_ptr result + (new OrthancStone::DicomStructureSet(dataset)); + + std::set instances; + result->GetReferencedInstances(instances); + + for (std::set::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + Json::Value lookup; + MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it); + + if (lookup.type() != Json::arrayValue || + lookup.size() != 1 || + !lookup[0].isMember("Type") || + !lookup[0].isMember("Path") || + lookup[0]["Type"].type() != Json::stringValue || + lookup[0]["ID"].type() != Json::stringValue || + lookup[0]["Type"].asString() != "Instance") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + OrthancPlugins::FullOrthancDataset slice + (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags"); + Orthanc::DicomMap m; + MessagingToolbox::ConvertDataset(m, slice); + result->AddReferencedSlice(m); + } + + result->CheckReferencedSlices(); + + return result.release(); + } } diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Deprecated/Volumes/StructureSetLoader.h --- a/Framework/Deprecated/Volumes/StructureSetLoader.h Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Deprecated/Volumes/StructureSetLoader.h Tue May 28 14:15:03 2019 +0200 @@ -53,5 +53,9 @@ } OrthancStone::DicomStructureSet& GetStructureSet(); + + static OrthancStone::DicomStructureSet* SynchronousLoad( + OrthancPlugins::IOrthancConnection& orthanc, + const std::string& instanceId); }; } diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Scene2D/LookupTableTextureSceneLayer.cpp --- a/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Scene2D/LookupTableTextureSceneLayer.cpp Tue May 28 14:15:03 2019 +0200 @@ -63,9 +63,9 @@ for (size_t i = 0; i < 256; i++) { - rgb[3 * i] = i; - rgb[3 * i + 1] = i; - rgb[3 * i + 2] = i; + rgb[3 * i] = static_cast(i); + rgb[3 * i + 1] = static_cast(i); + rgb[3 * i + 2] = static_cast(i); } SetLookupTableRgb(rgb); diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Toolbox/DicomStructureSet.cpp --- a/Framework/Toolbox/DicomStructureSet.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Tue May 28 14:15:03 2019 +0200 @@ -22,7 +22,6 @@ #include "DicomStructureSet.h" #include "../Toolbox/GeometryToolbox.h" -#include "../Toolbox/MessagingToolbox.h" #include #include @@ -31,7 +30,6 @@ #include #include -#include #include #include #include @@ -422,7 +420,6 @@ << static_cast(structures_[i].green_) << "," << static_cast(structures_[i].blue_) << ")"; - // These temporary variables avoid allocating many vectors in the loop below OrthancPlugins::DicomPath countPointsPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, DICOM_TAG_CONTOUR_SEQUENCE, 0, @@ -680,50 +677,9 @@ } - DicomStructureSet* DicomStructureSet::SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId) - { - const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; - OrthancPlugins::FullOrthancDataset dataset(orthanc, uri); - - std::auto_ptr result(new DicomStructureSet(dataset)); - - std::set instances; - result->GetReferencedInstances(instances); - - for (std::set::const_iterator it = instances.begin(); - it != instances.end(); ++it) - { - Json::Value lookup; - MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it); - - if (lookup.type() != Json::arrayValue || - lookup.size() != 1 || - !lookup[0].isMember("Type") || - !lookup[0].isMember("Path") || - lookup[0]["Type"].type() != Json::stringValue || - lookup[0]["ID"].type() != Json::stringValue || - lookup[0]["Type"].asString() != "Instance") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - - OrthancPlugins::FullOrthancDataset slice - (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags"); - Orthanc::DicomMap m; - MessagingToolbox::ConvertDataset(m, slice); - result->AddReferencedSlice(m); - } - - result->CheckReferencedSlices(); - - return result.release(); - } - - bool DicomStructureSet::ProjectStructure(std::vector< std::vector >& polygons, - Structure& structure, - const CoordinateSystem3D& slice) + const Structure& structure, + const CoordinateSystem3D& slice) const { polygons.clear(); @@ -734,7 +690,7 @@ { // This is an axial projection - for (Polygons::iterator polygon = structure.polygons_.begin(); + for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { if (polygon->IsOnSlice(slice)) @@ -760,7 +716,7 @@ // Sagittal or coronal projection std::vector projected; - for (Polygons::iterator polygon = structure.polygons_.begin(); + for (Polygons::const_iterator polygon = structure.polygons_.begin(); polygon != structure.polygons_.end(); ++polygon) { double x1, y1, x2, y2; diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Toolbox/DicomStructureSet.h --- a/Framework/Toolbox/DicomStructureSet.h Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.h Tue May 28 14:15:03 2019 +0200 @@ -135,13 +135,13 @@ Structure& GetStructure(size_t index); bool ProjectStructure(std::vector< std::vector >& polygons, - Structure& structure, - const CoordinateSystem3D& slice); + const Structure& structure, + const CoordinateSystem3D& slice) const; public: DicomStructureSet(const OrthancPlugins::FullOrthancDataset& instance); - size_t GetStructureCount() const + size_t GetStructuresCount() const { return structures_.size(); } @@ -170,13 +170,9 @@ Vector GetNormal() const; - // TODO - Remove - static DicomStructureSet* SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instanceId); - bool ProjectStructure(std::vector< std::vector >& polygons, size_t index, - const CoordinateSystem3D& slice) + const CoordinateSystem3D& slice) const { return ProjectStructure(polygons, GetStructure(index), slice); } diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Toolbox/LinearAlgebra.cpp --- a/Framework/Toolbox/LinearAlgebra.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Toolbox/LinearAlgebra.cpp Tue May 28 14:15:03 2019 +0200 @@ -26,7 +26,6 @@ #include #include -#include #include namespace OrthancStone @@ -71,9 +70,16 @@ { try { - target[i] = boost::lexical_cast(items[i]); + /** + * We don't use "boost::lexical_cast<>" here, as it is very + * slow. As we are parsing many doubles, we prefer to use + * the standard "std::stod" function: + * http://www.cplusplus.com/reference/string/stod/ + **/ + + target[i] = std::stod(items[i]); } - catch (boost::bad_lexical_cast&) + catch (std::exception&) { target.clear(); return false; diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Toolbox/MessagingToolbox.cpp --- a/Framework/Toolbox/MessagingToolbox.cpp Tue May 28 11:37:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,456 +0,0 @@ -/** - * 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 . - **/ - - -#include "MessagingToolbox.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace OrthancStone -{ - namespace MessagingToolbox - { - static bool ParseVersion(std::string& version, - unsigned int& major, - unsigned int& minor, - unsigned int& patch, - const Json::Value& info) - { - if (info.type() != Json::objectValue || - !info.isMember("Version") || - info["Version"].type() != Json::stringValue) - { - return false; - } - - version = info["Version"].asString(); - if (version == "mainline") - { - // Some arbitrary high values Orthanc versions will never reach ;) - major = 999; - minor = 999; - patch = 999; - return true; - } - - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, version, '.'); - - if (tokens.size() != 2 && - tokens.size() != 3) - { - return false; - } - - int a, b, c; - try - { - a = boost::lexical_cast(tokens[0]); - b = boost::lexical_cast(tokens[1]); - - if (tokens.size() == 3) - { - c = boost::lexical_cast(tokens[2]); - } - else - { - c = 0; - } - } - catch (boost::bad_lexical_cast&) - { - return false; - } - - if (a < 0 || - b < 0 || - c < 0) - { - return false; - } - else - { - major = static_cast(a); - minor = static_cast(b); - patch = static_cast(c); - return true; - } - } - - - bool ParseJson(Json::Value& target, - const void* content, - size_t size) - { - Json::Reader reader; - return reader.parse(reinterpret_cast(content), - reinterpret_cast(content) + size, - target); - } - - void JsonToString(std::string& target, - const Json::Value& source) - { - Json::FastWriter writer; - target = writer.write(source); - } - - static void ParseJsonException(Json::Value& target, - const std::string& source) - { - Json::Reader reader; - if (!reader.parse(source, target)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - } - - - void RestApiGet(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri) - { - std::string tmp; - orthanc.RestApiGet(tmp, uri); - ParseJsonException(target, tmp); - } - - - void RestApiPost(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri, - const std::string& body) - { - std::string tmp; - orthanc.RestApiPost(tmp, uri, body); - ParseJsonException(target, tmp); - } - - - bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc) - { - try - { - Json::Value json; - RestApiGet(json, orthanc, "/plugins/web-viewer"); - return json.type() == Json::objectValue; - } - catch (Orthanc::OrthancException&) - { - return false; - } - } - - - bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc) - { - Json::Value json; - std::string version; - unsigned int major, minor, patch; - - try - { - RestApiGet(json, orthanc, "/system"); - } - catch (Orthanc::OrthancException&) - { - LOG(ERROR) << "Cannot connect to your Orthanc server"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - if (!ParseVersion(version, major, minor, patch, json)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - LOG(WARNING) << "Version of the Orthanc core (must be above 1.3.1): " << version; - - // Stone is only compatible with Orthanc >= 1.3.1 - if (major < 1 || - (major == 1 && minor < 3) || - (major == 1 && minor == 3 && patch < 1)) - { - return false; - } - - try - { - RestApiGet(json, orthanc, "/plugins/web-viewer"); - } - catch (Orthanc::OrthancException&) - { - // The Web viewer is not installed, this is OK - LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled"; - return true; - } - - if (!ParseVersion(version, major, minor, patch, json)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version; - - return (major >= 3 || - (major == 2 && minor >= 2)); - } - - - Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - Orthanc::PixelFormat targetFormat) - { - std::string uri = ("instances/" + instance + "/frames/" + - boost::lexical_cast(frame)); - - std::string compressed; - - switch (targetFormat) - { - case Orthanc::PixelFormat_RGB24: - orthanc.RestApiGet(compressed, uri + "/preview"); - break; - - case Orthanc::PixelFormat_Grayscale16: - orthanc.RestApiGet(compressed, uri + "/image-uint16"); - break; - - case Orthanc::PixelFormat_SignedGrayscale16: - orthanc.RestApiGet(compressed, uri + "/image-int16"); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::auto_ptr result(new Orthanc::PngReader); - result->ReadFromMemory(compressed); - - if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16) - { - if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16) - { - result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - } - - return result.release(); - } - - - Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - unsigned int quality, - Orthanc::PixelFormat targetFormat) - { - if (quality <= 0 || - quality > 100) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - // This requires the official Web viewer plugin to be installed! - std::string uri = ("web-viewer/instances/jpeg" + - boost::lexical_cast(quality) + - "-" + instance + "_" + - boost::lexical_cast(frame)); - - Json::Value encoded; - RestApiGet(encoded, orthanc, uri); - - if (encoded.type() != Json::objectValue || - !encoded.isMember("Orthanc") || - encoded["Orthanc"].type() != Json::objectValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - 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_NetworkProtocol); - } - - bool isSigned = false; - bool isStretched = info["Stretched"].asBool(); - - if (info.isMember("IsSigned")) - { - if (info["IsSigned"].type() != Json::booleanValue) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - else - { - isSigned = info["IsSigned"].asBool(); - } - } - - std::string jpeg; - Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); - - std::auto_ptr reader(new Orthanc::JpegReader); - reader->ReadFromMemory(jpeg); - - if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image - { - if (targetFormat != Orthanc::PixelFormat_RGB24) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - if (isSigned || isStretched) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - else - { - return reader.release(); - } - } - - if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - if (!isStretched) - { - if (targetFormat != reader->GetFormat()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } - - return reader.release(); - } - - 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_NetworkProtocol); - } - - 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_NotImplemented); - } - - // Decode a grayscale JPEG 8bpp image coming from the Web viewer - std::auto_ptr image - (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight(), false)); - - float scaling = static_cast(stretchHigh - stretchLow) / 255.0f; - float offset = static_cast(stretchLow) / scaling; - - Orthanc::ImageProcessing::Convert(*image, *reader); - Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); - -#if 0 - /*info.removeMember("PixelData"); - std::cout << info.toStyledString();*/ - - int64_t a, b; - Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image); - std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl; -#endif - - return image.release(); - } - - - static void AddTag(Orthanc::DicomMap& target, - const OrthancPlugins::IDicomDataset& source, - const Orthanc::DicomTag& tag) - { - OrthancPlugins::DicomTag key(tag.GetGroup(), tag.GetElement()); - - std::string value; - if (source.GetStringValue(value, key)) - { - target.SetValue(tag, value, false); - } - } - - - void ConvertDataset(Orthanc::DicomMap& target, - const OrthancPlugins::IDicomDataset& source) - { - target.Clear(); - - AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED); - AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED); - AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS); - AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING); - AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER); - AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); - AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT); - AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT); - AddTag(target, source, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT); - AddTag(target, source, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES); - AddTag(target, source, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION); - AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION); - AddTag(target, source, Orthanc::DICOM_TAG_PIXEL_SPACING); - AddTag(target, source, Orthanc::DICOM_TAG_PLANAR_CONFIGURATION); - AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); - AddTag(target, source, Orthanc::DICOM_TAG_RESCALE_SLOPE); - AddTag(target, source, Orthanc::DICOM_TAG_ROWS); - AddTag(target, source, Orthanc::DICOM_TAG_SAMPLES_PER_PIXEL); - AddTag(target, source, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID); - AddTag(target, source, Orthanc::DICOM_TAG_SLICE_THICKNESS); - AddTag(target, source, Orthanc::DICOM_TAG_SOP_CLASS_UID); - AddTag(target, source, Orthanc::DICOM_TAG_SOP_INSTANCE_UID); - AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_CENTER); - AddTag(target, source, Orthanc::DICOM_TAG_WINDOW_WIDTH); - } - } -} diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Toolbox/MessagingToolbox.h --- a/Framework/Toolbox/MessagingToolbox.h Tue May 28 11:37:50 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/** - * 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 . - **/ - - -#pragma once - -#include "../StoneEnumerations.h" - -#include -#include -#include -#include - -#include - -namespace OrthancStone -{ - namespace MessagingToolbox - { - bool ParseJson(Json::Value& target, - const void* content, - size_t size); - - void JsonToString(std::string& target, - const Json::Value& source); - - - void RestApiGet(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri); - - void RestApiPost(Json::Value& target, - OrthancPlugins::IOrthancConnection& orthanc, - const std::string& uri, - const std::string& body); - - bool HasWebViewerInstalled(OrthancPlugins::IOrthancConnection& orthanc); - - bool CheckOrthancVersion(OrthancPlugins::IOrthancConnection& orthanc); - - // This downloads the image from Orthanc and keeps its pixel - // format unchanged (will be either Grayscale8, Grayscale16, - // SignedGrayscale16, or RGB24) - Orthanc::ImageAccessor* DecodeFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - Orthanc::PixelFormat targetFormat); - - Orthanc::ImageAccessor* DecodeJpegFrame(OrthancPlugins::IOrthancConnection& orthanc, - const std::string& instance, - unsigned int frame, - unsigned int quality, - Orthanc::PixelFormat targetFormat); - - void ConvertDataset(Orthanc::DicomMap& target, - const OrthancPlugins::IDicomDataset& source); - } -} diff -r 20262f5e5e88 -r 7efcbae4717e Framework/Volumes/VolumeImageGeometry.h --- a/Framework/Volumes/VolumeImageGeometry.h Tue May 28 11:37:50 2019 +0200 +++ b/Framework/Volumes/VolumeImageGeometry.h Tue May 28 14:15:03 2019 +0200 @@ -115,6 +115,15 @@ bool DetectProjection(VolumeProjection& projection, const Vector& planeNormal) const; + /** + Being given a cutting plane, this method will determine if it is an + axial, sagittal or coronal cut and returns + the slice number corresponding to this cut. + + If the cutting plane is not parallel to the tree x = 0, y = 0 or z = 0 + planes, it is considered as arbitrary and the method returns false. + Otherwise, it returns true. + */ bool DetectSlice(VolumeProjection& projection, unsigned int& slice, const CoordinateSystem3D& plane) const; diff -r 20262f5e5e88 -r 7efcbae4717e Platforms/Wasm/WasmPlatformApplicationAdapter.cpp --- a/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp Tue May 28 14:15:03 2019 +0200 @@ -1,6 +1,5 @@ #include "WasmPlatformApplicationAdapter.h" -#include "Framework/Toolbox/MessagingToolbox.h" #include "Framework/StoneException.h" #include #include "Platforms/Wasm/Defaults.h" @@ -57,4 +56,4 @@ } } -} \ No newline at end of file +} diff -r 20262f5e5e88 -r 7efcbae4717e Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Tue May 28 11:37:50 2019 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Tue May 28 14:15:03 2019 +0200 @@ -330,6 +330,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/DownloadStack.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IDelayedCallExecutor.h ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/IWebService.cpp + ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/MessagingToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancApiClient.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Deprecated/Toolbox/Slice.cpp @@ -478,7 +479,6 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/GeometryToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ImageGeometry.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/LinearAlgebra.cpp - ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp diff -r 20262f5e5e88 -r 7efcbae4717e Samples/Sdl/Loader.cpp --- a/Samples/Sdl/Loader.cpp Tue May 28 11:37:50 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Tue May 28 14:15:03 2019 +0200 @@ -193,10 +193,24 @@ } }; - + /** + This interface is implemented by objects representing 3D volume data and + that are able to return an object that represent a slice of their data + and are able to create the corresponding visual representation. + */ class IVolumeSlicer : public boost::noncopyable { public: + /** + This interface is implemented by objects representing a slice of + volume data and that are able to create a 2D layer to display a this + slice. + + The CreateSceneLayer factory method is called with an optional + configurator that possibly impacts the ISceneLayer subclass that is + created (for instance, if a LUT must be applied on the texture when + displaying it) + */ class IExtractedSlice : public boost::noncopyable { public: @@ -204,9 +218,18 @@ { } + /** + Invalid slices are created when the data is not ready yet or if the + cut is outside of the available geometry. + */ virtual bool IsValid() = 0; - // Must be a cheap call + /** + This retrieves the *revision* that gets incremented every time the + underlying object undergoes a mutable operation (that it, changes its + state). + This **must** be a cheap call. + */ virtual uint64_t GetRevision() = 0; // This call can take some time @@ -214,7 +237,9 @@ const CoordinateSystem3D& cuttingPlane) = 0; }; - + /** + See IExtractedSlice.IsValid() + */ class InvalidSlice : public IExtractedSlice { public: @@ -240,13 +265,21 @@ { } + /** + This method is implemented by the objects representing volumetric data + and must returns an IExtractedSlice subclass that contains all the data + needed to, later one, create its visual representation through + CreateSceneLayer. + Subclasses a.o.: ExtractedSlice, Slice, InvalidSlice + */ virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; }; - - - // This class combines a 3D image buffer, a 3D volume geometry and - // information about the DICOM parameters of the series. + /** + This class combines a 3D image buffer, a 3D volume geometry and + information about the DICOM parameters of the series. + (MPR means MultiPlanar Reconstruction) + */ class DicomVolumeImage : public boost::noncopyable { public: @@ -341,125 +374,157 @@ } }; - - - class DicomVolumeImageOrthogonalSlice : public IVolumeSlicer::IExtractedSlice + /** + Implements the IVolumeSlicer on Dicom volume data when the cutting plane + that is supplied to the slicer is either axial, sagittal or coronal. + Arbitrary planes are *not* supported + */ + class DicomVolumeImageMPRSlicer : public IVolumeSlicer { - private: - const DicomVolumeImage& volume_; - bool valid_; - VolumeProjection projection_; - unsigned int sliceIndex_; - - void CheckValid() const + public: + class Slice : public IExtractedSlice { - if (!valid_) + private: + const DicomVolumeImage& volume_; + bool valid_; + VolumeProjection projection_; + unsigned int sliceIndex_; + + void CheckValid() const + { + if (!valid_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + protected: + // Can be overloaded in subclasses + virtual uint64_t GetRevisionInternal(VolumeProjection projection, + unsigned int sliceIndex) const { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + return volume_.GetRevision(); + } + + public: + /** + The constructor initializes the type of projection (axial, sagittal or + coronal) and the corresponding slice index, from the cutting plane. + */ + Slice(const DicomVolumeImage& volume, + const CoordinateSystem3D& cuttingPlane) : + volume_(volume) + { + valid_ = (volume_.HasDicomParameters() && + volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); } - } + + VolumeProjection GetProjection() const + { + CheckValid(); + return projection_; + } + + unsigned int GetSliceIndex() const + { + CheckValid(); + return sliceIndex_; + } + + virtual bool IsValid() + { + return valid_; + } + + virtual uint64_t GetRevision() + { + CheckValid(); + return GetRevisionInternal(projection_, sliceIndex_); + } - protected: - // Can be overloaded in subclasses - virtual uint64_t GetRevisionInternal(VolumeProjection projection, - unsigned int sliceIndex) const - { - return volume_.GetRevision(); - } + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + CheckValid(); + + if (configurator == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer, + "A style configurator is mandatory for textures"); + } + + std::auto_ptr texture; + + { + const DicomInstanceParameters& parameters = volume_.GetDicomParameters(); + ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_); + texture.reset(dynamic_cast + (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); + } + + const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); + + double x0, y0, x1, y1; + cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); + cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); + texture->SetOrigin(x0, y0); + + double dx = x1 - x0; + double dy = y1 - y0; + if (!LinearAlgebra::IsCloseToZero(dx) || + !LinearAlgebra::IsCloseToZero(dy)) + { + texture->SetAngle(atan2(dy, dx)); + } + + Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_); + texture->SetPixelSpacing(tmp[0], tmp[1]); + + return texture.release(); + +#if 0 + double w = texture->GetTexture().GetWidth() * tmp[0]; + double h = texture->GetTexture().GetHeight() * tmp[1]; + printf("%.1f %.1f %.1f => %.1f %.1f => %.1f %.1f\n", + system.GetOrigin() [0], + system.GetOrigin() [1], + system.GetOrigin() [2], + x0, y0, x0 + w, y0 + h); + + std::auto_ptr toto(new PolylineSceneLayer); + + PolylineSceneLayer::Chain c; + c.push_back(ScenePoint2D(x0, y0)); + c.push_back(ScenePoint2D(x0 + w, y0)); + c.push_back(ScenePoint2D(x0 + w, y0 + h)); + c.push_back(ScenePoint2D(x0, y0 + h)); + + toto->AddChain(c, true); + + return toto.release(); +#endif + } + }; + + private: + boost::shared_ptr volume_; public: - DicomVolumeImageOrthogonalSlice(const DicomVolumeImage& volume, - const CoordinateSystem3D& cuttingPlane) : + DicomVolumeImageMPRSlicer(const boost::shared_ptr& volume) : volume_(volume) { - valid_ = (volume_.HasDicomParameters() && - volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); - } - - VolumeProjection GetProjection() const - { - CheckValid(); - return projection_; - } - - unsigned int GetSliceIndex() const - { - CheckValid(); - return sliceIndex_; - } - - virtual bool IsValid() - { - return valid_; - } - - virtual uint64_t GetRevision() - { - CheckValid(); - return GetRevisionInternal(projection_, sliceIndex_); } - virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, - const CoordinateSystem3D& cuttingPlane) + virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) { - CheckValid(); - - if (configurator == NULL) + if (volume_->HasGeometry()) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer, - "A style configurator is mandatory for textures"); - } - - std::auto_ptr texture; - - { - const DicomInstanceParameters& parameters = volume_.GetDicomParameters(); - ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_); - texture.reset(dynamic_cast - (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); + return new Slice(*volume_, cuttingPlane); } - - const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); - - double x0, y0, x1, y1; - cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); - cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); - texture->SetOrigin(x0, y0); - - double dx = x1 - x0; - double dy = y1 - y0; - if (!LinearAlgebra::IsCloseToZero(dx) || - !LinearAlgebra::IsCloseToZero(dy)) + else { - texture->SetAngle(atan2(dy, dx)); + return new IVolumeSlicer::InvalidSlice; } - - Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_); - texture->SetPixelSpacing(tmp[0], tmp[1]); - - return texture.release(); - -#if 0 - double w = texture->GetTexture().GetWidth() * tmp[0]; - double h = texture->GetTexture().GetHeight() * tmp[1]; - printf("%.1f %.1f %.1f => %.1f %.1f => %.1f %.1f\n", - system.GetOrigin() [0], - system.GetOrigin() [1], - system.GetOrigin() [2], - x0, y0, x0 + w, y0 + h); - - std::auto_ptr toto(new PolylineSceneLayer); - - PolylineSceneLayer::Chain c; - c.push_back(ScenePoint2D(x0, y0)); - c.push_back(ScenePoint2D(x0 + w, y0)); - c.push_back(ScenePoint2D(x0 + w, y0 + h)); - c.push_back(ScenePoint2D(x0, y0 + h)); - - toto->AddChain(c, true); - - return toto.release(); -#endif } }; @@ -583,6 +648,7 @@ } // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" + // (called with the slices created in LoadGeometry) void ComputeGeometry(SlicesSorter& slices) { Clear(); @@ -664,7 +730,7 @@ }; - class Slice : public DicomVolumeImageOrthogonalSlice + class ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice { private: const OrthancSeriesVolumeProgressiveLoader& that_; @@ -680,17 +746,25 @@ else { // For coronal and sagittal projections, we take the global - // revision of the volume + // revision of the volume because even if a single slice changes, + // this means the projection will yield a different result --> + // we must increase the revision as soon as any slice changes return that_.volume_->GetRevision(); } } public: - Slice(const OrthancSeriesVolumeProgressiveLoader& that, - const CoordinateSystem3D& plane) : - DicomVolumeImageOrthogonalSlice(*that.volume_, plane), + ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that, + const CoordinateSystem3D& plane) : + DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), that_(that) { + if (that_.strategy_.get() != NULL && + IsValid() && + GetProjection() == VolumeProjection_Axial) + { + that_.strategy_->SetCurrent(GetSliceIndex()); + } } }; @@ -746,7 +820,9 @@ } } - + /** + This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags" + */ void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message) { Json::Value body; @@ -770,6 +846,7 @@ std::auto_ptr instance(new DicomInstanceParameters(dicom)); instance->SetOrthancInstanceIdentifier(instances[i]); + // the 3D plane corresponding to the slice CoordinateSystem3D geometry = instance->GetGeometry(); slices.AddSlice(geometry, instance.release()); } @@ -791,7 +868,7 @@ volume_->SetDicomParameters(parameters); volume_->GetPixelData().Clear(); - strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(slicesCount), BEST_QUALITY)); + strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast(slicesCount)), BEST_QUALITY)); assert(simultaneousDownloads_ != 0); for (unsigned int i = 0; i < simultaneousDownloads_; i++) @@ -933,16 +1010,7 @@ { if (volume_->HasGeometry()) { - std::auto_ptr slice(new Slice(*this, cuttingPlane)); - - if (strategy_.get() != NULL && - slice->IsValid() && - slice->GetProjection() == VolumeProjection_Axial) - { - strategy_->SetCurrent(slice->GetSliceIndex()); - } - - return slice.release(); + return new ExtractedSlice(*this, cuttingPlane); } else { @@ -952,44 +1020,196 @@ }; - - class OrthancMultiframeVolumeLoader : - public IObserver, - public IObservable, - public IVolumeSlicer + /** + This class is supplied with Oracle commands and will schedule up to + simultaneousDownloads_ of them at the same time, then will schedule the + rest once slots become available. It is used, a.o., by the + OrtancMultiframeVolumeLoader class. + */ + class LoaderStateMachine : public IObserver { - private: + protected: class State : public Orthanc::IDynamicObject { private: - OrthancMultiframeVolumeLoader& that_; - - protected: - void Schedule(OrthancRestApiCommand* command) const - { - that_.oracle_.Schedule(that_, command); - } - - OrthancMultiframeVolumeLoader& GetTarget() const - { - return that_; - } + LoaderStateMachine& that_; public: - State(OrthancMultiframeVolumeLoader& that) : + State(LoaderStateMachine& that) : that_(that) { } + + State(const State& currentState) : + that_(currentState.that_) + { + } + + void Schedule(OracleCommandWithPayload* command) const + { + that_.Schedule(command); + } + + template + T& GetLoader() const + { + return dynamic_cast(that_); + } - virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const = 0; + virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) const + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } }; - - void Handle(const OrthancRestApiCommand::SuccessMessage& message) + + void Schedule(OracleCommandWithPayload* command) + { + std::auto_ptr protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (!command->HasPayload()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "The payload must contain the next state"); + } + + pendingCommands_.push_back(protection.release()); + Step(); + } + + void Start() + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + for (size_t i = 0; i < simultaneousDownloads_; i++) + { + Step(); + } + } + + private: + void Step() + { + if (!pendingCommands_.empty() && + activeCommands_ < simultaneousDownloads_) + { + oracle_.Schedule(*this, pendingCommands_.front()); + pendingCommands_.pop_front(); + + activeCommands_++; + } + } + + void Clear() + { + for (PendingCommands::iterator it = pendingCommands_.begin(); + it != pendingCommands_.end(); ++it) + { + delete *it; + } + } + + void HandleException(const OracleCommandExceptionMessage& message) + { + LOG(ERROR) << "Error in the state machine, stopping all processing"; + Clear(); + } + + template + void Handle(const T& message) { dynamic_cast(message.GetOrigin().GetPayload()).Handle(message); + activeCommands_--; + Step(); } + typedef std::list PendingCommands; + IOracle& oracle_; + bool active_; + unsigned int simultaneousDownloads_; + PendingCommands pendingCommands_; + unsigned int activeCommands_; + + public: + LoaderStateMachine(IOracle& oracle, + IObservable& oracleObservable) : + IObserver(oracleObservable.GetBroker()), + oracle_(oracle), + active_(false), + simultaneousDownloads_(4), + activeCommands_(0) + { + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &LoaderStateMachine::Handle)); + + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &LoaderStateMachine::Handle)); + + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &LoaderStateMachine::Handle)); + + oracleObservable.RegisterObserverCallback( + new Callable + (*this, &LoaderStateMachine::HandleException)); + } + + virtual ~LoaderStateMachine() + { + Clear(); + } + + bool IsActive() const + { + return active_; + } + + void SetSimultaneousDownloads(unsigned int count) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (count == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + simultaneousDownloads_ = count; + } + } + }; + + + + class OrthancMultiframeVolumeLoader : + public LoaderStateMachine, + public IObservable + { + private: class LoadRTDoseGeometry : public State { private: @@ -1005,6 +1225,7 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } + } virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const @@ -1013,7 +1234,7 @@ std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer()); dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false); - GetTarget().SetGeometry(*dicom_); + GetLoader().SetGeometry(*dicom_); } }; @@ -1043,6 +1264,8 @@ virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { + OrthancMultiframeVolumeLoader& loader = GetLoader(); + Json::Value body; message.ParseJsonBody(body); @@ -1060,15 +1283,15 @@ // mandatory for RT-DOSE, but is too long to be returned by default std::auto_ptr command(new OrthancRestApiCommand); - command->SetUri("/instances/" + GetTarget().GetInstanceId() + "/content/" + + command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" + Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format()); - command->SetPayload(new LoadRTDoseGeometry(GetTarget(), dicom.release())); + command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release())); Schedule(command.release()); } else { - GetTarget().SetGeometry(*dicom); + loader.SetGeometry(*dicom); } } }; @@ -1085,7 +1308,7 @@ virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { - GetTarget().SetTransferSyntax(message.GetAnswer()); + GetLoader().SetTransferSyntax(message.GetAnswer()); } }; @@ -1100,22 +1323,20 @@ virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message) const { - GetTarget().SetUncompressedPixelData(message.GetAnswer()); + GetLoader().SetUncompressedPixelData(message.GetAnswer()); } }; boost::shared_ptr volume_; - IOracle& oracle_; - bool active_; std::string instanceId_; std::string transferSyntaxUid_; const std::string& GetInstanceId() const { - if (active_) + if (IsActive()) { return instanceId_; } @@ -1133,7 +1354,13 @@ { return; } - + /* + 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM + 1.2.840.10008.1.2.1 Explicit VR Little Endian + 1.2.840.10008.1.2.2 Explicit VR Big Endian + + See https://www.dicomlibrary.com/dicom/transfer-syntax/ + */ if (transferSyntaxUid_ == "1.2.840.10008.1.2" || transferSyntaxUid_ == "1.2.840.10008.1.2.1" || transferSyntaxUid_ == "1.2.840.10008.1.2.2") @@ -1143,7 +1370,7 @@ command->SetUri("/instances/" + instanceId_ + "/content/" + Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0"); command->SetPayload(new LoadUncompressedPixelData(*this)); - oracle_.Schedule(*this, command.release()); + Schedule(command.release()); } else { @@ -1220,7 +1447,6 @@ { ImageBuffer3D& target = volume_->GetPixelData(); - const Orthanc::PixelFormat format = target.GetFormat(); const unsigned int bpp = target.GetBytesPerPixel(); const unsigned int width = target.GetWidth(); const unsigned int height = target.GetHeight(); @@ -1248,7 +1474,7 @@ for (unsigned int y = 0; y < height; y++) { - assert(sizeof(T) == Orthanc::GetBytesPerPixel(format)); + assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat())); T* target = reinterpret_cast(writer.GetAccessor().GetRow(y)); @@ -1286,61 +1512,36 @@ OrthancMultiframeVolumeLoader(const boost::shared_ptr& volume, IOracle& oracle, IObservable& oracleObservable) : - IObserver(oracleObservable.GetBroker()), + LoaderStateMachine(oracle, oracleObservable), IObservable(oracleObservable.GetBroker()), - volume_(volume), - oracle_(oracle), - active_(false) + volume_(volume) { if (volume.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - - oracleObservable.RegisterObserverCallback( - new Callable - (*this, &OrthancMultiframeVolumeLoader::Handle)); } void LoadInstance(const std::string& instanceId) { - if (active_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - active_ = true; - instanceId_ = instanceId; + Start(); - { - std::auto_ptr command(new OrthancRestApiCommand); - command->SetHttpHeader("Accept-Encoding", "gzip"); - command->SetUri("/instances/" + instanceId + "/tags"); - command->SetPayload(new LoadGeometry(*this)); - oracle_.Schedule(*this, command.release()); - } + instanceId_ = instanceId; - { - std::auto_ptr command(new OrthancRestApiCommand); - command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); - command->SetPayload(new LoadTransferSyntax(*this)); - oracle_.Schedule(*this, command.release()); - } + { + std::auto_ptr command(new OrthancRestApiCommand); + command->SetHttpHeader("Accept-Encoding", "gzip"); + command->SetUri("/instances/" + instanceId + "/tags"); + command->SetPayload(new LoadGeometry(*this)); + Schedule(command.release()); } - } - - virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) - { - if (volume_->HasGeometry()) { - return new DicomVolumeImageOrthogonalSlice(*volume_, cuttingPlane); - } - else - { - return new IVolumeSlicer::InvalidSlice; + std::auto_ptr command(new OrthancRestApiCommand); + command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax"); + command->SetPayload(new LoadTransferSyntax(*this)); + Schedule(command.release()); } } }; @@ -1469,46 +1670,108 @@ public IVolumeSlicer { private: - enum State - { - State_Setup, - State_Loading, - State_Ready - }; - - std::auto_ptr content_; IOracle& oracle_; - State state_; + bool active_; + uint64_t revision_; std::string instanceId_; void Handle(const OrthancRestApiCommand::SuccessMessage& message) { - if (state_ != State_Loading) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - const boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); + assert(active_); { OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer()); content_.reset(new DicomStructureSet(dicom)); } - const boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + std::set instances; + content_->GetReferencedInstances(instances); + + for (std::set::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + printf("[%s]\n", it->c_str()); + } + } + + + class Slice : public IExtractedSlice + { + private: + const DicomStructureSet& content_; + uint64_t revision_; + bool isValid_; + + public: + Slice(const DicomStructureSet& content, + uint64_t revision, + const CoordinateSystem3D& cuttingPlane) : + content_(content), + revision_(revision) + { + bool opposite; + + const Vector normal = content.GetNormal(); + isValid_ = ( + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) || + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) || + GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY())); + } + + virtual bool IsValid() + { + return isValid_; + } - printf("LOADED: %d\n", (end - start).total_milliseconds()); - state_ = State_Ready; - } - + virtual uint64_t GetRevision() + { + return revision_; + } + + virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, + const CoordinateSystem3D& cuttingPlane) + { + assert(isValid_); + + std::auto_ptr layer(new PolylineSceneLayer); + + for (size_t i = 0; i < content_.GetStructuresCount(); i++) + { + std::vector< std::vector > polygons; + + if (content_.ProjectStructure(polygons, i, cuttingPlane)) + { + printf(">> %d\n", static_cast(polygons.size())); + + for (size_t j = 0; j < polygons.size(); j++) + { + PolylineSceneLayer::Chain chain; + chain.resize(polygons[j].size()); + + for (size_t k = 0; k < polygons[i].size(); k++) + { + chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second); + } + + layer->AddChain(chain, true /* closed */); + } + } + } + + printf("OK\n"); + + return layer.release(); + } + }; public: DicomStructureSetLoader(IOracle& oracle, IObservable& oracleObservable) : IObserver(oracleObservable.GetBroker()), oracle_(oracle), - state_(State_Setup) + active_(false), + revision_(0) { oracleObservable.RegisterObserverCallback( new Callable @@ -1518,13 +1781,13 @@ void LoadInstance(const std::string& instanceId) { - if (state_ != State_Setup) + if (active_) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } else { - state_ = State_Loading; + active_ = true; instanceId_ = instanceId; { @@ -1538,7 +1801,15 @@ virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) { - return NULL; + if (content_.get() == NULL) + { + // Geometry is not available yet + return new IVolumeSlicer::InvalidSlice; + } + else + { + return new Slice(*content_, revision_, cuttingPlane); + } } }; @@ -1759,7 +2030,7 @@ OrthancStone::CoordinateSystem3D plane_; OrthancStone::IOracle& oracle_; OrthancStone::Scene2D scene_; - std::auto_ptr source1_, source2_; + std::auto_ptr source1_, source2_, source3_; void Refresh() @@ -1774,6 +2045,11 @@ source2_->Update(plane_); } + if (source3_.get() != NULL) + { + source3_->Update(plane_); + } + scene_.FitContent(1024, 768); { @@ -1923,6 +2199,13 @@ source2_->SetConfigurator(style); } } + + void SetStructureSet(int depth, + const boost::shared_ptr& volume) + { + source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); + } + }; @@ -1947,7 +2230,8 @@ } - toto->SetReferenceLoader(*ctLoader); + //toto->SetReferenceLoader(*ctLoader); + toto->SetReferenceLoader(*doseLoader); #if 1 @@ -1963,10 +2247,14 @@ { std::auto_ptr config(new OrthancStone::LookupTableStyleConfigurator); config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); - toto->SetVolume2(1, doseLoader, config.release()); + + boost::shared_ptr tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose)); + toto->SetVolume2(1, tmp, config.release()); } - //oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); + toto->SetStructureSet(2, rtstructLoader); + + oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); if (0) { @@ -2045,10 +2333,16 @@ // 2017-11-17-Anonymized +#if 0 + // BGO data + ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT + doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // RT-DOSE + //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT +#else //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT - //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE - rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT - + doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT +#endif // 2015-01-28-Multiframe //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279"); // Multiframe CT diff -r 20262f5e5e88 -r 7efcbae4717e UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Tue May 28 11:37:50 2019 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Tue May 28 14:15:03 2019 +0200 @@ -23,10 +23,10 @@ #include "../Framework/Deprecated/Layers/FrameRenderer.h" #include "../Framework/Deprecated/Toolbox/DownloadStack.h" +#include "../Framework/Deprecated/Toolbox/MessagingToolbox.h" #include "../Framework/Deprecated/Toolbox/OrthancSlicesLoader.h" #include "../Framework/Toolbox/FiniteProjectiveCamera.h" #include "../Framework/Toolbox/GeometryToolbox.h" -#include "../Framework/Toolbox/MessagingToolbox.h" #include "../Framework/Volumes/ImageBuffer3D.h" #include "../Platforms/Generic/OracleWebService.h" @@ -636,7 +636,7 @@ { Json::Value response; std::string source = "{\"command\":\"panel:takeDarkImage\",\"commandType\":\"simple\",\"args\":{}}"; - ASSERT_TRUE(OrthancStone::MessagingToolbox::ParseJson(response, source.c_str(), source.size())); + ASSERT_TRUE(Deprecated::MessagingToolbox::ParseJson(response, source.c_str(), source.size())); } TEST(VolumeImageGeometry, Basic)