Skip to content
Open
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
18 changes: 18 additions & 0 deletions examples/pi-heif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pi-heif Examples

Each sub-directory contains a self-contained example. The order in
which the examples are to appear is specified in `order.json` (an
array of directory names in the expected order).

In each example directory you'll find:

* `config.toml` - must conform to the specification outlined here:
https://docs.pyscript.net/latest/user-guide/configuration/ This is
parsed and ultimately turned into a JSON representation as part of
the package's API object.
* `setup.py` - Python code for contextual and environmental setup,
NOT SEEN BY THE END USER, but is run before the `code.py` code is
evaluated. Allows us to create useful (IPython) shims, avoid
repeating boilerplate and whatnot.
* `code.py` - the actual code added to the editor which forms the
practical example of using the package.
87 changes: 87 additions & 0 deletions examples/pi-heif/heif_hdr_and_thumbnails/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# ---------------------------------------------------------------------
# Two pi-heif features that are easy to miss:
# 1. `convert_hdr_to_8bit=False` keeps 10/12-bit HDR data intact, so
# you can hand off 16-bit arrays to libraries like OpenCV.
# 2. `bgr_mode=True` returns BGR-ordered channels, which is what
# OpenCV expects natively.
# We'll demonstrate the option flags and visualise the difference
# between an 8-bit and a simulated 16-bit decode.
# ---------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
from pi_heif import register_heif_opener

register_heif_opener()


heading("HDR decoding options")
note(
"Calling <code>open_heif</code> with "
"<code>convert_hdr_to_8bit=False</code> preserves the original "
"bit depth (10 or 12 bits) as a uint16 array. Combined with "
"<code>bgr_mode=True</code>, this is the recommended path for "
"feeding HEIF directly into OpenCV's <code>cv2.imwrite</code>."
)

# A code snippet readers can adapt verbatim.
example_call = """heif_file = pi_heif.open_heif(
"image.heic",
convert_hdr_to_8bit=False, # keep 10/12-bit HDR data
bgr_mode=True, # OpenCV-friendly channel order
)
np_array = np.asarray(heif_file) # uint16 if HDR, uint8 otherwise
print(heif_file.mode, heif_file.bit_depth, np_array.dtype)"""
display(HTML(f"<pre><code>{example_call}</code></pre>"), append=True)

# Simulate the visual difference between an 8-bit decode (clipped
# highlights) and a 16-bit HDR decode (full dynamic range).
height, width = 160, 320
xx = np.linspace(0, 1, width)[None, :].repeat(height, axis=0)

# Underlying scene with values that exceed the 8-bit range.
hdr_scene = (xx ** 0.5) * 65535 # smooth gradient up to 16-bit max

eight_bit = np.clip(hdr_scene / 256, 0, 255).astype(np.uint8)
sixteen_bit = hdr_scene.astype(np.uint16)

# Tone-map the 16-bit version for display, the way an HDR pipeline
# would: a simple gamma curve preserves shadow detail.
tone_mapped = (
(sixteen_bit / 65535) ** (1 / 2.2) * 255
).astype(np.uint8)

fig, axes = plt.subplots(1, 2, figsize=(10, 3))
axes[0].imshow(np.dstack([eight_bit] * 3))
axes[0].set_title("8-bit decode (convert_hdr_to_8bit=True)")
axes[0].axis("off")

axes[1].imshow(np.dstack([tone_mapped] * 3))
axes[1].set_title("16-bit HDR decode, tone-mapped")
axes[1].axis("off")
fig.tight_layout()
display(fig, append=True)

heading("Thumbnails embedded in HEIF files")
note(
"Many HEIF files carry one or more pre-baked thumbnails. "
"pi-heif exposes them via <code>heif_file.thumbnails</code> "
"(list of sizes) and <code>heif_file.get_thumbnail(size)</code>. "
"This is much faster than decoding the full image when you only "
"need a preview."
)

