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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ jobs:
container: aswf/ci-oiio:2025.5
cxx_std: 17
build_type: Debug
ctest_test_timeout: "240"
ctest_test_timeout: "300"
python_ver: "3.11"
simd: "avx2,f16c"
fmt_ver: 11.2.0
Expand Down
12 changes: 12 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1
### ⛰️ New features and public API changes:
* *New image file format support:*
* *oiiotool new features and major improvements*:
- `oiiotool --flipdiff` computes the FLIP perceptual difference between two
images, prints statistics, and leaves the error map on the image stack for
further processing or saving. Options: `hdr=1` for HDR-FLIP, `colormap=NAME`
to apply a false-color map (e.g. "magma"), `ppd=N` to override pixels-per-
degree, `tonemapper=NAME` for HDR tonemapper ("aces", "reinhard", "hable").
* *Command line utilities*:
- *iv*: Flip, rotate and save image [#5003](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5003) (by Valery Angelique) (3.2.0.0, 3.1.11.0)
* *ImageBuf/ImageBufAlgo*:
- `ImageBufAlgo::FLIP()` computes the FLIP (eLearning perceptual Image
difference Predictor) metric between two LDR or HDR images. The result is
a single-channel float image with per-pixel FLIP error in [0,1]. A
`FLIPResults` struct returns mean error, max error, and location. The
optional `colormap` kwarg applies a false-color map to the result.
`FLIP_ppd()` helper computes pixels-per-degree for a given display setup.
Python bindings and `oiiotool --flipdiff` are also provided.
- *ImageBuf*: `IB::localpixels_as_[writable_]byte_image_span` [#5011](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5011) (3.2.0.0, 3.1.10.0)
* *ImageCache/TextureSystem*:
- *api/TS*: `IBA::make_texture()` now honors "maketx:threads" hint [#5014](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5014) (3.2.0.0, 3.1.10.0)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ MY_CMAKE_FLAGS += -DTEX_BATCH_SIZE:STRING="${TEX_BATCH_SIZE}"
endif

ifneq (${TEST},)
TEST_FLAGS += -R ${TEST}
TEST_FLAGS += -R '${TEST}'
endif

ifneq (${USE_CCACHE},)
Expand Down
1 change: 1 addition & 0 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ macro (oiio_add_all_tests)
oiiotool-text
oiiotool-xform
diff
flip
dither dup-channels
jpeg jpeg-corrupt jpeg-metadata
maketx oiiotool-maketx
Expand Down
60 changes: 60 additions & 0 deletions src/doc/imagebufalgo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2190,6 +2190,66 @@ Image comparison and statistics
..


|

.. _sec-iba-flip:

FLIP perceptual difference
^^^^^^^^^^^^^^^^^^^^^^^^^^

.. doxygengroup:: FLIP_diff
:content-only:
..

Examples:

.. tabs::

.. code-tab:: c++

ImageBuf ref ("ref.exr");
ImageBuf test ("test.exr");

// Basic use: returns 1-channel float error map in [0,1].
ImageBuf flipmap = ImageBufAlgo::FLIP_diff(ref, test);
OIIO::print("Mean FLIP error: {}\n",
flipmap.spec().get_float_attribute("FLIP:meanerror"));
OIIO::print("Max FLIP error: {} at ({}, {})\n",
flipmap.spec().get_float_attribute("FLIP:maxerror"),
flipmap.spec().get_int_attribute("FLIP:maxx"),
flipmap.spec().get_int_attribute("FLIP:maxy"));

// LDR mode (display-referred images only):
ImageBufAlgo::FLIP_diff(flipmap, ref, test, { {"hdr", 0} });

// For a false-color visualization, pass the result to color_map():
ImageBuf colored = ImageBufAlgo::color_map(flipmap, 0, "magma");

.. code-tab:: py

ref = ImageBuf("ref.exr")
test = ImageBuf("test.exr")

# Basic use: returns 1-channel float error map in [0,1].
flipmap = ImageBufAlgo.FLIP_diff(ref, test)
print("Mean FLIP error:", flipmap.spec().get_float_attribute("FLIP:meanerror"))
print("Max FLIP error:", flipmap.spec().get_float_attribute("FLIP:maxerror"),
"at", flipmap.spec().get_int_attribute("FLIP:maxx"),
flipmap.spec().get_int_attribute("FLIP:maxy"))

# For a false-color visualization, pass the result to color_map():
colored = ImageBufAlgo.color_map(flipmap, 0, "magma")

# LDR mode:
flipmap = ImageBufAlgo.FLIP_diff(ref, test, hdr=0)

.. code-tab:: bash oiiotool

# Default HDR mode, outputting per-pixel error map
oiiotool ref.exr test.exr --flipdiff -o errormap.exr
# Output the false color visualization)
oiiotool ref.exr test.exr --flipdiff:colormap=magma -o errorvis.exr

|

.. doxygenfunction:: isConstantColor
Expand Down
88 changes: 86 additions & 2 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,10 @@ Writing images
221 > 1,1,1
65315 within range


:program:`oiiotool` commands that compare images
================================================

.. option:: --diff
--fail <A> --failpercent <B> --hardfail <C>
--warn <A> --warnpercent <B> --hardwarn <C>
Expand All @@ -1925,10 +1929,90 @@ Writing images
.. option:: --pdiff

This command computes the difference of the current image and the next
image on the stack using a perceptual metric, and prints whether or not
they match according to that metric. This command does not alter the
image on the stack using the Yee perceptual metric, and prints whether or
not they match according to that metric. This command does not alter the
image stack.

.. option:: --flipdiff

Compute the FLIP perceptual difference of the top two images on the stack
(removing them), optionally print error metrics, and leave the per-pixel
error map on the stack.

Limitations: Currently, this only operates on the first subimage,
and the first three (RGB) channels, and does not work on volumetric
or "deep" images.

Optional appended modifiers include:

`:hdr=` *int* (default: 1)
If nonzero, computes the HDR FLIP comparison. If zero, computes
the LDR FLIP comparision. The default is 1, for HDR mode.
`:maxluminance=` *float* (default: 2.0)
The top of the expected luminance range, used to compute exposure
settings. If set to 0.0, the "startExposure" and "stopExposure"
will be used instead. The default is 2.0, which should be adequate
for most production scenarios.
`:medianluminance=` *float* (default: 0.18)
The assumed median luminance (used if "maxluminance" is not 0, so
we are using these estimates instead of measuring from the image).
The default 0.18 assumes that "middle grey" is a good guess for
a typical median luminance of the image.
`:ppd=` *float* (default:67.02)
Specifies the horizontal pixels per degree of viewing. The default
value of 67.02 is computed as the value for a 3840 pixel (2xHD) image
filling a display that is 0.7m wide, 0.7m in front of the viewer.
`:tonemapper=` *name* (default: "aces")
Specifies the HDR tonemapper: one of "aces" (default), "reinhard", or
"hable".
`:startExposure=` *float* `:stopExposure=` *float*
If supplied, and if "maxluminance" is set to 0, specify start and stop
exposures for the HDR FLIP method. If not supplied, they will be
automatically computed from the contents of the image.
`:numExposures=` *int* (default: 0)
The number of exposures for HDR FLIP computation (default: 0, which
means to automatically compute it).
`:colormap=` *name*
If absent or empty, the output error image will be a single channel
grey value. If present and the name of a color map (the same set
accepted by the oiiotool `--colormap` action), the output error
image will be a 3-channel RGB false-color visualization of the
perceptual error for each pixel.
`:print=` *int* (default: 1)
If nonzero, will print information such as the average and maximum
error of the pixels. The default is 1, meaning that the report will
print to stdout. Set to 0 to suppress the printing.
`:fail=` *float*
If set, a PASS/FAIL message will be printed to the console, with
"failing" meaning that any pixel's error metric exceeded the threshold
specified as the value for this parameter. In the case of a failure,
the oiiotool run itself will have a shell error code.

The command also sets metadata on the result image based on the FLIP
computation:

`"FLIP:meanerror"`
Mean of error map in [0,1].
`"FLIP:maxerror"`
Maximum perceptual pixel error.
`"FLIP:maxx"`
x coordinate of the pixel with the highest error.
`"FLIP:maxy"`
y coordinate of the pixel with the highest error.
`"FLIP:startExposure"`, `"FLIP:stopExposure"`
(HDR mode only) The start and stop exposure stops used.
`"FLIP:numExposures"`
(HDR mode only) Number of exposure steps used.

Examples::

# Default HDR mode, with false-color visualization
oiiotool reference.exr test.exr --flipdiff:colormap=magma -o error.exr

# LDR mode (for display-referred images), 1-channel error map
oiiotool reference.jpg test.jpg --flipdiff:hdr=0 -o error.exr

This command was added in OIIO 3.2.


:program:`oiiotool` commands that change the current image metadata
Expand Down
92 changes: 92 additions & 0 deletions src/doc/pythonbindings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3287,6 +3287,98 @@ Image comparison and statistics



.. py:method:: ImageBuf ImageBufAlgo.FLIP_diff (ref, test, hdr=1, maxluminance=2.0, medianluminance=0.18, ppd=0.0, tonemapper="aces", roi=ROI.All, nthreads=0)
bool ImageBufAlgo.FLIP_diff (dst, ref, test, hdr=1, maxluminance=2.0, medianluminance=0.18, ppd=0.0, tonemapper="aces", roi=ROI.All, nthreads=0)

WARNING: This is EXPERIMENTAL and may change at any time. Do not rely
on it prior to the release of OIIO 3.2.

Compute the `FLIP <https://research.nvidia.com/publication/2020-07_flip-difference-evaluator-alternating-images>`_
perceptual difference between images `ref` and `test`, returning a
single-channel float error map whose pixel values lie in [0,1]. Higher
values indicate larger perceived differences.

If the image's ``oiio:ColorSpace`` metadata does not clearly define a
color space, it will be assumed to be ``lin_rec709_scene`` before
processing. Three channels starting at ``roi.chbegin`` are used.

Summary statistics are stored as metadata attributes on the result
``ImageBuf``:

.. code-block:: python

errmap = ImageBufAlgo.FLIP_diff (ref_image, test_image)
errmap.spec().get_float_attribute("FLIP:meanerror") # mean perceptual error
errmap.spec().get_float_attribute("FLIP:maxerror") # maximum per-pixel error
errmap.spec().get_int_attribute("FLIP:maxx") # x coordinate of max-error pixel
errmap.spec().get_int_attribute("FLIP:maxy") # y coordinate of max-error pixel
errmap.spec().get_float_attribute("FLIP:startExposure") # HDR: first exposure stop used
errmap.spec().get_float_attribute("FLIP:stopExposure") # HDR: last exposure stop used
errmap.spec().get_int_attribute("FLIP:numExposures") # HDR: number of exposure steps used

Options:

* `hdr` (int, default 1) — set to 0 to force use of LDR-FLIP mode (should
only be used for images in a LDR display-referred color space).
* `maxluminance` (float, default 2.0) — estimated maximum luminance
* `medianluminance` (float, default 0.18) — estimated median luminance
* `ppd` (float, default 67.02) — pixels per degree of visual angle
* `tonemapper` (str, default ``"aces"``) — HDR tonemapper:
``"aces"``, ``"reinhard"``, or ``"hable"``
* `startExposure`, `stopExposure` (float) - The start and end exposures
for HDR FLIP. If not supplied, they will be computed automatically based
on the `maxluminance`, which if it is 0, will be computed based on the
value range in the reference image.
* `numExposures` (int) - The number of exposures used for HDR FLIP. If
not supplied, it will be computed automatically.

Tip: For a false-color visualization pass the result to
:py:meth:`ImageBufAlgo.color_map`.

See also :py:meth:`ImageBufAlgo.FLIP_ppd` for computing `ppd` from
display geometry.

This was added in OpenImageIO 3.2.

Example:

.. code-block:: python

ref = ImageBuf("ref.exr")
test = ImageBuf("test.exr")

# Basic use: 1-channel float error map in [0,1].
errmap = ImageBufAlgo.FLIP_diff(ref, test)
print("Mean FLIP error:", errmap.spec().get_float_attribute("FLIP:meanerror"))
print("Max FLIP error:", errmap.spec().get_float_attribute("FLIP:maxerror"),
"at", errmap.spec().get_int_attribute("FLIP:maxx"),
errmap.spec().get_int_attribute("FLIP:maxy"))

# LDR mode (display-referred images only):
errmap = ImageBufAlgo.FLIP_diff(ref, test, hdr=0)

# False-color visualization:
colored = ImageBufAlgo.color_map(errmap, 0, "magma")



.. py:method:: float ImageBufAlgo.FLIP_ppd (monitor_distance_m=0.7, screen_width_px=3840, screen_width_m=0.7)

Compute the pixels-per-degree value for use as the `ppd` argument to
:py:meth:`ImageBufAlgo.FLIP_diff`, given the viewing distance in meters,
the screen width in pixels, and the screen width in meters. The default
values correspond to ~67 ppd (a common desktop monitor at typical viewing
distance).

Example:

.. code-block:: python

ppd = ImageBufAlgo.FLIP_ppd(0.5, 2560, 0.6)
errmap = ImageBufAlgo.FLIP_diff(ref, test, ppd=ppd)



.. py:method:: tuple ImageBufAlgo.isConstantColor (src, threshold=0.0, roi=ROI.All, nthreads=0)

If all pixels of `src` within the ROI have the same values (for the
Expand Down
Loading
Loading