Skip to content

Commit f218c14

Browse files
Evgeni Raikhelclaude
andcommitted
feat(realsense-viewer): viewport grid and metadata overlay
Adds an optional 2-D grid line overlay and per-stream metadata overlay to the realsense-viewer stream viewports, compiled on demand via the BUILD_VIEWPORT_GRID_OVERLAY CMake flag (default OFF). Grid configuration is read at startup from config-settings.xml (placed next to the exe) under a <realsense-viewer>/<viewport-grid> hierarchy, keeping the file extensible for future viewer-wide settings: <realsense-viewer> <viewport-grid> <horizontal_lines count="1"/> <!-- range 1-5, default 1 --> <vertical_lines count="1"/> <!-- range 1-5, default 1 --> <line_width pixels="1"/> <!-- >= 1, default 1 --> <line_color r="255" g="255" b="255"/> <!-- default white --> </viewport-grid> </realsense-viewer> Out-of-range values fall back to their defaults (no clamping). Missing file or invalid XML falls back to all defaults silently. Two toggle buttons are added to each stream header bar: - Grid icon (fa-th, U+F00A): toggles the GL line grid over the viewport - Info icon: toggles metadata text overlay (stream type, format, resolution, frame#, timestamp, FPS) Both buttons start in the off state on every launch. GL color state is properly saved/restored (GL_CURRENT_BIT) to prevent the grid color from bleeding into adjacent PiP thumbnails. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8c53a92 commit f218c14

7 files changed

Lines changed: 244 additions & 0 deletions

File tree

CMake/lrs_options.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ option(USE_EXTERNAL_LZ4 "Use externally build LZ4 library instead of building an
6363
option(BUILD_ASAN "Enable AddressSanitizer" OFF)
6464
option(BUILD_ROSBAG2 "Build and use rosbag2 recording system" ON) # temporary flag, should be removed when deprecated ROSBAG1 recording system is removed
6565
mark_as_advanced(BUILD_ASAN)
66+
option(BUILD_VIEWPORT_GRID_OVERLAY "Build 2D viewport grid overlay for realsense-viewer (configurable via config-settings.xml)" OFF)

common/device-model.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ namespace rs2
141141
static const char* show_map_ruler{ "viewer_model.show_map_ruler" };
142142
static const char* show_stream_details{ "viewer_model.show_stream_details" };
143143
static const char* metric_system{ "viewer_model.metric_system" };
144+
144145
static const char* shading_mode{ "viewer_model.shading_mode" };
145146
static const char* commands_xml{ "viewer_model.commands_xml" };
146147
static const char* hwlogger_xml{ "viewer_model.hwlogger_xml" };

common/stream-model.cpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
#include "os.h"
88
#include <imgui_internal.h>
99
#include <realsense_imgui.h>
10+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
11+
#include "../third-party/rapidxml/rapidxml.hpp"
12+
#include <fstream>
13+
#include <iomanip>
14+
#include <sstream>
15+
#endif
1016

1117
struct attribute
1218
{
@@ -28,6 +34,9 @@ namespace rs2
2834
configurations::viewer::show_stream_details, false);
2935
show_safety_zones_2d = config_file::instance().get_or_default(
3036
configurations::viewer::show_safety_zones_2d, true);
37+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
38+
load_grid_config();
39+
#endif
3140
}
3241

3342
std::shared_ptr<texture_buffer> stream_model::upload_frame(frame&& f)
@@ -131,6 +140,84 @@ namespace rs2
131140
glPopAttrib();
132141
}
133142

