comparison Applications/Samples/Deprecated/rt-viewer-demo/main.cpp @ 1347:bfd77672d825 broker

Moved Application/Samples/* to Application/Samples/Deprecated/*
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 07 Apr 2020 14:29:01 +0200
parents Applications/Samples/rt-viewer-demo/main.cpp@8a0a62189f46
children c7d98d750224
comparison
equal deleted inserted replaced
1346:df8bf351c23f 1347:bfd77672d825
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-2020 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::unique_ptr<OrthancVolumeImage>& ct,
510 std::unique_ptr<OrthancVolumeImage>& dose,
511 std::unique_ptr<StructureSetLoader>& structLoader,
512 VolumeProjection projection);
513
514 void AddCtLayer(SliceViewerWidget& widget, OrthancVolumeImage& volume);
515
516 std::unique_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::unique_ptr<OrthancStone::OrthancVolumeImage> dose_;
525 std::unique_ptr<OrthancStone::OrthancVolumeImage> ct_;
526 std::unique_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 // Generic
550 {
551 if (parameters.count("verbose"))
552 {
553 Orthanc::Logging::EnableInfoLevel(true);
554 LOG(INFO) << "Verbose logs (info) are enabled";
555 }
556 }
557
558 {
559 if (parameters.count("trace"))
560 {
561 LOG(INFO) << "parameters.count(\"trace\") != 0";
562 Orthanc::Logging::EnableTraceLevel(true);
563 VLOG(1) << "Trace logs (debug) are enabled";
564 }
565 }
566
567 // CT series
568 {
569
570 if (parameters.count("ct-series") != 1)
571 {
572 LOG(ERROR) << "There must be exactly one CT series specified";
573 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
574 }
575 ctSeries_ = parameters["ct-series"].as<std::string>();
576 }
577
578 // RTDOSE
579 {
580 if (parameters.count("dose-instance") == 1)
581 {
582 doseInstance_ = parameters["dose-instance"].as<std::string>();
583 }
584 else
585 {
586 #ifdef BGO_NOT_IMPLEMENTED_YET
587 // Dose series
588 if (parameters.count("dose-series") != 1)
589 {
590 LOG(ERROR) << "the RTDOSE series is missing";
591 throw Orthanc::OrthancException(
592 Orthanc::ErrorCode_ParameterOutOfRange);
593 }
594 doseSeries_ = parameters["ct"].as<std::string>();
595 #endif
596 LOG(ERROR) << "the RTSTRUCT instance is missing";
597 throw Orthanc::OrthancException(
598 Orthanc::ErrorCode_ParameterOutOfRange);
599 }
600 }
601
602 // RTSTRUCT
603 {
604 if (parameters.count("struct-instance") == 1)
605 {
606 structInstance_ = parameters["struct-instance"].as<std::string>();
607 }
608 else
609 {
610 #ifdef BGO_NOT_IMPLEMENTED_YET
611 // Struct series
612 if (parameters.count("struct-series") != 1)
613 {
614 LOG(ERROR) << "the RTSTRUCT series is missing";
615 throw Orthanc::OrthancException(
616 Orthanc::ErrorCode_ParameterOutOfRange);
617 }
618 structSeries_ = parameters["struct-series"].as<std::string>();
619 #endif
620 LOG(ERROR) << "the RTSTRUCT instance is missing";
621 throw Orthanc::OrthancException(
622 Orthanc::ErrorCode_ParameterOutOfRange);
623 }
624 }
625 }
626
627 virtual void DeclareStartupOptions(
628 boost::program_options::options_description& options)
629 {
630 boost::program_options::options_description generic(
631 "RtViewerDemo options. Please note that some of these options "
632 "are mutually exclusive");
633 generic.add_options()
634 ("ct-series", boost::program_options::value<std::string>(),
635 "Orthanc ID of the CT series")
636 ("dose-instance", boost::program_options::value<std::string>(),
637 "Orthanc ID of the RTDOSE instance (incompatible with dose-series)")
638 ("dose-series", boost::program_options::value<std::string>(),
639 "NOT IMPLEMENTED YET. Orthanc ID of the RTDOSE series (incompatible"
640 " with dose-instance)")
641 ("struct-instance", boost::program_options::value<std::string>(),
642 "Orthanc ID of the RTSTRUCT instance (incompatible with struct-"
643 "series)")
644 ("struct-series", boost::program_options::value<std::string>(),
645 "NOT IMPLEMENTED YET. Orthanc ID of the RTSTRUCT (incompatible with"
646 " struct-instance)")
647 ("smooth", boost::program_options::value<bool>()->default_value(true),
648 "Enable bilinear image smoothing")
649 ;
650
651 options.add(generic);
652 }
653
654 virtual void Initialize(
655 StoneApplicationContext* context,
656 IStatusBar& statusBar,
657 const boost::program_options::variables_map& parameters)
658 {
659 using namespace OrthancStone;
660
661 ParseParameters(parameters);
662
663 context_ = context;
664
665 statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
666
667 if (!ctSeries_.empty())
668 {
669 printf("CT = [%s]\n", ctSeries_.c_str());
670
671 ct_.reset(new OrthancStone::OrthancVolumeImage(
672 IObserver::GetBroker(), context->GetOrthancApiClient(), false));
673 ct_->ScheduleLoadSeries(ctSeries_);
674 //ct_->ScheduleLoadSeries(
675 // "a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // IBA
676 //ct_->ScheduleLoadSeries(
677 // "03677739-1d8bca40-db1daf59-d74ff548-7f6fc9c0"); // 0522c0001 TCIA
678 }
679
680 if (!doseSeries_.empty() ||
681 !doseInstance_.empty())
682 {
683 dose_.reset(new OrthancStone::OrthancVolumeImage(
684 IObserver::GetBroker(), context->GetOrthancApiClient(), true));
685
686
687 dose_->RegisterObserverCallback(
688 new Callable<RtViewerDemoApplication, ISlicedVolume::VolumeReadyMessage>
689 (*this, &RtViewerDemoApplication::OnVolumeReadyMessage));
690
691 dose_->RegisterObserverCallback(
692 new Callable<RtViewerDemoApplication, ISlicedVolume::SliceContentChangedMessage>
693 (*this, &RtViewerDemoApplication::OnSliceContentChangedMessage));
694
695 if (doseInstance_.empty())
696 {
697 dose_->ScheduleLoadSeries(doseSeries_);
698 }
699 else
700 {
701 dose_->ScheduleLoadInstance(doseInstance_);
702 }
703
704 //dose_->ScheduleLoadInstance(
705 //"830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // IBA 1
706 //dose_->ScheduleLoadInstance(
707 //"269f26f4-0c83eeeb-2e67abbd-5467a40f-f1bec90c"); //0522c0001 TCIA
708 }
709
710 if (!structInstance_.empty())
711 {
712 struct_.reset(new OrthancStone::StructureSetLoader(
713 IObserver::GetBroker(), context->GetOrthancApiClient()));
714
715 struct_->ScheduleLoadInstance(structInstance_);
716
717 //struct_->ScheduleLoadInstance(
718 //"54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // IBA
719 //struct_->ScheduleLoadInstance(
720 //"17cd032b-ad92a438-ca05f06a-f9e96668-7e3e9e20"); // 0522c0001 TCIA
721 }
722
723 mainWidget_ = new LayoutWidget("main-layout");
724 mainWidget_->SetBackgroundColor(0, 0, 0);
725 mainWidget_->SetBackgroundCleared(true);
726 mainWidget_->SetPadding(0);
727
728 auto axialWidget = CreateDoseCtWidget
729 (ct_, dose_, struct_, OrthancStone::VolumeProjection_Axial);
730 mainWidget_->AddWidget(axialWidget);
731
732 std::unique_ptr<OrthancStone::LayoutWidget> subLayout(
733 new OrthancStone::LayoutWidget("main-layout"));
734 subLayout->SetVertical();
735 subLayout->SetPadding(5);
736
737 auto coronalWidget = CreateDoseCtWidget
738 (ct_, dose_, struct_, OrthancStone::VolumeProjection_Coronal);
739 subLayout->AddWidget(coronalWidget);
740
741 auto sagittalWidget = CreateDoseCtWidget
742 (ct_, dose_, struct_, OrthancStone::VolumeProjection_Sagittal);
743 subLayout->AddWidget(sagittalWidget);
744
745 mainWidget_->AddWidget(subLayout.release());
746 }
747 };
748
749
750 size_t RtViewerDemoApplication::AddDoseLayer(
751 SliceViewerWidget& widget,
752 OrthancVolumeImage& volume, VolumeProjection projection)
753 {
754 size_t layer = widget.AddLayer(
755 new VolumeImageMPRSlicer(IObserver::GetBroker(), volume));
756
757 RenderStyle s;
758 //s.drawGrid_ = true;
759 s.SetColor(255, 0, 0); // Draw missing PET layer in red
760 s.alpha_ = 0.3f;
761 s.applyLut_ = true;
762 s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET;
763 s.interpolation_ = ImageInterpolation_Bilinear;
764 widget.SetLayerStyle(layer, s);
765
766 return layer;
767 }
768
769 void RtViewerDemoApplication::AddStructLayer(
770 SliceViewerWidget& widget, StructureSetLoader& loader)
771 {
772 widget.AddLayer(new DicomStructureSetSlicer(
773 IObserver::GetBroker(), loader));
774 }
775
776 SliceViewerWidget* RtViewerDemoApplication::CreateDoseCtWidget(
777 std::unique_ptr<OrthancVolumeImage>& ct,
778 std::unique_ptr<OrthancVolumeImage>& dose,
779 std::unique_ptr<StructureSetLoader>& structLoader,
780 VolumeProjection projection)
781 {
782 std::unique_ptr<OrthancStone::SliceViewerWidget> widget(
783 new OrthancStone::SliceViewerWidget(IObserver::GetBroker(),
784 "ct-dose-widget"));
785
786 if (ct.get() != NULL)
787 {
788 AddCtLayer(*widget, *ct);
789 }
790
791 if (dose.get() != NULL)
792 {
793 size_t layer = AddDoseLayer(*widget, *dose, projection);
794
795 // we need to store the dose rendering widget because we'll update them
796 // according to various asynchronous events
797 doseCtWidgetLayerPairs_.push_back(std::make_pair(widget.get(), layer));
798 #if 0
799 interactors_.push_back(new VolumeImageInteractor(
800 IObserver::GetBroker(), *dose, *widget, projection));
801 #else
802 interactors_.push_back(new DoseInteractor(
803 IObserver::GetBroker(), *dose, *widget, projection, layer));
804 #endif
805 }
806 else if (ct.get() != NULL)
807 {
808 interactors_.push_back(
809 new VolumeImageInteractor(
810 IObserver::GetBroker(), *ct, *widget, projection));
811 }
812
813 if (structLoader.get() != NULL)
814 {
815 AddStructLayer(*widget, *structLoader);
816 }
817
818 return widget.release();
819 }
820
821 void RtViewerDemoApplication::AddCtLayer(
822 SliceViewerWidget& widget,
823 OrthancVolumeImage& volume)
824 {
825 size_t layer = widget.AddLayer(
826 new VolumeImageMPRSlicer(IObserver::GetBroker(), volume));
827
828 RenderStyle s;
829 //s.drawGrid_ = true;
830 s.alpha_ = 1;
831 s.windowing_ = ImageWindowing_Bone;
832 widget.SetLayerStyle(layer, s);
833 }
834 }
835 }
836
837
838
839 #if ORTHANC_ENABLE_WASM==1
840
841 #include "Platforms/Wasm/WasmWebService.h"
842 #include "Platforms/Wasm/WasmViewport.h"
843
844 #include <emscripten/emscripten.h>
845
846 //#include "SampleList.h"
847
848
849 OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker)
850 {
851 return new OrthancStone::Samples::RtViewerDemoApplication(broker);
852 }
853
854 OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application)
855 {
856 return dynamic_cast<OrthancStone::Samples::RtViewerDemoApplication*>(application)->CreateWasmApplicationAdapter(broker);
857 }
858
859 #else
860
861 //#include "SampleList.h"
862 #if ORTHANC_ENABLE_SDL==1
863 #include "Applications/Sdl/SdlStoneApplicationRunner.h"
864 #endif
865 #if ORTHANC_ENABLE_QT==1
866 #include "Applications/Qt/SampleQtApplicationRunner.h"
867 #endif
868 #include "Framework/Messages/MessageBroker.h"
869
870 int main(int argc, char* argv[])
871 {
872 OrthancStone::MessageBroker broker;
873 OrthancStone::Samples::RtViewerDemoApplication sampleStoneApplication(broker);
874
875 #if ORTHANC_ENABLE_SDL==1
876 OrthancStone::SdlStoneApplicationRunner sdlApplicationRunner(broker, sampleStoneApplication);
877 return sdlApplicationRunner.Execute(argc, argv);
878 #endif
879 #if ORTHANC_ENABLE_QT==1
880 OrthancStone::Samples::SampleQtApplicationRunner qtAppRunner(broker, sampleStoneApplication);
881 return qtAppRunner.Execute(argc, argv);
882 #endif
883 }
884
885
886 #endif
887
888
889
890
891
892
893