thumbnail_snippet = """heif_file = pi_heif.open_heif("photo.heic")
for size in heif_file.thumbnails:
thumb = heif_file.get_thumbnail(size)
Image.frombytes(thumb.mode, thumb.size, thumb.data).save(
f"thumb_{size}.png"
)"""
display(HTML(f"<pre><code>{thumbnail_snippet}</code></pre>"), append=True)

note(
"From here, common next steps are: applying Pillow filters to the "
"decoded image, saving as PNG/JPEG via Pillow, or piping the "
"NumPy array into OpenCV or scikit-image. See "
"<a href='https://pillow-heif.readthedocs.io/'>"
"pillow-heif.readthedocs.io</a> for the full reference."
)
1 change: 1 addition & 0 deletions examples/pi-heif/heif_hdr_and_thumbnails/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pi-heif", "Pillow", "numpy", "matplotlib"]
20 changes: 20 additions & 0 deletions examples/pi-heif/heif_hdr_and_thumbnails/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Setup for the third cell: same names as before, no IPython shim."""
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(
*args, **kwargs, target=__pyscript_display_target__,
)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

54 changes: 54 additions & 0 deletions examples/pi-heif/heif_pillow_plugin/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Reading HEIF (High Efficiency Image Format) images with pi-heif.

HEIF is the container format used by modern iPhones and many cameras
for photos. pi-heif provides a decoder so you can open these files
just like JPEG or PNG.

We don't have a real .heic file lying around in the browser, so we'll
build a plain Pillow image as a stand-in and walk through the API
you'd use on a real photo.

Docs: https://pillow-heif.readthedocs.io/
"""
from IPython.core.display import display, HTML
# Example-specific imports below.
import io
from PIL import Image, ImageDraw
import pi_heif
from pi_heif import register_heif_opener


# Register pi-heif as a Pillow plugin. After this call, Image.open
# transparently understands .heic and .heif files.
register_heif_opener()

heading("Opening a HEIF image with Pillow")
note(
"Once <code>register_heif_opener()</code> has been called, "
"Pillow's <code>Image.open</code> handles HEIF files exactly like "
"any other format. On a real photo you'd simply write "
"<code>Image.open('photo.heic')</code>; here we build a Pillow "
"image directly as a stand-in for the decoded result."
)

# Stand-in for a decoded HEIF photo. In real code this would be the
# return value of Image.open("photo.heic").
demo = Image.new("RGB", (240, 160), "lightsteelblue")
draw = ImageDraw.Draw(demo)
draw.rectangle((20, 20, 220, 140), outline="navy", width=3)
draw.text((40, 70), "HEIF demo image", fill="navy")

note(f"Pillow image mode: <code>{demo.mode}</code>, size: {demo.size}")
display(demo, append=True)

