Skip to content

Commit 8e5713b

Browse files
authored
Image decoding tutorial (#1584)
1 parent d249ad5 commit 8e5713b

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __call__(self, filename):
7070
if "examples/decoding" in self.src_dir:
7171
order = [
7272
"basic_example.py",
73+
"image_decoding.py",
7374
"audio_decoding.py",
7475
"basic_cuda_example.py",
7576
"file_like.py",

docs/source/index.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Decoding
4242

4343
A simple video decoding example
4444

45+
.. grid-item-card:: :octicon:`file-code;1em`
46+
Image Decoding
47+
:link: generated_examples/decoding/image_decoding.html
48+
:link-type: url
49+
50+
How to decode images (JPEG on CPU and CUDA, PNG, WebP, and more)
51+
4552
.. grid-item-card:: :octicon:`file-code;1em`
4653
Audio Decoding
4754
:link: generated_examples/decoding/audio_decoding.html
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
Decoding images
10+
===============
11+
12+
In this example, we'll learn how to decode an image into a PyTorch tensor using
13+
:func:`~torchcodec.decoders.decode_image`. It supports JPEG, PNG, WebP, GIF,
14+
AVIF and HEIC, and automatically detects the format for you. You can also call
15+
any of the format-specific decoders directly, as they expose more fine-grained
16+
options (like CUDA decoding with :func:`~torchcodec.decoders.decode_jpeg`).:
17+
18+
- :func:`~torchcodec.decoders.decode_jpeg` for CPU and CUDA
19+
- :func:`~torchcodec.decoders.decode_png`
20+
- :func:`~torchcodec.decoders.decode_webp`
21+
- :func:`~torchcodec.decoders.decode_gif`
22+
- :func:`~torchcodec.decoders.decode_avif`
23+
- :func:`~torchcodec.decoders.decode_heic`
24+
"""
25+
26+
# %%
27+
# First, a bit of boilerplate: we'll download an image from the web and define a
28+
# plotting utility. You can ignore that part and jump right below to
29+
# :ref:`decoding_image`.
30+
31+
import torch
32+
import requests
33+
34+
35+
url = "https://raw.githubusercontent.com/meta-pytorch/torchcodec/refs/heads/main/docs/source/_static/thumbnails/grumps_6.jpg"
36+
response = requests.get(url, headers={"User-Agent": ""})
37+
if response.status_code != 200:
38+
raise RuntimeError(f"Failed to download image. {response.status_code = }.")
39+
40+
raw_image_bytes = response.content
41+
42+
43+
def plot(image: torch.Tensor):
44+
try:
45+
from torchvision.transforms.v2.functional import to_pil_image
46+
import matplotlib.pyplot as plt
47+
except ImportError:
48+
print("Cannot plot, please run `pip install torchvision matplotlib`")
49+
return
50+
51+
pil_image = to_pil_image(image)
52+
fig = plt.figure(figsize=(pil_image.width / 100, pil_image.height / 100))
53+
ax = fig.add_axes([0, 0, 1, 1])
54+
# cmap only kicks in for single-channel (grayscale) images.
55+
ax.imshow(pil_image, cmap="gray")
56+
ax.axis("off")
57+
58+
59+
# %%
60+
# .. _decoding_image:
61+
#
62+
# Decoding an image
63+
# -----------------
64+
#
65+
# :func:`~torchcodec.decoders.decode_image` accepts the raw (encoded) bytes, a
66+
# path to a local file, or a ``torch.Tensor`` of encoded bytes. The format is
67+
# detected automatically from the content, so the same call works for a JPEG, a
68+
# PNG, a WebP, etc.
69+
from torchcodec.decoders import decode_image
70+
71+
image = decode_image(raw_image_bytes)
72+
# You can also pass a path to a local file: decode_image("image.jpg")
73+
74+
print(f"{image.shape = }")
75+
print(f"{image.dtype = }")
76+
plot(image)
77+
78+
# %%
79+
# The decoded image is a :class:`torch.Tensor` of shape ``(C, H, W)`` where C is
80+
# the number of channels, H the height and W the width. By default images are
81+
# decoded as RGB (3 channels) with ``torch.uint8`` values.
82+
83+
# %%
84+
# Choosing the color mode
85+
# -----------------------
86+
#
87+
# The ``mode`` parameter controls the number and meaning of the output channels.
88+
# It can be ``"RGB"`` (the default), ``"GRAY"``, ``"RGB_ALPHA"``, and a few more.
89+
90+
gray = decode_image(raw_image_bytes, mode="GRAY")
91+
print(f"{gray.shape = }") # single channel
92+
plot(gray)
93+
94+
# %%
95+
# Controlling the output dtype
96+
# ----------------------------
97+
#
98+
# The ``output_dtype`` parameter controls the dtype of the returned tensor. It
99+
# can be ``torch.uint8`` (the default), ``torch.uint16``, or ``"auto"``.
100+
101+
image_16bit = decode_image(raw_image_bytes, output_dtype=torch.uint16)
102+
print(f"{image_16bit.dtype = }")
103+
# .max() isn't implemented for uint16, so we cast to a wider int just to print.
104+
print(f"{image_16bit.to(torch.int32).max() = }") # scaled up to the 16-bit range
105+
106+
# %%
107+
# For 8-bit formats like JPEG, WebP and GIF, ``torch.uint16`` simply scales the
108+
# 8-bit values up to the full 16-bit range (0-255 -> 0-65535). Formats that can
109+
# carry more than 8 bits per channel (PNG, AVIF, HEIC) actually **preserve** that
110+
# extra precision when you pass ``torch.uint16`` or ``"auto"``.
111+
112+
# %%
113+
# Decoding animated images
114+
# ------------------------
115+
#
116+
# GIF, WebP and AVIF can hold a *sequence* of frames (an animation). In that
117+
# case :func:`~torchcodec.decoders.decode_gif`,
118+
# :func:`~torchcodec.decoders.decode_webp,
119+
# :func:`~torchcodec.decoders.decode_avif`, and
120+
# :func:`~torchcodec.decoders.decode_heic` return an ``(N, C, H, W)`` tensor,
121+
# with one frame per animation frame, instead of the ``(C, H, W)`` you get for a
122+
# still image.
123+
124+
# %%
125+
# Decoding JPEGs on GPU
126+
# ---------------------
127+
#
128+
# :func:`~torchcodec.decoders.decode_jpeg` can decode directly on a CUDA device
129+
# by passing ``device="cuda"``. For best performance, decode a whole *batch* in
130+
# a single call by passing a list of sources: the entire batch is then decoded
131+
# in one nvJPEG call, which is much faster than decoding images one at a time.
132+
#
133+
# .. code-block:: python
134+
#
135+
# from torchcodec.decoders import decode_jpeg
136+
#
137+
# # A single image, decoded on the GPU:
138+
# image = decode_jpeg(raw_image_bytes, device="cuda")
139+
#
140+
# # A whole batch in one call (much faster than one-by-one):
141+
# images = decode_jpeg([img_0, img_1, img_2], device="cuda")
142+
143+
# sphinx_gallery_thumbnail_path = '_static/thumbnails/grumps_6.jpg'

0 commit comments

Comments
 (0)