diff --git a/examples/python/arkitscenes/main.py b/examples/python/arkitscenes/main.py index 021f2ba94f88..e05277eff918 100755 --- a/examples/python/arkitscenes/main.py +++ b/examples/python/arkitscenes/main.py @@ -15,6 +15,8 @@ from scipy.spatial.transform import Rotation as R from tqdm import tqdm +Color = Tuple[float, float, float, float] + # hack for now since dataset does not provide orientation information, only known after initial visual inspection ORIENTATION = { "48458663": "landscape", @@ -34,9 +36,7 @@ def load_json(js_path: Path) -> Dict[str, Any]: return json_data -def log_annotated_bboxes( - annotation: Dict[str, Any] -) -> Tuple[npt.NDArray[np.float64], List[str], List[Tuple[int, int, int, int]]]: +def log_annotated_bboxes(annotation: Dict[str, Any]) -> Tuple[npt.NDArray[np.float64], List[str], List[Color]]: """ Logs annotated oriented bounding boxes to Rerun. @@ -56,8 +56,7 @@ def log_annotated_bboxes( # TODO(pablovela5620): Once #1581 or #1728 is resolved this can be removed color_positions = np.linspace(0, 1, num_objects) colormap = plt.cm.get_cmap("viridis") - color_array_float = [colormap(pos) for pos in color_positions] - color_list = [(int(r * 255), int(g * 255), int(b * 255), int(a * 255)) for r, g, b, a in color_array_float] + colors = [colormap(pos) for pos in color_positions] for i, label_info in enumerate(annotation["data"]): uid = label_info["uid"] @@ -75,7 +74,7 @@ def log_annotated_bboxes( position=centroid, rotation_q=rot.as_quat(), label=label, - color=color_list[i], + color=colors[i], timeless=True, ) @@ -83,7 +82,7 @@ def log_annotated_bboxes( bbox_list.append(box3d) bbox_labels.append(label) bboxes_3d = np.array(bbox_list) - return bboxes_3d, bbox_labels, color_list + return bboxes_3d, bbox_labels, colors def compute_box_3d( @@ -109,9 +108,7 @@ def compute_box_3d( return bbox3d_raw -def log_line_segments( - entity_path: str, bboxes_2d_filtered: npt.NDArray[np.float64], color: Tuple[int, int, int, int], label: str -) -> None: +def log_line_segments(entity_path: str, bboxes_2d_filtered: npt.NDArray[np.float64], color: Color, label: str) -> None: """ Generates line segments for each object's bounding box in 2d. @@ -236,7 +233,7 @@ def log_camera( entity_id: str, bboxes: npt.NDArray[np.float64], bbox_labels: List[str], - color_list: List[Tuple[int, int, int, int]], + colors: List[Color], ) -> None: """Logs camera transform and 3D bounding boxes in the image frame.""" w, h, fx, fy, cx, cy = np.loadtxt(intri_path) @@ -250,7 +247,7 @@ def log_camera( rr.log_cleared(f"{entity_id}/bbox-2d-segments", recursive=True) # Log line segments for each bounding box in the image for i, (label, bbox_2d) in enumerate(zip(bbox_labels, bboxes_2d)): - log_line_segments(f"{entity_id}/bbox-2d-segments/{label}", bbox_2d.reshape(-1, 2), color_list[i], label) + log_line_segments(f"{entity_id}/bbox-2d-segments/{label}", bbox_2d.reshape(-1, 2), colors[i], label) rr.log_rigid3( # pathlib makes it easy to get the parent, but log_rigid requires a string diff --git a/rerun_py/rerun_sdk/rerun/log/__init__.py b/rerun_py/rerun_sdk/rerun/log/__init__.py index 69c8e8fe6328..b36abdacd5c6 100644 --- a/rerun_py/rerun_sdk/rerun/log/__init__.py +++ b/rerun_py/rerun_sdk/rerun/log/__init__.py @@ -4,7 +4,6 @@ import numpy.typing as npt from rerun import bindings -from rerun.color_conversion import linear_to_gamma_u8_pixel __all__ = [ "annotation", @@ -43,7 +42,14 @@ def _to_sequence(array: Optional[npt.ArrayLike]) -> Optional[Sequence[float]]: def _normalize_colors(colors: Optional[Union[Color, Colors]] = None) -> npt.NDArray[np.uint8]: - """Normalize flexible colors arrays.""" + """ + Normalize flexible colors arrays. + + Float colors are assumed to be in 0-1 gamma sRGB space. + All other colors are assumed to be in 0-255 gamma sRGB space. + + If there is an alpha, we assume it is in linear space, and separate (NOT pre-multiplied). + """ if colors is None: # An empty array represents no colors. return np.array((), dtype=np.uint8) @@ -52,7 +58,8 @@ def _normalize_colors(colors: Optional[Union[Color, Colors]] = None) -> npt.NDAr # Rust expects colors in 0-255 uint8 if colors_array.dtype.type in [np.float32, np.float64]: - return linear_to_gamma_u8_pixel(linear=colors_array) + # Assume gamma-space colors + return np.require(np.round(colors_array * 255.0), np.uint8) return np.require(colors_array, np.uint8) diff --git a/rerun_py/rerun_sdk/rerun/log/annotation.py b/rerun_py/rerun_sdk/rerun/log/annotation.py index 47980b4d771e..c941fc22ccb0 100644 --- a/rerun_py/rerun_sdk/rerun/log/annotation.py +++ b/rerun_py/rerun_sdk/rerun/log/annotation.py @@ -90,7 +90,7 @@ def log_annotation_context( Each ClassDescription must include an annotation info with an id, which will be used for matching the class and may optionally include a label and color. - Colors should either be in 0-255 gamma space or in 0-1 linear space. Colors + Colors should either be in 0-255 gamma space or in 0-1 gamma space. Colors can be RGB or RGBA. These can either be specified verbosely as: diff --git a/rerun_py/rerun_sdk/rerun/log/arrow.py b/rerun_py/rerun_sdk/rerun/log/arrow.py index b502baa6a0e9..7ad72d06fb80 100644 --- a/rerun_py/rerun_sdk/rerun/log/arrow.py +++ b/rerun_py/rerun_sdk/rerun/log/arrow.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Optional import numpy as np import numpy.typing as npt @@ -9,7 +9,7 @@ from rerun.components.instance import InstanceArray from rerun.components.label import LabelArray from rerun.components.radius import RadiusArray -from rerun.log import _normalize_colors, _normalize_radii +from rerun.log import Color, _normalize_colors, _normalize_radii from rerun.log.extension_components import _add_extension_components from rerun.log.log_decorator import log_decorator @@ -24,7 +24,7 @@ def log_arrow( origin: Optional[npt.ArrayLike], vector: Optional[npt.ArrayLike] = None, *, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, label: Optional[str] = None, width_scale: Optional[float] = None, ext: Optional[Dict[str, Any]] = None, @@ -48,7 +48,7 @@ def log_arrow( vector The vector along which the arrow will be drawn. color - An optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. label An optional text to show beside the arrow. width_scale diff --git a/rerun_py/rerun_sdk/rerun/log/bounding_box.py b/rerun_py/rerun_sdk/rerun/log/bounding_box.py index cda8b6e10d03..869eaf7a6553 100644 --- a/rerun_py/rerun_sdk/rerun/log/bounding_box.py +++ b/rerun_py/rerun_sdk/rerun/log/bounding_box.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Optional import numpy as np import numpy.typing as npt @@ -12,7 +12,7 @@ from rerun.components.quaternion import QuaternionArray from rerun.components.radius import RadiusArray from rerun.components.vec import Vec3DArray -from rerun.log import _normalize_colors, _normalize_ids, _normalize_radii +from rerun.log import Color, _normalize_colors, _normalize_ids, _normalize_radii from rerun.log.extension_components import _add_extension_components from rerun.log.log_decorator import log_decorator @@ -28,7 +28,7 @@ def log_obb( half_size: Optional[npt.ArrayLike], position: Optional[npt.ArrayLike] = None, rotation_q: Optional[npt.ArrayLike] = None, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, stroke_width: Optional[float] = None, label: Optional[str] = None, class_id: Optional[int] = None, @@ -55,7 +55,7 @@ def log_obb( rotation_q: Optional array with quaternion coordinates [x, y, z, w] for the rotation from model to world space. color: - Optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. stroke_width: Optional width of the line edges. label: diff --git a/rerun_py/rerun_sdk/rerun/log/lines.py b/rerun_py/rerun_sdk/rerun/log/lines.py index ac46b9fe43d8..01055647398a 100644 --- a/rerun_py/rerun_sdk/rerun/log/lines.py +++ b/rerun_py/rerun_sdk/rerun/log/lines.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Optional import numpy as np import numpy.typing as npt @@ -9,7 +9,7 @@ from rerun.components.instance import InstanceArray from rerun.components.linestrip import LineStrip2DArray, LineStrip3DArray from rerun.components.radius import RadiusArray -from rerun.log import _normalize_colors, _normalize_radii +from rerun.log import Color, _normalize_colors, _normalize_radii from rerun.log.extension_components import _add_extension_components from rerun.log.log_decorator import log_decorator @@ -26,7 +26,7 @@ def log_path( positions: Optional[npt.ArrayLike], *, stroke_width: Optional[float] = None, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, ext: Optional[Dict[str, Any]] = None, timeless: bool = False, ) -> None: @@ -39,7 +39,7 @@ def log_line_strip( positions: Optional[npt.ArrayLike], *, stroke_width: Optional[float] = None, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, ext: Optional[Dict[str, Any]] = None, timeless: bool = False, ) -> None: @@ -65,7 +65,7 @@ def log_line_strip( stroke_width: Optional width of the line. color: - Optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. ext: Optional dictionary of extension components. See [rerun.log_extension_components][] timeless: @@ -114,7 +114,7 @@ def log_line_segments( positions: npt.ArrayLike, *, stroke_width: Optional[float] = None, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, ext: Optional[Dict[str, Any]] = None, timeless: bool = False, ) -> None: @@ -139,7 +139,7 @@ def log_line_segments( stroke_width: Optional width of the line. color: - Optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. ext: Optional dictionary of extension components. See [rerun.log_extension_components][] timeless: diff --git a/rerun_py/rerun_sdk/rerun/log/mesh.py b/rerun_py/rerun_sdk/rerun/log/mesh.py index 8d7d986ce40e..8d122b6bc3f5 100644 --- a/rerun_py/rerun_sdk/rerun/log/mesh.py +++ b/rerun_py/rerun_sdk/rerun/log/mesh.py @@ -70,7 +70,8 @@ def log_mesh( albedo_factor: Optional color multiplier of the mesh using RGB or unmuliplied RGBA in linear 0-1 space. vertex_colors: - Optional array of RGB(a) vertex colors + Optional array of RGB(A) vertex colors, in sRGB gamma space, either as 0-1 floats or 0-255 integers. + If specified, the alpha is considered separate (unmultiplied). timeless: If true, the mesh will be timeless (default: False) diff --git a/rerun_py/rerun_sdk/rerun/log/points.py b/rerun_py/rerun_sdk/rerun/log/points.py index 040c7c6ae928..2da62475f1d3 100644 --- a/rerun_py/rerun_sdk/rerun/log/points.py +++ b/rerun_py/rerun_sdk/rerun/log/points.py @@ -36,7 +36,7 @@ def log_point( position: Optional[npt.ArrayLike] = None, *, radius: Optional[float] = None, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, label: Optional[str] = None, class_id: Optional[int] = None, keypoint_id: Optional[int] = None, @@ -66,7 +66,7 @@ def log_point( radius: Optional radius (make it a sphere). color: - Optional color of the point. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. label: Optional text to show with the point. class_id: @@ -169,6 +169,8 @@ def log_points( Unique numeric id that shows up when you hover or select the point. colors: Optional colors of the points. + The colors are interpreted as RGB or RGBA in sRGB gamma-space, + as either 0-1 floats or 0-255 integers, with separate alpha. radii: Optional radii (make it a sphere). labels: diff --git a/rerun_py/rerun_sdk/rerun/log/rects.py b/rerun_py/rerun_sdk/rerun/log/rects.py index 3d056d85f95b..36006fe4a43c 100644 --- a/rerun_py/rerun_sdk/rerun/log/rects.py +++ b/rerun_py/rerun_sdk/rerun/log/rects.py @@ -34,7 +34,7 @@ def log_rect( rect: Optional[npt.ArrayLike], *, rect_format: RectFormat = RectFormat.XYWH, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, label: Optional[str] = None, class_id: Optional[int] = None, ext: Optional[Dict[str, Any]] = None, @@ -52,7 +52,7 @@ def log_rect( rect_format: how to interpret the `rect` argument color: - Optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. label: Optional text to show inside the rectangle. class_id: @@ -139,7 +139,7 @@ def log_rects( identifiers: Unique numeric id that shows up when you hover or select the point. colors: - Optional per-rectangle RGB or RGBA triplet in 0-255 sRGB. + Optional per-rectangle gamma-space RGB or RGBA as 0-1 floats or 0-255 integers. labels: Optional per-rectangle text to show inside the rectangle. class_ids: diff --git a/rerun_py/rerun_sdk/rerun/log/scalar.py b/rerun_py/rerun_sdk/rerun/log/scalar.py index 66a6d49ff733..b80d4c7906c8 100644 --- a/rerun_py/rerun_sdk/rerun/log/scalar.py +++ b/rerun_py/rerun_sdk/rerun/log/scalar.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Sequence +from typing import Any, Dict, Optional import numpy as np @@ -8,7 +8,7 @@ from rerun.components.label import LabelArray from rerun.components.radius import RadiusArray from rerun.components.scalar import ScalarArray, ScalarPlotPropsArray -from rerun.log import _normalize_colors +from rerun.log import Color, _normalize_colors from rerun.log.extension_components import _add_extension_components from rerun.log.log_decorator import log_decorator @@ -23,7 +23,7 @@ def log_scalar( scalar: float, *, label: Optional[str] = None, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, radius: Optional[float] = None, scattered: Optional[bool] = None, ext: Optional[Dict[str, Any]] = None, @@ -82,7 +82,7 @@ def log_scalar( line will be named after the entity path. The plot itself is named after the space it's in. color: - An optional color in the form of a RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. If left unspecified, a pseudo-random color will be used instead. That same color will apply to all points residing in the same entity path @@ -122,7 +122,7 @@ def log_scalar( instanced["rerun.label"] = LabelArray.new([label]) if color: - colors = _normalize_colors(np.array([color])) + colors = _normalize_colors([color]) instanced["rerun.colorrgba"] = ColorRGBAArray.from_numpy(colors) if radius: diff --git a/rerun_py/rerun_sdk/rerun/log/text.py b/rerun_py/rerun_sdk/rerun/log/text.py index 5f2a1308f7ce..fddb04f37fb8 100644 --- a/rerun_py/rerun_sdk/rerun/log/text.py +++ b/rerun_py/rerun_sdk/rerun/log/text.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, Final, Optional, Sequence +from typing import Any, Dict, Final, Optional # Fully qualified to avoid circular import import rerun.log.extension_components @@ -7,7 +7,7 @@ from rerun.components.color import ColorRGBAArray from rerun.components.instance import InstanceArray from rerun.components.text_entry import TextEntryArray -from rerun.log import _normalize_colors +from rerun.log import Color, _normalize_colors from rerun.log.log_decorator import log_decorator from rerun.log.text_internal import LogLevel @@ -71,7 +71,7 @@ def log_text_entry( text: str, *, level: Optional[str] = LogLevel.INFO, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, ext: Optional[Dict[str, Any]] = None, timeless: bool = False, ) -> None: @@ -89,7 +89,7 @@ def log_text_entry( be an arbitrary string, but it's recommended to use one of the constants from [LogLevel][rerun.log.text.LogLevel] color: - Optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. ext: Optional dictionary of extension components. See [rerun.log_extension_components][] timeless: diff --git a/rerun_py/rerun_sdk/rerun/log/text_internal.py b/rerun_py/rerun_sdk/rerun/log/text_internal.py index 1164c6bd9500..41898e4dfa5f 100644 --- a/rerun_py/rerun_sdk/rerun/log/text_internal.py +++ b/rerun_py/rerun_sdk/rerun/log/text_internal.py @@ -1,13 +1,13 @@ import logging from dataclasses import dataclass -from typing import Any, Dict, Final, Optional, Sequence +from typing import Any, Dict, Final, Optional # Fully qualified to avoid circular import from rerun import bindings from rerun.components.color import ColorRGBAArray from rerun.components.instance import InstanceArray from rerun.components.text_entry import TextEntryArray -from rerun.log import _normalize_colors +from rerun.log import Color, _normalize_colors __all__ = [ "LogLevel", @@ -48,7 +48,7 @@ def log_text_entry_internal( text: str, *, level: Optional[str] = LogLevel.INFO, - color: Optional[Sequence[int]] = None, + color: Optional[Color] = None, timeless: bool = False, ) -> None: """ @@ -68,7 +68,7 @@ def log_text_entry_internal( be an arbitrary string, but it's recommended to use one of the constants from [LogLevel][rerun.log.text.LogLevel] color: - Optional RGB or RGBA triplet in 0-255 sRGB. + Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha. timeless: Whether the text entry should be timeless.