diff --git a/examples/pi-heif/README.md b/examples/pi-heif/README.md new file mode 100644 index 0000000..9e048b0 --- /dev/null +++ b/examples/pi-heif/README.md @@ -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. diff --git a/examples/pi-heif/heif_hdr_and_thumbnails/code.py b/examples/pi-heif/heif_hdr_and_thumbnails/code.py new file mode 100644 index 0000000..1d9e268 --- /dev/null +++ b/examples/pi-heif/heif_hdr_and_thumbnails/code.py @@ -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 open_heif with " + "convert_hdr_to_8bit=False preserves the original " + "bit depth (10 or 12 bits) as a uint16 array. Combined with " + "bgr_mode=True, this is the recommended path for " + "feeding HEIF directly into OpenCV's cv2.imwrite." +) + +# 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"
{example_call}
"), 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 heif_file.thumbnails " + "(list of sizes) and heif_file.get_thumbnail(size). " + "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"
{thumbnail_snippet}
"), 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 " + "" + "pillow-heif.readthedocs.io for the full reference." +) diff --git a/examples/pi-heif/heif_hdr_and_thumbnails/config.toml b/examples/pi-heif/heif_hdr_and_thumbnails/config.toml new file mode 100644 index 0000000..ae283ae --- /dev/null +++ b/examples/pi-heif/heif_hdr_and_thumbnails/config.toml @@ -0,0 +1 @@ +packages = ["pi-heif", "Pillow", "numpy", "matplotlib"] diff --git a/examples/pi-heif/heif_hdr_and_thumbnails/setup.py b/examples/pi-heif/heif_hdr_and_thumbnails/setup.py new file mode 100644 index 0000000..38a3e24 --- /dev/null +++ b/examples/pi-heif/heif_hdr_and_thumbnails/setup.py @@ -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"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + diff --git a/examples/pi-heif/heif_pillow_plugin/code.py b/examples/pi-heif/heif_pillow_plugin/code.py new file mode 100644 index 0000000..240b086 --- /dev/null +++ b/examples/pi-heif/heif_pillow_plugin/code.py @@ -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 register_heif_opener() has been called, " + "Pillow's Image.open handles HEIF files exactly like " + "any other format. On a real photo you'd simply write " + "Image.open('photo.heic'); 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: {demo.mode}, 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: {heif_exts}") \ No newline at end of file diff --git a/examples/pi-heif/heif_pillow_plugin/config.toml b/examples/pi-heif/heif_pillow_plugin/config.toml new file mode 100644 index 0000000..4a525bc --- /dev/null +++ b/examples/pi-heif/heif_pillow_plugin/config.toml @@ -0,0 +1 @@ +packages = ["pi-heif", "Pillow"] diff --git a/examples/pi-heif/heif_pillow_plugin/setup.py b/examples/pi-heif/heif_pillow_plugin/setup.py new file mode 100644 index 0000000..0cc7a6f --- /dev/null +++ b/examples/pi-heif/heif_pillow_plugin/setup.py @@ -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"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + diff --git a/examples/pi-heif/heif_to_numpy/code.py b/examples/pi-heif/heif_to_numpy/code.py new file mode 100644 index 0000000..6fad9ee --- /dev/null +++ b/examples/pi-heif/heif_to_numpy/code.py @@ -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( + "pi_heif.open_heif(...) returns a HeifFile " + "whose first image is exposed via the buffer protocol. Wrapping it " + "in np.asarray 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: {decoded.shape}, " + f"dtype: {decoded.dtype}. " + "This is exactly the layout you would get from " + "np.asarray(pi_heif.open_heif('photo.heic'))." +) + +# 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 HeifFile:") +display(HTML( + "" +), append=True) diff --git a/examples/pi-heif/heif_to_numpy/config.toml b/examples/pi-heif/heif_to_numpy/config.toml new file mode 100644 index 0000000..ae283ae --- /dev/null +++ b/examples/pi-heif/heif_to_numpy/config.toml @@ -0,0 +1 @@ +packages = ["pi-heif", "Pillow", "numpy", "matplotlib"] diff --git a/examples/pi-heif/heif_to_numpy/setup.py b/examples/pi-heif/heif_to_numpy/setup.py new file mode 100644 index 0000000..b1b0ccf --- /dev/null +++ b/examples/pi-heif/heif_to_numpy/setup.py @@ -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"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + diff --git a/examples/pi-heif/order.json b/examples/pi-heif/order.json new file mode 100644 index 0000000..f624463 --- /dev/null +++ b/examples/pi-heif/order.json @@ -0,0 +1,5 @@ +[ + "heif_pillow_plugin", + "heif_to_numpy", + "heif_hdr_and_thumbnails" +]