diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index ceb393b243..8805d74426 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -1028,6 +1028,34 @@ ImageBuf OIIO_API pow (const ImageBuf &A, cspan B, bool OIIO_API pow (ImageBuf &dst, const ImageBuf &A, cspan B, ROI roi={}, int nthreads=0); +/// Normalize a 3D vector texture (i.e., divide each pixel by its length). +/// This function assumes a 3-channel image that represents a 3-vector, or a +/// 4-channel image that represents a 3-vector plus an alpha value. If an +/// alpha channel is present, its value is merely copied, and is not part of +/// the normalization computation. If the destination has no alpha channel but +/// the sources do, the alpha channel will be dropped. +/// +/// `inCenter` and `outCenter` define the pixel value that corresponds to a +/// 0.0 vector value for input and output, respectively. `scale` defines the +/// scale factor to apply to the normalized vectors. +/// +/// Thus, if the input image encodes vector components into [0,1] range pixel +/// values so that a pixel value 0.5 indicates a 0-length vector, then you +/// should use `inCenter=0.5`, whereas if they are already using the full +/// range (0.0 is encoded as 0.0), then you want `inCenter=0.0`. Similarly, if +/// you want the output normalized vectors to be in the range [0,1], use +/// `outCenter=0.5` and `scale=0.5`, but if you want them to be in the range +/// [-1,1], use `outCenter=0.0` and `scale=1.0` (this probably will only work +/// if you intend to write the results in `float` or `half` format). +/// +bool OIIO_API normalize(ImageBuf& dst, const ImageBuf& A, float inCenter=0.0f, + float outCenter=0.0f, float scale=1.0f, + ROI roi={}, int nthreads=0); + +ImageBuf OIIO_API normalize(const ImageBuf& A, float inCenter=0.0f, + float outCenter=0.0, float scale=1.0f, + ROI roi={}, int nthreads=0); + /// Converts a multi-channel image into a one-channel image via a weighted /// sum of channels: diff --git a/src/libOpenImageIO/imagebufalgo_pixelmath.cpp b/src/libOpenImageIO/imagebufalgo_pixelmath.cpp index 6650a32a18..96c1f18b01 100644 --- a/src/libOpenImageIO/imagebufalgo_pixelmath.cpp +++ b/src/libOpenImageIO/imagebufalgo_pixelmath.cpp @@ -433,6 +433,71 @@ ImageBufAlgo::pow(const ImageBuf& A, cspan b, ROI roi, int nthreads) return result; } +template +static bool +normalize_impl(ImageBuf& R, const ImageBuf& A, float inCenter, float outCenter, + float scale, ROI roi, int nthreads) +{ + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + ImageBuf::ConstIterator a(A, roi); + for (ImageBuf::Iterator r(R, roi); !r.done(); ++r, ++a) { + float x = a[0] - inCenter; + float y = a[1] - inCenter; + float z = a[2] - inCenter; + + float length = std::sqrt(x * x + y * y + z * z); + + float s = (length > 0.0f) ? scale / length : 0.0f; + + r[0] = x * s + outCenter; + r[1] = y * s + outCenter; + r[2] = z * s + outCenter; + + if (R.spec().nchannels == 4) { + r[3] = a[3]; + } + } + }); + return true; +} + +bool +ImageBufAlgo::normalize(ImageBuf& dst, const ImageBuf& src, float inCenter, + float outCenter, float scale, ROI roi, int nthreads) +{ + if (!ImageBufAlgo::IBAprep(roi, &dst, &src)) + return false; + + if (src.spec().nchannels != 3 && src.spec().nchannels != 4) { + src.errorfmt("normalize can only handle 3- or 4-channel images"); + return false; + } + if (src.spec().nchannels < dst.spec().nchannels) { + dst.errorfmt( + "destination buffer can`t have more channels than the source"); + return false; + } + + bool ok; + OIIO_DISPATCH_COMMON_TYPES(ok, "normalize", normalize_impl, + dst.spec().format, dst, src, inCenter, outCenter, + scale, roi, nthreads); + + return ok; +} + +ImageBuf +ImageBufAlgo::normalize(const ImageBuf& A, float inCenter, float outCenter, + float scale, ROI roi, int nthreads) +{ + ImageBuf result; + bool ok = ImageBufAlgo::normalize(result, A, inCenter, outCenter, scale, + roi, nthreads); + if (!ok && !result.has_error()) + result.errorfmt("normalize error"); + return result; +} + template diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index d0004d0c4b..562c086796 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -4655,8 +4655,6 @@ OIIOTOOL_OP(unsharp, 1, [&](OiiotoolOp& op, span img) { threshold); }); - - UNARY_IMAGE_OP(laplacian, ImageBufAlgo::laplacian); // --laplacian UNARY_IMAGE_OP(fft, ImageBufAlgo::fft); // --fft UNARY_IMAGE_OP(ifft, ImageBufAlgo::ifft); // --ifft @@ -4665,6 +4663,17 @@ UNARY_IMAGE_OP(unpolar, ImageBufAlgo::polar_to_complex); // --unpolar +// --normalize +OIIOTOOL_OP(normalize, 1, [&](OiiotoolOp& op, span img) { + float inCenter = op.options().get_float("incenter", 0.0f); + float outCenter = op.options().get_float("outcenter", 0.0f); + float scale = op.options().get_float("scale", 1.0f); + return ImageBufAlgo::normalize(*img[0], *img[1], inCenter, outCenter, + scale); +}); + + + // --fixnan void action_fixnan(Oiiotool& ot, cspan argv) @@ -6864,6 +6873,9 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("--laplacian") .help("Laplacian filter the image") .OTACTION(action_laplacian); + ap.arg("--normalize") + .help("Normalize the image (options: incenter=0.5, outcenter=0.5, scale=0.5)") + .OTACTION(action_normalize); ap.arg("--fft") .help("Take the FFT of the image") .OTACTION(action_fft); diff --git a/src/python/py_imagebufalgo.cpp b/src/python/py_imagebufalgo.cpp index 6ae4b58fa1..f8c9db0abf 100644 --- a/src/python/py_imagebufalgo.cpp +++ b/src/python/py_imagebufalgo.cpp @@ -1699,6 +1699,28 @@ IBA_complex_to_polar_ret(const ImageBuf& src, ROI roi, int nthreads) +bool +IBA_normalize(ImageBuf& dst, const ImageBuf& src, float inCenter, + float outCenter, float scale, ROI roi, int nthreads) +{ + py::gil_scoped_release gil; + return ImageBufAlgo::normalize(dst, src, inCenter, outCenter, scale, roi, + nthreads); +} + + + +ImageBuf +IBA_normalize_ret(const ImageBuf& src, float inCenter, float outCenter, + float scale, ROI roi, int nthreads) +{ + py::gil_scoped_release gil; + return ImageBufAlgo::normalize(src, inCenter, outCenter, scale, roi, + nthreads); +} + + + bool IBA_fillholes_pushpull(ImageBuf& dst, const ImageBuf& src, ROI roi, int nthreads) @@ -2959,6 +2981,13 @@ declare_imagebufalgo(py::module& m) "mode"_a = ImageBufAlgo::NONFINITE_BOX3, "roi"_a = ROI::All(), "nthreads"_a = 0) + .def_static("normalize", &IBA_normalize, "dst"_a, "src"_a, + "inCenter"_a = 0.0f, "outCenter"_a = 0.0f, "scale"_a = 1.0f, + "roi"_a = ROI::All(), "nthreads"_a = 0) + .def_static("normalize", &IBA_normalize_ret, "src"_a, + "inCenter"_a = 0.0f, "outCenter"_a = 0.0f, "scale"_a = 1.0f, + "roi"_a = ROI::All(), "nthreads"_a = 0) + .def_static("fillholes_pushpull", &IBA_fillholes_pushpull, "dst"_a, "src"_a, "roi"_a = ROI::All(), "nthreads"_a = 0) .def_static("fillholes_pushpull", &IBA_fillholes_pushpull_ret, "src"_a, diff --git a/testsuite/common/vectorschart_raw.tif b/testsuite/common/vectorschart_raw.tif new file mode 100644 index 0000000000..59713d04f6 Binary files /dev/null and b/testsuite/common/vectorschart_raw.tif differ diff --git a/testsuite/common/vectorschart_raw_xyza.exr b/testsuite/common/vectorschart_raw_xyza.exr new file mode 100644 index 0000000000..210d19bc16 Binary files /dev/null and b/testsuite/common/vectorschart_raw_xyza.exr differ diff --git a/testsuite/python-imagebufalgo/ref/normalize_flfl.exr b/testsuite/python-imagebufalgo/ref/normalize_flfl.exr new file mode 100644 index 0000000000..b39bb81ab9 Binary files /dev/null and b/testsuite/python-imagebufalgo/ref/normalize_flfl.exr differ diff --git a/testsuite/python-imagebufalgo/ref/normalize_flui.tif b/testsuite/python-imagebufalgo/ref/normalize_flui.tif new file mode 100644 index 0000000000..c646fb5b22 Binary files /dev/null and b/testsuite/python-imagebufalgo/ref/normalize_flui.tif differ diff --git a/testsuite/python-imagebufalgo/ref/normalize_flui_na.tif b/testsuite/python-imagebufalgo/ref/normalize_flui_na.tif new file mode 100644 index 0000000000..c646fb5b22 Binary files /dev/null and b/testsuite/python-imagebufalgo/ref/normalize_flui_na.tif differ diff --git a/testsuite/python-imagebufalgo/ref/normalize_uifl.exr b/testsuite/python-imagebufalgo/ref/normalize_uifl.exr new file mode 100644 index 0000000000..113c7ad2eb Binary files /dev/null and b/testsuite/python-imagebufalgo/ref/normalize_uifl.exr differ diff --git a/testsuite/python-imagebufalgo/ref/normalize_uiui.tif b/testsuite/python-imagebufalgo/ref/normalize_uiui.tif new file mode 100644 index 0000000000..5b10e3fa89 Binary files /dev/null and b/testsuite/python-imagebufalgo/ref/normalize_uiui.tif differ diff --git a/testsuite/python-imagebufalgo/ref/out-freetype2.4.11-py2.7-pybind2.3.txt b/testsuite/python-imagebufalgo/ref/out-freetype2.4.11-py2.7-pybind2.3.txt index 35d0317238..5629f32cda 100644 --- a/testsuite/python-imagebufalgo/ref/out-freetype2.4.11-py2.7-pybind2.3.txt +++ b/testsuite/python-imagebufalgo/ref/out-freetype2.4.11-py2.7-pybind2.3.txt @@ -172,6 +172,16 @@ Comparing "resample.tif" and "../../../testsuite/oiiotool-xform/ref/resample.tif PASS Comparing "fit.tif" and "../../../testsuite/oiiotool-xform/ref/fit.tif" PASS +Comparing "normalize_uiui.tif" and "ref/normalize_uiui.tif" +PASS +Comparing "normalize_uifl.exr" and "ref/normalize_uifl.exr" +PASS +Comparing "normalize_flfl.exr" and "ref/normalize_flfl.exr" +PASS +Comparing "normalize_flui.tif" and "ref/normalize_flui.tif" +PASS +Comparing "normalize_flui_na.tif" and "ref/normalize_flui_na.tif" +PASS Comparing "bsplinekernel.exr" and "../../../testsuite/oiiotool/ref/bsplinekernel.exr" PASS Comparing "bspline-blur.tif" and "../../../testsuite/oiiotool/ref/bspline-blur.tif" diff --git a/testsuite/python-imagebufalgo/ref/out-freetype2.4.11.txt b/testsuite/python-imagebufalgo/ref/out-freetype2.4.11.txt index e8f06cb7b3..13e9d9f792 100644 --- a/testsuite/python-imagebufalgo/ref/out-freetype2.4.11.txt +++ b/testsuite/python-imagebufalgo/ref/out-freetype2.4.11.txt @@ -172,6 +172,16 @@ Comparing "resample.tif" and "../../../testsuite/oiiotool-xform/ref/resample.tif PASS Comparing "fit.tif" and "../../../testsuite/oiiotool-xform/ref/fit.tif" PASS +Comparing "normalize_uiui.tif" and "ref/normalize_uiui.tif" +PASS +Comparing "normalize_uifl.exr" and "ref/normalize_uifl.exr" +PASS +Comparing "normalize_flfl.exr" and "ref/normalize_flfl.exr" +PASS +Comparing "normalize_flui.tif" and "ref/normalize_flui.tif" +PASS +Comparing "normalize_flui_na.tif" and "ref/normalize_flui_na.tif" +PASS Comparing "bsplinekernel.exr" and "../../../testsuite/oiiotool/ref/bsplinekernel.exr" PASS Comparing "bspline-blur.tif" and "../../../testsuite/oiiotool/ref/bspline-blur.tif" diff --git a/testsuite/python-imagebufalgo/ref/out-python2-alt.txt b/testsuite/python-imagebufalgo/ref/out-python2-alt.txt index d3dfb36240..2e20341393 100644 --- a/testsuite/python-imagebufalgo/ref/out-python2-alt.txt +++ b/testsuite/python-imagebufalgo/ref/out-python2-alt.txt @@ -172,6 +172,16 @@ Comparing "resample.tif" and "../../../testsuite/oiiotool-xform/ref/resample.tif PASS Comparing "fit.tif" and "../../../testsuite/oiiotool-xform/ref/fit.tif" PASS +Comparing "normalize_uiui.tif" and "ref/normalize_uiui.tif" +PASS +Comparing "normalize_uifl.exr" and "ref/normalize_uifl.exr" +PASS +Comparing "normalize_flfl.exr" and "ref/normalize_flfl.exr" +PASS +Comparing "normalize_flui.tif" and "ref/normalize_flui.tif" +PASS +Comparing "normalize_flui_na.tif" and "ref/normalize_flui_na.tif" +PASS Comparing "bsplinekernel.exr" and "../../../testsuite/oiiotool/ref/bsplinekernel.exr" PASS Comparing "bspline-blur.tif" and "../../../testsuite/oiiotool/ref/bspline-blur.tif" diff --git a/testsuite/python-imagebufalgo/ref/out-python3-freetype2.4.11.txt b/testsuite/python-imagebufalgo/ref/out-python3-freetype2.4.11.txt index d0e252a2c9..d857d072ec 100644 --- a/testsuite/python-imagebufalgo/ref/out-python3-freetype2.4.11.txt +++ b/testsuite/python-imagebufalgo/ref/out-python3-freetype2.4.11.txt @@ -172,6 +172,16 @@ Comparing "resample.tif" and "../../../testsuite/oiiotool-xform/ref/resample.tif PASS Comparing "fit.tif" and "../../../testsuite/oiiotool-xform/ref/fit.tif" PASS +Comparing "normalize_uiui.tif" and "ref/normalize_uiui.tif" +PASS +Comparing "normalize_uifl.exr" and "ref/normalize_uifl.exr" +PASS +Comparing "normalize_flfl.exr" and "ref/normalize_flfl.exr" +PASS +Comparing "normalize_flui.tif" and "ref/normalize_flui.tif" +PASS +Comparing "normalize_flui_na.tif" and "ref/normalize_flui_na.tif" +PASS Comparing "bsplinekernel.exr" and "../../../testsuite/oiiotool/ref/bsplinekernel.exr" PASS Comparing "bspline-blur.tif" and "../../../testsuite/oiiotool/ref/bspline-blur.tif" diff --git a/testsuite/python-imagebufalgo/ref/out-python3.txt b/testsuite/python-imagebufalgo/ref/out-python3.txt index cf16cb3c70..4c96630ed9 100644 --- a/testsuite/python-imagebufalgo/ref/out-python3.txt +++ b/testsuite/python-imagebufalgo/ref/out-python3.txt @@ -172,6 +172,16 @@ Comparing "resample.tif" and "../../../testsuite/oiiotool-xform/ref/resample.tif PASS Comparing "fit.tif" and "../../../testsuite/oiiotool-xform/ref/fit.tif" PASS +Comparing "normalize_uiui.tif" and "ref/normalize_uiui.tif" +PASS +Comparing "normalize_uifl.exr" and "ref/normalize_uifl.exr" +PASS +Comparing "normalize_flfl.exr" and "ref/normalize_flfl.exr" +PASS +Comparing "normalize_flui.tif" and "ref/normalize_flui.tif" +PASS +Comparing "normalize_flui_na.tif" and "ref/normalize_flui_na.tif" +PASS Comparing "bsplinekernel.exr" and "../../../testsuite/oiiotool/ref/bsplinekernel.exr" PASS Comparing "bspline-blur.tif" and "../../../testsuite/oiiotool/ref/bspline-blur.tif" diff --git a/testsuite/python-imagebufalgo/ref/out.txt b/testsuite/python-imagebufalgo/ref/out.txt index b5f5ab928a..e945cc9c32 100644 --- a/testsuite/python-imagebufalgo/ref/out.txt +++ b/testsuite/python-imagebufalgo/ref/out.txt @@ -172,6 +172,16 @@ Comparing "resample.tif" and "../../../testsuite/oiiotool-xform/ref/resample.tif PASS Comparing "fit.tif" and "../../../testsuite/oiiotool-xform/ref/fit.tif" PASS +Comparing "normalize_uiui.tif" and "ref/normalize_uiui.tif" +PASS +Comparing "normalize_uifl.exr" and "ref/normalize_uifl.exr" +PASS +Comparing "normalize_flfl.exr" and "ref/normalize_flfl.exr" +PASS +Comparing "normalize_flui.tif" and "ref/normalize_flui.tif" +PASS +Comparing "normalize_flui_na.tif" and "ref/normalize_flui_na.tif" +PASS Comparing "bsplinekernel.exr" and "../../../testsuite/oiiotool/ref/bsplinekernel.exr" PASS Comparing "bspline-blur.tif" and "../../../testsuite/oiiotool/ref/bspline-blur.tif" diff --git a/testsuite/python-imagebufalgo/run.py b/testsuite/python-imagebufalgo/run.py index 58bb0ce264..56a0fa292b 100755 --- a/testsuite/python-imagebufalgo/run.py +++ b/testsuite/python-imagebufalgo/run.py @@ -50,6 +50,9 @@ "contrast-sigmoid5.tif", "saturate-0.tif", "saturate-2.tif", "resize.tif", "resample.tif", "fit.tif", + "normalize_uiui.tif", "normalize_uifl.exr", + "normalize_flfl.exr", "normalize_flui.tif", + "normalize_flui_na.tif", "bsplinekernel.exr", "bspline-blur.tif", "tahoe-median.tif", "dilate.tif", "erode.tif", "unsharp.tif", "unsharp-median.tif", "tahoe-laplacian.tif", diff --git a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py index cf03e6e168..5acb070458 100755 --- a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py +++ b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py @@ -264,6 +264,23 @@ def test_iba (func, *args, **kwargs) : b = test_iba (ImageBufAlgo.invert, a) write (b, "invert.tif", oiio.UINT8) + # normalize + a = ImageBuf (OIIO_TESTSUITE_ROOT+"/common/vectorschart_raw.tif") + b = test_iba (ImageBufAlgo.normalize, a, 0.5, 0.5, 0.5) + write (b, "normalize_uiui.tif", oiio.UINT16) + b = test_iba (ImageBufAlgo.normalize, a, 0.5, 0.0, 1.0) + write (b, "normalize_uifl.exr", oiio.HALF) + + a = ImageBuf (OIIO_TESTSUITE_ROOT+"/common/vectorschart_raw_xyza.exr") + b = test_iba (ImageBufAlgo.normalize, a, 0.0, 0.0, 1.0) + write (b, "normalize_flfl.exr", oiio.HALF) + b = test_iba (ImageBufAlgo.normalize, a, 0.0, 0.5, 0.5) + write (b, "normalize_flui.tif", oiio.UINT16) + b = ImageBuf() + b.specmod().nchannels = 3 + b = test_iba (ImageBufAlgo.normalize, a, 0.0, 0.5, 0.5) + write (b, "normalize_flui_na.tif", oiio.UINT16) + # pow b = ImageBufAlgo.pow (gray128, 2) write (b, "cpow1.exr")