143+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
144+
static void draw_2d_grid(const rect& r, int h_lines, int v_lines, int line_width,
145+
int cr, int cg, int cb)
146+
{
147+
glPushAttrib(GL_ENABLE_BIT | GL_LINE_BIT | GL_COLOR_BUFFER_BIT | GL_CURRENT_BIT);
148+
glLineWidth(static_cast<GLfloat>(line_width));
149+
glEnable(GL_BLEND);
150+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
151+
glColor4f(cr / 255.f, cg / 255.f, cb / 255.f, 0.7f);
152+
glBegin(GL_LINES);
153+
for (int c = 1; c <= v_lines; ++c)
154+
{
155+
float x = r.x + r.w * c / static_cast<float>(v_lines + 1);
156+
glVertex2f(x, r.y);
157+
glVertex2f(x, r.y + r.h);
158+
}
159+
for (int row = 1; row <= h_lines; ++row)
160+
{
161+
float y = r.y + r.h * row / static_cast<float>(h_lines + 1);
162+
glVertex2f(r.x, y);
163+
glVertex2f(r.x + r.w, y);
164+
}
165+
glEnd();
166+
glPopAttrib();
167+
}
168+
169+
void stream_model::load_grid_config()
170+
{
171+
std::ifstream fin("config-settings.xml");
172+
if (!fin.is_open())
173+
return;
174+
175+
std::stringstream ss;
176+
ss << fin.rdbuf();
177+
auto xml = ss.str();
178+
179+
try
180+
{
181+
rapidxml::xml_document<> doc;
182+
doc.parse<0>(doc.allocate_string(xml.c_str()));
183+
184+
auto viewer_root = doc.first_node("realsense-viewer");
185+
if (!viewer_root) return;
186+
auto root = viewer_root->first_node("viewport-grid");
187+
if (!root) return;
188+
189+
auto valid_lines = [](int v) { return (v >= 1 && v <= 5) ? v : 1; };
190+
191+
if (auto n = root->first_node("horizontal_lines"))
192+
if (auto a = n->first_attribute("count"))
193+
grid_h_lines = valid_lines(std::atoi(a->value()));
194+
195+
if (auto n = root->first_node("vertical_lines"))
196+
if (auto a = n->first_attribute("count"))
197+
grid_v_lines = valid_lines(std::atoi(a->value()));
198+
199+
if (auto n = root->first_node("line_width"))
200+
if (auto a = n->first_attribute("pixels"))
201+
{
202+
int w = std::atoi(a->value());
203+
if (w >= 1) grid_line_width = w;
204+
}
205+
206+
if (auto n = root->first_node("line_color"))
207+
{
208+
auto valid_byte = [](int v) { return v >= 0 && v <= 255; };
209+
int r = grid_color_r, g = grid_color_g, b = grid_color_b;
210+
if (auto a = n->first_attribute("r")) r = std::atoi(a->value());
211+
if (auto a = n->first_attribute("g")) g = std::atoi(a->value());
212+
if (auto a = n->first_attribute("b")) b = std::atoi(a->value());
213+
if (valid_byte(r) && valid_byte(g) && valid_byte(b))
214+
{ grid_color_r = r; grid_color_g = g; grid_color_b = b; }
215+
}
216+
}
217+
catch (...) {}
218+
}
219+
#endif
220+
134221
bool stream_model::is_stream_visible() const
135222
{
136223
if (dev &&
@@ -413,6 +500,10 @@ namespace rs2
413500
if (RS2_STREAM_DEPTH == profile.stream_type()) ++num_of_buttons; // Color map ruler button
414501
if (RS2_FORMAT_MOTION_XYZ32F == profile.format()) ++num_of_buttons; // Motion graph button
415502
if (RS2_STREAM_OCCUPANCY == profile.stream_type() && _normalized_zoom.w == 1) ++num_of_buttons; // Safety zones button
503+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
504+
++num_of_buttons; // Grid overlay button
505+
++num_of_buttons; // Viewport metadata overlay button
506+
#endif
416507

417508
RsImGui_ScopePushFont(font);
418509
ImGui::PushStyleColor(ImGuiCol_Text, light_grey);
@@ -548,6 +639,56 @@ namespace rs2
548639
}
549640
ImGui::SameLine();
550641

642+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
643+
label = rsutils::string::from() << textual_icons::grid << "##Grid " << profile.unique_id();
644+
if (show_grid)
645+
{
646+
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
647+
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue);
648+
if (ImGui::Button(label.c_str(), { 24, top_bar_height }))
649+
{
650+
show_grid = false;
651+
}
652+
if (ImGui::IsItemHovered())
653+
RsImGui::CustomTooltip("Hide grid overlay");
654+
ImGui::PopStyleColor(2);
655+
}
656+
else
657+
{
658+
if (ImGui::Button(label.c_str(), { 24, top_bar_height }))
659+
{
660+
show_grid = true;
661+
}
662+
if (ImGui::IsItemHovered())
663+
RsImGui::CustomTooltip("Show grid overlay");
664+
}
665+
ImGui::SameLine();
666+
667+
label = rsutils::string::from() << textual_icons::info_circle << "##VpMd " << profile.unique_id();
668+
if (show_viewport_metadata)
669+
{
670+
ImGui::PushStyleColor(ImGuiCol_Text, light_blue);
671+
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue);
672+
if (ImGui::Button(label.c_str(), { 24, top_bar_height }))
673+
{
674+
show_viewport_metadata = false;
675+
}
676+
if (ImGui::IsItemHovered())
677+
RsImGui::CustomTooltip("Hide metadata overlay");
678+
ImGui::PopStyleColor(2);
679+
}
680+
else
681+
{
682+
if (ImGui::Button(label.c_str(), { 24, top_bar_height }))
683+
{
684+
show_viewport_metadata = true;
685+
}
686+
if (ImGui::IsItemHovered())
687+
RsImGui::CustomTooltip("Show metadata overlay");
688+
}
689+
ImGui::SameLine();
690+
#endif
691+
551692
if (RS2_STREAM_DEPTH == profile.stream_type())
552693
{
553694
label = rsutils::string::from() << textual_icons::bar_chart << "##Color map";
@@ -2036,6 +2177,60 @@ namespace rs2
20362177
}
20372178

