comparison Samples/Common/RtViewer.cpp @ 1383:ab871499ed30

SingleFrameViewer: refactored file locations + names to share files for RtViewer
author Benjamin Golinvaux <bgo@osimis.io>
date Mon, 27 Apr 2020 10:01:03 +0200
parents
children dfb48f0794b1
comparison
equal deleted inserted replaced
1382:9d138883be66 1383:ab871499ed30
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 "RtViewer.h"
22
23 #include <stdio.h>
24
25 #include <boost/shared_ptr.hpp>
26 #include <boost/weak_ptr.hpp>
27 #include <boost/make_shared.hpp>
28
29 #include <Core/Images/Image.h>
30 #include <Core/Images/ImageProcessing.h>
31 #include <Core/Images/PngWriter.h>
32 #include <Core/Logging.h>
33 #include <Core/OrthancException.h>
34
35 #include <Framework/OpenGL/SdlOpenGLContext.h>
36 #include <Framework/StoneInitialization.h>
37
38 #include <Framework/Scene2D/CairoCompositor.h>
39 #include <Framework/Scene2D/ColorTextureSceneLayer.h>
40 #include <Framework/Scene2D/OpenGLCompositor.h>
41 #include <Framework/Scene2D/PanSceneTracker.h>
42 #include <Framework/Scene2D/ZoomSceneTracker.h>
43 #include <Framework/Scene2D/RotateSceneTracker.h>
44
45 #include <Framework/Scene2DViewport/UndoStack.h>
46 #include <Framework/Scene2DViewport/CreateLineMeasureTracker.h>
47 #include <Framework/Scene2DViewport/CreateAngleMeasureTracker.h>
48 #include <Framework/Scene2DViewport/IFlexiblePointerTracker.h>
49 #include <Framework/Scene2DViewport/MeasureTool.h>
50 #include <Framework/Scene2DViewport/PredeclaredTypes.h>
51 #include <Framework/Volumes/VolumeSceneLayerSource.h>
52
53 #include <Framework/Oracle/GetOrthancWebViewerJpegCommand.h>
54 #include <Framework/Oracle/ThreadedOracle.h>
55 #include <Framework/Scene2D/GrayscaleStyleConfigurator.h>
56 #include <Framework/Scene2D/LookupTableStyleConfigurator.h>
57 #include <Framework/Volumes/DicomVolumeImageMPRSlicer.h>
58 #include <Framework/StoneException.h>
59
60 namespace OrthancStone
61 {
62 const char* RtViewerGuiToolToString(size_t i)
63 {
64 static const char* descs[] = {
65 "RtViewerGuiTool_Rotate",
66 "RtViewerGuiTool_Pan",
67 "RtViewerGuiTool_Zoom",
68 "RtViewerGuiTool_LineMeasure",
69 "RtViewerGuiTool_CircleMeasure",
70 "RtViewerGuiTool_AngleMeasure",
71 "RtViewerGuiTool_EllipseMeasure",
72 "RtViewerGuiTool_LAST"
73 };
74 if (i >= RtViewerGuiTool_LAST)
75 {
76 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index");
77 }
78 return descs[i];
79 }
80
81 void RtViewerApp::SelectNextTool()
82 {
83 currentTool_ = static_cast<RtViewerGuiTool>(currentTool_ + 1);
84 if (currentTool_ == RtViewerGuiTool_LAST)
85 currentTool_ = static_cast<RtViewerGuiTool>(0);;
86 printf("Current tool is now: %s\n", RtViewerGuiToolToString(currentTool_));
87 }
88
89 void RtViewerApp::DisplayInfoText()
90 {
91 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
92 ViewportController& controller = lock->GetController();
93 Scene2D& scene = controller.GetScene();
94
95 // do not try to use stuff too early!
96 OrthancStone::ICompositor& compositor = lock->GetCompositor();
97
98 std::stringstream msg;
99
100 for (std::map<std::string, std::string>::const_iterator kv = infoTextMap_.begin();
101 kv != infoTextMap_.end(); ++kv)
102 {
103 msg << kv->first << " : " << kv->second << std::endl;
104 }
105 std::string msgS = msg.str();
106
107 TextSceneLayer* layerP = NULL;
108 if (scene.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
109 {
110 TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>(
111 scene.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
112 layerP = &layer;
113 }
114 else
115 {
116 std::unique_ptr<TextSceneLayer> layer(new TextSceneLayer);
117 layerP = layer.get();
118 layer->SetColor(0, 255, 0);
119 layer->SetFontIndex(1);
120 layer->SetBorder(20);
121 layer->SetAnchor(BitmapAnchor_TopLeft);
122 //layer->SetPosition(0,0);
123 scene.SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
124 }
125 // position the fixed info text in the upper right corner
126 layerP->SetText(msgS.c_str());
127 double cX = compositor.GetCanvasWidth() * (-0.5);
128 double cY = compositor.GetCanvasHeight() * (-0.5);
129 scene.GetCanvasToSceneTransform().Apply(cX, cY);
130 layerP->SetPosition(cX, cY);
131 lock->Invalidate();
132 }
133
134 void RtViewerApp::DisplayFloatingCtrlInfoText(const PointerEvent& e)
135 {
136 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
137 ViewportController& controller = lock->GetController();
138 Scene2D& scene = controller.GetScene();
139
140 ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
141
142 char buf[128];
143 sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)",
144 p.GetX(), p.GetY(),
145 e.GetMainPosition().GetX(), e.GetMainPosition().GetY());
146
147 if (scene.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
148 {
149 TextSceneLayer& layer =
150 dynamic_cast<TextSceneLayer&>(scene.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
151 layer.SetText(buf);
152 layer.SetPosition(p.GetX(), p.GetY());
153 }
154 else
155 {
156 std::unique_ptr<TextSceneLayer> layer(new TextSceneLayer);
157 layer->SetColor(0, 255, 0);
158 layer->SetText(buf);
159 layer->SetBorder(20);
160 layer->SetAnchor(BitmapAnchor_BottomCenter);
161 layer->SetPosition(p.GetX(), p.GetY());
162 scene.SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
163 }
164 }
165
166 void RtViewerApp::HideInfoText()
167 {
168 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
169 ViewportController& controller = lock->GetController();
170 Scene2D& scene = controller.GetScene();
171
172 scene.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
173 }
174
175 void RtViewerApp::HandleApplicationEvent(
176 const SDL_Event& event)
177 {
178 //DisplayInfoText();
179
180 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
181 ViewportController& controller = lock->GetController();
182 Scene2D& scene = controller.GetScene();
183 ICompositor& compositor = lock->GetCompositor();
184
185 if (event.type == SDL_MOUSEMOTION)
186 {
187 int scancodeCount = 0;
188 const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
189
190 if (activeTracker_.get() == NULL &&
191 SDL_SCANCODE_LALT < scancodeCount &&
192 keyboardState[SDL_SCANCODE_LALT])
193 {
194 // The "left-ctrl" key is down, while no tracker is present
195 // Let's display the info text
196 PointerEvent e;
197 e.AddPosition(compositor.GetPixelCenterCoordinates(
198 event.button.x, event.button.y));
199
200 DisplayFloatingCtrlInfoText(e);
201 }
202 else
203 {
204 HideInfoText();
205 //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)";
206 if (activeTracker_.get() != NULL)
207 {
208 //LOG(TRACE) << "(activeTracker_.get() != NULL)";
209 PointerEvent e;
210 e.AddPosition(compositor.GetPixelCenterCoordinates(
211 event.button.x, event.button.y));
212
213 //LOG(TRACE) << "event.button.x = " << event.button.x << " " <<
214 // "event.button.y = " << event.button.y;
215 LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
216 e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
217
218 activeTracker_->PointerMove(e);
219 if (!activeTracker_->IsAlive())
220 activeTracker_.reset();
221 }
222 }
223 }
224 else if (event.type == SDL_MOUSEBUTTONUP)
225 {
226 if (activeTracker_)
227 {
228 PointerEvent e;
229 e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
230 activeTracker_->PointerUp(e);
231 if (!activeTracker_->IsAlive())
232 activeTracker_.reset();
233 }
234 }
235 else if (event.type == SDL_MOUSEBUTTONDOWN)
236 {
237 PointerEvent e;
238 e.AddPosition(compositor.GetPixelCenterCoordinates(
239 event.button.x, event.button.y));
240 if (activeTracker_)
241 {
242 activeTracker_->PointerDown(e);
243 if (!activeTracker_->IsAlive())
244 activeTracker_.reset();
245 }
246 else
247 {
248 // we ATTEMPT to create a tracker if need be
249 activeTracker_ = CreateSuitableTracker(event, e);
250 }
251 }
252 else if (event.type == SDL_KEYDOWN &&
253 event.key.repeat == 0 /* Ignore key bounce */)
254 {
255 switch (event.key.keysym.sym)
256 {
257 case SDLK_ESCAPE:
258 if (activeTracker_)
259 {
260 activeTracker_->Cancel();
261 if (!activeTracker_->IsAlive())
262 activeTracker_.reset();
263 }
264 break;
265
266 case SDLK_t:
267 if (!activeTracker_)
268 SelectNextTool();
269 else
270 {
271 LOG(WARNING) << "You cannot change the active tool when an interaction"
272 " is taking place";
273 }
274 break;
275 case SDLK_s:
276 compositor.FitContent(scene);
277 break;
278
279 case SDLK_z:
280 LOG(TRACE) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
281 if (event.key.keysym.mod & KMOD_CTRL)
282 {
283 if (controller.CanUndo())
284 {
285 LOG(TRACE) << "Undoing...";
286 controller.Undo();
287 }
288 else
289 {
290 LOG(WARNING) << "Nothing to undo!!!";
291 }
292 }
293 break;
294
295 case SDLK_y:
296 LOG(TRACE) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
297 if (event.key.keysym.mod & KMOD_CTRL)
298 {
299 if (controller.CanRedo())
300 {
301 LOG(TRACE) << "Redoing...";
302 controller.Redo();
303 }
304 else
305 {
306 LOG(WARNING) << "Nothing to redo!!!";
307 }
308 }
309 break;
310
311 case SDLK_c:
312 TakeScreenshot(
313 "screenshot.png",
314 compositor.GetCanvasWidth(),
315 compositor.GetCanvasHeight());
316 break;
317
318 default:
319 break;
320 }
321 }
322 else if (viewport_->IsRefreshEvent(event))
323 {
324 viewport_->Paint();
325 }
326 }
327
328 void RtViewerApp::OnSceneTransformChanged(
329 const ViewportController::SceneTransformChanged& message)
330 {
331 DisplayInfoText();
332 }
333
334 void RtViewerApp::RetrieveGeometry()
335 {
336 ORTHANC_ASSERT(geometryProvider_.get() != NULL);
337 ORTHANC_ASSERT(geometryProvider_->HasGeometry());
338 const VolumeImageGeometry& geometry = geometryProvider_->GetImageGeometry();
339
340 const unsigned int depth = geometry.GetProjectionDepth(projection_);
341 currentPlane_ = depth / 2;
342
343 planes_.resize(depth);
344
345 for (unsigned int z = 0; z < depth; z++)
346 {
347 planes_[z] = geometry.GetProjectionSlice(projection_, z);
348 }
349
350 UpdateLayers();
351
352 std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
353 lock->GetCompositor().FitContent(lock->GetController().GetScene());
354 lock->Invalidate();
355 }
356
357 void RtViewerApp::FitContent()
358 {
359 std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
360 lock->GetCompositor().FitContent(lock->GetController().GetScene());
361 lock->Invalidate();
362 }
363
364 void RtViewerApp::UpdateLayers()
365 {
366 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
367 if ((planes_.size() == 0)
368 && (geometryProvider_.get() != NULL)
369 && (geometryProvider_->HasGeometry()))
370 {
371 RetrieveGeometry();
372 }
373
374 if (currentPlane_ < planes_.size())
375 {
376 if (ctVolumeLayerSource_.get() != NULL)
377 {
378 ctVolumeLayerSource_->Update(planes_[currentPlane_]);
379 }
380 if (doseVolumeLayerSource_.get() != NULL)
381 {
382 doseVolumeLayerSource_->Update(planes_[currentPlane_]);
383 }
384 if (structLayerSource_.get() != NULL)
385 {
386 structLayerSource_->Update(planes_[currentPlane_]);
387 }
388 }
389 lock->Invalidate();
390 }
391
392 boost::shared_ptr<IFlexiblePointerTracker> RtViewerApp::CreateSuitableTracker(
393 const SDL_Event& event,
394 const PointerEvent& e)
395 {
396 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
397 ViewportController& controller = lock->GetController();
398 Scene2D& scene = controller.GetScene();
399 ICompositor& compositor = lock->GetCompositor();
400
401 using namespace Orthanc;
402
403 switch (event.button.button)
404 {
405 case SDL_BUTTON_MIDDLE:
406 return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker
407 (viewport_, e));
408
409 case SDL_BUTTON_RIGHT:
410 return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker
411 (viewport_, e, compositor.GetCanvasHeight()));
412
413 case SDL_BUTTON_LEFT:
414 {
415 //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:";
416 // TODO: we need to iterate on the set of measuring tool and perform
417 // a hit test to check if a tracker needs to be created for edition.
418 // Otherwise, depending upon the active tool, we might want to create
419 // a "measuring tool creation" tracker
420
421 // TODO: if there are conflicts, we should prefer a tracker that
422 // pertains to the type of measuring tool currently selected (TBD?)
423 boost::shared_ptr<IFlexiblePointerTracker> hitTestTracker = TrackerHitTest(e);
424
425 if (hitTestTracker != NULL)
426 {
427 //LOG(TRACE) << "hitTestTracker != NULL";
428 return hitTestTracker;
429 }
430 else
431 {
432 switch (currentTool_)
433 {
434 case RtViewerGuiTool_Rotate:
435 //LOG(TRACE) << "Creating RotateSceneTracker";
436 return boost::shared_ptr<IFlexiblePointerTracker>(new RotateSceneTracker(viewport_, e));
437 case RtViewerGuiTool_Pan:
438 return boost::shared_ptr<IFlexiblePointerTracker>(new PanSceneTracker(viewport_, e));
439 case RtViewerGuiTool_Zoom:
440 return boost::shared_ptr<IFlexiblePointerTracker>(new ZoomSceneTracker(viewport_, e, compositor.GetCanvasHeight()));
441 //case GuiTool_AngleMeasure:
442 // return new AngleMeasureTracker(GetScene(), e);
443 //case GuiTool_CircleMeasure:
444 // return new CircleMeasureTracker(GetScene(), e);
445 //case GuiTool_EllipseMeasure:
446 // return new EllipseMeasureTracker(GetScene(), e);
447 case RtViewerGuiTool_LineMeasure:
448 return boost::shared_ptr<IFlexiblePointerTracker>(new CreateLineMeasureTracker(viewport_, e));
449 case RtViewerGuiTool_AngleMeasure:
450 return boost::shared_ptr<IFlexiblePointerTracker>(new CreateAngleMeasureTracker(viewport_, e));
451 case RtViewerGuiTool_CircleMeasure:
452 LOG(ERROR) << "Not implemented yet!";
453 return boost::shared_ptr<IFlexiblePointerTracker>();
454 case RtViewerGuiTool_EllipseMeasure:
455 LOG(ERROR) << "Not implemented yet!";
456 return boost::shared_ptr<IFlexiblePointerTracker>();
457 default:
458 throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
459 }
460 }
461 }
462 default:
463 return boost::shared_ptr<IFlexiblePointerTracker>();
464 }
465 }
466
467
468 RtViewerApp::RtViewerApp()
469 : oracle_(*this)
470 , currentTool_(RtViewerGuiTool_Rotate)
471 , undoStack_(new UndoStack)
472 , currentPlane_(0)
473 , projection_(VolumeProjection_Coronal)
474 {
475 loadersContext_.reset(new GenericLoadersContext(1, 4, 1));
476 loadersContext_->StartOracle();
477
478 // False means we do NOT let Windows treat this as a legacy application that needs to be scaled
479 viewport_ = SdlOpenGLViewport::Create("CT RTDOSE RTSTRUCT viewer", 1024, 1024, false);
480
481 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
482 ViewportController& controller = lock->GetController();
483 Scene2D& scene = controller.GetScene();
484
485 //oracleObservable.RegisterObserverCallback
486 //(new Callable
487 // <RtViewerApp, SleepOracleCommand::TimeoutMessage>(*this, &RtViewerApp::Handle));
488
489 //oracleObservable.RegisterObserverCallback
490 //(new Callable
491 // <Toto, GetOrthancImageCommand::SuccessMessage>(*this, &RtViewerApp::Handle));
492
493 //oracleObservable.RegisterObserverCallback
494 //(new Callable
495 // <RtViewerApp, GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &ToRtViewerAppto::Handle));
496
497
498 TEXTURE_2x2_1_ZINDEX = 1;
499 TEXTURE_1x1_ZINDEX = 2;
500 TEXTURE_2x2_2_ZINDEX = 3;
501 LINESET_1_ZINDEX = 4;
502 LINESET_2_ZINDEX = 5;
503 FLOATING_INFOTEXT_LAYER_ZINDEX = 6;
504 FIXED_INFOTEXT_LAYER_ZINDEX = 7;
505 }
506
507 void RtViewerApp::RegisterMessages()
508 {
509 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
510 ViewportController& controller = lock->GetController();
511 Scene2D& scene = controller.GetScene();
512 Register<OracleCommandExceptionMessage>(oracleObservable_, &RtViewerApp::Handle);
513 Register<ViewportController::SceneTransformChanged>(controller, &RtViewerApp::OnSceneTransformChanged);
514 }
515
516 boost::shared_ptr<RtViewerApp> RtViewerApp::Create()
517 {
518 boost::shared_ptr<RtViewerApp> thisOne(new RtViewerApp());
519 thisOne->RegisterMessages();
520 return thisOne;
521 }
522
523 void RtViewerApp::PrepareScene()
524 {
525 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
526 ViewportController& controller = lock->GetController();
527 Scene2D& scene = controller.GetScene();
528
529 // Texture of 2x2 size
530 {
531 Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
532
533 uint8_t* p = reinterpret_cast<uint8_t*>(i.GetRow(0));
534 p[0] = 255;
535 p[1] = 0;
536 p[2] = 0;
537
538 p[3] = 0;
539 p[4] = 255;
540 p[5] = 0;
541
542 p = reinterpret_cast<uint8_t*>(i.GetRow(1));
543 p[0] = 0;
544 p[1] = 0;
545 p[2] = 255;
546
547 p[3] = 255;
548 p[4] = 0;
549 p[5] = 0;
550
551 scene.SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i));
552 }
553 }
554
555 void RtViewerApp::DisableTracker()
556 {
557 if (activeTracker_)
558 {
559 activeTracker_->Cancel();
560 activeTracker_.reset();
561 }
562 }
563
564 void RtViewerApp::TakeScreenshot(const std::string& target,
565 unsigned int canvasWidth,
566 unsigned int canvasHeight)
567 {
568 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
569 ViewportController& controller = lock->GetController();
570 Scene2D& scene = controller.GetScene();
571
572 CairoCompositor compositor(canvasWidth, canvasHeight);
573 compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1);
574 compositor.Refresh(scene);
575
576 Orthanc::ImageAccessor canvas;
577 compositor.GetCanvas().GetReadOnlyAccessor(canvas);
578
579 Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
580 Orthanc::ImageProcessing::Convert(png, canvas);
581
582 Orthanc::PngWriter writer;
583 writer.WriteToFile(target, png);
584 }
585
586 boost::shared_ptr<IFlexiblePointerTracker> RtViewerApp::TrackerHitTest(const PointerEvent& e)
587 {
588 // std::vector<boost::shared_ptr<MeasureTool>> measureTools_;
589 return boost::shared_ptr<IFlexiblePointerTracker>();
590 }
591
592 static void GLAPIENTRY
593 OpenGLMessageCallback(GLenum source,
594 GLenum type,
595 GLuint id,
596 GLenum severity,
597 GLsizei length,
598 const GLchar* message,
599 const void* userParam)
600 {
601 if (severity != GL_DEBUG_SEVERITY_NOTIFICATION)
602 {
603 fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
604 (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
605 type, severity, message);
606 }
607 }
608
609 static bool g_stopApplication = false;
610
611
612 void RtViewerApp::Handle(const DicomVolumeImage::GeometryReadyMessage& message)
613 {
614 RetrieveGeometry();
615 }
616
617
618 void RtViewerApp::Handle(const OracleCommandExceptionMessage& message)
619 {
620 const OracleCommandBase& command = dynamic_cast<const OracleCommandBase&>(message.GetOrigin());
621
622 printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), command.GetType());
623
624 switch (command.GetType())
625 {
626 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
627 printf("URI: [%s]\n", dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command).GetUri().c_str());
628 break;
629
630 default:
631 break;
632 }
633 }
634
635
636 void RtViewerApp::HandleCTLoaded(const OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality& message)
637 {
638 UpdateLayers();
639 }
640
641 void RtViewerApp::HandleCTContentUpdated(const DicomVolumeImage::ContentUpdatedMessage& message)
642 {
643 UpdateLayers();
644 }
645
646 void RtViewerApp::HandleDoseLoaded(const DicomVolumeImage::ContentUpdatedMessage& message)
647 {
648 //TODO: compute dose extent, with outlier rejection
649 UpdateLayers();
650 }
651
652 void RtViewerApp::HandleStructuresReady(const DicomStructureSetLoader::StructuresReady& message)
653 {
654 UpdateLayers();
655 }
656
657 void RtViewerApp::HandleStructuresUpdated(const DicomStructureSetLoader::StructuresUpdated& message)
658 {
659 UpdateLayers();
660 }
661
662 void RtViewerApp::SetCtVolume(int depth,
663 const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
664 OrthancStone::ILayerStyleConfigurator* style)
665 {
666 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
667 ViewportController& controller = lock->GetController();
668 Scene2D& scene = controller.GetScene();
669
670 ctVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(scene, depth, volume));
671
672 if (style != NULL)
673 {
674 ctVolumeLayerSource_->SetConfigurator(style);
675 }
676 }
677
678 void RtViewerApp::SetDoseVolume(int depth,
679 const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
680 OrthancStone::ILayerStyleConfigurator* style)
681 {
682 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
683 ViewportController& controller = lock->GetController();
684 Scene2D& scene = controller.GetScene();
685
686 doseVolumeLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(scene, depth, volume));
687
688 if (style != NULL)
689 {
690 doseVolumeLayerSource_->SetConfigurator(style);
691 }
692 }
693
694 void RtViewerApp::SetStructureSet(int depth,
695 const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume)
696 {
697 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
698 ViewportController& controller = lock->GetController();
699 Scene2D& scene = controller.GetScene();
700
701 structLayerSource_.reset(new OrthancStone::VolumeSceneLayerSource(scene, depth, volume));
702 }
703
704 void RtViewerApp::Run()
705 {
706 {
707 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
708 ViewportController& controller = lock->GetController();
709 Scene2D& scene = controller.GetScene();
710 ICompositor& compositor = lock->GetCompositor();
711
712 // False means we do NOT let Windows treat this as a legacy application
713 // that needs to be scaled
714 controller.FitContent(compositor.GetCanvasWidth(), compositor.GetCanvasHeight());
715
716 glEnable(GL_DEBUG_OUTPUT);
717 glDebugMessageCallback(OpenGLMessageCallback, 0);
718
719 compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT,
720 FONT_SIZE_0, Orthanc::Encoding_Latin1);
721 compositor.SetFont(1, Orthanc::EmbeddedResources::UBUNTU_FONT,
722 FONT_SIZE_1, Orthanc::Encoding_Latin1);
723 }
724 //////// from loader
725 {
726 Orthanc::WebServiceParameters p;
727 //p.SetUrl("http://localhost:8043/");
728 p.SetCredentials("orthanc", "orthanc");
729 oracle_.SetOrthancParameters(p);
730 }
731
732 //////// from Run
733
734 boost::shared_ptr<DicomVolumeImage> ctVolume(new DicomVolumeImage);
735 boost::shared_ptr<DicomVolumeImage> doseVolume(new DicomVolumeImage);
736
737
738 boost::shared_ptr<OrthancSeriesVolumeProgressiveLoader> ctLoader;
739 boost::shared_ptr<OrthancMultiframeVolumeLoader> doseLoader;
740 boost::shared_ptr<DicomStructureSetLoader> rtstructLoader;
741
742 {
743 // "true" means use progressive quality (jpeg 50 --> jpeg 90 --> 16-bit raw)
744 ctLoader = OrthancSeriesVolumeProgressiveLoader::Create(*loadersContext_, ctVolume, false);
745
746 // we need to store the CT loader to ask from geometry details later on when geometry is loaded
747 geometryProvider_ = ctLoader;
748
749 doseLoader = OrthancMultiframeVolumeLoader::Create(*loadersContext_, doseVolume);
750 rtstructLoader = DicomStructureSetLoader::Create(*loadersContext_);
751 }
752
753 Register<DicomVolumeImage::GeometryReadyMessage>(*ctLoader, &RtViewerApp::Handle);
754 Register<OrthancSeriesVolumeProgressiveLoader::VolumeImageReadyInHighQuality>(*ctLoader, &RtViewerApp::HandleCTLoaded);
755 Register<DicomVolumeImage::ContentUpdatedMessage>(*ctLoader, &RtViewerApp::HandleCTContentUpdated);
756 Register<DicomVolumeImage::ContentUpdatedMessage>(*doseLoader, &RtViewerApp::HandleDoseLoaded);
757 Register<DicomStructureSetLoader::StructuresReady>(*rtstructLoader, &RtViewerApp::HandleStructuresReady);
758 Register<DicomStructureSetLoader::StructuresUpdated>(*rtstructLoader, &RtViewerApp::HandleStructuresUpdated);
759
760 std::auto_ptr<GrayscaleStyleConfigurator> style(new GrayscaleStyleConfigurator);
761 style->SetLinearInterpolation(true);
762
763 this->SetCtVolume(LAYER_POSITION + 0, ctLoader, style.release());
764
765 {
766 std::unique_ptr<LookupTableStyleConfigurator> config(new LookupTableStyleConfigurator);
767 config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
768
769 boost::shared_ptr<DicomVolumeImageMPRSlicer> tmp(new DicomVolumeImageMPRSlicer(doseVolume));
770 this->SetDoseVolume(LAYER_POSITION + 1, tmp, config.release());
771 }
772
773 this->SetStructureSet(LAYER_POSITION + 2, rtstructLoader);
774
775 #if 1
776 /*
777 BGO data
778 http://localhost:8042/twiga-orthanc-viewer-demo/twiga-orthanc-viewer-demo.html?ct-series=a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa
779 &
780 dose-instance=830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb
781 &
782 struct-instance=54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9
783 */
784 ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT
785 doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // RT-DOSE
786 rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT
787 #else
788 //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT
789 //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE
790 //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT
791
792 // 2017-05-16
793 ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT
794 doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad"); // RT-DOSE
795 rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT
796 #endif
797
798 oracle_.Start();
799
800 //// END from loader
801
802 while (!g_stopApplication)
803 {
804 //compositor.Refresh(scene);
805
806 //////// from loader
807 //// END from loader
808
809 SDL_Event event;
810 while (!g_stopApplication && SDL_PollEvent(&event))
811 {
812 if (event.type == SDL_QUIT)
813 {
814 g_stopApplication = true;
815 break;
816 }
817 else if (event.type == SDL_WINDOWEVENT &&
818 event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
819 {
820 DisableTracker(); // was: tracker.reset(NULL);
821 }
822 else if (event.type == SDL_KEYDOWN &&
823 event.key.repeat == 0 /* Ignore key bounce */)
824 {
825 switch (event.key.keysym.sym)
826 {
827 case SDLK_f:
828 // TODO: implement GetWindow!!!
829 // viewport_->GetContext()->GetWindow().ToggleMaximize();
830 ORTHANC_ASSERT(false, "Please implement GetWindow()");
831 break;
832
833 case SDLK_r:
834 break;
835
836 case SDLK_s:
837 FitContent();
838 break;
839
840 case SDLK_q:
841 g_stopApplication = true;
842 break;
843 default:
844 break;
845 }
846 }
847 HandleApplicationEvent(event);
848 }
849 SDL_Delay(1);
850 }
851
852 //// from loader
853
854 //Orthanc::SystemToolbox::ServerBarrier();
855
856 /**
857 * WARNING => The oracle must be stopped BEFORE the objects using
858 * it are destroyed!!! This forces to wait for the completion of
859 * the running callback methods. Otherwise, the callbacks methods
860 * might still be running while their parent object is destroyed,
861 * resulting in crashes. This is very visible if adding a sleep(),
862 * as in (*).
863 **/
864
865 oracle_.Stop();
866 //// END from loader
867 }
868
869 void RtViewerApp::SetInfoDisplayMessage(
870 std::string key, std::string value)
871 {
872 if (value == "")
873 infoTextMap_.erase(key);
874 else
875 infoTextMap_[key] = value;
876 DisplayInfoText();
877 }
878
879 }
880
881
882 boost::weak_ptr<OrthancStone::RtViewerApp> g_app;
883
884 void RtViewer_SetInfoDisplayMessage(std::string key, std::string value)
885 {
886 boost::shared_ptr<OrthancStone::RtViewerApp> app = g_app.lock();
887 if (app)
888 {
889 app->SetInfoDisplayMessage(key, value);
890 }
891 }
892
893 /**
894 * IMPORTANT: The full arguments to "main()" are needed for SDL on
895 * Windows. Otherwise, one gets the linking error "undefined reference
896 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
897 **/
898 int main(int argc, char* argv[])
899 {
900 using namespace OrthancStone;
901
902 StoneInitialize();
903 Orthanc::Logging::EnableInfoLevel(true);
904 //Orthanc::Logging::EnableTraceLevel(true);
905
906 try
907 {
908 boost::shared_ptr<RtViewerApp> app = RtViewerApp::Create();
909 g_app = app;
910 //app->PrepareScene();
911 app->Run();
912 }
913 catch (Orthanc::OrthancException& e)
914 {
915 LOG(ERROR) << "EXCEPTION: " << e.What();
916 }
917
918 StoneFinalize();
919
920 return 0;
921 }
922
923