# Ask Pillow which extensions now map to the HEIF/HEIC formats. This
# is the most reliable way to answer "will Pillow open my file?",
# because it reflects what register_heif_opener() actually wired up.
# (Avoid reaching into pi_heif.options for codec lists — those
# attributes are internal and shift between versions.)
heif_exts = sorted(
ext for ext, fmt in Image.registered_extensions().items()
if fmt in ("HEIF", "HEIC")
)
note(f"File extensions Pillow now routes to pi-heif: <code>{heif_exts}</code>")
1 change: 1 addition & 0 deletions examples/pi-heif/heif_pillow_plugin/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pi-heif", "Pillow"]
41 changes: 41 additions & 0 deletions examples/pi-heif/heif_pillow_plugin/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Shim IPython's display API onto PyScript so example code written in a
Jupyter/IPython idiom runs unmodified in the browser.
"""

import sys
import types
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(
*args, **kwargs, target=__pyscript_display_target__,
)


ipython = types.ModuleType("IPython")
core = types.ModuleType("IPython.core")
core_display = types.ModuleType("IPython.core.display")
core_display.display = display
core_display.HTML = HTML
ipython.core = core
core.display = core_display
ipython.get_ipython = lambda: None
ipython.display = core_display
sys.modules["IPython"] = ipython
sys.modules["IPython.core"] = core
sys.modules["IPython.core.display"] = core_display
sys.modules["IPython.display"] = core_display


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

81 changes: 81 additions & 0 deletions examples/pi-heif/heif_to_numpy/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# ---------------------------------------------------------------------
# pi-heif exposes the buffer protocol, so a decoded HEIF image can be
# turned directly into a NumPy array. This is the idiomatic way to feed
# HEIF photos into OpenCV, scikit-image, or any other array-based
# pipeline.
# ---------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
import pi_heif
from pi_heif import register_heif_opener

register_heif_opener()

heading("From HeifFile to NumPy array")
note(
"<code>pi_heif.open_heif(...)</code> returns a <code>HeifFile</code> "
"whose first image is exposed via the buffer protocol. Wrapping it "
"in <code>np.asarray</code> gives you a (height, width, channels) "
"array with no extra copy. We'll simulate this by creating an array "
"directly and inspecting it as if it had come from HEIF decoding."
)

# Synthetic 200x300 RGB image: a horizontal gradient with a circle.
height, width = 200, 300
yy, xx = np.mgrid[0:height, 0:width]
red = (xx * 255 / width).astype(np.uint8)
green = (yy * 255 / height).astype(np.uint8)
blue = np.full_like(red, 80)

# Add a brighter disc in the middle to give the eye something to track.
cy, cx = height // 2, width // 2
disc = (yy - cy) ** 2 + (xx - cx) ** 2 < 50 ** 2
red[disc] = 255
green[disc] = 240
blue[disc] = 200

decoded = np.dstack([red, green, blue])

note(
f"Array shape: <code>{decoded.shape}</code>, "
f"dtype: <code>{decoded.dtype}</code>. "
"This is exactly the layout you would get from "
"<code>np.asarray(pi_heif.open_heif('photo.heic'))</code>."
)

# Show channel statistics, the kind of summary you'd compute right
# after decoding.
channel_names = ["R", "G", "B"]
fig, axes = plt.subplots(1, 2, figsize=(10, 4))

axes[0].imshow(decoded)
axes[0].set_title("Decoded image")
axes[0].axis("off")

for i, name in enumerate(channel_names):
axes[1].hist(
decoded[..., i].ravel(),
bins=32,
alpha=0.5,
label=name,
color=name.lower().replace("r", "red").replace("g", "green").replace("b", "blue"),
)
axes[1].set_title("Per-channel histogram")
axes[1].set_xlabel("Pixel value")
axes[1].set_ylabel("Count")
axes[1].legend()
fig.tight_layout()
display(fig, append=True)

# The HeifFile object also carries useful metadata. Show the kinds of
# attributes you can read from it.
note("Typical attributes available on a <code>HeifFile</code>:")
display(HTML(
"<ul>"
"<li><code>heif_file.size</code> &mdash; (width, height) tuple</li>"
"<li><code>heif_file.mode</code> &mdash; e.g. 'RGB', 'RGBA', 'BGR;16'</li>"
"<li><code>heif_file.has_alpha</code> &mdash; bool</li>"
"<li><code>heif_file.bit_depth</code> &mdash; 8, 10, or 12</li>"
"<li><code>heif_file.info['exif']</code> &mdash; raw EXIF bytes if present</li>"
"</ul>"
), append=True)
1 change: 1 addition & 0 deletions examples/pi-heif/heif_to_numpy/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pi-heif", "Pillow", "numpy", "matplotlib"]
21 changes: 21 additions & 0 deletions examples/pi-heif/heif_to_numpy/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Setup for the second cell: same names as cell 1, no IPython shim."""
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(
*args, **kwargs, target=__pyscript_display_target__,
)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)


5 changes: 5 additions & 0 deletions examples/pi-heif/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"heif_pillow_plugin",
"heif_to_numpy",
"heif_hdr_and_thumbnails"
]