Skip to content

Commit 85ca2d2

Browse files
committed
Add tutorial for Image encoders
1 parent 27cf2fd commit 85ca2d2

4 files changed

Lines changed: 145 additions & 1 deletion

File tree

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def __call__(self, filename):
8585
else:
8686
assert "examples/encoding" in self.src_dir
8787
order = [
88+
"image_encoding.py",
8889
"audio_encoding.py",
8990
"video_encoding.py",
9091
"multi_stream_encoding.py",

docs/source/index.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,14 @@ Encoding
116116
:link: generated_examples/encoding/multi_stream_encoding.html
117117
:link-type: url
118118

119-
How encode audio and video streams
119+
How encode audio and video streams (CPU and CUDA)
120+
121+
.. grid-item-card:: :octicon:`file-code;1em`
122+
Image Encoding
123+
:link: generated_examples/encoding/image_encoding.html
124+
:link-type: url
125+
126+
How to encode image tensors into JPEG (CPU and CUDA) or PNG
120127

121128
.. grid-item-card:: :octicon:`file-code;1em`
122129
Video Encoding
@@ -132,6 +139,7 @@ Encoding
132139

133140
How to encode audio samples into an audio file
134141

142+
135143
.. toctree::
136144
:maxdepth: 1
137145
:hidden:
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""
8+
===============
9+
Encoding images
10+
===============
11+
12+
In this example, we'll learn how to encode an image tensor to JPEG or PNG using
13+
the :class:`~torchcodec.encoders.JpegEncoder` and
14+
:class:`~torchcodec.encoders.PngEncoder` classes.
15+
"""
16+
17+
# %%
18+
# First, a bit of boilerplate: we'll download an image from the web and define a
19+
# plotting utility. You can ignore that part and jump right below to
20+
# :ref:`encoding_image`.
21+
22+
import requests
23+
import torch
24+
25+
from torchcodec.decoders import decode_image
26+
27+
url = "https://raw.githubusercontent.com/meta-pytorch/torchcodec/refs/heads/main/docs/source/_static/thumbnails/cat_encoding.jpeg"
28+
response = requests.get(url, headers={"User-Agent": ""})
29+
if response.status_code != 200:
30+
raise RuntimeError(f"Failed to download image. {response.status_code = }.")
31+
32+
# The image to encode, a CHW uint8 tensor. It could come from anywhere (e.g. a
33+
# model output); here we just decode one.
34+
image = decode_image(response.content)
35+
36+
37+
def plot(image: torch.Tensor):
38+
try:
39+
import matplotlib.pyplot as plt
40+
from torchvision.transforms.v2.functional import to_pil_image
41+
except ImportError:
42+
print("Cannot plot, please run `pip install torchvision matplotlib`")
43+
return
44+
45+
pil_image = to_pil_image(image)
46+
fig = plt.figure(figsize=(pil_image.width / 100, pil_image.height / 100))
47+
ax = fig.add_axes([0, 0, 1, 1])
48+
ax.imshow(pil_image)
49+
ax.axis("off")
50+
51+
52+
# %%
53+
# .. _encoding_image:
54+
#
55+
# Encoding an image
56+
# -----------------
57+
#
58+
# Encoders expect a 3D uint8 tensor in CHW layout (1 or 3 channels), which is
59+
# exactly what our image is:
60+
print(f"{image.shape = }, {image.dtype = }")
61+
plot(image)
62+
63+
# %%
64+
# We instantiate a :class:`~torchcodec.encoders.JpegEncoder` with the image, and
65+
# encode it. Three destinations are supported: a file with
66+
# :meth:`~torchcodec.encoders.JpegEncoder.to_file`, a file-like object with
67+
# :meth:`~torchcodec.encoders.JpegEncoder.to_file_like`, or a 1D uint8 tensor of
68+
# raw bytes with :meth:`~torchcodec.encoders.JpegEncoder.to_tensor`.
69+
import io
70+
71+
from torchcodec.encoders import JpegEncoder
72+
73+
encoder = JpegEncoder(image)
74+
75+
encoder.to_file("image.jpg") # to a file
76+
encoder.to_file_like(io.BytesIO()) # to a file-like object
77+
encoded = encoder.to_tensor() # to a tensor
78+
79+
print(f"{encoded.shape = }, {encoded.dtype = }")
80+
81+
# %%
82+
# That's it! We can decode the encoded bytes back to make sure everything worked:
83+
from torchcodec.decoders import decode_jpeg
84+
85+
decoded = decode_jpeg(encoded)
86+
print(f"{decoded.shape = }")
87+
plot(decoded)
88+
89+
# %%
90+
# :class:`~torchcodec.encoders.PngEncoder` works exactly the same way, and PNG is
91+
# lossless (unlike JPEG):
92+
from torchcodec.encoders import PngEncoder
93+
94+
encoded = PngEncoder(image).to_tensor()
95+
print(f"{encoded.shape = }")
96+
97+
# %%
98+
# Both encoders support encoding options: ``JpegEncoder`` takes a ``quality``
99+
# (1-100), and ``PngEncoder`` takes a ``compression_level`` (0-9). For example, a
100+
# lower JPEG quality yields a smaller output:
101+
small = JpegEncoder(image).to_tensor(quality=10)
102+
large = JpegEncoder(image).to_tensor(quality=95)
103+
print(f"{small.numel() = }, {large.numel() = }")
104+
105+
# %%
106+
# Encoding JPEGs on GPU
107+
# ---------------------
108+
#
109+
# ``JpegEncoder`` can encode directly on a CUDA device with nvJPEG: just pass it
110+
# an image that already lives on the GPU, and the encoding happens there. Only
111+
# 3-channel RGB images are supported on CUDA. With :meth:`to_tensor
112+
# <torchcodec.encoders.JpegEncoder.to_tensor>`, the encoded bytes stay on the GPU
113+
# (call ``.cpu()`` to bring them back to the host).
114+
#
115+
# .. code-block:: python
116+
#
117+
# from torchcodec.encoders import JpegEncoder
118+
#
119+
# encoded = JpegEncoder(image.cuda()).to_tensor() # encoded bytes on the GPU
120+
# # you can still use to_file and to_file_like, but the encoded bytes will
121+
# # be copied back to the CPU first.
122+
#
123+
# PNG encoding is CPU-only.
124+
125+
# %%
126+
# Check the docstrings of the encoding methods to learn about the different
127+
# encoding options.
128+
129+
# sphinx_gallery_thumbnail_path = '_static/thumbnails/cat_encoding.jpeg'

src/torchcodec/encoders/_image_encoders.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def _encode_to_tensor_through_bytesio(img, param, to_file_like) -> Tensor:
3434
class JpegEncoder:
3535
"""Encoder for JPEG images.
3636
37+
For a tutorial, see:
38+
:ref:`sphx_glr_generated_examples_encoding_image_encoding.py`.
39+
3740
Example:
3841
3942
.. code-block:: python
@@ -114,6 +117,9 @@ def _validate_quality(quality: int) -> None:
114117
class PngEncoder:
115118
"""Encoder for PNG images.
116119
120+
For a tutorial, see:
121+
:ref:`sphx_glr_generated_examples_encoding_image_encoding.py`.
122+
117123
Example:
118124
119125
.. code-block:: python

0 commit comments

Comments
 (0)