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