Mercurial > hg > orthanc-stone
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 |