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 back rr.ImageEncoded for backwards compatibility #7096

Merged
merged 5 commits into from
Aug 7, 2024
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* `mesh_material: Material` has been renamed to `albedo_factor: AlbedoFactor` [#6841](https://github.com/rerun-io/rerun/pull/6841)
* 3D transform APIs: Previously, the transform component was represented as one of several variants (an Arrow union, `enum` in Rust) depending on how the transform was expressed. Instead, there are now several components for translation/scale/rotation/matrices that can live side-by-side in the 3D transform archetype.
* Python: `NV12/YUY2` are now logged with `Image`
* `ImageEncoded` has been replaced with [`EncodedImage`](https://rerun.io/docs/reference/types/archetypes/encoded_image?speculative-link) (JPEG, PNG, …) and [`Image`](https://rerun.io/docs/reference/types/archetypes/image?speculative-link) (NV12, YUY2, …)
* `ImageEncoded` is deprecated and replaced with [`EncodedImage`](https://rerun.io/docs/reference/types/archetypes/encoded_image?speculative-link) (JPEG, PNG, …) and [`Image`](https://rerun.io/docs/reference/types/archetypes/image?speculative-link) (NV12, YUY2, …)
* [`DepthImage`](https://rerun.io/docs/reference/types/archetypes/depth_image) and [`SegmentationImage`](https://rerun.io/docs/reference/types/archetypes/segmentation_image) are no longer encoded as a tensors, and expects its shape in `[width, height]` order

🧳 Migration guide: http://rerun.io/docs/reference/migration/migration-0-18?speculative-link
Expand Down
4 changes: 1 addition & 3 deletions docs/content/reference/migration/migration-0-18.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ TODO(andreas): more before/after image on different languages
`EncodedImage` is our new archetype for logging an image file, e.g. a PNG or JPEG.

#### Python
In Python we already had a `ImageEncoded` class that was responsible for logging both chroma-downsampled images (NV12, YUY2) and image files (JPEG, PNG, …).

Image files are now instead logged with [`EncodedImage`](https://rerun.io/docs/reference/types/archetypes/encoded_image?speculative-link),
`rr.ImageEncoded` is deprecated. Image files (JPEG, PNG, …) should instead be logged with [`EncodedImage`](https://rerun.io/docs/reference/types/archetypes/encoded_image?speculative-link),
and chroma-downsampled images (NV12/YUY2) are now logged with the new `Image` archetype:


Expand Down
4 changes: 4 additions & 0 deletions rerun_py/rerun_sdk/rerun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
experimental as experimental,
notebook as notebook,
)
from ._image_encoded import (
ImageEncoded as ImageEncoded,
ImageFormat as ImageFormat,
)
from ._log import (
AsComponents as AsComponents,
ComponentBatchLike as ComponentBatchLike,
Expand Down
227 changes: 227 additions & 0 deletions rerun_py/rerun_sdk/rerun/_image_encoded.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
"""
Deprecated helpers.

Use `Image` and `EncodedImage` instead.
"""

from __future__ import annotations

import io
import pathlib
import warnings
from typing import IO

from .archetypes import EncodedImage, Image
from .datatypes import Float32Like


class ImageFormat:
"""⚠️ DEPRECATED ⚠️ Image file format."""

name: str

BMP: ImageFormat
"""
BMP file format.
"""

GIF: ImageFormat
"""
JPEG/JPG file format.
"""

JPEG: ImageFormat
"""
JPEG/JPG file format.
"""

PNG: ImageFormat
"""
PNG file format.
"""

TIFF: ImageFormat
"""
TIFF file format.
"""

NV12: type[NV12]
"""
Raw NV12 encoded image.

The type comes with a `size_hint` attribute, a tuple of (height, width)
which has to be specified specifying in order to set the RGB size of the image.
"""

YUY2: type[YUY2]
"""
Raw YUY2 encoded image.

YUY2 is a YUV422 encoding with bytes ordered as `yuyv`.

The type comes with a `size_hint` attribute, a tuple of (height, width)
which has to be specified specifying in order to set the RGB size of the image.
"""

def __init__(self, name: str):
self.name = name

def __str__(self) -> str:
return self.name


class NV12(ImageFormat):
"""⚠️ DEPRECATED ⚠️ NV12 format."""

name = "NV12"
size_hint: tuple[int, int]

def __init__(self, size_hint: tuple[int, int]) -> None:
"""
An NV12 encoded image.

Parameters
----------
size_hint:
A tuple of (height, width), specifying the RGB size of the image

"""
self.size_hint = size_hint


class YUY2(ImageFormat):
"""⚠️ DEPRECATED ⚠️ YUY2 format."""

name = "YUY2"
size_hint: tuple[int, int]

def __init__(self, size_hint: tuple[int, int]) -> None:
"""
An YUY2 encoded image.

YUY2 is a YUV422 encoding with bytes ordered as `yuyv`.

Parameters
----------
size_hint:
A tuple of (height, width), specifying the RGB size of the image

"""
self.size_hint = size_hint


# Assign the variants
# This allows for rust like enums, for example:
# ImageFormat.NV12(width=1920, height=1080)
# isinstance(ImageFormat.NV12, ImageFormat) == True and isinstance(ImageFormat.NV12, NV12) == True
ImageFormat.BMP = ImageFormat("BMP")
ImageFormat.GIF = ImageFormat("GIF")
ImageFormat.JPEG = ImageFormat("JPEG")
ImageFormat.PNG = ImageFormat("PNG")
ImageFormat.TIFF = ImageFormat("TIFF")
ImageFormat.NV12 = NV12
ImageFormat.YUY2 = YUY2


def ImageEncoded(
Copy link
Member Author

Choose a reason for hiding this comment

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

ImageEncoded used to be a class, but is now a function so that it can return either an Image or an EncodedImage. Maybe a Python guru has a better idea?

*,
path: str | pathlib.Path | None = None,
contents: bytes | IO[bytes] | None = None,
format: ImageFormat | None = None,
draw_order: Float32Like | None = None,
) -> Image | EncodedImage:
"""
⚠️ DEPRECATED ⚠️ - Use [`Image`][rerun.archetypes.Image] (NV12, YUYV, …) and [`EncodedImage`][rerun.archetypes.EncodedImage] (PNG, JPEG, …) instead.

A monochrome or color image encoded with a common format (PNG, JPEG, etc.).

The encoded image can be loaded from either a file using its `path` or
provided directly via `contents`.

Parameters
----------
path:
A path to a file stored on the local filesystem. Mutually
exclusive with `contents`.
contents:
The contents of the file. Can be a BufferedReader, BytesIO, or
bytes. Mutually exclusive with `path`.
format:
The format of the image file or image encoding.
If not provided, it will be inferred from the file extension if a path is specified.
Note that encodings like NV12 and YUY2 can not be inferred from the file extension.
draw_order:
An optional floating point value that specifies the 2D drawing
order. Objects with higher values are drawn on top of those with
lower values.

"""

warnings.warn(
Copy link
Member Author

Choose a reason for hiding this comment

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

This warning makes the unit tests fail. I have no idea how to fix that.

message=(
"`ImageEncoded` is deprecated. Use `Image` (for NV12 and YUY2) or `EncodedImage` (for PNG, JPEG, …) instead."
),
category=DeprecationWarning,
)

if (path is None) == (contents is None):
raise ValueError("Must provide exactly one of 'path' or 'contents'")

if format is not None:
if isinstance(format, NV12) or isinstance(format, YUY2):
buffer: IO[bytes] | None
if path is not None:
buffer = io.BytesIO(pathlib.Path(path).read_bytes())
elif isinstance(contents, bytes):
buffer = io.BytesIO(contents)
else:
assert (
# For the type-checker - we've already ensured that either `path` or `contents` must be set
contents is not None
)
buffer = contents

contentx_bytes = buffer.read()

if isinstance(format, NV12):
return Image(
bytes=contentx_bytes,
width=format.size_hint[1],
height=format.size_hint[0],
pixel_format="NV12",
draw_order=draw_order,
)
elif isinstance(format, YUY2):
return Image(
bytes=contentx_bytes,
width=format.size_hint[1],
height=format.size_hint[0],
pixel_format="YUY2",
draw_order=draw_order,
)

media_type = None
if format is not None:
if str(format) == "BMP":
media_type = "image/bmp"
elif str(format) == "GIF":
media_type = "image/gif"
elif str(format) == "JPEG":
media_type = "image/jpeg"
elif str(format) == "PNG":
media_type = "image/png"
elif str(format) == "TIFF":
media_type = "image/tiff"

if path is not None:
return EncodedImage(
path=path,
media_type=media_type,
draw_order=draw_order,
)
else:
return EncodedImage(
contents=contents,
media_type=media_type,
draw_order=draw_order,
)
99 changes: 99 additions & 0 deletions rerun_py/tests/unit/test_image_encoded.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Testing of the deprecated `ImageEncoded` helper."""

from __future__ import annotations

import io
import tempfile

import cv2
import numpy as np
import rerun as rr # pip install rerun-sdk
from PIL import Image


def test_image_encoded_png() -> None:
_, file_path = tempfile.mkstemp(suffix=".png")

image = Image.new("RGBA", (300, 200), color=(0, 0, 0, 0))
image.save(file_path)

img = rr.ImageEncoded(path=file_path)

assert img.media_type == "image/png"


def test_image_encoded_jpg() -> None:
_, file_path = tempfile.mkstemp(suffix=".jpg")

image = Image.new("RGB", (300, 200), color=(0, 0, 0))
image.save(file_path)

img = rr.ImageEncoded(path=file_path)

assert img.media_type == "image/jpeg"


def test_image_encoded_mono_jpg() -> None:
_, file_path = tempfile.mkstemp(suffix=".jpg")

image = Image.new("L", (300, 200), color=0)
image.save(file_path)

img = rr.ImageEncoded(path=file_path)

assert img.media_type == "image/jpeg"


def test_image_encoded_jpg_from_bytes() -> None:
bin = io.BytesIO()

image = Image.new("RGB", (300, 200), color=(0, 0, 0))
image.save(bin, format="jpeg")

img = rr.ImageEncoded(contents=bin)

assert img.media_type == "image/jpeg"

bin.seek(0)
img = rr.ImageEncoded(contents=bin.read())

assert img.media_type == "image/jpeg"


def test_image_encoded_mono_jpg_from_bytes() -> None:
bin = io.BytesIO()

image = Image.new("L", (300, 200), color=0)
image.save(bin, format="jpeg")

img = rr.ImageEncoded(contents=bin)

assert img.media_type == "image/jpeg"

bin.seek(0)
img = rr.ImageEncoded(contents=bin.read())

assert img.media_type == "image/jpeg"


def test_image_encoded_nv12() -> None:
def bgr2nv12(bgr: cv2.typing.MatLike) -> cv2.typing.MatLike:
yuv = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV_I420)
uv_row_cnt = yuv.shape[0] // 3
uv_plane = np.transpose(yuv[uv_row_cnt * 2 :].reshape(2, -1), [1, 0])
yuv[uv_row_cnt * 2 :] = uv_plane.reshape(uv_row_cnt, -1)
return yuv

img_bgr = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)

img = (
rr.ImageEncoded(
contents=bytes(bgr2nv12(img_bgr)),
format=rr.ImageFormat.NV12((480, 640)),
draw_order=42,
),
)

assert img.resolution == rr.Resolution2D(640, 480)
assert img.pixel_format == rr.PixelFormat.NV12
assert img.draw_order == 42
Loading