# HG changeset patch # User am@osimis.io # Date 1542728129 -3600 # Node ID f87f28624b96972a98ab44f56cf224958f742b36 # Parent 8bf717c4e4975c3682565b91a54b831437b1f209 tentative to make SmartLoader and RadiographyScene work together (not really working) diff -r 8bf717c4e497 -r f87f28624b96 Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Applications/Samples/SingleFrameEditorApplication.h Tue Nov 20 16:35:29 2018 +0100 @@ -31,6 +31,7 @@ #include "../../Framework/Radiography/RadiographySceneCommand.h" #include "../../Framework/Radiography/RadiographyWidget.h" #include "../../Framework/Radiography/RadiographyWindowingTracker.h" +#include "../../Framework/SmartLoader.h" #include #include @@ -382,6 +383,7 @@ private: std::auto_ptr scene_; RadiographyEditorInteractor interactor_; + std::auto_ptr smartLoader_; public: SingleFrameEditorApplication(MessageBroker& broker) : @@ -443,9 +445,13 @@ Orthanc::FontRegistry fonts; fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); + smartLoader_.reset(new SmartLoader(IObserver::GetBroker(), context->GetOrthancApiClient())); + smartLoader_->SetImageQuality(SliceImageQuality_FullPam); + scene_.reset(new RadiographyScene(GetBroker())); //scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); - scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false); + //scene_->LoadDicomFrame(context->GetOrthancApiClient(), "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false); + smartLoader_->SetFrameInRadiographyScene(*scene_, "61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0); #if !defined(ORTHANC_ENABLE_WASM) || ORTHANC_ENABLE_WASM != 1 Orthanc::HttpClient::ConfigureSsl(true, "/etc/ssl/certs/ca-certificates.crt"); diff -r 8bf717c4e497 -r f87f28624b96 Framework/Layers/DicomSeriesVolumeSlicer.cpp --- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.cpp Tue Nov 20 16:35:29 2018 +0100 @@ -32,6 +32,11 @@ namespace OrthancStone { + void DicomSeriesVolumeSlicer::OnSliceTagsReady(const OrthancSlicesLoader::SliceTagsReadyMessage& message) + { + EmitMessage(IVolumeSlicer::TagsReadyMessage(*this, message.GetDicomTags())); + } + void DicomSeriesVolumeSlicer::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) { if (message.GetOrigin().GetSliceCount() > 0) @@ -94,6 +99,7 @@ quality_(SliceImageQuality_FullPng) { loader_.RegisterObserverCallback(new Callable(*this, &DicomSeriesVolumeSlicer::OnSliceGeometryReady)); + loader_.RegisterObserverCallback(new Callable(*this, &DicomSeriesVolumeSlicer::OnSliceTagsReady)); loader_.RegisterObserverCallback(new Callable(*this, &DicomSeriesVolumeSlicer::OnSliceGeometryError)); loader_.RegisterObserverCallback(new Callable(*this, &DicomSeriesVolumeSlicer::OnSliceImageReady)); loader_.RegisterObserverCallback(new Callable(*this, &DicomSeriesVolumeSlicer::OnSliceImageError)); diff -r 8bf717c4e497 -r f87f28624b96 Framework/Layers/DicomSeriesVolumeSlicer.h --- a/Framework/Layers/DicomSeriesVolumeSlicer.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.h Tue Nov 20 16:35:29 2018 +0100 @@ -117,6 +117,7 @@ protected: void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message); + void OnSliceTagsReady(const OrthancSlicesLoader::SliceTagsReadyMessage& message); void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message); void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message); void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message); diff -r 8bf717c4e497 -r f87f28624b96 Framework/Layers/IVolumeSlicer.h --- a/Framework/Layers/IVolumeSlicer.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Layers/IVolumeSlicer.h Tue Nov 20 16:35:29 2018 +0100 @@ -26,10 +26,12 @@ #include "../../Framework/Messages/IObservable.h" #include "../../Framework/Messages/IMessage.h" #include "Core/Images/Image.h" +#include #include namespace OrthancStone { + class IVolumeSlicer : public IObservable { public: @@ -37,6 +39,41 @@ typedef OriginMessage GeometryErrorMessage; typedef OriginMessage ContentChangedMessage; + class TagsReadyMessage : public OriginMessage + { + private: + const OrthancPlugins::FullOrthancDataset& dicomTags_; + public: + TagsReadyMessage(IVolumeSlicer& origin, + const OrthancPlugins::FullOrthancDataset& dicomTags) : + OriginMessage(origin), + dicomTags_(dicomTags) + { + } + + const OrthancPlugins::FullOrthancDataset& GetDicomTags() const + { + return dicomTags_; + } + }; + + class FrameReadyMessage : public OriginMessage + { + private: + boost::shared_ptr image_; + public: + FrameReadyMessage(IVolumeSlicer& origin, boost::shared_ptr image) : + OriginMessage(origin), + image_(image) + { + } + + boost::shared_ptr GetImage() const + { + return image_; + } + }; + class SliceContentChangedMessage : public OriginMessage { private: @@ -44,7 +81,7 @@ public: SliceContentChangedMessage(IVolumeSlicer& origin, - const Slice& slice) : + const Slice& slice) : OriginMessage(origin), slice_(slice) { @@ -69,7 +106,7 @@ virtual ILayerRenderer* CreateRenderer() const = 0; }; - + private: const IRendererFactory& factory_; const CoordinateSystem3D& slice_; diff -r 8bf717c4e497 -r f87f28624b96 Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Tue Nov 20 16:35:29 2018 +0100 @@ -55,7 +55,7 @@ } } - + RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, double x, double y) : @@ -65,7 +65,7 @@ if (scene.LookupLayer(index_, x, y)) { Layers::iterator layer = scene.layers_.find(index_); - + if (layer == scene.layers_.end()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); @@ -119,7 +119,7 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } - } + } @@ -145,12 +145,12 @@ useWindowing_ = false; foreground_ = foreground; } - - + + void SetAlpha(Orthanc::ImageAccessor* image) { std::auto_ptr raii(image); - + if (image == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); @@ -170,7 +170,7 @@ const std::string& utf8) { SetAlpha(font.RenderAlpha(utf8)); - } + } virtual bool GetDefaultWindowing(float& center, @@ -178,7 +178,7 @@ { return false; } - + virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, @@ -188,7 +188,7 @@ { return; } - + if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) { throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); @@ -198,12 +198,12 @@ GetCrop(cropX, cropY, cropWidth, cropHeight); const AffineTransform2D t = AffineTransform2D::Combine( - viewTransform, GetTransform(), - AffineTransform2D::CreateOffset(cropX, cropY)); + viewTransform, GetTransform(), + AffineTransform2D::CreateOffset(cropX, cropY)); Orthanc::ImageAccessor cropped; alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); - + Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); t.Apply(tmp, cropped, interpolation, true /* clear */); @@ -213,7 +213,7 @@ const unsigned int height = buffer.GetHeight(); float value = foreground_; - + if (useWindowing_) { float center, width; @@ -222,7 +222,7 @@ value = center + width / 2.0f; } } - + for (unsigned int y = 0; y < height; y++) { float *q = reinterpret_cast(buffer.GetRow(y)); @@ -231,13 +231,13 @@ for (unsigned int x = 0; x < width; x++, p++, q++) { float a = static_cast(*p) / 255.0f; - + *q = (a * value + (1.0f - a) * (*q)); } - } + } } - + virtual bool GetRange(float& minValue, float& maxValue) const { @@ -264,13 +264,13 @@ } } }; - - + + class RadiographyScene::DicomLayer : public RadiographyLayer { private: - std::auto_ptr source_; // Content of PixelData + boost::shared_ptr source_; // Content of PixelData std::auto_ptr converter_; std::auto_ptr converted_; // Float32 @@ -278,7 +278,7 @@ { return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); } - + void ApplyConverter() { @@ -288,7 +288,7 @@ converted_.reset(converter_->ConvertFrame(*source_)); } } - + public: void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) { @@ -298,7 +298,7 @@ std::string tmp; Vector pixelSpacing; - + if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && LinearAlgebra::ParseVector(pixelSpacing, tmp) && pixelSpacing.size() == 2) @@ -322,23 +322,21 @@ } } - - void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership + + void SetSourceImage(boost::shared_ptr image) { - std::auto_ptr raii(image); - - if (image == NULL) + if (image.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } SetSize(image->GetWidth(), image->GetHeight()); - - source_ = raii; + + source_ = image; ApplyConverter(); } - + virtual void Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, ImageInterpolation interpolation) const @@ -354,8 +352,8 @@ GetCrop(cropX, cropY, cropWidth, cropHeight); AffineTransform2D t = AffineTransform2D::Combine( - viewTransform, GetTransform(), - AffineTransform2D::CreateOffset(cropX, cropY)); + viewTransform, GetTransform(), + AffineTransform2D::CreateOffset(cropX, cropY)); Orthanc::ImageAccessor cropped; converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); @@ -411,7 +409,7 @@ } std::auto_ptr raii(layer); - + size_t index = countLayers_++; raii->SetIndex(index); layers_[index] = raii.release(); @@ -421,7 +419,7 @@ return *layer; } - + RadiographyScene::RadiographyScene(MessageBroker& broker) : IObserver(broker), @@ -489,7 +487,7 @@ return RegisterLayer(alpha.release()); } - + RadiographyLayer& RadiographyScene::LoadTestBlock(unsigned int width, unsigned int height) { @@ -520,7 +518,7 @@ return RegisterLayer(alpha.release()); } - + RadiographyLayer& RadiographyScene::LoadDicomFrame(OrthancApiClient& orthanc, const std::string& instance, unsigned int frame, @@ -531,12 +529,12 @@ { IWebService::HttpHeaders headers; std::string uri = "/instances/" + instance + "/tags"; - + orthanc.GetBinaryAsync( - uri, headers, - new Callable - (*this, &RadiographyScene::OnTagsReceived), NULL, - new Orthanc::SingleValueObject(layer.GetIndex())); + uri, headers, + new Callable + (*this, &RadiographyScene::OnTagsReceived), NULL, + new Orthanc::SingleValueObject(layer.GetIndex())); } { @@ -547,15 +545,15 @@ { headers["Accept-Encoding"] = "gzip"; } - + std::string uri = ("/instances/" + instance + "/frames/" + boost::lexical_cast(frame) + "/image-uint16"); - + orthanc.GetBinaryAsync( - uri, headers, - new Callable - (*this, &RadiographyScene::OnFrameReceived), NULL, - new Orthanc::SingleValueObject(layer.GetIndex())); + uri, headers, + new Callable + (*this, &RadiographyScene::OnFrameReceived), NULL, + new Orthanc::SingleValueObject(layer.GetIndex())); } return layer; @@ -566,25 +564,67 @@ { RadiographyLayer& layer = RegisterLayer(new DicomLayer); - + + return layer; + } + + RadiographyLayer& RadiographyScene::SetFrame(IVolumeSlicer* slice) + { + RadiographyLayer& layer = RegisterLayer(new DicomLayer); + layersIndexBySlice_[slice] = layer.GetIndex(); + + slice->RegisterObserverCallback(new Callable + (*this, &RadiographyScene::OnTagsReady)); + slice->RegisterObserverCallback(new Callable + (*this, &RadiographyScene::OnImageReady)); + + // TODO: register failures + return layer; } + void RadiographyScene::OnTagsReady(const IVolumeSlicer::TagsReadyMessage &message) + { + size_t layerIndex = layersIndexBySlice_[&(message.GetOrigin())]; + Layers::iterator layer = layers_.find(layerIndex); - + if (layer != layers_.end()) + { + assert(layer->second != NULL); + + const OrthancPlugins::FullOrthancDataset& dicom = message.GetDicomTags(); + dynamic_cast(layer->second)->SetDicomTags(dicom); + + float c, w; + if (!hasWindowing_ && + layer->second->GetDefaultWindowing(c, w)) + { + hasWindowing_ = true; + windowingCenter_ = c; + windowingWidth_ = w; + } + + EmitMessage(GeometryChangedMessage(*this)); + + CoordinateSystem3D dummy; + message.GetOrigin().ScheduleLayerCreation(dummy); + } + + } + void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) { size_t index = dynamic_cast&> - (message.GetPayload()).GetValue(); + (message.GetPayload()).GetValue(); LOG(INFO) << "JSON received: " << message.GetUri().c_str() << " (" << message.GetAnswerSize() << " bytes) for layer " << index; - + Layers::iterator layer = layers_.find(index); if (layer != layers_.end()) { assert(layer->second != NULL); - + OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); dynamic_cast(layer->second)->SetDicomTags(dicom); @@ -600,15 +640,30 @@ EmitMessage(GeometryChangedMessage(*this)); } } - + + + void RadiographyScene::OnImageReady(const IVolumeSlicer::FrameReadyMessage &message) + { + size_t layerIndex = layersIndexBySlice_[&(message.GetOrigin())]; + Layers::iterator layer = layers_.find(layerIndex); + + if (layer != layers_.end()) + { + assert(layer->second != NULL); + + dynamic_cast(layer->second)->SetSourceImage(message.GetImage()); + + EmitMessage(ContentChangedMessage(*this)); + } + } void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) { size_t index = dynamic_cast&>(message.GetPayload()).GetValue(); - + LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() << " (" << message.GetAnswerSize() << " bytes) for layer " << index; - + Layers::iterator layer = layers_.find(index); if (layer != layers_.end()) { @@ -619,10 +674,10 @@ { content.assign(reinterpret_cast(message.GetAnswer()), message.GetAnswerSize()); } - - std::auto_ptr reader(new Orthanc::PamReader); + + boost::shared_ptr reader(new Orthanc::PamReader); reader->ReadFromMemory(content); - dynamic_cast(layer->second)->SetSourceImage(reader.release()); + dynamic_cast(layer->second)->SetSourceImage(reader); EmitMessage(ContentChangedMessage(*this)); } @@ -642,7 +697,7 @@ return extent; } - + void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, const AffineTransform2D& viewTransform, @@ -685,13 +740,13 @@ return false; } - + void RadiographyScene::DrawBorder(CairoContext& context, unsigned int layer, double zoom) { Layers::const_iterator found = layers_.find(layer); - + if (found != layers_.end()) { context.SetSourceColor(255, 0, 0); @@ -704,7 +759,7 @@ float& maxValue) const { bool first = true; - + for (Layers::const_iterator it = layers_.begin(); it != layers_.end(); it++) { @@ -750,7 +805,7 @@ { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - + LOG(INFO) << "Exporting DICOM"; Extent2D extent = GetSceneExtent(); @@ -768,9 +823,9 @@ static_cast(h), false); AffineTransform2D view = AffineTransform2D::Combine( - AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), - AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1())); - + AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), + AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1())); + Render(layers, view, interpolation); Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, @@ -801,9 +856,9 @@ Json::Value json = Json::objectValue; json["Tags"] = Json::objectValue; - + for (std::set::const_iterator - tag = tags.begin(); tag != tags.end(); ++tag) + tag = tags.begin(); tag != tags.end(); ++tag) { const Orthanc::DicomValue& value = dicom.GetValue(*tag); if (!value.IsNull() && @@ -814,7 +869,7 @@ } json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = - (invert ? "MONOCHROME1" : "MONOCHROME2"); + (invert ? "MONOCHROME1" : "MONOCHROME2"); // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to // avoid floating-point numbers to grow over 16 characters, @@ -822,17 +877,17 @@ // ("dciodvfy" would complain). char buf[32]; sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); - + json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; float center, width; if (GetWindowing(center, width)) { json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = - boost::lexical_cast(boost::math::iround(center)); + boost::lexical_cast(boost::math::iround(center)); json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = - boost::lexical_cast(boost::math::iround(width)); + boost::lexical_cast(boost::math::iround(width)); } // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme @@ -841,10 +896,10 @@ ";base64," + base64); orthanc.PostJsonAsyncExpectJson( - "/tools/create-dicom", json, - new Callable - (*this, &RadiographyScene::OnDicomExported), - NULL, NULL); + "/tools/create-dicom", json, + new Callable + (*this, &RadiographyScene::OnDicomExported), + NULL, NULL); } @@ -861,7 +916,7 @@ const IWebService::HttpHeaders& h = message.GetAnswerHttpHeaders(); for (IWebService::HttpHeaders::const_iterator - it = h.begin(); it != h.end(); ++it) + it = h.begin(); it != h.end(); ++it) { printf("[%s] = [%s]\n", it->first.c_str(), it->second.c_str()); } diff -r 8bf717c4e497 -r f87f28624b96 Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Radiography/RadiographyScene.h Tue Nov 20 16:35:29 2018 +0100 @@ -22,11 +22,14 @@ #pragma once #include "RadiographyLayer.h" +#include #include "../Toolbox/OrthancApiClient.h" namespace OrthancStone { + class IVolumeSlicer; + class RadiographyScene : public IObserver, public IObservable @@ -80,6 +83,8 @@ float windowingWidth_; Layers layers_; + std::map layersIndexBySlice_; + RadiographyLayer& RegisterLayer(RadiographyLayer* layer); void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message); @@ -90,6 +95,10 @@ void OnDicomWebReceived(const IWebService::HttpRequestSuccessMessage& message); + void OnTagsReady(const IVolumeSlicer::TagsReadyMessage& message); + + void OnImageReady(const IVolumeSlicer::FrameReadyMessage& message); + public: RadiographyScene(MessageBroker& broker); @@ -117,6 +126,8 @@ RadiographyLayer& LoadDicomWebFrame(IWebService& web); + RadiographyLayer& SetFrame(IVolumeSlicer* slice); + Extent2D GetSceneExtent() const; void Render(Orthanc::ImageAccessor& buffer, diff -r 8bf717c4e497 -r f87f28624b96 Framework/SmartLoader.cpp --- a/Framework/SmartLoader.cpp Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/SmartLoader.cpp Tue Nov 20 16:35:29 2018 +0100 @@ -27,6 +27,7 @@ #include "Framework/StoneException.h" #include "Framework/Layers/FrameRenderer.h" #include "Core/Logging.h" +#include "Radiography/RadiographyScene.h" namespace OrthancStone { @@ -62,6 +63,8 @@ unsigned int sliceIndex_; std::auto_ptr slice_; + std::auto_ptr dicomTags_; + boost::shared_ptr image_; SliceImageQuality effectiveQuality_; CachedSliceStatus status_; @@ -95,6 +98,7 @@ LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId(); RendererFactory factory(*this); + EmitMessage(IVolumeSlicer::FrameReadyMessage(*this, image_)); EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); } else @@ -127,38 +131,69 @@ { } + void SmartLoader::SetFrameInRadiographyScene(RadiographyScene& scene, const std::string& instanceId, unsigned int frame) + { + // create the frame loader + std::auto_ptr layerSource(GetFrameLoader(instanceId, frame)); + IVolumeSlicer* layerSource2 = layerSource.get(); + + // make sure that the widget registers the events before we trigger them (once we start loading the frame) + scene.SetFrame(layerSource.release()); + + // start loading + LoadFrame(layerSource2, instanceId, frame); + } + + + IVolumeSlicer* SmartLoader::GetFrameLoader(const std::string &instanceId, unsigned int frame) + { + std::auto_ptr layerSource; + std::string sliceKeyId = instanceId + ":" + boost::lexical_cast(frame); + + + if (cachedSlices_.find(sliceKeyId) != cachedSlices_.end()) // && cachedSlices_[sliceKeyId]->status_ == CachedSliceStatus_Loaded) + { // if the image is cached, return a clone of the cached image + layerSource.reset(cachedSlices_[sliceKeyId]->Clone()); + } + else + { // return a standard frame loader + layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); + dynamic_cast(layerSource.get())->SetImageQuality(imageQuality_); + layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerTagsReady)); + layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnFrameReady)); + layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerReady)); + } + + return layerSource.release(); + } + + void SmartLoader::LoadFrame(IVolumeSlicer *frameLoader, const std::string &instanceId, unsigned int frame) + { + SmartLoader::CachedSlice* cachedSlice = dynamic_cast(frameLoader); + + if (cachedSlice != NULL) + { + EmitMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); + EmitMessage(IVolumeSlicer::TagsReadyMessage(*cachedSlice, *(cachedSlice->dicomTags_.get()))); + } + else + { + DicomSeriesVolumeSlicer* volumeLoader = dynamic_cast(frameLoader); + volumeLoader->LoadFrame(instanceId, frame); + } + + } + void SmartLoader::SetFrameInWidget(SliceViewerWidget& sliceViewer, size_t layerIndex, const std::string& instanceId, unsigned int frame) { - // TODO: check if this frame has already been loaded or is already being loaded. - // - if already loaded: create a "clone" that will emit the GeometryReady/ImageReady messages "immediately" - // (it can not be immediate because Observers needs to register first and this is done after this method returns) - // - if currently loading, we need to return an object that will observe the existing VolumeSlicer and forward - // the messages to its observables - // in both cases, we must be carefull about objects lifecycle !!! - - std::auto_ptr layerSource; - std::string sliceKeyId = instanceId + ":" + boost::lexical_cast(frame); - SmartLoader::CachedSlice* cachedSlice = NULL; + // create the frame loader + std::auto_ptr layerSource(GetFrameLoader(instanceId, frame)); + IVolumeSlicer* layerSource2 = layerSource.get(); - if (cachedSlices_.find(sliceKeyId) != cachedSlices_.end()) // && cachedSlices_[sliceKeyId]->status_ == CachedSliceStatus_Loaded) - { - layerSource.reset(cachedSlices_[sliceKeyId]->Clone()); - cachedSlice = dynamic_cast(layerSource.get()); - } - else - { - layerSource.reset(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); - dynamic_cast(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerGeometryReady)); - layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnFrameReady)); - layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerReady)); - dynamic_cast(layerSource.get())->LoadFrame(instanceId, frame); - } - - // make sure that the widget registers the events before we trigger them + // make sure that the widget registers the events before we trigger them (once we start loading the frame) if (sliceViewer.GetLayerCount() == layerIndex) { sliceViewer.AddLayer(layerSource.release()); @@ -172,11 +207,8 @@ throw StoneException(ErrorCode_CanOnlyAddOneLayerAtATime); } - if (cachedSlice != NULL) - { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); - } - + // start loading + LoadFrame(layerSource2, instanceId, frame); } void SmartLoader::PreloadSlice(const std::string instanceId, @@ -201,7 +233,7 @@ std::auto_ptr layerSource(new DicomSeriesVolumeSlicer(IObserver::GetBroker(), orthancApiClient_)); dynamic_cast(layerSource.get())->SetImageQuality(imageQuality_); - layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerGeometryReady)); + layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerTagsReady)); layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnFrameReady)); layerSource->RegisterObserverCallback(new Callable(*this, &SmartLoader::OnLayerReady)); dynamic_cast(layerSource.get())->LoadFrame(instanceId, frame); @@ -222,7 +254,7 @@ // } - void SmartLoader::OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) + void SmartLoader::OnLayerTagsReady(const IVolumeSlicer::TagsReadyMessage& message) { const DicomSeriesVolumeSlicer& source = dynamic_cast(message.GetOrigin()); @@ -238,11 +270,12 @@ cachedSlice->slice_.reset(slice.Clone()); cachedSlice->effectiveQuality_ = source.GetImageQuality(); cachedSlice->status_ = CachedSliceStatus_GeometryLoaded; + cachedSlice->dicomTags_.reset(message.GetDicomTags().Clone()); cachedSlices_[sliceKeyId] = boost::shared_ptr(cachedSlice); // re-emit original Layer message to observers - EmitMessage(message); + // EmitMessage(message); } @@ -264,7 +297,7 @@ cachedSlices_[sliceKeyId] = cachedSlice; // re-emit original Layer message to observers - EmitMessage(message); +// EmitMessage(IVolumeSlicer::FrameReadyMessage(*(cachedSlice.get()), cachedSlice->image_)); } @@ -286,6 +319,6 @@ } // re-emit original Layer message to observers - EmitMessage(message); +// EmitMessage(message); } } diff -r 8bf717c4e497 -r f87f28624b96 Framework/SmartLoader.h --- a/Framework/SmartLoader.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/SmartLoader.h Tue Nov 20 16:35:29 2018 +0100 @@ -29,6 +29,7 @@ namespace OrthancStone { class SliceViewerWidget; + class RadiographyScene; class SmartLoader : public IObservable, public IObserver { @@ -57,11 +58,15 @@ void GetFirstInstanceIdForSeries(std::string& output, const std::string& seriesId); + void SetFrameInRadiographyScene(RadiographyScene& scene, const std::string& instanceId, unsigned int frame); + private: - void OnLayerGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message); + void OnLayerTagsReady(const IVolumeSlicer::TagsReadyMessage& message); void OnFrameReady(const DicomSeriesVolumeSlicer::FrameReadyMessage& message); void OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message); + IVolumeSlicer* GetFrameLoader(const std::string& instanceId, unsigned int frame); + void LoadFrame(IVolumeSlicer* frameLoader, const std::string &instanceId, unsigned int frame); }; } diff -r 8bf717c4e497 -r f87f28624b96 Framework/StoneEnumerations.h --- a/Framework/StoneEnumerations.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/StoneEnumerations.h Tue Nov 20 16:35:29 2018 +0100 @@ -120,7 +120,9 @@ MessageType_Widget_GeometryChanged, MessageType_Widget_ContentChanged, - MessageType_VolumeSlicer_GeometryReady, // instance tags have been loaded + MessageType_VolumeSlicer_GeometryReady, // instance geometry is available + MessageType_VolumeSlicer_TagsReady, // instance tags are available + MessageType_VolumeSlicer_FrameReady, // pixels data are available MessageType_VolumeSlicer_GeometryError, MessageType_VolumeSlicer_ContentChanged, MessageType_VolumeSlicer_SliceChanged, @@ -132,6 +134,7 @@ MessageType_SliceViewerWidget_DisplayedSlice, // The displayed slice has changed MessageType_SliceLoader_GeometryReady, + MessageType_SliceLoader_TagsReady, MessageType_SliceLoader_GeometryError, MessageType_SliceLoader_ImageReady, MessageType_SliceLoader_ImageError, diff -r 8bf717c4e497 -r f87f28624b96 Framework/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Toolbox/OrthancSlicesLoader.cpp Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Tue Nov 20 16:35:29 2018 +0100 @@ -189,7 +189,7 @@ } - void OrthancSlicesLoader::SortAndFinalizeSlices() + void OrthancSlicesLoader::SortAndFinalizeSlices(const OrthancPlugins::FullOrthancDataset& dicomTags) { bool ok = false; @@ -211,6 +211,7 @@ { LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; EmitMessage(SliceGeometryReadyMessage(*this)); + EmitMessage(SliceTagsReadyMessage(*this, dicomTags)); } else { @@ -236,6 +237,12 @@ const Json::Value& series = message.GetJson(); Json::Value::Members instances = series.getMemberNames(); + if (instances.size() < 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem); + } + OrthancPlugins::FullOrthancDataset firstInstanceDataSet(series[instances[0]]); + slices_.Reserve(instances.size()); for (size_t i = 0; i < instances.size(); i++) @@ -264,8 +271,8 @@ } } } - - SortAndFinalizeSlices(); + + SortAndFinalizeSlices(firstInstanceDataSet); } void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message) @@ -301,7 +308,7 @@ } } - SortAndFinalizeSlices(); + SortAndFinalizeSlices(dataset); } @@ -324,6 +331,7 @@ LOG(INFO) << "Loaded instance geometry " << instanceId; slices_.AddSlice(slice.release()); EmitMessage(SliceGeometryReadyMessage(*this)); + EmitMessage(SliceTagsReadyMessage(*this, dataset)); } else { diff -r 8bf717c4e497 -r f87f28624b96 Framework/Toolbox/OrthancSlicesLoader.h --- a/Framework/Toolbox/OrthancSlicesLoader.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Toolbox/OrthancSlicesLoader.h Tue Nov 20 16:35:29 2018 +0100 @@ -28,6 +28,7 @@ #include "SlicesSorter.h" #include +#include namespace OrthancStone @@ -39,8 +40,26 @@ typedef OriginMessage SliceGeometryReadyMessage; typedef OriginMessage SliceGeometryErrorMessage; + class SliceTagsReadyMessage : public OriginMessage + { + private: + const OrthancPlugins::FullOrthancDataset& dicomTags_; + public: + SliceTagsReadyMessage(OrthancSlicesLoader& origin, + const OrthancPlugins::FullOrthancDataset& dicomTags) : + OriginMessage(origin), + dicomTags_(dicomTags) + { + } + + const OrthancPlugins::FullOrthancDataset& GetDicomTags() const + { + return dicomTags_; + } + }; + class SliceImageReadyMessage : - public OriginMessage + public OriginMessage { private: unsigned int sliceIndex_; @@ -80,12 +99,12 @@ SliceImageQuality GetEffectiveQuality() const { return effectiveQuality_; - } + } }; - class SliceImageErrorMessage : - public OriginMessage + class SliceImageErrorMessage : + public OriginMessage { private: const Slice& slice_; @@ -116,7 +135,7 @@ SliceImageQuality GetEffectiveQuality() const { return effectiveQuality_; - } + } }; private: @@ -177,7 +196,7 @@ size_t index, SliceImageQuality quality); - void SortAndFinalizeSlices(); + void SortAndFinalizeSlices(const OrthancPlugins::FullOrthancDataset& dicomTags); public: OrthancSlicesLoader(MessageBroker& broker, diff -r 8bf717c4e497 -r f87f28624b96 Framework/Volumes/ISlicedVolume.h --- a/Framework/Volumes/ISlicedVolume.h Mon Nov 19 12:45:37 2018 +0100 +++ b/Framework/Volumes/ISlicedVolume.h Tue Nov 20 16:35:29 2018 +0100 @@ -13,7 +13,7 @@ * 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 . **/ @@ -23,6 +23,7 @@ #include "../Messages/IObservable.h" #include "../Toolbox/Slice.h" +#include namespace OrthancStone { @@ -30,12 +31,12 @@ { public: typedef OriginMessage ContentChangedMessage; + typedef OriginMessage GeometryReadyMessage; typedef OriginMessage GeometryErrorMessage; - typedef OriginMessage GeometryReadyMessage; typedef OriginMessage VolumeReadyMessage; class SliceContentChangedMessage : - public OriginMessage + public OriginMessage { private: size_t sliceIndex_; diff -r 8bf717c4e497 -r f87f28624b96 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Mon Nov 19 12:45:37 2018 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Tue Nov 20 16:35:29 2018 +0100 @@ -235,6 +235,7 @@ #${ORTHANC_STONE_ROOT}/Framework/Layers/SeriesFrameRendererFactory.cpp #${ORTHANC_STONE_ROOT}/Framework/Layers/SingleFrameRendererFactory.cpp + ${ORTHANC_STONE_ROOT}/Framework/dev.h ${ORTHANC_STONE_ROOT}/Framework/Layers/CircleMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/ColorFrameRenderer.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/DicomSeriesVolumeSlicer.cpp @@ -286,6 +287,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Viewport/IViewport.h ${ORTHANC_STONE_ROOT}/Framework/Viewport/WidgetViewport.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/ImageBuffer3D.cpp + ${ORTHANC_STONE_ROOT}/Framework/Volumes/ISlicedVolume.h ${ORTHANC_STONE_ROOT}/Framework/Volumes/StructureSetLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/Volumes/VolumeReslicer.cpp ${ORTHANC_STONE_ROOT}/Framework/Widgets/CairoWidget.cpp