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

Add referee display layer #3337

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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 src/software/thunderscope/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ py_library(
"//software/thunderscope/gl/layers:gl_obstacle_layer",
"//software/thunderscope/gl/layers:gl_passing_layer",
"//software/thunderscope/gl/layers:gl_path_layer",
"//software/thunderscope/gl/layers:gl_referee_info_layer",
"//software/thunderscope/gl/layers:gl_sandbox_world_layer",
"//software/thunderscope/gl/layers:gl_simulator_layer",
"//software/thunderscope/gl/layers:gl_tactic_layer",
Expand Down
9 changes: 9 additions & 0 deletions src/software/thunderscope/gl/graphics/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,12 @@ py_library(
":gl_shape",
],
)

py_library(
name = "gl_label",
srcs = ["gl_label.py"],
deps = [
requirement("pyqtgraph"),
":gl_shape",
],
)
78 changes: 78 additions & 0 deletions src/software/thunderscope/gl/graphics/gl_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from PyQt6.QtGui import QFont, QColor
from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem
from pyqtgraph.Qt import QtCore, QtGui

from typing import Optional

from software.thunderscope.gl.graphics.gl_painter import GLPainter
from software.thunderscope.constants import Colors


class GLLabel(GLPainter):
"""Displays a 2D text label on the viewport"""

def __init__(
self,
parent_item: Optional[GLGraphicsItem] = None,
font: QFont = QFont("Roboto", 8),
text_color: QColor = Colors.PRIMARY_TEXT_COLOR,
offset: tuple[int, int] = (0, 0),
text: str = "",
) -> None:
"""Initialize the GLGradientLegend
Lmh-java marked this conversation as resolved.
Show resolved Hide resolved

:param parent_item: The parent item of the graphic
:param font: The font using to render the text
:param text_color: The color for rendering the text.
:param offset: The offset (x, y) from the viewport left and top edge
to use when positioning the label.
If x is negative then the x offset is |x| pixels from
the viewport right edge.
If y is negative then the y offset is |y| pixels from
the viewport bottom edge.
:param text: The optional title to display above the legend
"""
super().__init__(parent_item=parent_item)

self.text_pen = QtGui.QPen(text_color)
self.font = font
self.offset = offset
self.text = text

self.add_draw_function(self.draw_label)

def draw_label(self, painter: QtGui.QPainter, viewport_rect: QtCore.QRect) -> None:
"""Draw the label

:param painter: The QPainter to perform drawing operations with
:param viewport_rect: The QRect indicating the viewport dimensions
"""
# calculate width and height of the label
painter.setFont(self.font)
bounds = painter.boundingRect(
QtCore.QRectF(0, 0, 0, 0),
QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter,
str(self.text),
)

width = round(bounds.width())
height = round(bounds.height())

# Determine x and y coordinates of the label
if self.offset[0] < 0:
x = viewport_rect.right() + self.offset[0] - width
else:
x = viewport_rect.left() + self.offset[0]
if self.offset[1] < 0:
y = viewport_rect.bottom() + self.offset[1] - height
else:
y = viewport_rect.top() + self.offset[1]

if self.text:
painter.drawText(QtCore.QPoint(x, y), self.text)

def set_text(self, new_text: str) -> None:
"""Update the text being displayed
:param new_text: new text being displayed
"""
self.text = new_text
10 changes: 10 additions & 0 deletions src/software/thunderscope/gl/layers/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,13 @@ py_library(
requirement("pyqtgraph"),
],
)

py_library(
name = "gl_referee_info_layer",
srcs = ["gl_referee_info_layer.py"],
deps = [
":gl_layer",
"//software/thunderscope/gl/graphics:gl_label",
requirement("pyqtgraph"),
],
)
68 changes: 68 additions & 0 deletions src/software/thunderscope/gl/layers/gl_referee_info_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import Optional

from google.protobuf.json_format import MessageToDict

from proto.import_all_protos import *
from software.thunderscope.constants import DepthValues
from software.thunderscope.gl.graphics.gl_label import GLLabel
from software.thunderscope.gl.helpers.observable_list import ObservableList
from software.thunderscope.gl.layers.gl_layer import GLLayer
from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer


