Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kissualizer Update: Inspection Mode and more Keybindings #390

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
164eb9f
Merge pull request #2 from PRBonn/main
l00p3 Jul 26, 2024
5d75de3
Visualizer initialized
l00p3 Jul 26, 2024
d071d41
Simple buttons added
l00p3 Jul 26, 2024
caa7f1a
Almost done
l00p3 Jul 26, 2024
ff61af6
Center viewpoint option
l00p3 Jul 26, 2024
502ea17
Better static member management
l00p3 Jul 26, 2024
133e21a
Better palette and global view
l00p3 Jul 26, 2024
0d03ca7
Global View is not switchable standing
l00p3 Jul 26, 2024
a5c63ad
Arrows on trajectory
l00p3 Jul 26, 2024
addcbec
Slight beautification of arrows
l00p3 Jul 26, 2024
18725e2
Global points dimensions
l00p3 Jul 26, 2024
802ae40
Trajectory not invisible but actually not there in local mode
l00p3 Jul 26, 2024
089bd1b
Some TODO todo
l00p3 Jul 26, 2024
bd92e91
Refactor code, palette changed, trajectory swithc in global fixed
l00p3 Jul 29, 2024
8fb9c71
Some separator
l00p3 Jul 29, 2024
d0f9d0b
Points size sliders
l00p3 Jul 29, 2024
f2c270e
Better viewpoint centering
l00p3 Jul 29, 2024
fff0f79
Screenshot demo added
l00p3 Jul 29, 2024
27b4c17
Some button key added
l00p3 Jul 29, 2024
0dae3d0
Fixed DEBUG name window
l00p3 Jul 29, 2024
42c3a16
Parameters visualization and fps added
l00p3 Jul 30, 2024
a189757
Center viewpoint improved
l00p3 Jul 30, 2024
9ed426b
Trajectory un-registration for better bounding box
l00p3 Jul 30, 2024
60236f4
Color palette changed again
l00p3 Jul 30, 2024
62d756d
Some cleaning
l00p3 Jul 30, 2024
fbdef70
Button name with keys and missing keys added
l00p3 Jul 31, 2024
bed3c91
Embrace Benedikt's colors and fix typo
l00p3 Jul 31, 2024
d289e53
Fix Palette and quantity size
tizianoGuadagnino Aug 1, 2024
4ae44c0
Fix license
tizianoGuadagnino Aug 1, 2024
64b1cc0
Color approved by Gupta
tizianoGuadagnino Aug 1, 2024
877f43f
Now everything is member of Kissualizer
l00p3 Aug 2, 2024
34b7e71
Trajectory as points, not anymore a curve
l00p3 Aug 2, 2024
14825d0
Screenshot name with timestamp
l00p3 Aug 2, 2024
07d42e6
Style fix
l00p3 Aug 2, 2024
c676763
Button names constants less ugly
l00p3 Aug 2, 2024
3db2af5
Now we have a dict to visualize parameters and co
l00p3 Aug 2, 2024
5e3688f
FPS computation not anymore in visualizer class
l00p3 Aug 2, 2024
58bf027
isort ti odio
l00p3 Aug 2, 2024
bdeb70e
Odometry info is now open by default
l00p3 Aug 2, 2024
492d296
Quit button on the right, why not
l00p3 Aug 2, 2024
257551d
Pose picking started
l00p3 Aug 2, 2024
285d744
Pose selection and visualization on the trajectory
l00p3 Aug 2, 2024
71c2c0d
Voxel Grid added but still improvable
l00p3 Aug 6, 2024
b0c5847
TODO
l00p3 Aug 6, 2024
07929cb
Other TODOs
l00p3 Aug 6, 2024
f98daba
Analysis tab added
l00p3 Aug 7, 2024
9e46097
Target map -> Local map
l00p3 Aug 7, 2024
acfbc5a
Ortho mode added but incomplete
l00p3 Aug 7, 2024
094100b
Orthographic fixed and probably better voxel computations
l00p3 Aug 8, 2024
3dc8215
Keybindings added for toggles
l00p3 Aug 8, 2024
a55e218
Small update on pose selection
l00p3 Aug 8, 2024
89a59b4
Removed screenshots
l00p3 Aug 8, 2024
b6c7bc7
Gitignore update to avoid screenshots
l00p3 Aug 8, 2024
8514868
Typo fix
l00p3 Aug 8, 2024
0c8c77e
Do not reset camera view if not previously in ortho
l00p3 Aug 8, 2024
71122f6
Merge branch 'main' into luca-new-visualizer
l00p3 Aug 8, 2024
3dfcde1
Style check update
l00p3 Aug 8, 2024
3282b8b
Merge branch 'main' into luca-new-visualizer
l00p3 Aug 9, 2024
84d48df
Ortho removed
l00p3 Aug 9, 2024
fb58840
Started branch to visualize associations
l00p3 Aug 9, 2024
535cac8
Correspondences visualized
l00p3 Aug 9, 2024
1a19e86
Removed unused attributes
l00p3 Aug 9, 2024
60cfb1f
Some cleaning on voxels code
l00p3 Aug 9, 2024
6343354
Style fix
l00p3 Aug 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ _skbuild/
.gitlab-ci-local/
.polyscope.ini
imgui.ini
kisshot_*
l00p3 marked this conversation as resolved.
Show resolved Hide resolved

