Mercurial > hg > orthanc-stone
comparison Framework/Deprecated/Widgets/SliceViewerWidget.cpp @ 732:c35e98d22764
move Deprecated classes to a separate folder
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 21 May 2019 14:27:35 +0200 |
parents | Framework/Widgets/SliceViewerWidget.cpp@4f2416d519b4 |
children | b537002f83a9 2d8ab34c8c91 |
comparison
equal
deleted
inserted
replaced
729:529189f399ec | 732:c35e98d22764 |
---|---|
1 /** | |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2019 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "SliceViewerWidget.h" | |
23 | |
24 #include "../Layers/SliceOutlineRenderer.h" | |
25 #include "../../Toolbox/GeometryToolbox.h" | |
26 #include "../Layers/FrameRenderer.h" | |
27 | |
28 #include <Core/Logging.h> | |
29 #include <Core/OrthancException.h> | |
30 | |
31 #include <boost/math/constants/constants.hpp> | |
32 | |
33 | |
34 static const double THIN_SLICE_THICKNESS = 100.0 * std::numeric_limits<double>::epsilon(); | |
35 | |
36 namespace Deprecated | |
37 { | |
38 class SliceViewerWidget::Scene : public boost::noncopyable | |
39 { | |
40 private: | |
41 OrthancStone::CoordinateSystem3D plane_; | |
42 double thickness_; | |
43 size_t countMissing_; | |
44 std::vector<ILayerRenderer*> renderers_; | |
45 | |
46 public: | |
47 void DeleteLayer(size_t index) | |
48 { | |
49 if (index >= renderers_.size()) | |
50 { | |
51 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
52 } | |
53 | |
54 assert(countMissing_ <= renderers_.size()); | |
55 | |
56 if (renderers_[index] != NULL) | |
57 { | |
58 assert(countMissing_ < renderers_.size()); | |
59 delete renderers_[index]; | |
60 renderers_[index] = NULL; | |
61 countMissing_++; | |
62 } | |
63 } | |
64 | |
65 Scene(const OrthancStone::CoordinateSystem3D& plane, | |
66 double thickness, | |
67 size_t countLayers) : | |
68 plane_(plane), | |
69 thickness_(thickness), | |
70 countMissing_(countLayers), | |
71 renderers_(countLayers, NULL) | |
72 { | |
73 if (thickness <= 0) | |
74 { | |
75 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
76 } | |
77 } | |
78 | |
79 ~Scene() | |
80 { | |
81 for (size_t i = 0; i < renderers_.size(); i++) | |
82 { | |
83 DeleteLayer(i); | |
84 } | |
85 } | |
86 | |
87 void SetLayer(size_t index, | |
88 ILayerRenderer* renderer) // Takes ownership | |
89 { | |
90 if (renderer == NULL) | |
91 { | |
92 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
93 } | |
94 | |
95 DeleteLayer(index); | |
96 | |
97 renderers_[index] = renderer; | |
98 countMissing_--; | |
99 } | |
100 | |
101 const OrthancStone::CoordinateSystem3D& GetPlane() const | |
102 { | |
103 return plane_; | |
104 } | |
105 | |
106 bool HasRenderer(size_t index) | |
107 { | |
108 return renderers_[index] != NULL; | |
109 } | |
110 | |
111 bool IsComplete() const | |
112 { | |
113 return countMissing_ == 0; | |
114 } | |
115 | |
116 unsigned int GetCountMissing() const | |
117 { | |
118 return static_cast<unsigned int>(countMissing_); | |
119 } | |
120 | |
121 bool RenderScene(OrthancStone::CairoContext& context, | |
122 const ViewportGeometry& view, | |
123 const OrthancStone::CoordinateSystem3D& viewportPlane) | |
124 { | |
125 bool fullQuality = true; | |
126 cairo_t *cr = context.GetObject(); | |
127 | |
128 for (size_t i = 0; i < renderers_.size(); i++) | |
129 { | |
130 if (renderers_[i] != NULL) | |
131 { | |
132 const OrthancStone::CoordinateSystem3D& framePlane = renderers_[i]->GetLayerPlane(); | |
133 | |
134 double x0, y0, x1, y1, x2, y2; | |
135 viewportPlane.ProjectPoint(x0, y0, framePlane.GetOrigin()); | |
136 viewportPlane.ProjectPoint(x1, y1, framePlane.GetOrigin() + framePlane.GetAxisX()); | |
137 viewportPlane.ProjectPoint(x2, y2, framePlane.GetOrigin() + framePlane.GetAxisY()); | |
138 | |
139 /** | |
140 * Now we solve the system of linear equations Ax + b = x', given: | |
141 * A [0 ; 0] + b = [x0 ; y0] | |
142 * A [1 ; 0] + b = [x1 ; y1] | |
143 * A [0 ; 1] + b = [x2 ; y2] | |
144 * <=> | |
145 * b = [x0 ; y0] | |
146 * A [1 ; 0] = [x1 ; y1] - b = [x1 - x0 ; y1 - y0] | |
147 * A [0 ; 1] = [x2 ; y2] - b = [x2 - x0 ; y2 - y0] | |
148 * <=> | |
149 * b = [x0 ; y0] | |
150 * [a11 ; a21] = [x1 - x0 ; y1 - y0] | |
151 * [a12 ; a22] = [x2 - x0 ; y2 - y0] | |
152 **/ | |
153 | |
154 cairo_matrix_t transform; | |
155 cairo_matrix_init(&transform, x1 - x0, y1 - y0, x2 - x0, y2 - y0, x0, y0); | |
156 | |
157 cairo_save(cr); | |
158 cairo_transform(cr, &transform); | |
159 | |
160 if (!renderers_[i]->RenderLayer(context, view)) | |
161 { | |
162 cairo_restore(cr); | |
163 return false; | |
164 } | |
165 | |
166 cairo_restore(cr); | |
167 } | |
168 | |
169 if (renderers_[i] != NULL && | |
170 !renderers_[i]->IsFullQuality()) | |
171 { | |
172 fullQuality = false; | |
173 } | |
174 } | |
175 | |
176 if (!fullQuality) | |
177 { | |
178 double x, y; | |
179 view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10); | |
180 | |
181 cairo_translate(cr, x, y); | |
182 | |
183 #if 1 | |
184 double s = 5.0 / view.GetZoom(); | |
185 cairo_rectangle(cr, -s, -s, 2.0 * s, 2.0 * s); | |
186 #else | |
187 // TODO Drawing filled circles makes WebAssembly crash! | |
188 cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2.0 * boost::math::constants::pi<double>()); | |
189 #endif | |
190 | |
191 cairo_set_line_width(cr, 2.0 / view.GetZoom()); | |
192 cairo_set_source_rgb(cr, 1, 1, 1); | |
193 cairo_stroke_preserve(cr); | |
194 cairo_set_source_rgb(cr, 1, 0, 0); | |
195 cairo_fill(cr); | |
196 } | |
197 | |
198 return true; | |
199 } | |
200 | |
201 void SetLayerStyle(size_t index, | |
202 const RenderStyle& style) | |
203 { | |
204 if (renderers_[index] != NULL) | |
205 { | |
206 renderers_[index]->SetLayerStyle(style); | |
207 } | |
208 } | |
209 | |
210 bool ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const | |
211 { | |
212 bool isOpposite; | |
213 if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, | |
214 plane.GetNormal(), | |
215 plane_.GetNormal())) | |
216 { | |
217 return false; | |
218 } | |
219 else | |
220 { | |
221 double z = (plane_.ProjectAlongNormal(plane.GetOrigin()) - | |
222 plane_.ProjectAlongNormal(plane_.GetOrigin())); | |
223 | |
224 if (z < 0) | |
225 { | |
226 z = -z; | |
227 } | |
228 | |
229 return z <= thickness_; | |
230 } | |
231 } | |
232 | |
233 double GetThickness() const | |
234 { | |
235 return thickness_; | |
236 } | |
237 }; | |
238 | |
239 | |
240 bool SliceViewerWidget::LookupLayer(size_t& index /* out */, | |
241 const IVolumeSlicer& layer) const | |
242 { | |
243 LayersIndex::const_iterator found = layersIndex_.find(&layer); | |
244 | |
245 if (found == layersIndex_.end()) | |
246 { | |
247 return false; | |
248 } | |
249 else | |
250 { | |
251 index = found->second; | |
252 assert(index < layers_.size() && | |
253 layers_[index] == &layer); | |
254 return true; | |
255 } | |
256 } | |
257 | |
258 | |
259 void SliceViewerWidget::GetLayerExtent(OrthancStone::Extent2D& extent, | |
260 IVolumeSlicer& source) const | |
261 { | |
262 extent.Reset(); | |
263 | |
264 std::vector<OrthancStone::Vector> points; | |
265 if (source.GetExtent(points, plane_)) | |
266 { | |
267 for (size_t i = 0; i < points.size(); i++) | |
268 { | |
269 double x, y; | |
270 plane_.ProjectPoint(x, y, points[i]); | |
271 extent.AddPoint(x, y); | |
272 } | |
273 } | |
274 } | |
275 | |
276 | |
277 OrthancStone::Extent2D SliceViewerWidget::GetSceneExtent() | |
278 { | |
279 OrthancStone::Extent2D sceneExtent; | |
280 | |
281 for (size_t i = 0; i < layers_.size(); i++) | |
282 { | |
283 assert(layers_[i] != NULL); | |
284 OrthancStone::Extent2D layerExtent; | |
285 GetLayerExtent(layerExtent, *layers_[i]); | |
286 | |
287 sceneExtent.Union(layerExtent); | |
288 } | |
289 | |
290 return sceneExtent; | |
291 } | |
292 | |
293 | |
294 bool SliceViewerWidget::RenderScene(OrthancStone::CairoContext& context, | |
295 const ViewportGeometry& view) | |
296 { | |
297 if (currentScene_.get() != NULL) | |
298 { | |
299 return currentScene_->RenderScene(context, view, plane_); | |
300 } | |
301 else | |
302 { | |
303 return true; | |
304 } | |
305 } | |
306 | |
307 | |
308 void SliceViewerWidget::ResetPendingScene() | |
309 { | |
310 double thickness; | |
311 if (pendingScene_.get() == NULL) | |
312 { | |
313 thickness = 1.0; | |
314 } | |
315 else | |
316 { | |
317 thickness = pendingScene_->GetThickness(); | |
318 } | |
319 | |
320 pendingScene_.reset(new Scene(plane_, thickness, layers_.size())); | |
321 } | |
322 | |
323 | |
324 void SliceViewerWidget::UpdateLayer(size_t index, | |
325 ILayerRenderer* renderer, | |
326 const OrthancStone::CoordinateSystem3D& plane) | |
327 { | |
328 LOG(INFO) << "Updating layer " << index; | |
329 | |
330 std::auto_ptr<ILayerRenderer> tmp(renderer); | |
331 | |
332 if (renderer == NULL) | |
333 { | |
334 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
335 } | |
336 | |
337 if (index >= layers_.size()) | |
338 { | |
339 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
340 } | |
341 | |
342 assert(layers_.size() == styles_.size()); | |
343 renderer->SetLayerStyle(styles_[index]); | |
344 | |
345 if (currentScene_.get() != NULL && | |
346 currentScene_->ContainsPlane(plane)) | |
347 { | |
348 currentScene_->SetLayer(index, tmp.release()); | |
349 NotifyContentChanged(); | |
350 } | |
351 else if (pendingScene_.get() != NULL && | |
352 pendingScene_->ContainsPlane(plane)) | |
353 { | |
354 pendingScene_->SetLayer(index, tmp.release()); | |
355 | |
356 if (currentScene_.get() == NULL || | |
357 !currentScene_->IsComplete() || | |
358 pendingScene_->IsComplete()) | |
359 { | |
360 currentScene_ = pendingScene_; | |
361 NotifyContentChanged(); | |
362 } | |
363 } | |
364 } | |
365 | |
366 | |
367 SliceViewerWidget::SliceViewerWidget(OrthancStone::MessageBroker& broker, | |
368 const std::string& name) : | |
369 WorldSceneWidget(name), | |
370 IObserver(broker), | |
371 IObservable(broker), | |
372 started_(false) | |
373 { | |
374 SetBackgroundCleared(true); | |
375 } | |
376 | |
377 | |
378 SliceViewerWidget::~SliceViewerWidget() | |
379 { | |
380 for (size_t i = 0; i < layers_.size(); i++) | |
381 { | |
382 delete layers_[i]; | |
383 } | |
384 } | |
385 | |
386 void SliceViewerWidget::ObserveLayer(IVolumeSlicer& layer) | |
387 { | |
388 layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::GeometryReadyMessage> | |
389 (*this, &SliceViewerWidget::OnGeometryReady)); | |
390 // currently ignore errors layer->RegisterObserverCallback(new Callable<SliceViewerWidget, IVolumeSlicer::GeometryErrorMessage>(*this, &SliceViewerWidget::...)); | |
391 layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::SliceContentChangedMessage> | |
392 (*this, &SliceViewerWidget::OnSliceChanged)); | |
393 layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::ContentChangedMessage> | |
394 (*this, &SliceViewerWidget::OnContentChanged)); | |
395 layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerReadyMessage> | |
396 (*this, &SliceViewerWidget::OnLayerReady)); | |
397 layer.RegisterObserverCallback(new OrthancStone::Callable<SliceViewerWidget, IVolumeSlicer::LayerErrorMessage> | |
398 (*this, &SliceViewerWidget::OnLayerError)); | |
399 } | |
400 | |
401 | |
402 size_t SliceViewerWidget::AddLayer(IVolumeSlicer* layer) // Takes ownership | |
403 { | |
404 if (layer == NULL) | |
405 { | |
406 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
407 } | |
408 | |
409 size_t index = layers_.size(); | |
410 layers_.push_back(layer); | |
411 styles_.push_back(RenderStyle()); | |
412 layersIndex_[layer] = index; | |
413 | |
414 ResetPendingScene(); | |
415 | |
416 ObserveLayer(*layer); | |
417 | |
418 ResetChangedLayers(); | |
419 | |
420 return index; | |
421 } | |
422 | |
423 | |
424 void SliceViewerWidget::ReplaceLayer(size_t index, IVolumeSlicer* layer) // Takes ownership | |
425 { | |
426 if (layer == NULL) | |
427 { | |
428 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
429 } | |
430 | |
431 if (index >= layers_.size()) | |
432 { | |
433 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
434 } | |
435 | |
436 delete layers_[index]; | |
437 layers_[index] = layer; | |
438 layersIndex_[layer] = index; | |
439 | |
440 ResetPendingScene(); | |
441 | |
442 ObserveLayer(*layer); | |
443 | |
444 InvalidateLayer(index); | |
445 } | |
446 | |
447 | |
448 void SliceViewerWidget::RemoveLayer(size_t index) | |
449 { | |
450 if (index >= layers_.size()) | |
451 { | |
452 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
453 } | |
454 | |
455 IVolumeSlicer* previousLayer = layers_[index]; | |
456 layersIndex_.erase(layersIndex_.find(previousLayer)); | |
457 layers_.erase(layers_.begin() + index); | |
458 changedLayers_.erase(changedLayers_.begin() + index); | |
459 styles_.erase(styles_.begin() + index); | |
460 | |
461 delete layers_[index]; | |
462 | |
463 currentScene_->DeleteLayer(index); | |
464 ResetPendingScene(); | |
465 | |
466 NotifyContentChanged(); | |
467 } | |
468 | |
469 | |
470 const RenderStyle& SliceViewerWidget::GetLayerStyle(size_t layer) const | |
471 { | |
472 if (layer >= layers_.size()) | |
473 { | |
474 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
475 } | |
476 | |
477 assert(layers_.size() == styles_.size()); | |
478 return styles_[layer]; | |
479 } | |
480 | |
481 | |
482 void SliceViewerWidget::SetLayerStyle(size_t layer, | |
483 const RenderStyle& style) | |
484 { | |
485 if (layer >= layers_.size()) | |
486 { | |
487 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
488 } | |
489 | |
490 assert(layers_.size() == styles_.size()); | |
491 styles_[layer] = style; | |
492 | |
493 if (currentScene_.get() != NULL) | |
494 { | |
495 currentScene_->SetLayerStyle(layer, style); | |
496 } | |
497 | |
498 if (pendingScene_.get() != NULL) | |
499 { | |
500 pendingScene_->SetLayerStyle(layer, style); | |
501 } | |
502 | |
503 NotifyContentChanged(); | |
504 } | |
505 | |
506 | |
507 void SliceViewerWidget::SetSlice(const OrthancStone::CoordinateSystem3D& plane) | |
508 { | |
509 LOG(INFO) << "Setting slice origin: (" << plane.GetOrigin()[0] | |
510 << "," << plane.GetOrigin()[1] | |
511 << "," << plane.GetOrigin()[2] << ")"; | |
512 | |
513 Deprecated::Slice displayedSlice(plane_, THIN_SLICE_THICKNESS); | |
514 | |
515 //if (!displayedSlice.ContainsPlane(slice)) | |
516 { | |
517 if (currentScene_.get() == NULL || | |
518 (pendingScene_.get() != NULL && | |
519 pendingScene_->IsComplete())) | |
520 { | |
521 currentScene_ = pendingScene_; | |
522 } | |
523 | |
524 plane_ = plane; | |
525 ResetPendingScene(); | |
526 | |
527 InvalidateAllLayers(); // TODO Removing this line avoid loading twice the image in WASM | |
528 } | |
529 | |
530 BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice)); | |
531 } | |
532 | |
533 | |
534 void SliceViewerWidget::OnGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message) | |
535 { | |
536 size_t i; | |
537 if (LookupLayer(i, message.GetOrigin())) | |
538 { | |
539 LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName(); | |
540 | |
541 changedLayers_[i] = true; | |
542 //layers_[i]->ScheduleLayerCreation(plane_); | |
543 } | |
544 BroadcastMessage(GeometryChangedMessage(*this)); | |
545 } | |
546 | |
547 | |
548 void SliceViewerWidget::InvalidateAllLayers() | |
549 { | |
550 for (size_t i = 0; i < layers_.size(); i++) | |
551 { | |
552 assert(layers_[i] != NULL); | |
553 changedLayers_[i] = true; | |
554 | |
555 //layers_[i]->ScheduleLayerCreation(plane_); | |
556 } | |
557 } | |
558 | |
559 | |
560 void SliceViewerWidget::InvalidateLayer(size_t layer) | |
561 { | |
562 if (layer >= layers_.size()) | |
563 { | |
564 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
565 } | |
566 | |
567 assert(layers_[layer] != NULL); | |
568 changedLayers_[layer] = true; | |
569 | |
570 //layers_[layer]->ScheduleLayerCreation(plane_); | |
571 } | |
572 | |
573 | |
574 void SliceViewerWidget::OnContentChanged(const IVolumeSlicer::ContentChangedMessage& message) | |
575 { | |
576 size_t index; | |
577 if (LookupLayer(index, message.GetOrigin())) | |
578 { | |
579 InvalidateLayer(index); | |
580 } | |
581 | |
582 BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); | |
583 } | |
584 | |
585 | |
586 void SliceViewerWidget::OnSliceChanged(const IVolumeSlicer::SliceContentChangedMessage& message) | |
587 { | |
588 if (message.GetSlice().ContainsPlane(plane_)) | |
589 { | |
590 size_t index; | |
591 if (LookupLayer(index, message.GetOrigin())) | |
592 { | |
593 InvalidateLayer(index); | |
594 } | |
595 } | |
596 | |
597 BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); | |
598 } | |
599 | |
600 | |
601 void SliceViewerWidget::OnLayerReady(const IVolumeSlicer::LayerReadyMessage& message) | |
602 { | |
603 size_t index; | |
604 if (LookupLayer(index, message.GetOrigin())) | |
605 { | |
606 LOG(INFO) << "Renderer ready for layer " << index; | |
607 UpdateLayer(index, message.CreateRenderer(), message.GetSlice()); | |
608 } | |
609 | |
610 BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); | |
611 } | |
612 | |
613 | |
614 void SliceViewerWidget::OnLayerError(const IVolumeSlicer::LayerErrorMessage& message) | |
615 { | |
616 size_t index; | |
617 if (LookupLayer(index, message.GetOrigin())) | |
618 { | |
619 LOG(ERROR) << "Using error renderer on layer " << index; | |
620 | |
621 // TODO | |
622 //UpdateLayer(index, new SliceOutlineRenderer(slice), slice); | |
623 | |
624 BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); | |
625 } | |
626 } | |
627 | |
628 | |
629 void SliceViewerWidget::ResetChangedLayers() | |
630 { | |
631 changedLayers_.resize(layers_.size()); | |
632 | |
633 for (size_t i = 0; i < changedLayers_.size(); i++) | |
634 { | |
635 changedLayers_[i] = false; | |
636 } | |
637 } | |
638 | |
639 | |
640 void SliceViewerWidget::DoAnimation() | |
641 { | |
642 assert(changedLayers_.size() <= layers_.size()); | |
643 | |
644 for (size_t i = 0; i < changedLayers_.size(); i++) | |
645 { | |
646 if (changedLayers_[i]) | |
647 { | |
648 layers_[i]->ScheduleLayerCreation(plane_); | |
649 } | |
650 } | |
651 | |
652 ResetChangedLayers(); | |
653 } | |
654 } |