# HG changeset patch # User Sebastien Jodogne # Date 1506610551 -7200 # Node ID 42c05a3baee3a1843d61eeb9d2b5131d023124aa # Parent 4c5f7cda8624f697fe9567217f2b66f0d6f2f28d loading multi-frame instances as 3D volumes diff -r 4c5f7cda8624 -r 42c05a3baee3 Applications/BasicApplicationContext.cpp --- a/Applications/BasicApplicationContext.cpp Mon Sep 25 13:43:47 2017 +0200 +++ b/Applications/BasicApplicationContext.cpp Thu Sep 28 16:55:51 2017 +0200 @@ -41,6 +41,7 @@ BasicApplicationContext::BasicApplicationContext(Orthanc::WebServiceParameters& orthanc) : oracle_(viewportMutex_, 4), // Use 4 threads to download + //oracle_(viewportMutex_, 1), // Disable threading to be reproducible webService_(oracle_, orthanc), stopped_(true), updateDelay_(100) // By default, 100ms between each refresh of the content diff -r 4c5f7cda8624 -r 42c05a3baee3 Applications/Samples/SingleFrameApplication.h --- a/Applications/Samples/SingleFrameApplication.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Applications/Samples/SingleFrameApplication.h Thu Sep 28 16:55:51 2017 +0200 @@ -252,7 +252,7 @@ std::auto_ptr layer (new OrthancFrameLayerSource(context.GetWebService())); //layer->SetImageQuality(SliceImageQuality_Jpeg50); - layer->LoadInstance(instance, frame); + layer->LoadFrame(instance, frame); //layer->LoadSeries("6f1b492a-e181e200-44e51840-ef8db55e-af529ab6"); layer->Register(*this); source_ = layer.get(); diff -r 4c5f7cda8624 -r 42c05a3baee3 Applications/Samples/SingleVolumeApplication.h --- a/Applications/Samples/SingleVolumeApplication.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Applications/Samples/SingleVolumeApplication.h Thu Sep 28 16:55:51 2017 +0200 @@ -45,6 +45,8 @@ generic.add_options() ("series", boost::program_options::value(), "Orthanc ID of the series") + ("instance", boost::program_options::value(), + "Orthanc ID of a multi-frame instance that describes a 3D volume") ("threads", boost::program_options::value()->default_value(3), "Number of download threads") ("projection", boost::program_options::value()->default_value("axial"), @@ -62,13 +64,39 @@ { using namespace OrthancStone; - if (parameters.count("series") != 1) + if (parameters.count("series") > 1 || + parameters.count("instance") > 1) { - LOG(ERROR) << "The series ID is missing"; + LOG(ERROR) << "Only one series or instance is allowed"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (parameters.count("series") == 1 && + parameters.count("instance") == 1) + { + LOG(ERROR) << "Cannot specify both a series and an instance"; throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - std::string series = parameters["series"].as(); + std::string series; + if (parameters.count("series") == 1) + { + series = parameters["series"].as(); + } + + std::string instance; + if (parameters.count("instance") == 1) + { + instance = parameters["instance"].as(); + } + + if (series.empty() && + instance.empty()) + { + LOG(ERROR) << "The series ID or instance ID is missing"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + unsigned int threads = parameters["threads"].as(); bool reverse = parameters["reverse"].as(); @@ -98,7 +126,14 @@ #if 1 std::auto_ptr volume(new OrthancVolumeImage(context.GetWebService())); - volume->ScheduleLoadSeries(series); + if (series.empty()) + { + volume->ScheduleLoadInstance(instance); + } + else + { + volume->ScheduleLoadSeries(series); + } widget->AddLayer(new VolumeImageSource(*volume)); diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Enumerations.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Enumerations.cpp Thu Sep 28 16:55:51 2017 +0200 @@ -0,0 +1,42 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, 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 "Enumerations.h" + +#include + +namespace OrthancStone +{ + bool StringToSopClassUid(SopClassUid& result, + const std::string& source) + { + if (source == "1.2.840.10008.5.1.4.1.1.481.2") + { + result = SopClassUid_RTDose; + return true; + } + else + { + //LOG(INFO) << "Unknown SOP class UID: " << source; + return false; + } + } +} diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Enumerations.h --- a/Framework/Enumerations.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Enumerations.h Thu Sep 28 16:55:51 2017 +0200 @@ -21,6 +21,8 @@ #pragma once +#include + namespace OrthancStone { enum SliceOffsetMode @@ -79,4 +81,12 @@ SliceImageQuality_Jpeg90, SliceImageQuality_Jpeg95 }; + + enum SopClassUid + { + SopClassUid_RTDose + }; + + bool StringToSopClassUid(SopClassUid& result, + const std::string& source); } diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Layers/OrthancFrameLayerSource.cpp --- a/Framework/Layers/OrthancFrameLayerSource.cpp Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.cpp Thu Sep 28 16:55:51 2017 +0200 @@ -75,16 +75,22 @@ } - void OrthancFrameLayerSource::LoadInstance(const std::string& instanceId, - unsigned int frame) + void OrthancFrameLayerSource::LoadSeries(const std::string& seriesId) { - loader_.ScheduleLoadInstance(instanceId, frame); + loader_.ScheduleLoadSeries(seriesId); } - void OrthancFrameLayerSource::LoadSeries(const std::string& seriesId) + void OrthancFrameLayerSource::LoadInstance(const std::string& instanceId) { - loader_.ScheduleLoadSeries(seriesId); + loader_.ScheduleLoadInstance(instanceId); + } + + + void OrthancFrameLayerSource::LoadFrame(const std::string& instanceId, + unsigned int frame) + { + loader_.ScheduleLoadFrame(instanceId, frame); } diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Layers/OrthancFrameLayerSource.h --- a/Framework/Layers/OrthancFrameLayerSource.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Layers/OrthancFrameLayerSource.h Thu Sep 28 16:55:51 2017 +0200 @@ -53,10 +53,12 @@ public: OrthancFrameLayerSource(IWebService& orthanc); - void LoadInstance(const std::string& instanceId, - unsigned int frame); + void LoadSeries(const std::string& seriesId); - void LoadSeries(const std::string& seriesId); + void LoadInstance(const std::string& instanceId); + + void LoadFrame(const std::string& instanceId, + unsigned int frame); void SetImageQuality(SliceImageQuality quality) { diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Toolbox/OrthancSlicesLoader.cpp Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Thu Sep 28 16:55:51 2017 +0200 @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -96,25 +97,29 @@ unsigned int GetSliceIndex() const { - assert(mode_ == Mode_LoadImage); + assert(mode_ == Mode_LoadImage || + mode_ == Mode_LoadRawImage); return sliceIndex_; } const Slice& GetSlice() const { - assert(mode_ == Mode_LoadImage && slice_ != NULL); + assert(mode_ == Mode_LoadImage || + mode_ == Mode_LoadRawImage); + assert(slice_ != NULL); return *slice_; } unsigned int GetFrame() const { - assert(mode_ == Mode_InstanceGeometry); + assert(mode_ == Mode_FrameGeometry); return frame_; } const std::string& GetInstanceId() const { - assert(mode_ == Mode_InstanceGeometry); + assert(mode_ == Mode_FrameGeometry || + mode_ == Mode_InstanceGeometry); return instanceId_; } @@ -123,11 +128,18 @@ return new Operation(Mode_SeriesGeometry); } - static Operation* DownloadInstanceGeometry(const std::string& instanceId, - unsigned int frame) + static Operation* DownloadInstanceGeometry(const std::string& instanceId) { std::auto_ptr operation(new Operation(Mode_InstanceGeometry)); operation->instanceId_ = instanceId; + return operation.release(); + } + + static Operation* DownloadFrameGeometry(const std::string& instanceId, + unsigned int frame) + { + std::auto_ptr operation(new Operation(Mode_FrameGeometry)); + operation->instanceId_ = instanceId; operation->frame_ = frame; return operation.release(); } @@ -142,6 +154,15 @@ tmp->quality_ = quality; return tmp.release(); } + + static Operation* DownloadSliceRawImage(unsigned int sliceIndex, + const Slice& slice) + { + std::auto_ptr tmp(new Operation(Mode_LoadRawImage)); + tmp->sliceIndex_ = sliceIndex; + tmp->slice_ = &slice; + return tmp.release(); + } }; @@ -170,8 +191,12 @@ break; case Mode_InstanceGeometry: - that_.ParseInstanceGeometry(operation->GetInstanceId(), - operation->GetFrame(), answer, answerSize); + that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize); + break; + + case Mode_FrameGeometry: + that_.ParseFrameGeometry(operation->GetInstanceId(), + operation->GetFrame(), answer, answerSize); break; case Mode_LoadImage: @@ -193,6 +218,10 @@ break; + case Mode_LoadRawImage: + that_.ParseSliceRawImage(*operation, answer, answerSize); + break; + default: throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } @@ -206,7 +235,7 @@ switch (operation->GetMode()) { - case Mode_InstanceGeometry: + case Mode_FrameGeometry: case Mode_SeriesGeometry: that_.userCallback_.NotifyGeometryError(that_); that_.state_ = State_Error; @@ -266,15 +295,25 @@ for (size_t i = 0; i < instances.size(); i++) { OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]); + OrthancPlugins::DicomDatasetReader reader(dataset); + + unsigned int frames; + if (!reader.GetUnsignedIntegerValue(frames, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES)) + { + frames = 1; + } - Slice slice; - if (slice.ParseOrthancFrame(dataset, instances[i], 0 /* todo */)) + for (unsigned int frame = 0; frame < frames; frame++) { - slices_.AddSlice(slice); - } - else - { - LOG(WARNING) << "Skipping invalid instance " << instances[i]; + Slice slice; + if (slice.ParseOrthancFrame(dataset, instances[i], frame)) + { + slices_.AddSlice(slice); + } + else + { + LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i]; + } } } @@ -308,7 +347,6 @@ void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId, - unsigned int frame, const void* answer, size_t size) { @@ -321,6 +359,51 @@ } OrthancPlugins::FullOrthancDataset dataset(tags); + OrthancPlugins::DicomDatasetReader reader(dataset); + + unsigned int frames; + if (!reader.GetUnsignedIntegerValue(frames, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES)) + { + frames = 1; + } + + LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)"; + + state_ = State_GeometryReady; + + for (unsigned int frame = 0; frame < frames; frame++) + { + Slice slice; + if (slice.ParseOrthancFrame(dataset, instanceId, frame)) + { + slices_.AddSlice(slice); + } + else + { + LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; + userCallback_.NotifyGeometryError(*this); + return; + } + } + + userCallback_.NotifyGeometryReady(*this); + } + + + void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId, + unsigned int frame, + const void* answer, + size_t size) + { + Json::Value tags; + if (!MessagingToolbox::ParseJson(tags, answer, size) || + tags.type() != Json::objectValue) + { + userCallback_.NotifyGeometryError(*this); + return; + } + + OrthancPlugins::FullOrthancDataset dataset(tags); state_ = State_GeometryReady; @@ -526,6 +609,19 @@ } + void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation, + const void* answer, + size_t size) + { + Orthanc::GzipCompressor compressor; + + std::string raw; + compressor.Uncompress(raw, answer, size); + + printf("[%d => %d]\n", size, raw.size()); + } + + OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback, IWebService& orthanc) : webCallback_(new WebCallback(*this)), @@ -551,8 +647,27 @@ } - void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId, - unsigned int frame) + void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId) + { + if (state_ != State_Initialization) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_LoadingGeometry; + + // Tag "3004-000c" is "Grid Frame Offset Vector", which is + // mandatory to read RT DOSE, but is too long to be returned by default + std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c"; + orthanc_.ScheduleGetRequest + (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId)); + } + } + + + void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame) { if (state_ != State_Initialization) { @@ -563,7 +678,7 @@ state_ = State_LoadingGeometry; std::string uri = "/instances/" + instanceId + "/tags"; orthanc_.ScheduleGetRequest - (*webCallback_, uri, Operation::DownloadInstanceGeometry(instanceId, frame)); + (*webCallback_, uri, Operation::DownloadFrameGeometry(instanceId, frame)); } } @@ -608,10 +723,9 @@ } - void OrthancSlicesLoader::ScheduleSliceImagePng(size_t index) + void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice, + size_t index) { - const Slice& slice = GetSlice(index); - std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast(slice.GetFrame())); @@ -638,7 +752,8 @@ } - void OrthancSlicesLoader::ScheduleSliceImageJpeg(size_t index, + void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice, + size_t index, SliceImageQuality quality) { unsigned int value; @@ -662,7 +777,6 @@ } // This requires the official Web viewer plugin to be installed! - const Slice& slice = GetSlice(index); std::string uri = ("/web-viewer/instances/jpeg" + boost::lexical_cast(value) + "-" + slice.GetOrthancInstanceId() + "_" + @@ -673,6 +787,7 @@ } + void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index, SliceImageQuality quality) { @@ -681,13 +796,25 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - if (quality == SliceImageQuality_Full) + const Slice& slice = GetSlice(index); + + if (slice.HasOrthancDecoding()) { - ScheduleSliceImagePng(index); + if (quality == SliceImageQuality_Full) + { + ScheduleSliceImagePng(slice, index); + } + else + { + ScheduleSliceImageJpeg(slice, index, quality); + } } else { - ScheduleSliceImageJpeg(index, quality); + std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + + boost::lexical_cast(slice.GetFrame()) + "/raw.gz"); + orthanc_.ScheduleGetRequest(*webCallback_, uri, + Operation::DownloadSliceRawImage(index, slice)); } } } diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Toolbox/OrthancSlicesLoader.h --- a/Framework/Toolbox/OrthancSlicesLoader.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.h Thu Sep 28 16:55:51 2017 +0200 @@ -47,7 +47,7 @@ unsigned int sliceIndex, const Slice& slice, std::auto_ptr& image, - SliceImageQuality quality) = 0; + SliceImageQuality effectiveQuality) = 0; virtual void NotifySliceImageError(const OrthancSlicesLoader& loader, unsigned int sliceIndex, @@ -68,7 +68,9 @@ { Mode_SeriesGeometry, Mode_InstanceGeometry, - Mode_LoadImage + Mode_FrameGeometry, + Mode_LoadImage, + Mode_LoadRawImage }; class Operation; @@ -90,10 +92,14 @@ size_t size); void ParseInstanceGeometry(const std::string& instanceId, - unsigned int frame, const void* answer, size_t size); + void ParseFrameGeometry(const std::string& instanceId, + unsigned int frame, + const void* answer, + size_t size); + void ParseSliceImagePng(const Operation& operation, const void* answer, size_t size); @@ -102,9 +108,15 @@ const void* answer, size_t size); - void ScheduleSliceImagePng(size_t index); + void ParseSliceRawImage(const Operation& operation, + const void* answer, + size_t size); + + void ScheduleSliceImagePng(const Slice& slice, + size_t index); - void ScheduleSliceImageJpeg(size_t index, + void ScheduleSliceImageJpeg(const Slice& slice, + size_t index, SliceImageQuality quality); public: @@ -113,8 +125,10 @@ void ScheduleLoadSeries(const std::string& seriesId); - void ScheduleLoadInstance(const std::string& instanceId, - unsigned int frame); + void ScheduleLoadInstance(const std::string& instanceId); + + void ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame); bool IsGeometryReady() const; @@ -126,6 +140,6 @@ const CoordinateSystem3D& plane) const; void ScheduleLoadSliceImage(size_t index, - SliceImageQuality quality); + SliceImageQuality requestedQuality); }; } diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Toolbox/Slice.cpp --- a/Framework/Toolbox/Slice.cpp Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Toolbox/Slice.cpp Thu Sep 28 16:55:51 2017 +0200 @@ -21,32 +21,135 @@ #include "Slice.h" +#include "../Enumerations.h" + +#include #include +#include + +#include namespace OrthancStone { + static bool ParseDouble(double& target, + const std::string& source) + { + try + { + target = boost::lexical_cast(source); + return true; + } + catch (boost::bad_lexical_cast&) + { + return false; + } + } + + bool Slice::ComputeRTDoseGeometry(const OrthancPlugins::DicomDatasetReader& reader, + unsigned int frame) + { + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html + static const OrthancPlugins::DicomTag DICOM_TAG_GRID_FRAME_OFFSET_VECTOR(0x3004, 0x000c); + static const OrthancPlugins::DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009); + + std::string increment = reader.GetStringValue(DICOM_TAG_FRAME_INCREMENT_POINTER, ""); + std::string offsetTag; + + bool ok = reader.GetDataset().GetStringValue(offsetTag, DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); + if (!ok) + { + LOG(ERROR) << "Cannot read the \"GridFrameOffsetVector\" tag, check you are using Orthanc >= 1.3.1"; + return false; + } + + Orthanc::Toolbox::ToUpperCase(increment); + if (increment != "3004,000C" || + offsetTag.empty()) + { + return false; + } + + std::vector offsets; + Orthanc::Toolbox::TokenizeString(offsets, offsetTag, '\\'); + + if (frameCount_ == 0 || + offsets.size() != frameCount_ || + frame >= frameCount_) + { + LOG(ERROR) << "No information about the 3D location of some slice(s) in a RT DOSE"; + return false; + } + + double offset0, z; + + if (!ParseDouble(offset0, offsets[0]) || + !ParseDouble(z, offsets[frame])) + { + LOG(ERROR) << "Invalid syntax"; + return false; + } + + if (!GeometryToolbox::IsCloseToZero(offset0)) + { + LOG(ERROR) << "Invalid syntax"; + return false; + } + + geometry_ = CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(), + geometry_.GetAxisX(), + geometry_.GetAxisY()); + + return true; + } + + bool Slice::ParseOrthancFrame(const OrthancPlugins::IDicomDataset& dataset, const std::string& instanceId, unsigned int frame) { + orthancInstanceId_ = instanceId; + frame_ = frame; + type_ = Type_OrthancDecodableFrame; + OrthancPlugins::DicomDatasetReader reader(dataset); - unsigned int frameCount; - if (!reader.GetUnsignedIntegerValue(frameCount, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES)) + sopClassUid_ = reader.GetStringValue(OrthancPlugins::DICOM_TAG_SOP_CLASS_UID, ""); + if (sopClassUid_.empty()) { - frameCount = 1; // Assume instance with one frame + LOG(ERROR) << "Instance without a SOP class UID"; + return false; + } + + if (!reader.GetUnsignedIntegerValue(frameCount_, OrthancPlugins::DICOM_TAG_NUMBER_OF_FRAMES)) + { + frameCount_ = 1; // Assume instance with one frame } - if (frame >= frameCount) + if (frame >= frameCount_) { return false; } - if (!reader.GetDoubleValue(thickness_, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS)) + if (!reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) || + !reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS)) { - thickness_ = 100.0 * std::numeric_limits::epsilon(); + return false; } + thickness_ = 100.0 * std::numeric_limits::epsilon(); + + std::string tmp; + if (dataset.GetStringValue(tmp, OrthancPlugins::DICOM_TAG_SLICE_THICKNESS)) + { + if (!tmp.empty() && + !ParseDouble(thickness_, tmp)) + { + return false; // Syntax error + } + } + + converter_.ReadParameters(dataset); + GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset); std::string position, orientation; @@ -54,33 +157,47 @@ dataset.GetStringValue(orientation, OrthancPlugins::DICOM_TAG_IMAGE_ORIENTATION_PATIENT)) { geometry_ = CoordinateSystem3D(position, orientation); + + bool ok = true; + SopClassUid tmp; + + if (StringToSopClassUid(tmp, sopClassUid_)) + { + switch (tmp) + { + case SopClassUid_RTDose: + type_ = Type_OrthancRawFrame; + ok = ComputeRTDoseGeometry(reader, frame); + break; + + default: + break; + } + } + + if (!ok) + { + LOG(ERROR) << "Cannot deduce the 3D location of frame " << frame + << " in instance " << instanceId << ", whose SOP class UID is: " << sopClassUid_; + return false; + } } - - if (reader.GetUnsignedIntegerValue(width_, OrthancPlugins::DICOM_TAG_COLUMNS) && - reader.GetUnsignedIntegerValue(height_, OrthancPlugins::DICOM_TAG_ROWS)) - { - orthancInstanceId_ = instanceId; - frame_ = frame; - converter_.ReadParameters(dataset); - type_ = Type_OrthancInstance; - return true; - } - else - { - return false; - } + return true; } const std::string Slice::GetOrthancInstanceId() const { - if (type_ != Type_OrthancInstance) + if (type_ == Type_OrthancDecodableFrame || + type_ == Type_OrthancRawFrame) + { + return orthancInstanceId_; + } + else { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - return orthancInstanceId_; + } } diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/Toolbox/Slice.h --- a/Framework/Toolbox/Slice.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/Toolbox/Slice.h Thu Sep 28 16:55:51 2017 +0200 @@ -33,13 +33,19 @@ { Type_Invalid, Type_Standalone, - Type_OrthancInstance + Type_OrthancDecodableFrame, + Type_OrthancRawFrame // TODO A slice could come from some DICOM file (URL) }; + bool ComputeRTDoseGeometry(const OrthancPlugins::DicomDatasetReader& reader, + unsigned int frame); + Type type_; std::string orthancInstanceId_; + std::string sopClassUid_; unsigned int frame_; + unsigned int frameCount_; CoordinateSystem3D geometry_; double pixelSpacingX_; double pixelSpacingY_; @@ -59,6 +65,7 @@ double thickness) : type_(Type_Standalone), frame_(0), + frameCount_(0), geometry_(plane), pixelSpacingX_(1), pixelSpacingY_(1), @@ -76,6 +83,7 @@ unsigned int height, const DicomFrameConverter& converter) : type_(Type_Standalone), + frameCount_(1), geometry_(plane), pixelSpacingX_(pixelSpacingX), pixelSpacingY_(pixelSpacingY), @@ -95,9 +103,9 @@ const std::string& instanceId, unsigned int frame); - bool IsOrthancInstance() const + bool HasOrthancDecoding() const { - return type_ == Type_OrthancInstance; + return type_ == Type_OrthancDecodableFrame; } const std::string GetOrthancInstanceId() const; diff -r 4c5f7cda8624 -r 42c05a3baee3 Framework/dev.h --- a/Framework/dev.h Mon Sep 25 13:43:47 2017 +0200 +++ b/Framework/dev.h Thu Sep 28 16:55:51 2017 +0200 @@ -211,10 +211,15 @@ loader_.ScheduleLoadSeries(seriesId); } - void ScheduleLoadInstance(const std::string& instanceId, - unsigned int frame) + void ScheduleLoadInstance(const std::string& instanceId) { - loader_.ScheduleLoadInstance(instanceId, frame); + loader_.ScheduleLoadInstance(instanceId); + } + + void ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame) + { + loader_.ScheduleLoadFrame(instanceId, frame); } virtual size_t GetSliceCount() const diff -r 4c5f7cda8624 -r 42c05a3baee3 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Mon Sep 25 13:43:47 2017 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu Sep 28 16:55:51 2017 +0200 @@ -164,6 +164,7 @@ #${ORTHANC_STONE_DIR}/Framework/Layers/SeriesFrameRendererFactory.cpp #${ORTHANC_STONE_DIR}/Framework/Layers/SiblingSliceLocationFactory.cpp #${ORTHANC_STONE_DIR}/Framework/Layers/SingleFrameRendererFactory.cpp + ${ORTHANC_STONE_DIR}/Framework/Enumerations.cpp ${ORTHANC_STONE_DIR}/Framework/Layers/CircleMeasureTracker.cpp ${ORTHANC_STONE_DIR}/Framework/Layers/ColorFrameRenderer.cpp ${ORTHANC_STONE_DIR}/Framework/Layers/FrameRenderer.cpp