Skip to content

Commit 9c1babe

Browse files
authored
Python SDK: document that we also accept colors in 0-1 floats (#1740)
* Python SDK: document that we also accept colors in 0-1 floats * Assume float colors to be in gamma-space, and document that * Update arkitscenes example * Fix bug * typo * py-format
1 parent 1946683 commit 9c1babe

File tree

12 files changed

+57
-50
lines changed

12 files changed

+57
-50
lines changed

examples/python/arkitscenes/main.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from scipy.spatial.transform import Rotation as R
1616
from tqdm import tqdm
1717

18+
Color = Tuple[float, float, float, float]
19+
1820
# hack for now since dataset does not provide orientation information, only known after initial visual inspection
1921
ORIENTATION = {
2022
"48458663": "landscape",
@@ -34,9 +36,7 @@ def load_json(js_path: Path) -> Dict[str, Any]:
3436
return json_data
3537

3638

37-
def log_annotated_bboxes(
38-
annotation: Dict[str, Any]
39-
) -> Tuple[npt.NDArray[np.float64], List[str], List[Tuple[int, int, int, int]]]:
39+
def log_annotated_bboxes(annotation: Dict[str, Any]) -> Tuple[npt.NDArray[np.float64], List[str], List[Color]]:
4040
"""
4141
Logs annotated oriented bounding boxes to Rerun.
4242
@@ -56,8 +56,7 @@ def log_annotated_bboxes(
5656
# TODO(pablovela5620): Once #1581 or #1728 is resolved this can be removed
5757
color_positions = np.linspace(0, 1, num_objects)
5858
colormap = plt.cm.get_cmap("viridis")
59-
color_array_float = [colormap(pos) for pos in color_positions]
60-
color_list = [(int(r * 255), int(g * 255), int(b * 255), int(a * 255)) for r, g, b, a in color_array_float]
59+
colors = [colormap(pos) for pos in color_positions]
6160

6261
for i, label_info in enumerate(annotation["data"]):
6362
uid = label_info["uid"]
@@ -75,15 +74,15 @@ def log_annotated_bboxes(
7574
position=centroid,
7675
rotation_q=rot.as_quat(),
7776
label=label,
78-
color=color_list[i],
77+
color=colors[i],
7978
timeless=True,
8079
)
8180

8281
box3d = compute_box_3d(half_size, centroid, rotation)
8382
bbox_list.append(box3d)
8483
bbox_labels.append(label)
8584
bboxes_3d = np.array(bbox_list)
86-
return bboxes_3d, bbox_labels, color_list
85+
return bboxes_3d, bbox_labels, colors
8786

8887

8988
def compute_box_3d(
@@ -109,9 +108,7 @@ def compute_box_3d(
109108
return bbox3d_raw
110109

111110

112-
def log_line_segments(
113-
entity_path: str, bboxes_2d_filtered: npt.NDArray[np.float64], color: Tuple[int, int, int, int], label: str
114-
) -> None:
111+
def log_line_segments(entity_path: str, bboxes_2d_filtered: npt.NDArray[np.float64], color: Color, label: str) -> None:
115112
"""
116113
Generates line segments for each object's bounding box in 2d.
117114
@@ -236,7 +233,7 @@ def log_camera(
236233
entity_id: str,
237234
bboxes: npt.NDArray[np.float64],
238235
bbox_labels: List[str],
239-
color_list: List[Tuple[int, int, int, int]],
236+
colors: List[Color],
240237
) -> None:
241238
"""Logs camera transform and 3D bounding boxes in the image frame."""
242239
w, h, fx, fy, cx, cy = np.loadtxt(intri_path)
@@ -250,7 +247,7 @@ def log_camera(
250247
rr.log_cleared(f"{entity_id}/bbox-2d-segments", recursive=True)
251248
# Log line segments for each bounding box in the image
252249
for i, (label, bbox_2d) in enumerate(zip(bbox_labels, bboxes_2d)):
253-
log_line_segments(f"{entity_id}/bbox-2d-segments/{label}", bbox_2d.reshape(-1, 2), color_list[i], label)
250+
log_line_segments(f"{entity_id}/bbox-2d-segments/{label}", bbox_2d.reshape(-1, 2), colors[i], label)
254251

255252
rr.log_rigid3(
256253
# pathlib makes it easy to get the parent, but log_rigid requires a string

rerun_py/rerun_sdk/rerun/log/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import numpy.typing as npt
55

66
from rerun import bindings
7-
from rerun.color_conversion import linear_to_gamma_u8_pixel
87

98
__all__ = [
109
"annotation",
@@ -43,7 +42,14 @@ def _to_sequence(array: Optional[npt.ArrayLike]) -> Optional[Sequence[float]]:
4342

4443

4544
def _normalize_colors(colors: Optional[Union[Color, Colors]] = None) -> npt.NDArray[np.uint8]:
46-
"""Normalize flexible colors arrays."""
45+
"""
46+
Normalize flexible colors arrays.
47+
48+
Float colors are assumed to be in 0-1 gamma sRGB space.
49+
All other colors are assumed to be in 0-255 gamma sRGB space.
50+
51+
If there is an alpha, we assume it is in linear space, and separate (NOT pre-multiplied).
52+
"""
4753
if colors is None:
4854
# An empty array represents no colors.
4955
return np.array((), dtype=np.uint8)
@@ -52,7 +58,8 @@ def _normalize_colors(colors: Optional[Union[Color, Colors]] = None) -> npt.NDAr
5258

5359
# Rust expects colors in 0-255 uint8
5460
if colors_array.dtype.type in [np.float32, np.float64]:
55-
return linear_to_gamma_u8_pixel(linear=colors_array)
61+
# Assume gamma-space colors
62+
return np.require(np.round(colors_array * 255.0), np.uint8)
5663

5764
return np.require(colors_array, np.uint8)
5865

rerun_py/rerun_sdk/rerun/log/annotation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def log_annotation_context(
9090
9191
Each ClassDescription must include an annotation info with an id, which will
9292
be used for matching the class and may optionally include a label and color.
93-
Colors should either be in 0-255 gamma space or in 0-1 linear space. Colors
93+
Colors should either be in 0-255 gamma space or in 0-1 gamma space. Colors
9494
can be RGB or RGBA.
9595
9696
These can either be specified verbosely as:

rerun_py/rerun_sdk/rerun/log/arrow.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, Optional, Sequence
1+
from typing import Any, Dict, Optional
22

33
import numpy as np
44
import numpy.typing as npt
@@ -9,7 +9,7 @@
99
from rerun.components.instance import InstanceArray
1010
from rerun.components.label import LabelArray
1111
from rerun.components.radius import RadiusArray
12-
from rerun.log import _normalize_colors, _normalize_radii
12+
from rerun.log import Color, _normalize_colors, _normalize_radii
1313
from rerun.log.extension_components import _add_extension_components
1414
from rerun.log.log_decorator import log_decorator
1515

@@ -24,7 +24,7 @@ def log_arrow(
2424
origin: Optional[npt.ArrayLike],
2525
vector: Optional[npt.ArrayLike] = None,
2626
*,
27-
color: Optional[Sequence[int]] = None,
27+
color: Optional[Color] = None,
2828
label: Optional[str] = None,
2929
width_scale: Optional[float] = None,
3030
ext: Optional[Dict[str, Any]] = None,
@@ -48,7 +48,7 @@ def log_arrow(
4848
vector
4949
The vector along which the arrow will be drawn.
5050
color
51-
An optional RGB or RGBA triplet in 0-255 sRGB.
51+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
5252
label
5353
An optional text to show beside the arrow.
5454
width_scale

rerun_py/rerun_sdk/rerun/log/bounding_box.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, Optional, Sequence
1+
from typing import Any, Dict, Optional
22

33
import numpy as np
44
import numpy.typing as npt
@@ -12,7 +12,7 @@
1212
from rerun.components.quaternion import QuaternionArray
1313
from rerun.components.radius import RadiusArray
1414
from rerun.components.vec import Vec3DArray
15-
from rerun.log import _normalize_colors, _normalize_ids, _normalize_radii
15+
from rerun.log import Color, _normalize_colors, _normalize_ids, _normalize_radii
1616
from rerun.log.extension_components import _add_extension_components
1717
from rerun.log.log_decorator import log_decorator
1818

@@ -28,7 +28,7 @@ def log_obb(
2828
half_size: Optional[npt.ArrayLike],
2929
position: Optional[npt.ArrayLike] = None,
3030
rotation_q: Optional[npt.ArrayLike] = None,
31-
color: Optional[Sequence[int]] = None,
31+
color: Optional[Color] = None,
3232
stroke_width: Optional[float] = None,
3333
label: Optional[str] = None,
3434
class_id: Optional[int] = None,
@@ -55,7 +55,7 @@ def log_obb(
5555
rotation_q:
5656
Optional array with quaternion coordinates [x, y, z, w] for the rotation from model to world space.
5757
color:
58-
Optional RGB or RGBA triplet in 0-255 sRGB.
58+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
5959
stroke_width:
6060
Optional width of the line edges.
6161
label:

rerun_py/rerun_sdk/rerun/log/lines.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, Optional, Sequence
1+
from typing import Any, Dict, Optional
22

33
import numpy as np
44
import numpy.typing as npt
@@ -9,7 +9,7 @@
99
from rerun.components.instance import InstanceArray
1010
from rerun.components.linestrip import LineStrip2DArray, LineStrip3DArray
1111
from rerun.components.radius import RadiusArray
12-
from rerun.log import _normalize_colors, _normalize_radii
12+
from rerun.log import Color, _normalize_colors, _normalize_radii
1313
from rerun.log.extension_components import _add_extension_components
1414
from rerun.log.log_decorator import log_decorator
1515

@@ -26,7 +26,7 @@ def log_path(
2626
positions: Optional[npt.ArrayLike],
2727
*,
2828
stroke_width: Optional[float] = None,
29-
color: Optional[Sequence[int]] = None,
29+
color: Optional[Color] = None,
3030
ext: Optional[Dict[str, Any]] = None,
3131
timeless: bool = False,
3232
) -> None:
@@ -39,7 +39,7 @@ def log_line_strip(
3939
positions: Optional[npt.ArrayLike],
4040
*,
4141
stroke_width: Optional[float] = None,
42-
color: Optional[Sequence[int]] = None,
42+
color: Optional[Color] = None,
4343
ext: Optional[Dict[str, Any]] = None,
4444
timeless: bool = False,
4545
) -> None:
@@ -65,7 +65,7 @@ def log_line_strip(
6565
stroke_width:
6666
Optional width of the line.
6767
color:
68-
Optional RGB or RGBA triplet in 0-255 sRGB.
68+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
6969
ext:
7070
Optional dictionary of extension components. See [rerun.log_extension_components][]
7171
timeless:
@@ -114,7 +114,7 @@ def log_line_segments(
114114
positions: npt.ArrayLike,
115115
*,
116116
stroke_width: Optional[float] = None,
117-
color: Optional[Sequence[int]] = None,
117+
color: Optional[Color] = None,
118118
ext: Optional[Dict[str, Any]] = None,
119119
timeless: bool = False,
120120
) -> None:
@@ -139,7 +139,7 @@ def log_line_segments(
139139
stroke_width:
140140
Optional width of the line.
141141
color:
142-
Optional RGB or RGBA triplet in 0-255 sRGB.
142+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
143143
ext:
144144
Optional dictionary of extension components. See [rerun.log_extension_components][]
145145
timeless:

rerun_py/rerun_sdk/rerun/log/mesh.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ def log_mesh(
7070
albedo_factor:
7171
Optional color multiplier of the mesh using RGB or unmuliplied RGBA in linear 0-1 space.
7272
vertex_colors:
73-
Optional array of RGB(a) vertex colors
73+
Optional array of RGB(A) vertex colors, in sRGB gamma space, either as 0-1 floats or 0-255 integers.
74+
If specified, the alpha is considered separate (unmultiplied).
7475
timeless:
7576
If true, the mesh will be timeless (default: False)
7677

rerun_py/rerun_sdk/rerun/log/points.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def log_point(
3636
position: Optional[npt.ArrayLike] = None,
3737
*,
3838
radius: Optional[float] = None,
39-
color: Optional[Sequence[int]] = None,
39+
color: Optional[Color] = None,
4040
label: Optional[str] = None,
4141
class_id: Optional[int] = None,
4242
keypoint_id: Optional[int] = None,
@@ -66,7 +66,7 @@ def log_point(
6666
radius:
6767
Optional radius (make it a sphere).
6868
color:
69-
Optional color of the point.
69+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
7070
label:
7171
Optional text to show with the point.
7272
class_id:
@@ -169,6 +169,8 @@ def log_points(
169169
Unique numeric id that shows up when you hover or select the point.
170170
colors:
171171
Optional colors of the points.
172+
The colors are interpreted as RGB or RGBA in sRGB gamma-space,
173+
as either 0-1 floats or 0-255 integers, with separate alpha.
172174
radii:
173175
Optional radii (make it a sphere).
174176
labels:

rerun_py/rerun_sdk/rerun/log/rects.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def log_rect(
3434
rect: Optional[npt.ArrayLike],
3535
*,
3636
rect_format: RectFormat = RectFormat.XYWH,
37-
color: Optional[Sequence[int]] = None,
37+
color: Optional[Color] = None,
3838
label: Optional[str] = None,
3939
class_id: Optional[int] = None,
4040
ext: Optional[Dict[str, Any]] = None,
@@ -52,7 +52,7 @@ def log_rect(
5252
rect_format:
5353
how to interpret the `rect` argument
5454
color:
55-
Optional RGB or RGBA triplet in 0-255 sRGB.
55+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
5656
label:
5757
Optional text to show inside the rectangle.
5858
class_id:
@@ -139,7 +139,7 @@ def log_rects(
139139
identifiers:
140140
Unique numeric id that shows up when you hover or select the point.
141141
colors:
142-
Optional per-rectangle RGB or RGBA triplet in 0-255 sRGB.
142+
Optional per-rectangle gamma-space RGB or RGBA as 0-1 floats or 0-255 integers.
143143
labels:
144144
Optional per-rectangle text to show inside the rectangle.
145145
class_ids:

rerun_py/rerun_sdk/rerun/log/scalar.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, Optional, Sequence
1+
from typing import Any, Dict, Optional
22

33
import numpy as np
44

@@ -8,7 +8,7 @@
88
from rerun.components.label import LabelArray
99
from rerun.components.radius import RadiusArray
1010
from rerun.components.scalar import ScalarArray, ScalarPlotPropsArray
11-
from rerun.log import _normalize_colors
11+
from rerun.log import Color, _normalize_colors
1212
from rerun.log.extension_components import _add_extension_components
1313
from rerun.log.log_decorator import log_decorator
1414

@@ -23,7 +23,7 @@ def log_scalar(
2323
scalar: float,
2424
*,
2525
label: Optional[str] = None,
26-
color: Optional[Sequence[int]] = None,
26+
color: Optional[Color] = None,
2727
radius: Optional[float] = None,
2828
scattered: Optional[bool] = None,
2929
ext: Optional[Dict[str, Any]] = None,
@@ -82,7 +82,7 @@ def log_scalar(
8282
line will be named after the entity path. The plot itself is named after
8383
the space it's in.
8484
color:
85-
An optional color in the form of a RGB or RGBA triplet in 0-255 sRGB.
85+
Optional RGB or RGBA in sRGB gamma-space as either 0-1 floats or 0-255 integers, with separate alpha.
8686
8787
If left unspecified, a pseudo-random color will be used instead. That
8888
same color will apply to all points residing in the same entity path
@@ -122,7 +122,7 @@ def log_scalar(
122122
instanced["rerun.label"] = LabelArray.new([label])
123123

124124
if color:
125-
colors = _normalize_colors(np.array([color]))
125+
colors = _normalize_colors([color])
126126
instanced["rerun.colorrgba"] = ColorRGBAArray.from_numpy(colors)
127127

128128
if radius:

0 commit comments

Comments
 (0)