Mercurial > hg > orthanc-stone
view Framework/Widgets/SliceViewerWidget.cpp @ 573:adc1be326b62
cont
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 18 Apr 2019 17:29:44 +0200 |
parents | b70e9be013e4 |
children | 42dadae61fa9 |
line wrap: on
line source
/** * Stone of Orthanc * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2019 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "SliceViewerWidget.h" #include "../Layers/SliceOutlineRenderer.h" #include "../Toolbox/GeometryToolbox.h" #include "Framework/Layers/FrameRenderer.h" #include <Core/Logging.h> #include <Core/OrthancException.h> #include <boost/math/constants/constants.hpp> static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon(); namespace OrthancStone { class SliceViewerWidget::Scene : public boost::noncopyable { private: CoordinateSystem3D plane_; double thickness_; size_t countMissing_; std::vector<ILayerRenderer*> renderers_; public: void DeleteLayer(size_t index) { if (index >= renderers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } assert(countMissing_ <= renderers_.size()); if (renderers_[index] != NULL) { assert(countMissing_ < renderers_.size()); delete renderers_[index]; renderers_[index] = NULL; countMissing_++; } } Scene(const CoordinateSystem3D& plane, double thickness, size_t countLayers) : plane_(plane), thickness_(thickness), countMissing_(countLayers), renderers_(countLayers, NULL) { if (thickness <= 0) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } ~Scene() { for (size_t i = 0; i < renderers_.size(); i++) { DeleteLayer(i); } } void SetLayer(size_t index, ILayerRenderer* renderer) // Takes ownership { if (renderer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } DeleteLayer(index); renderers_[index] = renderer; countMissing_--; } const CoordinateSystem3D& GetPlane() const { return plane_; } bool HasRenderer(size_t index) { return renderers_[index] != NULL; } bool IsComplete() const { return countMissing_ == 0; } unsigned int GetCountMissing() const { return countMissing_; } bool RenderScene(CairoContext& context, const ViewportGeometry& view, const CoordinateSystem3D& viewportPlane) { bool fullQuality = true; cairo_t *cr = context.GetObject(); for (size_t i = 0; i < renderers_.size(); i++) { if (renderers_[i] != NULL) { const CoordinateSystem3D& framePlane = renderers_[i]->GetLayerPlane(); double x0, y0, x1, y1, x2, y2; viewportPlane.ProjectPoint(x0, y0, framePlane.GetOrigin()); viewportPlane.ProjectPoint(x1, y1, framePlane.GetOrigin() + framePlane.GetAxisX()); viewportPlane.ProjectPoint(x2, y2, framePlane.GetOrigin() + framePlane.GetAxisY()); /** * Now we solve the system of linear equations Ax + b = x', given: * A [0 ; 0] + b = [x0 ; y0] * A [1 ; 0] + b = [x1 ; y1] * A [0 ; 1] + b = [x2 ; y2] * <=> * b = [x0 ; y0] * A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0] * A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0] * <=> * b = [x0 ; y0] * [a11 ; a21] = [x1 - x0 ; y1 - y0] * [a12 ; a22] = [x2 - x0 ; y2 - y0] **/ cairo_matrix_t transform; cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0); cairo_save(cr); cairo_transform(cr, &transform); if (!renderers_[i]->RenderLayer(context, view)) { cairo_restore(cr); return false; } cairo_restore(cr); } if (renderers_[i] != NULL && !renderers_[i]->IsFullQuality()) { fullQuality = false; } } if (!fullQuality) { double x, y; view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10); cairo_translate(cr, x, y); #if 1 double s = 5.0 / view.GetZoom(); cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s); #else // TODO Drawing filled circles makes WebAssembly crash! cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>()); #endif cairo_set_line_width(cr, 2.0 / view.GetZoom()); cairo_set_source_rgb(cr, 1, 1, 1); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 1, 0, 0); cairo_fill(cr); } return true; } void SetLayerStyle(size_t index, const RenderStyle& style) { if (renderers_[index] != NULL) { renderers_[index]->SetLayerStyle(style); } } bool ContainsPlane(const CoordinateSystem3D& plane) const { bool isOpposite; if (!GeometryToolbox::IsParallelOrOpposite(isOpposite, plane.GetNormal(), plane_.GetNormal())) { return false; } else { double z = (plane_.ProjectAlongNormal(plane.GetOrigin()) - plane_.ProjectAlongNormal(plane_.GetOrigin())); if (z < 0) { z = -z; } return z <= thickness_; } } double GetThickness() const { return thickness_; } }; bool SliceViewerWidget::LookupLayer(size_t& index /* out */, const IVolumeSlicer& layer) const { LayersIndex::const_iterator found = layersIndex_.find(&layer); if (found == layersIndex_.end()) { return false; } else { index = found->second; assert(index < layers_.size() && layers_[index] == &layer); return true; } } void SliceViewerWidget::GetLayerExtent(Extent2D& extent, IVolumeSlicer& source) const { extent.Reset(); std::vector<Vector> points; if (source.GetExtent(points, plane_)) { for (size_t i = 0; i < points.size(); i++) { double x, y; plane_.ProjectPoint(x, y, points[i]); extent.AddPoint(x, y); } } } Extent2D SliceViewerWidget::GetSceneExtent() { Extent2D sceneExtent; for (size_t i = 0; i < layers_.size(); i++) { assert(layers_[i] != NULL); Extent2D layerExtent; GetLayerExtent(layerExtent, *layers_[i]); sceneExtent.Union(layerExtent); } return sceneExtent; } bool SliceViewerWidget::RenderScene(CairoContext& context, const ViewportGeometry& view) { if (currentScene_.get() != NULL) { return currentScene_->RenderScene(context, view, plane_); } else { return true; } } void SliceViewerWidget::ResetPendingScene() { double thickness; if (pendingScene_.get() == NULL) { thickness = 1.0; } else { thickness = pendingScene_->GetThickness(); } pendingScene_.reset(new Scene(plane_, thickness, layers_.size())); } void SliceViewerWidget::UpdateLayer(size_t index, ILayerRenderer* renderer, const CoordinateSystem3D& plane) { LOG(INFO) << "Updating layer " << index; std::auto_ptr<ILayerRenderer> tmp(renderer); if (renderer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } if (index >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } assert(layers_.size() == styles_.size()); renderer->SetLayerStyle(styles_[index]); if (currentScene_.get() != NULL && currentScene_->ContainsPlane(plane)) { currentScene_->SetLayer(index, tmp.release()); NotifyContentChanged(); } else if (pendingScene_.get() != NULL && pendingScene_->ContainsPlane(plane)) { pendingScene_->SetLayer(index, tmp.release()); if (currentScene_.get() == NULL || !currentScene_->IsComplete() || pendingScene_->IsComplete()) { currentScene_ = pendingScene_; NotifyContentChanged(); } } } SliceViewerWidget::SliceViewerWidget(MessageBroker& broker, const std::string& name) : WorldSceneWidget(name), IObserver(broker), IObservable(broker), started_(false) { SetBackgroundCleared(true); } SliceViewerWidget::~SliceViewerWidget() { for (size_t i = 0; i < layers_.size(); i++) { delete layers_[i]; } } void SliceViewerWidget::ObserveLayer(IVolumeSlicer& layer) { layer.RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage> (*this, &SliceViewerWidget::OnGeometryReady)); // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...)); layer.RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage> (*this, &SliceViewerWidget::OnSliceChanged)); layer.RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage> (*this, &SliceViewerWidget::OnContentChanged)); layer.RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage> (*this, &SliceViewerWidget::OnLayerReady)); layer.RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage> (*this, &SliceViewerWidget::OnLayerError)); } size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer) // Takes ownership { if (layer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } size_t index = layers_.size(); layers_.push_back(layer); styles_.push_back(RenderStyle()); layersIndex_[layer] = index; ResetPendingScene(); ObserveLayer(*layer); ResetChangedLayers(); return index; } void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer) // Takes ownership { if (layer == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } if (index >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } delete layers_[index]; layers_[index] = layer; layersIndex_[layer] = index; ResetPendingScene(); ObserveLayer(*layer); InvalidateLayer(index); } void SliceViewerWidget::RemoveLayer(size_t index) { if (index >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } IVolumeSlicer* previousLayer = layers_[index]; layersIndex_.erase(layersIndex_.find(previousLayer)); layers_.erase(layers_.begin() + index); changedLayers_.erase(changedLayers_.begin() + index); styles_.erase(styles_.begin() + index); delete layers_[index]; currentScene_->DeleteLayer(index); ResetPendingScene(); NotifyContentChanged(); } const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const { if (layer >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } assert(layers_.size() == styles_.size()); return styles_[layer]; } void SliceViewerWidget::SetLayerStyle(size_t layer, const RenderStyle& style) { if (layer >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } assert(layers_.size() == styles_.size()); styles_[layer] = style; if (currentScene_.get() != NULL) { currentScene_->SetLayerStyle(layer, style); } if (pendingScene_.get() != NULL) { pendingScene_->SetLayerStyle(layer, style); } NotifyContentChanged(); } void SliceViewerWidget::SetSlice(const CoordinateSystem3D& plane) { LOG(INFO) << "Setting slice origin: (" << plane.GetOrigin()[0] << "," << plane.GetOrigin()[1] << "," << plane.GetOrigin()[2] << ")"; Slice displayedSlice(plane_, THIN_SLICE_THICKNESS); //if (!displayedSlice.ContainsPlane(slice)) { if (currentScene_.get() == NULL || (pendingScene_.get() != NULL && pendingScene_->IsComplete())) { currentScene_ = pendingScene_; } plane_ = plane; ResetPendingScene(); InvalidateAllLayers(); // TODO Removing this line avoid loading twice the image in WASM } EmitMessage(DisplayedSliceMessage(*this, displayedSlice)); } void SliceViewerWidget::OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) { size_t i; if (LookupLayer(i, message.GetOrigin())) { LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName(); changedLayers_[i] = true; //layers_[i]->ScheduleLayerCreation(plane_); } EmitMessage(GeometryChangedMessage(*this)); } void SliceViewerWidget::InvalidateAllLayers() { for (size_t i = 0; i < layers_.size(); i++) { assert(layers_[i] != NULL); changedLayers_[i] = true; //layers_[i]->ScheduleLayerCreation(plane_); } } void SliceViewerWidget::InvalidateLayer(size_t layer) { if (layer >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } assert(layers_[layer] != NULL); changedLayers_[layer] = true; //layers_[layer]->ScheduleLayerCreation(plane_); } void SliceViewerWidget::OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message) { size_t index; if (LookupLayer(index, message.GetOrigin())) { InvalidateLayer(index); } EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); } void SliceViewerWidget::OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message) { if (message.GetSlice().ContainsPlane(plane_)) { size_t index; if (LookupLayer(index, message.GetOrigin())) { InvalidateLayer(index); } } EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); } void SliceViewerWidget::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message) { size_t index; if (LookupLayer(index, message.GetOrigin())) { LOG(INFO) << "Renderer ready for layer " << index; UpdateLayer(index, message.CreateRenderer(), message.GetSlice()); } EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); } void SliceViewerWidget::OnLayerError(const IVolumeSlicer::LayerErrorMessage& message) { size_t index; if (LookupLayer(index, message.GetOrigin())) { LOG(ERROR) << "Using error renderer on layer " << index; // TODO //UpdateLayer(index, new SliceOutlineRenderer(slice), slice); EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); } } void SliceViewerWidget::ResetChangedLayers() { changedLayers_.resize(layers_.size()); for (size_t i = 0; i < changedLayers_.size(); i++) { changedLayers_[i] = false; } } void SliceViewerWidget::DoAnimation() { assert(changedLayers_.size() <= layers_.size()); for (size_t i = 0; i < changedLayers_.size(); i++) { if (changedLayers_[i]) { layers_[i]->ScheduleLayerCreation(plane_); } } ResetChangedLayers(); } }