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