# Created by https://www.toptal.com/developers/gitignore/api/python,c++
# Edit at https://www.toptal.com/developers/gitignore?templates=python,c++
Expand Down
8 changes: 8 additions & 0 deletions cpp/kiss_icp/core/VoxelHashMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ std::vector<Eigen::Vector3d> VoxelHashMap::Pointcloud() const {
return points;
}

// Get the indices of the occupided voxels as points, mainly used for visualization
std::vector<Voxel> VoxelHashMap::GetVoxels() const {
std::vector<Voxel> voxels(map_.size());
std::transform(map_.cbegin(), map_.cend(), voxels.begin(),
[](const auto &map_element) { return map_element.first; });
return voxels;
}

void VoxelHashMap::Update(const std::vector<Eigen::Vector3d> &points,
const Eigen::Vector3d &origin) {
AddPoints(points);
Expand Down
1 change: 1 addition & 0 deletions cpp/kiss_icp/core/VoxelHashMap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct VoxelHashMap {
void AddPoints(const std::vector<Eigen::Vector3d> &points);
void RemovePointsFarFromLocation(const Eigen::Vector3d &origin);
std::vector<Eigen::Vector3d> Pointcloud() const;
std::vector<Voxel> GetVoxels() const;
std::tuple<Eigen::Vector3d, double> GetClosestNeighbor(const Eigen::Vector3d &point) const;

double voxel_size_;
Expand Down
4 changes: 4 additions & 0 deletions python/kiss_icp/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ def remove_far_away_points(self, origin):
def point_cloud(self) -> np.ndarray:
"""Return the internal representaion as a np.array (pointcloud)."""
return np.asarray(self._internal_map._point_cloud())

def get_voxels(self) -> np.ndarray:
"""Return the occupied voxels, in indices (i,j,k) as a np.array."""
return np.asarray(self._internal_map._get_voxels())
2 changes: 2 additions & 0 deletions python/kiss_icp/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ def __init__(
self._vis_infos = {
"max_range": self.config.data.max_range,
"min_range": self.config.data.min_range,
"voxel_size": self.config.mapping.voxel_size,
}
self.visualizer.set_voxel_size(self.config.mapping.voxel_size)
if hasattr(self._dataset, "use_global_visualizer"):
self.visualizer._global_view = self._dataset.use_global_visualizer

Expand Down
3 changes: 2 additions & 1 deletion python/kiss_icp/pybind/kiss_icp_pybind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ PYBIND11_MODULE(kiss_icp_pybind, m) {
"points"_a, "pose"_a)
.def("_add_points", &VoxelHashMap::AddPoints, "points"_a)
.def("_remove_far_away_points", &VoxelHashMap::RemovePointsFarFromLocation, "origin"_a)
.def("_point_cloud", &VoxelHashMap::Pointcloud);
.def("_point_cloud", &VoxelHashMap::Pointcloud)
.def("_get_voxels", &VoxelHashMap::GetVoxels);

// Point Cloud registration
py::class_<Registration> internal_registration(m, "_Registration", "Don't use this");
Expand Down
175 changes: 159 additions & 16 deletions python/kiss_icp/tools/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
LOCAL_VIEW_BUTTON = "LOCAL VIEW\n\t\t [G]"
GLOBAL_VIEW_BUTTON = "GLOBAL VIEW\n\t\t [G]"
CENTER_VIEWPOINT_BUTTON = "CENTER VIEWPOINT\n\t\t\t\t[C]"
SHOW_VOXEL_GRID_BUTTON = "SHOW VOXEL GRID\n\t\t\t\t[V]"
HIDE_VOXEL_GRID_BUTTON = "HIDE VOXEL GRID\n\t\t\t [V]"
ORTHO_ON_BUTTON = "ORTHO VIEW ON\n\t\t\t [O]"
ORTHO_OFF_BUTTON = "ORTHO VIEW OFF\n\t\t\t [O]"
QUIT_BUTTON = "QUIT\n [Q]"

# Colors
Expand All @@ -42,6 +46,7 @@
KEYPOINTS_COLOR = [1, 0.7568, 0.0274]
LOCAL_MAP_COLOR = [0.0, 0.3019, 0.2509]
TRAJECTORY_COLOR = [0.1176, 0.5333, 0.8980]
VOXEL_GRID_COLOR = [0.9, 0.9, 0.9]

# Size constants
FRAME_PTS_SIZE = 0.06
Expand All @@ -53,7 +58,10 @@ class StubVisualizer(ABC):
def __init__(self):
pass

def update(self, source, keypoints, target_map, pose, vis_infos):
def update(self, source, keypoints, local_map, pose, vis_infos):
pass

def set_voxel_size(self, voxel_size):
pass


Expand All @@ -77,27 +85,36 @@ def __init__(self):
self._toggle_frame = True
self._toggle_keypoints = True
self._toggle_map = True
self._toggle_voxel_grid = False
self._toggle_ortho = False
self._global_view = False

# Create data
self._trajectory = []
self._last_pose = np.eye(4)
self._vis_infos = dict()
self._selected_pose = ""
self._voxel_grid_nodes = np.array([])
self._voxel_grid_edges = np.array([])
self._last_local_map = None
self._voxel_size = 1.0

# Initialize Visualizer
self._initialize_visualizer()

def update(self, source, keypoints, target_map, pose, vis_infos: dict):
def update(self, source, keypoints, local_map, pose, vis_infos: dict):
self._vis_infos = dict(sorted(vis_infos.items(), key=lambda item: len(item[0])))
self._update_geometries(source, keypoints, target_map, pose)
self._last_pose = pose
self._update_geometries(source, keypoints, local_map, pose)
while self._block_execution:
self._ps.frame_tick()
if self._play_mode:
break
self._block_execution = not self._block_execution

def set_voxel_size(self, voxel_size):
self._voxel_size = voxel_size

# Private Interface ---------------------------------------------------------------------------
def _initialize_visualizer(self):
self._ps.set_program_name("KissICP Visualizer")
Expand All @@ -107,8 +124,9 @@ def _initialize_visualizer(self):
self._ps.set_verbosity(0)
self._ps.set_user_callback(self._main_gui_callback)
self._ps.set_build_default_gui_panels(False)
self._ps.set_SSAA_factor(4)

def _update_geometries(self, source, keypoints, target_map, pose):
def _update_geometries(self, source, keypoints, local_map, pose):
# CURRENT FRAME
frame_cloud = self._ps.register_point_cloud(
"current_frame",
Expand Down Expand Up @@ -137,7 +155,7 @@ def _update_geometries(self, source, keypoints, target_map, pose):
# LOCAL MAP
map_cloud = self._ps.register_point_cloud(
"local_map",
target_map.point_cloud(),
local_map.point_cloud(),
color=LOCAL_MAP_COLOR,
point_render_mode="quad",
)
Expand All @@ -148,6 +166,11 @@ def _update_geometries(self, source, keypoints, target_map, pose):
map_cloud.set_transform(np.linalg.inv(pose))
map_cloud.set_enabled(self._toggle_map)

# VOXEL GRID (only if toggled)
self._last_local_map = local_map
if self._toggle_voxel_grid:
self._register_voxel_grid()

# TRAJECTORY (only visible in global view)
self._trajectory.append(pose[:3, 3])
if self._global_view:
Expand All @@ -164,11 +187,80 @@ def _register_trajectory(self):
def _unregister_trajectory(self):
self._ps.remove_point_cloud("trajectory")

def _register_voxel_grid(self):
if self._last_local_map is None:
return
voxels = self._last_local_map.get_voxels()
self._voxel_grid_nodes = np.zeros((voxels.shape[0] * 8, 3), dtype=np.float64)
self._voxel_grid_edges = np.zeros((voxels.shape[0] * 12, 2), dtype=np.int64)

for idx, voxel in enumerate(voxels):
self._generate_new_voxel(voxel, idx)

voxel_grid = self._ps.register_curve_network(
"voxel_grid", self._voxel_grid_nodes, self._voxel_grid_edges, color=VOXEL_GRID_COLOR
)
voxel_grid.set_radius(0.01, relative=False)
if self._global_view:
voxel_grid.set_transform(np.eye(4))
else:
voxel_grid.set_transform(np.linalg.inv(self._last_pose))

def _unregister_voxel_grid(self):
if self._ps.has_curve_network("voxel_grid"):
self._ps.remove_curve_network("voxel_grid")

def _generate_new_voxel(self, voxel: np.ndarray, idx: int):
verts = np.array(
[
[0, 0, 0],
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
[0, 0, 1],
[1, 0, 1],
[1, 1, 1],
[0, 1, 1],
]
).astype(np.float64)
edges = np.array(
[
[0, 1],
[1, 2],
[2, 3],
[0, 3],
[0, 4],
[5, 4],
[1, 5],
[5, 6],
[2, 6],
[6, 7],
[3, 7],
[4, 7],
]
).astype(np.int64)
verts = verts + voxel
verts = verts * self._voxel_size
edges += idx * 8
self._voxel_grid_nodes[idx * 8 : idx * 8 + 8] = verts
self._voxel_grid_edges[idx * 12 : idx * 12 + 12] = edges

# GUI Callbacks ---------------------------------------------------------------------------
def _start_pause_callback(self):
button_name = PAUSE_BUTTON if self._play_mode else START_BUTTON
if self._gui.Button(button_name) or self._gui.IsKeyPressed(self._gui.ImGuiKey_Space):
self._play_mode = not self._play_mode
if self._play_mode:
self._toggle_voxel_grid = False
self._unregister_voxel_grid()
self._ps.set_SSAA_factor(1)
if self._toggle_ortho:
self._ps.reset_camera_to_home_view() # to reset FoV
self._ps.set_view_projection_mode("perspective")
self._ps.set_navigation_style("turntable")
self._toggle_ortho = False
else:
self._ps.set_SSAA_factor(4)

def _next_frame_callback(self):
if self._gui.Button(NEXT_FRAME_BUTTON) or self._gui.IsKeyPressed(self._gui.ImGuiKey_N):
Expand All @@ -185,8 +277,6 @@ def _vis_infos_callback(self):
if self._gui.TreeNodeEx("Odometry Information", self._gui.ImGuiTreeNodeFlags_DefaultOpen):
for key in self._vis_infos:
self._gui.TextUnformatted(f"{key}: {self._vis_infos[key]}")
if not self._play_mode and self._global_view:
self._gui.TextUnformatted(f"Selected Pose: {self._selected_pose}")
self._gui.TreePop()

def _center_viewpoint_callback(self):
Expand All @@ -203,8 +293,11 @@ def _toggle_buttons_andslides_callback(self):
if changed:
self._ps.get_point_cloud("current_frame").set_radius(self._frame_size, relative=False)
self._gui.SameLine()
changed, self._toggle_frame = self._gui.Checkbox("Frame Cloud", self._toggle_frame)
if changed:
f_key_pressed = self._gui.IsKeyPressed(self._gui.ImGuiKey_F)
if f_key_pressed:
self._toggle_frame = not self._toggle_frame
changed, self._toggle_frame = self._gui.Checkbox("[F] Frame Cloud", self._toggle_frame)
if changed or f_key_pressed:
self._ps.get_point_cloud("current_frame").set_enabled(self._toggle_frame)

# KEYPOINTS
Expand All @@ -214,8 +307,13 @@ def _toggle_buttons_andslides_callback(self):
if changed:
self._ps.get_point_cloud("keypoints").set_radius(self._keypoints_size, relative=False)
self._gui.SameLine()
changed, self._toggle_keypoints = self._gui.Checkbox("Keypoints", self._toggle_keypoints)
if changed:
k_key_pressed = self._gui.IsKeyPressed(self._gui.ImGuiKey_K)
if k_key_pressed:
self._toggle_keypoints = not self._toggle_keypoints
changed, self._toggle_keypoints = self._gui.Checkbox(
"[K] Keypoints", self._toggle_keypoints
)
if changed or k_key_pressed:
self._ps.get_point_cloud("keypoints").set_enabled(self._toggle_keypoints)

# LOCAL MAP
Expand All @@ -225,8 +323,11 @@ def _toggle_buttons_andslides_callback(self):
if changed:
self._ps.get_point_cloud("local_map").set_radius(self._map_size, relative=False)
self._gui.SameLine()
changed, self._toggle_map = self._gui.Checkbox("Local Map", self._toggle_map)
if changed:
m_key_pressed = self._gui.IsKeyPressed(self._gui.ImGuiKey_M)
if m_key_pressed:
self._toggle_map = not self._toggle_map
changed, self._toggle_map = self._gui.Checkbox("[M] Local Map", self._toggle_map)
if changed or m_key_pressed:
self._ps.get_point_cloud("local_map").set_enabled(self._toggle_map)

def _background_color_callback(self):
Expand All @@ -237,6 +338,39 @@ def _background_color_callback(self):
if changed:
self._ps.set_background_color(self._background_color)

def _inspection_callback(self):
info_string = "Double-click on the trajectory to visualize it (only global view):"
if self._gui.TreeNodeEx("Inspection", self._gui.ImGuiTreeNodeFlags_DefaultOpen):
voxel_grid_button_name = (
HIDE_VOXEL_GRID_BUTTON if self._toggle_voxel_grid else SHOW_VOXEL_GRID_BUTTON
)
if self._gui.Button(voxel_grid_button_name) or self._gui.IsKeyPressed(
self._gui.ImGuiKey_V
):
self._toggle_voxel_grid = not self._toggle_voxel_grid
if self._toggle_voxel_grid:
self._register_voxel_grid()
else:
self._unregister_voxel_grid()

ortho_button_name = ORTHO_OFF_BUTTON if self._toggle_ortho else ORTHO_ON_BUTTON
self._gui.SameLine()
if self._gui.Button(ortho_button_name) or self._gui.IsKeyPressed(self._gui.ImGuiKey_O):
self._toggle_ortho = not self._toggle_ortho
if self._toggle_ortho:
self._ps.set_view_projection_mode("orthographic")
self._ps.set_navigation_style("planar")
else:
self._ps.reset_camera_to_home_view() # to reset Fov
self._ps.set_view_projection_mode("perspective")
self._ps.set_navigation_style("turntable")
self._ps.reset_camera_to_home_view()

self._gui.TextUnformatted(info_string)
if self._selected_pose != "":
self._gui.TextUnformatted(f"\t\tSelected Pose: {self._selected_pose}")
self._gui.TreePop()

def _global_view_callback(self):
button_name = LOCAL_VIEW_BUTTON if self._global_view else GLOBAL_VIEW_BUTTON
if self._gui.Button(button_name) or self._gui.IsKeyPressed(self._gui.ImGuiKey_G):
Expand All @@ -246,11 +380,17 @@ def _global_view_callback(self):
self._ps.get_point_cloud("keypoints").set_transform(self._last_pose)
self._ps.get_point_cloud("local_map").set_transform(np.eye(4))
self._register_trajectory()
if self._toggle_voxel_grid:
self._ps.get_curve_network("voxel_grid").set_transform(np.eye(4))
else:
self._ps.get_point_cloud("current_frame").set_transform(np.eye(4))
self._ps.get_point_cloud("keypoints").set_transform(np.eye(4))
self._ps.get_point_cloud("local_map").set_transform(np.linalg.inv(self._last_pose))
self._unregister_trajectory()
if self._toggle_voxel_grid:
self._ps.get_curve_network("voxel_grid").set_transform(
np.linalg.inv(self._last_pose)
)
self._ps.reset_camera_to_home_view()

def _quit_callback(self):
Expand All @@ -271,7 +411,7 @@ def _trajectory_pick_callback(self):
name, idx = self._ps.get_selection()
if name == "trajectory" and self._ps.has_point_cloud(name):
pose = self._trajectory[idx]
self._selected_pose = f"x: {pose[0]:7.3f}, y: {pose[1]:7.3f}, z: {pose[2]:7.3f}>"
self._selected_pose = f"x: {pose[0]:7.3f}, y: {pose[1]:7.3f}, z: {pose[2]:7.3f}"
else:
self._selected_pose = ""

Expand All @@ -288,11 +428,14 @@ def _main_gui_callback(self):
self._gui.Separator()
self._toggle_buttons_andslides_callback()
self._background_color_callback()
if not self._play_mode:
self._gui.Separator()
self._inspection_callback()
self._gui.Separator()
self._trajectory_pick_callback()
self._global_view_callback()
self._gui.SameLine()
self._center_viewpoint_callback()
self._gui.Separator()
self._quit_callback()

# Mouse callbacks
self._trajectory_pick_callback()
Loading