Message types and operators
Built-in decoders, encoders and operators, and how to register your own.
Decoders
A decoder reads a message into an array. Only these types, and types with a custom decoder, work as observations or port from a bag. A message that fails to decode is dropped with one warning per stream.
Type |
dtype |
|
Field paths |
|---|---|---|---|
|
|
no |
Whole image. Encodings |
|
|
no |
Whole image, decoded with OpenCV. |
|
|
required |
|
|
|
required |
Dotted path, e.g. |
|
|
required |
Dotted path, e.g. |
|
|
required |
|
|
|
required |
|
|
|
required |
|
|
|
required |
First point only. |
|
|
required |
|
|
|
optional |
|
|
|
optional |
Same. |
|
|
optional |
Same. |
|
matching |
no |
Scalar. |
|
|
no |
Text. |
Encoders
An encoder writes an array into a message. Only these types work as actions
or teleop.feedback.
Type |
|
Stamp |
Notes |
|---|---|---|---|
|
required |
|
A selected field has to cover every named joint. Unselected fields stay empty. |
|
required |
|
One point, |
|
required |
|
Arrays sized to the highest index. Gaps are zero. Buttons are rounded. |
|
required |
none |
Full coverage, as for JointState. |
|
required |
none |
|
|
required |
|
|
|
no |
none |
The source has to be one wide. |
|
optional |
none |
One wide without |
|
optional |
none |
One wide without |
On the serve path a frame runs the inverse apply pipeline, then a width
check, then a finiteness check. A frame with NaN or Inf in it is dropped
whole and logged. If frames keep failing, the watchdog publishes the declared
safety action.
Operators
Operator |
Form |
Inverse |
On actions |
Behavior |
|---|---|---|---|---|
|
|
|
yes |
Bijective. Round-trip checked at load. |
|
|
itself |
yes |
Element-wise clip. Exactly the keys |
|
|
none |
no |
Nearest-neighbour. Image sources only, an error elsewhere. |
Recording runs apply front to back. Serving runs it back to front through
the inverses.
Custom codecs
Register a decoder for a type Rosetta doesn’t ship, and an encoder if the type is used as an action.
import numpy as np
from rosetta.frames.codecs import register_decoder, register_encoder
@register_decoder("my_msgs/msg/MySensor", dtype="float64")
def decode_my_sensor(msg, spec):
# spec.names is the select list as a tuple, () without select
return np.array([msg.field1, msg.field2], dtype=np.float64)
@register_encoder("my_msgs/msg/MyCommand")
def encode_my_command(values, spec, stamp_ns=None):
# values already ran the inverse apply pipeline
...
Two ways to make Rosetta find them:
Advertise the module under the entry-point group
rosetta.codecs. Rosetta imports it when a contract loads, and the contract only names the type.Name the function in the contract with
decoder: my_pkg.codecs:decode_my_sensororencoder: .... The module has to be importable. The path is checked at load.
Registering a codec for a type that already has one is an error unless you
pass override=True. Passing override=True for a type with no codec is an
error too.
Custom operators
from rosetta.contract.operators import register_operator
Advertise the module under the entry-point group rosetta.operators. An
operator registers with a kind: FORWARD_ONLY like resize,
BIDIRECTIONAL like clamp, or BIJECTIVE like rad2deg. Only a
BIJECTIVE operator is round-trip checked at load. A duplicate name is an
error unless you pass override=True.