GCC Code Coverage Report


./
File: modules/viz/scene3d/adaptor/voxel_picker.cpp
Date: 2025-01-21 16:21:04
Lines:
0/198
0.0%
Functions:
0/13
0.0%
Branches:
0/242
0.0%

Line Branch Exec Source
1 /************************************************************************
2 *
3 * Copyright (C) 2020-2024 IRCAD France
4 * Copyright (C) 2020 IHU Strasbourg
5 *
6 * This file is part of Sight.
7 *
8 * Sight is free software: you can redistribute it and/or modify it under
9 * the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Sight is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with Sight. If not, see <https://www.gnu.org/licenses/>.
20 *
21 ***********************************************************************/
22
23 #include "modules/viz/scene3d/adaptor/voxel_picker.hpp"
24
25 #include <core/com/signal.hxx>
26 #include <core/com/signals.hpp>
27 #include <core/com/slots.hxx>
28
29 #include <data/helper/medical_image.hpp>
30
31 #include <viz/scene3d/helper/camera.hpp>
32 #include <viz/scene3d/utils.hpp>
33
34 namespace sight::module::viz::scene3d::adaptor
35 {
36
37 const core::com::slots::key_t SLICE_TYPE_SLOT = "sliceType";
38
39 static const core::com::signals::key_t PICKED_SIG = "picked";
40
41 //-----------------------------------------------------------------------------
42
43 voxel_picker::voxel_picker() noexcept
44 {
45 new_slot(SLICE_TYPE_SLOT, &voxel_picker::change_slice_type, this);
46
47 m_picked_sig = new_signal<core::com::signal<void(data::tools::picking_info)> >(PICKED_SIG);
48 }
49
50 //-----------------------------------------------------------------------------
51
52 void voxel_picker::configuring()
53 {
54 this->configure_params();
55
56 const config_t config = this->get_config();
57
58 static const std::string s_PRIORITY_CONFIG = CONFIG + "priority";
59 static const std::string s_ORIENTATION_CONFIG = CONFIG + "orientation";
60 static const std::string s_MODE_CONFIG = CONFIG + "mode";
61 static const std::string s_LAYER_ORDER_DEPENDANT_CONFIG = CONFIG + "layerOrderDependant";
62 static const std::string s_MOVE_ON_PICK_CONFIG = CONFIG + "moveOnPick";
63
64 m_priority = config.get<int>(s_PRIORITY_CONFIG, m_priority);
65 m_layer_order_dependant = config.get<bool>(s_LAYER_ORDER_DEPENDANT_CONFIG, m_layer_order_dependant);
66
67 const std::string orientation = config.get<std::string>(s_ORIENTATION_CONFIG, "sagittal");
68 SIGHT_ASSERT(
69 "Orientation mode must be 'axial', 'frontal' or 'sagittal'.",
70 orientation == "axial" || orientation == "frontal" || orientation == "sagittal"
71 );
72 if(orientation == "axial")
73 {
74 m_orientation = orientation_mode::z_axis;
75 }
76 else if(orientation == "frontal")
77 {
78 m_orientation = orientation_mode::y_axis;
79 }
80 else if(orientation == "sagittal")
81 {
82 m_orientation = orientation_mode::x_axis;
83 }
84
85 const std::string mode = config.get<std::string>(s_MODE_CONFIG, "2D");
86 SIGHT_ASSERT("Mode must be '2D' or '3D'.", mode == "2D" || mode == "3D");
87 m_mode_2d = mode == "2D";
88
89 m_move_on_pick = config.get<bool>(s_MOVE_ON_PICK_CONFIG, m_move_on_pick);
90 }
91
92 //-----------------------------------------------------------------------------
93
94 void voxel_picker::starting()
95 {
96 adaptor::init();
97
98 const auto interactor = std::dynamic_pointer_cast<sight::viz::scene3d::interactor::base>(this->get_sptr());
99 this->layer()->add_interactor(interactor, m_priority);
100 }
101
102 //-----------------------------------------------------------------------------
103
104 service::connections_t voxel_picker::auto_connections() const
105 {
106 service::connections_t connections = adaptor::auto_connections();
107 connections.push(IMAGE_INPUT, data::image::SLICE_TYPE_MODIFIED_SIG, SLICE_TYPE_SLOT);
108
109 return connections;
110 }
111
112 //-----------------------------------------------------------------------------
113
114 void voxel_picker::updating() noexcept
115 {
116 }
117
118 //-----------------------------------------------------------------------------
119
120 void voxel_picker::stopping()
121 {
122 const auto interactor = std::dynamic_pointer_cast<sight::viz::scene3d::interactor::base>(this->get_sptr());
123 this->layer()->remove_interactor(interactor);
124
125 adaptor::deinit();
126 }
127
128 //-----------------------------------------------------------------------------
129
130 void voxel_picker::button_press_event(mouse_button _button, modifier _mod, int _x, int _y)
131 {
132 this->pick(_button, _mod, _x, _y, true);
133 }
134
135 //-----------------------------------------------------------------------------
136
137 void voxel_picker::button_release_event(mouse_button _button, modifier _mod, int _x, int _y)
138 {
139 this->pick(_button, _mod, _x, _y, false);
140 }
141
142 //-----------------------------------------------------------------------------
143
144 void voxel_picker::pick(mouse_button _button, modifier _mod, int _x, int _y, bool _pressed)
145 {
146 if(_button == mouse_button::left)
147 {
148 if(auto layer = m_layer.lock())
149 {
150 if(!sight::module::viz::scene3d::adaptor::voxel_picker::is_in_layer(_x, _y, layer, m_layer_order_dependant))
151 {
152 return;
153 }
154 }
155
156 // Get the scene manager.
157 const auto* const scene_manager = this->get_scene_manager();
158
159 // Create the ray from picking positions.
160 const auto* const camera = scene_manager->getCamera(sight::viz::scene3d::layer::DEFAULT_CAMERA_NAME);
161 const auto vp_pos =
162 sight::viz::scene3d::helper::camera::convert_from_window_to_viewport_space(*camera, _x, _y);
163 const Ogre::Ray vp_ray = camera->getCameraToViewportRay(vp_pos.x, vp_pos.y);
164
165 // Get image information.
166 const auto image = m_image.lock();
167 const auto spacing = sight::viz::scene3d::utils::get_ogre_spacing(*image);
168 const auto origin = sight::viz::scene3d::utils::get_ogre_origin(*image);
169
170 const std::pair<bool, Ogre::Vector3> result =
171 this->compute_ray_image_intersection(vp_ray, image.get_shared(), origin, spacing);
172
173 if(result.first)
174 {
175 // Compute the ray/slice intersection.
176 const Ogre::Vector3 intersection = result.second;
177
178 // Create the picking information.
179 data::tools::picking_info info;
180 info.m_world_pos[0] = static_cast<double>(intersection.x);
181 info.m_world_pos[1] = static_cast<double>(intersection.y);
182 info.m_world_pos[2] = static_cast<double>(intersection.z);
183
184 using picking_event_t = data::tools::picking_info::event;
185 switch(_button)
186 {
187 case mouse_button::left:
188 info.m_event_id = _pressed ? picking_event_t::mouse_left_down : picking_event_t::mouse_left_up;
189 break;
190
191 case mouse_button::right:
192 info.m_event_id = _pressed ? picking_event_t::mouse_right_down : picking_event_t::mouse_right_up;
193 break;
194
195 case mouse_button::middle:
196 info.m_event_id = _pressed ? picking_event_t::mouse_middle_down : picking_event_t::mouse_middle_up;
197 break;
198
199 default:
200 SIGHT_ERROR("Unknown mouse button");
201 break;
202 }
203
204 if(static_cast<bool>(_mod & modifier::control))
205 {
206 info.m_modifier_mask |= data::tools::picking_info::ctrl;
207
208 if(m_move_on_pick && info.m_event_id == data::tools::picking_info::event::mouse_left_up)
209 {
210 // Emit slices positions
211 const int sagittal_idx = static_cast<int>((info.m_world_pos[0] - origin[0]) / spacing[0]);
212 const int frontal_idx = static_cast<int>((info.m_world_pos[1] - origin[1]) / spacing[1]);
213 const int axial_idx = static_cast<int>((info.m_world_pos[2] - origin[2]) / spacing[2]);
214 const auto sig = image->signal<data::image::slice_index_modified_signal_t>(
215 data::image::SLICE_INDEX_MODIFIED_SIG
216 );
217 sig->async_emit(axial_idx, frontal_idx, sagittal_idx);
218 }
219 }
220
221 if(static_cast<bool>(_mod & modifier::shift))
222 {
223 info.m_modifier_mask |= data::tools::picking_info::shift;
224 }
225
226 // Emit the picking info.
227 m_picked_sig->async_emit(info);
228
229 // Cancel further interactors on the same layer.
230 this->layer()->cancel_further_interaction();
231 }
232 }
233 }
234
235 //-----------------------------------------------------------------------------
236
237 void voxel_picker::change_slice_type(int _from, int _to)
238 {
239 const auto to_orientation = static_cast<orientation_mode>(_to);
240 const auto from_orientation = static_cast<orientation_mode>(_from);
241
242 m_orientation = m_orientation == to_orientation ? from_orientation
243 : m_orientation
244 == from_orientation ? to_orientation : m_orientation;
245 }
246
247 //-----------------------------------------------------------------------------
248
249 std::pair<bool, Ogre::Vector3> voxel_picker::compute_ray_image_intersection(
250 const Ogre::Ray& _ray,
251 const data::image::csptr _image,
252 const Ogre::Vector3& _origin,
253 const Ogre::Vector3& _spacing
254 )
255 {
256 namespace imHelper = data::helper::medical_image;
257 const auto axial_idx = imHelper::get_slice_index(*_image, imHelper::axis_t::axial).value_or(0);
258 const auto frontal_idx = imHelper::get_slice_index(*_image, imHelper::axis_t::frontal).value_or(0);
259 const auto sagittal_idx = imHelper::get_slice_index(*_image, imHelper::axis_t::sagittal).value_or(0);
260
261 const auto axial_index = static_cast<Ogre::Real>(axial_idx);
262 const auto frontal_index = static_cast<Ogre::Real>(frontal_idx);
263 const auto sagittal_index = static_cast<Ogre::Real>(sagittal_idx);
264
265 const auto size = _image->size();
266
267 // Function to check if an intersection is inside an image.
268 std::function<bool(orientation_mode, Ogre::Vector3)> is_inside_image =
269 [&](orientation_mode _orientation, const Ogre::Vector3 _inter) -> bool
270 {
271 switch(_orientation)
272 {
273 case orientation_mode::x_axis:
274 return _inter.y >= _origin.y
275 && _inter.z >= _origin.z
276 && _inter.y <= _origin.y + _spacing.y * static_cast<Ogre::Real>(size[1])
277 && _inter.z <= _origin.z + _spacing.z * static_cast<Ogre::Real>(size[2]);
278
279 case orientation_mode::y_axis:
280 return _inter.x >= _origin.x
281 && _inter.z >= _origin.z
282 && _inter.x <= _origin.x + _spacing.x * static_cast<Ogre::Real>(size[0])
283 && _inter.z <= _origin.z + _spacing.z * static_cast<Ogre::Real>(size[2]);
284
285 case orientation_mode::z_axis:
286 return _inter.x >= _origin.x
287 && _inter.y >= _origin.y
288 && _inter.x <= _origin.x + _spacing.x * static_cast<Ogre::Real>(size[0])
289 && _inter.y <= _origin.y + _spacing.y * static_cast<Ogre::Real>(size[1]);
290
291 default:
292 SIGHT_ERROR("Unknown orientation mode");
293 return false;
294 }
295 };
296
297 // Function to cast the non depth coordinate into image spacing.
298 std::function<Ogre::Vector3(orientation_mode, Ogre::Vector3)> cast_to_voxel =
299 [&](orientation_mode _orientation, Ogre::Vector3 _inter) -> Ogre::Vector3
300 {
301 switch(_orientation)
302 {
303 case orientation_mode::x_axis:
304 {
305 const int y_idx = static_cast<int>(_inter.y / _spacing.y);
306 const int z_idx = static_cast<int>(_inter.z / _spacing.z);
307 _inter.y = static_cast<float>(y_idx) * _spacing.y;
308 _inter.z = static_cast<float>(z_idx) * _spacing.z;
309 break;
310 }
311
312 case orientation_mode::y_axis:
313 {
314 const int x_idx = static_cast<int>(_inter.x / _spacing.x);
315 const int z_idx = static_cast<int>(_inter.z / _spacing.z);
316 _inter.x = static_cast<float>(x_idx) * _spacing.x;
317 _inter.z = static_cast<float>(z_idx) * _spacing.z;
318 break;
319 }
320
321 case orientation_mode::z_axis:
322 {
323 const int x_idx = static_cast<int>(_inter.x / _spacing.x);
324 const int y_idx = static_cast<int>(_inter.y / _spacing.y);
325 _inter.x = static_cast<float>(x_idx) * _spacing.x;
326 _inter.y = static_cast<float>(y_idx) * _spacing.y;
327 break;
328 }
329
330 default:
331 SIGHT_ERROR("Unknown orientation mode");
332 break;
333 }
334
335 return _inter;
336 };
337
338 // If it's a 2D mode, the intersection is computed between the ray and the current image slice.
339 if(m_mode_2d)
340 {
341 // Create the plane from image information.
342 Ogre::Plane plane;
343 switch(m_orientation)
344 {
345 case orientation_mode::x_axis:
346 plane = Ogre::Plane(Ogre::Vector3::UNIT_X, _origin.x + sagittal_index * _spacing.x);
347 break;
348
349 case orientation_mode::y_axis:
350 plane = Ogre::Plane(Ogre::Vector3::UNIT_Y, _origin.y + frontal_index * _spacing.y);
351 break;
352
353 case orientation_mode::z_axis:
354 plane = Ogre::Plane(Ogre::Vector3::UNIT_Z, _origin.z + axial_index * _spacing.z);
355 break;
356
357 default:
358 SIGHT_ERROR("Unknown orientation mode");
359 break;
360 }
361
362 // Check the intersection.
363 const Ogre::RayTestResult result = _ray.intersects(plane);
364
365 if(result.first)
366 {
367 Ogre::Vector3 intersection = _ray.getPoint(result.second);
368 return std::make_pair(
369 is_inside_image(m_orientation, intersection),
370 cast_to_voxel(m_orientation, intersection)
371 );
372 }
373 }
374 // Else, the intersection is computed between each slice. The nearest one is returned.
375 else
376 {
377 const Ogre::Plane sagittal_plane =
378 Ogre::Plane(Ogre::Vector3::UNIT_X, _origin.x + sagittal_index * _spacing.x);
379 const Ogre::Plane frontal_plane =
380 Ogre::Plane(Ogre::Vector3::UNIT_Y, _origin.y + frontal_index * _spacing.y);
381 const Ogre::Plane axial_plane = Ogre::Plane(Ogre::Vector3::UNIT_Z, _origin.z + axial_index * _spacing.z);
382
383 Ogre::RayTestResult sagittal_inter = _ray.intersects(sagittal_plane);
384 Ogre::RayTestResult frontal_inter = _ray.intersects(frontal_plane);
385 Ogre::RayTestResult axial_inter = _ray.intersects(axial_plane);
386
387 if(sagittal_inter.first)
388 {
389 Ogre::Vector3 intersection = _ray.getPoint(sagittal_inter.second);
390 sagittal_inter.first = is_inside_image(orientation_mode::x_axis, intersection);
391 }
392
393 if(frontal_inter.first)
394 {
395 Ogre::Vector3 intersection = _ray.getPoint(frontal_inter.second);
396 frontal_inter.first = is_inside_image(orientation_mode::y_axis, intersection);
397 }
398
399 if(axial_inter.first)
400 {
401 Ogre::Vector3 intersection = _ray.getPoint(axial_inter.second);
402 axial_inter.first = is_inside_image(orientation_mode::z_axis, intersection);
403 }
404
405 orientation_mode orientation = orientation_mode::x_axis;
406 Ogre::RayTestResult result = std::make_pair(false, std::numeric_limits<Ogre::Real>::max());
407 if(sagittal_inter.first)
408 {
409 orientation = orientation_mode::x_axis;
410 result.second = sagittal_inter.second;
411 result.first = true;
412 }
413
414 if(frontal_inter.first && frontal_inter.second < result.second)
415 {
416 orientation = orientation_mode::y_axis;
417 result.second = frontal_inter.second;
418 result.first = true;
419 }
420
421 if(axial_inter.first && axial_inter.second < result.second)
422 {
423 orientation = orientation_mode::z_axis;
424 result.second = axial_inter.second;
425 result.first = true;
426 }
427
428 if(result.first)
429 {
430 const Ogre::Vector3 intersection = _ray.getPoint(result.second);
431 return std::make_pair(true, cast_to_voxel(orientation, intersection));
432 }
433 }
434
435 return std::make_pair(false, Ogre::Vector3 {0.0, 0.0, 0.0});
436 }
437
438 //-----------------------------------------------------------------------------
439
440 } // namespace sight::module::viz::scene3d::adaptor.
441