class GLRefereeInfoLayer(GLLayer):
"""GLLayer that visualizes referee info"""

REFEREE_COMMAND_PREFIX = "Command: "
GAMESTATE_PREFIX = "Game State: "

def __init__(self, name: str, buffer_size: int = 1) -> None:
"""Initialize the GLRefereeInfoLayer

:param name: The displayed name of the layer
:param buffer_size: the buffer size, set higher for smoother plots.
Set lower for more realtime plots. Default is arbitrary.
"""
super().__init__(name)
self.setDepthValue(DepthValues.OVERLAY_DEPTH)
self.referee_vis_buffer = ThreadSafeBuffer(buffer_size, Referee, False)

self.referee_text_graphics = ObservableList(self._graphics_changed)

# initialize the two text items to display
self.gamestate_type_text: Optional[GLLabel] = None
self.command_type_text: Optional[GLLabel] = None

def refresh_graphics(self) -> None:
"""Update displays in the layer"""
referee_proto = self.referee_vis_buffer.get(block=False, return_cached=False)
if not referee_proto:
return

referee_msg_dict = MessageToDict(referee_proto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually don't need MessageToDict (I'm not sure why we use it in some of our code). You can just access the fields directly e.g. referee_proto.stage

Copy link
Contributor Author

@Lmh-java Lmh-java Oct 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After testing, I think in this case I might want to keep MessageToDict, since directly acessing referee_proto.stage only gives me the index number (instead of the text)

if not referee_msg_dict:
return

if not self.gamestate_type_text:
self.gamestate_type_text = GLLabel(
parent_item=self,
offset=(-10, 50),
text=GLRefereeInfoLayer.GAMESTATE_PREFIX + referee_msg_dict["stage"],
)
self.referee_text_graphics.append(self.gamestate_type_text)
else:
self.gamestate_type_text.set_text(
GLRefereeInfoLayer.GAMESTATE_PREFIX + referee_msg_dict["stage"]
)

if not self.command_type_text:
self.command_type_text = GLLabel(
parent_item=self,
offset=(-10, 70),
text=GLRefereeInfoLayer.GAMESTATE_PREFIX + referee_msg_dict["command"],
)
self.referee_text_graphics.append(self.command_type_text)
else:
self.command_type_text.set_text(
GLRefereeInfoLayer.REFEREE_COMMAND_PREFIX + referee_msg_dict["command"]
)
6 changes: 6 additions & 0 deletions src/software/thunderscope/widget_setup_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
gl_tactic_layer,
gl_cost_vis_layer,
gl_trail_layer,
gl_referee_info_layer,
)


Expand Down Expand Up @@ -129,6 +130,9 @@ def setup_gl_widget(
)
tactic_layer = gl_tactic_layer.GLTacticLayer("Tactics", visualization_buffer_size)
trail_layer = gl_trail_layer.GLTrailLayer("Trail", visualization_buffer_size)
referee_layer = gl_referee_info_layer.GLRefereeInfoLayer(
"Referee Info", visualization_buffer_size
)

gl_widget.add_layer(world_layer)
gl_widget.add_layer(simulator_layer, False)
Expand All @@ -141,6 +145,7 @@ def setup_gl_widget(
gl_widget.add_layer(validation_layer)
gl_widget.add_layer(trail_layer, False)
gl_widget.add_layer(debug_shapes_layer, True)
gl_widget.add_layer(referee_layer, False)

simulation_control_toolbar = gl_widget.get_sim_control_toolbar()
simulation_control_toolbar.set_speed_callback(world_layer.set_simulation_speed)
Expand Down Expand Up @@ -189,6 +194,7 @@ def setup_gl_widget(
(CostVisualization, cost_vis_layer.cost_visualization_buffer),
(World, trail_layer.world_buffer),
(DebugShapes, debug_shapes_layer.debug_shapes_buffer),
(Referee, referee_layer.referee_vis_buffer),
]:
full_system_proto_unix_io.register_observer(*arg)

Expand Down
Loading