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