Created
June 2, 2025 13:00
-
-
Save zachlewis/bdc55517f3c3797155ed64576c1e8c68 to your computer and use it in GitHub Desktop.
MaxCLL and MaxFALL example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import lru_cache | |
from typing import Sequence, Callable, Tuple | |
from fastprogress import progress_bar | |
import numpy as np | |
import OpenImageIO as oiio | |
@lru_cache | |
def compute_maxcll_maxfall( | |
image_sequence: Sequence[Union[str, Path, oiio.ImageBuf]], | |
convert_to_pq: Callable[[oiio.ImageBuf], oiio.ImageBuf] | None = None, | |
permit_outliers: bool = False, | |
dry_run: bool = False, | |
) -> Tuple[float, float]: | |
""" | |
Compute MaxCLL and MaxFALL using an outlier rejection method. | |
Based on the paper "A New Method for Measuring Maximum Content Light Level | |
(MaxCLL) and Maximum Frame Average Light Level (MaxFALL) for HDR Video" by | |
Michael D. Smith and Michael Zink, published in 2021. | |
See https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9508136 | |
Parameters | |
---------- | |
image_sequence: | |
List of file paths or OIIO ImageBufs. | |
convert_to_pq: | |
Arbitrary callable for pre-transforming each `ImageBuf`, i.e. when analyzing | |
non-PQ-encoded image sequences. | |
permit_outliers: | |
Use an outlier rejection method to compute MaxCLL and MaxFALL. | |
dry_run: | |
If True, only process the first two images in the sequence for testing. | |
Returns | |
------- | |
(MaxCLL, MaxFALL) in cd/m^2 for the entire sequence (float). | |
""" | |
per_frame_max,per_frame_99_99,per_frame_avg = [],[],[] | |
if dry_run: | |
image_sequence = image_sequence[0:2] | |
for img in progress_bar(image_sequence): | |
buf = oiio.ImageBuf(str(img)) if isinstance(img, (str, Path)) else img | |
if convert_to_pq: | |
buf = convert_to_pq(buf) | |
buf = oiio.ImageBufAlgo.ocionamedtransform( | |
buf, "ST-2084 - Curve", colorconfig="ocio://studio-config-latest") | |
stats = oiio.ImageBufAlgo.computePixelStats(buf) | |
if not permit_outliers: | |
# 99.99th percentile of all pixel values | |
per_frame_99_99.append(np.percentile(buf.get_pixels(oiio.FLOAT), 99.99)) | |
else: | |
per_frame_max.append(np.max(stats.max)) | |
per_frame_avg.append(np.max(stats.avg)) | |
if not permit_outliers: | |
# 99.5th percentile of per-frame 99.99th percentiles | |
maxcll = np.percentile(per_frame_99_99, 99.5) | |
# 99.75th percentile of per-frame averages | |
maxfall = np.percentile(per_frame_avg, 99.75) | |
else: | |
maxcll = np.max(per_frame_max) | |
maxfall = np.max(per_frame_avg) | |
return float(maxcll * 100), float(maxfall * 100) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
(note: I haven't actually tested this in production; but it produces seemingly valid values for the datasets I've experimented with, which certainly include a handful of NaNs and infs here and there).