Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
14 changes: 12 additions & 2 deletions dimos/memory/codecs/jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@

from __future__ import annotations

from dimos.msgs.sensor_msgs.Image import Image
from dimos.msgs.sensor_msgs.Image import Image, ImageFormat

DEPTH_FORMATS = (ImageFormat.DEPTH, ImageFormat.DEPTH16)


class JpegCodec:
"""Codec for Image types — JPEG-compressed inside an LCM Image envelope.

Uses ``Image.lcm_jpeg_encode/decode`` which preserves ``ts``, ``frame_id``,
and all LCM header fields. Pixel data is lossy-compressed via TurboJPEG.

Depth frames are stored uncompressed instead. JPEG is 8-bit colour: a float32
metre map cannot be encoded at all, and a uint16 one would be rescaled to 8
bits and then lossy-compressed, quietly destroying the metric values a cloud
is unprojected from. The LCM envelope carries its own encoding, so both kinds
decode through the same path.
"""

def __init__(self, quality: int = 50) -> None:
Expand All @@ -33,7 +41,9 @@ def payload_type(self) -> type[Image]:
return Image

def encode(self, value: Image) -> bytes:
if value.format in DEPTH_FORMATS:
return value.lcm_encode()
return value.lcm_jpeg_encode(quality=self._quality)

def decode(self, data: bytes) -> Image:
return Image.lcm_jpeg_decode(data)
return Image.lcm_decode(data)
25 changes: 25 additions & 0 deletions dimos/memory/codecs/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,31 @@ def test_different_values_produce_different_bytes(self, case: Case) -> None:
assert len(set(encodings)) > 1, "All values encoded to identical bytes"


class TestJpegCodecDepth:
"""Depth shares the Image type with colour, so one codec has to carry both."""

@pytest.mark.parametrize(
("dtype", "fmt"),
[("float32", ImageFormat.DEPTH), ("uint16", ImageFormat.DEPTH16)],
)
def test_depth_survives_the_roundtrip_exactly(self, dtype: str, fmt: ImageFormat) -> None:
"""Lossy depth is not degraded depth, it is wrong geometry.

A JPEG'd metre map unprojects to a cloud in the wrong place, and the
float32 case does not even encode — it raises out of the recorder.
"""
import numpy as np

metres = np.linspace(0.5, 6.0, 48 * 64).reshape(48, 64).astype(dtype)
depth = Image(data=metres, format=fmt, frame_id="head_optical", ts=1.0)

decoded = JpegCodec().decode(JpegCodec().encode(depth))

assert decoded.format == fmt
assert decoded.frame_id == "head_optical"
np.testing.assert_array_equal(decoded.data, metres)


class TestCodecFor:
"""codec_for() auto-selects the right codec."""

Expand Down
Loading