comparison Applications/Samples/rt-viewer-demo/main.cpp @ 549:1d9deb4ee84c ct-pet-dose-struct

Added RTSTRUCT demo viewer based on captain + fix in dev.h (wrong override signature)
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 02 Apr 2019 15:08:31 +0200
parents
children 70992b38aa8a
comparison
equal deleted inserted replaced
548:d10a295b607a 549:1d9deb4ee84c
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 #include "Applications/IStoneApplication.h"
22 #include "Framework/Widgets/WorldSceneWidget.h"
23 #include "Framework/Widgets/LayoutWidget.h"
24
25 #if ORTHANC_ENABLE_WASM==1
26 #include "Platforms/Wasm/WasmPlatformApplicationAdapter.h"
27 #include "Platforms/Wasm/Defaults.h"
28 #include "Platforms/Wasm/WasmViewport.h"
29 #endif
30
31 #if ORTHANC_ENABLE_QT==1
32 #include "Qt/SampleMainWindow.h"
33 #include "Qt/SampleMainWindowWithButtons.h"
34 #endif
35
36 #include "Framework/Layers/DicomSeriesVolumeSlicer.h"
37 #include "Framework/Widgets/SliceViewerWidget.h"
38 #include "Framework/Volumes/StructureSetLoader.h"
39
40 #include <Core/Logging.h>
41 #include <Core/OrthancException.h>
42 #include <Core/Images/ImageTraits.h>
43
44 #include <boost/math/constants/constants.hpp>
45 #include "Framework/dev.h"
46 #include "Framework/Widgets/LayoutWidget.h"
47 #include "Framework/Layers/DicomStructureSetSlicer.h"
48
49 namespace OrthancStone
50 {
51 namespace Samples
52 {
53 class RtViewerDemoBaseApplication : public IStoneApplication
54 {
55 protected:
56 // ownership is transferred to the application context
57 #ifndef RESTORE_NON_RTVIEWERDEMO_BEHAVIOR
58 LayoutWidget* mainWidget_;
59 #else
60 WorldSceneWidget* mainWidget_;
61 #endif
62
63 public:
64 virtual void Initialize(StoneApplicationContext* context,
65 IStatusBar& statusBar,
66 const boost::program_options::variables_map& parameters) ORTHANC_OVERRIDE
67 {
68 }
69
70 virtual std::string GetTitle() const ORTHANC_OVERRIDE
71 {
72 return "Stone of Orthanc - Sample";
73 }
74
75 /**
76 * In the basic samples, the commands are handled by the platform adapter and NOT
77 * by the application handler
78 */
79 virtual void HandleSerializedMessage(const char* data) ORTHANC_OVERRIDE {};
80
81
82 virtual void Finalize() ORTHANC_OVERRIDE {}
83 virtual IWidget* GetCentralWidget() ORTHANC_OVERRIDE {return mainWidget_;}
84
85 #if ORTHANC_ENABLE_WASM==1
86 // default implementations for a single canvas named "canvas" in the HTML and an empty WasmApplicationAdapter
87
88 virtual void InitializeWasm() ORTHANC_OVERRIDE
89 {
90 AttachWidgetToWasmViewport("canvas", mainWidget_);
91 }
92
93 virtual WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(MessageBroker& broker)
94 {
95 return new WasmPlatformApplicationAdapter(broker, *this);
96 }
97 #endif
98
99 };
100
101 // this application actually works in Qt and WASM
102 class RtViewerDemoBaseSingleCanvasWithButtonsApplication : public RtViewerDemoBaseApplication
103 {
104 public:
105 virtual void OnPushButton1Clicked() {}
106 virtual void OnPushButton2Clicked() {}
107 virtual void OnTool1Clicked() {}
108 virtual void OnTool2Clicked() {}
109
110 virtual void GetButtonNames(std::string& pushButton1,
111 std::string& pushButton2,
112 std::string& tool1,
113 std::string& tool2
114 ) {
115 pushButton1 = "action1";
116 pushButton2 = "action2";
117 tool1 = "tool1";
118 tool2 = "tool2";
119 }
120
121 #if ORTHANC_ENABLE_QT==1
122 virtual QStoneMainWindow* CreateQtMainWindow() {
123 return new SampleMainWindowWithButtons(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this);
124 }
125 #endif
126
127 };
128
129 // this application actually works in SDL and WASM
130 class RtViewerDemoBaseApplicationSingleCanvas : public RtViewerDemoBaseApplication
131 {
132 public:
133
134 #if ORTHANC_ENABLE_QT==1
135 virtual QStoneMainWindow* CreateQtMainWindow() {
136 return new SampleMainWindow(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this);
137 }
138 #endif
139 };
140 }
141 }
142
143
144
145 namespace OrthancStone
146 {
147 namespace Samples
148 {
149 template <Orthanc::PixelFormat T>
150 void ReadDistributionInternal(std::vector<float>& distribution,
151 const Orthanc::ImageAccessor& image)
152 {
153 const unsigned int width = image.GetWidth();
154 const unsigned int height = image.GetHeight();
155
156 distribution.resize(width * height);
157 size_t pos = 0;
158
159 for (unsigned int y = 0; y < height; y++)
160 {
161 for (unsigned int x = 0; x < width; x++, pos++)
162 {
163 distribution[pos] = Orthanc::ImageTraits<T>::GetFloatPixel(image, x, y);
164 }
165 }
166 }
167
168 void ReadDistribution(std::vector<float>& distribution,
169 const Orthanc::ImageAccessor& image)
170 {
171 switch (image.GetFormat())
172 {
173 case Orthanc::PixelFormat_Grayscale8:
174 ReadDistributionInternal<Orthanc::PixelFormat_Grayscale8>(distribution, image);
175 break;
176
177 case Orthanc::PixelFormat_Grayscale16:
178 ReadDistributionInternal<Orthanc::PixelFormat_Grayscale16>(distribution, image);
179 break;
180
181 case Orthanc::PixelFormat_SignedGrayscale16:
182 ReadDistributionInternal<Orthanc::PixelFormat_SignedGrayscale16>(distribution, image);
183 break;
184
185 case Orthanc::PixelFormat_Grayscale32:
186 ReadDistributionInternal<Orthanc::PixelFormat_Grayscale32>(distribution, image);
187 break;
188
189 case Orthanc::PixelFormat_Grayscale64:
190 ReadDistributionInternal<Orthanc::PixelFormat_Grayscale64>(distribution, image);
191 break;
192
193 default:
194 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
195 }
196 }
197
198
199 class DoseInteractor : public VolumeImageInteractor
200 {
201 private:
202 SliceViewerWidget& widget_;
203 size_t layer_;
204 DicomFrameConverter converter_;
205
206
207
208 protected:
209 virtual void NotifySliceChange(const ISlicedVolume& slicedVolume,
210 const size_t& sliceIndex,
211 const Slice& slice)
212 {
213 converter_ = slice.GetConverter();
214
215 #if 0
216 const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume);
217
218 RenderStyle s = widget_.GetLayerStyle(layer_);
219
220 if (volume.FitWindowingToRange(s, slice.GetConverter()))
221 {
222 printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);
223 widget_.SetLayerStyle(layer_, s);
224 }
225 #endif
226 }
227
228 virtual void NotifyVolumeReady(const ISlicedVolume& slicedVolume)
229 {
230 const float percentile = 0.01f;
231 const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume);
232
233 std::vector<float> distribution;
234 ReadDistribution(distribution, volume.GetImage().GetInternalImage());
235 std::sort(distribution.begin(), distribution.end());
236
237 int start = static_cast<int>(std::ceil(distribution.size() * percentile));
238 int end = static_cast<int>(std::floor(distribution.size() * (1.0f - percentile)));
239
240 float a = 0;
241 float b = 0;
242
243 if (start < end &&
244 start >= 0 &&
245 end < static_cast<int>(distribution.size()))
246 {
247 a = distribution[start];
248 b = distribution[end];
249 }
250 else if (!distribution.empty())
251 {
252 // Too small distribution: Use full range
253 a = distribution.front();
254 b = distribution.back();
255 }
256
257 //printf("%f %f\n", a, b);
258
259 RenderStyle s = widget_.GetLayerStyle(layer_);
260 s.windowing_ = ImageWindowing_Custom;
261 s.customWindowCenter_ = static_cast<float>(converter_.Apply((a + b) / 2.0f));
262 s.customWindowWidth_ = static_cast<float>(converter_.Apply(b - a));
263
264 // 96.210556 => 192.421112
265 widget_.SetLayerStyle(layer_, s);
266 printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);
267 }
268
269 public:
270 DoseInteractor(MessageBroker& broker, OrthancVolumeImage& volume,
271 SliceViewerWidget& widget,
272 VolumeProjection projection,
273 size_t layer) :
274 VolumeImageInteractor(broker, volume, widget, projection),
275 widget_(widget),
276 layer_(layer)
277 {
278 }
279 };
280
281 class RtViewerDemoApplication :
282 public RtViewerDemoBaseApplicationSingleCanvas,
283 public IObserver
284 {
285 public:
286 std::vector<std::pair<SliceViewerWidget*, size_t> > doseCtWidgetLayerPairs_;
287 std::list<OrthancStone::IWorldSceneInteractor*> interactors_;
288
289 class Interactor : public IWorldSceneInteractor
290 {
291 private:
292 RtViewerDemoApplication& application_;
293
294 public:
295 Interactor(RtViewerDemoApplication& application) :
296 application_(application)
297 {
298 }
299
300 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
301 const ViewportGeometry& view,
302 MouseButton button,
303 KeyboardModifiers modifiers,
304 int viewportX,
305 int viewportY,
306 double x,
307 double y,
308 IStatusBar* statusBar,
309 const std::vector<Touch>& displayTouches)
310 {
311 return NULL;
312 }
313
314 virtual void MouseOver(CairoContext& context,
315 WorldSceneWidget& widget,
316 const ViewportGeometry& view,
317 double x,
318 double y,
319 IStatusBar* statusBar)
320 {
321 if (statusBar != NULL)
322 {
323 Vector p = dynamic_cast<SliceViewerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
324
325 char buf[64];
326 sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
327 p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
328 statusBar->SetMessage(buf);
329 }
330 }
331
332 virtual void MouseWheel(WorldSceneWidget& widget,
333 MouseWheelDirection direction,
334 KeyboardModifiers modifiers,
335 IStatusBar* statusBar)
336 {
337 int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1);
338
339 switch (direction)
340 {
341 case MouseWheelDirection_Up:
342 application_.OffsetSlice(-scale);
343 break;
344
345 case MouseWheelDirection_Down:
346 application_.OffsetSlice(scale);
347 break;
348
349 default:
350 break;
351 }
352 }
353
354 virtual void KeyPressed(WorldSceneWidget& widget,
355 KeyboardKeys key,
356 char keyChar,
357 KeyboardModifiers modifiers,
358 IStatusBar* statusBar)
359 {
360 switch (keyChar)
361 {
362 case 's':
363 // TODO: recursively traverse children
364 widget.FitContent();
365 break;
366
367 default:
368 break;
369 }
370 }
371 };
372
373 void OffsetSlice(int offset)
374 {
375 if (source_ != NULL)
376 {
377 int slice = static_cast<int>(slice_) + offset;
378
379 if (slice < 0)
380 {
381 slice = 0;
382 }
383
384 if (slice >= static_cast<int>(source_->GetSliceCount()))
385 {
386 slice = static_cast<int>(source_->GetSliceCount()) - 1;
387 }
388
389 if (slice != static_cast<int>(slice_))
390 {
391 SetSlice(slice);
392 }
393 }
394 }
395
396
397 SliceViewerWidget& GetMainWidget()
398 {
399 return *dynamic_cast<SliceViewerWidget*>(mainWidget_);
400 }
401
402
403 void SetSlice(size_t index)
404 {
405 if (source_ != NULL &&
406 index < source_->GetSliceCount())
407 {
408 slice_ = static_cast<unsigned int>(index);
409
410 #if 1
411 GetMainWidget().SetSlice(source_->GetSlice(slice_).GetGeometry());
412 #else
413 // TEST for scene extents - Rotate the axes
414 double a = 15.0 / 180.0 * boost::math::constants::pi<double>();
415
416 #if 1
417 Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
418 Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0);
419 #else
420 // Flip the normal
421 Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0);
422 Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0);
423 #endif
424
425 SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y);
426 widget_->SetSlice(s);
427 #endif
428 }
429 }
430
431
432 void OnMainWidgetGeometryReady(const IVolumeSlicer::GeometryReadyMessage& message)
433 {
434 // Once the geometry of the series is downloaded from Orthanc,
435 // display its middle slice, and adapt the viewport to fit this
436 // slice
437 if (source_ == &message.GetOrigin())
438 {
439 SetSlice(source_->GetSliceCount() / 2);
440 }
441
442 GetMainWidget().FitContent();
443 }
444
445 DicomFrameConverter converter_;
446
447 void OnSliceContentChangedMessage(const ISlicedVolume::SliceContentChangedMessage& message)
448 {
449 converter_ = message.GetSlice().GetConverter();
450 }
451
452 void OnVolumeReadyMessage(const ISlicedVolume::VolumeReadyMessage& message)
453 {
454 const float percentile = 0.01f;
455
456 auto& slicedVolume = message.GetOrigin();
457 const OrthancVolumeImage& volume = dynamic_cast<const OrthancVolumeImage&>(slicedVolume);
458
459 std::vector<float> distribution;
460 ReadDistribution(distribution, volume.GetImage().GetInternalImage());
461 std::sort(distribution.begin(), distribution.end());
462
463 int start = static_cast<int>(std::ceil(distribution.size() * percentile));
464 int end = static_cast<int>(std::floor(distribution.size() * (1.0f - percentile)));
465
466 float a = 0;
467 float b = 0;
468
469 if (start < end &&
470 start >= 0 &&
471 end < static_cast<int>(distribution.size()))
472 {
473 a = distribution[start];
474 b = distribution[end];
475 }
476 else if (!distribution.empty())
477 {
478 // Too small distribution: Use full range
479 a = distribution.front();
480 b = distribution.back();
481 }
482
483 //printf("WINDOWING %f %f\n", a, b);
484
485 for (const auto& pair : doseCtWidgetLayerPairs_)
486 {
487 auto widget = pair.first;
488 auto layer = pair.second;
489 RenderStyle s = widget->GetLayerStyle(layer);
490 s.windowing_ = ImageWindowing_Custom;
491 s.customWindowCenter_ = static_cast<float>(converter_.Apply((a + b) / 2.0f));
492 s.customWindowWidth_ = static_cast<float>(converter_.Apply(b - a));
493
494 // 96.210556 => 192.421112
495 widget->SetLayerStyle(layer, s);
496 printf("Windowing: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_);
497 }
498 }
499
500
501
502 size_t AddDoseLayer(SliceViewerWidget& widget,
503 OrthancVolumeImage& volume, VolumeProjection projection);
504
505 void AddStructLayer(
506 SliceViewerWidget& widget, StructureSetLoader& loader);
507
508 SliceViewerWidget* CreateDoseCtWidget(
509 std::auto_ptr<OrthancVolumeImage>& ct,
510 std::auto_ptr<OrthancVolumeImage>& dose,
511 std::auto_ptr<StructureSetLoader>& structLoader,
512 VolumeProjection projection);
513
514 void AddCtLayer(SliceViewerWidget& widget, OrthancVolumeImage& volume);
515
516 std::auto_ptr<Interactor> mainWidgetInteractor_;
517 const DicomSeriesVolumeSlicer* source_;
518 unsigned int slice_;
519
520 std::string ctSeries_;
521 std::string doseInstance_;
522 std::string doseSeries_;
523 std::string structInstance_;
524 std::auto_ptr<OrthancStone::OrthancVolumeImage> dose_;
525 std::auto_ptr<OrthancStone::OrthancVolumeImage> ct_;
526 std::auto_ptr<OrthancStone::StructureSetLoader> struct_;
527
528 public:
529 RtViewerDemoApplication(MessageBroker& broker) :
530 IObserver(broker),
531 source_(NULL),
532 slice_(0)
533 {
534 }
535
536 /*
537 dev options on bgo xps15
538
539 COMMAND LINE
540 --ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa --dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb --struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
541
542 URL PARAMETERS
543 ?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa&dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb&struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
544
545 */
546
547 void ParseParameters(const boost::program_options::variables_map& parameters)
548 {
549 // CT series
550 {
551
552 if (parameters.count("ct-series") != 1)
553 {
554 LOG(ERROR) << "There must be exactly one CT series specified";
555 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
556 }
557 ctSeries_ = parameters["ct-series"].as<std::string>();
558 }
559
560 // RTDOSE
561 {
562 if (parameters.count("dose-instance") == 1)
563 {
564 doseInstance_ = parameters["dose-instance"].as<std::string>();
565 }
566 else
567 {
568 #ifdef BGO_NOT_IMPLEMENTED_YET
569 // Dose series
570 if (parameters.count("dose-series") != 1)
571 {
572 LOG(ERROR) << "the RTDOSE series is missing";
573 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
574 }
575 doseSeries_ = parameters["ct"].as<std::string>();
576 #endif
577 LOG(ERROR) << "the RTSTRUCT instance is missing";
578 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
579 }
580 }
581
582 // RTSTRUCT
583 {
584 if (parameters.count("struct-instance") == 1)
585 {
586 structInstance_ = parameters["struct-instance"].as<std::string>();
587 }
588 else
589 {
590 #ifdef BGO_NOT_IMPLEMENTED_YET
591 // Struct series
592 if (parameters.count("struct-series") != 1)
593 {
594 LOG(ERROR) << "the RTSTRUCT series is missing";
595 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
596 }
597 structSeries_ = parametersstruct - series"].as<std::string>();
598 #endif
599 LOG(ERROR) << "the RTSTRUCT instance is missing";
600 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
601 }
602 }
603 }
604
605 virtual void DeclareStartupOptions(boost::program_options::options_description& options)
606 {
607 boost::program_options::options_description generic("RtViewerDemo options. Please note that some of these options are mutually exclusive");
608 generic.add_options()
609 ("ct-series", boost::program_options::value<std::string>(),"Orthanc ID of the CT series")
610 ("dose-instance", boost::program_options::value<std::string>(), "Orthanc ID of the RTDOSE instance (incompatible with dose-series)")
611 ("dose-series", boost::program_options::value<std::string>(), "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible with dose-instance)")
612 ("struct-instance", boost::program_options::value<std::string>(), "Orthanc ID of the RTSTRUCT instance (incompatible with struct-series)")
613 ("struct-series", boost::program_options::value<std::string>(), "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with struct-instance)")
614 ("smooth", boost::program_options::value<bool>()->default_value(true),"Enable bilinear image smoothing")
615 ;
616
617 options.add(generic);
618 }
619
620 virtual void Initialize(
621 StoneApplicationContext* context,
622 IStatusBar& statusBar,
623 const boost::program_options::variables_map& parameters)
624 {
625 using namespace OrthancStone;
626
627 ParseParameters(parameters);
628
629 context_ = context;
630
631 statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
632
633 if (!ctSeries_.empty())
634 {
635 printf("CT = [%s]\n", ctSeries_.c_str());
636
637 ct_.reset(new OrthancStone::OrthancVolumeImage(
638 IObserver::GetBroker(), context->GetOrthancApiClient(), false));
639 ct_->ScheduleLoadSeries(ctSeries_);
640 //ct_->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA
641 //ct_->ScheduleLoadSeries("03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA
642 }
643
644 if (!doseSeries_.empty() ||
645 !doseInstance_.empty())
646 {
647 dose_.reset(new OrthancStone::OrthancVolumeImage(
648 IObserver::GetBroker(), context->GetOrthancApiClient(), true));
649
650
651 dose_->RegisterObserverCallback(
652 new Callable<RtViewerDemoApplication, ISlicedVolume::VolumeReadyMessage>
653 (*this, &RtViewerDemoApplication::OnVolumeReadyMessage));
654
655 dose_->RegisterObserverCallback(
656 new Callable<RtViewerDemoApplication, ISlicedVolume::SliceContentChangedMessage>
657 (*this, &RtViewerDemoApplication::OnSliceContentChangedMessage));
658
659 if (doseInstance_.empty())
660 {
661 dose_->ScheduleLoadSeries(doseSeries_);
662 }
663 else
664 {
665 dose_->ScheduleLoadInstance(doseInstance_);
666 }
667
668 //dose_->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1
669 //dose_->ScheduleLoadInstance("269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); // 0522c0001 TCIA
670 }
671
672 if (!structInstance_.empty())
673 {
674 struct_.reset(new OrthancStone::StructureSetLoader(
675 IObserver::GetBroker(), context->GetOrthancApiClient()));
676
677 struct_->ScheduleLoadInstance(structInstance_);
678
679 //struct_->ScheduleLoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA
680 //struct_->ScheduleLoadInstance("17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA
681 }
682
683 mainWidget_ = new LayoutWidget("main-layout");
684 mainWidget_->SetBackgroundColor(0, 0, 0);
685 mainWidget_->SetBackgroundCleared(true);
686 mainWidget_->SetPadding(0);
687
688 auto axialWidget = CreateDoseCtWidget
689 (ct_, dose_, struct_, OrthancStone::VolumeProjection_Axial);
690 mainWidget_->AddWidget(axialWidget);
691
692 std::auto_ptr<OrthancStone::LayoutWidget> subLayout(
693 new OrthancStone::LayoutWidget("main-layout"));
694 subLayout->SetVertical();
695 subLayout->SetPadding(5);
696
697 auto coronalWidget = CreateDoseCtWidget
698 (ct_, dose_, struct_, OrthancStone::VolumeProjection_Coronal);
699 subLayout->AddWidget(coronalWidget);
700
701 auto sagittalWidget = CreateDoseCtWidget
702 (ct_, dose_, struct_, OrthancStone::VolumeProjection_Sagittal);
703 subLayout->AddWidget(sagittalWidget);
704
705 mainWidget_->AddWidget(subLayout.release());
706 }
707 };
708
709
710 size_t RtViewerDemoApplication::AddDoseLayer(
711 SliceViewerWidget& widget,
712 OrthancVolumeImage& volume, VolumeProjection projection)
713 {
714 size_t layer = widget.AddLayer(
715 new VolumeImageMPRSlicer(IObserver::GetBroker(), volume));
716
717 RenderStyle s;
718 //s.drawGrid_ = true;
719 s.SetColor(255, 0, 0); // Draw missing PET layer in red
720 s.alpha_ = 0.3f;
721 s.applyLut_ = true;
722 s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
723 s.interpolation_ = ImageInterpolation_Bilinear;
724 widget.SetLayerStyle(layer, s);
725
726 return layer;
727 }
728
729 void RtViewerDemoApplication::AddStructLayer(
730 SliceViewerWidget& widget, StructureSetLoader& loader)
731 {
732 widget.AddLayer(new DicomStructureSetSlicer(
733 IObserver::GetBroker(), loader));
734 }
735
736 SliceViewerWidget* RtViewerDemoApplication::CreateDoseCtWidget(
737 std::auto_ptr<OrthancVolumeImage>& ct,
738 std::auto_ptr<OrthancVolumeImage>& dose,
739 std::auto_ptr<StructureSetLoader>& structLoader,
740 VolumeProjection projection)
741 {
742 std::auto_ptr<OrthancStone::SliceViewerWidget> widget(
743 new OrthancStone::SliceViewerWidget(IObserver::GetBroker(),
744 "ct-dose-widget"));
745
746 if (ct.get() != NULL)
747 {
748 AddCtLayer(*widget, *ct);
749 }
750
751 if (dose.get() != NULL)
752 {
753 size_t layer = AddDoseLayer(*widget, *dose, projection);
754
755 // we need to store the dose rendering widget because we'll update them
756 // according to various asynchronous events
757 doseCtWidgetLayerPairs_.push_back(std::make_pair(widget.get(), layer));
758 #if 0
759 interactors_.push_back(new VolumeImageInteractor(
760 IObserver::GetBroker(), *dose, *widget, projection));
761 #else
762 interactors_.push_back(new DoseInteractor(
763 IObserver::GetBroker(), *dose, *widget, projection, layer));
764 #endif
765 }
766 else if (ct.get() != NULL)
767 {
768 interactors_.push_back(
769 new VolumeImageInteractor(
770 IObserver::GetBroker(), *ct, *widget, projection));
771 }
772
773 if (structLoader.get() != NULL)
774 {
775 AddStructLayer(*widget, *structLoader);
776 }
777
778 return widget.release();
779 }
780
781 void RtViewerDemoApplication::AddCtLayer(
782 SliceViewerWidget& widget,
783 OrthancVolumeImage& volume)
784 {
785 size_t layer = widget.AddLayer(
786 new VolumeImageMPRSlicer(IObserver::GetBroker(), volume));
787
788 RenderStyle s;
789 //s.drawGrid_ = true;
790 s.alpha_ = 1;
791 s.windowing_ = ImageWindowing_Bone;
792 widget.SetLayerStyle(layer, s);
793 }
794 }
795 }
796
797
798
799 #if ORTHANC_ENABLE_WASM==1
800
801 #include "Platforms/Wasm/WasmWebService.h"
802 #include "Platforms/Wasm/WasmViewport.h"
803
804 #include <emscripten/emscripten.h>
805
806 //#include "SampleList.h"
807
808
809 OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker)
810 {
811 return new OrthancStone::Samples::RtViewerDemoApplication(broker);
812 }
813
814 OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application)
815 {
816 return dynamic_cast<OrthancStone::Samples::RtViewerDemoApplication*>(application)->CreateWasmApplicationAdapter(broker);
817 }
818
819 #else
820
821 //#include "SampleList.h"
822 #if ORTHANC_ENABLE_SDL==1
823 #include "Applications/Sdl/SdlStoneApplicationRunner.h"
824 #endif
825 #if ORTHANC_ENABLE_QT==1
826 #include "Applications/Qt/SampleQtApplicationRunner.h"
827 #endif
828 #include "Framework/Messages/MessageBroker.h"
829
830 int main(int argc, char* argv[])
831 {
832 OrthancStone::MessageBroker broker;
833 OrthancStone::Samples::RtViewerDemoApplication sampleStoneApplication(broker);
834
835 #if ORTHANC_ENABLE_SDL==1
836 OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication);
837 return sdlApplicationRunner.Execute(argc, argv);
838 #endif
839 #if ORTHANC_ENABLE_QT==1
840 OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication);
841 return qtAppRunner.Execute(argc, argv);
842 #endif
843 }
844
845
846 #endif
847
848
849
850
851
852
853