Skip to content

Commit

Permalink
feat(IBA): IBA::scale() (AcademySoftwareFoundation#4541)
Browse files Browse the repository at this point in the history
Implements IBA::scale, an algo which takes two images, one of which is
required to be a single channel image, each channel of the other image
gets multiplied by that single channel, per pixel.

Note that this is subtly different from IBA::mul(), which is a
channel-by-channel multiplication, and so doesn't have a provision for
allowing you to scale all channels of one image by the single channel of
a second image.

I have added tests for the C++ code, python code, oiiotool, and the
documentation examples


Signed-off-by: Anton Dukhovnikov <[email protected]>
  • Loading branch information
antond-weta authored and lgritz committed Dec 1, 2024
1 parent f4370a2 commit d523fba
Show file tree
Hide file tree
Showing 18 changed files with 216 additions and 0 deletions.
Binary file added src/doc/figures/scale.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions src/doc/imagebufalgo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,38 @@ Image arithmetic

|
.. doxygenfunction:: scale(const ImageBuf &A, const ImageBuf &B, KWArgs options = {}, ROI roi = {}, int nthreads = 0)
..
Result-as-parameter version:

.. doxygenfunction:: scale(ImageBuf &dst, const ImageBuf &A, const ImageBuf &B, KWArgs options = {}, ROI roi = {}, int nthreads = 0)

Examples:

.. tabs::

.. tab:: C++
.. literalinclude:: ../../testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp
:language: c++
:start-after: BEGIN-imagebufalgo-scale
:end-before: END-imagebufalgo-scale
:dedent: 4

.. tab:: Python
.. literalinclude:: ../../testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py
:language: py
:start-after: BEGIN-imagebufalgo-scale
:end-before: END-imagebufalgo-scale
:dedent: 4

.. code-tab:: bash oiiotool

# Pixel-by-pixel multiplication of all channels of one image by the only channel of another image
oiiotool a.exr mono.exr --scale -o scale.exr

|
.. doxygenfunction:: mul(Image_or_Const A, Image_or_Const B, ROI roi = {}, int nthreads = 0)
..
Expand Down
21 changes: 21 additions & 0 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2507,6 +2507,27 @@ current top image.
Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`).


.. option:: --scale

Replace the *two* top images with a new image that is the pixel-by-pixel
multiplicative product of those images. One of the images must have a single
channel, that channel's pixel value is used to scale all channels of the
other image by.


Example::

# Apply vertical gradient
oiiotool tahoe.jpg --pattern fill:top=0.5:bottom=1 512x384 1 --scale -o scale.jpg
..
.. image:: figures/tahoe-small.jpg
:width: 2.0 in
.. image:: figures/scale.jpg
:width: 2.0 in
|

.. option:: --mul
--mulc <value>
--mulc <value0,value1,value2...>
Expand Down
17 changes: 17 additions & 0 deletions src/doc/pythonbindings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2833,6 +2833,23 @@ Image arithmetic
.. py:method:: ImageBuf ImageBufAlgo.scale (A, B, roi=ROI.All, nthreads=0)
bool ImageBufAlgo.scale (dst, A, B, roi=ROI.All, nthreads=0)
Per-pixel multiply all channels of one image by the single channle of the
other image. One of the input images must have only one channel.
Example:
.. code-block:: python
# Scale one image by the other
buf = ImageBufAlgo.scale (ImageBuf("a.exr"), ImageBuf("mono.exr"))
# Scale one image by the other, in place
ImageBufAlgo.scale (buf, buf, ImageBuf("mono.exr"))
.. py:method:: ImageBuf ImageBufAlgo.mul (A, B, roi=ROI.All, nthreads=0)
bool ImageBufAlgo.mul (dst, A, B, roi=ROI.All, nthreads=0)
Expand Down
15 changes: 15 additions & 0 deletions src/include/OpenImageIO/imagebufalgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,21 @@ ImageBuf OIIO_API abs (const ImageBuf &A, ROI roi={}, int nthreads=0);
bool OIIO_API abs (ImageBuf &dst, const ImageBuf &A, ROI roi={}, int nthreads=0);


/// Compute per-pixel product `A * B`, returning the result image. At least
/// one of `A` and `B` must be a single channel image, whose value is used to
/// scale all channels of the other image.
///
/// @param options
/// Optional ParamValue's that may control the reconstruction.
/// (Reserved for future expansion.)
///
ImageBuf OIIO_API scale (const ImageBuf &A, const ImageBuf &B,
KWArgs options = {}, ROI roi={}, int nthreads=0);
/// Write to an existing image `dst` (allocating if it is uninitialized).
bool OIIO_API scale (ImageBuf &dst, const ImageBuf &A, const ImageBuf &B,
KWArgs options = {}, ROI roi={}, int nthreads=0);


/// Compute per-pixel product `A * B`, returning the result image.
///
/// Either both `A` and `B` are images, or one is an image and the other is
Expand Down
59 changes: 59 additions & 0 deletions src/libOpenImageIO/imagebufalgo_muldiv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,65 @@
OIIO_NAMESPACE_BEGIN


template<class Rtype, class Atype, class Btype>
static bool
scale_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi,
int nthreads)
{
ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
ImageBuf::Iterator<Rtype> r(R, roi);
ImageBuf::ConstIterator<Atype> a(A, roi);
ImageBuf::ConstIterator<Btype> b(B, roi);
for (; !r.done(); ++r, ++a, ++b)
for (int c = roi.chbegin; c < roi.chend; ++c)
r[c] = a[c] * b[0];
});
return true;
}



bool
ImageBufAlgo::scale(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B,
KWArgs options, ROI roi, int nthreads)
{
pvt::LoggedTimer logtime("IBA::scale");
bool ok = false;
if (B.nchannels() == 1) {
if (IBAprep(roi, &dst, &A, &B))
OIIO_DISPATCH_COMMON_TYPES3(ok, "scale", scale_impl,
dst.spec().format, A.spec().format,
B.spec().format, dst, A, B, roi,
nthreads);
} else if (A.nchannels() == 1) {
if (IBAprep(roi, &dst, &A, &B))
OIIO_DISPATCH_COMMON_TYPES3(ok, "scale", scale_impl,
dst.spec().format, B.spec().format,
A.spec().format, dst, B, A, roi,
nthreads);
} else {
dst.errorfmt(
"ImageBufAlgo::scale(): one of the arguments must be a single channel image.");
}

return ok;
}



ImageBuf
ImageBufAlgo::scale(const ImageBuf& A, const ImageBuf& B, KWArgs options,
ROI roi, int nthreads)
{
ImageBuf result;
bool ok = scale(result, A, B, options, roi, nthreads);
if (!ok && !result.has_error())
result.errorfmt("ImageBufAlgo::scale() error");
return result;
}



template<class Rtype, class Atype, class Btype>
static bool
mul_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi,
Expand Down
4 changes: 4 additions & 0 deletions src/oiiotool/oiiotool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3090,6 +3090,7 @@ BINARY_IMAGE_OP(add, ImageBufAlgo::add); // --add
BINARY_IMAGE_OP(sub, ImageBufAlgo::sub); // --sub
BINARY_IMAGE_OP(mul, ImageBufAlgo::mul); // --mul
BINARY_IMAGE_OP(div, ImageBufAlgo::div); // --div
BINARY_IMAGE_OP(scale, ImageBufAlgo::scale); // --scale
BINARY_IMAGE_OP(absdiff, ImageBufAlgo::absdiff); // --absdiff

BINARY_IMAGE_COLOR_OP(addc, ImageBufAlgo::add, 0); // --addc
Expand Down Expand Up @@ -6644,6 +6645,9 @@ Oiiotool::getargs(int argc, char* argv[])
ap.arg("--csub %s:VAL")
.hidden() // Deprecated synonym
.OTACTION(action_subc);
ap.arg("--scale")
.help("Scale all channels of one image by the single channel of another image")
.OTACTION(action_scale);
ap.arg("--mul")
.help("Multiply two images")
.OTACTION(action_mul);
Expand Down
21 changes: 21 additions & 0 deletions src/python/py_imagebufalgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,22 @@ IBA_abs_ret(const ImageBuf& A, ROI roi = ROI::All(), int nthreads = 0)



bool
IBA_scale_images(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B,
ROI roi = ROI::All(), int nthreads = 0)
{
py::gil_scoped_release gil;
return ImageBufAlgo::scale(dst, A, B, {}, roi, nthreads);
}

ImageBuf
IBA_scale_images_ret(const ImageBuf& A, const ImageBuf& B, ROI roi = ROI::All(),
int nthreads = 0)
{
py::gil_scoped_release gil;
return ImageBufAlgo::scale(A, B, {}, roi, nthreads);
}

bool
IBA_mul_color(ImageBuf& dst, const ImageBuf& A, py::object values_tuple,
ROI roi = ROI::All(), int nthreads = 0)
Expand Down Expand Up @@ -2625,6 +2641,11 @@ declare_imagebufalgo(py::module& m)
.def_static("abs", &IBA_abs_ret, "A"_a, "roi"_a = ROI::All(),
"nthreads"_a = 0)

.def_static("scale", &IBA_scale_images, "dst"_a, "A"_a, "B"_a,
"roi"_a = ROI::All(), "nthreads"_a = 0)
.def_static("scale", &IBA_scale_images_ret, "A"_a, "B"_a,
"roi"_a = ROI::All(), "nthreads"_a = 0)

.def_static("mul", &IBA_mul_images, "dst"_a, "A"_a, "B"_a,
"roi"_a = ROI::All(), "nthreads"_a = 0)
.def_static("mul", &IBA_mul_color, "dst"_a, "A"_a, "B"_a,
Expand Down
1 change: 1 addition & 0 deletions testsuite/docs-examples-cpp/ref/out-arm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ example_add
example_sub
example_absdiff
example_abs
example_scale
example_mul
example_div
example_fixNonFinite
Expand Down
1 change: 1 addition & 0 deletions testsuite/docs-examples-cpp/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ example_add
example_sub
example_absdiff
example_abs
example_scale
example_mul
example_div
example_fixNonFinite
Expand Down
2 changes: 2 additions & 0 deletions testsuite/docs-examples-cpp/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
command += run_app("cmake -E copy " + test_source_dir + "/../common/unpremult.tif unpremult.tif")
command += run_app("cmake -E copy " + test_source_dir + "/../common/bayer.png bayer.png")

command += oiio_app("oiiotool") + "--pattern fill:top=0:bottom=1 256x256 1 -o mono.exr > out.txt ;"

# Copy the grid to both a tiled and scanline version
command += oiio_app("iconvert") + "../common/grid.tif --scanline scanline.tif > out.txt ;"
command += oiio_app("iconvert") + "../common/grid.tif --tile 64 64 tiled.tif > out.txt ;"
Expand Down
14 changes: 14 additions & 0 deletions testsuite/docs-examples-cpp/src/docs-examples-imagebufalgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,19 @@ void example_abs()
Abs.write("abs.exr");
}

void example_scale()
{
print("example_scale\n");
// BEGIN-imagebufalgo-scale
// Pixel-by-pixel multiplication of all channels of A by the single channel of B
ImageBuf A("A.exr");
ImageBuf B("mono.exr");
ImageBuf Product = ImageBufAlgo::scale(A, B);

// END-imagebufalgo-scale
Product.write("scale.exr");
}

void example_mul()
{
print("example_mul\n");
Expand Down Expand Up @@ -725,6 +738,7 @@ int main(int /*argc*/, char** /*argv*/)
example_sub();
example_absdiff();
example_abs();
example_scale();
example_mul();
example_div();

Expand Down
1 change: 1 addition & 0 deletions testsuite/docs-examples-python/ref/out-arm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ example_add
example_sub
example_absdiff
example_abs
example_scale
example_mul
example_div
example_fixNonFinite
Expand Down
1 change: 1 addition & 0 deletions testsuite/docs-examples-python/ref/out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ example_add
example_sub
example_absdiff
example_abs
example_scale
example_mul
example_div
example_fixNonFinite
Expand Down
2 changes: 2 additions & 0 deletions testsuite/docs-examples-python/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
command += run_app("cmake -E copy " + test_source_dir + "/../common/unpremult.tif unpremult.tif")
command += run_app("cmake -E copy " + test_source_dir + "/../common/bayer.png bayer.png")

command += oiio_app("oiiotool") + "--pattern fill:top=0:bottom=1 256x256 1 -o mono.exr > out.txt ;"

# Copy the grid to both a tiled and scanline version
command += oiio_app("iconvert") + "../common/grid.tif --scanline scanline.tif > out.txt ;"
command += oiio_app("iconvert") + "../common/grid.tif --tile 64 64 tiled.tif > out.txt ;"
Expand Down
11 changes: 11 additions & 0 deletions testsuite/docs-examples-python/src/docs-examples-imagebufalgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,16 @@ def example_abs():
# END-imagebufalgo-absolute
Abs.write("abs.exr", "half")

def example_scale():
print("example_scale")
# BEGIN-imagebufalgo-scale
# Pixel-by-pixel multiplication of all channels of one image A by the single channel of the other image
A = ImageBuf("A.exr")
B = ImageBuf("mono.exr")
Product = ImageBufAlgo.scale (A, B)
#END-imagebufalgo-scale
Product.write("scale.exr", "half")

def example_mul():
print("example_mul")
# BEGIN-imagebufalgo-mul
Expand Down Expand Up @@ -626,6 +636,7 @@ def example_make_texture():
example_sub()
example_absdiff()
example_abs()
example_scale()
example_mul()
example_div()

Expand Down
5 changes: 5 additions & 0 deletions testsuite/oiiotool/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
+ " --sub -d half -o sub.exr")
command += oiiotool ("--pattern constant:color=.1,.2,.3 64x64+0+0 3 "
+ " --subc 0.1,0.1,0.1 -d half -o subc.exr")

# Test -- scale
command += oiiotool ("--pattern fill:topleft=0,0,1:topright=0,1,0:bottomleft=1,0,1:bottomright=1,1,0 64x64 3"
+ " --pattern fill:top=0:bottom=1 64x64 1"
+ " --scale -o scale.exr")

# test --mul of images
command += oiiotool ("grey64.exr -pattern constant:color=1.5,1,0.5 64x64 3 --mul -o mul.exr")
Expand Down
9 changes: 9 additions & 0 deletions testsuite/python-imagebufalgo/src/test_imagebufalgo.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ def test_iba (func, *args, **kwargs) :
b = test_iba (ImageBufAlgo.absdiff, a, (0.2,0.2,0.2))
write (b, "absdiff.exr", oiio.HALF)
a = ImageBuf()

# scale
a = ImageBuf(ImageSpec(128, 128, 3, oiio.HALF))
ImageBufAlgo.fill(a, topleft = (0, 0, 1), topright = (0, 1, 0),
bottomleft = (1, 0, 1), bottomright = (1, 1, 0))
b = ImageBuf(ImageSpec(128, 128, 1, oiio.HALF))
ImageBufAlgo.fill(a, top = 0, bottom = 1)
b = test_iba(ImageBufAlgo.scale, a, b)
a = ImageBuf()

# mul
b = ImageBufAlgo.mul (gray128, 1.5)
Expand Down

0 comments on commit d523fba

Please sign in to comment.