20382179
update_ae_roi_rect(stream_rect, g, error_message);
2180+
2181+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
2182+
if (show_grid)
2183+
draw_2d_grid(stream_rect, grid_h_lines, grid_v_lines, grid_line_width,
2184+
grid_color_r, grid_color_g, grid_color_b);
2185+
2186+
if (show_viewport_metadata)
2187+
{
2188+
auto* dl = ImGui::GetForegroundDrawList();
2189+
auto fnt = ImGui::GetFont();
2190+
const float line_h = ImGui::GetTextLineHeight();
2191+
const float pad = 6.f;
2192+
const float bg_alpha = 0.55f;
2193+
const ImU32 bg_col = IM_COL32(0, 0, 0, static_cast<int>(bg_alpha * 255));
2194+
const ImU32 txt_col = IM_COL32(255, 255, 255, 230);
2195+
2196+
std::string stream_type_str = rs2_stream_to_string(profile.stream_type());
2197+
std::string format_str = rs2_format_to_string(profile.format());
2198+
std::ostringstream oss;
2199+
oss << static_cast<int>(original_size.x) << "x" << static_cast<int>(original_size.y);
2200+
std::string res_str = oss.str();
2201+
2202+
oss.str(""); oss << "FPS: " << std::fixed << std::setprecision(1) << view_fps.get_fps();
2203+
std::string fps_str = oss.str();
2204+
2205+
oss.str(""); oss << "Frame#: " << frame_number;
2206+
std::string frame_str = oss.str();
2207+
2208+
oss.str(""); oss << "TS: " << std::fixed << std::setprecision(3) << timestamp << " ms";
2209+
std::string ts_str = oss.str();
2210+
2211+
std::string hdr_str = stream_type_str + " " + format_str + " " + res_str;
2212+
2213+
std::vector<std::string> lines = { hdr_str, frame_str, ts_str, fps_str };
2214+
2215+
float max_w = 0.f;
2216+
for (auto& s : lines)
2217+
max_w = std::max(max_w, ImGui::CalcTextSize(s.c_str()).x);
2218+
2219+
float bg_x1 = stream_rect.x + pad;
2220+
float bg_y1 = stream_rect.y + pad;
2221+
float bg_x2 = bg_x1 + max_w + 2 * pad;
2222+
float bg_y2 = bg_y1 + lines.size() * (line_h + 2.f) + pad;
2223+
2224+
dl->AddRectFilled({ bg_x1, bg_y1 }, { bg_x2, bg_y2 }, bg_col, 3.f);
2225+
2226+
float ty = bg_y1 + pad * 0.5f;
2227+
for (auto& s : lines)
2228+
{
2229+
dl->AddText(fnt, line_h, { bg_x1 + pad, ty }, txt_col, s.c_str());
2230+
ty += line_h + 2.f;
2231+
}
2232+
}
2233+
#endif
20392234
}
20402235
texture->show_preview(stream_rect, _normalized_zoom);
20412236

common/stream-model.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ namespace rs2
106106
bool show_metadata = false;
107107
bool show_safety_zones_2d = true;
108108

109+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
110+
bool show_grid = false;
111+
int grid_h_lines = 1;
112+
int grid_v_lines = 1;
113+
int grid_line_width = 1;
114+
int grid_color_r = 255;
115+
int grid_color_g = 255;
116+
int grid_color_b = 255;
117+
bool show_viewport_metadata = false;
118+
#endif
119+
109120
std::shared_ptr<graph_model> graph;
110121
bool show_graph = false;
111122
bool graph_initialized = false;
@@ -128,6 +139,9 @@ namespace rs2
128139
void add_dds_metadata_descriptions(std::map<rs2_frame_metadata_value, std::string>& descriptions) const;
129140
void deal_d585S_metadata_md_values_special_cases(const frame& f);
130141
std::string get_meaning(const rs2_frame_metadata_value& md_val, const std::vector<std::string>& reasons, const std::string& reason_for_zero = "") const;
142+
#ifdef BUILD_VIEWPORT_GRID_OVERLAY
143+
void load_grid_config();
144+
#endif
131145
};
132146

133147

common/textual-icons.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ namespace rs2
3434
// A note to a maintainer - preserve order when adding values to avoid duplicates
3535
static const textual_icon search{ u8"\uf002" };
3636
static const textual_icon file_movie{ u8"\uf008" };
37+
static const textual_icon grid{ u8"\uf00a" }; // fa-th: 3x3 grid of squares (viewport grid overlay)
3738
static const textual_icon check{ u8"\uf00c" };
3839
static const textual_icon times{ u8"\uf00d" };
3940
static const textual_icon power_off{ u8"\uf011" };

tools/realsense-viewer/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@ if(OPENVINO_NGRAPH)
256256
target_compile_definitions(realsense-viewer PRIVATE OPENVINO_NGRAPH)
257257
endif()
258258

259+
if(BUILD_VIEWPORT_GRID_OVERLAY)
260+
message(STATUS "Viewport grid overlay enabled for realsense-viewer")
261+
target_compile_definitions(realsense-viewer PRIVATE BUILD_VIEWPORT_GRID_OVERLAY)
262+
add_custom_command(TARGET realsense-viewer POST_BUILD
263+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
264+
${CMAKE_CURRENT_SOURCE_DIR}/config-settings.xml
265+
$<TARGET_FILE_DIR:realsense-viewer>/config-settings.xml
266+
COMMENT "Copying grid config-settings.xml to output directory"
267+
)
268+
endif()
269+
259270
source_group("SW-Update" FILES ${SW_UPDATE_FILES})
260271

261272
include_directories(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
realsense-viewer configuration file.
4+
5+
viewport-grid section (requires BUILD_VIEWPORT_GRID_OVERLAY=ON):
6+
horizontal_lines : number of horizontal interior lines (range 1-5, default 1)
7+
vertical_lines : number of vertical interior lines (range 1-5, default 1)
8+
line_width : line thickness in pixels (integer >= 1, default 1)
9+
line_color : RGB color, each channel in the range 0-255 (default White 255,255,255)
10+
11+
Values outside the valid range are clamped to the nearest bound.
12+
Missing or invalid values fall back to the defaults listed above.
13+
-->
14+
<realsense-viewer>
15+
<viewport-grid>
16+
<horizontal_lines count="1"/>
17+
<vertical_lines count="1"/>
18+
<line_width pixels="1"/>
19+
<line_color r="255" g="255" b="255"/>
20+
</viewport-grid>
21+
</realsense-viewer>

0 commit comments

Comments
 (0)